ronin-nmap 0.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.document +4 -0
  3. data/.github/workflows/ruby.yml +47 -0
  4. data/.gitignore +14 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +15 -0
  7. data/.ruby-version +1 -0
  8. data/.yardopts +1 -0
  9. data/COPYING.txt +165 -0
  10. data/ChangeLog.md +10 -0
  11. data/Gemfile +42 -0
  12. data/README.md +238 -0
  13. data/Rakefile +43 -0
  14. data/bin/ronin-nmap +32 -0
  15. data/data/completions/ronin-nmap +79 -0
  16. data/data/templates/script.rb.erb +58 -0
  17. data/gemspec.yml +42 -0
  18. data/lib/ronin/nmap/cli/command.rb +40 -0
  19. data/lib/ronin/nmap/cli/commands/completion.rb +61 -0
  20. data/lib/ronin/nmap/cli/commands/convert.rb +108 -0
  21. data/lib/ronin/nmap/cli/commands/dump.rb +293 -0
  22. data/lib/ronin/nmap/cli/commands/grep.rb +378 -0
  23. data/lib/ronin/nmap/cli/commands/import.rb +79 -0
  24. data/lib/ronin/nmap/cli/commands/new.rb +226 -0
  25. data/lib/ronin/nmap/cli/commands/print.rb +133 -0
  26. data/lib/ronin/nmap/cli/commands/scan.rb +233 -0
  27. data/lib/ronin/nmap/cli/filtering_options.rb +355 -0
  28. data/lib/ronin/nmap/cli/importable.rb +68 -0
  29. data/lib/ronin/nmap/cli/port_list.rb +102 -0
  30. data/lib/ronin/nmap/cli.rb +50 -0
  31. data/lib/ronin/nmap/converter.rb +114 -0
  32. data/lib/ronin/nmap/converters/csv.rb +162 -0
  33. data/lib/ronin/nmap/converters/json.rb +562 -0
  34. data/lib/ronin/nmap/converters.rb +54 -0
  35. data/lib/ronin/nmap/exceptions.rb +47 -0
  36. data/lib/ronin/nmap/importer.rb +369 -0
  37. data/lib/ronin/nmap/root.rb +28 -0
  38. data/lib/ronin/nmap/version.rb +26 -0
  39. data/lib/ronin/nmap.rb +223 -0
  40. data/man/ronin-nmap-completion.1 +76 -0
  41. data/man/ronin-nmap-completion.1.md +78 -0
  42. data/man/ronin-nmap-convert.1 +33 -0
  43. data/man/ronin-nmap-convert.1.md +36 -0
  44. data/man/ronin-nmap-dump.1 +141 -0
  45. data/man/ronin-nmap-dump.1.md +119 -0
  46. data/man/ronin-nmap-grep.1 +33 -0
  47. data/man/ronin-nmap-grep.1.md +36 -0
  48. data/man/ronin-nmap-import.1 +52 -0
  49. data/man/ronin-nmap-import.1.md +57 -0
  50. data/man/ronin-nmap-new.1 +81 -0
  51. data/man/ronin-nmap-new.1.md +73 -0
  52. data/man/ronin-nmap-print.1 +61 -0
  53. data/man/ronin-nmap-print.1.md +63 -0
  54. data/man/ronin-nmap-scan.1 +86 -0
  55. data/man/ronin-nmap-scan.1.md +84 -0
  56. data/man/ronin-nmap.1 +58 -0
  57. data/man/ronin-nmap.1.md +57 -0
  58. data/ronin-nmap.gemspec +62 -0
  59. data/scripts/setup +161 -0
  60. metadata +168 -0
