Unverified Commit 21cee3c4 authored by Kenton Varda's avatar Kenton Varda Committed by GitHub

Merge pull request #870 from capnproto/501-on-bad-method

Return HTTP 501 instead of 400 on unrecognized method.
parents 40eb7779 0c9fabb2
...@@ -2448,7 +2448,7 @@ KJ_TEST("HttpServer bad request") { ...@@ -2448,7 +2448,7 @@ KJ_TEST("HttpServer bad request") {
auto listenTask = server.listenHttp(kj::mv(pipe.ends[0])); auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
static constexpr auto request = "bad request\r\n\r\n"_kj; static constexpr auto request = "GET / HTTP/1.1\r\nbad request\r\n\r\n"_kj;
auto writePromise = pipe.ends[1]->write(request.begin(), request.size()); auto writePromise = pipe.ends[1]->write(request.begin(), request.size());
auto response = pipe.ends[1]->readAllText().wait(waitScope); auto response = pipe.ends[1]->readAllText().wait(waitScope);
KJ_EXPECT(writePromise.poll(waitScope)); KJ_EXPECT(writePromise.poll(waitScope));
...@@ -2457,6 +2457,34 @@ KJ_TEST("HttpServer bad request") { ...@@ -2457,6 +2457,34 @@ KJ_TEST("HttpServer bad request") {
static constexpr auto expectedResponse = static constexpr auto expectedResponse =
"HTTP/1.1 400 Bad Request\r\n" "HTTP/1.1 400 Bad Request\r\n"
"Connection: close\r\n" "Connection: close\r\n"
"Content-Length: 53\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"ERROR: The headers sent by your client are not valid."_kj;
KJ_EXPECT(expectedResponse == response, expectedResponse, response);
}
KJ_TEST("HttpServer invalid method") {
KJ_HTTP_TEST_SETUP_IO;
kj::TimerImpl timer(kj::origin<kj::TimePoint>());
auto pipe = KJ_HTTP_TEST_CREATE_2PIPE;
HttpHeaderTable table;
BrokenHttpService service;
HttpServer server(timer, table, service);
auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
static constexpr auto request = "bad request\r\n\r\n"_kj;
auto writePromise = pipe.ends[1]->write(request.begin(), request.size());
auto response = pipe.ends[1]->readAllText().wait(waitScope);
KJ_EXPECT(writePromise.poll(waitScope));
writePromise.wait(waitScope);
static constexpr auto expectedResponse =
"HTTP/1.1 501 Not Implemented\r\n"
"Connection: close\r\n"
"Content-Length: 35\r\n" "Content-Length: 35\r\n"
"Content-Type: text/plain\r\n" "Content-Type: text/plain\r\n"
"\r\n" "\r\n"
......
...@@ -848,7 +848,8 @@ static char* trimHeaderEnding(kj::ArrayPtr<char> content) { ...@@ -848,7 +848,8 @@ static char* trimHeaderEnding(kj::ArrayPtr<char> content) {
HttpHeaders::RequestOrProtocolError HttpHeaders::tryParseRequest(kj::ArrayPtr<char> content) { HttpHeaders::RequestOrProtocolError HttpHeaders::tryParseRequest(kj::ArrayPtr<char> content) {
char* end = trimHeaderEnding(content); char* end = trimHeaderEnding(content);
if (end == nullptr) { if (end == nullptr) {
return ProtocolError { "ERROR: Request headers have no terminal newline.", content }; return ProtocolError { 400, "Bad Request",
"ERROR: Request headers have no terminal newline.", content };
} }
char* ptr = content.begin(); char* ptr = content.begin();
...@@ -858,24 +859,28 @@ HttpHeaders::RequestOrProtocolError HttpHeaders::tryParseRequest(kj::ArrayPtr<ch ...@@ -858,24 +859,28 @@ HttpHeaders::RequestOrProtocolError HttpHeaders::tryParseRequest(kj::ArrayPtr<ch
KJ_IF_MAYBE(method, consumeHttpMethod(ptr)) { KJ_IF_MAYBE(method, consumeHttpMethod(ptr)) {
request.method = *method; request.method = *method;
if (*ptr != ' ' && *ptr != '\t') { if (*ptr != ' ' && *ptr != '\t') {
return ProtocolError { "ERROR: Unrecognized request method.", content }; return ProtocolError { 501, "Not Implemented",
"ERROR: Unrecognized request method.", content };
} }
++ptr; ++ptr;
} else { } else {
return ProtocolError { "ERROR: Unrecognized request method.", content }; return ProtocolError { 501, "Not Implemented",
"ERROR: Unrecognized request method.", content };
} }
KJ_IF_MAYBE(path, consumeWord(ptr)) { KJ_IF_MAYBE(path, consumeWord(ptr)) {
request.url = *path; request.url = *path;
} else { } else {
return ProtocolError { "ERROR: Invalid request line.", content }; return ProtocolError { 400, "Bad Request",
"ERROR: Invalid request line.", content };
} }
// Ignore rest of line. Don't care about "HTTP/1.1" or whatever. // Ignore rest of line. Don't care about "HTTP/1.1" or whatever.
consumeLine(ptr); consumeLine(ptr);
if (!parseHeaders(ptr, end)) { if (!parseHeaders(ptr, end)) {
return ProtocolError { "ERROR: The headers sent by your client are not valid.", content }; return ProtocolError { 400, "Bad Request",
"ERROR: The headers sent by your client are not valid.", content };
} }
return request; return request;
...@@ -884,7 +889,8 @@ HttpHeaders::RequestOrProtocolError HttpHeaders::tryParseRequest(kj::ArrayPtr<ch ...@@ -884,7 +889,8 @@ HttpHeaders::RequestOrProtocolError HttpHeaders::tryParseRequest(kj::ArrayPtr<ch
HttpHeaders::ResponseOrProtocolError HttpHeaders::tryParseResponse(kj::ArrayPtr<char> content) { HttpHeaders::ResponseOrProtocolError HttpHeaders::tryParseResponse(kj::ArrayPtr<char> content) {
char* end = trimHeaderEnding(content); char* end = trimHeaderEnding(content);
if (end == nullptr) { if (end == nullptr) {
return ProtocolError { "ERROR: Response headers have no terminal newline.", content }; return ProtocolError { 502, "Bad Gateway",
"ERROR: Response headers have no terminal newline.", content };
} }
char* ptr = content.begin(); char* ptr = content.begin();
...@@ -893,22 +899,26 @@ HttpHeaders::ResponseOrProtocolError HttpHeaders::tryParseResponse(kj::ArrayPtr< ...@@ -893,22 +899,26 @@ HttpHeaders::ResponseOrProtocolError HttpHeaders::tryParseResponse(kj::ArrayPtr<
KJ_IF_MAYBE(version, consumeWord(ptr)) { KJ_IF_MAYBE(version, consumeWord(ptr)) {
if (!version->startsWith("HTTP/")) { if (!version->startsWith("HTTP/")) {
return ProtocolError { "ERROR: Invalid response status line.", content }; return ProtocolError { 502, "Bad Gateway",
"ERROR: Invalid response status line.", content };
} }
} else { } else {
return ProtocolError { "ERROR: Invalid response status line.", content }; return ProtocolError { 502, "Bad Gateway",
"ERROR: Invalid response status line.", content };
} }
KJ_IF_MAYBE(code, consumeNumber(ptr)) { KJ_IF_MAYBE(code, consumeNumber(ptr)) {
response.statusCode = *code; response.statusCode = *code;
} else { } else {
return ProtocolError { "ERROR: Invalid response status code.", content }; return ProtocolError { 502, "Bad Gateway",
"ERROR: Invalid response status code.", content };
} }
response.statusText = consumeLine(ptr); response.statusText = consumeLine(ptr);
if (!parseHeaders(ptr, end)) { if (!parseHeaders(ptr, end)) {
return ProtocolError { "ERROR: The headers sent by the server are not valid.", content }; return ProtocolError { 502, "Bad Gateway",
"ERROR: The headers sent by the server are not valid.", content };
} }
return response; return response;
...@@ -4740,6 +4750,7 @@ public: ...@@ -4740,6 +4750,7 @@ public:
.then([this]() -> HttpHeaders::RequestOrProtocolError { .then([this]() -> HttpHeaders::RequestOrProtocolError {
timedOut = true; timedOut = true;
return HttpHeaders::ProtocolError { return HttpHeaders::ProtocolError {
408, "Request Timeout",
"ERROR: Timed out waiting for next request headers.", nullptr "ERROR: Timed out waiting for next request headers.", nullptr
}; };
})); }));
...@@ -4750,6 +4761,7 @@ public: ...@@ -4750,6 +4761,7 @@ public:
// error, so don't report one. // error, so don't report one.
this->closed = true; this->closed = true;
return HttpHeaders::RequestOrProtocolError(HttpHeaders::ProtocolError { return HttpHeaders::RequestOrProtocolError(HttpHeaders::ProtocolError {
408, "Request Timeout",
"ERROR: Client closed connection or connection timeout " "ERROR: Client closed connection or connection timeout "
"while waiting for request headers.", nullptr "while waiting for request headers.", nullptr
}); });
...@@ -4763,6 +4775,7 @@ public: ...@@ -4763,6 +4775,7 @@ public:
.then([this]() -> HttpHeaders::RequestOrProtocolError { .then([this]() -> HttpHeaders::RequestOrProtocolError {
timedOut = true; timedOut = true;
return HttpHeaders::ProtocolError { return HttpHeaders::ProtocolError {
408, "Request Timeout",
"ERROR: Timed out waiting for initial request headers.", nullptr "ERROR: Timed out waiting for initial request headers.", nullptr
}; };
}); });
...@@ -5082,7 +5095,8 @@ private: ...@@ -5082,7 +5095,8 @@ private:
kj::Own<WebSocket> sendWebSocketError(StringPtr errorMessage) { kj::Own<WebSocket> sendWebSocketError(StringPtr errorMessage) {
kj::Exception exception = KJ_EXCEPTION(FAILED, kj::Exception exception = KJ_EXCEPTION(FAILED,
"received bad WebSocket handshake", errorMessage); "received bad WebSocket handshake", errorMessage);
webSocketError = sendError(HttpHeaders::ProtocolError { errorMessage, nullptr }); webSocketError = sendError(
HttpHeaders::ProtocolError { 400, "Bad Request", errorMessage, nullptr });
kj::throwRecoverableException(kj::mv(exception)); kj::throwRecoverableException(kj::mv(exception));
// Fallback path when exceptions are disabled. // Fallback path when exceptions are disabled.
...@@ -5215,7 +5229,8 @@ kj::Promise<void> HttpServerErrorHandler::handleClientProtocolError( ...@@ -5215,7 +5229,8 @@ kj::Promise<void> HttpServerErrorHandler::handleClientProtocolError(
headers.set(HttpHeaderId::CONTENT_TYPE, "text/plain"); headers.set(HttpHeaderId::CONTENT_TYPE, "text/plain");
auto errorMessage = kj::str(protocolError.description); auto errorMessage = kj::str(protocolError.description);
auto body = response.send(400, "Bad Request", headers, errorMessage.size()); auto body = response.send(protocolError.statusCode, protocolError.statusMessage,
headers, errorMessage.size());
return body->write(errorMessage.begin(), errorMessage.size()) return body->write(errorMessage.begin(), errorMessage.size())
.attach(kj::mv(errorMessage), kj::mv(body)); .attach(kj::mv(errorMessage), kj::mv(body));
......
...@@ -331,6 +331,14 @@ public: ...@@ -331,6 +331,14 @@ public:
// Or HttpProtocolError? Or maybe we need a more general way of attaching sensitive context to // Or HttpProtocolError? Or maybe we need a more general way of attaching sensitive context to
// kj::Exceptions? // kj::Exceptions?
uint statusCode;
// Suggested HTTP status code that should be used when returning an error to the client.
//
// Most errors are 400. An unrecognized method will be 501.
kj::StringPtr statusMessage;
// HTTP status message to go with `statusCode`, e.g. "Bad Request".
kj::StringPtr description; kj::StringPtr description;
// An error description safe for all the world to see. // An error description safe for all the world to see.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment