1
0
Fork 0

WebAdmin uses the new HTTP parser framework.

This commit is contained in:
Mattes D 2016-02-20 11:50:52 +01:00
parent 12d95ab047
commit 52d18b4559
23 changed files with 912 additions and 883 deletions

View File

@ -127,6 +127,9 @@ if(${SELF_TEST})
add_definitions(-DSELF_TEST)
endif()
# Build all dependent libraries as static:
SET(CMAKE_BUILD_STATIC_LIBRARIES ON)
@ -275,7 +278,7 @@ if (MSVC)
creatable-exe
EchoServer
Google-exe
HTTPResponseParser_file-exe
HTTPMessageParser_file-exe
LoadablePieces
NameLookup
PROPERTIES FOLDER Tests

View File

@ -8,8 +8,7 @@ SET (SRCS
EnvelopeParser.cpp
HTTPFormParser.cpp
HTTPMessage.cpp
HTTPRequestParser.cpp
HTTPResponseParser.cpp
HTTPMessageParser.cpp
HTTPServer.cpp
HTTPServerConnection.cpp
MultipartParser.cpp
@ -23,8 +22,7 @@ SET (HDRS
EnvelopeParser.h
HTTPFormParser.h
HTTPMessage.h
HTTPRequestParser.h
HTTPResponseParser.h
HTTPMessageParser.h
HTTPServer.h
HTTPServerConnection.h
MultipartParser.h

View File

@ -5,7 +5,7 @@
#include "Globals.h"
#include "HTTPFormParser.h"
#include "HTTPRequestParser.h"
#include "HTTPMessage.h"
#include "MultipartParser.h"
#include "NameValueParser.h"
@ -13,7 +13,7 @@
cHTTPFormParser::cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks) :
cHTTPFormParser::cHTTPFormParser(const cHTTPIncomingRequest & a_Request, cCallbacks & a_Callbacks) :
m_Callbacks(a_Callbacks),
m_IsValid(true),
m_IsCurrentPartFile(false),
@ -121,7 +121,7 @@ bool cHTTPFormParser::Finish(void)
bool cHTTPFormParser::HasFormData(const cHTTPRequestParser & a_Request)
bool cHTTPFormParser::HasFormData(const cHTTPIncomingRequest & a_Request)
{
const AString & ContentType = a_Request.GetContentType();
return (
@ -138,7 +138,7 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequestParser & a_Request)
void cHTTPFormParser::BeginMultipart(const cHTTPRequestParser & a_Request)
void cHTTPFormParser::BeginMultipart(const cHTTPIncomingRequest & a_Request)
{
ASSERT(m_MultipartParser.get() == nullptr);
m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this));

View File

@ -15,7 +15,7 @@
// fwd:
class cHTTPRequestParser;
class cHTTPIncomingRequest;
@ -51,7 +51,7 @@ public:
/** Creates a parser that is tied to a request and notifies of various events using a callback mechanism */
cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks);
cHTTPFormParser(const cHTTPIncomingRequest & a_Request, cCallbacks & a_Callbacks);
/** Creates a parser with the specified content type that reads data from a string */
cHTTPFormParser(eKind a_Kind, const char * a_Data, size_t a_Size, cCallbacks & a_Callbacks);
@ -64,7 +64,7 @@ public:
bool Finish(void);
/** Returns true if the headers suggest the request has form data parseable by this class */
static bool HasFormData(const cHTTPRequestParser & a_Request);
static bool HasFormData(const cHTTPIncomingRequest & a_Request);
protected:
@ -97,7 +97,7 @@ protected:
/** Sets up the object for parsing a fpkMultipart request */
void BeginMultipart(const cHTTPRequestParser & a_Request);
void BeginMultipart(const cHTTPIncomingRequest & a_Request);
/** Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) */
void ParseFormUrlEncoded(void);

View File

@ -100,3 +100,60 @@ void cHTTPResponse::AppendToData(AString & a_DataStream) const
////////////////////////////////////////////////////////////////////////////////
// cHTTPIncomingRequest:
cHTTPIncomingRequest::cHTTPIncomingRequest(const AString & a_Method, const AString & a_URL):
Super(mkRequest),
m_Method(a_Method),
m_URL(a_URL)
{
}
AString cHTTPIncomingRequest::GetURLPath(void) const
{
auto idxQuestionMark = m_URL.find('?');
if (idxQuestionMark == AString::npos)
{
return m_URL;
}
else
{
return m_URL.substr(0, idxQuestionMark);
}
}
void cHTTPIncomingRequest::AddHeader(const AString & a_Key, const AString & a_Value)
{
if (
(NoCaseCompare(a_Key, "Authorization") == 0) &&
(strncmp(a_Value.c_str(), "Basic ", 6) == 0)
)
{
AString UserPass = Base64Decode(a_Value.substr(6));
size_t idxCol = UserPass.find(':');
if (idxCol != AString::npos)
{
m_AuthUsername = UserPass.substr(0, idxCol);
m_AuthPassword = UserPass.substr(idxCol + 1);
m_HasAuth = true;
}
}
if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0))
{
m_AllowKeepAlive = true;
}
}

