http: add "HttpParser#keepalive?" method
[unicorn.git] / test / unit / test_http_parser_ng.rb
blobbacf2cf3f1485695d3dfa0b8233d140aa423fea7
1 # coding: binary
2 require 'test/test_helper'
3 require 'digest/md5'
5 include Unicorn
7 class HttpParserNgTest < Test::Unit::TestCase
9   def setup
10     @parser = HttpParser.new
11   end
13   def test_identity_step_headers
14     req = {}
15     str = "PUT / HTTP/1.1\r\n"
16     assert ! @parser.headers(req, str)
17     str << "Content-Length: 123\r\n"
18     assert ! @parser.headers(req, str)
19     str << "\r\n"
20     assert_equal req.object_id, @parser.headers(req, str).object_id
21     assert_equal '123', req['CONTENT_LENGTH']
22     assert_equal 0, str.size
23     assert ! @parser.keepalive?
24   end
26   def test_identity_oneshot_header
27     req = {}
28     str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
29     assert_equal req.object_id, @parser.headers(req, str).object_id
30     assert_equal '123', req['CONTENT_LENGTH']
31     assert_equal 0, str.size
32     assert ! @parser.keepalive?
33   end
35   def test_identity_oneshot_header_with_body
36     body = ('a' * 123).freeze
37     req = {}
38     str = "PUT / HTTP/1.1\r\n" \
39           "Content-Length: #{body.length}\r\n" \
40           "\r\n#{body}"
41     assert_equal req.object_id, @parser.headers(req, str).object_id
42     assert_equal '123', req['CONTENT_LENGTH']
43     assert_equal 123, str.size
44     assert_equal body, str
45     tmp = ''
46     assert_nil @parser.filter_body(tmp, str)
47     assert_equal 0, str.size
48     assert_equal tmp, body
49     assert_equal "", @parser.filter_body(tmp, str)
50     assert ! @parser.keepalive?
51   end
53   def test_identity_oneshot_header_with_body_partial
54     str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
55     assert_equal Hash, @parser.headers({}, str).class
56     assert_equal 1, str.size
57     assert_equal 'a', str
58     tmp = ''
59     assert_nil @parser.filter_body(tmp, str)
60     assert_equal "", str
61     assert_equal "a", tmp
62     str << ' ' * 122
63     rv = @parser.filter_body(tmp, str)
64     assert_equal 122, tmp.size
65     assert_nil rv
66     assert_equal "", str
67     assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
68     assert ! @parser.keepalive?
69   end
71   def test_identity_oneshot_header_with_body_slop
72     str = "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
73     assert_equal Hash, @parser.headers({}, str).class
74     assert_equal 2, str.size
75     assert_equal 'aG', str
76     tmp = ''
77     assert_nil @parser.filter_body(tmp, str)
78     assert_equal "G", str
79     assert_equal "G", @parser.filter_body(tmp, str)
80     assert_equal 1, tmp.size
81     assert_equal "a", tmp
82     assert ! @parser.keepalive?
83   end
85   def test_chunked
86     str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
87     req = {}
88     assert_equal req, @parser.headers(req, str)
89     assert_equal 0, str.size
90     tmp = ""
91     assert_nil @parser.filter_body(tmp, "6")
92     assert_equal 0, tmp.size
93     assert_nil @parser.filter_body(tmp, rv = "\r\n")
94     assert_equal 0, rv.size
95     assert_equal 0, tmp.size
96     tmp = ""
97     assert_nil @parser.filter_body(tmp, "..")
98     assert_equal "..", tmp
99     assert_nil @parser.filter_body(tmp, "abcd\r\n0\r\n")
100     assert_equal "abcd", tmp
101     rv = "PUT"
102     assert_equal rv.object_id, @parser.filter_body(tmp, rv).object_id
103     assert_equal "PUT", rv
104     assert ! @parser.keepalive?
105   end
107   def test_two_chunks
108     str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
109     req = {}
110     assert_equal req, @parser.headers(req, str)
111     assert_equal 0, str.size
112     tmp = ""
113     assert_nil @parser.filter_body(tmp, "6")
114     assert_equal 0, tmp.size
115     assert_nil @parser.filter_body(tmp, rv = "\r\n")
116     assert_equal "", rv
117     assert_equal 0, tmp.size
118     tmp = ""
119     assert_nil @parser.filter_body(tmp, "..")
120     assert_equal 2, tmp.size
121     assert_equal "..", tmp
122     assert_nil @parser.filter_body(tmp, "abcd\r\n1")
123     assert_equal "abcd", tmp
124     assert_nil @parser.filter_body(tmp, "\r")
125     assert_equal "", tmp
126     assert_nil @parser.filter_body(tmp, "\n")
127     assert_equal "", tmp
128     assert_nil @parser.filter_body(tmp, "z")
129     assert_equal "z", tmp
130     assert_nil @parser.filter_body(tmp, "\r\n")
131     assert_nil @parser.filter_body(tmp, "0")
132     assert_nil @parser.filter_body(tmp, "\r")
133     rv = @parser.filter_body(tmp, buf = "\nGET")
134     assert_equal "GET", rv
135     assert_equal buf.object_id, rv.object_id
136     assert ! @parser.keepalive?
137   end
139   def test_big_chunk
140     str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
141           "4000\r\nabcd"
142     req = {}
143     assert_equal req, @parser.headers(req, str)
144     tmp = ''
145     assert_nil @parser.filter_body(tmp, str)
146     assert_equal '', str
147     str = ' ' * 16300
148     assert_nil @parser.filter_body(tmp, str)
149     assert_equal '', str
150     str = ' ' * 80
151     assert_nil @parser.filter_body(tmp, str)
152     assert_equal '', str
153     assert ! @parser.body_eof?
154     assert_equal "", @parser.filter_body(tmp, "\r\n0\r\n")
155     assert @parser.body_eof?
156     assert ! @parser.keepalive?
157   end
159   def test_two_chunks_oneshot
160     str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
161           "1\r\na\r\n2\r\n..\r\n0\r\n"
162     req = {}
163     assert_equal req, @parser.headers(req, str)
164     tmp = ''
165     assert_nil @parser.filter_body(tmp, str)
166     assert_equal 'a..', tmp
167     rv = @parser.filter_body(tmp, str)
168     assert_equal rv.object_id, str.object_id
169     assert ! @parser.keepalive?
170   end
172   def test_trailers
173     str = "PUT / HTTP/1.1\r\n" \
174           "Trailer: Content-MD5\r\n" \
175           "transfer-Encoding: chunked\r\n\r\n" \
176           "1\r\na\r\n2\r\n..\r\n0\r\n"
177     req = {}
178     assert_equal req, @parser.headers(req, str)
179     assert_equal 'Content-MD5', req['HTTP_TRAILER']
180     assert_nil req['HTTP_CONTENT_MD5']
181     tmp = ''
182     assert_nil @parser.filter_body(tmp, str)
183     assert_equal 'a..', tmp
184     md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
185     rv = @parser.filter_body(tmp, str)
186     assert_equal rv.object_id, str.object_id
187     assert_equal '', str
188     md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
189     str << md5_hdr
190     assert_nil @parser.trailers(req, str)
191     assert_equal md5_b64, req['HTTP_CONTENT_MD5']
192     assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
193     assert_nil @parser.trailers(req, str << "\r")
194     assert_equal req, @parser.trailers(req, str << "\nGET / ")
195     assert_equal "GET / ", str
196     assert ! @parser.keepalive?
197   end
199   def test_max_chunk
200     str = "PUT / HTTP/1.1\r\n" \
201           "transfer-Encoding: chunked\r\n\r\n" \
202           "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
203     req = {}
204     assert_equal req, @parser.headers(req, str)
205     assert_nil @parser.content_length
206     assert_nothing_raised { @parser.filter_body('', str) }
207     assert ! @parser.keepalive?
208   end
210   def test_max_body
211     n = HttpParser::LENGTH_MAX
212     str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
213     req = {}
214     assert_nothing_raised { @parser.headers(req, str) }
215     assert_equal n, req['CONTENT_LENGTH'].to_i
216     assert ! @parser.keepalive?
217   end
219   def test_overflow_chunk
220     n = HttpParser::CHUNK_MAX + 1
221     str = "PUT / HTTP/1.1\r\n" \
222           "transfer-Encoding: chunked\r\n\r\n" \
223           "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
224     req = {}
225     assert_equal req, @parser.headers(req, str)
226     assert_nil @parser.content_length
227     assert_raise(HttpParserError) { @parser.filter_body('', str) }
228     assert ! @parser.keepalive?
229   end
231   def test_overflow_content_length
232     n = HttpParser::LENGTH_MAX + 1
233     str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
234     assert_raise(HttpParserError) { @parser.headers({}, str) }
235     assert ! @parser.keepalive?
236   end
238   def test_bad_chunk
239     str = "PUT / HTTP/1.1\r\n" \
240           "transfer-Encoding: chunked\r\n\r\n" \
241           "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
242     req = {}
243     assert_equal req, @parser.headers(req, str)
244     assert_nil @parser.content_length
245     assert_raise(HttpParserError) { @parser.filter_body('', str) }
246     assert ! @parser.keepalive?
247   end
249   def test_bad_content_length
250     str = "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
251     assert_raise(HttpParserError) { @parser.headers({}, str) }
252     assert ! @parser.keepalive?
253   end
255   def test_bad_trailers
256     str = "PUT / HTTP/1.1\r\n" \
257           "Trailer: Transfer-Encoding\r\n" \
258           "transfer-Encoding: chunked\r\n\r\n" \
259           "1\r\na\r\n2\r\n..\r\n0\r\n"
260     req = {}
261     assert_equal req, @parser.headers(req, str)
262     assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
263     tmp = ''
264     assert_nil @parser.filter_body(tmp, str)
265     assert_equal 'a..', tmp
266     assert_equal '', str
267     str << "Transfer-Encoding: identity\r\n\r\n"
268     assert_raise(HttpParserError) { @parser.trailers(req, str) }
269     assert ! @parser.keepalive?
270   end
272   def test_repeat_headers
273     str = "PUT / HTTP/1.1\r\n" \
274           "Trailer: Content-MD5\r\n" \
275           "Trailer: Content-SHA1\r\n" \
276           "transfer-Encoding: chunked\r\n\r\n" \
277           "1\r\na\r\n2\r\n..\r\n0\r\n"
278     req = {}
279     assert_equal req, @parser.headers(req, str)
280     assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
281     assert ! @parser.keepalive?
282   end