1
0
Fork 0

Merge pull request #3031 from cuberite/RenameHttpClasses

Refactor HTTP parsing
This commit is contained in:
Mattes D 2016-03-01 16:58:43 +01:00
commit 7cc8f8dd22
41 changed files with 1974 additions and 903 deletions

4
.gitattributes vendored Normal file
View File

@ -0,0 +1,4 @@
# Set the *.data files to be checked out as binary files.
# Used for the HTTP test data files, they need to have the CRLF line endings
# even on Linux, because they're HTTP protocol dumps.
*.data binary

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)
@ -263,6 +266,7 @@ if (MSVC)
if (${SELF_TEST})
set_target_properties(
Network
HTTP
PROPERTIES FOLDER Lib
)
set_target_properties(
@ -274,6 +278,7 @@ if (MSVC)
creatable-exe
EchoServer
Google-exe
HTTPMessageParser_file-exe
LoadablePieces
NameLookup
PROPERTIES FOLDER Tests

View File

@ -36,7 +36,7 @@
#include "../StringCompression.h"
#include "../CommandOutput.h"
#include "../BuildInfo.h"
#include "../HTTPServer/UrlParser.h"
#include "../HTTP/UrlParser.h"

View File

@ -8,7 +8,7 @@ include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/polarssl/include
include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/libevent/include")
set(FOLDERS
OSSupport HTTPServer Items Blocks Protocol Generating PolarSSL++ Bindings
OSSupport HTTP Items Blocks Protocol Generating PolarSSL++ Bindings
WorldStorage Mobs Entities Simulator Simulator/IncrementalRedstoneSimulator
BlockEntities UI Noise
)

View File

@ -6,31 +6,35 @@ include_directories ("${PROJECT_SOURCE_DIR}/../")
SET (SRCS
EnvelopeParser.cpp
HTTPConnection.cpp
HTTPFormParser.cpp
HTTPMessage.cpp
HTTPMessageParser.cpp
HTTPServer.cpp
HTTPServerConnection.cpp
MultipartParser.cpp
NameValueParser.cpp
SslHTTPConnection.cpp
SslHTTPServerConnection.cpp
TransferEncodingParser.cpp
UrlParser.cpp
)
SET (HDRS
EnvelopeParser.h
HTTPConnection.h
HTTPFormParser.h
HTTPMessage.h
HTTPMessageParser.h
HTTPServer.h
HTTPServerConnection.h
MultipartParser.h
NameValueParser.h
SslHTTPConnection.h
SslHTTPServerConnection.h
TransferEncodingParser.h
UrlParser.h
)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set_source_files_properties(HTTPServer.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=global-constructors ")
set_source_files_properties(HTTPConnection.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum")
set_source_files_properties(HTTPServerConnection.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum")
set_source_files_properties(HTTPMessage.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=tautological-compare")
endif()

View File

@ -28,12 +28,12 @@ size_t cEnvelopeParser::Parse(const char * a_Data, size_t a_Size)
}
// Start searching 1 char from the end of the already received data, if available:
size_t SearchStart = m_IncomingData.size();
SearchStart = (SearchStart > 1) ? SearchStart - 1 : 0;
auto searchStart = m_IncomingData.size();
searchStart = (searchStart > 1) ? searchStart - 1 : 0;
m_IncomingData.append(a_Data, a_Size);
size_t idxCRLF = m_IncomingData.find("\r\n", SearchStart);
size_t idxCRLF = m_IncomingData.find("\r\n", searchStart);
if (idxCRLF == AString::npos)
{
// Not a complete line yet, all input consumed:

View File

@ -13,7 +13,7 @@
cHTTPFormParser::cHTTPFormParser(cHTTPRequest & 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 cHTTPRequest & a_Request)
bool cHTTPFormParser::HasFormData(const cHTTPIncomingRequest & a_Request)
{
const AString & ContentType = a_Request.GetContentType();
return (
@ -138,7 +138,7 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request)
void cHTTPFormParser::BeginMultipart(const cHTTPRequest & 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 cHTTPRequest;
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(cHTTPRequest & 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 cHTTPRequest & 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 cHTTPRequest & a_Request);
void BeginMultipart(const cHTTPIncomingRequest & a_Request);
/** Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) */
void ParseFormUrlEncoded(void);

159
src/HTTP/HTTPMessage.cpp Normal file
View File

@ -0,0 +1,159 @@
// HTTPMessage.cpp
// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes
#include "Globals.h"
#include "HTTPMessage.h"
// Disable MSVC warnings:
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4355) // 'this' : used in base member initializer list
#endif
////////////////////////////////////////////////////////////////////////////////
// cHTTPMessage:
cHTTPMessage::cHTTPMessage(eKind a_Kind) :
m_Kind(a_Kind),
m_ContentLength(AString::npos)
{
}
void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
{
auto Key = StrToLower(a_Key);
auto itr = m_Headers.find(Key);
if (itr == m_Headers.end())
{
m_Headers[Key] = a_Value;
}
else
{
// The header-field key is specified multiple times, combine into comma-separated list (RFC 2616 @ 4.2)
itr->second.append(", ");
itr->second.append(a_Value);
}
// Special processing for well-known headers:
if (Key == "content-type")
{
m_ContentType = m_Headers[Key];
}
else if (Key == "content-length")
{
if (!StringToInteger(m_Headers[Key], m_ContentLength))
{
m_ContentLength = 0;
}
}
}
////////////////////////////////////////////////////////////////////////////////
// cHTTPOutgoingResponse:
cHTTPOutgoingResponse::cHTTPOutgoingResponse(void) :
super(mkResponse)
{
}
void cHTTPOutgoingResponse::AppendToData(AString & a_DataStream) const
{
a_DataStream.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: ");
a_DataStream.append(m_ContentType);
a_DataStream.append("\r\n");
for (auto itr = m_Headers.cbegin(), end = m_Headers.cend(); itr != end; ++itr)
{
if ((itr->first == "Content-Type") || (itr->first == "Content-Length"))
{
continue;
}
a_DataStream.append(itr->first);
a_DataStream.append(": ");
a_DataStream.append(itr->second);
a_DataStream.append("\r\n");
} // for itr - m_Headers[]
a_DataStream.append("\r\n");
}
////////////////////////////////////////////////////////////////////////////////
// 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; }
@ -49,7 +49,8 @@ protected:
eKind m_Kind;
cNameValueMap m_Headers;
/** Map of headers, with their keys lowercased. */
AStringMap m_Headers;
/** Type of the content; parsed by AddHeader(), set directly by SetContentLength() */
AString m_ContentType;
@ -64,22 +65,42 @@ protected:
class cHTTPRequest :
public cHTTPMessage,
protected cEnvelopeParser::cCallbacks
/** Stores outgoing response headers and serializes them to an HTTP data stream. */
class cHTTPOutgoingResponse :
public cHTTPMessage
{
typedef cHTTPMessage super;
public:
cHTTPRequest(void);
cHTTPOutgoingResponse(void);
/** Parses the request line and then headers from the received data.
Returns the number of bytes consumed or AString::npos number for error
*/
size_t ParseHeaders(const char * a_Data, size_t a_Size);
/** Appends the response to the specified datastream - response line and headers.
The body will be sent later directly through cConnection::Send() */
void AppendToData(AString & a_DataStream) const;
} ;
/** Returns true if the request did contain a Content-Length header */
bool HasReceivedContentLength(void) const { return (m_ContentLength != AString::npos); }
/** 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; }
@ -87,20 +108,10 @@ public:
/** 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;
/** Returns the path part of the URL. */
AString GetURLPath(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 */
/** 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 */
@ -111,15 +122,17 @@ public:
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:
/** 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;
@ -127,9 +140,6 @@ protected:
/** 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;
@ -143,34 +153,6 @@ protected:
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;
} ;
class cHTTPResponse :
public cHTTPMessage
{
typedef cHTTPMessage super;
public:
cHTTPResponse(void);
/** Appends the response to the specified datastream - response line and headers.
The body will be sent later directly through cConnection::Send()
*/
void AppendToData(AString & a_DataStream) const;
} ;
/** 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

@ -0,0 +1,125 @@
// 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
#pragma once
#include "EnvelopeParser.h"
#include "TransferEncodingParser.h"
class cHTTPMessageParser:
protected cEnvelopeParser::cCallbacks,
protected cTransferEncodingParser::cCallbacks
{
public:
class cCallbacks
{
public:
// Force a virtual destructor in descendants:
virtual ~cCallbacks() {}
/** Called when an error has occured while parsing. */
virtual void OnError(const AString & a_ErrorDescription) = 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;
/** Called when all the headers have been parsed. */
virtual void OnHeadersFinished(void) = 0;
/** Called for each chunk of the incoming body data. */
virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0;
/** Called when the entire body has been reported by OnBodyData(). */
virtual void OnBodyFinished(void) = 0;
};
/** 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. */
size_t Parse(const char * a_Data, size_t a_Size);
/** Called when the server indicates no more data will be sent (HTTP 1.0 socket closed).
Finishes all parsing and calls apropriate callbacks (error if incomplete response). */
void Finish(void);
/** 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:
/** The callbacks used for reporting. */
cCallbacks & m_Callbacks;
/** Set to true if an error has been encountered by the parser. */
bool m_HasHadError;
/** True if the response has been fully parsed. */
bool m_IsFinished;
/** 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;
/** Parser for the envelope data (headers) */
cEnvelopeParser m_EnvelopeParser;
/** 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;
/** 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.
Returns the number of bytes consumed or AString::npos number for error. */
size_t ParseBody(const char * a_Data, size_t a_Size);
/** Called internally when the headers-parsing has just finished. */
void HeadersFinished(void);
// cEnvelopeParser::cCallbacks overrides:
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
// cTransferEncodingParser::cCallbacks overrides:
virtual void OnError(const AString & a_ErrorDescription) override;
virtual void OnBodyData(const void * a_Data, size_t a_Size) override;
virtual void OnBodyFinished(void) override;
};

View File

@ -5,10 +5,10 @@
#include "Globals.h"
#include "HTTPServer.h"
#include "HTTPMessage.h"
#include "HTTPConnection.h"
#include "HTTPMessageParser.h"
#include "HTTPServerConnection.h"
#include "HTTPFormParser.h"
#include "SslHTTPConnection.h"
#include "SslHTTPServerConnection.h"
@ -24,102 +24,6 @@
class cDebugCallbacks :
public cHTTPServer::cCallbacks,
protected cHTTPFormParser::cCallbacks
{
virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override
{
UNUSED(a_Connection);
if (cHTTPFormParser::HasFormData(a_Request))
{
a_Request.SetUserData(new cHTTPFormParser(a_Request, *this));
}
}
virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & 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(cHTTPConnection & a_Connection, cHTTPRequest & 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:
@ -268,11 +172,11 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo
if (m_Cert.get() != nullptr)
{
return std::make_shared<cSslHTTPConnection>(*this, m_Cert, m_CertPrivKey);
return std::make_shared<cSslHTTPServerConnection>(*this, m_Cert, m_CertPrivKey);
}
else
{
return std::make_shared<cHTTPConnection>(*this);
return std::make_shared<cHTTPServerConnection>(*this);
}
}
@ -280,7 +184,7 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo
void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & 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(cHTTPConnection & a_Connection, cHTTPRequest & a_Re
void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & 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(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
m_Callbacks->OnRequestFinished(a_Connection, a_Request);
a_Connection.AwaitNextRequest();
}

View File

@ -21,11 +21,9 @@
// fwd:
class cHTTPMessage;
class cHTTPRequest;
class cHTTPResponse;
class cHTTPConnection;
typedef std::vector<cHTTPConnection *> cHTTPConnections;
class cHTTPRequestParser;
class cHTTPIncomingRequest;
class cHTTPServerConnection;
@ -42,14 +40,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(cHTTPConnection & a_Connection, cHTTPRequest & 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(cHTTPConnection & a_Connection, cHTTPRequest & 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(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) = 0;
} ;
cHTTPServer(void);
@ -65,8 +63,8 @@ public:
void Stop(void);
protected:
friend class cHTTPConnection;
friend class cSslHTTPConnection;
friend class cHTTPServerConnection;
friend class cSslHTTPServerConnection;
friend class cHTTPServerListenCallbacks;
/** The cNetwork API handle for the listening socket. */
@ -86,15 +84,15 @@ protected:
Returns the connection instance to be used as the cTCPLink callbacks. */
cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort);
/** Called by cHTTPConnection when it finishes parsing the request header */
void NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
/** Called by cHTTPServerConnection when it finishes parsing the request header */
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(cHTTPConnection & a_Connection, cHTTPRequest & 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 cHTTPConnection when it detects that the request has finished (all of its body has been received) */
void RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
/** Called by cHTTPServerConnection when it detects that the request has finished (all of its body has been received) */
void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);
} ;

View File

@ -0,0 +1,240 @@
// HTTPConnection.cpp
// Implements the cHTTPServerConnection class representing a single persistent connection in the HTTP server.
#include "Globals.h"
#include "HTTPServerConnection.h"
#include "HTTPMessage.h"
#include "HTTPMessageParser.h"
#include "HTTPServer.h"
cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) :
m_HTTPServer(a_HTTPServer),
m_Parser(*this),
m_CurrentRequest(nullptr)
{
// LOGD("HTTP: New connection at %p", this);
}
cHTTPServerConnection::~cHTTPServerConnection()
{
// LOGD("HTTP: Connection deleting: %p", this);
m_CurrentRequest.reset();
}
void cHTTPServerConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response)
{
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_CurrentRequest.reset();
m_Parser.Reset();
}
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_CurrentRequest.reset();
m_Parser.Reset();
}
void cHTTPServerConnection::Send(const cHTTPOutgoingResponse & a_Response)
{
ASSERT(m_CurrentRequest != nullptr);
AString toSend;
a_Response.AppendToData(toSend);
SendData(toSend);
}
void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size)
{
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);
SendData("\r\n");
}
void cHTTPServerConnection::FinishResponse(void)
{
ASSERT(m_CurrentRequest != nullptr);
SendData("0\r\n\r\n");
m_CurrentRequest.reset();
m_Parser.Reset();
}
void cHTTPServerConnection::Terminate(void)
{
if (m_CurrentRequest != nullptr)
{
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
}
m_Link.reset();
}
void cHTTPServerConnection::OnLinkCreated(cTCPLinkPtr a_Link)
{
ASSERT(m_Link == nullptr);
m_Link = a_Link;
}
void cHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
ASSERT(m_Link != nullptr);
m_Parser.Parse(a_Data, a_Size);
}
void cHTTPServerConnection::OnRemoteClosed(void)
{
if (m_CurrentRequest != nullptr)
{
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
}
m_Link.reset();
}
void cHTTPServerConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
{
OnRemoteClosed();
}
void cHTTPServerConnection::OnError(const AString & a_ErrorDescription)
{
OnRemoteClosed();
}
void cHTTPServerConnection::OnFirstLine(const AString & a_FirstLine)
{
// Create a new request object for this request:
auto split = StringSplit(a_FirstLine, " ");
if (split.size() < 2)
{
// Invalid request line. We need at least the Method and URL
OnRemoteClosed();
return;
}
m_CurrentRequest.reset(new cHTTPIncomingRequest(split[0], split[1]));
}
void cHTTPServerConnection::OnHeaderLine(const AString & a_Key, const AString & a_Value)
{
if (m_CurrentRequest == nullptr)
{
return;
}
m_CurrentRequest->AddHeader(a_Key, a_Value);
}
void cHTTPServerConnection::OnHeadersFinished(void)
{
if (m_CurrentRequest == nullptr)
{
return;
}
m_HTTPServer.NewRequest(*this, *m_CurrentRequest);
}
void cHTTPServerConnection::OnBodyData(const void * a_Data, size_t a_Size)
{
if (m_CurrentRequest == nullptr)
{
return;
}
m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, a_Size);
}
void cHTTPServerConnection::OnBodyFinished(void)
{
// Process the request and reset:
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
m_CurrentRequest.reset();
m_Parser.Reset();
}
void cHTTPServerConnection::SendData(const void * a_Data, size_t a_Size)
{
m_Link->Send(a_Data, a_Size);
}