View File

@ -36,7 +36,7 @@ public:
virtual ~cHTTPMessage() {}
/** Adds a header into the internal map of headers. Recognizes special headers: Content-Type and Content-Length */
void AddHeader(const AString & a_Key, const AString & a_Value);
virtual void AddHeader(const AString & a_Key, const AString & a_Value);
void SetContentType (const AString & a_ContentType) { m_ContentType = a_ContentType; }
void SetContentLength(size_t a_ContentLength) { m_ContentLength = a_ContentLength; }
@ -82,3 +82,77 @@ public:
/** Provides storage for an incoming HTTP request. */
class cHTTPIncomingRequest:
public cHTTPMessage
{
typedef cHTTPMessage Super;
public:
/** Base class for anything that can be used as the UserData for the request. */
class cUserData
{
public:
// Force a virtual destructor in descendants:
virtual ~cUserData() {}
};
typedef SharedPtr<cUserData> cUserDataPtr;
/** Creates a new instance of the class, containing the method and URL provided by the client. */
cHTTPIncomingRequest(const AString & a_Method, const AString & a_URL);
/** Returns the method used in the request */
const AString & GetMethod(void) const { return m_Method; }
/** Returns the URL used in the request */
const AString & GetURL(void) const { return m_URL; }
/** Returns the path part of the URL. */
AString GetURLPath(void) const;
/** Returns true if the request has had the Auth header present. */
bool HasAuth(void) const { return m_HasAuth; }
/** Returns the username that the request presented. Only valid if HasAuth() is true */
const AString & GetAuthUsername(void) const { return m_AuthUsername; }
/** Returns the password that the request presented. Only valid if HasAuth() is true */
const AString & GetAuthPassword(void) const { return m_AuthPassword; }
bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; }
/** Attaches any kind of data to this request, to be later retrieved by GetUserData(). */
void SetUserData(cUserDataPtr a_UserData) { m_UserData = a_UserData; }
/** Returns the data attached to this request by the class client. */
cUserDataPtr GetUserData(void) { return m_UserData; }
/** Adds the specified header into the internal list of headers.
Overrides the parent to add recognizing additional headers: auth and keepalive. */
virtual void AddHeader(const AString & a_Key, const AString & a_Value) override;
protected:
/** Method of the request (GET / PUT / POST / ...) */
AString m_Method;
/** Full URL of the request */
AString m_URL;
/** Set to true if the request contains auth data that was understood by the parser */
bool m_HasAuth;
/** The username used for auth */
AString m_AuthUsername;
/** The password used for auth */
AString m_AuthPassword;
/** Set to true if the request indicated that it supports keepalives.
If false, the server will close the connection once the request is finished */
bool m_AllowKeepAlive;
/** Any data attached to the request by the class client. */
cUserDataPtr m_UserData;
};

View File

