ronin-nmap 0.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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