View File

@ -10,6 +10,7 @@
#pragma once
#include "../OSSupport/Network.h"
#include "HTTPMessageParser.h"
@ -17,39 +18,34 @@
// fwd:
class cHTTPServer;
class cHTTPResponse;
class cHTTPRequest;
class cHTTPOutgoingResponse;
class cHTTPIncomingRequest;
class cHTTPConnection :
public cTCPLink::cCallbacks
class cHTTPServerConnection :
public cTCPLink::cCallbacks,
public cHTTPMessageParser::cCallbacks
{
public:
/** Creates a new instance, connected to the specified HTTP server instance */
cHTTPServerConnection(cHTTPServer & a_HTTPServer);
enum eState
{
wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if nullptr)
wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid)
wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == nullptr)
wcsSendingResp, ///< Sending response body (m_CurrentRequest == nullptr)
wcsInvalid, ///< The request was malformed, the connection is closing
} ;
cHTTPConnection(cHTTPServer & a_HTTPServer);
virtual ~cHTTPConnection();
// Force a virtual destructor in all descendants
virtual ~cHTTPServerConnection();
/** Sends HTTP status code together with a_Reason (used for HTTP errors).
Sends the a_Reason as the body as well, so that browsers display it. */
Sends the a_Reason as the body as well, so that browsers display it.
Clears the current request (since it's finished by this call). */
void SendStatusAndReason(int a_StatusCode, const AString & a_Reason);
/** Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm */
/** Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm.
Clears the current request (since it's finished by this call). */
void SendNeedAuth(const AString & a_Realm);
/** Sends the headers contained in a_Response */
void Send(const cHTTPResponse & a_Response);
void Send(const cHTTPOutgoingResponse & a_Response);
/** Sends the data as the response (may be called multiple times) */
void Send(const void * a_Data, size_t a_Size);
@ -57,13 +53,10 @@ public:
/** Sends the data as the response (may be called multiple times) */
void Send(const AString & a_Data) { Send(a_Data.data(), a_Data.size()); }
/** Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive) */
/** Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive).
Clears the current request (since it's finished by this call). */
void FinishResponse(void);
/** Resets the internal connection state for a new request.
Depending on the state, this will send an "InternalServerError" status or a "ResponseEnd" */
void AwaitNextRequest(void);
/** Terminates the connection; finishes any request being currently processed */
void Terminate(void);
@ -73,19 +66,12 @@ protected:
/** The parent webserver that is to be notified of events on this connection */
cHTTPServer & m_HTTPServer;
/** All the incoming data until the entire request header is parsed */
AString m_IncomingHeaderData;
/** Status in which the request currently is */
eState m_State;
/** The parser responsible for reading the requests. */
cHTTPMessageParser m_Parser;
/** The request being currently received
Valid only between having parsed the headers and finishing receiving the body. */
cHTTPRequest * m_CurrentRequest;
/** Number of bytes that remain to read for the complete body of the message to be received.
Valid only in wcsRecvBody */
size_t m_CurrentRequestBodyRemaining;
std::unique_ptr<cHTTPIncomingRequest> m_CurrentRequest;
/** The network link attached to this connection. */
cTCPLinkPtr m_Link;
@ -104,6 +90,14 @@ protected:
/** An error has occurred on the socket. */
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
// cHTTPMessageParser::cCallbacks overrides:
virtual void OnError(const AString & a_ErrorDescription) override;
virtual void OnFirstLine(const AString & a_FirstLine) override;
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
virtual void OnHeadersFinished(void) override;
virtual void OnBodyData(const void * a_Data, size_t a_Size) override;
virtual void OnBodyFinished(void) override;
// Overridable:
/** Called to send raw data over the link. Descendants may provide data transformations (SSL etc.) */
virtual void SendData(const void * a_Data, size_t a_Size);
@ -116,7 +110,7 @@ protected:
}
} ;
typedef std::vector<cHTTPConnection *> cHTTPConnections;
typedef std::vector<cHTTPServerConnection *> cHTTPServerConnections;

