ruby-masscan 0.2.3 → 0.3.0

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