Skip to content

Commit

Permalink
Reject http request without host
Browse files Browse the repository at this point in the history
  • Loading branch information
chenBright committed Apr 13, 2024
1 parent d7eca39 commit 18df3e9
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 15 deletions.
38 changes: 24 additions & 14 deletions src/brpc/details/http_message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
// specific language governing permissions and limitations
// under the License.


#include <cstdlib>

#include <string> // std::string
#include <iostream>
#include <gflags/gflags.h>
Expand All @@ -35,6 +32,8 @@ namespace brpc {

DEFINE_bool(allow_chunked_length, false,
"Allow both Transfer-Encoding and Content-Length headers are present.");
DEFINE_bool(allow_http_1_1_request_without_host, true,
"Allow HTTP/1.1 request without host which violates the HTTP/1.1 specification.");
DEFINE_bool(http_verbose, false,
"[DEBUG] Print EVERY http request/response");
DEFINE_int32(http_verbose_max_body_length, 512,
Expand Down Expand Up @@ -143,37 +142,48 @@ int HttpMessage::on_headers_complete(http_parser *parser) {
LOG(WARNING) << "Invalid major_version=" << parser->http_major;
parser->http_major = 1;
}
http_message->header().set_version(parser->http_major, parser->http_minor);
HttpHeader& headers = http_message->header();
headers.set_version(parser->http_major, parser->http_minor);
// Only for response
// http_parser may set status_code to 0 when the field is not needed,
// e.g. in a request. In principle status_code is undefined in a request,
// but to be consistent and not surprise users, we set it to OK as well.
http_message->header().set_status_code(
headers.set_status_code(
!parser->status_code ? HTTP_STATUS_OK : parser->status_code);
// Only for request
// method is 0(which is DELETE) for response as well. Since users are
// unlikely to check method of a response, we don't do anything.
http_message->header().set_method(static_cast<HttpMethod>(parser->method));
if (parser->type == HTTP_REQUEST &&
http_message->header().uri().SetHttpURL(http_message->_url) != 0) {
headers.set_method(static_cast<HttpMethod>(parser->method));
bool is_http_request = parser->type == HTTP_REQUEST;
if (is_http_request && headers.uri().SetHttpURL(http_message->_url) != 0) {
LOG(ERROR) << "Fail to parse url=`" << http_message->_url << '\'';
return -1;
}
//rfc2616-sec5.2
// https://datatracker.ietf.org/doc/html/rfc2616#section-5.2
//1. If Request-URI is an absoluteURI, the host is part of the Request-URI.
//Any Host header field value in the request MUST be ignored.
//2. If the Request-URI is not an absoluteURI, and the request includes a
//Host header field, the host is determined by the Host header field value.
//3. If the host as determined by rule 1 or 2 is not a valid host on the
//server, the responce MUST be a 400 error messsage.
URI & uri = http_message->header().uri();
//server, the responce MUST be a 400 (Bad Request) error messsage.
URI& uri = headers.uri();
if (uri._host.empty()) {
const std::string* host_header = http_message->header().GetHeader("host");
const std::string* host_header = headers.GetHeader("host");
if (host_header != NULL) {
uri.SetHostAndPort(*host_header);
}
}

// https://datatracker.ietf.org/doc/html/rfc2616#section-14.23
// All Internet-based HTTP/1.1 servers MUST respond with a 400 (Bad Request)
// status code to any HTTP/1.1 request message which lacks a Host header field.
if (uri.host().empty() && is_http_request &&
!headers.before_http_1_1() &&
!FLAGS_allow_http_1_1_request_without_host) {
LOG(ERROR) << "HTTP protocol error: missing host header";
return -1;
}

// https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3
// If a message is received with both a Transfer-Encoding and a
// Content-Length header field, the Transfer-Encoding overrides the
Expand All @@ -188,9 +198,9 @@ int HttpMessage::on_headers_complete(http_parser *parser) {
// is chunked - remove Content-Length and serve request.
if (parser->uses_transfer_encoding && parser->flags & F_CONTENTLENGTH) {
if (parser->flags & F_CHUNKED && FLAGS_allow_chunked_length) {
http_message->header().RemoveHeader("Content-Length");
headers.RemoveHeader("Content-Length");
} else {
LOG(ERROR) << "HTTP/1.1 protocol error: both Content-Length "
LOG(ERROR) << "HTTP protocol error: both Content-Length "
<< "and Transfer-Encoding are set.";
return -1;
}
Expand Down
2 changes: 1 addition & 1 deletion src/butil/iobuf_profiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ static uint g_iobuf_profiler_sample_rate = 100;
static void InitGlobalIOBufProfilerInfo() {
const char* enabled = getenv("ENABLE_IOBUF_PROFILER");
g_iobuf_profiler_enabled = enabled && strcmp("1", enabled) == 0 && ::GetStackTrace != NULL;
if (g_iobuf_profiler_enabled) {
if (!g_iobuf_profiler_enabled) {
return;
}

Expand Down
43 changes: 43 additions & 0 deletions test/brpc_http_message_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@
namespace brpc {

DECLARE_bool(allow_chunked_length);
DECLARE_bool(allow_http_1_1_request_without_host);

int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true);
brpc::FLAGS_allow_http_1_1_request_without_host = true;
return RUN_ALL_TESTS();
}

namespace policy {
Server::MethodProperty*
Expand Down Expand Up @@ -576,4 +584,39 @@ TEST(HttpMessageTest, serialize_http_response) {
<< butil::ToPrintable(response);
}

TEST(HttpMessageTest, http_1_1_request_without_host) {
brpc::FLAGS_allow_http_1_1_request_without_host = false;
{
butil::IOBuf request;
request.append("GET /service/method HTTP/1.1\r\n"
"Content-Type: text/plain\r\n\r\n");

brpc::HttpMessage http_message;
ASSERT_TRUE(http_message.ParseFromIOBuf(request) < 0);
}
{
butil::IOBuf request;
request.append("GET http://baidu.com/service/method HTTP/1.1\r\n"
"Content-Type: text/plain\r\n\r\n");

brpc::HttpMessage http_message;
ASSERT_TRUE(http_message.ParseFromIOBuf(request) >= 0);
ASSERT_TRUE(http_message.Completed());
ASSERT_EQ("text/plain", http_message.header().content_type());
}
{
butil::IOBuf request;
request.append("GET /service/method HTTP/1.1\r\n"
"Content-Type: text/plain\r\n"
"Host: baidu.com\r\n\r\n");

brpc::HttpMessage http_message;
ASSERT_GE(http_message.ParseFromIOBuf(request), 0);
ASSERT_GE(http_message.ParseFromArray(NULL, 0), 0);
ASSERT_TRUE(http_message.Completed());
ASSERT_EQ("text/plain", http_message.header().content_type());
}
brpc::FLAGS_allow_http_1_1_request_without_host = true;
}

} //namespace

0 comments on commit 18df3e9

Please sign in to comment.