ruby-masscan 0.2.3 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10e9abe454a6c9e5715a61cff5b55eb5d29c037d1caf7692ccb937a811b83a3b
4
- data.tar.gz: 8807aa7d6f7868f0ed84d0296de265b1ea1bf41a1301e779b767bd678747dc9f
3
+ metadata.gz: 5c7131e7fffe842982596ea49bdc4caafdcab24c9a2b1bd1dcc0b1372ca710ef
4
+ data.tar.gz: 50a2a06aca667ba92b82366f904420f6d42e30d05259ebab46a372ca7b4f4618
5
5
  SHA512:
6
- metadata.gz: 75b5b24e0596c0d887b86b9d471c39dbedf966f74b9861db1b4aeb2809b243f6e7ad79b30851aa9c726e3178dda23dd6cbfc938836de750b7ff71772e952746e
7
- data.tar.gz: faa1362698c3c0b11acc83d30b7e240d84b7786c17807e5d60d49836c654eaa7efa38f8e0f99144c895b4e308c7815e9c9a834a680cf837feed852516031018a
6
+ metadata.gz: f7c91b8b36dcd52defb23ee391cc4f3f6a04fe47cb9d5495f8a737f10210e32b1c5929cbd6da138f617b3f0eb27727285eaec1dcd05cb245d7c95f3cbd97589d
7
+ data.tar.gz: a714178ff916f8c469df962a3cf6aa8905c537de2f9c39d0a71fda4487ba820f6862357a1c0028e93966e8c6276e5c796b07399eee0dd87d262d33aabc589e1a
data/ChangeLog.md CHANGED
@@ -1,3 +1,13 @@
1
+ ### 0.3.0 / 2024-06-23
2
+
3
+ * Include `Enumerable` into {Masscan::OutputFile}.
4
+ * Improvements to {Masscan::Command}:
5
+ * Added the `rotate` attribute for the `--rotate` option.
6
+ * Allow the `ports` attribute to accept a raw String value.
7
+ * Improve validation of String values passed to `ports`, `adapter_port`,
8
+ `range`, `shards`, and `ips` attributes.
9
+ * Correct the type used for the `exclude` attribute.
10
+
1
11
  ### 0.2.3 / 2024-01-27
2
12
 
3
13
  * Switched to using `require_relative` to improve load-times.
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'command_mapper/command'
4
4
 
5
+ require 'ipaddr'
6
+
5
7
  module Masscan
6
8
  #
7
9
  # Provides an interface for invoking the `masscan` utility.
@@ -88,20 +90,98 @@ module Masscan
88
90
  #
89
91
  class Command < CommandMapper::Command
90
92
 
91
- class PortList < CommandMapper::Types::Num
93
+ #
94
+ # Represents a port number.
95
+ #
96
+ # @api private
97
+ #
98
+ # @since 0.3.0
99
+ #
100
+ class Port < CommandMapper::Types::Num
101
+
102
+ # Regular expression that validates a port number.
103
+ PORT_REGEXP = /[1-9][0-9]{0,3}|[1-5][0-9][0-9][0-9][0-9]|6[0-4][0-9][0-9][0-9]|65[0-4][0-9][0-9]|655[0-2][0-9]|6553[0-5]/
104
+
105
+ # Regular expression that validates either a port number or service name.
106
+ REGEXP = /\A#{PORT_REGEXP}\z/
107
+
108
+ #
109
+ # Initializes the port type.
110
+ #
111
+ def initialize
112
+ super(range: 1..65535)
113
+ end
92
114
 
115
+ #
116
+ # Validates the given value.
117
+ #
118
+ # @param [Object] value
119
+ # The value to validate.
120
+ #
121
+ # @return [true, (false, String)]
122
+ # Returns true if the value is valid, or `false` and a validation error
123
+ # message if the value is not compatible.
124
+ #
93
125
  def validate(value)
94
126
  case value
95
- when Array
96
- value.each do |element|
97
- valid, message = validate(element)
98
-
99
- unless valid
100
- return [valid, message]
101
- end
127
+ when String
128
+ unless value =~ REGEXP
129
+ return [false, "must be a valid port number (#{value.inspect})"]
102
130
  end
103
131
 
104
132
  return true
133
+ else
134
+ super(value)
135
+ end
136
+ end
137
+
138
+ #
139
+ # Formats the given port number.
140
+ #
141
+ # @param [Integer, String] value
142
+ # The given port number.
143
+ #
144
+ # @return [String]
145
+ # The formatted port number.
146
+ #
147
+ def format(value)
148
+ case value
149
+ when String
150
+ value
151
+ else
152
+ super(value)
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+ #
159
+ # Represents a port range.
160
+ #
161
+ # @api private
162
+ #
163
+ # @since 0.3.0
164
+ #
165
+ class PortRange < Port
166
+
167
+ # Regular expression to validate either a port or a port range.
168
+ PORT_RANGE_REGEXP = /#{PORT_REGEXP}-#{PORT_REGEXP}|#{PORT_REGEXP}/
169
+
170
+ # Regular expression to validate either a port or a port range.
171
+ REGEXP = /\A#{PORT_RANGE_REGEXP}\z/
172
+
173
+ #
174
+ # Validates the given port or port range value.
175
+ #
176
+ # @param [Object] value
177
+ # The port or port range value to validate.
178
+ #
179
+ # @return [true, (false, String)]
180
+ # Returns true if the value is valid, or `false` and a validation error
181
+ # message if the value is not compatible.
182
+ #
183
+ def validate(value)
184
+ case value
105
185
  when Range
