packetgen 3.1.6 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/bin/pgconsole +3 -3
  4. data/lib/packetgen/capture.rb +5 -1
  5. data/lib/packetgen/header/arp.rb +24 -13
  6. data/lib/packetgen/header/asn1_base.rb +19 -9
  7. data/lib/packetgen/header/base.rb +1 -1
  8. data/lib/packetgen/header/dhcpv6/option.rb +11 -13
  9. data/lib/packetgen/header/dns/opt.rb +2 -2
  10. data/lib/packetgen/header/dns/rr.rb +61 -28
  11. data/lib/packetgen/header/dns.rb +13 -6
  12. data/lib/packetgen/header/dot11/data.rb +19 -29
  13. data/lib/packetgen/header/dot11/management.rb +2 -5
  14. data/lib/packetgen/header/dot11.rb +1 -1
  15. data/lib/packetgen/header/eap.rb +1 -1
  16. data/lib/packetgen/header/eth.rb +1 -1
  17. data/lib/packetgen/header/http/request.rb +5 -1
  18. data/lib/packetgen/header/http/response.rb +46 -20
  19. data/lib/packetgen/header/http/verbs.rb +1 -1
  20. data/lib/packetgen/header/igmp.rb +1 -1
  21. data/lib/packetgen/header/ip/option.rb +2 -1
  22. data/lib/packetgen/header/ipv6/addr.rb +3 -0
  23. data/lib/packetgen/header/mdns.rb +19 -25
  24. data/lib/packetgen/header/mld.rb +1 -1
  25. data/lib/packetgen/header/ospfv2/lsa.rb +7 -3
  26. data/lib/packetgen/header/ospfv2/lsa_header.rb +2 -4
  27. data/lib/packetgen/header/ospfv2.rb +1 -1
  28. data/lib/packetgen/header/ospfv3/ipv6_prefix.rb +14 -5
  29. data/lib/packetgen/header/ospfv3/lsa.rb +8 -4
  30. data/lib/packetgen/header/ospfv3/lsa_header.rb +2 -4
  31. data/lib/packetgen/header/ospfv3.rb +1 -1
  32. data/lib/packetgen/header/snmp.rb +20 -14
  33. data/lib/packetgen/header/tcp/option.rb +1 -1
  34. data/lib/packetgen/header/tcp.rb +12 -5
  35. data/lib/packetgen/header/tftp.rb +15 -9
  36. data/lib/packetgen/inspect.rb +15 -9
  37. data/lib/packetgen/packet.rb +51 -5
  38. data/lib/packetgen/pcapng/file.rb +17 -15
  39. data/lib/packetgen/pcapng.rb +1 -0
  40. data/lib/packetgen/pcaprub_wrapper.rb +0 -4
  41. data/lib/packetgen/types/abstract_tlv.rb +1 -1
  42. data/lib/packetgen/types/array.rb +8 -1
  43. data/lib/packetgen/types/cstring.rb +20 -7
  44. data/lib/packetgen/types/fields.rb +19 -19
  45. data/lib/packetgen/types/int.rb +7 -0
  46. data/lib/packetgen/types/oui.rb +1 -1
  47. data/lib/packetgen/types/string.rb +16 -3
  48. data/lib/packetgen/types/tlv.rb +17 -9
  49. data/lib/packetgen/unknown_packet.rb +84 -0
  50. data/lib/packetgen/utils.rb +60 -27
  51. data/lib/packetgen/version.rb +1 -1
  52. data/lib/packetgen.rb +13 -4
  53. metadata +7 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8959dbe60df80490d1ac31eaf40b05a206860bf6bed306997a66ca9c77dfb0b
4
- data.tar.gz: 4615a1084f86e4b0ab216b3766dbde7d1b0b2b1e3c2e6b0d2e1a63aba1ff7bd6
3
+ metadata.gz: 79dc573da10eb15bbd9f38e0c84ecdf9a92d076980628635d1b9e240e3ca2210
4
+ data.tar.gz: 0ae2bafa08e5a4a0b176276a141b8c8326265873bb20d86a75e07bb7018275ac
5
5
  SHA512:
