DIY-pcap 0.4.1 → 0.4.3

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