@ -0,0 +1,222 @@
// HTTPMessageParser.cpp
// Implements the cHTTPMessageParser class that parses HTTP messages (request or response) being pushed into the parser,
// and reports the individual parts via callbacks
#include "Globals.h"
#include "HTTPMessageParser.h"
cHTTPMessageParser::cHTTPMessageParser(cHTTPMessageParser::cCallbacks & a_Callbacks):
m_Callbacks(a_Callbacks),
m_EnvelopeParser(*this)
{
Reset();
}
size_t cHTTPMessageParser::Parse(const char * a_Data, size_t a_Size)
{
// If parsing already finished or errorred, let the caller keep all the data:
if (m_IsFinished || m_HasHadError)
{
return 0;
}
// If still waiting for the status line, add to buffer and try parsing it:
auto inBufferSoFar = m_Buffer.size();
if (m_FirstLine.empty())
{
m_Buffer.append(a_Data, a_Size);
auto bytesConsumedFirstLine = ParseFirstLine();
ASSERT(bytesConsumedFirstLine <= inBufferSoFar + a_Size); // Haven't consumed more data than there is in the buffer
ASSERT(bytesConsumedFirstLine > inBufferSoFar); // Have consumed at least the previous buffer contents
if (m_FirstLine.empty())
{
// All data used, but not a complete status line yet.
return a_Size;
}
if (m_HasHadError)
{
return AString::npos;
}
// Status line completed, feed the rest of the buffer into the envelope parser:
auto bytesConsumedEnvelope = m_EnvelopeParser.Parse(m_Buffer.data(), m_Buffer.size());
if (bytesConsumedEnvelope == AString::npos)
{
m_HasHadError = true;
m_Callbacks.OnError("Failed to parse the envelope");
return AString::npos;
}
ASSERT(bytesConsumedEnvelope <= bytesConsumedFirstLine + a_Size); // Haven't consumed more data than there was in the buffer
m_Buffer.erase(0, bytesConsumedEnvelope);
if (!m_EnvelopeParser.IsInHeaders())
{
HeadersFinished();
// Process any data still left in the buffer as message body:
auto bytesConsumedBody = ParseBody(m_Buffer.data(), m_Buffer.size());
if (bytesConsumedBody == AString::npos)
{
// Error has already been reported by ParseBody, just bail out:
return AString::npos;
}
return bytesConsumedBody + bytesConsumedEnvelope + bytesConsumedFirstLine - inBufferSoFar;
}
return a_Size;
} // if (m_FirstLine.empty())
// If still parsing headers, send them to the envelope parser:
if (m_EnvelopeParser.IsInHeaders())
{
auto bytesConsumed = m_EnvelopeParser.Parse(a_Data, a_Size);
if (bytesConsumed == AString::npos)
{
m_HasHadError = true;
m_Callbacks.OnError("Failed to parse the envelope");
return AString::npos;
}
if (!m_EnvelopeParser.IsInHeaders())
{
HeadersFinished();
// Process any data still left as message body:
auto bytesConsumedBody = ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed);
if (bytesConsumedBody == AString::npos)
{
// Error has already been reported by ParseBody, just bail out:
return AString::npos;
}
}
return a_Size;
}
// Already parsing the body
return ParseBody(a_Data, a_Size);
}
void cHTTPMessageParser::Reset(void)
{
m_HasHadError = false;
m_IsFinished = false;
m_FirstLine.clear();
m_Buffer.clear();
m_EnvelopeParser.Reset();
m_TransferEncodingParser.reset();
m_TransferEncoding.clear();
m_ContentLength = 0;
}
size_t cHTTPMessageParser::ParseFirstLine(void)
{
auto idxLineEnd = m_Buffer.find("\r\n");
if (idxLineEnd == AString::npos)
{
// Not a complete line yet
return m_Buffer.size();
}
m_FirstLine = m_Buffer.substr(0, idxLineEnd);
m_Buffer.erase(0, idxLineEnd + 2);
m_Callbacks.OnFirstLine(m_FirstLine);
return idxLineEnd + 2;
}
size_t cHTTPMessageParser::ParseBody(const char * a_Data, size_t a_Size)
{
if (m_TransferEncodingParser == nullptr)
{
// We have no Transfer-encoding parser assigned. This should have happened when finishing the envelope
OnError("No transfer encoding parser");
return AString::npos;
}
// Parse the body using the transfer encoding parser:
// (Note that TE parser returns the number of bytes left, while we return the number of bytes consumed)
return a_Size - m_TransferEncodingParser->Parse(a_Data, a_Size);
}
void cHTTPMessageParser::HeadersFinished(void)
{
m_Callbacks.OnHeadersFinished();
m_TransferEncodingParser = cTransferEncodingParser::Create(*this, m_TransferEncoding, m_ContentLength);
if (m_TransferEncodingParser == nullptr)
{
OnError(Printf("Unknown transfer encoding: %s", m_TransferEncoding.c_str()));
return;
}
}
void cHTTPMessageParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
{
m_Callbacks.OnHeaderLine(a_Key, a_Value);
auto Key = StrToLower(a_Key);
if (Key == "content-length")
{
if (!StringToInteger(a_Value, m_ContentLength))
{
OnError(Printf("Invalid content length header value: \"%s\"", a_Value.c_str()));
}
return;
}
if (Key == "transfer-encoding")
{
m_TransferEncoding = a_Value;
return;
}
}
void cHTTPMessageParser::OnError(const AString & a_ErrorDescription)
{
m_HasHadError = true;
m_Callbacks.OnError(a_ErrorDescription);
}
void cHTTPMessageParser::OnBodyData(const void * a_Data, size_t a_Size)
{
m_Callbacks.OnBodyData(a_Data, a_Size);
}
void cHTTPMessageParser::OnBodyFinished(void)
{
m_IsFinished = true;
m_Callbacks.OnBodyFinished();
}

View File

