DIY-pcap 0.4.1 → 0.4.3

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.
Files changed (35) hide show
  1. data/.gitignore +4 -4
  2. data/DIY-pcap.gemspec +17 -17
  3. data/Gemfile +3 -3
  4. data/Rakefile +1 -1
  5. data/lib/DIY-pcap.rb +2 -2
  6. data/lib/diy/command.rb +7 -1
  7. data/lib/diy/controller.rb +10 -15
  8. data/lib/diy/live.rb +9 -2
  9. data/lib/diy/parser/mu/pcap/ethernet.rb +148 -148
  10. data/lib/diy/parser/mu/pcap/header.rb +75 -75
  11. data/lib/diy/parser/mu/pcap/io_pair.rb +67 -67
  12. data/lib/diy/parser/mu/pcap/io_wrapper.rb +76 -76
  13. data/lib/diy/parser/mu/pcap/ip.rb +61 -61
  14. data/lib/diy/parser/mu/pcap/ipv4.rb +257 -257
  15. data/lib/diy/parser/mu/pcap/ipv6.rb +148 -148
  16. data/lib/diy/parser/mu/pcap/packet.rb +104 -104
  17. data/lib/diy/parser/mu/pcap/pkthdr.rb +155 -155
  18. data/lib/diy/parser/mu/pcap/reader.rb +61 -61
  19. data/lib/diy/parser/mu/pcap/reader/http_family.rb +170 -170
  20. data/lib/diy/parser/mu/pcap/sctp.rb +367 -367
  21. data/lib/diy/parser/mu/pcap/sctp/chunk.rb +123 -123
  22. data/lib/diy/parser/mu/pcap/sctp/chunk/data.rb +134 -134
  23. data/lib/diy/parser/mu/pcap/sctp/chunk/init.rb +100 -100
  24. data/lib/diy/parser/mu/pcap/sctp/chunk/init_ack.rb +68 -68
  25. data/lib/diy/parser/mu/pcap/sctp/parameter.rb +110 -110
  26. data/lib/diy/parser/mu/pcap/sctp/parameter/ip_address.rb +48 -48
  27. data/lib/diy/parser/mu/pcap/stream_packetizer.rb +72 -72
  28. data/lib/diy/parser/mu/pcap/tcp.rb +505 -505
  29. data/lib/diy/parser/mu/pcap/udp.rb +69 -69
  30. data/lib/diy/parser/mu/scenario/pcap.rb +172 -172
  31. data/lib/diy/parser/mu/scenario/pcap/fields.rb +50 -50
  32. data/lib/diy/parser/mu/scenario/pcap/rtp.rb +71 -71
  33. data/lib/diy/parser/pcap.rb +109 -109
  34. data/lib/diy/version.rb +1 -1
  35. metadata +7 -9