6
- metadata.gz: 636f0dcea926b57cb7ec7a824744dfbcab9c6ddbb7f6749d8fda8f63fec410d20dcf199f362ae63878104725e94c7075d17f94b3f0dc8458b3d975e9521343da
7
- data.tar.gz: 50e679400b1f882fbe2d3bc9e3ba5e01bb24dbe4a83b3372b999cbe0d1565703a7fc6afc08c437900e23b92c549c46ad422e3e76140254034c8840ba3a8ecb2e
6
+ metadata.gz: 7c405a57b79c4a9bf0d9032b34d7381b33e8c0705c554023f6a8131c19c48fb0118201d94da62e4f74b9d53551cdbcd434cb6363f27a84e5c3e8459cb28d613b
7
+ data.tar.gz: 835969235415864e05b227dd5f0fb48535e3f414e60852cf788d6d90bf29be9a3a8dcf9b636ce8d500d29720753246e11459da925fea3f72f6cb8e11291565b8
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
 
2
2
  [![Gem Version](https://badge.fury.io/rb/packetgen.svg)](https://badge.fury.io/rb/packetgen)
3
- [![Build Status](https://travis-ci.org/sdaubert/packetgen.svg?branch=master)](https://travis-ci.org/sdaubert/packetgen)
4
-
3
+ [![Action status](https://github.com/sdaubert/packetgen/workflows/ci/badge.svg?branch=master)](https://github.com/sdaubert/packetgen/actions?query=workflow%3Aci)
5
4
  # PacketGen
6
5
 
7
6
  PacketGen provides simple ways to generate, send and capture network packets.
data/bin/pgconsole CHANGED
@@ -31,7 +31,7 @@ class PgConsole
31
31
  define_method m, Utils.method(m).to_proc
32
32
  end
33
33
 
34
- def get_binding
34
+ def console_binding
35
35
  binding
36
36
  end
37
37
  end
@@ -52,10 +52,10 @@ if use_pry
52
52
  }
53
53
  ]
54
54
  Pry.config.prompt_name = 'pg'
55
- PgConsole.new.get_binding.pry
55
+ PgConsole.new.console_binding.pry
56
56
  else
57
57
  IRB.setup nil
58
- irb = IRB::Irb.new(IRB::WorkSpace.new(PgConsole.new.get_binding))
58
+ irb = IRB::Irb.new(IRB::WorkSpace.new(PgConsole.new.console_binding))
59
59
  IRB.conf[:MAIN_CONTEXT] = irb.context
60
60
  irb.context.auto_indent_mode = true
61
61
  irb.context.prompt_i = 'pg> '
@@ -122,7 +122,11 @@ module PacketGen
122
122
  def add_packet(data, &block)
123
123
  raw_packets << data
124
124
  if @parse
125
- packet = Packet.parse(data)
125
+ begin
126
+ packet = Packet.parse(data)
127
+ rescue ParseError
128
+ packet = UnknownPacket.new.parse(data)
129
+ end
126
130
  packets << packet
127
131
  block&.call(packet)
128
132
  elsif block
@@ -81,15 +81,7 @@ module PacketGen
81
81
  # @option options [String] :tha target hardware address
82
82
  # @option options [String] :tpa targetr internet address
83
83
  def initialize(options={})
84
- options[:hrd] ||= options[:htype]
85
- options[:pro] ||= options[:ptype]
86
- options[:hln] ||= options[:hlen]
87
- options[:pln] ||= options[:plen]
88
- options[:op] ||= options[:opcode]
89
- options[:sha] ||= options[:src_mac]
90
- options[:spa] ||= options[:src_ip]
91
- options[:tha] ||= options[:dst_mac]
92
- options[:tpa] ||= options[:dst_ip]
84
+ handle_options(options)
93
85
  super
94
86
  end
95
87
 
@@ -118,16 +110,35 @@ module PacketGen
118
110
  case opcode.to_i
119
111
  when 1
120
112
  self.opcode = 2
121
- self.spa, self.tpa = self.tpa, self.spa
122
- self.sha, self.tha = self.tha, self.sha
113
+ invert_addresses
123
114
  when 2
124
115
  self.opcode = 1
125
- self.spa, self.tpa = self.tpa, self.spa
126
- self.sha = self.tha
116
+ invert_addresses
127
117
  self[:tha].from_human('00:00:00:00:00:00')
128
118
  end
129
119
  self
130
120
  end
121
+
122
+ private
123
+
124
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
125
+ def handle_options(options)
126
+ options[:hrd] ||= options[:htype]
127
+ options[:pro] ||= options[:ptype]
128
+ options[:hln] ||= options[:hlen]
129
+ options[:pln] ||= options[:plen]
130
+ options[:op] ||= options[:opcode]
131
+ options[:sha] ||= options[:src_mac]
132
+ options[:spa] ||= options[:src_ip]
133
+ options[:tha] ||= options[:dst_mac]
134
+ options[:tpa] ||= options[:dst_ip]
135
+ end
136
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
137
+
138
+ def invert_addresses
139
+ self.spa, self.tpa = self.tpa, self.spa
140
+ self.sha, self.tha = self.tha, self.sha
141
+ end
131
142
  end
132
143
 
133
144
  self.add_class ARP
@@ -19,14 +19,20 @@ module PacketGen
19
19
  class ASN1Base < RASN1::Model
20
20
  include Headerable
21
21
 
22
- # Define some methods from given ASN.1 fields to mimic {Base} attributes
23
- # @param [Array<Symbol>] attributes
24
- # @return [void]
25
- def self.define_attributes(*attributes)
26
- @attributes = attributes
27
- attributes.each do |attr|
28
- class_eval "def #{attr}; @elements[:#{attr}].value; end\n" \
29
- "def #{attr}=(v); @elements[:#{attr}].value = v; end"
22
+ class << self
23
+ # Define some methods from given ASN.1 fields to mimic {Base} attributes
24
+ # @param [Array<Symbol>] attributes
25
+ # @return [void]
26
+ def define_attributes(*attributes)
27
+ @attributes = attributes
28
+ attributes.each do |attr|
29
+ class_eval "def #{attr}; @elements[:#{attr}].value; end\n" \
30
+ "def #{attr}=(v); @elements[:#{attr}].value = v; end"
31
+ end
32
+ end
33
+
34
+ def known_headers
35
+ @known_headers ||= {}.freeze
30
36
  end
31
37
  end
32
38
 
@@ -37,7 +43,11 @@ module PacketGen
37
43
  # @param [String] str
38
44
  # @return [ASN1Base] self
39
45
  def read(str)
40
- parse(str, ber: true)
46
+ begin
47
+ parse(str, ber: true)
48
+ rescue RASN1::ASN1Error
49
+ # suppress exception to allow guessing
50
+ end
41
51
  self
42
52
  end
43
53
 
@@ -142,7 +142,7 @@ module PacketGen
142
142
  klass.class_eval { @known_headers = {} }
143
143
  end
144
144
 
145
- class <<self
145
+ class << self
146
146
  # @api private
147
147
  # Get known headers
148
148
  # @return [Hash] keys: header classes, values: hashes
@@ -53,19 +53,17 @@ module PacketGen
53
53
  # @param [Hash] options
54
54
  # @return [Option]
55
55
  def new(options={})
56
- if self == Option
57
- case options[:type]
58
- when Integer
59
- klass = Option.subclasses[options[:type]]
60
- klass&.new(options)
61
- when String
62
- if DHCPv6.const_defined?(options[:type])
63
- klass = DHCPv6.const_get(options[:type])
64
- options.delete :type
65
- klass.new(options) if klass < Option
66
- end
67
- else
68
- super
56
+ return super unless self == Option
57
+
58
+ case options[:type]
59
+ when Integer
60
+ klass = Option.subclasses[options[:type]]
61
+ klass&.new(options)
62
+ when String
63
+ if DHCPv6.const_defined?(options[:type])
64
+ klass = DHCPv6.const_get(options[:type])
65
+ options.delete :type
66
+ klass.new(options) if klass < Option
69
67
  end
70
68
  else
71
69
  super
@@ -125,8 +125,8 @@ module PacketGen
125
125
  # @return [String]
126
126
  def to_human
127
127
  "#{name} #{human_type} UDPsize:#{udp_size} " \
128
- "extRCODE:#{ext_rcode} EDNSversion:#{version} flags:#{human_flags} " \
129
- "options:#{options.empty? ? 'none' : options.to_human}"
128
+ "extRCODE:#{ext_rcode} EDNSversion:#{version} flags:#{human_flags} " \
129
+ "options:#{options.empty? ? 'none' : options.to_human}"
130
130
  end
131
131
  end
132
132
  end
@@ -50,48 +50,29 @@ module PacketGen
50
50
  self[:rdata].read data
51
51
  end
52
52
 
53
+ # rubocop:disable Metrics/AbcSize
54
+
53
55
  # Get human readable rdata
54
56
  # @return [String]
55
57
  def human_rdata
56
- str = self[:rdata].inspect
57
-
58
- # Need to mask: mDNS uses leftmost bit as a flag (CACHE FLUSH)
59
- if self.rrclass & 0x7fff == CLASSES['IN']
60
- case type
61
- when TYPES['A'], TYPES['AAAA']
62
- str = IPAddr.new_ntoh(self[:rdata]).to_s
63
- end
64
- end
58
+ str = human_ip_rdata || self[:rdata].inspect
65
59
 
66
- name = Name.new
67
- name.dns = self[:name].dns
68
60
  case type
69
61
  when TYPES['NS'], TYPES['PTR'], TYPES['CNAME']
62
+ name = Name.new
63
+ name.dns = self[:name].dns
70
64
  str = name.read(self[:rdata]).to_human
71
65
  when TYPES['SOA']
72
- mname = name.read(self[:rdata]).dup
73
- rname = name.read(self[:rdata][mname.sz..-1])
74
- serial = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz, 4])
75
- refresh = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 4, 4])
76
- retryi = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 8, 4])
77
- expire = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 12, 4])
78
- minimum = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 16, 4])
79
- str = "#{mname.to_human} #{rname.to_human} #{serial.to_i} #{refresh.to_i} " \
80
- "#{retryi.to_i} #{expire.to_i} #{minimum.to_i}"
66
+ str = human_soa_rdata
81
67
  when TYPES['MX']