@@ -0,0 +1,355 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-nmap - A Ruby library for automating nmap and importing nmap scans.
4
+ #
5
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
6
+ #
7
+ # ronin-nmap is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-nmap is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-nmap. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/nmap/cli/port_list'
22
+
23
+ module Ronin
24
+ module Nmap
25
+ class CLI
26
+ #
27
+ # Mixin which adds nmap target filtering options to commands.
28
+ #
29
+ module FilteringOptions
30
+ #
31
+ # Adds filtering options to the command class including
32
+ # {FilteringOptions}.
33
+ #
34
+ # @param [Class<Command>] command
35
+ # The command class including {FilteringOptions}.
36
+ #
37
+ def self.included(command)
38
+ command.option :ip, value: {
39
+ type: String,
40
+ usage: 'IP'
41
+ },
42
+ desc: 'Filters the targets by IP' do |ip|
43
+ @with_ips << ip
44
+ end
45
+
46
+ command.option :ip_range, value: {
47
+ type: String,
48
+ usage: 'CIDR'
49
+ },
50
+ desc: 'Filter the targets by IP range' do |ip_range|
51
+ @with_ip_ranges << IPAddr.new(ip_range)
52
+ end
53
+
54
+ command.option :domain, value: {
55
+ type: String,
56
+ usage: 'DOMAIN'
57
+ },
58
+ desc: 'Filters the targets by domain' do |domain|
59
+ @with_domains << domain
60
+ end
61
+
62
+ command.option :with_os, value: {
63
+ type: String,
64
+ usage: 'OS'
65
+ },
66
+ desc: 'Filters the targets by OS' do |os|
67
+ @with_oses << os.to_sym
68
+ end
69
+
70
+ command.option :with_ports, value: {
71
+ type: /\A(?:\d+|\d+-\d+)(?:,(?:\d+|\d+-\d+))*\z/,
72
+ usage: '{PORT | PORT1-PORT2},...'
73
+ },
74
+ desc: 'Filter targets by port numbers' do |ports|
75
+ @with_ports << PortList.parse(ports)
76
+ end
77
+
78
+ command.option :with_service, value: {
79
+ type: /\A[a-z]+[a-z0-9_+-]*\z/,
80
+ usage: 'SERVICE[,...]'
81
+ },
82
+ desc: 'Filters targets by service' do |service|
83
+ @with_services << service
84
+ end
85
+
86
+ command.option :with_script, value: {
87
+ type: /\A[a-z][a-z0-9-]*\z/,
88
+ usage: 'SCRIPT[,...]'
89
+ },
90
+ desc: 'Filters targets with the script' do |script|
91
+ @with_scripts << script
92
+ end
93
+
94
+ command.option :with_script_output, value: {
95
+ type: String,
96
+ usage: 'STRING'
97
+ },
98
+ desc: 'Filters targets containing the script output' do |string|
99
+ @with_script_output << string
100
+ end
101
+
102
+ command.option :with_script_regex, value: {
103
+ type: Regexp,
104
+ usage: '/REGEX/'
105
+ },
106
+ desc: 'Filters targets containing the script output' do |regexp|
107
+ @with_script_output << regexp
108
+ end
109
+ end
110
+
111
+ # @return [Set<String>]
112
+ attr_reader :with_ips
113
+
114
+ # @return [Set<IPAddr>]
115
+ attr_reader :with_ip_ranges
116
+
117
+ # @return [Set<String>]
118
+ attr_reader :with_domains
119
+
120
+ # @return [Set<String>]
121
+ attr_reader :with_oses
122
+
123
+ # @return [Set<PortList>]
124
+ attr_reader :with_ports
125
+
126
+ # @return [Set<String>]
127
+ attr_reader :with_services
128
+
129
+ # @return [Set<String>]
130
+ attr_reader :with_scripts
131
+
132
+ # @return [Set<String, Regexp>]
133
+ attr_reader :with_script_output
134
+
135
+ #
136
+ # Initializes the command.
137
+ #
138
+ # @param [Hash{Symbol => String}] kwargs
139
+ # Additional keywords for the command.
140
+ #
141
+ def initialize(**kwargs)
142
+ super(**kwargs)
143
+
144
+ @with_ips = Set.new
145
+ @with_ip_ranges = Set.new
146
+ @with_domains = Set.new
147
+ @with_oses = Set.new
148
+ @with_ports = Set.new
149
+ @with_services = Set.new
150
+ @with_scripts = Set.new
151
+ @with_script_output = Set.new
152
+ end
153
+
154
+ #
155
+ # Filters the nmap scan targets.
156
+ #
157
+ # @param [::Nmap::XML] xml
158
+ # The parsed nmap xml data to filter.
159
+ #
160
+ # @return [Enumerator::Lazy]
161
+ # A lazy enumerator of the filtered targets.
162
+ #
163
+ def filter_targets(xml)
164
+ targets = xml.each_up_host.lazy
165
+
166
+ unless @with_ips.empty?
167
+ targets = filter_targets_by_ip(targets)
168
+ end
169
+
170
+ unless @with_ip_ranges.empty?
171
+ targets = filter_targets_by_ip_range(targets)
172
+ end
173
+
174
+ unless @with_domains.empty?
175
+ targets = filter_targets_by_domain(targets)
176
+ end
177
+
178
+ unless @with_oses.empty?
179
+ targets = filter_targets_by_os(targets)
180
+ end
181
+
182
+ unless @with_ports.empty?
183
+ targets = filter_targets_by_port(targets)
184
+ end
185
+
186
+ unless @with_services.empty?
187
+ targets = filter_targets_by_scripts(targets)
188
+ end
189
+
190
+ unless @with_scripts.empty?
191
+ targets = filter_targets_by_script(targets)
192
+ end
193
+
194
+ unless @with_script_output.empty?
195
+ targets = filter_targets_by_script_output(targets)
196
+ end
197
+
198
+ return targets
199
+ end
200
+
201
+ #
202
+ # Filters the targets by IP address.
203
+ #
204
+ # @param [Enumerator::Lazy] targets
205
+ # The targets to filter.
206
+ #
207
+ # @return [Enumerator::Lazy]
208
+ # A lazy enumerator of the filtered targets.
209
+ #
210
+ def filter_targets_by_ip(targets)
211
+ targets.filter do |host|
212
+ @with_ips.include?(host.address)
213
+ end
214
+ end
215
+
216
+ #
217
+ # Filters the targets by an IP rangeo.
218
+ #
219
+ # @param [Enumerator::Lazy] targets
220
+ # The targets to filter.
221
+ #
222
+ # @return [Enumerator::Lazy]
223
+ # A lazy enumerator of the filtered targets.
224
+ #
225
+ def filter_targets_by_ip_range(targets)
226
+ targets.filter do |host|
227
+ @with_ip_ranges.any? do |ip_range|
228
+ ip_range.include?(host.address)
229
+ end
230
+ end
231
+ end
232
+
233
+ #
234
+ # Filters the targets by a domain.
235
+ #
236
+ # @param [Enumerator::Lazy] targets
237
+ # The targets to filter.
238
+ #
239
+ # @return [Enumerator::Lazy]
240
+ # A lazy enumerator of the filtered targets.
241
+ #
242
+ def filter_targets_by_domain(targets)
243
+ regexp = Regexp.union(
244
+ @with_domains.map { |domain|
245
+ escaped_domain = Regexp.escape(domain)
246
+
247
+ /\.#{escaped_domain}\z|\A#{escaped_domain}\z/
248
+ }
249
+ )
250
+
251
+ targets.filter do |host|
252
+ if (hostname = host.hostname)
253
+ hostname.name =~ regexp
254
+ end
255
+ end
256
+ end
257
+
258
+ #
259
+ # Filters the targets by OS.
260
+ #
261
+ # @param [Enumerator::Lazy] targets
262
+ # The targets to filter.
263
+ #
264
+ # @return [Enumerator::Lazy]
265
+ # A lazy enumerator of the filtered targets.
266
+ #
267
+ def filter_targets_by_os(targets)
268
+ targets.filter do |host|
269
+ if (os = host.os)
270
+ os.each_class.any? do |os_class|
271
+ @with_oses.include?(os_class.family)
272
+ end
273
+ end
274
+ end
275
+ end
276
+
277
+ #
278
+ # Filters the targets by port number.
279
+ #
280
+ # @param [Enumerator::Lazy] targets
281
+ # The targets to filter.
282
+ #
283
+ # @return [Enumerator::Lazy]
284
+ # A lazy enumerator of the filtered targets.
285
+ #
286
+ def filter_targets_by_port(targets)
287
+ targets.filter do |host|
288
+ host.each_open_port.any? do |port|
289
+ @with_ports.any? do |port_list|
290
+ port_list.include?(port.number)
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ #
297
+ # Filters the targets by service name.
298
+ #
299
+ # @param [Enumerator::Lazy] targets
300
+ # The targets to filter.
301
+ #
302
+ # @return [Enumerator::Lazy]
303
+ # A lazy enumerator of the filtered targets.
304
+ #
305
+ def filter_targets_by_service(targets)
306
+ targets.filter do |host|
307
+ host.each_open_port.any? do |port|
308
+ if (service = port.service)
309
+ @with_services.include?(service.name)
310
+ end
311
+ end
312
+ end
313
+ end
314
+
315
+ #
316
+ # Filters the targets by script IDs.
317
+ #
318
+ # @param [Enumerator::Lazy] targets
319
+ # The targets to filter.
320
+ #
321
+ # @return [Enumerator::Lazy]
322
+ # A lazy enumerator of the filtered targets.
323
+ #
324
+ def filter_targets_by_script(targets)
325
+ targets.filter do |host|
326
+ host.each_open_port.any? do |port|
327
+ @with_scripts.intersect?(port.scripts.keys)
328
+ end
329
+ end
330
+ end
331
+
332
+ #
333
+ # Filters the targets by script output.
334
+ #
335
+ # @param [Enumerator::Lazy] targets
336
+ # The targets to filter.
337
+ #
338
+ # @return [Enumerator::Lazy]
339
+ # A lazy enumerator of the filtered targets.
340
+ #
341
+ def filter_targets_by_script_output(targets)
342
+ regexp = Regexp.union(@with_script_output.to_a)
343
+
344
+ targets.filter do |host|
345
+ host.each_open_port.any? do |port|
346
+ port.scripts.each_value.any? do |script|
347
+ script.output =~ regexp
348
+ end
349
+ end
350
+ end
351
+ end
352
+ end
353
+ end
354
+ end
355
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-nmap - A Ruby library for automating nmap and importing nmap scans.
4
+ #
5
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
6
+ #
7
+ # ronin-nmap is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-nmap is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-nmap. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/nmap/importer'
22
+ require 'ronin/db/cli/database_options'
23
+ require 'ronin/db/cli/printing'
24
+ require 'ronin/core/cli/logging'
25
+
26
+ module Ronin
27
+ module Nmap
28
+ class CLI
29
+ #
30
+ # Mixin module which adds the ability to import nmap XML into the
31
+ # [ronin-db] database.
32
+ #
33
+ # [ronin-db]: https://github.com/ronin-rb/ronin-db#readme
34
+ #
35
+ module Importable
36
+ include DB::CLI::Printing
37
+ include Core::CLI::Logging
38
+
39
+ #
40
+ # Includes `Ronin::DB::CLI::DatabaseOptions` into the including command
41
+ # class.
42
+ #
43
+ # @param [Class<Command>] command
44
+ # The command class including {Importable}.
45
+ #
46
+ def self.included(command)
47
+ command.include DB::CLI::DatabaseOptions
48
+ end
49
+
50
+ #
51
+ # Imports an nmap XML file into the [ronin-db] database.
52
+ #
53
+ # [ronin-db]: https://github.com/ronin-rb/ronin-db#readme
54
+ #
55
+ # @param [String] xml_file
56
+ # The path to the nmap XML file to import.
57
+ #
58
+ def import_file(xml_file)
59
+ Importer.import_file(xml_file) do |record|
60
+ if (type = record_type(record))
61
+ log_info "Imported #{type}: #{record}"
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-nmap - A Ruby library for automating nmap and importing nmap scans.
4
+ #
5
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
6
+ #
7
+ # ronin-nmap is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-nmap is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-nmap. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'set'
22
+
23
+ module Ronin
24
+ module Nmap
25
+ class CLI
26
+ #
27
+ # Represents a list of port numbers and port ranges.
28
+ #
29
+ class PortList
30
+
31
+ # Port numbers.
32
+ #
33
+ # @return [Array<Integer>]
34
+ attr_reader :numbers
35
+
36
+ # Port ranges.
37
+ #
38
+ # @return [Array<Range>]
39
+ attr_reader :ranges
40
+
41
+ #
42
+ # Initialize the port list.
43
+ #
44
+ # @param [Array<Integer, Range>] ports
45
+ # The port numbers and ranges.
46
+ #
47
+ # @raise [ArgumentError]
48
+ # One of the ports was not an Integer or Range object.
49
+ #
50
+ def initialize(ports)
51
+ @numbers = Set.new
52
+ @ranges = Set.new
53
+
54
+ ports.each do |port|
55
+ case port
56
+ when Integer then @numbers << port
57
+ when Range then @ranges << port
58
+ else
59
+ raise(ArgumentError,"port must be an Integer or Range: #{port.inspect}")
60
+ end
61
+ end
62
+ end
63
+
64
+ #
65
+ # Parses the port list.
66
+ #
67
+ # @param [String] ports
68
+ # The port list to parse.
69
+ #
70
+ # @return [PortList]
71
+ # The port numbers and port ranges.
72
+ #
73
+ def self.parse(ports)
74
+ new(
75
+ ports.split(',').map do |port|
76
+ if port.include?('-')
77
+ start, stop = port.split('-',2)
78
+
79
+ Range.new(start.to_i,stop.to_i)
80
+ else
81
+ port.to_i
82
+ end
83
+ end
84
+ )
85
+ end
86
+
87
+ #
88
+ # Determines if the port is in the port list.
89
+ #
90
+ # @param [Integer] port
91
+ #
92
+ # @return [Boolean]
93
+ #
94
+ def include?(port)
95
+ @numbers.include?(port) ||
96
+ @ranges.any? { |range| range.include?(port) }
97
+ end
98
+
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-nmap - A Ruby library for automating nmap and importing nmap scans.
4
+ #
5
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
6
+ #
7
+ # ronin-nmap is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-nmap is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-nmap. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/nmap/version'
22
+ require 'ronin/core/cli/help/banner'
23
+
24
+ require 'command_kit/commands'
25
+ require 'command_kit/commands/auto_load'
26
+ require 'command_kit/options/version'
27
+
28
+ module Ronin
29
+ module Nmap
30
+ #
31
+ # The `ronin-nmap` command-line interface (CLI).
32
+ #
33
+ # @api private
34
+ #
35
+ class CLI
36
+
37
+ include CommandKit::Commands
38
+ include CommandKit::Commands::AutoLoad.new(
39
+ dir: "#{__dir__}/cli/commands",
40
+ namespace: "#{self}::Commands"
41
+ )
42
+ include CommandKit::Options::Version
43
+ include Core::CLI::Help::Banner
44
+
45
+ command_name 'ronin-nmap'
46
+ version Ronin::Nmap::VERSION
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-nmap - A Ruby library for automating nmap and importing nmap scans.
4
+ #
5
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
6
+ #
7
+ # ronin-nmap is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-nmap is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-nmap. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/nmap/converters'
22
+
23
+ require 'nmap/xml'
24
+
25
+ module Ronin
26
+ module Nmap
27
+ #
28
+ # Handles converting nmap XML into other formats.
29
+ #
30
+ # Supports the following formats:
31
+ #
32
+ # * JSON
33
+ # * CSV
34
+ #
35
+ # @api public
36
+ #
37
+ module Converter
38
+ # Mapping of file extension names to formats.
39
+ #
40
+ # @api private
41
+ FILE_FORMATS = {
42
+ '.json' => :json,
43
+ '.csv' => :csv
44
+ }
45
+
46
+ #
47
+ # Converts an nmap XML scan file into another format.
48
+ #
49
+ # @param [String] src
50
+ # The input XML file path.
51
+ #
52
+ # @param [String] dest
53
+ # The output file path.
54
+ #
55
+ # @api public
56
+ #
57
+ def self.convert_file(src,dest, format: infer_format_for(dest))
58
+ xml = ::Nmap::XML.open(src)
59
+ converter = Converters[format]
60
+
61
+ File.open(dest,'w') do |output|
62
+ converter.convert(xml,output)
63
+ end
64
+ end
65
+
66
+ #
67
+ # Converts parsed nmap XML into the desired format.
68
+ #
69
+ # @param [::Nmap::XML] xml
70
+ # The nmap XML to convert.
71
+ #
72
+ # @param [IO, nil] output
73
+ # Optional output to write the converted output to.
74
+ #
75
+ # @param [:json, :csv] format
76
+ # The desired convert to convert the parsed nmap XML to.
77
+ #
78
+ # @return [String]
79
+ # The converted nmap XML.
80
+ #
81
+ # @api public
82
+ #
83
+ def self.convert(xml,output=nil, format: )
84
+ if output
85
+ Converters[format].convert(xml,output)
86
+ else
87
+ output = StringIO.new
88
+ convert(xml,output, format: format)
89
+ output.string
90
+ end
91
+ end
92
+
93
+ #
94
+ # Infers the output format from the output file's extension.
95
+ #
96
+ # @param [String] path
97
+ # The output file name.
98
+ #
99
+ # @return [:json, :csv]
100
+ # The conversion format.
101
+ #
102
+ # @raise [ArgumentError]
103
+ # The format could not be inferred from the path's file extension.
104
+ #
105
+ # @api private
106
+ #
107
+ def self.infer_format_for(path)
108
+ FILE_FORMATS.fetch(File.extname(path)) do
109
+ raise(ArgumentError,"cannot infer output format from path: #{path.inspect}")
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end