View File

@ -1,17 +1,17 @@
// SslHTTPConnection.cpp
// Implements the cSslHTTPConnection class representing a HTTP connection made over a SSL link
// Implements the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link
#include "Globals.h"
#include "SslHTTPConnection.h"
#include "SslHTTPServerConnection.h"
#include "HTTPServer.h"
cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) :
cSslHTTPServerConnection::cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) :
super(a_HTTPServer),
m_Ssl(64000),
m_Cert(a_Cert),
@ -25,7 +25,7 @@ cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509Ce
cSslHTTPConnection::~cSslHTTPConnection()
cSslHTTPServerConnection::~cSslHTTPServerConnection()
{
m_Ssl.NotifyClose();
}
@ -34,7 +34,7 @@ cSslHTTPConnection::~cSslHTTPConnection()
void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
void cSslHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
// Process the received data:
const char * Data = a_Data;
@ -77,7 +77,7 @@ void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
void cSslHTTPConnection::SendData(const void * a_Data, size_t a_Size)
void cSslHTTPServerConnection::SendData(const void * a_Data, size_t a_Size)
{
const char * OutgoingData = reinterpret_cast<const char *>(a_Data);
size_t pos = 0;

View File

@ -1,7 +1,7 @@
// SslHTTPConnection.h
// SslHTTPServerConnection.h
// Declared the cSslHTTPConnection class representing a HTTP connection made over a SSL link
// Declares the cSslHTTPServerConnection class representing a HTTP connection made over an SSL link
@ -9,24 +9,24 @@
#pragma once
#include "HTTPConnection.h"
#include "HTTPServerConnection.h"
#include "PolarSSL++/BufferedSslContext.h"
class cSslHTTPConnection :
public cHTTPConnection
class cSslHTTPServerConnection :
public cHTTPServerConnection
{
typedef cHTTPConnection super;
typedef cHTTPServerConnection super;
public:
/** Creates a new connection on the specified server.
Sends the specified cert as the server certificate, uses the private key for decryption. */
cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey);
cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey);
~cSslHTTPConnection();
~cSslHTTPServerConnection();
protected:
cBufferedSslContext m_Ssl;

View File