82
- pref = Types::Int16.new.read(self[:rdata][0, 2])
83
- exchange = name.read(self[:rdata][2..-1]).to_human
84
- str = '%u %s' % [pref.to_i, exchange]
68
+ str = human_mx_data
85
69
  when TYPES['SRV']
86
- priority = Types::Int16.new.read(self[:rdata][0, 2])
87
- weight = Types::Int16.new.read(self[:rdata][2, 2])
88
- port = Types::Int16.new.read(self[:rdata][4, 2])
89
- target = name.read(self[:rdata][6, self[:rdata].size]).to_human
90
- str = "#{priority.to_i} #{weight.to_i} #{port.to_i} #{target}"
70
+ str = human_srv_data
91
71
  end
92
72
 
93
73
  str
94
74
  end
75
+ # rubocop:enable Metrics/AbcSize
95
76
 
96
77
  # Get human readable class
97
78
  # @return [String]
@@ -109,6 +90,58 @@ module PacketGen
109
90
  def to_human
110
91
  "#{human_type} #{human_rrclass} #{name} TTL #{ttl} #{human_rdata}"
111
92
  end
93
+
94
+ private
95
+
96
+ def human_ip_rdata
97
+ # Need to mask: mDNS uses leftmost bit as a flag (CACHE FLUSH)
98
+ return unless self.rrclass & 0x7fff == CLASSES['IN']
99
+
100
+ case type
101
+ when TYPES['A'], TYPES['AAAA']
102
+ IPAddr.new_ntoh(self[:rdata]).to_s
103
+ end
104
+ end
105
+
106
+ def human_mx_data
107
+ name = Name.new
108
+ name.dns = self[:name].dns
109
+
110
+ pref = Types::Int16.new.read(self[:rdata][0, 2])
111
+ exchange = name.read(self[:rdata][2..-1]).to_human
112
+
113
+ '%u %s' % [pref.to_i, exchange]
114
+ end
115
+
116
+ # rubocop:disable Metrics/AbcSize
117
+ def human_soa_rdata
118
+ name = Name.new
119
+ name.dns = self[:name].dns
120
+ mname = name.read(self[:rdata]).dup
121
+ rname = name.read(self[:rdata][mname.sz..-1])
122
+
123
+ serial = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz, 4])
124
+ refresh = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 4, 4])
125
+ retryi = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 8, 4])
126
+ expire = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 12, 4])
127
+ minimum = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 16, 4])
128
+
129
+ "#{mname.to_human} #{rname.to_human} #{serial.to_i} #{refresh.to_i} " \
130
+ "#{retryi.to_i} #{expire.to_i} #{minimum.to_i}"
131
+ end
132
+
133
+ def human_srv_data
134
+ name = Name.new
135
+ name.dns = self[:name].dns
136
+
137
+ priority = Types::Int16.new.read(self[:rdata][0, 2])
138
+ weight = Types::Int16.new.read(self[:rdata][2, 2])
139
+ port = Types::Int16.new.read(self[:rdata][4, 2])
140
+ target = name.read(self[:rdata][6, self[:rdata].size]).to_human
141
+
142
+ "#{priority.to_i} #{weight.to_i} #{port.to_i} #{target}"
143
+ end
144
+ # rubocop:enable Metrics/AbcSize
112
145
  end
