http: add test for invalid trailer
[unicorn.git] / test / unit / test_http_parser_ng.rb
blob6fc0bda2ffd86ec6f6b311b00ad319a58f9ec709
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   end
25   def test_identity_oneshot_header
26     req = {}
27     str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
28     assert_equal req.object_id, @parser.headers(req, str).object_id
29     assert_equal '123', req['CONTENT_LENGTH']
30     assert_equal 0, str.size
31   end
33   def test_identity_oneshot_header_with_body
34     body = ('a' * 123).freeze
35     req = {}
36     str = "PUT / HTTP/1.1\r\n" \
37           "Content-Length: #{body.length}\r\n" \
38           "\r\n#{body}"
39     assert_equal req.object_id, @parser.headers(req, str).object_id
40     assert_equal '123', req['CONTENT_LENGTH']
41     assert_equal 123, str.size
42     assert_equal body, str
43     tmp = ''
44     assert_nil @parser.read_body(tmp, str)
45     assert_equal 0, str.size
46     assert_equal tmp, body
47     assert_equal "", @parser.read_body(tmp, str)
48   end
50   def test_identity_oneshot_header_with_body_partial
51     str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
52     assert_equal Hash, @parser.headers({}, str).class
53     assert_equal 1, str.size
54     assert_equal 'a', str
55     tmp = ''
56     assert_nil @parser.read_body(tmp, str)
57     assert_equal "", str
58     assert_equal "a", tmp
59     str << ' ' * 122
60     rv = @parser.read_body(tmp, str)
61     assert_equal 122, tmp.size
62     assert_nil rv
63     assert_equal "", str
64     assert_equal str.object_id, @parser.read_body(tmp, str).object_id
65   end
67   def test_identity_oneshot_header_with_body_slop
68     str = "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
69     assert_equal Hash, @parser.headers({}, str).class
70     assert_equal 2, str.size
71     assert_equal 'aG', str
72     tmp = ''
73     assert_nil @parser.read_body(tmp, str)
74     assert_equal "G", str
75     assert_equal "G", @parser.read_body(tmp, str)
76     assert_equal 1, tmp.size
77     assert_equal "a", tmp
78   end
80   def test_chunked
81     str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
82     req = {}
83     assert_equal req, @parser.headers(req, str)
84     assert_equal 0, str.size
85     tmp = ""
86     assert_nil @parser.read_body(tmp, "6")
87     assert_equal 0, tmp.size
88     assert_nil @parser.read_body(tmp, rv = "\r\n")
89     assert_equal 0, rv.size
90     assert_equal 0, tmp.size
91     tmp = ""
92     assert_nil @parser.read_body(tmp, "..")
93     assert_equal "..", tmp
94     assert_nil @parser.read_body(tmp, "abcd\r\n0\r\n")
95     assert_equal "abcd", tmp
96     rv = "PUT"
97     assert_equal rv.object_id, @parser.read_body(tmp, rv).object_id
98     assert_equal "PUT", rv
99   end
101   def test_two_chunks
102     str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
103     req = {}
104     assert_equal req, @parser.headers(req, str)
105     assert_equal 0, str.size
106     tmp = ""
107     assert_nil @parser.read_body(tmp, "6")
108     assert_equal 0, tmp.size
109     assert_nil @parser.read_body(tmp, rv = "\r\n")
110     assert_equal "", rv
111     assert_equal 0, tmp.size
112     tmp = ""
113     assert_nil @parser.read_body(tmp, "..")
114     assert_equal 2, tmp.size
115     assert_equal "..", tmp
116     assert_nil @parser.read_body(tmp, "abcd\r\n1")
117     assert_equal "abcd", tmp
118     assert_nil @parser.read_body(tmp, "\r")
119     assert_equal "", tmp
120     assert_nil @parser.read_body(tmp, "\n")
121     assert_equal "", tmp
122     assert_nil @parser.read_body(tmp, "z")
123     assert_equal "z", tmp
124     assert_nil @parser.read_body(tmp, "\r\n")
125     assert_nil @parser.read_body(tmp, "0")
126     assert_nil @parser.read_body(tmp, "\r")
127     rv = @parser.read_body(tmp, buf = "\nGET")
128     assert_equal "GET", rv
129     assert_equal buf.object_id, rv.object_id
130   end
132   def test_big_chunk
133     str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
134           "4000\r\nabcd"
135     req = {}
136     assert_equal req, @parser.headers(req, str)
137     tmp = ''
138     assert_nil @parser.read_body(tmp, str)
139     assert_equal '', str
140     str = ' ' * 16300
141     assert_nil @parser.read_body(tmp, str)
142     assert_equal '', str
143     str = ' ' * 80
144     assert_nil @parser.read_body(tmp, str)
145     assert_equal '', str
146     assert ! @parser.body_eof?
147     assert_equal "", @parser.read_body(tmp, "\r\n0\r\n")
148     assert @parser.body_eof?
149   end
151   def test_two_chunks_oneshot
152     str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
153           "1\r\na\r\n2\r\n..\r\n0\r\n"
154     req = {}
155     assert_equal req, @parser.headers(req, str)
156     tmp = ''
157     assert_nil @parser.read_body(tmp, str)
158     assert_equal 'a..', tmp
159     rv = @parser.read_body(tmp, str)
160     assert_equal rv.object_id, str.object_id
161   end
163   def test_trailers
164     str = "PUT / HTTP/1.1\r\n" \
165           "Trailer: Content-MD5\r\n" \
166           "transfer-Encoding: chunked\r\n\r\n" \
167           "1\r\na\r\n2\r\n..\r\n0\r\n"
168     req = {}
169     assert_equal req, @parser.headers(req, str)
170     assert_equal 'Content-MD5', req['HTTP_TRAILER']
171     assert_nil req['HTTP_CONTENT_MD5']
172     tmp = ''
173     assert_nil @parser.read_body(tmp, str)
174     assert_equal 'a..', tmp
175     md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
176     rv = @parser.read_body(tmp, str)
177     assert_equal rv.object_id, str.object_id
178     assert_equal '', str
179     md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
180     str << md5_hdr
181     assert_nil @parser.trailers(req, str)
182     assert_equal md5_b64, req['HTTP_CONTENT_MD5']
183     assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
184     assert_nil @parser.trailers(req, str << "\r")
185     assert_equal req, @parser.trailers(req, str << "\nGET / ")
186     assert_equal "GET / ", str
187   end
189   def test_max_chunk
190     str = "PUT / HTTP/1.1\r\n" \
191           "transfer-Encoding: chunked\r\n\r\n" \
192           "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
193     req = {}
194     assert_equal req, @parser.headers(req, str)
195     assert_nil @parser.content_length
196     assert_nothing_raised { @parser.read_body('', str) }
197   end
199   def test_max_body
200     n = HttpParser::LENGTH_MAX
201     str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
202     req = {}
203     assert_nothing_raised { @parser.headers(req, str) }
204     assert_equal n, req['CONTENT_LENGTH'].to_i
205   end
207   def test_overflow_chunk
208     n = HttpParser::CHUNK_MAX + 1
209     str = "PUT / HTTP/1.1\r\n" \
210           "transfer-Encoding: chunked\r\n\r\n" \
211           "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
212     req = {}
213     assert_equal req, @parser.headers(req, str)
214     assert_nil @parser.content_length
215     assert_raise(HttpParserError) { @parser.read_body('', str) }
216   end
218   def test_overflow_content_length
219     n = HttpParser::LENGTH_MAX + 1
220     str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
221     assert_raise(HttpParserError) { @parser.headers({}, str) }
222   end
224   def test_bad_chunk
225     str = "PUT / HTTP/1.1\r\n" \
226           "transfer-Encoding: chunked\r\n\r\n" \
227           "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
228     req = {}
229     assert_equal req, @parser.headers(req, str)
230     assert_nil @parser.content_length
231     assert_raise(HttpParserError) { @parser.read_body('', str) }
232   end
234   def test_bad_content_length
235     str = "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
236     assert_raise(HttpParserError) { @parser.headers({}, str) }
237   end
239   def test_bad_trailers
240     str = "PUT / HTTP/1.1\r\n" \
241           "Trailer: Transfer-Encoding\r\n" \
242           "transfer-Encoding: chunked\r\n\r\n" \
243           "1\r\na\r\n2\r\n..\r\n0\r\n"
244     req = {}
245     assert_equal req, @parser.headers(req, str)
246     assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
247     tmp = ''
248     assert_nil @parser.read_body(tmp, str)
249     assert_equal 'a..', tmp
250     assert_equal '', str
251     str << "Transfer-Encoding: identity\r\n\r\n"
252     assert_raise(HttpParserError) { @parser.trailers(req, str) }
253   end