@ -0,0 +1,394 @@
// TransferEncodingParser.cpp
// Implements the cTransferEncodingParser class and its descendants representing the parsers for the various transfer encodings (chunked etc.)
#include "Globals.h"
#include "TransferEncodingParser.h"
#include "EnvelopeParser.h"
////////////////////////////////////////////////////////////////////////////////
// cChunkedTEParser:
class cChunkedTEParser:
public cTransferEncodingParser,
public cEnvelopeParser::cCallbacks
{
typedef cTransferEncodingParser Super;
public:
cChunkedTEParser(Super::cCallbacks & a_Callbacks):
Super(a_Callbacks),
m_State(psChunkLength),
m_ChunkDataLengthLeft(0),
m_TrailerParser(*this)
{
}
protected:
enum eState
{
psChunkLength, ///< Parsing the chunk length hex number
psChunkLengthTrailer, ///< Any trailer (chunk extension) specified after the chunk length
psChunkLengthLF, ///< The LF character after the CR character terminating the chunk length
psChunkData, ///< Relaying chunk data
psChunkDataCR, ///< Skipping the extra CR character after chunk data
psChunkDataLF, ///< Skipping the extra LF character after chunk data
psTrailer, ///< Received an empty chunk, parsing the trailer (through the envelope parser)
psFinished, ///< The parser has finished parsing, either successfully or with an error
};
/** The current state of the parser (parsing chunk length / chunk data). */
eState m_State;
/** Number of bytes that still belong to the chunk currently being parsed.
When in psChunkLength, the value is the currently parsed length digits. */
size_t m_ChunkDataLengthLeft;
/** The parser used for the last (empty) chunk's trailer data */
cEnvelopeParser m_TrailerParser;
/** Calls the OnError callback and sets parser state to finished. */
void Error(const AString & a_ErrorMsg)
{
m_State = psFinished;
m_Callbacks.OnError(a_ErrorMsg);
}
/** Parses the incoming data, the current state is psChunkLength.
Stops parsing when either the chunk length has been read, or there is no more data in the input.
Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
size_t ParseChunkLength(const char * a_Data, size_t a_Size)
{
// Expected input: <hexnumber>[;<trailer>]<CR><LF>
// Only the hexnumber is parsed into m_ChunkDataLengthLeft, the rest is postponed into psChunkLengthTrailer or psChunkLengthLF
for (size_t i = 0; i < a_Size; i++)
{
switch (a_Data[i])
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast<decltype(m_ChunkDataLengthLeft)>(a_Data[i] - '0');
break;
}
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
{
m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast<decltype(m_ChunkDataLengthLeft)>(a_Data[i] - 'a' + 10);
break;
}
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
{
m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast<decltype(m_ChunkDataLengthLeft)>(a_Data[i] - 'A' + 10);
break;
}
case '\r':
{
m_State = psChunkLengthLF;
return i + 1;
}
case ';':
{
m_State = psChunkLengthTrailer;
return i + 1;
}
default:
{
Error(Printf("Invalid character in chunk length line: 0x%x", a_Data[i]));
return AString::npos;
}
} // switch (a_Data[i])
} // for i - a_Data[]
return a_Size;
}
/** Parses the incoming data, the current state is psChunkLengthTrailer.
Stops parsing when either the chunk length trailer has been read, or there is no more data in the input.
Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
size_t ParseChunkLengthTrailer(const char * a_Data, size_t a_Size)
{
// Expected input: <trailer><CR><LF>
// The LF itself is not parsed, it is instead postponed into psChunkLengthLF
for (size_t i = 0; i < a_Size; i++)
{
switch (a_Data[i])
{
case '\r':
{
m_State = psChunkLengthLF;
return i;
}
default:
{
if (a_Data[i] < 32)
{
// Only printable characters are allowed in the trailer
Error(Printf("Invalid character in chunk length line: 0x%x", a_Data[i]));
return AString::npos;
}
}
} // switch (a_Data[i])
} // for i - a_Data[]
return a_Size;
}
/** Parses the incoming data, the current state is psChunkLengthLF.
Only the LF character is expected, if found, moves to psChunkData, otherwise issues an error.
If the chunk length that just finished reading is equal to 0, signals the end of stream (via psTrailer).
Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
size_t ParseChunkLengthLF(const char * a_Data, size_t a_Size)
{
// Expected input: <LF>
if (a_Size == 0)
{
return 0;
}
if (a_Data[0] == '\n')
{
if (m_ChunkDataLengthLeft == 0)
{
m_State = psTrailer;
}
else
{
m_State = psChunkData;
}
return 1;
}
Error(Printf("Invalid character past chunk length's CR: 0x%x", a_Data[0]));
return AString::npos;
}
/** Consumes as much chunk data from the input as possible.
Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error() handler). */
size_t ParseChunkData(const char * a_Data, size_t a_Size)
{
ASSERT(m_ChunkDataLengthLeft > 0);
auto bytes = std::min(a_Size, m_ChunkDataLengthLeft);
m_ChunkDataLengthLeft -= bytes;
m_Callbacks.OnBodyData(a_Data, bytes);
if (m_ChunkDataLengthLeft == 0)
{
m_State = psChunkDataCR;
}
return bytes;
}
/** Parses the incoming data, the current state is psChunkDataCR.
Only the CR character is expected, if found, moves to psChunkDataLF, otherwise issues an error.
Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
size_t ParseChunkDataCR(const char * a_Data, size_t a_Size)
{
// Expected input: <CR>
if (a_Size == 0)
{
return 0;
}
if (a_Data[0] == '\r')
{
m_State = psChunkDataLF;
return 1;
}
Error(Printf("Invalid character past chunk data: 0x%x", a_Data[0]));
return AString::npos;
}
/** Parses the incoming data, the current state is psChunkDataCR.
Only the CR character is expected, if found, moves to psChunkDataLF, otherwise issues an error.
Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
size_t ParseChunkDataLF(const char * a_Data, size_t a_Size)
{
// Expected input: <LF>
if (a_Size == 0)
{
return 0;
}
if (a_Data[0] == '\n')
{
m_State = psChunkLength;
return 1;
}
Error(Printf("Invalid character past chunk data's CR: 0x%x", a_Data[0]));
return AString::npos;
}
/** Parses the incoming data, the current state is psChunkDataCR.
The trailer is normally a set of "Header: Value" lines, terminated by an empty line. Use the m_TrailerParser for that.
Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */
size_t ParseTrailer(const char * a_Data, size_t a_Size)
{
auto res = m_TrailerParser.Parse(a_Data, a_Size);
if (res == AString::npos)
{
Error("Error while parsing the trailer");
}
if ((res < a_Size) || !m_TrailerParser.IsInHeaders())
{
m_Callbacks.OnBodyFinished();
m_State = psFinished;
}
return res;
}
// cTransferEncodingParser overrides:
virtual size_t Parse(const char * a_Data, size_t a_Size) override
{
while ((a_Size > 0) && (m_State != psFinished))
{
size_t consumed = 0;
switch (m_State)
{
case psChunkLength: consumed = ParseChunkLength (a_Data, a_Size); break;
case psChunkLengthTrailer: consumed = ParseChunkLengthTrailer(a_Data, a_Size); break;
case psChunkLengthLF: consumed = ParseChunkLengthLF (a_Data, a_Size); break;
case psChunkData: consumed = ParseChunkData (a_Data, a_Size); break;
case psChunkDataCR: consumed = ParseChunkDataCR (a_Data, a_Size); break;
case psChunkDataLF: consumed = ParseChunkDataLF (a_Data, a_Size); break;
case psTrailer: consumed = ParseTrailer (a_Data, a_Size); break;
case psFinished: consumed = 0; break; // Not supposed to happen, but Clang complains without it
}
if (consumed == AString::npos)
{
return AString::npos;
}
a_Data += consumed;
a_Size -= consumed;
}
return a_Size;
}
virtual void Finish(void) override
{
if (m_State != psFinished)
{
Error(Printf("ChunkedTransferEncoding: Finish signal received before the data stream ended (state: %d)", m_State));
}
m_State = psFinished;
}
// cEnvelopeParser::cCallbacks overrides:
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override
{
// Ignored
}
};
////////////////////////////////////////////////////////////////////////////////
// cIdentityTEParser:
class cIdentityTEParser:
public cTransferEncodingParser
{
typedef cTransferEncodingParser Super;
public:
cIdentityTEParser(cCallbacks & a_Callbacks, size_t a_ContentLength):
Super(a_Callbacks),
m_BytesLeft(a_ContentLength)
{
}
protected:
/** How many bytes of content are left before the message ends. */
size_t m_BytesLeft;
// cTransferEncodingParser overrides:
virtual size_t Parse(const char * a_Data, size_t a_Size) override
{
auto size = std::min(a_Size, m_BytesLeft);
if (size > 0)
{
m_Callbacks.OnBodyData(a_Data, size);
}
m_BytesLeft -= size;
if (m_BytesLeft == 0)
{
m_Callbacks.OnBodyFinished();
}
return a_Size - size;
}
virtual void Finish(void) override
{
if (m_BytesLeft > 0)
{
m_Callbacks.OnError("IdentityTransferEncoding: body was truncated");
}
else
{
// BodyFinished has already been called, just bail out
}
}
};
////////////////////////////////////////////////////////////////////////////////
// cTransferEncodingParser:
cTransferEncodingParserPtr cTransferEncodingParser::Create(
cCallbacks & a_Callbacks,
const AString & a_TransferEncoding,
size_t a_ContentLength
)
{
if (a_TransferEncoding == "chunked")
{
return std::make_shared<cChunkedTEParser>(a_Callbacks);
}
if (a_TransferEncoding == "identity")
{
return std::make_shared<cIdentityTEParser>(a_Callbacks, a_ContentLength);
}
if (a_TransferEncoding.empty())
{
return std::make_shared<cIdentityTEParser>(a_Callbacks, a_ContentLength);
}
return nullptr;
}