@ -1,27 +1,26 @@
// HTTPResponseParser.h
// HTTPMessageParser.h
// Declares the cHTTPMessageParser class that parses HTTP messages (request or response) being pushed into the parser,
// and reports the individual parts via callbacks
// Declares the cHTTPResponseParser class representing the parser for incoming HTTP responses
#pragma once
#include "HTTPMessage.h"
#include "EnvelopeParser.h"
#include "TransferEncodingParser.h"
class cHTTPResponseParser:
public cHTTPMessage,
class cHTTPMessageParser:
protected cEnvelopeParser::cCallbacks,
protected cTransferEncodingParser::cCallbacks
{
typedef cHTTPMessage Super;
public:
class cCallbacks
{
@ -32,8 +31,8 @@ public:
/** Called when an error has occured while parsing. */
virtual void OnError(const AString & a_ErrorDescription) = 0;
/** Called when the status line is fully parsed. */
virtual void OnStatusLine(const AString & a_StatusLine) = 0;
/** Called when the first line (request / status) is fully parsed. */
virtual void OnFirstLine(const AString & a_FirstLine) = 0;
/** Called when a single header line is parsed. */
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0;
@ -48,7 +47,8 @@ public:
virtual void OnBodyFinished(void) = 0;
};
cHTTPResponseParser(cCallbacks & a_Callbacks);
/** Creates a new parser instance that will use the specified callbacks for reporting. */
cHTTPMessageParser(cCallbacks & a_Callbacks);
/** Parses the incoming data and calls the appropriate callbacks.
Returns the number of bytes consumed or AString::npos number for error. */
@ -61,6 +61,9 @@ public:
/** Returns true if the entire response has been already parsed. */
bool IsFinished(void) const { return m_IsFinished; }
/** Resets the parser to the initial state, so that a new request can be parsed. */
void Reset(void);
protected:
@ -70,14 +73,11 @@ protected:
/** Set to true if an error has been encountered by the parser. */
bool m_HasHadError;
/** True if the parser is still parsing the status or headers. */
bool m_IsInHeaders;
/** True if the response has been fully parsed. */
bool m_IsFinished;
/** The complete status line of the response. Empty if not parsed yet. */
AString m_StatusLine;
/** The complete first line of the response. Empty if not parsed yet. */
AString m_FirstLine;
/** Buffer for the incoming data until the status line is parsed. */
AString m_Buffer;
@ -88,11 +88,20 @@ protected:
/** The specific parser for the transfer encoding used by this response. */
cTransferEncodingParserPtr m_TransferEncodingParser;
/** The transfer encoding to be used by the parser.
Filled while parsing headers, used when headers are finished. */
AString m_TransferEncoding;
/** Parses the status line out of the m_Buffer.
Removes the status line from m_Buffer, if appropriate.
Returns true if the entire status line has been parsed. */
bool ParseStatusLine(void);
/** The content length, parsed from the headers, if available.
Unused for chunked encoding.
Filled while parsing headers, used when headers are finished. */
size_t m_ContentLength;
/** Parses the first line out of m_Buffer.
Removes the first line from m_Buffer, if appropriate.
Returns the number of bytes consumed out of m_Buffer, or AString::npos number for error. */
size_t ParseFirstLine(void);
/** Parses the message body.
Processes transfer encoding and calls the callbacks for body data.

View File

@ -1,196 +0,0 @@
// HTTPRequestParser.cpp
// Implements the cHTTPRequestParser class representing the parser for incoming HTTP requests
#include "Globals.h"
#include "HTTPRequestParser.h"
cHTTPRequestParser::cHTTPRequestParser(void) :
super(mkRequest),
m_EnvelopeParser(*this),
m_IsValid(true),
m_UserData(nullptr),
m_HasAuth(false),
m_AllowKeepAlive(false)
{
}
size_t cHTTPRequestParser::ParseHeaders(const char * a_Data, size_t a_Size)
{
if (!m_IsValid)
{
return AString::npos;
}
if (m_Method.empty())
{
// The first line hasn't been processed yet
size_t res = ParseRequestLine(a_Data, a_Size);
ASSERT((res == AString::npos) || (res <= a_Size));
if ((res == AString::npos) || (res == a_Size))
{
return res;
}
size_t res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res);
ASSERT((res2 == AString::npos) || (res2 <= a_Size - res));
if (res2 == AString::npos)
{
m_IsValid = false;
return res2;
}
return res2 + res;
}
if (m_EnvelopeParser.IsInHeaders())
{
size_t res = m_EnvelopeParser.Parse(a_Data, a_Size);
ASSERT((res == AString::npos) || (res <= a_Size));
if (res == AString::npos)
{
m_IsValid = false;
}
return res;
}
return 0;
}
AString cHTTPRequestParser::GetBareURL(void) const
{
size_t idxQM = m_URL.find('?');
if (idxQM != AString::npos)
{
return m_URL.substr(0, idxQM);
}
else
{
return m_URL;
}
}
size_t cHTTPRequestParser::ParseRequestLine(const char * a_Data, size_t a_Size)
{
auto inBufferSoFar = m_IncomingHeaderData.size();
m_IncomingHeaderData.append(a_Data, a_Size);
auto IdxEnd = m_IncomingHeaderData.size();
// Ignore the initial CRLFs (HTTP spec's "should")
size_t LineStart = 0;
while (
(LineStart < IdxEnd) &&
(
(m_IncomingHeaderData[LineStart] == '\r') ||
(m_IncomingHeaderData[LineStart] == '\n')
)
)
{
LineStart++;
}
if (LineStart >= IdxEnd)
{
m_IsValid = false;
return AString::npos;
}
int NumSpaces = 0;
size_t MethodEnd = 0;
size_t URLEnd = 0;
for (size_t i = LineStart; i < IdxEnd; i++)
{
switch (m_IncomingHeaderData[i])
{
case ' ':
{
switch (NumSpaces)
{
case 0:
{
MethodEnd = i;
break;
}
case 1:
{
URLEnd = i;
break;
}
default:
{
// Too many spaces in the request
m_IsValid = false;
return AString::npos;
}
}
NumSpaces += 1;
break;
}
case '\n':
{
if ((i == 0) || (m_IncomingHeaderData[i - 1] != '\r') || (NumSpaces != 2) || (i < URLEnd + 7))
{
// LF too early, without a CR, without two preceeding spaces or too soon after the second space
m_IsValid = false;
return AString::npos;
}
// Check that there's HTTP / version at the end
if (strncmp(m_IncomingHeaderData.c_str() + URLEnd + 1, "HTTP/1.", 7) != 0)
{
m_IsValid = false;
return AString::npos;
}
m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart);
m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1);
return i + 1 - inBufferSoFar;
}
} // switch (m_IncomingHeaderData[i])
} // for i - m_IncomingHeaderData[]
// CRLF hasn't been encountered yet, consider all data consumed
return a_Size;
}
void cHTTPRequestParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
{
if (
(NoCaseCompare(a_Key, "Authorization") == 0) &&
(strncmp(a_Value.c_str(), "Basic ", 6) == 0)
)
{
AString UserPass = Base64Decode(a_Value.substr(6));
size_t idxCol = UserPass.find(':');
if (idxCol != AString::npos)
{
m_AuthUsername = UserPass.substr(0, idxCol);
m_AuthPassword = UserPass.substr(idxCol + 1);
m_HasAuth = true;
}
}
if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0))
{
m_AllowKeepAlive = true;
}
AddHeader(a_Key, a_Value);
}

View File

@ -1,109 +0,0 @@
// HTTPRequestParser.h
// Declares the cHTTPRequestParser class representing the parser for incoming HTTP requests
#pragma once
#include "HTTPMessage.h"
#include "EnvelopeParser.h"
class cHTTPRequestParser :
public cHTTPMessage,
protected cEnvelopeParser::cCallbacks
{
typedef cHTTPMessage super;
public:
cHTTPRequestParser(void);
/** Parses the request line and then headers from the received data.
Returns the number of bytes consumed or AString::npos number for error.
Once it has fully parsed all the headers, doesn't consume any more data. */
size_t ParseHeaders(const char * a_Data, size_t a_Size);
/** Returns true if the request did contain a Content-Length header */
bool HasReceivedContentLength(void) const { return (m_ContentLength != AString::npos); }
/** Returns the method used in the request */
const AString & GetMethod(void) const { return m_Method; }
/** Returns the URL used in the request */
const AString & GetURL(void) const { return m_URL; }
/** Returns the URL used in the request, without any parameters */
AString GetBareURL(void) const;
/** Sets the UserData pointer that is stored within this request.
The request doesn't touch this data (doesn't delete it)! */
void SetUserData(void * a_UserData) { m_UserData = a_UserData; }
/** Retrieves the UserData pointer that has been stored within this request. */
void * GetUserData(void) const { return m_UserData; }
/** Returns true if more data is expected for the request headers */
bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); }
/** Returns true if the request did present auth data that was understood by the parser */
bool HasAuth(void) const { return m_HasAuth; }
/** Returns the username that the request presented. Only valid if HasAuth() is true */
const AString & GetAuthUsername(void) const { return m_AuthUsername; }
/** Returns the password that the request presented. Only valid if HasAuth() is true */
const AString & GetAuthPassword(void) const { return m_AuthPassword; }
bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; }
protected:
/** Parser for the envelope data */
cEnvelopeParser m_EnvelopeParser;
/** True if the data received so far is parsed successfully. When false, all further parsing is skipped */
bool m_IsValid;
/** Bufferred incoming data, while parsing for the request line */
AString m_IncomingHeaderData;
/** Method of the request (GET / PUT / POST / ...) */
AString m_Method;
/** Full URL of the request */
AString m_URL;
/** Data that the HTTPServer callbacks are allowed to store. */
void * m_UserData;
/** Set to true if the request contains auth data that was understood by the parser */
bool m_HasAuth;
/** The username used for auth */
AString m_AuthUsername;
/** The password used for auth */
AString m_AuthPassword;
/** Set to true if the request indicated that it supports keepalives.
If false, the server will close the connection once the request is finished */
bool m_AllowKeepAlive;
/** Parses the incoming data for the first line (RequestLine)
Returns the number of bytes consumed, or AString::npos for an error
*/
size_t ParseRequestLine(const char * a_Data, size_t a_Size);
// cEnvelopeParser::cCallbacks overrides:
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
} ;

