nghttpx: Support unknown method

This commit is contained in:
Tatsuhiro Tsujikawa 2016-05-28 16:08:20 +09:00
parent 852a320586
commit f38babe30f
17 changed files with 377 additions and 103 deletions

View File

@ -53,23 +53,20 @@ func TestH1H1PlainGETClose(t *testing.T) {
}
}
// TestH1H1InvalidMethod tests that server rejects invalid method with
// 501 status code
func TestH1H1InvalidMethod(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
// TestH1H1UnknownMethod tests that server can forward unknown method
func TestH1H1UnknownMethod(t *testing.T) {
st := newServerTester(nil, t, noopHandler)
defer st.Close()
res, err := st.http1(requestParam{
name: "TestH1H1InvalidMethod",
name: "TestH1H1UnknownMethod",
method: "get",
})
if err != nil {
t.Fatalf("Error st.http1() = %v", err)
}
if got, want := res.status, 501; got != want {
if got, want := res.status, 200; got != want {
t.Errorf("status = %v; want %v", got, want)
}
}

View File

@ -589,22 +589,19 @@ func TestH2H1InvalidRequestCL(t *testing.T) {
// }
// }
// TestH2H1InvalidMethod tests that server rejects invalid method with
// 501.
func TestH2H1InvalidMethod(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
// TestH2H1UnknownMethod tests that server can forward unknown method.
func TestH2H1UnknownMethod(t *testing.T) {
st := newServerTester(nil, t, noopHandler)
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1InvalidMethod",
name: "TestH2H1UnknownMethod",
method: "get",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 501; got != want {
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}

View File

@ -210,22 +210,19 @@ func TestS3H1HeaderFields(t *testing.T) {
}
}
// TestS3H1InvalidMethod tests that server rejects invalid method with
// 501.
func TestS3H1InvalidMethod(t *testing.T) {
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
// TestS3H1UnknownMethod tests that server can forward unknown method.
func TestS3H1UnknownMethod(t *testing.T) {
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, noopHandler)
defer st.Close()
res, err := st.spdy(requestParam{
name: "TestS3H1InvalidMethod",
name: "TestS3H1UnknownMethod",
method: "get",
})
if err != nil {
t.Fatalf("Error st.spdy() = %v", err)
}
if got, want := res.status, 501; got != want {
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}

View File

@ -824,7 +824,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
// proxy mode falls in this case.
if (groups.size() == 1) {
group_idx = 0;
} else if (req.method == HTTP_CONNECT) {
} else if (req.method_token == HTTP_CONNECT) {
// We don't know how to treat CONNECT request in host-path
// mapping. It most likely appears in proxy scenario. Since we
// have dealt with proxy case already, just use catch-all group.
@ -1040,14 +1040,14 @@ void ClientHandler::write_accesslog(Downstream *downstream) {
upstream_accesslog(
get_config()->logging.access.format,
LogSpec{
downstream, StringRef{ipaddr_}, http2::to_method_string(req.method),
downstream, StringRef{ipaddr_}, req.method,
req.method == HTTP_CONNECT
req.method_token == HTTP_CONNECT
? StringRef(req.authority)
: get_config()->http2_proxy
? StringRef(construct_absolute_request_uri(balloc, req))
: req.path.empty()
? req.method == HTTP_OPTIONS
? req.method_token == HTTP_OPTIONS
? StringRef::from_lit("*")
: StringRef::from_lit("-")
: StringRef(req.path),

View File

@ -635,7 +635,7 @@ bool Downstream::validate_response_recv_body_length() const {
}
void Downstream::check_upgrade_fulfilled() {
if (req_.method == HTTP_CONNECT) {
if (req_.method_token == HTTP_CONNECT) {
upgraded_ = 200 <= resp_.http_status && resp_.http_status < 300;
return;
@ -650,13 +650,13 @@ void Downstream::check_upgrade_fulfilled() {
}
void Downstream::inspect_http2_request() {
if (req_.method == HTTP_CONNECT) {
if (req_.method_token == HTTP_CONNECT) {
req_.upgrade_request = true;
}
}
void Downstream::inspect_http1_request() {
if (req_.method == HTTP_CONNECT) {
if (req_.method_token == HTTP_CONNECT) {
req_.upgrade_request = true;
} else {
auto upgrade = req_.fs.header(http2::HD_UPGRADE);
@ -741,7 +741,7 @@ bool Downstream::get_expect_final_response() const {
bool Downstream::expect_response_body() const {
return !resp_.headers_only &&
http2::expect_response_body(req_.method, resp_.http_status);
http2::expect_response_body(req_.method_token, resp_.http_status);
}
bool Downstream::expect_response_trailer() const {

View File

@ -126,7 +126,7 @@ struct Request {
: fs(balloc, 16),
recv_body_length(0),
unconsumed_body_length(0),
method(-1),
method_token(-1),
http_major(1),
http_minor(1),
upgrade_request(false),
@ -158,7 +158,8 @@ struct Request {
int64_t recv_body_length;
// The number of bytes not consumed by the application yet.
size_t unconsumed_body_length;
int method;
StringRef method;
int method_token;
// HTTP major and minor version
int http_major, http_minor;
// Returns true if the request is HTTP upgrade (HTTP Upgrade or

View File

@ -103,7 +103,7 @@ int Http2DownstreamConnection::attach_downstream(Downstream *downstream) {
auto &req = downstream_->request();
// HTTP/2 disables HTTP Upgrade.
if (req.method != HTTP_CONNECT) {
if (req.method_token != HTTP_CONNECT) {
req.upgrade_request = false;
}
@ -251,7 +251,7 @@ int Http2DownstreamConnection::push_request_headers() {
auto no_host_rewrite = httpconf.no_host_rewrite ||
get_config()->http2_proxy ||
req.method == HTTP_CONNECT;
req.method_token == HTTP_CONNECT;
// http2session_ has already in CONNECTED state, so we can get
// addr_idx here.
@ -286,15 +286,14 @@ int Http2DownstreamConnection::push_request_headers() {
nva.reserve(req.fs.headers().size() + 9 + num_cookies +
httpconf.add_request_headers.size());
nva.push_back(
http2::make_nv_ls_nocopy(":method", http2::to_method_string(req.method)));
nva.push_back(http2::make_nv_ls_nocopy(":method", req.method));
if (req.method != HTTP_CONNECT) {
if (req.method_token != HTTP_CONNECT) {
assert(!req.scheme.empty());
nva.push_back(http2::make_nv_ls_nocopy(":scheme", req.scheme));
if (req.method == HTTP_OPTIONS && req.path.empty()) {
if (req.method_token == HTTP_OPTIONS && req.path.empty()) {
nva.push_back(http2::make_nv_ll(":path", "*"));
} else {
nva.push_back(http2::make_nv_ls_nocopy(":path", req.path));
@ -333,7 +332,7 @@ int Http2DownstreamConnection::push_request_headers() {
if (fwdconf.params) {
auto params = fwdconf.params;
if (get_config()->http2_proxy || req.method == HTTP_CONNECT) {
if (get_config()->http2_proxy || req.method_token == HTTP_CONNECT) {
params &= ~FORWARDED_PROTO;
}
@ -376,7 +375,7 @@ int Http2DownstreamConnection::push_request_headers() {
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff->value));
}
if (!get_config()->http2_proxy && req.method != HTTP_CONNECT) {
if (!get_config()->http2_proxy && req.method_token != HTTP_CONNECT) {
// We use same protocol with :scheme header field
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", req.scheme));
}
@ -428,7 +427,7 @@ int Http2DownstreamConnection::push_request_headers() {
// Add body as long as transfer-encoding is given even if
// req.fs.content_length == 0 to forward trailer fields.
if (req.method == HTTP_CONNECT || transfer_encoding ||
if (req.method_token == HTTP_CONNECT || transfer_encoding ||
req.fs.content_length > 0 || req.http2_expect_body) {
// Request-body is expected.
nghttp2_data_provider data_prd{{}, http2_data_read_callback};

View File

@ -2055,13 +2055,6 @@ int Http2Session::handle_downstream_push_promise_complete(
}
auto method_token = http2::lookup_method_token(method->value);
if (method_token == -1) {
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Unrecognized method: " << method->value;
}
return -1;
}
// TODO Rewrite authority if we enabled rewrite host. But we
// really don't know how to rewrite host. Should we use the same
@ -2069,7 +2062,8 @@ int Http2Session::handle_downstream_push_promise_complete(
if (authority) {
promised_req.authority = authority->value;
}
promised_req.method = method_token;
promised_req.method = method->value;
promised_req.method_token = method_token;
// libnghttp2 ensures that we don't have CONNECT method in
// PUSH_PROMISE, and guarantees that :scheme exists.
if (scheme) {

View File

@ -117,7 +117,7 @@ int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
rv = nghttp2_session_upgrade2(
session_, reinterpret_cast<const uint8_t *>(settings_payload.c_str()),
settings_payload.size(),
http->get_downstream()->request().method == HTTP_HEAD, nullptr);
http->get_downstream()->request().method_token == HTTP_HEAD, nullptr);
if (rv != 0) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "nghttp2_session_upgrade() returned error: "
@ -296,12 +296,6 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
auto scheme = req.fs.header(http2::HD__SCHEME);
auto method_token = http2::lookup_method_token(method->value);
if (method_token == -1) {
if (error_reply(downstream, 501) != 0) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
return 0;
}
// For HTTP/2 proxy, we request :authority.
if (method_token != HTTP_CONNECT && get_config()->http2_proxy && !authority) {
@ -309,7 +303,8 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
return 0;
}
req.method = method_token;
req.method = method->value;
req.method_token = method_token;
if (scheme) {
req.scheme = scheme->value;
}
@ -604,9 +599,11 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
auto token = http2::lookup_token(nv.name, nv.namelen);
switch (token) {
case http2::HD__METHOD:
req.method = http2::lookup_method_token(value);
case http2::HD__METHOD: {
req.method = value;
req.method_token = http2::lookup_method_token(value);
break;
}
case http2::HD__SCHEME:
req.scheme = value;
break;
@ -1415,7 +1412,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
!get_config()->http2_proxy && (downstream->get_stream_id() % 2) &&
resp.fs.header(http2::HD_LINK) &&
(downstream->get_non_final_response() || resp.http_status == 200) &&
(req.method == HTTP_GET || req.method == HTTP_POST)) {
(req.method_token == HTTP_GET || req.method_token == HTTP_POST)) {
if (prepare_push_promise(downstream) != 0) {
// Continue to send response even if push was failed.

View File

@ -321,7 +321,7 @@ int HttpDownstreamConnection::push_request_headers() {
auto &balloc = downstream_->get_block_allocator();
auto connect_method = req.method == HTTP_CONNECT;
auto connect_method = req.method_token == HTTP_CONNECT;
auto &httpconf = get_config()->http;
@ -340,8 +340,7 @@ int HttpDownstreamConnection::push_request_headers() {
auto buf = downstream_->get_request_buf();
// Assume that method and request path do not contain \r\n.
auto meth = http2::to_method_string(req.method);
buf->append(meth);
buf->append(req.method);
buf->append(" ");
if (connect_method) {
@ -354,7 +353,7 @@ int HttpDownstreamConnection::push_request_headers() {
buf->append("://");
buf->append(authority);
buf->append(req.path);
} else if (req.method == HTTP_OPTIONS && req.path.empty()) {
} else if (req.method_token == HTTP_OPTIONS && req.path.empty()) {
// Server-wide OPTIONS
buf->append("*");
} else {
@ -708,7 +707,7 @@ int htp_hdrs_completecb(http_parser *htp) {
// TODO It seems that the cases other than HEAD are handled by
// http-parser. Need test.
return !http2::expect_response_body(req.method, resp.http_status);
return !http2::expect_response_body(req.method_token, resp.http_status);
}
} // namespace

View File

@ -80,6 +80,33 @@ int htp_msg_begin(http_parser *htp) {
}
} // namespace
namespace {
int htp_methodcb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data);
auto downstream = upstream->get_downstream();
auto &req = downstream->request();
auto &balloc = downstream->get_block_allocator();
if (req.fs.buffer_size() + len >
get_config()->http.request_header_field_buffer) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too large request size="
<< req.fs.buffer_size() + len;
}
assert(downstream->get_request_state() == Downstream::INITIAL);
downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
return -1;
}
req.fs.add_extra_buffer_size(len);
req.method = concat_string_ref(balloc, req.method, StringRef{data, len});
return 0;
}
} // namespace
namespace {
int htp_uricb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data);
@ -88,8 +115,11 @@ int htp_uricb(http_parser *htp, const char *data, size_t len) {
auto &balloc = downstream->get_block_allocator();
// This could be executed more than once, but no harm here.
if (htp->method != HTTP_METHOD_UNKNOWN) {
// We happen to have the same value for method token.
req.method = htp->method;
req.method_token = htp->method;
}
if (req.fs.buffer_size() + len >
get_config()->http.request_header_field_buffer) {
@ -104,7 +134,7 @@ int htp_uricb(http_parser *htp, const char *data, size_t len) {
req.fs.add_extra_buffer_size(len);
if (req.method == HTTP_CONNECT) {
if (req.method_token == HTTP_CONNECT) {
req.authority =
concat_string_ref(balloc, req.authority, StringRef{data, len});
} else {
@ -243,7 +273,7 @@ void rewrite_request_host_path_from_uri(BlockAllocator &balloc, Request &req,
StringRef path;
if (u.field_set & (1 << UF_PATH)) {
path = util::get_uri_field(uri.c_str(), u, UF_PATH);
} else if (req.method == HTTP_OPTIONS) {
} else if (req.method_token == HTTP_OPTIONS) {
// Server-wide OPTIONS takes following form in proxy request:
//
// OPTIONS http://example.org HTTP/1.1
@ -297,12 +327,10 @@ int htp_hdrs_completecb(http_parser *htp) {
req.connection_close = !http_should_keep_alive(htp);
auto method = req.method;
if (LOG_ENABLED(INFO)) {
std::stringstream ss;
ss << http2::to_method_string(method) << " "
<< (method == HTTP_CONNECT ? req.authority : req.path) << " "
ss << req.method << " "
<< (req.method_token == HTTP_CONNECT ? req.authority : req.path)
<< " HTTP/" << req.http_major << "." << req.http_minor << "\n";
for (const auto &kv : req.fs.headers()) {
@ -340,7 +368,7 @@ int htp_hdrs_completecb(http_parser *htp) {
auto &balloc = downstream->get_block_allocator();
if (method != HTTP_CONNECT) {
if (req.method_token != HTTP_CONNECT) {
http_parser_url u{};
rv = http_parser_parse_url(req.path.c_str(), req.path.size(), 0, &u);
if (rv != 0) {
@ -356,7 +384,8 @@ int htp_hdrs_completecb(http_parser *htp) {
req.no_authority = true;
if (method == HTTP_OPTIONS && req.path == StringRef::from_lit("*")) {
if (req.method_token == HTTP_OPTIONS &&
req.path == StringRef::from_lit("*")) {
req.path = StringRef{};
} else {
req.path = http2::rewrite_clean_path(balloc, req.path);
@ -482,7 +511,10 @@ http_parser_settings htp_hooks = {
htp_hdr_valcb, // http_data_cb on_header_value;
htp_hdrs_completecb, // http_cb on_headers_complete;
htp_bodycb, // http_data_cb on_body;
htp_msg_completecb // http_cb on_message_complete;
htp_msg_completecb, // http_cb on_message_complete;
nullptr, // http_cb on_chunk_header;
nullptr, // http_cb on_chunk_complete;
htp_methodcb, // http_data_cb on_method;
};
} // namespace
@ -973,7 +1005,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
}
#endif // HAVE_MRUBY
auto connect_method = req.method == HTTP_CONNECT;
auto connect_method = req.method_token == HTTP_CONNECT;
auto buf = downstream->get_response_buf();

View File

@ -68,9 +68,8 @@ mrb_value request_get_method(mrb_state *mrb, mrb_value self) {
auto data = static_cast<MRubyAssocData *>(mrb->ud);
auto downstream = data->downstream;
const auto &req = downstream->request();
auto method = http2::to_method_string(req.method);
return mrb_str_new(mrb, method.c_str(), method.size());
return mrb_str_new(mrb, req.method.c_str(), req.method.size());
}
} // namespace
@ -80,6 +79,8 @@ mrb_value request_set_method(mrb_state *mrb, mrb_value self) {
auto downstream = data->downstream;
auto &req = downstream->request();
auto &balloc = downstream->get_block_allocator();
check_phase(mrb, data->phase, PHASE_REQUEST);
const char *method;
@ -88,13 +89,13 @@ mrb_value request_set_method(mrb_state *mrb, mrb_value self) {
if (n == 0) {
mrb_raise(mrb, E_RUNTIME_ERROR, "method must not be empty string");
}
auto token =
http2::lookup_method_token(reinterpret_cast<const uint8_t *>(method), n);
if (token == -1) {
mrb_raise(mrb, E_RUNTIME_ERROR, "method not supported");
}
req.method = token;
req.method =
make_string_ref(balloc, StringRef{method, static_cast<size_t>(n)});
req.method_token = token;
return self;
}

View File

@ -220,12 +220,6 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
}
auto method_token = http2::lookup_method_token(method->value);
if (method_token == -1) {
if (upstream->error_reply(downstream, 501) != 0) {
ULOG(FATAL, upstream) << "error_reply failed";
}
return;
}
auto is_connect = method_token == HTTP_CONNECT;
if (!path || !host || !http2::non_empty_value(host) ||
@ -264,7 +258,8 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
return;
}
req.method = method_token;
req.method = method->value;
req.method_token = method_token;
if (is_connect) {
req.authority = path->value;
} else {

View File

@ -28,6 +28,7 @@ EXTRA_DIST = CMakeLists.txt
if ENABLE_THIRD_PARTY
noinst_LTLIBRARIES = libhttp-parser.la
libhttp_parser_la_CPPFLAGS = ${AMCPPFLAGS} -DHTTP_PARSER_METHOD_CB=1
libhttp_parser_la_SOURCES = \
http-parser/http_parser.c \
http-parser/http_parser.h

View File

@ -280,6 +280,7 @@ enum state
{ s_dead = 1 /* important that this is > 0 */
, s_start_req_or_res
, s_res_or_resp_mark_H
, s_res_or_resp_H
, s_start_res
, s_res_H
@ -298,7 +299,11 @@ enum state
, s_start_req
, s_req_method_start
, s_req_method
#if HTTP_PARSER_METHOD_CB
, s_req_method_unknown
#endif
, s_req_spaces_before_url
, s_req_schema
, s_req_schema_slash
@ -644,6 +649,9 @@ size_t http_parser_execute (http_parser *parser,
const char *url_mark = 0;
const char *body_mark = 0;
const char *status_mark = 0;
#if HTTP_PARSER_METHOD_CB
const char *method_mark = 0;
#endif
enum state p_state = (enum state) parser->state;
const unsigned int lenient = parser->lenient_http_headers;
@ -692,6 +700,14 @@ size_t http_parser_execute (http_parser *parser,
case s_req_fragment:
url_mark = data;
break;
#if HTTP_PARSER_METHOD_CB
case s_res_or_resp_H:
case s_req_method_start:
case s_req_method:
case s_req_method_unknown:
method_mark = data;
break;
#endif
case s_res_status:
status_mark = data;
break;
@ -726,9 +742,10 @@ reexecute:
parser->content_length = ULLONG_MAX;
if (ch == 'H') {
UPDATE_STATE(s_res_or_resp_H);
UPDATE_STATE(s_res_or_resp_mark_H);
CALLBACK_NOTIFY(message_begin);
CALLBACK_NOTIFY_NOADVANCE(message_begin);
REEXECUTE();
} else {
parser->type = HTTP_REQUEST;
UPDATE_STATE(s_start_req);
@ -738,19 +755,46 @@ reexecute:
break;
}
case s_res_or_resp_mark_H:
assert(ch == 'H');
UPDATE_STATE(s_res_or_resp_H);
#if HTTP_PARSER_METHOD_CB
/* TODO This will call on_method even if type == HTTP_BOTH is
* used and it later turns out that this is response.
* Automatic handling bites us hard.
*/
MARK(method);
#endif
break;
case s_res_or_resp_H:
if (ch == 'T') {
parser->type = HTTP_RESPONSE;
UPDATE_STATE(s_res_HT);
} else {
#if HTTP_PARSER_METHOD_CB
if (UNLIKELY(!TOKEN(ch))) {
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
}
if (UNLIKELY(ch != 'E')) {
parser->method = HTTP_METHOD_UNKNOWN;
} else {
parser->method = HTTP_HEAD;
parser->index = 2;
}
#else
if (UNLIKELY(ch != 'E')) {
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
}
parser->type = HTTP_REQUEST;
parser->method = HTTP_HEAD;
parser->index = 2;
#endif
parser->type = HTTP_REQUEST;
UPDATE_STATE(s_req_method);
}
break;
@ -952,7 +996,6 @@ reexecute:
break;
case s_start_req:
{
if (ch == CR || ch == LF)
break;
parser->flags = 0;
@ -963,6 +1006,25 @@ reexecute:
goto error;
}
UPDATE_STATE(s_req_method_start);
#if HTTP_PARSER_METHOD_CB
MARK(method);
#endif
CALLBACK_NOTIFY_NOADVANCE(message_begin);
REEXECUTE();
break;
case s_req_method_start:
{
enum state next_state = s_req_method;
if (UNLIKELY(!IS_ALPHA(ch))) {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
parser->method = (enum http_method) 0;
parser->index = 1;
switch (ch) {
@ -984,12 +1046,26 @@ reexecute:
case 'T': parser->method = HTTP_TRACE; break;
case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break;
default:
#if HTTP_PARSER_METHOD_CB
if (UNLIKELY(!TOKEN(ch))) {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
UPDATE_STATE(s_req_method);
CALLBACK_NOTIFY(message_begin);
parser->method = (unsigned int) HTTP_METHOD_UNKNOWN;
next_state = s_req_method_unknown;
#else
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
#endif
}
UPDATE_STATE(next_state);
#if HTTP_PARSER_METHOD_CB
MARK(method);
#endif
break;
}
@ -1003,8 +1079,21 @@ reexecute:
}
matcher = method_strings[parser->method];
if (ch == ' ' && matcher[parser->index] == '\0') {
if (ch == ' ') {
if (matcher[parser->index] != '\0') {
#if HTTP_PARSER_METHOD_CB
parser->method = (unsigned int) HTTP_METHOD_UNKNOWN;
#else
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
#endif
}
UPDATE_STATE(s_req_spaces_before_url);
#if HTTP_PARSER_METHOD_CB
CALLBACK_DATA(method);
#endif
} else if (ch == matcher[parser->index]) {
; /* nada */
} else if (IS_ALPHA(ch)) {
@ -1034,14 +1123,56 @@ reexecute:
#undef XX
default:
#if HTTP_PARSER_METHOD_CB
if (UNLIKELY(!TOKEN(ch))) {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
parser->method = (unsigned int) HTTP_METHOD_UNKNOWN;
UPDATE_STATE(s_req_method_unknown);
#else
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
#endif
}
} else if (ch == '-' &&
parser->index == 1 &&
parser->method == HTTP_MKCOL) {
parser->method = HTTP_MSEARCH;
} else {
#if HTTP_PARSER_METHOD_CB
if (UNLIKELY(!TOKEN(ch))) {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
parser->method = (unsigned int) HTTP_METHOD_UNKNOWN;
UPDATE_STATE(s_req_method_unknown);
#else
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
#endif
}
++parser->index;
break;
}
#if HTTP_PARSER_METHOD_CB
case s_req_method_unknown:
{
if (UNLIKELY(ch == '\0')) {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
if (ch == ' ') {
UPDATE_STATE(s_req_spaces_before_url);
CALLBACK_DATA(method);
} else if (UNLIKELY(!TOKEN(ch))) {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
@ -1049,6 +1180,7 @@ reexecute:
++parser->index;
break;
}
#endif
case s_req_spaces_before_url:
{
@ -2078,6 +2210,9 @@ reexecute:
CALLBACK_DATA_NOADVANCE(url);
CALLBACK_DATA_NOADVANCE(body);
CALLBACK_DATA_NOADVANCE(status);
#if HTTP_PARSER_METHOD_CB
CALLBACK_DATA_NOADVANCE(method);
#endif
RETURN(len);

View File

@ -53,6 +53,17 @@ typedef unsigned __int64 uint64_t;
# define HTTP_PARSER_STRICT 1
#endif
/* Compile with -DHTTP_PARSER_METHOD_CB=1 to enable method
* callback. If it is enabled, method string is notified with
* on_method callback. The unknown method which would be rejeted
* previously is also accepted and notified with the on_method
* callback. The method field of http_parser struct becomes
* HTTP_METHOD_UNKNOWN if method is unknown to http_parser.
*/
#ifndef HTTP_PARSER_METHOD_CB
# define HTTP_PARSER_METHOD_CB 0
#endif
/* Maximium header size allowed. If the macro is not defined
* before including this header then the default is used. To
* change the maximum header size, define the macro in the build
@ -140,6 +151,8 @@ enum http_method
#undef XX
};
/* Unknown HTTP method */
#define HTTP_METHOD_UNKNOWN 255
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
@ -176,6 +189,7 @@ enum flags
XX(CB_status, "the on_status callback failed") \
XX(CB_chunk_header, "the on_chunk_header callback failed") \
XX(CB_chunk_complete, "the on_chunk_complete callback failed") \
XX(CB_method, "the on_method callback failed") \
\
/* Parsing-related errors */ \
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
@ -218,7 +232,6 @@ enum http_errno {
/* Get an http_errno value from an http_parser */
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
struct http_parser {
/** PRIVATE **/
unsigned int type : 2; /* enum http_parser_type */
@ -264,6 +277,7 @@ struct http_parser_settings {
*/
http_cb on_chunk_header;
http_cb on_chunk_complete;
http_data_cb on_method;
};

View File

@ -52,6 +52,7 @@ struct message {
enum http_method method;
int status_code;
char response_status[MAX_ELEMENT_SIZE];
char request_method[MAX_ELEMENT_SIZE];
char request_path[MAX_ELEMENT_SIZE];
char request_url[MAX_ELEMENT_SIZE];
char fragment[MAX_ELEMENT_SIZE];
@ -103,6 +104,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/test"
@ -134,6 +136,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/favicon.ico"
@ -163,6 +166,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/dumbfuck"
@ -184,6 +188,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= "page=1"
,.fragment= "posts-17408"
,.request_path= "/forums/1/topics/2375"
@ -203,6 +208,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/get_no_headers_no_body/world"
@ -222,6 +228,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/get_one_header_no_body"
@ -245,6 +252,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 0
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/get_funky_content_length_body_hello"
@ -270,6 +278,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_POST
,.request_method = "POST"
,.query_string= "q=search"
,.fragment= "hey"
,.request_path= "/post_identity_body_world"
@ -297,6 +306,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_POST
,.request_method = "POST"
,.query_string= ""
,.fragment= ""
,.request_path= "/post_chunked_all_your_base"
@ -325,6 +335,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_POST
,.request_method = "POST"
,.query_string= ""
,.fragment= ""
,.request_path= "/two_chunks_mult_zero_end"
@ -355,6 +366,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_POST
,.request_method = "POST"
,.query_string= ""
,.fragment= ""
,.request_path= "/chunked_w_trailing_headers"
@ -385,6 +397,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_POST
,.request_method = "POST"
,.query_string= ""
,.fragment= ""
,.request_path= "/chunked_w_bullshit_after_length"
@ -407,6 +420,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= "foo=\"bar\""
,.fragment= ""
,.request_path= "/with_\"stupid\"_quotes"
@ -433,6 +447,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 0
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/test"
@ -456,6 +471,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= "foo=bar?baz"
,.fragment= ""
,.request_path= "/test.cgi"
@ -477,6 +493,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/test"
@ -504,6 +521,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/demo"
@ -535,6 +553,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 0
,.method= HTTP_CONNECT
,.request_method = "CONNECT"
,.query_string= ""
,.fragment= ""
,.request_path= ""
@ -557,6 +576,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_REPORT
,.request_method = "REPORT"
,.query_string= ""
,.fragment= ""
,.request_path= "/test"
@ -576,6 +596,7 @@ const struct message requests[] =
,.http_major= 0
,.http_minor= 9
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/"
@ -598,6 +619,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_MSEARCH
,.request_method = "M-SEARCH"
,.query_string= ""
,.fragment= ""
,.request_path= "*"
@ -633,6 +655,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/"
@ -658,6 +681,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= "hail=all"
,.fragment= ""
,.request_path= ""
@ -678,6 +702,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= "hail=all"
,.fragment= ""
,.request_path= ""
@ -699,6 +724,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= ""
@ -725,6 +751,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_PATCH
,.request_method = "PATCH"
,.query_string= ""
,.fragment= ""
,.request_path= "/file.txt"
@ -750,6 +777,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 0
,.method= HTTP_CONNECT
,.request_method = "CONNECT"
,.query_string= ""
,.fragment= ""
,.request_path= ""
@ -774,6 +802,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= "q=1"
,.fragment= "narf"
,.request_path= "/δ¶/δt/pope"
@ -796,6 +825,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 0
,.method= HTTP_CONNECT
,.request_method = "CONNECT"
,.query_string= ""
,.fragment= ""
,.request_path= ""
@ -823,6 +853,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_POST
,.request_method = "POST"
,.query_string= ""
,.fragment= ""
,.request_path= "/"
@ -851,6 +882,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_POST
,.request_method = "POST"
,.query_string= ""
,.fragment= ""
,.request_path= "/"
@ -876,6 +908,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_PURGE
,.request_method = "PURGE"
,.query_string= ""
,.fragment= ""
,.request_path= "/file.txt"
@ -896,6 +929,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_SEARCH
,.request_method = "SEARCH"
,.query_string= ""
,.fragment= ""
,.request_path= "/"
@ -915,6 +949,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.fragment= ""
,.request_path= "/toto"
,.request_url= "http://a%12:b!&*$@hypnotoad.org:1234/toto"
@ -949,6 +984,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/"
@ -982,6 +1018,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/demo"
@ -1012,6 +1049,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/demo"
@ -1037,6 +1075,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GET
,.request_method = "GET"
,.query_string= ""
,.fragment= ""
,.request_path= "/demo"
@ -1065,6 +1104,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_POST
,.request_method = "POST"
,.request_path= "/demo"
,.request_url= "/demo"
,.num_headers= 4
@ -1091,6 +1131,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 0
,.method= HTTP_CONNECT
,.request_method = "CONNECT"
,.request_url= "foo.bar.com:443"
,.num_headers= 3
,.upgrade="blarfcicle"
@ -1118,6 +1159,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_LINK
,.request_method = "LINK"
,.request_path= "/images/my_dog.jpg"
,.request_url= "/images/my_dog.jpg"
,.query_string= ""
@ -1142,6 +1184,7 @@ const struct message requests[] =
,.http_major= 1
,.http_minor= 1
,.method= HTTP_UNLINK
,.request_method = "UNLINK"
,.request_path= "/images/my_dog.jpg"
,.request_url= "/images/my_dog.jpg"
,.query_string= ""
@ -1153,6 +1196,30 @@ const struct message requests[] =
,.body= ""
}
#if HTTP_PARSER_METHOD_CB
#define UNKNOWN_METHOD 42
, {.name="unknown method"
,.type= HTTP_REQUEST
,.raw= "NON-STANDARD-METHOD / HTTP/1.1\r\n"
"Host: example.com\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.message_complete_on_eof= FALSE
,.http_major= 1
,.http_minor= 1
,.method= HTTP_METHOD_UNKNOWN
,.request_method= "NON-STANDARD-METHOD"
,.query_string= ""
,.fragment= ""
,.request_path= "/"
,.request_url= "/"
,.num_headers= 1
,.headers=
{ { "Host", "example.com" } }
,.body= ""
}
#endif
, {.name= NULL } /* sentinel */
};
@ -1815,6 +1882,19 @@ strlcpy(char *dst, const char *src, size_t len)
return strlncpy(dst, len, src, (size_t) -1);
}
#if HTTP_PARSER_METHOD_CB
int
request_method_cb(http_parser *p, const char *buf, size_t len)
{
assert(p == parser);
strlncat(messages[num_messages].request_method,
sizeof(messages[num_messages].request_method),
buf,
len);
return 0;
}
#endif
int
request_url_cb (http_parser *p, const char *buf, size_t len)
{
@ -2117,6 +2197,16 @@ pause_header_value_cb (http_parser *p, const char *buf, size_t len)
return header_value_cb(p, buf, len);
}
#if HTTP_PARSER_METHOD_CB
int
pause_request_method_cb (http_parser *p, const char *buf, size_t len)
{
http_parser_pause(p, 1);
*current_pause_parser = settings_dontcall;
return request_method_cb(p, buf, len);
}
#endif
int
pause_request_url_cb (http_parser *p, const char *buf, size_t len)
{
@ -2198,6 +2288,9 @@ static http_parser_settings settings_pause =
,.on_message_complete = pause_message_complete_cb
,.on_chunk_header = pause_chunk_header_cb
,.on_chunk_complete = pause_chunk_complete_cb
#if HTTP_PARSER_METHOD_CB
,.on_method = pause_request_method_cb
#endif
};
static http_parser_settings settings =
@ -2211,6 +2304,9 @@ static http_parser_settings settings =
,.on_message_complete = message_complete_cb
,.on_chunk_header = chunk_header_cb
,.on_chunk_complete = chunk_complete_cb
#if HTTP_PARSER_METHOD_CB
,.on_method = request_method_cb
#endif
};
static http_parser_settings settings_count_body =
@ -2224,6 +2320,9 @@ static http_parser_settings settings_count_body =
,.on_message_complete = message_complete_cb
,.on_chunk_header = chunk_header_cb
,.on_chunk_complete = chunk_complete_cb
#if HTTP_PARSER_METHOD_CB
,.on_method = request_method_cb
#endif
};
static http_parser_settings settings_connect =
@ -2237,6 +2336,9 @@ static http_parser_settings settings_connect =
,.on_message_complete = connect_message_complete_cb
,.on_chunk_header = chunk_header_cb
,.on_chunk_complete = chunk_complete_cb
#if HTTP_PARSER_METHOD_CB
,.on_method = request_method_cb
#endif
};
static http_parser_settings settings_null =
@ -2250,6 +2352,9 @@ static http_parser_settings settings_null =
,.on_message_complete = 0
,.on_chunk_header = 0
,.on_chunk_complete = 0
#if HTTP_PARSER_METHOD_CB
,.on_method = 0
#endif
};
void
@ -2376,6 +2481,9 @@ message_eq (int index, int connect, const struct message *expected)
if (expected->type == HTTP_REQUEST) {
MESSAGE_CHECK_NUM_EQ(expected, m, method);
#if HTTP_PARSER_METHOD_CB
MESSAGE_CHECK_STR_EQ(expected, m, request_method);
#endif
} else {
MESSAGE_CHECK_NUM_EQ(expected, m, status_code);
MESSAGE_CHECK_STR_EQ(expected, m, response_status);
@ -3850,11 +3958,10 @@ test_message_connect (const struct message *msg)
{
char *buf = (char*) msg->raw;
size_t buflen = strlen(msg->raw);
size_t nread;
parser_init(msg->type);
nread = parse_connect(buf, buflen);
parse_connect(buf, buflen);
if (num_messages != 1) {
printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name);
@ -4074,7 +4181,15 @@ main (void)
for (this_method = bad_methods; *this_method; this_method++) {
char buf[200];
sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method);
#if HTTP_PARSER_METHOD_CB
if (strchr(*this_method, ' ')) {
test_simple(buf, HPE_INVALID_URL);
} else {
test_simple(buf, HPE_OK);
}
#else
test_simple(buf, HPE_INVALID_METHOD);
#endif
}
// illegal header field name line folding