View File

@ -0,0 +1,76 @@
// TransferEncodingParser.h
// Declares the cTransferEncodingParser class representing the parser for the various transfer encodings (chunked etc.)
#pragma once
// fwd:
class cTransferEncodingParser;
typedef SharedPtr<cTransferEncodingParser> cTransferEncodingParserPtr;
/** Used as both the interface that all the parsers share and the (static) factory creating such parsers. */
class cTransferEncodingParser
{
public:
class cCallbacks
{
public:
// Force a virtual destructor in descendants
virtual ~cCallbacks() {}
/** Called when an error has occured while parsing. */
virtual void OnError(const AString & a_ErrorDescription) = 0;
/** Called for each chunk of the incoming body data. */
virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0;
/** Called when the entire body has been reported by OnBodyData(). */
virtual void OnBodyFinished(void) = 0;
};
// Force a virtual destructor in all descendants
virtual ~cTransferEncodingParser() {}
/** Parses the incoming data and calls the appropriate callbacks.
Returns the number of bytes from the end of a_Data that is already not part of this message (if the parser can detect it).
Returns AString::npos on an error. */
virtual size_t Parse(const char * a_Data, size_t a_Size) = 0;
/** To be called when the stream is terminated from the source (connection closed).
Flushes any buffers and calls appropriate callbacks. */
virtual void Finish(void) = 0;
/** Creates a new parser for the specified encoding.
If the encoding is not known, returns a nullptr.
a_ContentLength is the length of the content, received in a Content-Length header.
It is used for the Identity encoding, it is ignored for the Chunked encoding. */
static cTransferEncodingParserPtr Create(
cCallbacks & a_Callbacks,
const AString & a_TransferEncoding,
size_t a_ContentLength
);
protected:
/** The callbacks used to report progress. */
cCallbacks & m_Callbacks;
cTransferEncodingParser(cCallbacks & a_Callbacks):
m_Callbacks(a_Callbacks)
{
}
};

View File

@ -1,278 +0,0 @@
// HTTPConnection.cpp
// Implements the cHTTPConnection class representing a single persistent connection in the HTTP server.
#include "Globals.h"
#include "HTTPConnection.h"
#include "HTTPMessage.h"
#include "HTTPServer.h"
cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) :
m_HTTPServer(a_HTTPServer),
m_State(wcsRecvHeaders),
m_CurrentRequest(nullptr),
m_CurrentRequestBodyRemaining(0)
{
// LOGD("HTTP: New connection at %p", this);
}
cHTTPConnection::~cHTTPConnection()
{
// LOGD("HTTP: Connection deleting: %p", this);
delete m_CurrentRequest;
m_CurrentRequest = nullptr;
}
void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response)
{
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;
}
void cHTTPConnection::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;
}
void cHTTPConnection::Send(const cHTTPResponse & a_Response)
{
ASSERT(m_State == wcsRecvIdle);
AString toSend;
a_Response.AppendToData(toSend);
m_State = wcsSendingResp;
SendData(toSend);
}
void cHTTPConnection::Send(const void * a_Data, size_t a_Size)
{
ASSERT(m_State == wcsSendingResp);
// We're sending in Chunked transfer encoding
SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size));
SendData(a_Data, a_Size);
SendData("\r\n");
}
void cHTTPConnection::FinishResponse(void)
{
ASSERT(m_State == wcsSendingResp);
SendData("0\r\n\r\n");
m_State = wcsRecvHeaders;
}
void cHTTPConnection::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;
}
}
}
void cHTTPConnection::Terminate(void)
{
if (m_CurrentRequest != nullptr)
{
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
}
m_Link.reset();
}
void cHTTPConnection::OnLinkCreated(cTCPLinkPtr a_Link)
{
ASSERT(m_Link == nullptr);
m_Link = a_Link;
}
void cHTTPConnection::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 cHTTPRequest;
}
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)
{
cHTTPConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed);
return;
}
else
{
cHTTPConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away
return;
}
}
case wcsRecvBody:
{
ASSERT(m_CurrentRequest != nullptr);
if (m_CurrentRequestBodyRemaining > 0)
{
size_t BytesToConsume = std::min(m_CurrentRequestBodyRemaining, static_cast<size_t>(a_Size));
m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, BytesToConsume);
m_CurrentRequestBodyRemaining -= BytesToConsume;
}
if (m_CurrentRequestBodyRemaining == 0)
{
m_State = wcsRecvIdle;
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
if (!m_CurrentRequest->DoesAllowKeepAlive())
{
m_State = wcsInvalid;
m_Link->Close();
m_Link.reset();
return;
}
delete m_CurrentRequest;
m_CurrentRequest = nullptr;
}
break;
}
default:
{
// TODO: Should we be receiving data in this state?
break;
}
}
}
void cHTTPConnection::OnRemoteClosed(void)
{
if (m_CurrentRequest != nullptr)
{
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
}
m_Link.reset();
}
void cHTTPConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
{
OnRemoteClosed();
}
void cHTTPConnection::SendData(const void * a_Data, size_t a_Size)
{
m_Link->Send(a_Data, a_Size);
}

View File

@ -1,290 +0,0 @@
// HTTPMessage.cpp
// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes
#include "Globals.h"
#include "HTTPMessage.h"
// Disable MSVC warnings:
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4355) // 'this' : used in base member initializer list
#endif
////////////////////////////////////////////////////////////////////////////////
// cHTTPMessage:
cHTTPMessage::cHTTPMessage(eKind a_Kind) :
m_Kind(a_Kind),
m_ContentLength(AString::npos)
{
}
void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
{
AString Key = StrToLower(a_Key);
cNameValueMap::iterator itr = m_Headers.find(Key);
if (itr == m_Headers.end())
{
m_Headers[Key] = a_Value;
}
else
{
// The header-field key is specified multiple times, combine into comma-separated list (RFC 2616 @ 4.2)
itr->second.append(", ");
itr->second.append(a_Value);
}
// Special processing for well-known headers:
if (Key == "content-type")
{
m_ContentType = m_Headers[Key];
}
else if (Key == "content-length")
{
if (!StringToInteger(m_Headers[Key], m_ContentLength))
{
m_ContentLength = 0;
}
}
}
////////////////////////////////////////////////////////////////////////////////
// cHTTPRequest:
cHTTPRequest::cHTTPRequest(void) :
super(mkRequest),
m_EnvelopeParser(*this),
m_IsValid(true),
m_UserData(nullptr),
m_HasAuth(false),
m_AllowKeepAlive(false)
{
}
size_t cHTTPRequest::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 cHTTPRequest::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 cHTTPRequest::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 cHTTPRequest::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);
}
////////////////////////////////////////////////////////////////////////////////
// cHTTPResponse:
cHTTPResponse::cHTTPResponse(void) :
super(mkResponse)
{
}
void cHTTPResponse::AppendToData(AString & a_DataStream) const
{
a_DataStream.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: ");
a_DataStream.append(m_ContentType);
a_DataStream.append("\r\n");
for (cNameValueMap::const_iterator itr = m_Headers.begin(), end = m_Headers.end(); itr != end; ++itr)
{
if ((itr->first == "Content-Type") || (itr->first == "Content-Length"))
{
continue;
}
a_DataStream.append(itr->first);
a_DataStream.append(": ");
a_DataStream.append(itr->second);
a_DataStream.append("\r\n");
} // for itr - m_Headers[]
a_DataStream.append("\r\n");
}