113
146
  end
114
147
  end
@@ -241,18 +241,25 @@ module PacketGen
241
241
  super do |attr|
242
242
  next unless attr == :u16
243
243
 
244
- flags = %i[qr aa tc rd ra].select! { |flag| send "#{flag}?" }
245
- .map(&:to_s).join(',')
246
- str = Inspect.shift_level
247
- str << Inspect::FMT_ATTR % ['Flags', 'flags', flags]
248
- opcode = '%-16s (%u)' % [OPCODES.key(self.opcode), self.opcode]
244
+ str = inspect_flags
245
+
249
246
  str << Inspect.shift_level
247
+ opcode = '%-16s (%u)' % [OPCODES.key(self.opcode), self.opcode]
250
248
  str << Inspect::FMT_ATTR % ['Integer', 'opcode', opcode]
251
- rcode = '%-16s (%u)' % [RCODES.key(self.rcode), self.rcode]
249
+
252
250
  str << Inspect.shift_level
251
+ rcode = '%-16s (%u)' % [RCODES.key(self.rcode), self.rcode]
253
252
  str << Inspect::FMT_ATTR % ['Integer', 'rcode', rcode]
254
253
  end
255
254
  end
255
+
256
+ private
257
+
258
+ def inspect_flags
259
+ flags = %i[qr aa tc rd ra].select! { |flag| send "#{flag}?" }.map(&:to_s).join(',')
260
+ str = Inspect.shift_level
261
+ str << Inspect::FMT_ATTR % ['Flags', 'flags', flags]
262
+ end
256
263
  end
