Merge pull request #3031 from cuberite/RenameHttpClasses
Refactor HTTP parsing
This commit is contained in:
commit
7cc8f8dd22
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal 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
|
@ -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
|
||||
|
@ -36,7 +36,7 @@
|
||||
#include "../StringCompression.h"
|
||||
#include "../CommandOutput.h"
|
||||
#include "../BuildInfo.h"
|
||||
#include "../HTTPServer/UrlParser.h"
|
||||
#include "../HTTP/UrlParser.h"
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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()
|
||||
|
@ -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:
|
@ -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));
|
@ -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
159
src/HTTP/HTTPMessage.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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;
|
||||
};
|
222
src/HTTP/HTTPMessageParser.cpp
Normal file
222
src/HTTP/HTTPMessageParser.cpp
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
125
src/HTTP/HTTPMessageParser.h
Normal file
125
src/HTTP/HTTPMessageParser.h
Normal 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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
} ;
|
||||
|
||||
|
240
src/HTTP/HTTPServerConnection.cpp
Normal file
240
src/HTTP/HTTPServerConnection.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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;
|
@ -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;
|
394
src/HTTP/TransferEncodingParser.cpp
Normal file
394
src/HTTP/TransferEncodingParser.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
76
src/HTTP/TransferEncodingParser.h
Normal file
76
src/HTTP/TransferEncodingParser.h
Normal 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)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
60
tests/HTTP/CMakeLists.txt
Normal 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)
|
||||
|
153
tests/HTTP/HTTPMessageParser_file.cpp
Normal file
153
tests/HTTP/HTTPMessageParser_file.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
5
tests/HTTP/HTTPRequest1.data
Normal file
5
tests/HTTP/HTTPRequest1.data
Normal file
@ -0,0 +1,5 @@
|
||||
GET /some/url HTTP/1.1
|
||||
Note: This is a test of a regular request
|
||||
Content-Length: 3
|
||||
|
||||
bla
|
3
tests/HTTP/HTTPRequest2.data
Normal file
3
tests/HTTP/HTTPRequest2.data
Normal file
@ -0,0 +1,3 @@
|
||||
GET /some/url HTTP/1.1
|
||||
Note: This is a test of a regular body-less request
|
||||
|
153
tests/HTTP/HTTPRequestParser_file.cpp
Normal file
153
tests/HTTP/HTTPRequestParser_file.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
10
tests/HTTP/HTTPResponse1.data
Normal file
10
tests/HTTP/HTTPResponse1.data
Normal 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
|
15
tests/HTTP/HTTPResponse2.data
Normal file
15
tests/HTTP/HTTPResponse2.data
Normal 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
|
||||
|
153
tests/HTTP/HTTPResponseParser_file.cpp
Normal file
153
tests/HTTP/HTTPResponseParser_file.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user