From 2fdc5384ea9cee6b00d4b1aafb0dc46eb970803d Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 15 Feb 2024 12:47:29 -0800 Subject: [PATCH] expect handles presence of request content --- include/boost/http_proto/error.hpp | 10 +++ src/detail/header.cpp | 48 ++++++++++--- test/unit/fields_base.cpp | 108 +++++++++++++++++++++++++++++ test/unit/request.cpp | 39 +++++++++-- 4 files changed, 190 insertions(+), 15 deletions(-) diff --git a/include/boost/http_proto/error.hpp b/include/boost/http_proto/error.hpp index 54f26bd0..109588a3 100644 --- a/include/boost/http_proto/error.hpp +++ b/include/boost/http_proto/error.hpp @@ -123,6 +123,16 @@ enum class error */ body_too_large, + /** Expect needs content + + The parser found a valid Expect + header but has not yet found + a field that defines the request + as having either a content-length + or a chunked transfer-encoding + */ + expect_needs_content, + /** Headers too large. * The combined size of the start line and diff --git a/src/detail/header.cpp b/src/detail/header.cpp index 9cdc3565..d4fabddc 100644 --- a/src/detail/header.cpp +++ b/src/detail/header.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2024 Christian Mazakas // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,6 +9,7 @@ // #include +#include #include #include #include @@ -24,7 +26,6 @@ #include #include #include -#include #include namespace boost { @@ -524,6 +525,11 @@ on_insert_content_length( // one value md.content_length.ec = {}; md.content_length.value = *rv; + if( md.expect.ec == error::expect_needs_content ) + { + md.expect.ec = {}; + md.expect.is_100_continue = true; + } update_payload(); return; } @@ -548,16 +554,25 @@ on_insert_expect( ++md.expect.count; if(kind != detail::kind::request) return; - if(md.expect.ec.failed()) - return; + // VFALCO Should we allow duplicate // Expect fields that have 100-continue? - // TODO: we need to add content checks - // i.e. does the request have either `content-length` - // or a `transfer-encoding: chunked`. - // https://datatracker.ietf.org/doc/html/rfc9110#section-10.1.1-10 + // https://datatracker.ietf.org/doc/html/rfc9110#section-10.1.1-11.1 + // A client MUST NOT generate a 100-continue expectation in a + // request that does not include content. + auto const has_content = [&] + { + auto const has_content_length = + md.content_length.count > 0 && + !md.content_length.ec.failed(); + + auto const is_chunked = + md.transfer_encoding.is_chunked; + return has_content_length || + is_chunked; + }(); if( md.expect.count > 1 || ! grammar::ci_is_equal(v, @@ -569,6 +584,16 @@ on_insert_expect( md.expect.is_100_continue = false; return; } + + if(! has_content ) + { + md.expect.ec = + BOOST_HTTP_PROTO_ERR( + error::expect_needs_content); + md.expect.is_100_continue = false; + return; + } + md.expect.is_100_continue = true; } @@ -606,7 +631,14 @@ on_insert_transfer_encoding() if(! md.transfer_encoding.is_chunked) { if(t.id == transfer_coding::chunked) + { md.transfer_encoding.is_chunked = true; + if( md.expect.ec == error::expect_needs_content ) + { + md.expect.ec = {}; + md.expect.is_100_continue = true; + } + } continue; } if(t.id == transfer_coding::chunked) @@ -758,7 +790,7 @@ on_erase_expect() return; */ // reset and re-insert - auto n = md.expect.count; + auto n = count; auto const p = cbuf + prefix; auto const* e = &tab()[0]; md.expect = {}; diff --git a/test/unit/fields_base.cpp b/test/unit/fields_base.cpp index bbec9f11..5d05fce8 100644 --- a/test/unit/fields_base.cpp +++ b/test/unit/fields_base.cpp @@ -1087,12 +1087,35 @@ struct fields_base_test { {}, 1, true }, "POST / HTTP/1.1\r\n" "Expect: 100-continue\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n"); + + check( + { {}, 1, true }, + "POST / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "Expect: 100-continue\r\n" + "\r\n"); + + check( + { {}, 1, true }, + "POST / HTTP/1.1\r\n" + "Content-Length: 1234\r\n" + "Expect: 100-continue\r\n" + "\r\n"); + + check( + { {}, 1, true }, + "POST / HTTP/1.1\r\n" + "Expect: 100-continue\r\n" + "Content-Length: 1234\r\n" "\r\n"); check( { error::bad_expect, 1, false }, "POST / HTTP/1.1\r\n" "Expect: 100-continueish\r\n" + "Content-Length: 1234\r\n" "\r\n"); check( @@ -1100,14 +1123,30 @@ struct fields_base_test "POST / HTTP/1.1\r\n" "Expect: 100-continue\r\n" "Expect: 100-continue\r\n" + "Content-Length: 1234\r\n" "\r\n"); check( { error::bad_expect, 2, false }, "POST / HTTP/1.1\r\n" "Expect: 100-continue\r\n" + "Expect: 100-continue\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n"); + + check( + { error::bad_expect, 2, false }, + "POST / HTTP/1.1\r\n" + "Expect: 100-continue\r\n" + "Content-Length: 1234\r\n" "Expect: 404-not-found\r\n" "\r\n"); + + check( + { error::expect_needs_content, 1, false }, + "POST / HTTP/1.1\r\n" + "Expect: 100-continue\r\n" + "\r\n"); } // parse response @@ -1240,6 +1279,7 @@ struct fields_base_test req.count(field::expect), 1); }, "POST / HTTP/1.1\r\n" + "Content-Length: 1234\r\n" "Expect: 100-continue\r\n" "Expect: 100-continue\r\n" "\r\n"); @@ -1273,6 +1313,7 @@ struct fields_base_test "POST / HTTP/1.1\r\n" "Expect: 100-continue\r\n" "Expect: 404-not-found\r\n" + "Transfer-Encoding: chunked\r\n" "\r\n"); // set @@ -1299,6 +1340,7 @@ struct fields_base_test }, "POST / HTTP/1.1\r\n" "Expect: 100-continueish\r\n" + "Content-Length: 1234\r\n" "\r\n"); check( @@ -1310,9 +1352,75 @@ struct fields_base_test "100-continue"); }, "POST / HTTP/1.1\r\n" + "Content-Length: 1234\r\n" "Expect: 500-server-error\r\n" "Expect: 404-not-found\r\n" "\r\n"); + + // erase + set + + check( + { {}, 1, true }, + [](request& req) + { + BOOST_TEST_EQ( + req.metadata().expect.ec, + error::bad_expect); + + req.erase(req.find(field::expect)); + + BOOST_TEST_EQ( + req.metadata().expect.ec, + error::expect_needs_content); + + req.set_content_length(1234); + }, + "POST / HTTP/1.1\r\n" + "Expect: 100-continue\r\n" + "Expect: 100-continue\r\n" + "\r\n"); + + check( + { {}, 1, true }, + [](request& req) + { + BOOST_TEST_EQ( + req.metadata().expect.ec, + error::bad_expect); + + req.erase(req.find(field::expect)); + + BOOST_TEST_EQ( + req.metadata().expect.ec, + error::expect_needs_content); + + req.set_content_length(1234); + }, + "POST / HTTP/1.1\r\n" + "Expect: 101-dalmations\r\n" + "Expect: 100-continue\r\n" + "\r\n"); + + check( + { {}, 1, true }, + [](request& req) + { + BOOST_TEST_EQ( + req.metadata().expect.ec, + error::bad_expect); + + req.erase(req.find(field::expect)); + + BOOST_TEST_EQ( + req.metadata().expect.ec, + error::expect_needs_content); + + req.set_content_length(1234); + }, + "POST / HTTP/1.1\r\n" + "Expect: 101-dalmations\r\n" + "Expect: 100-continue\r\n" + "\r\n"); } } diff --git a/test/unit/request.cpp b/test/unit/request.cpp index 95b345c6..fe219fe3 100644 --- a/test/unit/request.cpp +++ b/test/unit/request.cpp @@ -469,23 +469,48 @@ struct request_test request req; req.set_expect_100_continue(true); BOOST_TEST( - req.metadata().expect.is_100_continue); + req.metadata().expect.ec.failed()); + } + { + request req; req.set_expect_100_continue(false); BOOST_TEST( !req.metadata().expect.is_100_continue); + BOOST_TEST( + !req.metadata().expect.ec.failed()); } + auto make_request = [] + { + request req( + "POST / HTTP/1.1\r\n" + "content-length: 1234\r\n" + "\r\n"); + + return req; + }; { - request req; + request req = make_request(); + req.set_expect_100_continue(true); + BOOST_TEST( + req.metadata().expect.is_100_continue); + req.set_expect_100_continue(false); BOOST_TEST( !req.metadata().expect.is_100_continue); } { - request req; + request req = make_request(); + req.set_expect_100_continue(false); + BOOST_TEST( + !req.metadata().expect.is_100_continue); + } + + { + request req = make_request(); req.set_expect_100_continue(true); BOOST_TEST( req.metadata().expect.is_100_continue); @@ -496,7 +521,7 @@ struct request_test } { - request req; + request req = make_request(); BOOST_TEST( !req.metadata().expect.ec.failed()); @@ -510,7 +535,7 @@ struct request_test } { - request req; + request req = make_request(); req.append("Expect", "100-continue"); req.append("Expect", "101-dalmations"); @@ -527,7 +552,7 @@ struct request_test } { - request req; + request req = make_request(); req.append("Expect", "100-continue"); req.append("Expect", "100-continue"); @@ -544,7 +569,7 @@ struct request_test } { - request req; + request req = make_request(); req.append("Expect", "100-continue"); req.append("Expect", "100-continue");