257
264
 
258
265
  self.add_class DNS
@@ -67,59 +67,49 @@ module PacketGen
67
67
  # Get destination MAC address
68
68
  # @return [String]
69
69
  def dst
70
- ds = frame_ctrl & 3
71
- case ds
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
- ds = frame_ctrl & 3
84
- case ds
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
- ds = frame_ctrl & 3
96
- case ds
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, 1
113
- self.mac2 = mac
102
+ when 0
103
+ %i[mac2 mac1]
104
+ when 1
105
+ %i[mac2 mac3]
114
106
  when 2
115
- self.mac3 = mac
107
+ %i[mac3 mac1]
116
108
  when 3
117
- self.mac4 = mac
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)
@@ -53,12 +53,9 @@ module PacketGen
53
53
  # @return [self]
54
54
  # @since 2.1.3
55
55
  def add_element(type:, value:)
56
- if self[:body].is_a? SubMngt
57
- self[:body].elements << { type: type, value: value }
58
- else
59
- raise FormatError, 'Before adding an Element, you have to add a Dot11::SubMngt subclass instance'
60
- end
56
+ raise FormatError, 'Before adding an Element, you have to add a Dot11::SubMngt subclass instance' unless self[:body].is_a? SubMngt
61
57
 
58
+ self[:body].elements << { type: type, value: value }
62
59
  self
