Only read body from authenticated connections

This means we are not allowing unauthenticated requests anymore
This commit is contained in:
Jean Flach 2018-02-01 15:10:28 +01:00
parent e1c46cedd1
commit a46dc64e6a
3 changed files with 162 additions and 138 deletions

View File

@ -29,135 +29,147 @@
using namespace icinga; using namespace icinga;
HttpRequest::HttpRequest(const Stream::Ptr& stream) HttpRequest::HttpRequest(const Stream::Ptr& stream)
: Complete(false), : CompleteHeaders(false),
ProtocolVersion(HttpVersion11), CompleteBody(false),
Headers(new Dictionary()), ProtocolVersion(HttpVersion11),
m_Stream(stream), Headers(new Dictionary()),
m_State(HttpRequestStart) m_Stream(stream),
m_State(HttpRequestStart)
{ } { }
bool HttpRequest::Parse(StreamReadContext& src, bool may_wait) bool HttpRequest::ParseHeader(StreamReadContext& src, bool may_wait)
{ {
if (!m_Stream) if (!m_Stream)
return false; return false;
if (m_State != HttpRequestBody) { if (m_State != HttpRequestStart && m_State != HttpRequestHeaders)
String line; return false;
StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait);
if (srs != StatusNewItem) { String line;
if (src.Size > 512) StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait);
BOOST_THROW_EXCEPTION(std::invalid_argument("Line length for HTTP header exceeded"));
if (srs != StatusNewItem) {
if (src.Size > 512)
BOOST_THROW_EXCEPTION(std::invalid_argument("Line length for HTTP header exceeded"));
return false;
}
if (line.GetLength() > 512)
BOOST_THROW_EXCEPTION(std::invalid_argument("Line length for HTTP header exceeded"));
if (m_State == HttpRequestStart) {
/* ignore trailing new-lines */
if (line == "")
return true;
std::vector<String> tokens = line.Split(" ");
Log(LogDebug, "HttpRequest")
<< "line: " << line << ", tokens: " << tokens.size();
if (tokens.size() != 3)
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
RequestMethod = tokens[0];
RequestUrl = new class Url(tokens[1]);
if (tokens[2] == "HTTP/1.0")
ProtocolVersion = HttpVersion10;
else if (tokens[2] == "HTTP/1.1") {
ProtocolVersion = HttpVersion11;
} else
BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
m_State = HttpRequestHeaders;
return true;
} else { // m_State = HttpRequestHeaders
if (line == "") {
m_State = HttpRequestBody;
CompleteHeaders = true;
return true;
} else {
if (Headers->GetLength() > 128)
BOOST_THROW_EXCEPTION(std::invalid_argument("Maximum number of HTTP request headers exceeded"));
String::SizeType pos = line.FindFirstOf(":");
if (pos == String::NPos)
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
String key = line.SubStr(0, pos).ToLower().Trim();
String value = line.SubStr(pos + 1).Trim();
Headers->Set(key, value);
if (key == "x-http-method-override")
RequestMethod = value;
return true;
}
}
}
bool HttpRequest::ParseBody(StreamReadContext& src, bool may_wait)
{
if (!m_Stream || m_State != HttpRequestBody)
return false;
/* we're done if the request doesn't contain a message body */
if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding")) {
CompleteBody = true;
return true;
} else if (!m_Body)
m_Body = new FIFO();
if (CompleteBody)
return true;
if (Headers->Get("transfer-encoding") == "chunked") {
if (!m_ChunkContext)
m_ChunkContext = boost::make_shared<ChunkReadContext>(boost::ref(src));
char *data;
size_t size;
StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait);
if (srs != StatusNewItem)
return false;
m_Body->Write(data, size);
delete [] data;
if (size == 0) {
CompleteBody = true;
return true;
}
} else {
if (src.Eof)
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
if (src.MustRead) {
if (!src.FillFromStream(m_Stream, false)) {
src.Eof = true;
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
}
src.MustRead = false;
}
long length_indicator_signed = Convert::ToLong(Headers->Get("content-length"));
if (length_indicator_signed < 0)
BOOST_THROW_EXCEPTION(std::invalid_argument("Content-Length must not be negative."));
size_t length_indicator = length_indicator_signed;
if (src.Size < length_indicator) {
src.MustRead = true;
return false; return false;
} }
if (line.GetLength() > 512) m_Body->Write(src.Buffer, length_indicator);
BOOST_THROW_EXCEPTION(std::invalid_argument("Line length for HTTP header exceeded")); src.DropData(length_indicator);
CompleteBody = true;
if (m_State == HttpRequestStart) { return true;
/* ignore trailing new-lines */
if (line == "")
return true;
std::vector<String> tokens;
boost::algorithm::split(tokens, line, boost::is_any_of(" "));
Log(LogDebug, "HttpRequest")
<< "line: " << line << ", tokens: " << tokens.size();
if (tokens.size() != 3)
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
RequestMethod = tokens[0];
RequestUrl = new class Url(tokens[1]);
if (tokens[2] == "HTTP/1.0")
ProtocolVersion = HttpVersion10;
else if (tokens[2] == "HTTP/1.1") {
ProtocolVersion = HttpVersion11;
} else
BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
m_State = HttpRequestHeaders;
} else if (m_State == HttpRequestHeaders) {
if (line == "") {
m_State = HttpRequestBody;
/* we're done if the request doesn't contain a message body */
if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding"))
Complete = true;
else
m_Body = new FIFO();
return true;
} else {
if (Headers->GetLength() > 128)
BOOST_THROW_EXCEPTION(std::invalid_argument("Maximum number of HTTP request headers exceeded"));
String::SizeType pos = line.FindFirstOf(":");
if (pos == String::NPos)
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
String key = line.SubStr(0, pos).ToLower().Trim();
String value = line.SubStr(pos + 1).Trim();
Headers->Set(key, value);
if (key == "x-http-method-override")
RequestMethod = value;
}
} else {
VERIFY(!"Invalid HTTP request state.");
}
} else if (m_State == HttpRequestBody) {
if (Headers->Get("transfer-encoding") == "chunked") {
if (!m_ChunkContext)
m_ChunkContext = boost::make_shared<ChunkReadContext>(boost::ref(src));
char *data;
size_t size;
StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait);
if (srs != StatusNewItem)
return false;
m_Body->Write(data, size);
delete [] data;
if (size == 0) {
Complete = true;
return true;
}
} else {
if (src.Eof)
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
if (src.MustRead) {
if (!src.FillFromStream(m_Stream, false)) {
src.Eof = true;
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
}
src.MustRead = false;
}
long length_indicator_signed = Convert::ToLong(Headers->Get("content-length"));
if (length_indicator_signed < 0)
BOOST_THROW_EXCEPTION(std::invalid_argument("Content-Length must not be negative."));
size_t length_indicator = length_indicator_signed;
if (src.Size < length_indicator) {
src.MustRead = true;
return false;
}
m_Body->Write(src.Buffer, length_indicator);
src.DropData(length_indicator);
Complete = true;
return true;
}
} }
return true; return true;

