Skip to content

Commit b4dc8ce

Browse files
committed
Allow overriding defaulted headers.
This change fixes the linearization of HTTP requests so that two things happen: - empty headers are ignored - explicitly defining the defaulted headers will suppress defaults Fixes cpp-netlib#263
1 parent 79f6727 commit b4dc8ce

File tree

3 files changed

+145
-38
lines changed

3 files changed

+145
-38
lines changed

boost/network/protocol/http/algorithms/linearize.hpp

+76-35
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
// (See accompanying file LICENSE_1_0.txt or copy at
77
// http://www.boost.org/LICENSE_1_0.txt)
88

9+
#include <algorithm>
10+
#include <bitset>
911
#include <boost/network/traits/string.hpp>
1012
#include <boost/network/protocol/http/message/header/name.hpp>
1113
#include <boost/network/protocol/http/message/header/value.hpp>
@@ -15,6 +17,7 @@
1517
#include <boost/concept/requires.hpp>
1618
#include <boost/optional.hpp>
1719
#include <boost/range/algorithm/copy.hpp>
20+
#include <boost/algorithm/string/compare.hpp>
1821

1922
namespace boost { namespace network { namespace http {
2023

@@ -92,48 +95,86 @@ namespace boost { namespace network { namespace http {
9295
*oi = consts::dot_char();
9396
boost::copy(version_minor_str, oi);
9497
boost::copy(crlf, oi);
95-
boost::copy(host, oi);
96-
*oi = consts::colon_char();
97-
*oi = consts::space_char();
98-
boost::copy(request.host(), oi);
99-
boost::optional<boost::uint16_t> port_ = port(request);
100-
if (port_) {
101-
string_type port_str = boost::lexical_cast<string_type>(*port_);
102-
*oi = consts::colon_char();
103-
boost::copy(port_str, oi);
104-
}
105-
boost::copy(crlf, oi);
106-
boost::copy(accept, oi);
107-
*oi = consts::colon_char();
108-
*oi = consts::space_char();
109-
boost::copy(accept_mime, oi);
110-
boost::copy(crlf, oi);
111-
if (version_major == 1u && version_minor == 1u) {
112-
boost::copy(accept_encoding, oi);
113-
*oi = consts::colon_char();
114-
*oi = consts::space_char();
115-
boost::copy(default_accept_encoding, oi);
116-
boost::copy(crlf, oi);
117-
}
98+
99+
// We need to determine whether we've seen any of the following headers
100+
// before setting the defaults. We use a bitset to keep track of the
101+
// defaulted headers.
102+
enum { ACCEPT, ACCEPT_ENCODING, HOST, MAX };
103+
std::bitset<MAX> found_headers;
104+
static char const* defaulted_headers[][2] = {
105+
{consts::accept(),
106+
consts::accept() + std::strlen(consts::accept())},
107+
{consts::accept_encoding(),
108+
consts::accept_encoding() + std::strlen(consts::accept_encoding())},
109+
{consts::host(), consts::host() + std::strlen(consts::host())}
110+
};
111+
118112
typedef typename headers_range<Request>::type headers_range;
119113
typedef typename range_value<headers_range>::type headers_value;
120-
BOOST_FOREACH(const headers_value &header, headers(request))
121-
{
122-
string_type header_name = name(header),
123-
header_value = value(header);
124-
boost::copy(header_name, oi);
125-
*oi = consts::colon_char();
126-
*oi = consts::space_char();
127-
boost::copy(header_value, oi);
128-
boost::copy(crlf, oi);
114+
BOOST_FOREACH(const headers_value & header, headers(request)) {
115+
string_type header_name = name(header), header_value = value(header);
116+
// Here we check that we have not seen an override to the defaulted
117+
// headers.
118+
for (int header_index = 0; header_index < MAX; ++header_index)
119+
if (std::distance(header_name.begin(), header_name.end()) ==
120+
std::distance(defaulted_headers[header_index][0],
121+
defaulted_headers[header_index][1]) &&
122+
std::equal(header_name.begin(),
123+
header_name.end(),
124+
defaulted_headers[header_index][0],
125+
algorithm::is_iequal()))
126+
found_headers.set(header_index, true);
127+
128+
// We ignore empty headers.
129+
if (header_value.empty()) continue;
130+
boost::copy(header_name, oi);
131+
*oi = consts::colon_char();
132+
*oi = consts::space_char();
133+
boost::copy(header_value, oi);
134+
boost::copy(crlf, oi);
135+
136+
}
137+
138+
if (!found_headers[HOST]) {
139+
boost::copy(host, oi);
140+
*oi = consts::colon_char();
141+
*oi = consts::space_char();
142+
boost::copy(request.host(), oi);
143+
boost::optional<boost::uint16_t> port_ = port(request);
144+
if (port_) {
145+
string_type port_str = boost::lexical_cast<string_type>(*port_);
146+
*oi = consts::colon_char();
147+
boost::copy(port_str, oi);
148+
}
149+
boost::copy(crlf, oi);
150+
}
151+
152+
if (!found_headers[ACCEPT]) {
153+
boost::copy(accept, oi);
154+
*oi = consts::colon_char();
155+
*oi = consts::space_char();
156+
boost::copy(accept_mime, oi);
157+
boost::copy(crlf, oi);
158+
}
159+
160+
if (version_major == 1u &&
161+
version_minor == 1u &&
162+
!found_headers[ACCEPT_ENCODING]) {
163+
boost::copy(accept_encoding, oi);
164+
*oi = consts::colon_char();
165+
*oi = consts::space_char();
166+
boost::copy(default_accept_encoding, oi);
167+
boost::copy(crlf, oi);
129168
}
169+
130170
if (!connection_keepalive<Tag>::value) {
131-
boost::copy(connection, oi);
132-
*oi = consts::colon_char();
133-
*oi = consts::space_char();
171+
boost::copy(connection, oi);
172+
*oi = consts::colon_char();
173+
*oi = consts::space_char();
134174
boost::copy(close, oi);
135175
boost::copy(crlf, oi);
136176
}
177+
137178
boost::copy(crlf, oi);
138179
typename body_range<Request>::type body_data = body(request).range();
139180
return boost::copy(body_data, oi);

libs/network/test/http/CMakeLists.txt

+23
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,29 @@ if (OPENSSL_FOUND)
1212
endif()
1313

1414
if (Boost_FOUND)
15+
set ( TESTS
16+
request_incremental_parser_test
17+
request_linearize_test
18+
)
19+
foreach ( test ${TESTS} )
20+
if (${CMAKE_CXX_COMPILER_ID} MATCHES GNU)
21+
set_source_files_properties(${test}.cpp
22+
PROPERTIES COMPILE_FLAGS "-Wall")
23+
endif()
24+
add_executable(cpp-netlib-http-${test} ${test}.cpp)
25+
add_dependencies(cpp-netlib-http-${test}
26+
cppnetlib-uri)
27+
target_link_libraries(cpp-netlib-http-${test}
28+
${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}
29+
cppnetlib-uri)
30+
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
31+
target_link_libraries(cpp-netlib-http-${test} rt)
32+
endif()
33+
set_target_properties(cpp-netlib-http-${test}
34+
PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CPP-NETLIB_BINARY_DIR}/tests)
35+
add_test(cpp-netlib-http-${test}
36+
${CPP-NETLIB_BINARY_DIR}/tests/cpp-netlib-http-${test})
37+
endforeach (test)
1538
set ( TESTS
1639
client_constructor_test
1740
client_get_test

libs/network/test/http/request_linearize_test.cpp

+46-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <boost/test/unit_test.hpp>
1111
#include <boost/mpl/list.hpp>
1212
#include <iostream>
13+
#include <iterator>
1314

1415
namespace http = boost::network::http;
1516
namespace tags = boost::network::http::tags;
@@ -24,8 +25,50 @@ typedef mpl::list<
2425
> tag_types;
2526

2627
BOOST_AUTO_TEST_CASE_TEMPLATE(linearize_request, T, tag_types) {
27-
http::basic_request<T> request("http://www.boost.org");
28-
linearize(request, "GET", 1, 0, std::ostream_iterator<typename net::char_<T>::type>(std::cout));
29-
linearize(request, "GET", 1, 1, std::ostream_iterator<typename net::char_<T>::type>(std::cout));
28+
http::basic_request<T> request("http://www.boost.org");
29+
static char http_1_0_output[] =
30+
"GET / HTTP/1.0\r\n"
31+
"Host: www.boost.org\r\n"
32+
"Accept: */*\r\n"
33+
"Connection: Close\r\n"
34+
"\r\n";
35+
static char http_1_1_output[] =
36+
"GET / HTTP/1.1\r\n"
37+
"Host: www.boost.org\r\n"
38+
"Accept: */*\r\n"
39+
"Accept-Encoding: identity;q=1.0, *;q=0\r\n"
40+
"Connection: Close\r\n"
41+
"\r\n";
42+
typename http::basic_request<T>::string_type output_1_0;
43+
linearize(request, "GET", 1, 0, std::back_inserter(output_1_0));
44+
BOOST_CHECK_EQUAL(output_1_0, http_1_0_output);
45+
typename http::basic_request<T>::string_type output_1_1;
46+
linearize(request, "GET", 1, 1, std::back_inserter(output_1_1));
47+
BOOST_CHECK_EQUAL(output_1_1, http_1_1_output);
48+
}
49+
50+
BOOST_AUTO_TEST_CASE_TEMPLATE(linearize_request_override_headers,
51+
T,
52+
tag_types) {
53+
http::basic_request<T> request("http://www.boost.org");
54+
// We can override the defaulted headers and test that here.
55+
request << net::header("Accept", "");
56+
static char http_1_0_no_accept_output[] =
57+
"GET / HTTP/1.0\r\n"
58+
"Host: www.boost.org\r\n"
59+
"Connection: Close\r\n"
60+
"\r\n";
61+
static char http_1_1_no_accept_output[] =
62+
"GET / HTTP/1.1\r\n"
63+
"Host: www.boost.org\r\n"
64+
"Accept-Encoding: identity;q=1.0, *;q=0\r\n"
65+
"Connection: Close\r\n"
66+
"\r\n";
67+
typename http::basic_request<T>::string_type output_1_0;
68+
linearize(request, "GET", 1, 0, std::back_inserter(output_1_0));
69+
BOOST_CHECK_EQUAL(output_1_0, http_1_0_no_accept_output);
70+
typename http::basic_request<T>::string_type output_1_1;
71+
linearize(request, "GET", 1, 1, std::back_inserter(output_1_1));
72+
BOOST_CHECK_EQUAL(output_1_1, http_1_1_no_accept_output);
3073
}
3174

0 commit comments

Comments
 (0)