View File

@ -3,7 +3,7 @@
#include "Protocol/Authenticator.h"
#include "Protocol/MojangAPI.h"
#include "HTTPServer/HTTPServer.h"
#include "HTTP/HTTPServer.h"
#include "Defines.h"
#include "RankManager.h"
#include <thread>

View File

@ -12,8 +12,8 @@
#include "Server.h"
#include "Root.h"
#include "HTTPServer/HTTPMessage.h"
#include "HTTPServer/HTTPConnection.h"
#include "HTTP/HTTPServerConnection.h"
#include "HTTP/HTTPFormParser.h"
@ -49,6 +49,40 @@ public:
////////////////////////////////////////////////////////////////////////////////
// cWebadminRequestData
/** The form parser callbacks for requests in the "/webadmin" and "/~webadmin" paths */
class cWebadminRequestData :
public cHTTPFormParser::cCallbacks,
public cHTTPIncomingRequest::cUserData
{
public:
cHTTPFormParser m_Form;
cWebadminRequestData(const cHTTPIncomingRequest & a_Request):
m_Form(a_Request, *this)
{
}
// cHTTPFormParser::cCallbacks overrides. Files are ignored:
virtual void OnFileStart(cHTTPFormParser &, const AString & a_FileName) override
{
UNUSED(a_FileName);
}
virtual void OnFileData(cHTTPFormParser &, const char * a_Data, size_t a_Size) override
{
UNUSED(a_Data);
UNUSED(a_Size);
}
virtual void OnFileEnd(cHTTPFormParser &) override {}
} ;
////////////////////////////////////////////////////////////////////////////////
// cWebAdmin:
@ -212,7 +246,7 @@ bool cWebAdmin::LoadLoginTemplate(void)
void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
if (!a_Request.HasAuth())
{
@ -229,12 +263,12 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque
}
// Check if the contents should be wrapped in the template:
AString BareURL = a_Request.GetBareURL();
auto BareURL = a_Request.GetURLPath();
ASSERT(BareURL.length() > 0);
bool ShouldWrapInTemplate = ((BareURL.length() > 1) && (BareURL[1] != '~'));
// Retrieve the request data:
cWebadminRequestData * Data = reinterpret_cast<cWebadminRequestData *>(a_Request.GetUserData());
auto Data = std::static_pointer_cast<cWebadminRequestData>(a_Request.GetUserData());
if (Data == nullptr)
{
a_Connection.SendStatusAndReason(500, "Bad UserData");
@ -280,7 +314,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque
{
if (m_TemplateScript.Call("ShowPage", this, &TemplateRequest, cLuaState::Return, Template))
{
cHTTPResponse Resp;
cHTTPOutgoingResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send(Template.c_str(), Template.length());
@ -339,21 +373,22 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque
Printf(NumChunks, "%d", cRoot::Get()->GetTotalChunkCount());
ReplaceString(Template, "{NUMCHUNKS}", NumChunks);
cHTTPResponse Resp;
cHTTPOutgoingResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send(Template.c_str(), Template.length());
a_Connection.FinishResponse();
}
void cWebAdmin::HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
UNUSED(a_Request);
cHTTPResponse Resp;
cHTTPOutgoingResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send(m_LoginTemplate);
@ -364,7 +399,7 @@ void cWebAdmin::HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest &
void cWebAdmin::HandleFileRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
AString FileURL = a_Request.GetURL();
std::replace(FileURL.begin(), FileURL.end(), '\\', '/');
@ -406,7 +441,7 @@ void cWebAdmin::HandleFileRequest(cHTTPConnection & a_Connection, cHTTPRequest &
}
// Send the response:
cHTTPResponse Resp;
cHTTPOutgoingResponse Resp;
Resp.SetContentType(ContentType);
a_Connection.Send(Resp);
a_Connection.Send(Content);
@ -621,7 +656,7 @@ AString cWebAdmin::GetBaseURL(const AStringVector & a_URLSplit)
void cWebAdmin::OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
void cWebAdmin::OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
UNUSED(a_Connection);
const AString & URL = a_Request.GetURL();
@ -630,7 +665,7 @@ void cWebAdmin::OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_
(strncmp(URL.c_str(), "/~webadmin", 10) == 0)
)
{
a_Request.SetUserData(new cWebadminRequestData(a_Request));
a_Request.SetUserData(std::make_shared<cWebadminRequestData>(a_Request));
return;
}
if (URL == "/")
@ -645,22 +680,22 @@ void cWebAdmin::OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_
void cWebAdmin::OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size)
void cWebAdmin::OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const char * a_Data, size_t a_Size)
{
UNUSED(a_Connection);
cRequestData * Data = reinterpret_cast<cRequestData *>(a_Request.GetUserData());
auto Data = std::static_pointer_cast<cWebadminRequestData>(a_Request.GetUserData());
if (Data == nullptr)
{
return;
}
Data->OnBody(a_Data, a_Size);
Data->m_Form.Parse(a_Data, a_Size);
}
void cWebAdmin::OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
void cWebAdmin::OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
const AString & URL = a_Request.GetURL();
if (
@ -679,24 +714,9 @@ void cWebAdmin::OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest &
{
HandleFileRequest(a_Connection, a_Request);
}
// Delete any request data assigned to the request:
cRequestData * Data = reinterpret_cast<cRequestData *>(a_Request.GetUserData());
delete Data;
Data = nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// cWebAdmin::cWebadminRequestData
void cWebAdmin::cWebadminRequestData::OnBody(const char * a_Data, size_t a_Size)
{
m_Form.Parse(a_Data, a_Size);
}

View File

@ -7,8 +7,8 @@
#include "Bindings/LuaState.h"
#include "IniFile.h"
#include "HTTPServer/HTTPServer.h"
#include "HTTPServer/HTTPFormParser.h"
#include "HTTP/HTTPServer.h"
#include "HTTP/HTTPMessage.h"
@ -171,46 +171,6 @@ public:
static AString GetContentTypeFromFileExt(const AString & a_FileExtension);
protected:
/** Common base class for request body data handlers */
class cRequestData
{
public:
virtual ~cRequestData() {} // Force a virtual destructor in all descendants
/** Called when a new chunk of body data is received */
virtual void OnBody(const char * a_Data, size_t a_Size) = 0;
} ;
/** The body handler for requests in the "/webadmin" and "/~webadmin" paths */
class cWebadminRequestData :
public cRequestData,
public cHTTPFormParser::cCallbacks
{
public:
cHTTPFormParser m_Form;
cWebadminRequestData(cHTTPRequest & a_Request) :
m_Form(a_Request, *this)
{
}
// cRequestData overrides:
virtual void OnBody(const char * a_Data, size_t a_Size) override;
// cHTTPFormParser::cCallbacks overrides. Files are ignored:
virtual void OnFileStart(cHTTPFormParser &, const AString & a_FileName) override
{
UNUSED(a_FileName);
}
virtual void OnFileData(cHTTPFormParser &, const char * a_Data, size_t a_Size) override
{
UNUSED(a_Data);
UNUSED(a_Size);
}
virtual void OnFileEnd(cHTTPFormParser &) override {}
} ;
/** Set to true if Init() succeeds and the webadmin isn't to be disabled */
bool m_IsInitialized;
@ -236,18 +196,18 @@ protected:
cHTTPServer m_HTTPServer;
/** Handles requests coming to the "/webadmin" or "/~webadmin" URLs */
void HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
void HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);
/** Handles requests for the root page */
void HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
void HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);
/** Handles requests for a file */
void HandleFileRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
void HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);
// cHTTPServer::cCallbacks overrides:
virtual void OnRequestBegun (cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override;
virtual void OnRequestBody (cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) override;
virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override;
virtual void OnRequestBegun (cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) override;
virtual void OnRequestBody (cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const char * a_Data, size_t a_Size) override;
virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) override;
} ; // tolua_export

