packetgen 3.1.4 → 3.2.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 +4 -4
- data/README.md +0 -1
- data/bin/pgconsole +1 -0
- data/lib/packetgen.rb +19 -3
- data/lib/packetgen/capture.rb +30 -9
- data/lib/packetgen/config.rb +15 -9
- data/lib/packetgen/deprecation.rb +1 -1
- data/lib/packetgen/header/asn1_base.rb +19 -9
- data/lib/packetgen/header/base.rb +68 -70
- data/lib/packetgen/header/dhcpv6/duid.rb +3 -1
- data/lib/packetgen/header/dhcpv6/option.rb +4 -12
- data/lib/packetgen/header/dns/name.rb +18 -7
- data/lib/packetgen/header/dns/qdsection.rb +1 -1
- data/lib/packetgen/header/dns/question.rb +2 -0
- data/lib/packetgen/header/dot11.rb +25 -38
- data/lib/packetgen/header/dot11/data.rb +28 -34
- data/lib/packetgen/header/dot1x.rb +1 -14
- data/lib/packetgen/header/eap.rb +14 -17
- data/lib/packetgen/header/eth.rb +5 -6
- data/lib/packetgen/header/http/headers.rb +4 -2
- data/lib/packetgen/header/http/request.rb +37 -18
- data/lib/packetgen/header/http/response.rb +11 -5
- data/lib/packetgen/header/http/verbs.rb +1 -1
- data/lib/packetgen/header/igmpv3/group_record.rb +2 -0
- data/lib/packetgen/header/ip.rb +27 -26
- data/lib/packetgen/header/ip/addr.rb +3 -1
- data/lib/packetgen/header/ip/option.rb +4 -4
- data/lib/packetgen/header/ipv6/addr.rb +2 -0
- data/lib/packetgen/header/mldv2/mcast_address_record.rb +2 -0
- data/lib/packetgen/header/ospfv2/ls_request.rb +2 -0
- data/lib/packetgen/header/ospfv2/lsa.rb +13 -3
- data/lib/packetgen/header/ospfv2/lsa_header.rb +2 -1
- data/lib/packetgen/header/ospfv3/ipv6_prefix.rb +2 -0
- data/lib/packetgen/header/ospfv3/ls_request.rb +2 -0
- data/lib/packetgen/header/ospfv3/lsa.rb +9 -3
- data/lib/packetgen/header/ospfv3/lsa_header.rb +2 -1
- data/lib/packetgen/header/snmp.rb +3 -2
- data/lib/packetgen/header/tcp.rb +1 -20
- data/lib/packetgen/header/tcp/option.rb +8 -6
- data/lib/packetgen/inspect.rb +1 -17
- data/lib/packetgen/packet.rb +10 -6
- data/lib/packetgen/pcapng.rb +11 -11
- data/lib/packetgen/pcapng/block.rb +15 -2
- data/lib/packetgen/pcapng/epb.rb +22 -15
- data/lib/packetgen/pcapng/file.rb +166 -81
- data/lib/packetgen/pcapng/idb.rb +7 -9
- data/lib/packetgen/pcapng/shb.rb +35 -28
- data/lib/packetgen/pcapng/spb.rb +16 -12
- data/lib/packetgen/pcapng/unknown_block.rb +3 -11
- data/lib/packetgen/pcaprub_wrapper.rb +25 -11
- data/lib/packetgen/types.rb +1 -0
- data/lib/packetgen/types/abstract_tlv.rb +3 -1
- data/lib/packetgen/types/array.rb +17 -10
- data/lib/packetgen/types/cstring.rb +56 -19
- data/lib/packetgen/types/enum.rb +4 -0
- data/lib/packetgen/types/fieldable.rb +65 -0
- data/lib/packetgen/types/fields.rb +180 -113
- data/lib/packetgen/types/int.rb +15 -1
- data/lib/packetgen/types/int_string.rb +8 -0
- data/lib/packetgen/types/length_from.rb +18 -10
- data/lib/packetgen/types/oui.rb +2 -0
- data/lib/packetgen/types/string.rb +58 -7
- data/lib/packetgen/types/tlv.rb +2 -0
- data/lib/packetgen/unknown_packet.rb +84 -0
- data/lib/packetgen/utils.rb +6 -7
- data/lib/packetgen/version.rb +1 -1
- metadata +18 -15
@@ -43,28 +43,32 @@ module PacketGen
|
|
43
43
|
self << Types::IntString.new
|
44
44
|
end
|
45
45
|
|
46
|
+
# Clear name
|
47
|
+
# @return [void]
|
48
|
+
def clear
|
49
|
+
super
|
50
|
+
@pointer = nil
|
51
|
+
@pointer_name = nil
|
52
|
+
end
|
53
|
+
|
46
54
|
# Read a sequence of label from a string
|
47
55
|
# @param [String] str binary string
|
48
56
|
# @return [Name] self
|
49
57
|
def read(str)
|
50
|
-
@pointer = nil
|
51
|
-
@pointer_name = nil
|
52
58
|
clear
|
53
59
|
return self if str.nil?
|
54
60
|
|
55
61
|
PacketGen.force_binary str
|
56
62
|
start = 0
|
57
63
|
loop do
|
58
|
-
index = str[start, 2].
|
64
|
+
index = str[start, 2].unpack1('n')
|
59
65
|
if pointer? index
|
60
66
|
# Pointer on another label
|
61
67
|
@pointer = str[start, 2]
|
62
68
|
break
|
63
69
|
else
|
64
|
-
label =
|
65
|
-
label.read(str[start..-1])
|
70
|
+
label = add_label_from(str[start..-1])
|
66
71
|
start += label.sz
|
67
|
-
self << label
|
68
72
|
break if label.length.zero? || str[start..-1].length.zero?
|
69
73
|
end
|
70
74
|
end
|
@@ -101,7 +105,7 @@ module PacketGen
|
|
101
105
|
return nil unless @pointer
|
102
106
|
return @pointer_name if @pointer_name
|
103
107
|
|
104
|
-
index = @pointer.
|
108
|
+
index = @pointer.unpack1('n')
|
105
109
|
mask = ~POINTER_MASK & 0xffff
|
106
110
|
ptr = index & mask
|
107
111
|
name = Name.new
|
@@ -112,6 +116,13 @@ module PacketGen
|
|
112
116
|
def record_from_hash(_hsh)
|
113
117
|
raise NotImplementedError, "not supported by #{self.class}"
|
114
118
|
end
|
119
|
+
|
120
|
+
def add_label_from(str)
|
121
|
+
label = Types::IntString.new
|
122
|
+
label.read(str)
|
123
|
+
self << label
|
124
|
+
label
|
125
|
+
end
|
115
126
|
end
|
116
127
|
end
|
117
128
|
end
|
@@ -28,7 +28,7 @@ module PacketGen
|
|
28
28
|
define_field :dlt, Types::Int32le
|
29
29
|
# @!attribute ppi_fields
|
30
30
|
# @return [Type::String] concatenation of PPI fields
|
31
|
-
define_field :ppi_fields, Types::String
|
31
|
+
define_field :ppi_fields, Types::String, builder: ->(h, t) { t.new(length_from: -> { h.length - 8 }) }
|
32
32
|
# @!attribute body
|
33
33
|
# @return [Type::String]
|
34
34
|
define_field :body, Types::String
|
@@ -36,21 +36,6 @@ module PacketGen
|
|
36
36
|
# @return [Boolean] align flag from {#flags} attribute
|
37
37
|
define_bit_fields_on :flags, :reserved, 7, :align
|
38
38
|
|
39
|
-
# @param [String] str
|
40
|
-
# @return [PPI] self
|
41
|
-
def read(str)
|
42
|
-
return self if str.nil?
|
43
|
-
|
44
|
-
force_binary str
|
45
|
-
self[:version].read str[0, 1]
|
46
|
-
self[:flags].read str[1, 1]
|
47
|
-
self[:length].read str[2, 2]
|
48
|
-
self[:dlt].read str[4, 4]
|
49
|
-
self[:ppi_fields].read str[8, length - 8]
|
50
|
-
self[:body].read str[length, str.size]
|
51
|
-
self
|
52
|
-
end
|
53
|
-
|
54
39
|
# Check version field
|
55
40
|
# @see [Base#parse?]
|
56
41
|
def parse?
|
@@ -91,26 +76,11 @@ module PacketGen
|
|
91
76
|
define_field :present_flags, Types::Int32le
|
92
77
|
# @!attribute radio_fields
|
93
78
|
# @return [Type::String] concatenation of RadioTap fields
|
94
|
-
define_field :radio_fields, Types::String
|
79
|
+
define_field :radio_fields, Types::String, builder: ->(h, t) { t.new(length_from: -> { h.length - 8 }) }
|
95
80
|
# @!attribute body
|
96
81
|
# @return [Type::String]
|
97
82
|
define_field :body, Types::String
|
98
83
|
|
99
|
-
# @param [String] str
|
100
|
-
# @return [RadioTap] self
|
101
|
-
def read(str)
|
102
|
-
return self if str.nil?
|
103
|
-
|
104
|
-
force_binary str
|
105
|
-
self[:version].read str[0, 1]
|
106
|
-
self[:pad].read str[1, 1]
|
107
|
-
self[:length].read str[2, 2]
|
108
|
-
self[:present_flags].read str[4, 4]
|
109
|
-
self[:radio_fields].read str[8, length - 8]
|
110
|
-
self[:body].read str[length, str.size]
|
111
|
-
self
|
112
|
-
end
|
113
|
-
|
114
84
|
# Check version field
|
115
85
|
# @see [Base#parse?]
|
116
86
|
def parse?
|
@@ -310,7 +280,7 @@ module PacketGen
|
|
310
280
|
def read(str)
|
311
281
|
fcs = Dot11.fcs?
|
312
282
|
|
313
|
-
if self.
|
283
|
+
if self.instance_of? Dot11
|
314
284
|
return self if str.nil?
|
315
285
|
|
316
286
|
force_binary str
|
@@ -353,7 +323,7 @@ module PacketGen
|
|
353
323
|
|
354
324
|
# @return [String]
|
355
325
|
def inspect
|
356
|
-
str = if self.
|
326
|
+
str = if self.instance_of? Dot11
|
357
327
|
Inspect.dashed_line("#{self.class} #{human_type}", 1)
|
358
328
|
elsif self.respond_to? :human_subtype
|
359
329
|
Inspect.dashed_line("#{self.class} #{human_subtype}", 1)
|
@@ -390,27 +360,44 @@ module PacketGen
|
|
390
360
|
|
391
361
|
private
|
392
362
|
|
393
|
-
def
|
363
|
+
def remove_from_applicable_fields(fields)
|
364
|
+
fields = [fields] unless fields.is_a? Array
|
365
|
+
@applicable_fields -= fields
|
366
|
+
end
|
367
|
+
|
368
|
+
def handle_mac4
|
394
369
|
if to_ds? && from_ds?
|
395
370
|
@applicable_fields[6, 0] = :mac4 unless @applicable_fields.include? :mac4
|
396
371
|
else
|
397
|
-
|
372
|
+
remove_from_applicable_fields :mac4
|
398
373
|
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def handle_ht_ctrl
|
399
377
|
if order?
|
400
378
|
unless @applicable_fields.include? :ht_ctrl
|
401
379
|
idx = @applicable_fields.index(:body)
|
402
380
|
@applicable_fields[idx, 0] = :ht_ctrl
|
403
381
|
end
|
404
382
|
else
|
405
|
-
|
383
|
+
remove_from_applicable_fields %i[ht_ctrl]
|
406
384
|
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def handle_fcs
|
407
388
|
if Dot11.fcs?
|
408
389
|
@applicable_fields << :fcs unless @applicable_fields.include? :fcs
|
409
390
|
else
|
410
|
-
|
391
|
+
remove_from_applicable_fields :fcs
|
411
392
|
end
|
412
393
|
end
|
413
394
|
|
395
|
+
def define_applicable_fields
|
396
|
+
handle_mac4
|
397
|
+
handle_ht_ctrl
|
398
|
+
handle_fcs
|
399
|
+
end
|
400
|
+
|
414
401
|
def private_read(str, fcs)
|
415
402
|
self[:frame_ctrl].read str[0, 2]
|
416
403
|
define_applicable_fields
|
@@ -44,22 +44,22 @@ module PacketGen
|
|
44
44
|
case ds
|
45
45
|
when 0
|
46
46
|
# MAC1: RA/DA, MAC2: TA/SA
|
47
|
-
|
47
|
+
invert_mac :mac1, :mac2
|
48
48
|
when 1
|
49
49
|
# MAC1: RA/BSSID, MAC2: TA/SA, MAC3: DA
|
50
|
-
|
50
|
+
invert_mac :mac1, :mac2
|
51
51
|
self.to_ds = false
|
52
52
|
self.from_ds = true
|
53
53
|
when 2
|
54
54
|
# MAC1: RA/DA, MAC2: BSSID, MAC3: SA or BSSID
|
55
|
-
|
55
|
+
invert_mac :mac1, :mac2
|
56
56
|
self.to_ds = true
|
57
57
|
self.from_ds = false
|
58
58
|
when 3
|
59
59
|
# MAC1: RA, MAC2: TA
|
60
|
-
|
60
|
+
invert_mac :mac1, :mac2
|
61
61
|
# MAC3: DA, MAC4: SA
|
62
|
-
|
62
|
+
invert_mac :mac3, :mac4
|
63
63
|
end
|
64
64
|
self
|
65
65
|
end
|
@@ -67,59 +67,49 @@ module PacketGen
|
|
67
67
|
# Get destination MAC address
|
68
68
|
# @return [String]
|
69
69
|
def dst
|
70
|
-
|
71
|
-
|
72
|
-
when 0, 2
|
73
|
-
self.mac1
|
74
|
-
when 1, 3
|
75
|
-
self.mac3
|
76
|
-
end
|
70
|
+
_src_mac, dst_mac = src_dst_from_mac
|
71
|
+
self.send(dst_mac)
|
77
72
|
end
|
78
73
|
|
79
74
|
# Set destination MAC address
|
80
75
|
# @param [String] mac MAC address to set
|
81
76
|
# @return [String]
|
82
77
|
def dst=(mac)
|
83
|
-
|
84
|
-
|
85
|
-
when 0, 2
|
86
|
-
self.mac1 = mac
|
87
|
-
when 1, 3
|
88
|
-
self.mac3 = mac
|
89
|
-
end
|
78
|
+
_src_mac, dst_mac = src_dst_from_mac
|
79
|
+
self.send("#{dst_mac}=", mac)
|
90
80
|
end
|
91
81
|
|
92
82
|
# Get source MAC address
|
93
83
|
# @return [String]
|
94
84
|
def src
|
95
|
-
|
96
|
-
|
97
|
-
when 0, 1
|
98
|
-
self.mac2
|
99
|
-
when 2
|
100
|
-
self.mac3
|
101
|
-
when 3
|
102
|
-
self.mac4
|
103
|
-
end
|
85
|
+
src_mac, = src_dst_from_mac
|
86
|
+
self.send(src_mac)
|
104
87
|
end
|
105
88
|
|
106
89
|
# Set source MAC address
|
107
90
|
# @param [String] mac MAC address to set
|
108
91
|
# @return [String]
|
109
92
|
def src=(mac)
|
93
|
+
src_mac, = src_dst_from_mac
|
94
|
+
self.send("#{src_mac}=", mac)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def src_dst_from_mac
|
110
100
|
ds = frame_ctrl & 3
|
111
101
|
case ds
|
112
|
-
when 0
|
113
|
-
|
102
|
+
when 0
|
103
|
+
%i[mac2 mac1]
|
104
|
+
when 1
|
105
|
+
%i[mac2 mac3]
|
114
106
|
when 2
|
115
|
-
|
107
|
+
%i[mac3 mac1]
|
116
108
|
when 3
|
117
|
-
|
109
|
+
%i[mac4 mac3]
|
118
110
|
end
|
119
111
|
end
|
120
112
|
|
121
|
-
private
|
122
|
-
|
123
113
|
def define_applicable_fields
|
124
114
|
super
|
125
115
|
if (subtype >= 8) && !@applicable_fields.include?(:qos_ctrl)
|
@@ -135,6 +125,10 @@ module PacketGen
|
|
135
125
|
@applicable_fields -= %i[qos_ctrl]
|
136
126
|
end
|
137
127
|
end
|
128
|
+
|
129
|
+
def invert_mac(mac1, mac2)
|
130
|
+
self[mac1], self[mac2] = self[mac2], self[mac1]
|
131
|
+
end
|
138
132
|
end
|
139
133
|
end
|
140
134
|
end
|
@@ -45,20 +45,7 @@ module PacketGen
|
|
45
45
|
define_field :length, Types::Int16
|
46
46
|
# @!attribute body
|
47
47
|
# @return [Types::String,Header::Base]
|
48
|
-
define_field :body, Types::String
|
49
|
-
|
50
|
-
# Populate object from string
|
51
|
-
# @param [String] str
|
52
|
-
# @return [self]
|
53
|
-
def read(str)
|
54
|
-
return self if str.nil?
|
55
|
-
|
56
|
-
self[:version].read(str[0, 1])
|
57
|
-
self[:type].read(str[1, 1])
|
58
|
-
self[:length].read(str[2, 2])
|
59
|
-
self[:body].read(str[4, self.length])
|
60
|
-
self
|
61
|
-
end
|
48
|
+
define_field :body, Types::String, builder: ->(h, t) { t.new(length_from: h[:length]) }
|
62
49
|
|
63
50
|
# Get human readable type
|
64
51
|
# @return [String]
|
data/lib/packetgen/header/eap.rb
CHANGED
@@ -95,7 +95,8 @@ module PacketGen
|
|
95
95
|
# This field is present only for Request or Response packets,
|
96
96
|
# with type different from Expanded Types (254).
|
97
97
|
# @return [Integer] 8-bit request or response type
|
98
|
-
define_field :type, Types::Int8Enum,
|
98
|
+
define_field :type, Types::Int8Enum,
|
99
|
+
enum: TYPES,
|
99
100
|
optional: ->(eap) { eap.type? }
|
100
101
|
|
101
102
|
# @!attribute vendor_id
|
@@ -131,22 +132,18 @@ module PacketGen
|
|
131
132
|
# may be determined
|
132
133
|
def read(str)
|
133
134
|
super str
|
134
|
-
return self unless self.
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
else
|
147
|
-
self
|
148
|
-
end
|
149
|
-
obj
|
135
|
+
return self unless self.instance_of?(EAP)
|
136
|
+
return self unless type?
|
137
|
+
|
138
|
+
case self.type
|
139
|
+
when 4
|
140
|
+
EAP::MD5.new.read(str)
|
141
|
+
when 13
|
142
|
+
EAP::TLS.new.read(str)
|
143
|
+
when 21
|
144
|
+
EAP::TTLS.new.read(str)
|
145
|
+
when 43
|
146
|
+
EAP::FAST.new.read(str)
|
150
147
|
else
|
151
148
|
self
|
152
149
|
end
|
data/lib/packetgen/header/eth.rb
CHANGED
@@ -33,6 +33,8 @@ module PacketGen
|
|
33
33
|
# Ethernet MAC address, as a group of 6 bytes
|
34
34
|
# @author Sylvain Daubert
|
35
35
|
class MacAddr < Types::Fields
|
36
|
+
include Types::Fieldable
|
37
|
+
|
36
38
|
# @!attribute a0
|
37
39
|
# @return [Integer] first byte from MacAddr
|
38
40
|
define_field :a0, Types::Int8
|
@@ -61,12 +63,9 @@ module PacketGen
|
|
61
63
|
bytes = str.split(/:/)
|
62
64
|
raise ArgumentError, 'not a MAC address' unless bytes.size == 6
|
63
65
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
self[:a3].read(bytes[3].to_i(16))
|
68
|
-
self[:a4].read(bytes[4].to_i(16))
|
69
|
-
self[:a5].read(bytes[5].to_i(16))
|
66
|
+
6.times do |i|
|
67
|
+
self["a#{i}".to_sym].read(bytes[i].to_i(16))
|
68
|
+
end
|
70
69
|
self
|
71
70
|
end
|
72
71
|
|
@@ -12,13 +12,15 @@ module PacketGen
|
|
12
12
|
# @abstract Base class for HTTP headers.
|
13
13
|
# @author Kent 'picat' Gruber
|
14
14
|
class Headers
|
15
|
+
include Types::Fieldable
|
16
|
+
|
15
17
|
# Underlying Headers data (or nil).
|
16
18
|
# @return [Hash, nil]
|
17
19
|
attr_reader :data
|
18
20
|
alias to_h data
|
19
21
|
|
20
22
|
def initialize
|
21
|
-
@data =
|
23
|
+
@data = {}
|
22
24
|
end
|
23
25
|
|
24
26
|
# Populate object from a string or directly from a hash.
|
@@ -46,7 +48,7 @@ module PacketGen
|
|
46
48
|
|
47
49
|
d = []
|
48
50
|
@data.map do |k, v|
|
49
|
-
d << k
|
51
|
+
d << "#{k}: #{v}"
|
50
52
|
end
|
51
53
|
d.join("\r\n") << "\r\n\r\n"
|
52
54
|
end
|
@@ -67,26 +67,24 @@ module PacketGen
|
|
67
67
|
# Read in the HTTP portion of the packet, and parse it.
|
68
68
|
# @return [PacketGen::HTTP::Request]
|
69
69
|
def read(str)
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
self[:path].read first_line[1]
|
77
|
-
self[:version].read first_line[2]
|
70
|
+
lines = lines(str)
|
71
|
+
first_line_words = lines.shift.split
|
72
|
+
self[:verb].read first_line_words[0]
|
73
|
+
self[:path].read first_line_words[1]
|
74
|
+
self[:version].read first_line_words[2]
|
75
|
+
|
78
76
|
# requests can sometimes have a payload
|
79
|
-
|
80
|
-
data = str[data_index + 1..-1].join("\n")
|
81
|
-
headers = str[0..data_index - 1].join("\n")
|
82
|
-
else
|
83
|
-
headers = str.join("\n")
|
84
|
-
end
|
77
|
+
headers, data = headers_and_payload_from_lines(lines)
|
85
78
|
self[:headers].read(headers)
|
86
|
-
self[:body].read
|
79
|
+
self[:body].read(data)
|
80
|
+
|
87
81
|
self
|
88
82
|
end
|
89
83
|
|
84
|
+
def parse?
|
85
|
+
VERBS.include?(self.verb) && self.version.start_with?('HTTP/1.')
|
86
|
+
end
|
87
|
+
|
90
88
|
# String representation of data.
|
91
89
|
# @return [String]
|
92
90
|
def to_s
|
@@ -94,13 +92,34 @@ module PacketGen
|
|
94
92
|
raise FormatError, 'Missing #path.' if self.path.empty?
|
95
93
|
raise FormatError, 'Missing #version.' if self.version.empty?
|
96
94
|
|
97
|
-
|
98
|
-
|
95
|
+
"#{self.verb.dup} #{self.path} #{self.version}\r\n#{self[:headers]}#{self.body}"
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# @todo check verb is correct or raise a ParseError
|
101
|
+
def lines(str)
|
102
|
+
str = str.bytes.map!(&:chr).join unless str.valid_encoding?
|
103
|
+
# vrb = HTTP::VERBS.detect { |verb| str.include?(verb) }
|
104
|
+
|
105
|
+
str.split("\r\n").map(&:chomp)
|
106
|
+
end
|
107
|
+
|
108
|
+
def headers_and_payload_from_lines(lines)
|
109
|
+
if (data_index = lines.find_index(''))
|
110
|
+
data = lines[data_index + 1..-1].join("\n")
|
111
|
+
headers = lines[0..data_index - 1].join("\n")
|
112
|
+
else
|
113
|
+
headers = lines.join("\n")
|
114
|
+
data = nil
|
115
|
+
end
|
116
|
+
|
117
|
+
[headers, data]
|
99
118
|
end
|
100
119
|
end
|
101
120
|
end
|
102
121
|
|
103
122
|
self.add_class HTTP::Request
|
104
|
-
TCP.bind HTTP::Request, body: ->(b) { HTTP::REQUEST_REGEX =~ b
|
123
|
+
TCP.bind HTTP::Request, body: ->(b) { b.nil? ? '' : HTTP::REQUEST_REGEX =~ b }
|
105
124
|
end
|
106
125
|
end
|