106
186
  valid, message = super(value.begin)
107
187
 
@@ -115,16 +195,29 @@ module Masscan
115
195
  return [valid, message]
116
196
  end
117
197
 
198
+ return true
199
+ when String
200
+ unless value =~ REGEXP
201
+ return [false, "must be a valid port range or port number (#{value.inspect})"]
202
+ end
203
+
118
204
  return true
119
205
  else
120
206
  super(value)
121
207
  end
122
208
  end
123
209
 
210
+ #
211
+ # Formats the given port or port range value.
212
+ #
213
+ # @param [Range, Integer, String] value
214
+ # The port or port range value to format.
215
+ #
216
+ # @return [String]
217
+ # The formatted port or port range.
218
+ #
124
219
  def format(value)
125
220
  case value
126
- when Array
127
- value.map(&method(:format)).join(',')
128
221
  when Range
129
222
  "#{value.begin}-#{value.end}"
130
223
  else
@@ -134,21 +227,135 @@ module Masscan
134
227
 
135
228
  end
136
229
 
230
+ #
231
+ # Represents the type for the `-p,--ports` option.
232
+ #
233
+ # @api private
234
+ #
235
+ class PortList < CommandMapper::Types::List
236
+
237
+ # Regular expression for validating a port or port range.
238
+ PORT_RANGE_REGEXP = PortRange::PORT_RANGE_REGEXP
239
+
240
+ # Regular expression that validates port list String values.
241
+ REGEXP = /\A(?:(?:U:)?#{PORT_RANGE_REGEXP})(?:,(?:U:)?#{PORT_RANGE_REGEXP})*\z/
242
+
243
+ #
244
+ # Initializes the port list type.
245
+ #
246
+ def initialize
247
+ super(type: PortRange.new)
248
+ end
249
+
250
+ #
251
+ # Validates a given value.
252
+ #
253
+ # @param [Array, Range, String, Object] value
254
+ # The port list value.
255
+ #
256
+ # @return [true, (false, String)]
257
+ # Returns true if the value is valid, or `false` and a validation error
258
+ # message if the value is not compatible.
259
+ #
260
+ def validate(value)
261
+ case value
262
+ when Range
263
+ @type.validate(value)
264
+ when String
265
+ unless value =~ REGEXP
266
+ return [false, "not a valid port list (#{value.inspect})"]
267
+ end
268
+
269
+ return true
270
+ else
271
+ super(value)
272
+ end
273
+ end
274
+
275
+ #
276
+ # Formats a port list value into a String.
277
+ #
278
+ # @param [Array<String, Integer, Range>, Range<Integer,Integer>, String, #to_s] value
279
+ # The port list value to format.
280
+ #
281
+ # @return [String]
282
+ # The formatted port list string.
283
+ #
284
+ def format(value)
285
+ case value
286
+ when Range
287
+ # format an individual port range
288
+ @type.format(value)
289
+ when String
290
+ # pass strings directly through
291
+ value
292
+ else
293
+ super(value)
294
+ end
295
+ end
296
+
297
+ end
298
+
299
+ #
300
+ # Represents the type for the `--shards` option.
301
+ #
302
+ # @api private
303
+ #
137
304
  class Shards < CommandMapper::Types::Str
138
305
 
306
+ # Regular expression for validating `--shards` values.
307
+ REGEXP = %r{\A\d+/\d+\z}
308
+
309
+ #
310
+ # Validates a shards value.
311
+ #
312
+ # @param [Array, Rational, String, #to_s] value
313
+ # The shards value to validate.
314
+ #
315
+ # @return [true, (false, String)]
316
+ # Returns true if the value is valid, or `false` and a validation error
317
+ # message if the value is not compatible.
318
+ #
139
319
  def validate(value)
140
320
  case value
141
321
  when Array
142
- if value.length > 2
143
- return [false, "cannot contain more tha two elements (#{value.inspect})"]
322
+ unless value.length == 2
323
+ return [false, "must contain two elements (#{value.inspect})"]
324
+ end
325
+
326
+ unless (value[0].kind_of?(Integer) && value[1].kind_of?(Integer))
327
+ return [false, "shard values must be Integers (#{value.inspect})"]
144
328
  end
145
329
 
330
+ return true
331
+ when Rational
146
332
  return true
147
333
  else
148
- super(value)
334
+ valid, message = super(value)
335
+
336
+ unless valid
337
+ return [valid, message]
338
+ end
339
+
340
+ string = value.to_s
341
+
342
+ unless string =~ REGEXP
343
+ return [false, "invalid shards value (#{value.inspect})"]
344
+ end
345
+
346
+ return true
149
347
  end
150
348
  end
151
349
 
350
+ #
351
+ # Formats a shards value into a String.
352
+ #
353
+ # @param [(Integer, Integer), Rational, #to_s] value
354
+ # The shards value to format.
355
+ #
356
+ # @return [String]
357
+ # The formatted shards value.
358
+ #
152
359
  def format(value)
153
360
  case value
154
361
  when Array
@@ -160,8 +367,166 @@ module Masscan
160
367
 
161
368
  end
162
369
 
370
+ #
371
+ # Represents the type for the `--rotate` option.
372
+ #
373
+ # @api private
374
+ #
375
+ # @since 0.3.0
376
+ #
377
+ class RotateTime < CommandMapper::Types::Str
378
+
379
+ # Regular expression to validate the `--rotate` time value.
380
+ REGEXP = /\A(?:\d+|hourly|\d+hours|\d+min)\z/
381
+
382
+ #
383
+ # Validates a `--rotate` time value.
384
+ #
385
+ # @param [Integer, String, #to_s] value
386
+ # The time value to validate.
387
+ #
388
+ # @return [true, (false, String)]
389
+ # Returns true if the value is valid, or `false` and a validation error
390
+ # message if the value is not compatible.
391
+ #
392
+ def validate(value)
393
+ case value
394
+ when Integer
395
+ return true
396
+ else
397
+ valid, message = super(value)
398
+
399
+ unless valid
400
+ return [valid, message]
401
+ end
402
+
403
+ string = value.to_s
404
+
405
+ unless string =~ REGEXP
406
+ return [false, "invalid rotation time (#{value.inspect})"]
407
+ end
408
+
409
+ return true
410
+ end
411
+ end
412
+
413
+ end
414
+
415
+ #
416
+ # Represents the type for the `--adapter-mac` and `--router-mac` options.
417
+ #
418
+ # @api private
419
+ #
420
+ # @since 0.3.0
421
+ #
422
+ class MACAddress < CommandMapper::Types::Str
423
+
424
+ # Regular expression to validate a MAC address.
425
+ REGEXP = /\A[A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2}){5}\z/
426
+
427
+ #
428
+ # Validates a MAC address value.
429
+ #
430
+ # @param [String, #to_s] value
431
+ # The MAC address value to validate.
432
+ #
433
+ # @return [true, (false, String)]
434
+ # Returns true if the value is valid, or `false` and a validation error
435
+ # message if the value is not compatible.
436
+ #
437
+ def validate(value)
438
+ valid, message = super(value)
439
+
440
+ unless valid
441
+ return [valid, message]
442
+ end
443
+
444
+ string = value.to_s
445
+
446
+ unless string =~ REGEXP
447
+ return [false, "invalid MAC address (#{value.inspect})"]
448
+ end
449
+
450
+ return true
451
+ end
452
+
453
+ end
454
+
455
+ #
456
+ # Represents the type for the `--range` option and `ips` argument(s).
457
+ #
458
+ # @api private
459
+ #
460
+ # @since 0.3.0
461
+ #
462
+ class Target < CommandMapper::Types::Str
463
+
464
+ # Regular expression for validating decimal octets (0-255).
465
+ DECIMAL_OCTET_REGEXP = /(?<=[^\d]|^)(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])(?=[^\d]|$)/
466
+
467
+ # Regular expression for validating IPv4 addresses or CIDR ranges.
468
+ IPV4_REGEXP = %r{#{DECIMAL_OCTET_REGEXP}(?:\.#{DECIMAL_OCTET_REGEXP}){3}(?:/\d{1,2})?}
469
+
470
+ # Regular expression for validating IPv6 addresses or CIDR ranges.
471
+ IPV6_REGEXP = %r{
472
+ (?:[0-9a-f]{1,4}:){6}#{IPV4_REGEXP}|
473
+ (?:[0-9a-f]{1,4}:){5}[0-9a-f]{1,4}:#{IPV4_REGEXP}|
474
+ (?:[0-9a-f]{1,4}:){5}:[0-9a-f]{1,4}:#{IPV4_REGEXP}|
475
+ (?:[0-9a-f]{1,4}:){1,1}(?::[0-9a-f]{1,4}){1,4}:#{IPV4_REGEXP}|
476
+ (?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,3}:#{IPV4_REGEXP}|
477
+ (?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,2}:#{IPV4_REGEXP}|
478
+ (?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,1}:#{IPV4_REGEXP}|
479
+ :(?::[0-9a-f]{1,4}){1,5}:#{IPV4_REGEXP}|
480
+ (?:(?:[0-9a-f]{1,4}:){1,5}|:):#{IPV4_REGEXP}|
481
+ (?:[0-9a-f]{1,4}:){1,1}(?::[0-9a-f]{1,4}){1,6}(?:/\d{1,3})?|
482
+ (?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,5}(?:/\d{1,3})?|
483
+ (?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,4}(?:/\d{1,3})?|
484
+ (?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,3}(?:/\d{1,3})?|
485
+ (?:[0-9a-f]{1,4}:){1,5}(?::[0-9a-f]{1,4}){1,2}(?:/\d{1,3})?|
486
+ (?:[0-9a-f]{1,4}:){1,6}(?::[0-9a-f]{1,4}){1,1}(?:/\d{1,3})?|
487
+ [0-9a-f]{1,4}(?::[0-9a-f]{1,4}){7}(?:/\d{1,3})?|
488
+ :(?::[0-9a-f]{1,4}){1,7}(?:/\d{1,3})?|
489
+ (?:(?:[0-9a-f]{1,4}:){1,7}|:):(?:/\d{1,3})?
490
+ }x
491
+
492
+ # Regular expression for validating masscan target IPs or IP ranges.
493
+ REGEXP = /\A(?:#{IPV4_REGEXP}|#{IPV6_REGEXP})\z/
494
+
495
+ #
496
+ # Validates a IP or IP range target value.
497
+ #
498
+ # @param [IPAddr, String, #to_s] value
499
+ # The IP or IP range value to validate.
500
+ #
501
+ # @return [true, (false, String)]
502
+ # Returns true if the value is valid, or `false` and a validation error
503
+ # message if the value is not compatible.
504
+ #
505
+ def validate(value)
506
+ case value
507
+ when IPAddr
508
+ return true
509
+ else
510
+ valid, message = super(value)
511
+
512
+ unless valid
513
+ return [valid, message]
514
+ end
515
+
516
+ string = value.to_s
517
+
518
+ unless string =~ REGEXP
519
+ return [false, "invalid IP or IP range (#{value.inspect})"]
520
+ end
521
+
522
+ return true
523
+ end
524
+ end
525
+
526
+ end
527
+
163
528
  command "masscan" do
164
- option '--range', name: :range, value: true, repeats: true
529
+ option '--range', name: :range, value: {type: Target.new}, repeats: true
165
530
  option '-p', name: :ports, value: {type: PortList.new}
166
531
  option '--banners', name: :banners
167
532
  option '--rate', name: :rate, value: {type: Num.new}
@@ -170,12 +535,12 @@ module Masscan
170
535
  option '--echo', name: :echo, value: true
171
536
  option '--adapter', name: :adapter, value: true
172
537
  option '--adapter-ip', name: :adapter_ip, value: true
173
- option '--adapter-port', name: :adapter_port, value: {type: Num.new}
174
- option '--adapter-mac', name: :adapter_mac, value: true
538
+ option '--adapter-port', name: :adapter_port, value: {type: PortRange.new}
539
+ option '--adapter-mac', name: :adapter_mac, value: {type: MACAddress.new}
175
540
  option '--adapter-vlan', name: :adapter_vlan, value: true
176
- option '--router-mac', name: :router_mac, value: true
541
+ option '--router-mac', name: :router_mac, value: {type: MACAddress.new}
177
542
  option '--ping', name: :ping
178
- option '--exclude', name: :exclude, value: true, repeats: true
543
+ option '--exclude', name: :exclude, value: {type: Target.new}, repeats: true
179
544
  option '--excludefile', name: :exclude_file, value: {type: InputFile.new}, repeats: true
180
545
  option '--includefile', name: :include_file, value: {type: InputFile.new}, repeats: true
181
546
  option '--append-output', name: :append_output
@@ -205,7 +570,7 @@ module Masscan
205
570
  option '--resume-index', name: :resume_index
206
571
  option '--resume-count', name: :resume_count
207
572
  option '--shards', name: :shards, value: {type: Shards.new}
208
- option '--rotate', name: :rotate, value: true
573
+ option '--rotate', name: :rotate, value: {type: RotateTime.new}
209
574
  option '--rotate-offset', name: :rotate_offset, value: true
210
575
  option '--rotate-size', name: :rotate_size, value: true
211
576
  option '--rotate-dir', name: :rotate_dir, value: {type: InputDir.new}
@@ -235,7 +600,7 @@ module Masscan
235
600
  option '-V', name: :version
236
601
  option '-h', name: :help
237
602
 
238
- argument :ips, repeats: true
603
+ argument :ips, repeats: true, type: Target.new
239
604
  end
240
605
 
241
606
  end
@@ -17,6 +17,8 @@ module Masscan
17
17
  #
18
18
  class OutputFile
19
19
 
20
+ include Enumerable
21
+
20
22
  # Mapping of formats to parsers.
21
23
  #
22
24
  # @api semipublic
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Masscan
4
4
  # ruby-masscan version
5
- VERSION = '0.2.3'
5
+ VERSION = '0.3.0'
6
6
  end
data/spec/command_spec.rb CHANGED
@@ -2,6 +2,164 @@ require 'spec_helper'
2
2
  require 'masscan/command'
3
3
 
4
4
  describe Masscan::Command do
5
+ describe described_class::Port do
6
+ describe "#validate" do
7
+ context "when given an Integer" do
8
+ let(:value) { 443 }
9
+
10
+ it "must return true" do
11
+ expect(subject.validate(value)).to be(true)
12
+ end
13
+
14
+ context "but it's less than 1" do
15
+ let(:value) { 0 }
16
+
17
+ it "must return [false, \"(...) not within the range of acceptable values (1..65535)\"]" do
18
+ expect(subject.validate(value)).to eq(
19
+ [false, "(#{value.inspect}) not within the range of acceptable values (1..65535)"]
20
+ )
21
+ end
22
+ end
23
+
24
+ context "but it's greater than 65535" do
25
+ let(:value) { 65536 }
26
+
27
+ it "must return [false, \"(...) not within the range of acceptable values (1..65535)\"]" do
28
+ expect(subject.validate(value)).to eq(
29
+ [false, "(#{value.inspect}) not within the range of acceptable values (1..65535)"]
30
+ )
31
+ end
32
+ end
33
+ end
34
+
35
+ context "when given a String" do
36
+ context "and it's a number" do
37
+ let(:value) { '443' }
38
+
39
+ it "must return true" do
40
+ expect(subject.validate(value)).to be(true)
41
+ end
42
+
43
+ context "but it's less than 1" do
44
+ let(:value) { '0' }
45
+
46
+ it "must return [false, \"must be a valid port number (...)\"]" do
47
+ expect(subject.validate(value)).to eq(
48
+ [false, "must be a valid port number (#{value.inspect})"]
49
+ )
50
+ end
51
+ end
52
+
53
+ context "but it's greater than 65535" do
54
+ let(:value) { '65536' }
55
+
56
+ it "must return [false, \"must be a valid port number (...)\"]" do
57
+ expect(subject.validate(value)).to eq(
58
+ [false, "must be a valid port number (#{value.inspect})"]
59
+ )
60
+ end
61
+ end
62
+ end
63
+
64
+ context "but it contains numbers" do
65
+ let(:value) { "foo" }
66
+
67
+ it "must return [false, \"must be a valid port number (...)\"]" do
68
+ expect(subject.validate(value)).to eq(
69
+ [false, "must be a valid port number (#{value.inspect})"]
70
+ )
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ describe described_class::PortRange do
78
+ describe "#validate" do
79
+ context "when given an Integer value" do
80
+ let(:value) { 443 }
81
+
82
+ it "must return true" do
83
+ expect(subject.validate(value)).to be(true)
84
+ end
85
+
86
+ context "but it's less than 1" do
87
+ let(:value) { 0 }
88
+
89
+ it "must return [false, \"(...) not within the range of acceptable values (1..65535)\"]" do
90
+ expect(subject.validate(value)).to eq(
91
+ [false, "(#{value.inspect}) not within the range of acceptable values (1..65535)"]
92
+ )
93
+ end
94
+ end
95
+
96
+ context "but it's greater than 65535" do
97
+ let(:value) { 65536 }
98
+
99
+ it "must return [false, \"(...) not within the range of acceptable values (1..65535)\"]" do
100
+ expect(subject.validate(value)).to eq(
101
+ [false, "(#{value.inspect}) not within the range of acceptable values (1..65535)"]
102
+ )
103
+ end
104
+ end
105
+ end
106
+
107
+ context "when given a String value" do
108
+ let(:value) { '443' }
109
+
110
+ it "must return true" do
111
+ expect(subject.validate(value)).to be(true)
112
+ end
113
+
114
+ context "but it's less than 1" do
115
+ let(:value) { '0' }
116
+
117
+ it "must return [false, \"must be a valid port range or port number (...)\"]" do
118
+ expect(subject.validate(value)).to eq(
119
+ [false, "must be a valid port range or port number (#{value.inspect})"]
120
+ )
121
+ end
122
+ end
123
+
124
+ context "but it's greater than 65535" do
125
+ let(:value) { '65536' }
126
+
127
+ it "must return [false, \"must be a valid port range or port number (...)\"]" do
128
+ expect(subject.validate(value)).to eq(
129
+ [false, "must be a valid port range or port number (#{value.inspect})"]
130
+ )
131
+ end
132
+ end
133
+ end
134
+
135
+ context "when given a Range of port numbers" do
136
+ let(:value) { (1..1024) }
137
+
138
+ it "must return true" do
139
+ expect(subject.validate(value)).to be(true)
140
+ end
141
+ end
142
+ end
143
+
144
+ describe "#format" do
145
+ context "when given a single port number" do
146
+ let(:value) { 443 }
147
+
148
+ it "must return the formatted port number" do
149
+ expect(subject.format(value)).to eq(value.to_s)
150
+ end
151
+ end
152
+
153
+ context "when given a Range of port numbers" do
154
+ let(:value) { 1..1024 }
155
+
156
+ it "must return the formatted port number range (ex: 1-102)" do
157
+ expect(subject.format(value)).to eq("#{value.begin}-#{value.end}")
158
+ end
159
+ end
160
+ end
161
+ end
162
+
5
163
  describe described_class::PortList do
6
164
  describe "#validate" do
7
165
  context "when given a single port number" do
@@ -35,6 +193,118 @@ describe Masscan::Command do
35
193
  end
36
194
  end
37
195
  end
196
+
197
+ context "when given a String" do
198
+ context "and it contains a single number" do
199
+ let(:value) { "443" }
200
+
201
+ it "must return true" do
202
+ expect(subject.validate(value)).to be(true)
203
+ end
204
+
205
+ context "and it's prefixed by 'U:'" do
206
+ let(:value) { "U:#{super()}" }
207
+
208
+ it "must return true" do
209
+ expect(subject.validate(value)).to be(true)
210
+ end
211
+ end
212
+ end
213
+
214
+ context "and it contains a range of ports" do
215
+ let(:value) { "1-1024" }
216
+
217
+ it "must return true" do
218
+ expect(subject.validate(value)).to be(true)
219
+ end
220
+
221
+ context "and it's prefixed by 'U:'" do
222
+ let(:value) { "U:#{super()}" }
223
+
224
+ it "must return true" do
225
+ expect(subject.validate(value)).to be(true)
226
+ end
227
+ end
228
+ end
229
+
230
+ context "and it contains a comma separated list of port numbers" do
231
+ let(:value) { "80,443" }
232
+
233
+ it "must return true" do
234
+ expect(subject.validate(value)).to be(true)
235
+ end
236
+
237
+ context "and it's prefixed by 'U:'" do
238
+ let(:value) { "U:#{super()}" }
239
+
240
+ it "must return true" do
241
+ expect(subject.validate(value)).to be(true)
242
+ end
243
+ end
244
+ end
245
+
246
+ context "and it contains a comma separated list of port ranges" do
247
+ let(:value) { "1-42,80-8080" }
248
+
249
+ it "must return true" do
250
+ expect(subject.validate(value)).to be(true)
251
+ end
252
+
253
+ context "and it's prefixed by 'U:'" do
254
+ let(:value) { "U:#{super()}" }
255
+
256
+ it "must return true" do
257
+ expect(subject.validate(value)).to be(true)
258
+ end
259
+ end
260
+ end
261
+
262
+ context "and it contains a comma separated list of port numbers and ranges" do
263
+ let(:value) { "1-42,50,60,70,80-8080,9000" }
264
+
265
+ it "must return true" do
266
+ expect(subject.validate(value)).to be(true)
267
+ end
268
+
269
+ context "and it's prefixed by 'U:'" do
270
+ let(:value) { "U:#{super()}" }
271
+
272
+ it "must return true" do
273
+ expect(subject.validate(value)).to be(true)
274
+ end
275
+ end
276
+ end
277
+
278
+ context "when it contains non-digits" do
279
+ let(:value) { "1,2,3,4,a,b,c" }
280
+
281
+ it "must return false and a validation error message" do
282
+ expect(subject.validate(value)).to eq(
283
+ [false, "not a valid port list (#{value.inspect})"]
284
+ )
285
+ end
286
+ end
287
+
288
+ context "when it contains whitespace" do
289
+ let(:value) { "1,2, 3,4" }
290
+
291
+ it "must return false and a validation error message" do
292
+ expect(subject.validate(value)).to eq(
293
+ [false, "not a valid port list (#{value.inspect})"]
294
+ )
295
+ end
296
+ end
297
+
298
+ context "when it contains new-lines" do
299
+ let(:value) { "1,2,\n3,4" }
300
+
301
+ it "must return false and a validation error message" do
302
+ expect(subject.validate(value)).to eq(
303
+ [false, "not a valid port list (#{value.inspect})"]
304
+ )
305
+ end
306
+ end
307
+ end
38
308
  end
39
309
 
40
310
  describe "#format" do
@@ -69,6 +339,14 @@ describe Masscan::Command do
69
339
  end
70
340
  end
71
341
  end
342
+
343
+ context "when given a String" do
344
+ let(:value) { "22,25,80,443" }
345
+
346
+ it "must return the String" do
347
+ expect(subject.format(value)).to eq(value)
348
+ end
349
+ end
72
350
  end
73
351
  end
74
352
 
@@ -89,12 +367,42 @@ describe Masscan::Command do
89
367
  expect(subject.validate(value)).to be(true)
90
368
  end
91
369
 
370
+ context "but the Array length is 1" do
371
+ let(:value) { [1] }
372
+
373
+ it "must return a validation error" do
374
+ expect(subject.validate(value)).to eq(
375
+ [false, "must contain two elements (#{value.inspect})"]
376
+ )
377
+ end
378
+ end
379
+
92
380
  context "but the Array length is > 2" do
93
381
  let(:value) { [1,2,3] }
94
382
 
95
383
  it "must return a validation error" do
96
384
  expect(subject.validate(value)).to eq(
97
- [false, "cannot contain more tha two elements (#{value.inspect})"]
385
+ [false, "must contain two elements (#{value.inspect})"]
386
+ )
387
+ end
388
+ end
389
+ end
390
+
391
+ context "when given a String" do
392
+ context "and it matches X/Y" do
393
+ let(:value) { "1/2" }
394
+
395
+ it "must return true" do
396
+ expect(subject.validate(value)).to be(true)
397
+ end
398
+ end
399
+
400
+ context "but it does not match X/Y" do
401
+ let(:value) { "1" }
402
+
403
+ it "must return a validation error" do
404
+ expect(subject.validate(value)).to eq(
405
+ [false, "invalid shards value (#{value.inspect})"]
98
406
  )
99
407
  end
100
408
  end
@@ -139,4 +447,256 @@ describe Masscan::Command do
139
447
  end
140
448
  end
141
449
  end
450
+
451
+ describe described_class::RotateTime do
452
+ describe "#validate" do
453
+ context "when given an Integer" do
454
+ let(:value) { 42 }
455
+
456
+ it "must return true" do
457
+ expect(subject.validate(value)).to be(true)
458
+ end
459
+ end
460
+
461
+ context "when given a String" do
462
+ context "but the String is a number" do
463
+ let(:value) { '42' }
464
+
465
+ it "must return true" do
466
+ expect(subject.validate(value)).to be(true)
467
+ end
468
+ end
469
+
470
+ context "but the String is 'hourly'" do
471
+ let(:value) { 'hourly' }
472
+
473
+ it "must return true" do
474
+ expect(subject.validate(value)).to be(true)
475
+ end
476
+ end
477
+
478
+ context "but the String is '<N>hours'" do
479
+ let(:value) { '2hours' }
480
+
481
+ it "must return true" do
482
+ expect(subject.validate(value)).to be(true)
483
+ end
484
+ end
485
+
486
+ context "but the String is '<N>min'" do
487
+ let(:value) { '10min' }
488
+
489
+ it "must return true" do
490
+ expect(subject.validate(value)).to be(true)
491
+ end
492
+ end
493
+
494
+ context "but the String is not a number" do
495
+ let(:value) { "abc" }
496
+
497
+ it "must return a validation error" do
498
+ expect(subject.validate(value)).to eq([false, "invalid rotation time (#{value.inspect})"])
499
+ end
500
+ end
501
+
502
+ context "but the String contains a new-line" do
503
+ let(:value) { "10\nfoo" }
504
+
505
+ it "must return a validation error" do
506
+ expect(subject.validate(value)).to eq([false, "invalid rotation time (#{value.inspect})"])
507
+ end
508
+ end
509
+ end
510
+ end
511
+ end
512
+
513
+ describe described_class::MACAddress do
514
+ describe "#validate" do
515
+ context "when given a String" do
516
+ context "and it's a valid MAC address" do
517
+ let(:value) { "00:11:22:33:44:55" }
518
+
519
+ it "must return true" do
520
+ expect(subject.validate(value)).to be(true)
521
+ end
522
+ end
523
+
524
+ context "but an octent contains less than two hex digits" do
525
+ let(:value) { "0:11:22:33:44" }
526
+
527
+ it "must return [false, \"invalid MAC address (...)\"]" do
528
+ expect(subject.validate(value)).to eq(
529
+ [false, "invalid MAC address (#{value.inspect})"]
530
+ )
531
+ end
532
+ end
533
+
534
+ context "but an octent contains more than two hex digits" do
535
+ let(:value) { "000:11:22:33:44" }
536
+
537
+ it "must return [false, \"invalid MAC address (...)\"]" do
538
+ expect(subject.validate(value)).to eq(
539
+ [false, "invalid MAC address (#{value.inspect})"]
540
+ )
541
+ end
542
+ end
543
+
544
+ context "but it's contains less than six octets" do
545
+ let(:value) { "00:11:22:33:44" }
546
+
547
+ it "must return [false, \"invalid MAC address (...)\"]" do
548
+ expect(subject.validate(value)).to eq(
549
+ [false, "invalid MAC address (#{value.inspect})"]
550
+ )
551
+ end
552
+ end
553
+
554
+ context "but it's contains more than six octets" do
555
+ let(:value) { "00:11:22:33:44:55:66" }
556
+
557
+ it "must return [false, \"invalid MAC address (...)\"]" do
558
+ expect(subject.validate(value)).to eq(
559
+ [false, "invalid MAC address (#{value.inspect})"]
560
+ )
561
+ end
562
+ end
563
+
564
+ context "but it contains non-hex characters" do
565
+ let(:value) { "000:11:22:33:44:xx" }
566
+
567
+ it "must return [false, \"invalid MAC address (...)\"]" do
568
+ expect(subject.validate(value)).to eq(
569
+ [false, "invalid MAC address (#{value.inspect})"]
570
+ )
571
+ end
572
+ end
573
+
574
+ context "but it is not separated by ':' characters" do
575
+ let(:value) { "00.11.22.33.44.55" }
576
+
577
+ it "must return [false, \"invalid MAC address (...)\"]" do
578
+ expect(subject.validate(value)).to eq(
579
+ [false, "invalid MAC address (#{value.inspect})"]
580
+ )
581
+ end
582
+ end
583
+
584
+ context "but it contains spaces" do
585
+ let(:value) { "00:11:22: 33:44:55" }
586
+
587
+ it "must return [false, \"invalid MAC address (...)\"]" do
588
+ expect(subject.validate(value)).to eq(
589
+ [false, "invalid MAC address (#{value.inspect})"]
590
+ )
591
+ end
592
+ end
593
+
594
+ context "but it contains new-line characters" do
595
+ let(:value) { "00:11:22:\n33:44:55" }
596
+
597
+ it "must return [false, \"invalid MAC address (...)\"]" do
598
+ expect(subject.validate(value)).to eq(
599
+ [false, "invalid MAC address (#{value.inspect})"]
600
+ )
601
+ end
602
+ end
603
+ end
604
+ end
605
+ end
606
+
607
+ describe described_class::Target do
608
+ describe "#validate" do
609
+ context "when given an IPAddr object" do
610
+ let(:value) { IPAddr.new('127.0.0.1') }
611
+
612
+ it "must return true" do
613
+ expect(subject.validate(value)).to be(true)
614
+ end
615
+ end
616
+
617
+ context "when given a String" do
618
+ context "and it's an IPv4 address" do
619
+ let(:value) { '127.0.0.1' }
620
+
621
+ it "must return true" do
622
+ expect(subject.validate(value)).to be(true)
623
+ end
624
+ end
625
+
626
+ context "and it's an IPv4 range" do
627
+ let(:value) { '127.0.0.1/24' }
628
+
629
+ it "must return true" do
630
+ expect(subject.validate(value)).to be(true)
631
+ end
632
+ end
633
+
634
+ context "and it's an IPv6 address" do
635
+ context "but it's in compressed notation" do
636
+ let(:value) { '::1' }
637
+
638
+ it "must return true" do
639
+ expect(subject.validate(value)).to be(true)
640
+ end
641
+ end
642
+
643
+ context "and it's in full notation" do
644
+ let(:value) { '2606:2800:220:1:248:1893:25c8:1946' }
645
+
646
+ it "must return true" do
647
+ expect(subject.validate(value)).to be(true)
648
+ end
649
+ end
650
+ end
651
+
652
+ context "and it's an IPv6 range" do
653
+ context "but it's in compressed notation" do
654
+ let(:value) { '::1/32' }
655
+
656
+ it "must return true" do
657
+ expect(subject.validate(value)).to be(true)
658
+ end
659
+ end
660
+
661
+ context "and it's in full notation" do
662
+ let(:value) { '2606:2800:220:1:248:1893:25c8:1946/32' }
663
+
664
+ it "must return true" do
665
+ expect(subject.validate(value)).to be(true)
666
+ end
667
+ end
668
+ end
669
+
670
+ context "but it contains non-hex characters" do
671
+ let(:value) { '2606:2800:220:1:248:1893:25c8:xxxx/32' }
672
+
673
+ it "must return [false, \"invalid IP or IP range (...)\"]" do
674
+ expect(subject.validate(value)).to eq(
675
+ [false, "invalid IP or IP range (#{value.inspect})"]
676
+ )
677
+ end
678
+ end
679
+
680
+ context "but it contains spaces" do
681
+ let(:value) { '2606:2800:220:1: 248:1893:25c8:1946/32' }
682
+
683
+ it "must return [false, \"invalid IP or IP range (...)\"]" do
684
+ expect(subject.validate(value)).to eq(
685
+ [false, "invalid IP or IP range (#{value.inspect})"]
686
+ )
687
+ end
688
+ end
689
+
690
+ context "but it contains new-line characters" do
691
+ let(:value) { "2606:2800:220:1:\n248:1893:25c8:1946/32" }
692
+
693
+ it "must return [false, \"invalid IP or IP range (...)\"]" do
694
+ expect(subject.validate(value)).to eq(
695
+ [false, "invalid IP or IP range (#{value.inspect})"]
696
+ )
697
+ end
698
+ end
699
+ end
700
+ end
701
+ end
142
702
  end
@@ -2,6 +2,8 @@ require 'spec_helper'
2
2
  require 'masscan/output_file'
3
3
 
4
4
  describe Masscan::OutputFile do
5
+ it { expect(described_class).to include(Enumerable) }
6
+
5
7
  describe ".infer_format" do
6
8
  subject { described_class.infer_format(path) }
7
9
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-masscan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Postmodern
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-27 00:00:00.000000000 Z
11
+ date: 2024-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: command_mapper
@@ -111,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
111
  version: '0'
112
112
  requirements:
113
113
  - masscan >= 1.0.0
114
- rubygems_version: 3.4.10
114
+ rubygems_version: 3.5.9
115
115
  signing_key:
116
116
  specification_version: 4
117
117
  summary: A Ruby interface to masscan.