View File

@ -1,191 +0,0 @@
// HTTPResponseParser.cpp
// Implements the cHTTPResponseParser class representing the parser for incoming HTTP responses
#include "Globals.h"
#include "HTTPResponseParser.h"
cHTTPResponseParser::cHTTPResponseParser(cHTTPResponseParser::cCallbacks & a_Callbacks):
Super(mkResponse),
m_Callbacks(a_Callbacks),
m_HasHadError(false),
m_IsInHeaders(true),
m_IsFinished(false),
m_EnvelopeParser(*this)
{
}
size_t cHTTPResponseParser::Parse(const char * a_Data, size_t a_Size)
{
// If parsing already finished or errorred, let the caller keep all the data:
if (m_IsFinished || m_HasHadError)
{
return 0;
}
// If still waiting for the status line, add to buffer and try parsing it:
auto inBufferSoFar = m_Buffer.size();
if (m_StatusLine.empty())
{
m_Buffer.append(a_Data, a_Size);
if (!ParseStatusLine())
{
// All data used, but not a complete status line yet.
return a_Size;
}
if (m_HasHadError)
{
return AString::npos;
}
// Status line completed, feed the rest of the buffer into the envelope parser:
auto bytesConsumed = m_EnvelopeParser.Parse(m_Buffer.data(), m_Buffer.size());
if (bytesConsumed == AString::npos)
{
m_HasHadError = true;
m_Callbacks.OnError("Failed to parse the envelope");
return AString::npos;
}
ASSERT(bytesConsumed < inBufferSoFar + a_Size);
m_Buffer.erase(0, bytesConsumed);
if (!m_Buffer.empty())
{
// Headers finished and there's still data left in the buffer, process it as message body:
HeadersFinished();
auto res = ParseBody(m_Buffer.data(), m_Buffer.size());
if (res == AString::npos)
{
return AString::npos;
}
return res + bytesConsumed - inBufferSoFar;
}
return a_Size;
} // if (m_StatusLine.empty())
// If still parsing headers, send them to the envelope parser:
if (m_IsInHeaders)
{
auto bytesConsumed = m_EnvelopeParser.Parse(a_Data, a_Size);
if (bytesConsumed == AString::npos)
{
m_HasHadError = true;
m_Callbacks.OnError("Failed to parse the envelope");
return AString::npos;
}
if (bytesConsumed < a_Size)
{
// Headers finished and there's still data left in the buffer, process it as message body:
HeadersFinished();
return bytesConsumed + ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed);
}
return a_Size;
}
// Already parsing the body
return ParseBody(a_Data, a_Size);
}
bool cHTTPResponseParser::ParseStatusLine(void)
{
auto idxLineEnd = m_Buffer.find("\r\n");
if (idxLineEnd == AString::npos)
{
// Not a complete line yet
return false;
}
m_StatusLine = m_Buffer.substr(0, idxLineEnd);
m_Buffer.erase(0, idxLineEnd + 2);
m_Callbacks.OnStatusLine(m_StatusLine);
return true;
}
size_t cHTTPResponseParser::ParseBody(const char * a_Data, size_t a_Size)
{
if (m_TransferEncodingParser == nullptr)
{
// We have no Transfer-encoding parser assigned. This should have happened when finishing the envelope
return AString::npos;
}
// Parse the body using the transfer encoding parser:
// (Note that TE parser returns the number of bytes left, while we return the number of bytes consumed)
return a_Size - m_TransferEncodingParser->Parse(a_Data, a_Size);
}
void cHTTPResponseParser::HeadersFinished(void)
{
m_IsInHeaders = false;
m_Callbacks.OnHeadersFinished();
auto transferEncoding = m_Headers.find("transfer-encoding");
if (transferEncoding == m_Headers.end())
{
m_TransferEncodingParser = cTransferEncodingParser::Create(*this, "identity", m_ContentLength);
}
else
{
m_TransferEncodingParser = cTransferEncodingParser::Create(*this, transferEncoding->second, m_ContentLength);
}
}
void cHTTPResponseParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
{
AddHeader(a_Key, a_Value);
m_Callbacks.OnHeaderLine(a_Key, a_Value);
}
void cHTTPResponseParser::OnError(const AString & a_ErrorDescription)
{
m_HasHadError = true;
m_Callbacks.OnError(a_ErrorDescription);
}
void cHTTPResponseParser::OnBodyData(const void * a_Data, size_t a_Size)
{
m_Callbacks.OnBodyData(a_Data, a_Size);
}
void cHTTPResponseParser::OnBodyFinished(void)
{
m_IsFinished = true;
m_Callbacks.OnBodyFinished();
}