63
60
  end
64
61
 
@@ -355,7 +355,7 @@ module PacketGen
355
355
  def added_to_packet(packet)
356
356
  return if packet.respond_to? :dot11
357
357
 
358
- packet.instance_eval("def dot11(arg=nil); header(#{self.class}, arg); end")
358
+ packet.instance_eval("def dot11(arg=nil); header(#{self.class}, arg); end") # def dot11(arg=nil); header(Dot11, arg); end
359
359
  end
360
360
 
361
361
  private
@@ -218,7 +218,7 @@ module PacketGen
218
218
  def added_to_packet(packet)
219
219
  return if packet.respond_to? :eap
220
220
 
221
- packet.instance_eval("def eap(arg=nil); header(#{self.class}, arg); end")
221
+ packet.instance_eval("def eap(arg=nil); header(#{self.class}, arg); end") # def eap(arg=nil); header(EAP, arg); end
222
222
  end
223
223
 
224
224
  # Invert between a request and a response packet. Not action for
@@ -60,7 +60,7 @@ module PacketGen
60
60
  def from_human(str)
61
61
  return self if str.nil?
62
62
 
63
- bytes = str.split(/:/)
63
+ bytes = str.split(':')
64
64
  raise ArgumentError, 'not a MAC address' unless bytes.size == 6
65
65
 
66
66
  6.times do |i|
@@ -81,6 +81,10 @@ module PacketGen
81
81
  self
82
82
  end
83
83
 
84
+ def parse?
85
+ VERBS.include?(self.verb) && self.version.start_with?('HTTP/1.')
86
+ end
87
+
84
88
  # String representation of data.
85
89
  # @return [String]
86
90
  def to_s
@@ -116,6 +120,6 @@ module PacketGen
116
120
  end
117
121
 
118
122
  self.add_class HTTP::Request
119
- TCP.bind HTTP::Request, body: ->(b) { HTTP::REQUEST_REGEX =~ b.chars.select(&:valid_encoding?).join }
123
+ TCP.bind HTTP::Request, body: ->(b) { b.nil? ? '' : HTTP::REQUEST_REGEX =~ b }
120
124
  end
121
125
  end
@@ -67,11 +67,42 @@ module PacketGen
67
67
  # Read in the HTTP portion of the packet, and parse it.
68
68
  # @return [PacketGen::HTTP::Response]
69
69
  def read(str)
70
- str = str.bytes.map!(&:chr).join unless str.valid_encoding?
71
- arr = str.split("\r\n")
70
+ headers, data = collect_headers_and_data(str)
71
+
72
+ unless headers.empty?
73
+ extract_info_from_first_line(headers)
74
+ self[:headers].read(headers.join("\n"))
75
+ end
76
+ self[:body].read data.join("\n")
77
+
78
+ self
79
+ end
80
+
81
+ def parse?
82
+ version.start_with?('HTTP/1.')
83
+ end
84
+
85
+ # String representation of data.
86
+ # @return [String]
87
+ def to_s
88
+ raise_on_bad_version_status
89
+
90
+ str = +''
91
+ str << self.version << ' ' << self.status_code << ' ' << self.status_mesg << "\r\n"
92
+ str << self[:headers].to_s if self[:headers].given?
93
+ str << self.body
94
+ end
95
+
96
+ private
97
+
98
+ def collect_headers_and_data(str)
72
99
  headers = [] # header stream
73
100
  data = [] # data stream
74
101
  switch = false
102
+
103
+ str = str.bytes.map!(&:chr).join unless str.valid_encoding?
104
+ arr = str.split("\r\n")
105
+
75
106
  arr.each do |line|
76
107
  if line.empty?
77
108
  data << line if switch # already done
@@ -85,33 +116,28 @@ module PacketGen
85
116
  headers << line
86
117
  end
87
118
  end
