1//
2// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
3//
4// Distributed under the Boost Software License, Version 1.0. (See accompanying
5// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6//
7// Official repository: https://github.com/boostorg/beast
8//
9
10// Test that header file is self-contained.
11#include <boost/beast/http/parser.hpp>
12
13#include "test_parser.hpp"
14
15#include <boost/beast/_experimental/unit_test/suite.hpp>
16#include <boost/beast/core/buffer_traits.hpp>
17#include <boost/beast/core/buffers_suffix.hpp>
18#include <boost/beast/core/flat_buffer.hpp>
19#include <boost/beast/core/multi_buffer.hpp>
20#include <boost/beast/core/ostream.hpp>
21#include <boost/beast/http/string_body.hpp>
22#include <boost/system/system_error.hpp>
23#include <algorithm>
24
25namespace boost {
26namespace beast {
27namespace http {
28
29class parser_test
30 : public beast::unit_test::suite
31{
32public:
33 template<bool isRequest>
34 using parser_type =
35 parser<isRequest, string_body>;
36
37 static
38 net::const_buffer
39 buf(string_view s)
40 {
41 return {s.data(), s.size()};
42 }
43
44 template<class ConstBufferSequence,
45 bool isRequest>
46 static
47 void
48 put(ConstBufferSequence const& buffers,
49 basic_parser<isRequest>& p,
50 error_code& ec)
51 {
52 buffers_suffix<ConstBufferSequence> cb{buffers};
53 for(;;)
54 {
55 auto const used = p.put(cb, ec);
56 cb.consume(used);
57 if(ec)
58 return;
59 if(p.need_eof() &&
60 buffer_bytes(cb) == 0)
61 {
62 p.put_eof(ec);
63 if(ec)
64 return;
65 }
66 if(p.is_done())
67 break;
68 }
69 }
70
71 template<bool isRequest, class F>
72 void
73 doMatrix(string_view s0, F const& f)
74 {
75 // parse a single buffer
76 {
77 auto s = s0;
78 error_code ec;
79 parser_type<isRequest> p;
80 put(net::buffer(data: s.data(), size_in_bytes: s.size()), p, ec);
81 if(! BEAST_EXPECTS(! ec, ec.message()))
82 return;
83 f(p);
84 }
85 // parse two buffers
86 for(auto n = s0.size() - 1; n >= 1; --n)
87 {
88 auto s = s0;
89 error_code ec;
90 parser_type<isRequest> p;
91 p.eager(true);
92 auto used =
93 p.put(net::buffer(data: s.data(), size_in_bytes: n), ec);
94 s.remove_prefix(n: used);
95 if(ec == error::need_more)
96 ec = {};
97 if(! BEAST_EXPECTS(! ec, ec.message()))
98 continue;
99 BEAST_EXPECT(! p.is_done());
100 used = p.put(
101 net::buffer(data: s.data(), size_in_bytes: s.size()), ec);
102 s.remove_prefix(n: used);
103 if(! BEAST_EXPECTS(! ec, ec.message()))
104 continue;
105 BEAST_EXPECT(s.empty());
106 if(p.need_eof())
107 {
108 p.put_eof(ec);
109 if(! BEAST_EXPECTS(! ec, ec.message()))
110 continue;
111 }
112 if(BEAST_EXPECT(p.is_done()))
113 f(p);
114 }
115 }
116
117 void
118 testParse()
119 {
120 doMatrix<false>(
121 s0: "HTTP/1.0 200 OK\r\n"
122 "Server: test\r\n"
123 "\r\n"
124 "Hello, world!",
125 f: [&](parser_type<false> const& p)
126 {
127 auto const& m = p.get();
128 BEAST_EXPECT(! p.chunked());
129 BEAST_EXPECT(p.need_eof());
130 BEAST_EXPECT(p.content_length() == boost::none);
131 BEAST_EXPECT(m.version() == 10);
132 BEAST_EXPECT(m.result() == status::ok);
133 BEAST_EXPECT(m.reason() == "OK");
134 BEAST_EXPECT(m["Server"] == "test");
135 BEAST_EXPECT(m.body() == "Hello, world!");
136 }
137 );
138 doMatrix<false>(
139 s0: "HTTP/1.1 200 OK\r\n"
140 "Server: test\r\n"
141 "Expect: Expires, MD5-Fingerprint\r\n"
142 "Transfer-Encoding: chunked\r\n"
143 "\r\n"
144 "5\r\n"
145 "*****\r\n"
146 "2;a;b=1;c=\"2\"\r\n"
147 "--\r\n"
148 "0;d;e=3;f=\"4\"\r\n"
149 "Expires: never\r\n"
150 "MD5-Fingerprint: -\r\n"
151 "\r\n",
152 f: [&](parser_type<false> const& p)
153 {
154 auto const& m = p.get();
155 BEAST_EXPECT(! p.need_eof());
156 BEAST_EXPECT(p.chunked());
157 BEAST_EXPECT(p.content_length() == boost::none);
158 BEAST_EXPECT(m.version() == 11);
159 BEAST_EXPECT(m.result() == status::ok);
160 BEAST_EXPECT(m.reason() == "OK");
161 BEAST_EXPECT(m["Server"] == "test");
162 BEAST_EXPECT(m["Transfer-Encoding"] == "chunked");
163 BEAST_EXPECT(m["Expires"] == "never");
164 BEAST_EXPECT(m["MD5-Fingerprint"] == "-");
165 BEAST_EXPECT(m.body() == "*****--");
166 }
167 );
168 doMatrix<false>(
169 s0: "HTTP/1.0 200 OK\r\n"
170 "Server: test\r\n"
171 "Content-Length: 5\r\n"
172 "\r\n"
173 "*****",
174 f: [&](parser_type<false> const& p)
175 {
176 auto const& m = p.get();
177 BEAST_EXPECT(m.body() == "*****");
178 }
179 );
180 doMatrix<true>(
181 s0: "GET / HTTP/1.1\r\n"
182 "User-Agent: test\r\n"
183 "\r\n",
184 f: [&](parser_type<true> const& p)
185 {
186 auto const& m = p.get();
187 BEAST_EXPECT(m.method() == verb::get);
188 BEAST_EXPECT(m.target() == "/");
189 BEAST_EXPECT(m.version() == 11);
190 BEAST_EXPECT(! p.need_eof());
191 BEAST_EXPECT(! p.chunked());
192 BEAST_EXPECT(p.content_length() == boost::none);
193 }
194 );
195 doMatrix<true>(
196 s0: "GET / HTTP/1.1\r\n"
197 "User-Agent: test\r\n"
198 "X: \t x \t \r\n"
199 "\r\n",
200 f: [&](parser_type<true> const& p)
201 {
202 auto const& m = p.get();
203 BEAST_EXPECT(m["X"] == "x");
204 }
205 );
206
207 // test eager(true)
208 {
209 error_code ec;
210 parser_type<true> p;
211 p.eager(v: true);
212 p.put(buffer: buf(
213 s: "GET / HTTP/1.1\r\n"
214 "User-Agent: test\r\n"
215 "Content-Length: 1\r\n"
216 "\r\n"
217 "*")
218 , ec);
219 auto const& m = p.get();
220 BEAST_EXPECT(! ec);
221 BEAST_EXPECT(p.is_done());
222 BEAST_EXPECT(p.is_header_done());
223 BEAST_EXPECT(! p.need_eof());
224 BEAST_EXPECT(m.method() == verb::get);
225 BEAST_EXPECT(m.target() == "/");
226 BEAST_EXPECT(m.version() == 11);
227 BEAST_EXPECT(m["User-Agent"] == "test");
228 BEAST_EXPECT(m.body() == "*");
229 }
230 {
231 // test partial parsing of final chunk
232 // parse through the chunk body
233 error_code ec;
234 flat_buffer b;
235 parser_type<true> p;
236 p.eager(v: true);
237 ostream(buffer&: b) <<
238 "PUT / HTTP/1.1\r\n"
239 "Transfer-Encoding: chunked\r\n"
240 "\r\n"
241 "1\r\n"
242 "*";
243 auto used = p.put(buffers: b.data(), ec);
244 b.consume(n: used);
245 BEAST_EXPECT(! ec);
246 BEAST_EXPECT(! p.is_done());
247 BEAST_EXPECT(p.get().body() == "*");
248 ostream(buffer&: b) <<
249 "\r\n"
250 "0;d;e=3;f=\"4\"\r\n"
251 "Expires: never\r\n"
252 "MD5-Fingerprint: -\r\n";
253 // incomplete parse, missing the final crlf
254 used = p.put(buffers: b.data(), ec);
255 b.consume(n: used);
256 BEAST_EXPECT(ec == error::need_more);
257 ec = {};
258 BEAST_EXPECT(! p.is_done());
259 ostream(buffer&: b) <<
260 "\r\n"; // final crlf to end message
261 used = p.put(buffers: b.data(), ec);
262 b.consume(n: used);
263 BEAST_EXPECTS(! ec, ec.message());
264 BEAST_EXPECT(p.is_done());
265 }
266 // skip body
267 {
268 error_code ec;
269 response_parser<string_body> p;
270 p.skip(v: true);
271 p.put(buffer: buf(
272 s: "HTTP/1.1 200 OK\r\n"
273 "Content-Length: 5\r\n"
274 "\r\n"
275 "*****")
276 , ec);
277 BEAST_EXPECTS(! ec, ec.message());
278 BEAST_EXPECT(p.is_done());
279 BEAST_EXPECT(p.is_header_done());
280 BEAST_EXPECT(p.content_length() &&
281 *p.content_length() == 5);
282 }
283 }
284
285 //--------------------------------------------------------------------------
286
287 template<class DynamicBuffer>
288 void
289 testNeedMore()
290 {
291 error_code ec;
292 std::size_t used;
293 {
294 DynamicBuffer b;
295 parser_type<true> p;
296 ostream(b) <<
297 "GET / HTTP/1.1\r\n";
298 used = p.put(b.data(), ec);
299 BEAST_EXPECTS(ec == error::need_more, ec.message());
300 b.consume(used);
301 ec = {};
302 ostream(b) <<
303 "User-Agent: test\r\n"
304 "\r\n";
305 used = p.put(b.data(), ec);
306 BEAST_EXPECTS(! ec, ec.message());
307 b.consume(used);
308 BEAST_EXPECT(p.is_done());
309 BEAST_EXPECT(p.is_header_done());
310 }
311 }
312
313 void
314 testGotSome()
315 {
316 error_code ec;
317 parser_type<true> p;
318 auto used = p.put(buffer: buf(s: ""), ec);
319 BEAST_EXPECT(ec == error::need_more);
320 BEAST_EXPECT(! p.got_some());
321 BEAST_EXPECT(used == 0);
322 ec = {};
323 used = p.put(buffer: buf(s: "G"), ec);
324 BEAST_EXPECT(ec == error::need_more);
325 BEAST_EXPECT(p.got_some());
326 BEAST_EXPECT(used == 0);
327 }
328
329 void
330 testIssue818()
331 {
332 // Make sure that the parser clears pre-existing fields
333 request<string_body> m;
334 m.set(name: field::accept, value: "html/text");
335 BEAST_EXPECT(std::distance(m.begin(), m.end()) == 1);
336 request_parser<string_body> p{std::move(m)};
337 BEAST_EXPECT(std::distance(m.begin(), m.end()) == 0);
338 auto& m1 = p.get();
339 BEAST_EXPECT(std::distance(m1.begin(), m1.end()) == 0);
340 }
341
342 void
343 testIssue1187()
344 {
345 // make sure parser finishes on redirect
346 error_code ec;
347 parser_type<false> p;
348 p.eager(v: true);
349 p.put(buffer: buf(
350 s: "HTTP/1.1 301 Moved Permanently\r\n"
351 "Location: https://www.ebay.com\r\n"
352 "\r\n\r\n"), ec);
353 BEAST_EXPECTS(! ec, ec.message());
354 BEAST_EXPECT(p.is_header_done());
355 BEAST_EXPECT(! p.is_done());
356 BEAST_EXPECT(p.need_eof());
357 }
358
359 void
360 testIssue1880()
361 {
362 // A user raised the issue that multiple Content-Length fields and
363 // values are permissible provided all values are the same.
364 // See rfc7230 section-3.3.2
365 // https://tools.ietf.org/html/rfc7230#section-3.3.2
366 // Credit: Dimitry Bulsunov
367
368 auto checkPass = [&](std::string const& message)
369 {
370 response_parser<string_body> parser;
371 error_code ec;
372 parser.put(buffers: net::buffer(data: message), ec);
373 BEAST_EXPECTS(!ec.failed(), ec.message());
374 };
375
376 auto checkFail = [&](std::string const& message)
377 {
378 response_parser<string_body> parser;
379 error_code ec;
380 parser.put(buffers: net::buffer(data: message), ec);
381 BEAST_EXPECTS(ec == error::multiple_content_length, ec.message());
382 };
383
384 // multiple contents lengths the same
385 checkPass(
386 "HTTP/1.1 200 OK\r\n"
387 "Content-Length: 0\r\n"
388 "Content-Length: 0\r\n"
389 "\r\n");
390
391 // multiple contents lengths different
392 checkFail(
393 "HTTP/1.1 200 OK\r\n"
394 "Content-Length: 0\r\n"
395 "Content-Length: 1\r\n"
396 "\r\n");
397
398 // multiple content in same header
399 checkPass(
400 "HTTP/1.1 200 OK\r\n"
401 "Content-Length: 0, 0, 0\r\n"
402 "\r\n");
403
404 // multiple content in same header but mismatch (case 1)
405 checkFail(
406 "HTTP/1.1 200 OK\r\n"
407 "Content-Length: 0, 0, 1\r\n"
408 "\r\n");
409
410 // multiple content in same header but mismatch (case 2)
411 checkFail(
412 "HTTP/1.1 200 OK\r\n"
413 "Content-Length: 0, 0, 0\r\n"
414 "Content-Length: 1\r\n"
415 "\r\n");
416 }
417
418 void
419 run() override
420 {
421 testParse();
422 testNeedMore<flat_buffer>();
423 testNeedMore<multi_buffer>();
424 testGotSome();
425 testIssue818();
426 testIssue1187();
427 testIssue1880();
428 }
429};
430
431BEAST_DEFINE_TESTSUITE(beast,http,parser);
432
433} // http
434} // beast
435} // boost
436

source code of boost/libs/beast/test/beast/http/parser.cpp