View File

@ -52,7 +52,8 @@ enum HttpRequestState
struct I2_REMOTE_API HttpRequest struct I2_REMOTE_API HttpRequest
{ {
public: public:
bool Complete; bool CompleteHeaders;
bool CompleteBody;
String RequestMethod; String RequestMethod;
Url::Ptr RequestUrl; Url::Ptr RequestUrl;
@ -62,7 +63,8 @@ public:
HttpRequest(const Stream::Ptr& stream); HttpRequest(const Stream::Ptr& stream);
bool Parse(StreamReadContext& src, bool may_wait); bool ParseHeader(StreamReadContext& src, bool may_wait);
bool ParseBody(StreamReadContext& src, bool may_wait);
size_t ReadBody(char *data, size_t count); size_t ReadBody(char *data, size_t count);
void AddHeader(const String& key, const String& value); void AddHeader(const String& key, const String& value);

View File

@ -92,7 +92,7 @@ bool HttpServerConnection::ProcessMessage(void)
bool res; bool res;
try { try {
res = m_CurrentRequest.Parse(m_Context, false); res = m_CurrentRequest.ParseHeader(m_Context, false);
} catch (const std::invalid_argument& ex) { } catch (const std::invalid_argument& ex) {
HttpResponse response(m_Stream, m_CurrentRequest); HttpResponse response(m_Stream, m_CurrentRequest);
response.SetStatus(400, "Bad request"); response.SetStatus(400, "Bad request");
@ -113,7 +113,7 @@ bool HttpServerConnection::ProcessMessage(void)
return false; return false;
} }
if (m_CurrentRequest.Complete) { if (m_CurrentRequest.CompleteHeaders) {
m_RequestQueue.Enqueue(boost::bind(&HttpServerConnection::ProcessMessageAsync, m_RequestQueue.Enqueue(boost::bind(&HttpServerConnection::ProcessMessageAsync,
HttpServerConnection::Ptr(this), m_CurrentRequest)); HttpServerConnection::Ptr(this), m_CurrentRequest));
@ -241,25 +241,35 @@ void HttpServerConnection::ProcessMessageAsync(HttpRequest& request)
response.WriteBody(msg.CStr(), msg.GetLength()); response.WriteBody(msg.CStr(), msg.GetLength());
} }
} else { } else {
try { bool res = true;
HttpHandler::ProcessRequest(user, request, response); while (!request.CompleteBody)
} catch (const std::exception& ex) { res = request.ParseBody(m_Context, false);
Log(LogCritical, "HttpServerConnection") if (!res) {
<< "Unhandled exception while processing Http request: " << DiagnosticInformation(ex); Log(LogCritical, "HttpServerConnection", "Failed to read body");
response.SetStatus(503, "Unhandled exception"); Dictionary::Ptr result = new Dictionary;
result->Set("error", 404);
result->Set("status", "Bad Request: Malformed body.");
HttpUtility::SendJsonBody(response, result);
} else {
try {
HttpHandler::ProcessRequest(user, request, response);
} catch (const std::exception& ex) {
Log(LogCritical, "HttpServerConnection")
<< "Unhandled exception while processing Http request: " << DiagnosticInformation(ex);
response.SetStatus(503, "Unhandled exception");
String errorInfo = DiagnosticInformation(ex); String errorInfo = DiagnosticInformation(ex);
if (request.Headers->Get("accept") == "application/json") { if (request.Headers->Get("accept") == "application/json") {
Dictionary::Ptr result = new Dictionary(); Dictionary::Ptr result = new Dictionary();
result->Set("error", 503);
result->Set("status", errorInfo);
result->Set("error", 503); HttpUtility::SendJsonBody(response, result);
result->Set("status", errorInfo); } else {
response.AddHeader("Content-Type", "text/plain");
HttpUtility::SendJsonBody(response, result); response.WriteBody(errorInfo.CStr(), errorInfo.GetLength());
} else { }
response.AddHeader("Content-Type", "text/plain");
response.WriteBody(errorInfo.CStr(), errorInfo.GetLength());
} }
} }
} }