88
- unless headers.empty?
89
- first_line = headers.shift.split
90
- self[:version].read first_line[0]
91
- self[:status_code].read first_line[1]
92
- self[:status_mesg].read first_line[2..-1].join(' ')
93
- self[:headers].read(headers.join("\n"))
94
- end
95
- self[:body].read data.join("\n")
96
- self
119
+
120
+ [headers, data]
97
121
  end
98
122
 
99
- # String representation of data.
100
- # @return [String]
101
- def to_s
123
+ def extract_info_from_first_line(headers)
124
+ first_line = headers.shift.split
125
+ return if first_line.size < 3
126
+
127
+ self[:version].read first_line[0]
128
+ self[:status_code].read first_line[1]
129
+ self[:status_mesg].read first_line[2..-1].join(' ')
130
+ end
131
+
132
+ def raise_on_bad_version_status
102
133
  raise FormatError, 'Missing #status_code.' if self.status_code.empty?
103
134
  raise FormatError, 'Missing #status_mesg.' if self.status_mesg.empty?
104
135
  raise FormatError, 'Missing #version.' if self.version.empty?
105
-
106
- str = +''
107
- str << self.version << ' ' << self.status_code << ' ' << self.status_mesg << "\r\n"
108
- str << self[:headers].to_s if self[:headers].given?
109
- str << self.body
110
136
  end
111
137
  end
112
138
  end
113
139
 
114
140
  self.add_class HTTP::Response
115
- TCP.bind HTTP::Response, body: ->(b) { %r[^HTTP/1\.1\s\d{3,}\s.+] =~ b.chars.select(&:valid_encoding?).join }
141
+ TCP.bind HTTP::Response, body: ->(b) { b.nil? ? '' : %r[^HTTP/1\.1\s\d{3,}\s.+] =~ b }
116
142
  end
117
143
  end
@@ -16,7 +16,7 @@ module PacketGen
16
16
  VERBS = %w[GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATCH].freeze
17
17
 
18
18
  # Identifiable HTTP request regular expression.
19
- REQUEST_REGEX = Regexp.new("(#{VERBS.dup.join('|')})\\s+\\S+\\s+HTTP/1.1")
19
+ REQUEST_REGEX = Regexp.new("^(#{VERBS.dup.join('|')})\\s+\\S+\\s+HTTP/1.1")
20
20
  end
21
21
  end
22
22
  end
@@ -77,7 +77,7 @@ module PacketGen
77
77
  # directly called
78
78
  def added_to_packet(packet)
79
79
  igmp_idx = packet.headers.size
80
- packet.instance_eval "def igmpize() @headers[#{igmp_idx}].igmpize; end"
80
+ packet.instance_eval "def igmpize() @headers[#{igmp_idx}].igmpize; end" # def igmpize() @headers[2].igmpize; end
81
81
  end
82
82
 
83
83
  # Get human readbale type
@@ -55,7 +55,7 @@ module PacketGen
55
55
  # option data
56
56
  # @return [String]
57
57
  define_field :data, Types::String, optional: ->(h) { h.length > 2 },
58
- builder: ->(h, t) { t.new(length_from: -> { h.length - 2 }) }
58
+ builder: ->(h, t) { t.new(length_from: -> { h.length - 2 }) }
59
59
 
60
60
  # @!attribute copied
61
61
  # 1-bit copied flag from {#type} field
@@ -161,6 +161,7 @@ module PacketGen
161
161
 
162
162
  # Strict Source and Record Route IP option
163
163
  class SSRR < LSRR; end
164
+
164
165
  # Record Route IP option
165
166
  class RR < LSRR; end
166
167
 
@@ -49,6 +49,8 @@ module PacketGen
49
49
  # @return [Integer]
50
50
  define_field :a8, Types::Int16
51
51
 
52
+ # rubocop:disable Metrics/AbcSize
53
+
52
54
  # Read a colon-delimited address
53
55
  # @param [String] str
54
56
  # @return [self]
@@ -69,6 +71,7 @@ module PacketGen
69
71
  self.a8 = addri & 0xffff
70
72
  self
71
73
  end
74
+ # rubocop:enable Metrics/AbcSize
72
75
 
73
76
  # Addr6 in human readable form (colon-delimited hex string)
74
77
  # @return [String]