View File

@ -5,7 +5,7 @@
#include "Globals.h"
#include "HTTPServer.h"
#include "HTTPRequestParser.h"
#include "HTTPMessageParser.h"
#include "HTTPServerConnection.h"
#include "HTTPFormParser.h"
#include "SslHTTPServerConnection.h"
@ -24,102 +24,6 @@
class cDebugCallbacks :
public cHTTPServer::cCallbacks,
protected cHTTPFormParser::cCallbacks
{
virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override
{
UNUSED(a_Connection);
if (cHTTPFormParser::HasFormData(a_Request))
{
a_Request.SetUserData(new cHTTPFormParser(a_Request, *this));
}
}
virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) override
{
UNUSED(a_Connection);
cHTTPFormParser * FormParser = reinterpret_cast<cHTTPFormParser *>(a_Request.GetUserData());
if (FormParser != nullptr)
{
FormParser->Parse(a_Data, a_Size);
}
}
virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override
{
cHTTPFormParser * FormParser = reinterpret_cast<cHTTPFormParser *>(a_Request.GetUserData());
if (FormParser != nullptr)
{
if (FormParser->Finish())
{
cHTTPResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send("<html><body><table border=1 cellspacing=0><tr><th>Name</th><th>Value</th></tr>\r\n");
for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr)
{
a_Connection.Send(Printf("<tr><td valign=\"top\"><pre>%s</pre></td><td valign=\"top\"><pre>%s</pre></td></tr>\r\n", itr->first.c_str(), itr->second.c_str()));
} // for itr - FormParser[]
a_Connection.Send("</table></body></html>");
return;
}
// Parsing failed:
cHTTPResponse Resp;
Resp.SetContentType("text/plain");
a_Connection.Send(Resp);
a_Connection.Send("Form parsing failed");
return;
}
// Test the auth failure and success:
if (a_Request.GetURL() == "/auth")
{
if (!a_Request.HasAuth() || (a_Request.GetAuthUsername() != "a") || (a_Request.GetAuthPassword() != "b"))
{
a_Connection.SendNeedAuth("Cuberite WebAdmin");
return;
}
}
cHTTPResponse Resp;
Resp.SetContentType("text/plain");
a_Connection.Send(Resp);
a_Connection.Send("Hello, world");
}
virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) override
{
// TODO
}
virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, size_t a_Size) override
{
// TODO
}
virtual void OnFileEnd(cHTTPFormParser & a_Parser) override
{
// TODO
}
};
static cDebugCallbacks g_DebugCallbacks;
////////////////////////////////////////////////////////////////////////////////
// cHTTPServerListenCallbacks:
@ -280,7 +184,7 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo
void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request)
void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
m_Callbacks->OnRequestBegun(a_Connection, a_Request);
}
@ -289,19 +193,18 @@ void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestP
void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size)
void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const void * a_Data, size_t a_Size)
{
m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size);
m_Callbacks->OnRequestBody(a_Connection, a_Request, reinterpret_cast<const char *>(a_Data), a_Size);
}
void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request)
void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
m_Callbacks->OnRequestFinished(a_Connection, a_Request);
a_Connection.AwaitNextRequest();
}