@@ -1,155 +1,155 @@
1
- # http://www.mudynamics.com
2
- # http://labs.mudynamics.com
3
- # http://www.pcapr.net
4
-
5
- module Mu
6
- class Pcap
7
-
8
- class Pkthdr
9
- attr_accessor :endian, :ts_sec, :ts_usec, :caplen, :len, :pkt, :pkt_raw
10
-
11
- def initialize endian=BIG_ENDIAN, ts_sec=0, ts_usec=0, caplen=0, len=0, pkt=nil
12
- @endian = endian
13
- @ts_sec = ts_sec
14
- @ts_usec = ts_usec
15
- @caplen = caplen
16
- @len = len
17
- @pkt = pkt
18
- @pkt_raw = pkt
19
- end
20
-
21
- FMT_NNNN = 'NNNN'
22
- FMT_VVVV = 'VVVV'
23
- def self.read io, endian=BIG_ENDIAN
24
- if endian == BIG_ENDIAN
25
- format = FMT_NNNN
26
- elsif endian == LITTLE_ENDIAN
27
- format = FMT_VVVV
28
- end
29
- bytes = io.read 16
30
- if not bytes
31
- raise EOFError, 'Missing PCAP packet header'
32
- end
33
- if not bytes.length == 16
34
- raise ParseError, "Truncated PCAP packet header: expected 16 bytes, got #{bytes.length} bytes"
35
- end
36
- ts_sec, ts_usec, caplen, len = bytes.unpack format
37
- pkt = io.read(caplen)
38
- if not pkt
39
- raise ParseError, 'Missing PCAP packet header packet'
40
- end
41
- if not pkt.length == caplen
42
- raise ParseError, "Truncated PCAP packet header: expected #{pkthdr.caplen} bytes, got #{pkthdr.pkt.length} bytes"
43
- end
44
- pkthdr = Pkthdr.new endian, ts_sec, ts_usec, caplen, len, pkt
45
- return pkthdr
46
- end
47
-
48
- def write io
49
- if @pkt.is_a? String
50
- pkt = @pkt
51
- else
52
- string_io = StringIO.new
53
- @pkt.write string_io
54
- pkt = string_io.string
55
- end
56
- len = pkt.length
57
- bytes = [@ts_sec, @ts_usec, len, len].pack FMT_NNNN
58
- io.write bytes
59
- io.write pkt
60
- end
61
-
62
- def decode! endian, linktype
63
- @pkt = case linktype
64
- when DLT_NULL; Pkthdr.decode_null endian, @pkt
65
- when DLT_EN10MB; Pkthdr.decode_en10mb @pkt
66
- when DLT_RAW; raise NotImplementedError
67
- when DLT_LINUX_SLL; Pkthdr.decode_linux_sll @pkt
68
- else raise ParseError, "Unknown PCAP linktype: #{linktype}"
69
- end
70
- end
71
-
72
- # See http://wiki.wireshark.org/NullLoopback
73
- # and epan/aftypes.h in wireshark code.
74
- BSD_AF_INET6 = [
75
- OPENBSD_AF_INET6 = 24,
76
- FREEBSD_AF_INET6 = 28,
77
- DARWIN_AF_INET6 = 30
78
- ]
79
-
80
- def self.decode_null endian, bytes
81
- Pcap.assert bytes.length >= 4, 'Truncated PCAP packet header: ' +
82
- "expected at least 4 bytes, got #{bytes.length} bytes"
83
- if endian == BIG_ENDIAN
84
- format = 'N'
85
- elsif endian == LITTLE_ENDIAN
86
- format = 'V'
87
- end
88
- family = bytes[0, 4].unpack(format)[0]
89
- bytes = bytes[4..-1]
90
- ethernet = Ethernet.new
91
- ethernet.src = '00:01:01:00:00:01'
92
- ethernet.dst = '00:01:01:00:00:02'
93
- ethernet.payload = ethernet.payload_raw = bytes
94
- if family != Socket::AF_INET and family != Socket::AF_INET6 and
95
- not BSD_AF_INET6.include?(family)
96
- raise ParseError, "Unknown PCAP packet header family: #{family}"
97
- end
98
- begin
99
- case family
100
- when Socket::AF_INET
101
- ethernet.type = Ethernet::ETHERTYPE_IP
102
- ethernet.payload = IPv4.from_bytes ethernet.payload
103
- when Socket::AF_INET6, FREEBSD_AF_INET6, OPENBSD_AF_INET6, DARWIN_AF_INET6
104
- ethernet.type = Ethernet::ETHERTYPE_IP6
105
- ethernet.payload = IPv6.from_bytes ethernet.payload
106
- else
107
- raise NotImplementedError
108
- end
109
- rescue ParseError => e
110
- Pcap.warning e
111
- end
112
- return ethernet
113
- end
114
-
115
- def self.decode_en10mb bytes
116
- return Ethernet.from_bytes(bytes)
117
- end
118
-
119
- def self.decode_linux_sll bytes
120
- Pcap.assert bytes.length >= 16, 'Truncated PCAP packet header: ' +
121
- "expected at least 16 bytes, got #{bytes.length} bytes"
122
- packet_type, link_type, addr_len = bytes.unpack('nnn')
123
- type = bytes[14, 2].unpack('n')[0]
124
- bytes = bytes[16..-1]
125
- ethernet = Ethernet.new
126
- ethernet.type = type
127
- ethernet.src = '00:01:01:00:00:01'
128
- ethernet.dst = '00:01:01:00:00:02'
129
- ethernet.payload = ethernet.payload_raw = bytes
130
- begin
131
- case type
132
- when Ethernet::ETHERTYPE_IP
133
- ethernet.payload = IPv4.from_bytes ethernet.payload
134
- when Ethernet::ETHERTYPE_IP6
135
- ethernet.payload = IPv6.from_bytes ethernet.payload
136
- end
137
- rescue ParseError => e
138
- Pcap.warning e
139
- end
140
- return ethernet
141
- end
142
-
143
- def == other
144
- return self.class == other.class &&
145
- self.endian == other.endian &&
146
- self.ts_sec == other.ts_sec &&
147
- self.ts_usec == other.ts_usec &&
148
- self.caplen == other.caplen &&
149
- self.len == other.len &&
150
- self.pkt == other.pkt
151
- end
152
- end
153
-
154
- end
155
- end
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ module Mu
6
+ class Pcap
7
+
8
+ class Pkthdr
9
+ attr_accessor :endian, :ts_sec, :ts_usec, :caplen, :len, :pkt, :pkt_raw
10
+
11
+ def initialize endian=BIG_ENDIAN, ts_sec=0, ts_usec=0, caplen=0, len=0, pkt=nil
12
+ @endian = endian
13
+ @ts_sec = ts_sec
14
+ @ts_usec = ts_usec
15
+ @caplen = caplen
16
+ @len = len
17
+ @pkt = pkt
18
+ @pkt_raw = pkt
19
+ end
20
+
21
+ FMT_NNNN = 'NNNN'
22
+ FMT_VVVV = 'VVVV'
23
+ def self.read io, endian=BIG_ENDIAN
24
+ if endian == BIG_ENDIAN
25
+ format = FMT_NNNN
26
+ elsif endian == LITTLE_ENDIAN
27
+ format = FMT_VVVV
28
+ end
29
+ bytes = io.read 16
30
+ if not bytes
31
+ raise EOFError, 'Missing PCAP packet header'
32
+ end
33
+ if not bytes.length == 16
34
+ raise ParseError, "Truncated PCAP packet header: expected 16 bytes, got #{bytes.length} bytes"
35
+ end
36
+ ts_sec, ts_usec, caplen, len = bytes.unpack format
37
+ pkt = io.read(caplen)
38
+ if not pkt
39
+ raise ParseError, 'Missing PCAP packet header packet'
40
+ end
41
+ if not pkt.length == caplen
42
+ raise ParseError, "Truncated PCAP packet header: expected #{pkthdr.caplen} bytes, got #{pkthdr.pkt.length} bytes"
43
+ end
44
+ pkthdr = Pkthdr.new endian, ts_sec, ts_usec, caplen, len, pkt
45
+ return pkthdr
46
+ end
47
+
48
+ def write io
49
+ if @pkt.is_a? String
50
+ pkt = @pkt
51
+ else
52
+ string_io = StringIO.new
53
+ @pkt.write string_io
54
+ pkt = string_io.string
55
+ end
56
+ len = pkt.length
57
+ bytes = [@ts_sec, @ts_usec, len, len].pack FMT_NNNN
58
+ io.write bytes
59
+ io.write pkt
60
+ end
61
+
62
+ def decode! endian, linktype
63
+ @pkt = case linktype
64
+ when DLT_NULL; Pkthdr.decode_null endian, @pkt
65
+ when DLT_EN10MB; Pkthdr.decode_en10mb @pkt
66
+ when DLT_RAW; raise NotImplementedError
67
+ when DLT_LINUX_SLL; Pkthdr.decode_linux_sll @pkt
68
+ else raise ParseError, "Unknown PCAP linktype: #{linktype}"
69
+ end
70
+ end
71
+
72
+ # See http://wiki.wireshark.org/NullLoopback
73
+ # and epan/aftypes.h in wireshark code.
74
+ BSD_AF_INET6 = [
75
+ OPENBSD_AF_INET6 = 24,
76
+ FREEBSD_AF_INET6 = 28,
77
+ DARWIN_AF_INET6 = 30
78
+ ]
79
+
80
+ def self.decode_null endian, bytes
81
+ Pcap.assert bytes.length >= 4, 'Truncated PCAP packet header: ' +
82
+ "expected at least 4 bytes, got #{bytes.length} bytes"
83
+ if endian == BIG_ENDIAN
84
+ format = 'N'
85
+ elsif endian == LITTLE_ENDIAN
86
+ format = 'V'
87
+ end
88
+ family = bytes[0, 4].unpack(format)[0]
89
+ bytes = bytes[4..-1]
90
+ ethernet = Ethernet.new
91
+ ethernet.src = '00:01:01:00:00:01'
92
+ ethernet.dst = '00:01:01:00:00:02'
93
+ ethernet.payload = ethernet.payload_raw = bytes
94
+ if family != Socket::AF_INET and family != Socket::AF_INET6 and
95
+ not BSD_AF_INET6.include?(family)
96
+ raise ParseError, "Unknown PCAP packet header family: #{family}"
97
+ end
98
+ begin
99
+ case family
100
+ when Socket::AF_INET
101
+ ethernet.type = Ethernet::ETHERTYPE_IP
102
+ ethernet.payload = IPv4.from_bytes ethernet.payload
103
+ when Socket::AF_INET6, FREEBSD_AF_INET6, OPENBSD_AF_INET6, DARWIN_AF_INET6
104
+ ethernet.type = Ethernet::ETHERTYPE_IP6
105
+ ethernet.payload = IPv6.from_bytes ethernet.payload
106
+ else
107
+ raise NotImplementedError
108
+ end
109
+ rescue ParseError => e
110
+ Pcap.warning e
111
+ end
112
+ return ethernet
113
+ end
114
+
115
+ def self.decode_en10mb bytes
116
+ return Ethernet.from_bytes(bytes)
117
+ end
118
+
119
+ def self.decode_linux_sll bytes
120
+ Pcap.assert bytes.length >= 16, 'Truncated PCAP packet header: ' +
121
+ "expected at least 16 bytes, got #{bytes.length} bytes"
122
+ packet_type, link_type, addr_len = bytes.unpack('nnn')
123
+ type = bytes[14, 2].unpack('n')[0]
124
+ bytes = bytes[16..-1]
125
+ ethernet = Ethernet.new
126
+ ethernet.type = type
127
+ ethernet.src = '00:01:01:00:00:01'
128
+ ethernet.dst = '00:01:01:00:00:02'
129
+ ethernet.payload = ethernet.payload_raw = bytes
130
+ begin
131
+ case type
132
+ when Ethernet::ETHERTYPE_IP
133
+ ethernet.payload = IPv4.from_bytes ethernet.payload
134
+ when Ethernet::ETHERTYPE_IP6
135
+ ethernet.payload = IPv6.from_bytes ethernet.payload
136
+ end
137
+ rescue ParseError => e
138
+ Pcap.warning e
139
+ end
140
+ return ethernet
141
+ end
142
+
143
+ def == other
144
+ return self.class == other.class &&
145
+ self.endian == other.endian &&
146
+ self.ts_sec == other.ts_sec &&
147
+ self.ts_usec == other.ts_usec &&
148
+ self.caplen == other.caplen &&
149
+ self.len == other.len &&
150
+ self.pkt == other.pkt
151
+ end
152
+ end
153
+
154
+ end
155
+ end
@@ -1,61 +1,61 @@
1
- # http://www.mudynamics.com
2
- # http://labs.mudynamics.com
3
- # http://www.pcapr.net
4
-
5
- module Mu
6
- class Pcap
7
-
8
- class Reader
9
- attr_accessor :pcap2scenario
10
-
11
- FAMILY_TO_READER = {}
12
-
13
- # Returns a reader instance of specified family. Returns nil when family is :none.
14
- def self.reader family
15
- if family == :none
16
- return nil
17
- end
18
-
19
- if klass = FAMILY_TO_READER[family]
20
- return klass.new
21
- end
22
-
23
- raise ArgumentError, "Unknown protocol family: '#{family}'"
24
- end
25
-
26
- # Returns family name
27
- def family
28
- raise NotImplementedError
29
- end
30
-
31
- # Notify parser of bytes written. Parser may update state
32
- # to serve as a hint for subsequent reads.
33
- def record_write bytes, state=nil
34
- begin
35
- do_record_write bytes, state
36
- rescue
37
- nil
38
- end
39
- end
40
-
41
- # Returns next complete message from byte stream or nil.
42
- def read_message bytes, state=nil
43
- read_message! bytes.dup, state
44
- end
45
-
46
- # Mutating form of read_message. Removes a complete message
47
- # from input stream. Returns the message or nil if there.
48
- # is not a complete message.
49
- def read_message! bytes, state=nil
50
- begin
51
- do_read_message! bytes, state
52
- rescue
53
- nil
54
- end
55
- end
56
-
57
- end
58
- end
59
- end
60
-
61
- require 'mu/pcap/reader/http_family'
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ module Mu
6
+ class Pcap
7
+
8
+ class Reader
9
+ attr_accessor :pcap2scenario
10
+
11
+ FAMILY_TO_READER = {}
12
+
13
+ # Returns a reader instance of specified family. Returns nil when family is :none.
14
+ def self.reader family
15
+ if family == :none
16
+ return nil
17
+ end
18
+
19
+ if klass = FAMILY_TO_READER[family]
20
+ return klass.new
21
+ end
22
+
23
+ raise ArgumentError, "Unknown protocol family: '#{family}'"
24
+ end
25
+
26
+ # Returns family name
27
+ def family
28
+ raise NotImplementedError
29
+ end
30
+
31
+ # Notify parser of bytes written. Parser may update state
32
+ # to serve as a hint for subsequent reads.
33
+ def record_write bytes, state=nil
34
+ begin
35
+ do_record_write bytes, state
36
+ rescue
37
+ nil
38
+ end
39
+ end
40
+
41
+ # Returns next complete message from byte stream or nil.
42
+ def read_message bytes, state=nil
43
+ read_message! bytes.dup, state
44
+ end
45
+
46
+ # Mutating form of read_message. Removes a complete message
47
+ # from input stream. Returns the message or nil if there.
48
+ # is not a complete message.
49
+ def read_message! bytes, state=nil
50
+ begin
51
+ do_read_message! bytes, state
52
+ rescue
53
+ nil
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+
61
+ require 'mu/pcap/reader/http_family'
@@ -1,170 +1,170 @@
1
- # http://www.mudynamics.com
2
- # http://labs.mudynamics.com
3
- # http://www.pcapr.net
4
-
5
- require 'mu/pcap/reader'
6
- require 'stringio'
7
- require 'zlib'
8
-
9
- module Mu
10
- class Pcap
11
- class Reader
12
-
13
- # Reader for HTTP family of protocols (HTTP/SIP/RTSP).
14
- # Handles message boundaries and decompressing/dechunking payloads.
15
- class HttpFamily < Reader
16
- FAMILY = :http
17
- FAMILY_TO_READER[FAMILY] = self
18
- CRLF = "\r\n"
19
- def family
20
- FAMILY
21
- end
22
-
23
- def do_record_write bytes, state=nil
24
- return if not state
25
- if bytes =~ RE_REQUEST_LINE
26
- method = $1
27
- requests = state[:requests] ||= []
28
- requests << method
29
- end
30
- end
31
- private :do_record_write
32
-
33
- RE_CONTENT_ENCODING = /^content-encoding:\s*(gzip|deflate)/i
34
- RE_CHUNKED = /Transfer-Encoding:\s*chunked/i
35
- RE_HEADERS_COMPLETE = /.*?\r\n\r\n/m
36
- # Request line e.g. GET /index.html HTTP/1.1
37
- RE_REQUEST_LINE = /\A([^ \t\r\n]+)[ \t]+([^ \t\r\n]+)[ \t]+(HTTP|SIP|RTSP)\/[\d.]+.*\r\n/
38
- # Status line e.g. SIP/2.0 404 Authorization required
39
- RE_STATUS_LINE = /\A((HTTP|SIP|RTSP)\/[\d.]+[ \t]+(\d+))\b.*\r\n/
40
-
41
- RE_CONTENT_LENGTH = /^(Content-Length)(:\s*)(\d+)\r\n/i
42
- RE_CONTENT_LENGTH_SIP = /^(Content-Length|l)(:\s*)(\d+)\r\n/i
43
-
44
-
45
- def do_read_message! bytes, state=nil
46
- case bytes
47
- when RE_REQUEST_LINE
48
- proto = $3
49
- when RE_STATUS_LINE
50
- proto = $2
51
- status = $3.to_i
52
- if state
53
- requests = state[:requests] ||= []
54
- if requests[0] == "HEAD"
55
- reply_to_head = true
56
- end
57
- if status > 199
58
- # We have a final response. Forget about request.
59
- requests.shift
60
- end
61
- end
62
- else
63
- return nil # Not http family.
64
- end
65
-
66
- # Read headers
67
- if bytes =~ RE_HEADERS_COMPLETE
68
- headers = $&
69
- rest = $'
70
- else
71
- return nil
72
- end
73
- message = [headers]
74
-
75
- # Read payload.
76
- if proto == 'SIP'
77
- re_content_length = RE_CONTENT_LENGTH_SIP
78
- else
79
- re_content_length = RE_CONTENT_LENGTH
80
- end
81
- if reply_to_head
82
- length = 0
83
- elsif headers =~ RE_CHUNKED
84
- # Read chunks, dechunking in runtime case.
85
- raw, dechunked = get_chunks(rest)
86
- if raw
87
- length = raw.length
88
- payload = raw
89
- else
90
- return nil # Last chunk not received.
91
- end
92
- elsif headers =~ re_content_length
93
- length = $3.to_i
94
- if rest.length >= length
95
- payload = rest.slice(0,length)
96
- else
97
- return nil # Not enough bytes.
98
- end
99
- else
100
- # XXX. When there is a payload and no content-length
101
- # header HTTP RFC says to read until connection close.
102
- length = 0
103
- end
104
-
105
- message << payload
106
-
107
- # Consume message from input bytes.
108
- message_len = headers.length + length
109
- if bytes.length >= message_len
110
- bytes.slice!(0, message_len)
111
- return message.join
112
- else
113
- return nil # Not enough bytes.
114
- end
115
- end
116
- private :do_read_message!
117
-
118
- # Returns array containing raw and dechunked payload. Returns nil
119
- # if payload cannot be completely read.
120
- RE_CHUNK_SIZE_LINE = /\A([[:xdigit:]]+)\r\n?/
121
- def get_chunks bytes
122
- raw = []
123
- dechunked = []
124
- io = StringIO.new bytes
125
- until io.eof?
126
- # Read size line
127
- size_line = io.readline
128
- raw << size_line
129
- if size_line =~ RE_CHUNK_SIZE_LINE
130
- chunk_size = $1.to_i(16)
131
- else
132
- # Malformed chunk size line
133
- $stderr.puts "malformed size line : #{size_line.inspect}"
134
- return nil
135
- end
136
-
137
- # Read chunk data
138
- chunk = io.read(chunk_size)
139
- if chunk.size < chunk_size
140
- # malformed/incomplete
141
- $stderr.puts "malformed/incomplete #{chunk_size}"
142
- return nil
143
- end
144
- raw << chunk
145
- dechunked << chunk
146
- # Get end-of-chunk CRLF
147
- crlf = io.read(2)
148
- if crlf == CRLF
149
- raw << crlf
150
- else
151
- # CRLF has not arrived or, if this is the last chunk,
152
- # we might be looking at the first two bytes of a trailer
153
- # and we don't support trailers (see rfc 2616 sec3.6.1).
154
- return nil
155
- end
156
-
157
- if chunk_size == 0
158
- # Done. Return raw and dechunked payloads.
159
- return raw.join, dechunked.join
160
- end
161
- end
162
-
163
- # EOF w/out reaching last chunk.
164
- return nil
165
- end
166
- end
167
-
168
- end
169
- end
170
- end
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ require 'mu/pcap/reader'
6
+ require 'stringio'
7
+ require 'zlib'
8
+
9
+ module Mu
10
+ class Pcap
11
+ class Reader
12
+
13
+ # Reader for HTTP family of protocols (HTTP/SIP/RTSP).
14
+ # Handles message boundaries and decompressing/dechunking payloads.
15
+ class HttpFamily < Reader
16
+ FAMILY = :http
17
+ FAMILY_TO_READER[FAMILY] = self
18
+ CRLF = "\r\n"
19
+ def family
20
+ FAMILY
21
+ end
22
+
23
+ def do_record_write bytes, state=nil
24
+ return if not state
25
+ if bytes =~ RE_REQUEST_LINE
26
+ method = $1
27
+ requests = state[:requests] ||= []
28
+ requests << method
29
+ end
30
+ end
31
+ private :do_record_write
32
+
33
+ RE_CONTENT_ENCODING = /^content-encoding:\s*(gzip|deflate)/i
34
+ RE_CHUNKED = /Transfer-Encoding:\s*chunked/i
35
+ RE_HEADERS_COMPLETE = /.*?\r\n\r\n/m
36
+ # Request line e.g. GET /index.html HTTP/1.1
37
+ RE_REQUEST_LINE = /\A([^ \t\r\n]+)[ \t]+([^ \t\r\n]+)[ \t]+(HTTP|SIP|RTSP)\/[\d.]+.*\r\n/
38
+ # Status line e.g. SIP/2.0 404 Authorization required
39
+ RE_STATUS_LINE = /\A((HTTP|SIP|RTSP)\/[\d.]+[ \t]+(\d+))\b.*\r\n/
40
+
41
+ RE_CONTENT_LENGTH = /^(Content-Length)(:\s*)(\d+)\r\n/i
42
+ RE_CONTENT_LENGTH_SIP = /^(Content-Length|l)(:\s*)(\d+)\r\n/i
43
+
44
+
45
+ def do_read_message! bytes, state=nil
46
+ case bytes
47
+ when RE_REQUEST_LINE
48
+ proto = $3
49
+ when RE_STATUS_LINE
50
+ proto = $2
51
+ status = $3.to_i
52
+ if state
53
+ requests = state[:requests] ||= []
54
+ if requests[0] == "HEAD"
55
+ reply_to_head = true
56
+ end
57
+ if status > 199
58
+ # We have a final response. Forget about request.
59
+ requests.shift
60
+ end
61
+ end
62
+ else
63
+ return nil # Not http family.
64
+ end
65
+
66
+ # Read headers
67
+ if bytes =~ RE_HEADERS_COMPLETE
68
+ headers = $&
69
+ rest = $'
70
+ else
71
+ return nil
72
+ end
73
+ message = [headers]
74
+
75
+ # Read payload.
76
+ if proto == 'SIP'
77
+ re_content_length = RE_CONTENT_LENGTH_SIP
78
+ else
79
+ re_content_length = RE_CONTENT_LENGTH
80
+ end
81
+ if reply_to_head
82
+ length = 0
83
+ elsif headers =~ RE_CHUNKED
84
+ # Read chunks, dechunking in runtime case.
85
+ raw, dechunked = get_chunks(rest)
86
+ if raw
87
+ length = raw.length
88
+ payload = raw
89
+ else
90
+ return nil # Last chunk not received.
91
+ end
92
+ elsif headers =~ re_content_length
93
+ length = $3.to_i
94
+ if rest.length >= length
95
+ payload = rest.slice(0,length)
96
+ else
97
+ return nil # Not enough bytes.
98
+ end
99
+ else
100
+ # XXX. When there is a payload and no content-length
101
+ # header HTTP RFC says to read until connection close.
102
+ length = 0
103
+ end
104
+
105
+ message << payload
106
+
107
+ # Consume message from input bytes.
108
+ message_len = headers.length + length
109
+ if bytes.length >= message_len
110
+ bytes.slice!(0, message_len)
111
+ return message.join
112
+ else
113
+ return nil # Not enough bytes.
114
+ end
115
+ end
116
+ private :do_read_message!
117
+
118
+ # Returns array containing raw and dechunked payload. Returns nil
119
+ # if payload cannot be completely read.
120
+ RE_CHUNK_SIZE_LINE = /\A([[:xdigit:]]+)\r\n?/
121
+ def get_chunks bytes
122
+ raw = []
123
+ dechunked = []
124
+ io = StringIO.new bytes
125
+ until io.eof?
126
+ # Read size line
127
+ size_line = io.readline
128
+ raw << size_line
129
+ if size_line =~ RE_CHUNK_SIZE_LINE
130
+ chunk_size = $1.to_i(16)
131
+ else
132
+ # Malformed chunk size line
133
+ $stderr.puts "malformed size line : #{size_line.inspect}"
134
+ return nil
135
+ end
136
+
137
+ # Read chunk data
138
+ chunk = io.read(chunk_size)
139
+ if chunk.size < chunk_size
140
+ # malformed/incomplete
141
+ $stderr.puts "malformed/incomplete #{chunk_size}"
142
+ return nil
143
+ end
144
+ raw << chunk
145
+ dechunked << chunk
146
+ # Get end-of-chunk CRLF
147
+ crlf = io.read(2)
148
+ if crlf == CRLF
149
+ raw << crlf
150
+ else
151
+ # CRLF has not arrived or, if this is the last chunk,
152
+ # we might be looking at the first two bytes of a trailer
153
+ # and we don't support trailers (see rfc 2616 sec3.6.1).
154
+ return nil
155
+ end
156
+
157
+ if chunk_size == 0
158
+ # Done. Return raw and dechunked payloads.
159
+ return raw.join, dechunked.join
160
+ end
161
+ end
162
+
163
+ # EOF w/out reaching last chunk.
164
+ return nil
165
+ end
166
+ end
167
+
168
+ end
169
+ end
170
+ end