View File

@ -9,5 +9,6 @@ endif()
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(ChunkData)
add_subdirectory(HTTP)
add_subdirectory(Network)
add_subdirectory(LoadablePieces)

60
tests/HTTP/CMakeLists.txt Normal file
View File

@ -0,0 +1,60 @@
cmake_minimum_required (VERSION 2.6)
enable_testing()
include_directories(${CMAKE_SOURCE_DIR}/src/)
include_directories(${CMAKE_SOURCE_DIR}/lib/libevent/include)
add_definitions(-DTEST_GLOBALS=1)
# Create a single HTTP library that contains all the HTTP code:
set (HTTP_SRCS
${CMAKE_SOURCE_DIR}/src/HTTP/EnvelopeParser.cpp
${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessage.cpp
${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessageParser.cpp
${CMAKE_SOURCE_DIR}/src/HTTP/TransferEncodingParser.cpp
${CMAKE_SOURCE_DIR}/src/StringUtils.cpp
)
set (HTTP_HDRS
${CMAKE_SOURCE_DIR}/src/HTTP/EnvelopeParser.h
${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessage.h
${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessageParser.h
${CMAKE_SOURCE_DIR}/src/HTTP/TransferEncodingParser.h
${CMAKE_SOURCE_DIR}/src/StringUtils.h
)
add_library(HTTP
${HTTP_SRCS}
${HTTP_HDRS}
)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_flags_cxx("-Wno-error=conversion -Wno-error=old-style-cast")
endif()
# Define individual tests:
# HTTPMessageParser_file: Feed file contents into a cHTTPResponseParser and print the callbacks as they're called:
add_executable(HTTPMessageParser_file-exe HTTPMessageParser_file.cpp)
target_link_libraries(HTTPMessageParser_file-exe HTTP)
# Test parsing the response file in 2-byte chunks (should go from response line parsing through headers parsing to body parsing, each within a different step):
add_test(NAME HTTPMessageParser_file-test1-2 COMMAND HTTPMessageParser_file-exe HTTPResponse1.data 2)
# Test parsing the response file in 128-byte chunks (should parse response line and part of headers in one step, the rest in another step):
add_test(NAME HTTPMessageParser_file-test1-128 COMMAND HTTPMessageParser_file-exe HTTPResponse1.data 128)
# Test parsing a chunked-encoding response:
add_test(NAME HTTPMessageParser_file-test2 COMMAND HTTPMessageParser_file-exe HTTPResponse2.data)
# Test parsing the request file in 2-byte chunks (should go from request line parsing through headers parsing to body parsing, each within a different step):
add_test(NAME HTTPMessageParser_file-test3-2 COMMAND HTTPMessageParser_file-exe HTTPRequest1.data 2)
# Test parsing the request file in 512-byte chunks (should process everything in a single call):
add_test(NAME HTTPMessageParser_file-test4-512 COMMAND HTTPMessageParser_file-exe HTTPRequest1.data 512)

View File

@ -0,0 +1,153 @@
// HTTPMessageParser_file.cpp
// Implements a test that feeds file contents into a cHTTPMessageParser instance and prints all callbacks
#include "Globals.h"
#include "HTTP/HTTPMessageParser.h"
/** Maximum size of the input buffer, through which the file is read */
static const size_t MAX_BUF = 4096;
class cCallbacks:
public cHTTPMessageParser::cCallbacks
{
typedef cHTTPMessageParser::cCallbacks Super;
public:
cCallbacks(void)
{
printf("cCallbacks created\n");
}
// cHTTPResponseParser::cCallbacks overrides:
virtual void OnError(const AString & a_ErrorDescription) override
{
printf("Error: \"%s\"\n", a_ErrorDescription.c_str());
}
/** Called when the first line (request / status) is fully parsed. */
virtual void OnFirstLine(const AString & a_FirstLine) override
{
printf("First line: \"%s\"\n", a_FirstLine.c_str());
}
/** Called when a single header line is parsed. */
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override
{
printf("Header line: \"%s\": \"%s\"\n", a_Key.c_str(), a_Value.c_str());
}
/** Called when all the headers have been parsed. */
virtual void OnHeadersFinished(void) override
{
printf("Headers finished\n");
}
/** Called for each chunk of the incoming body data. */
virtual void OnBodyData(const void * a_Data, size_t a_Size) override
{
AString hexDump;
CreateHexDump(hexDump, a_Data, a_Size, 16);
printf("Body data: %u bytes\n%s", static_cast<unsigned>(a_Size), hexDump.c_str());
}
virtual void OnBodyFinished(void) override
{
printf("Body finished\n");
}
};
int main(int argc, char * argv[])
{
printf("HTTPMessageParser_file beginning\n");
// Open the input file:
if (argc <= 1)
{
printf("Usage: %s <filename> [<buffersize>]\n", argv[0]);
return 1;
}
FILE * f;
if (strcmp(argv[1], "-") == 0)
{
f = stdin;
}
else
{
f = fopen(argv[1], "rb");
if (f == nullptr)
{
printf("Cannot open file \"%s\". Aborting.\n", argv[1]);
return 2;
}
}
// If a third param is present, use it as the buffer size
size_t bufSize = MAX_BUF;
if (argc >= 3)
{
if (!StringToInteger(argv[2], bufSize) || (bufSize == 0))
{
bufSize = MAX_BUF;
printf("\"%s\" is not a valid buffer size, using the default of %u instead.\n", argv[2], static_cast<unsigned>(bufSize));
}
if (bufSize > MAX_BUF)
{
bufSize = MAX_BUF;
printf("\"%s\" is too large, maximum buffer size is %u. Using the size %u instead.\n", argv[2], static_cast<unsigned>(bufSize), static_cast<unsigned>(bufSize));
}
}
// Feed the file contents into the parser:
cCallbacks callbacks;
cHTTPMessageParser parser(callbacks);
while (true)
{
char buf[MAX_BUF];
auto numBytes = fread(buf, 1, bufSize, f);
if (numBytes == 0)
{
printf("Read 0 bytes from file (EOF?), terminating\n");
break;
}
auto numConsumed = parser.Parse(buf, numBytes);
if (numConsumed == AString::npos)
{
printf("Parser indicates there was an error, terminating parsing.\n");
break;
}
ASSERT(numConsumed <= numBytes);
if (numConsumed < numBytes)
{
printf("Parser indicates stream end, but there's more data (at least %u bytes) in the file.\n", static_cast<unsigned>(numBytes - numConsumed));
}
}
if (!parser.IsFinished())
{
printf("Parser indicates an incomplete stream.\n");
}
// Close the input file:
if (f != stdin)
{
fclose(f);
}
return 0;
}

View File

@ -0,0 +1,5 @@
GET /some/url HTTP/1.1
Note: This is a test of a regular request
Content-Length: 3
bla

View File

@ -0,0 +1,3 @@
GET /some/url HTTP/1.1
Note: This is a test of a regular body-less request

View File

@ -0,0 +1,153 @@
// HTTPResponseParser_file.cpp
// Implements a test that feeds file contents into a cHTTPResponseParser instance and prints all callbacks
#include "Globals.h"
#include "HTTP/HTTPRequestParser.h"
/** Maximum size of the input buffer, through which the file is read */
static const size_t MAX_BUF = 4096;
class cCallbacks:
public cHTTPRequestParser::cCallbacks
{
typedef cHTTPResponseParser::cCallbacks Super;
public:
cCallbacks(void)
{
printf("cCallbacks created\n");
}
// cHTTPResponseParser::cCallbacks overrides:
virtual void OnError(const AString & a_ErrorDescription) override
{
printf("Error: \"%s\"\n", a_ErrorDescription.c_str());
}
/** Called when the status line is fully parsed. */
virtual void OnStatusLine(const AString & a_StatusLine) override
{
printf("Status line: \"%s\"\n", a_StatusLine.c_str());
}
/** Called when a single header line is parsed. */
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override
{
printf("Header line: \"%s\": \"%s\"\n", a_Key.c_str(), a_Value.c_str());
}
/** Called when all the headers have been parsed. */
virtual void OnHeadersFinished(void) override
{
printf("Headers finished\n");
}
/** Called for each chunk of the incoming body data. */
virtual void OnBodyData(const void * a_Data, size_t a_Size) override
{
AString hexDump;
CreateHexDump(hexDump, a_Data, a_Size, 16);
printf("Body data: %u bytes\n%s", static_cast<unsigned>(a_Size), hexDump.c_str());
}
virtual void OnBodyFinished(void) override
{
printf("Body finished\n");
}
};
int main(int argc, char * argv[])
{
printf("HTTPResponseParser_file beginning\n");
// Open the input file:
if (argc <= 1)
{
printf("Usage: %s <filename> [<buffersize>]\n", argv[0]);
return 1;
}
FILE * f;
if (strcmp(argv[1], "-") == 0)
{
f = stdin;
}
else
{
f = fopen(argv[1], "rb");
if (f == nullptr)
{
printf("Cannot open file \"%s\". Aborting.\n", argv[1]);
return 2;
}
}
// If a third param is present, use it as the buffer size
size_t bufSize = MAX_BUF;
if (argc >= 3)
{
if (!StringToInteger(argv[2], bufSize) || (bufSize == 0))
{
bufSize = MAX_BUF;
printf("\"%s\" is not a valid buffer size, using the default of %u instead.\n", argv[2], static_cast<unsigned>(bufSize));
}
if (bufSize > MAX_BUF)
{
bufSize = MAX_BUF;
printf("\"%s\" is too large, maximum buffer size is %u. Using the size %u instead.\n", argv[2], static_cast<unsigned>(bufSize), static_cast<unsigned>(bufSize));
}
}
// Feed the file contents into the parser:
cCallbacks callbacks;
cHTTPResponseParser parser(callbacks);
while (!feof(f))
{
char buf[MAX_BUF];
auto numBytes = fread(buf, 1, bufSize, f);
if (numBytes == 0)
{
printf("Read 0 bytes from file (EOF?), terminating\n");
break;
}
auto numConsumed = parser.Parse(buf, numBytes);
if (numConsumed == AString::npos)
{
printf("Parser indicates there was an error, terminating parsing.\n");
break;
}
ASSERT(numConsumed <= numBytes);
if (numConsumed < numBytes)
{
printf("Parser indicates stream end, but there's more data (at least %u bytes) in the file.\n", static_cast<unsigned>(numBytes - numConsumed));
}
}
if (!parser.IsFinished())
{
printf("Parser indicates an incomplete stream.\n");
}
// Close the input file:
if (f != stdin)
{
fclose(f);
}
return 0;
}

View File

@ -0,0 +1,10 @@
HTTP/1.0 200 OK
Note: This is a test of a regular response with Content-Length set
(identity transfer encoding)
Note2: The above header also tests multi-line header lines
Note3: The data is 2 bytes longer than the actual request, parser should indicate 2 extra bytes at the end
Header1: Value1
Header2: Value2
Content-Length: 3
bla

View File

@ -0,0 +1,15 @@
HTTP/1.0 200 OK
Note: This is a Chunked transfer encoding test
Header2: Value2
Transfer-Encoding: chunked
4
Wiki
5
pedia
e
in
chunks.
0

View File

@ -0,0 +1,153 @@
// HTTPResponseParser_file.cpp
// Implements a test that feeds file contents into a cHTTPResponseParser instance and prints all callbacks
#include "Globals.h"
#include "HTTP/HTTPResponseParser.h"
/** Maximum size of the input buffer, through which the file is read */
static const size_t MAX_BUF = 4096;
class cCallbacks:
public cHTTPResponseParser::cCallbacks
{
typedef cHTTPResponseParser::cCallbacks Super;
public:
cCallbacks(void)
{
printf("cCallbacks created\n");
}
// cHTTPResponseParser::cCallbacks overrides:
virtual void OnError(const AString & a_ErrorDescription) override
{
printf("Error: \"%s\"\n", a_ErrorDescription.c_str());
}
/** Called when the status line is fully parsed. */
virtual void OnStatusLine(const AString & a_StatusLine) override
{
printf("Status line: \"%s\"\n", a_StatusLine.c_str());
}
/** Called when a single header line is parsed. */
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override
{
printf("Header line: \"%s\": \"%s\"\n", a_Key.c_str(), a_Value.c_str());
}
/** Called when all the headers have been parsed. */
virtual void OnHeadersFinished(void) override
{
printf("Headers finished\n");
}
/** Called for each chunk of the incoming body data. */
virtual void OnBodyData(const void * a_Data, size_t a_Size) override
{
AString hexDump;
CreateHexDump(hexDump, a_Data, a_Size, 16);
printf("Body data: %u bytes\n%s", static_cast<unsigned>(a_Size), hexDump.c_str());
}
virtual void OnBodyFinished(void) override
{
printf("Body finished\n");
}
};
int main(int argc, char * argv[])
{
printf("HTTPResponseParser_file beginning\n");
// Open the input file:
if (argc <= 1)
{
printf("Usage: %s <filename> [<buffersize>]\n", argv[0]);
return 1;
}
FILE * f;
if (strcmp(argv[1], "-") == 0)
{
f = stdin;
}
else
{
f = fopen(argv[1], "rb");
if (f == nullptr)
{
printf("Cannot open file \"%s\". Aborting.\n", argv[1]);
return 2;
}
}
// If a third param is present, use it as the buffer size
size_t bufSize = MAX_BUF;
if (argc >= 3)
{
if (!StringToInteger(argv[2], bufSize) || (bufSize == 0))
{
bufSize = MAX_BUF;
printf("\"%s\" is not a valid buffer size, using the default of %u instead.\n", argv[2], static_cast<unsigned>(bufSize));
}
if (bufSize > MAX_BUF)
{
bufSize = MAX_BUF;
printf("\"%s\" is too large, maximum buffer size is %u. Using the size %u instead.\n", argv[2], static_cast<unsigned>(bufSize), static_cast<unsigned>(bufSize));
}
}
// Feed the file contents into the parser:
cCallbacks callbacks;
cHTTPResponseParser parser(callbacks);
while (!feof(f))
{
char buf[MAX_BUF];
auto numBytes = fread(buf, 1, bufSize, f);
if (numBytes == 0)
{
printf("Read 0 bytes from file (EOF?), terminating\n");
break;
}
auto numConsumed = parser.Parse(buf, numBytes);
if (numConsumed == AString::npos)
{
printf("Parser indicates there was an error, terminating parsing.\n");
break;
}
ASSERT(numConsumed <= numBytes);
if (numConsumed < numBytes)
{
printf("Parser indicates stream end, but there's more data (at least %u bytes) in the file.\n", static_cast<unsigned>(numBytes - numConsumed));
}
}
if (!parser.IsFinished())
{
printf("Parser indicates an incomplete stream.\n");
}
// Close the input file:
if (f != stdin)
{
fclose(f);
}
return 0;
}