View File

@ -22,6 +22,7 @@
// fwd:
class cHTTPMessage;
class cHTTPRequestParser;
class cHTTPIncomingRequest;
class cHTTPResponse;
class cHTTPServerConnection;
@ -40,14 +41,14 @@ public:
/** Called when a new request arrives over a connection and all its headers have been parsed.
The request body needn't have arrived yet. */
virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0;
virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) = 0;
/** Called when another part of request body has arrived.
May be called multiple times for a single request. */
virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) = 0;
virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const char * a_Data, size_t a_Size) = 0;
/** Called when the request body has been fully received in previous calls to OnRequestBody() */
virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0;
virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) = 0;
} ;
cHTTPServer(void);
@ -85,14 +86,14 @@ protected:
cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort);
/** Called by cHTTPServerConnection when it finishes parsing the request header */
void NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request);
void NewRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);
/** Called by cHTTPConenction when it receives more data for the request body.
May be called multiple times for a single request. */
void RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size);
void RequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const void * a_Data, size_t a_Size);
/** Called by cHTTPServerConnection when it detects that the request has finished (all of its body has been received) */
void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request);
void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);
} ;

View File

@ -5,7 +5,8 @@
#include "Globals.h"
#include "HTTPServerConnection.h"
#include "HTTPRequestParser.h"
#include "HTTPMessage.h"
#include "HTTPMessageParser.h"
#include "HTTPServer.h"
@ -14,9 +15,8 @@
cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) :
m_HTTPServer(a_HTTPServer),
m_State(wcsRecvHeaders),
m_CurrentRequest(nullptr),
m_CurrentRequestBodyRemaining(0)
m_Parser(*this),
m_CurrentRequest(nullptr)
{
// LOGD("HTTP: New connection at %p", this);
}
@ -28,8 +28,7 @@ cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) :
cHTTPServerConnection::~cHTTPServerConnection()
{
// LOGD("HTTP: Connection deleting: %p", this);
delete m_CurrentRequest;
m_CurrentRequest = nullptr;
m_CurrentRequest.reset();
}
@ -41,7 +40,8 @@ void cHTTPServerConnection::SendStatusAndReason(int a_StatusCode, const AString
SendData(Printf("HTTP/1.1 %d %s\r\n", a_StatusCode, a_Response.c_str()));
SendData(Printf("Content-Length: %u\r\n\r\n", static_cast<unsigned>(a_Response.size())));
SendData(a_Response.data(), a_Response.size());
m_State = wcsRecvHeaders;
m_CurrentRequest.reset();
m_Parser.Reset();
}
@ -51,7 +51,8 @@ void cHTTPServerConnection::SendStatusAndReason(int a_StatusCode, const AString
void cHTTPServerConnection::SendNeedAuth(const AString & a_Realm)
{
SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str()));
m_State = wcsRecvHeaders;
m_CurrentRequest.reset();
m_Parser.Reset();
}
@ -60,10 +61,9 @@ void cHTTPServerConnection::SendNeedAuth(const AString & a_Realm)
void cHTTPServerConnection::Send(const cHTTPResponse & a_Response)
{
ASSERT(m_State == wcsRecvIdle);
ASSERT(m_CurrentRequest != nullptr);
AString toSend;
a_Response.AppendToData(toSend);
m_State = wcsSendingResp;
SendData(toSend);
}
@ -73,7 +73,7 @@ void cHTTPServerConnection::Send(const cHTTPResponse & a_Response)
void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size)
{
ASSERT(m_State == wcsSendingResp);
ASSERT(m_CurrentRequest != nullptr);
// We're sending in Chunked transfer encoding
SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size));
SendData(a_Data, a_Size);
@ -86,47 +86,10 @@ void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size)
void cHTTPServerConnection::FinishResponse(void)
{
ASSERT(m_State == wcsSendingResp);
ASSERT(m_CurrentRequest != nullptr);
SendData("0\r\n\r\n");
m_State = wcsRecvHeaders;
}
void cHTTPServerConnection::AwaitNextRequest(void)
{
switch (m_State)
{
case wcsRecvHeaders:
{
// Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth())
break;
}
case wcsRecvIdle:
{
// The client is waiting for a response, send an "Internal server error":
SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n");
m_State = wcsRecvHeaders;
break;
}
case wcsSendingResp:
{
// The response headers have been sent, we need to terminate the response body:
SendData("0\r\n\r\n");
m_State = wcsRecvHeaders;
break;
}
default:
{
ASSERT(!"Unhandled state recovery");
break;
}
}
m_CurrentRequest.reset();
m_Parser.Reset();
}
@ -160,86 +123,7 @@ void cHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
ASSERT(m_Link != nullptr);
switch (m_State)
{
case wcsRecvHeaders:
{
if (m_CurrentRequest == nullptr)
{
m_CurrentRequest = new cHTTPRequestParser;
}
size_t BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size);
if (BytesConsumed == AString::npos)
{
delete m_CurrentRequest;
m_CurrentRequest = nullptr;
m_State = wcsInvalid;
m_Link->Close();
m_Link.reset();
return;
}
if (m_CurrentRequest->IsInHeaders())
{
// The request headers are not yet complete
return;
}
// The request has finished parsing its headers successfully, notify of it:
m_State = wcsRecvBody;
m_HTTPServer.NewRequest(*this, *m_CurrentRequest);
m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength();
if (m_CurrentRequestBodyRemaining == AString::npos)
{
// The body length was not specified in the request, assume zero
m_CurrentRequestBodyRemaining = 0;
}
// Process the rest of the incoming data into the request body:
if (a_Size > BytesConsumed)
{
cHTTPServerConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed);
return;
}
else
{
cHTTPServerConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away
return;
}
}