1
0

Renamed HTTPServer folder to HTTP.

It contains client code as well.
This commit is contained in:
Mattes D
2016-01-03 15:59:55 +01:00
parent fce68dc8f3
commit fea556ca1b
31 changed files with 407 additions and 140 deletions

45
src/HTTP/CMakeLists.txt Normal file
View File

@@ -0,0 +1,45 @@
cmake_minimum_required (VERSION 2.6)
project (Cuberite)
include_directories ("${PROJECT_SOURCE_DIR}/../")
SET (SRCS
EnvelopeParser.cpp
HTTPFormParser.cpp
HTTPMessage.cpp
HTTPRequestParser.cpp
HTTPResponseParser.cpp
HTTPServer.cpp
HTTPServerConnection.cpp
MultipartParser.cpp
NameValueParser.cpp
SslHTTPServerConnection.cpp
TransferEncodingParser.cpp
UrlParser.cpp
)
SET (HDRS
EnvelopeParser.h
HTTPFormParser.h
HTTPMessage.h
HTTPRequestParser.h
HTTPResponseParser.h
HTTPServer.h
HTTPServerConnection.h
MultipartParser.h
NameValueParser.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(HTTPServerConnection.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum")
set_source_files_properties(HTTPMessage.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=tautological-compare")
endif()
if(NOT MSVC)
add_library(HTTPServer ${SRCS} ${HDRS})
endif()

132
src/HTTP/EnvelopeParser.cpp Normal file
View File

@@ -0,0 +1,132 @@
// EnvelopeParser.cpp
// Implements the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME
#include "Globals.h"
#include "EnvelopeParser.h"
cEnvelopeParser::cEnvelopeParser(cCallbacks & a_Callbacks) :
m_Callbacks(a_Callbacks),
m_IsInHeaders(true)
{
}
size_t cEnvelopeParser::Parse(const char * a_Data, size_t a_Size)
{
if (!m_IsInHeaders)
{
return 0;
}
// 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;
m_IncomingData.append(a_Data, a_Size);
size_t idxCRLF = m_IncomingData.find("\r\n", SearchStart);
if (idxCRLF == AString::npos)
{
// Not a complete line yet, all input consumed:
return a_Size;
}
// Parse as many lines as found:
size_t Last = 0;
do
{
if (idxCRLF == Last)
{
// This was the last line of the data. Finish whatever value has been cached and return:
NotifyLast();
m_IsInHeaders = false;
return a_Size - (m_IncomingData.size() - idxCRLF) + 2;
}
if (!ParseLine(m_IncomingData.c_str() + Last, idxCRLF - Last))
{
// An error has occurred
m_IsInHeaders = false;
return AString::npos;
}
Last = idxCRLF + 2;
idxCRLF = m_IncomingData.find("\r\n", idxCRLF + 2);
} while (idxCRLF != AString::npos);
m_IncomingData.erase(0, Last);
// Parsed all lines and still expecting more
return a_Size;
}
void cEnvelopeParser::Reset(void)
{
m_IsInHeaders = true;
m_IncomingData.clear();
m_LastKey.clear();
m_LastValue.clear();
}
void cEnvelopeParser::NotifyLast(void)
{
if (!m_LastKey.empty())
{
m_Callbacks.OnHeaderLine(m_LastKey, m_LastValue);
m_LastKey.clear();
}
m_LastValue.clear();
}
bool cEnvelopeParser::ParseLine(const char * a_Data, size_t a_Size)
{
ASSERT(a_Size > 0);
if (a_Data[0] <= ' ')
{
// This line is a continuation for the previous line
if (m_LastKey.empty())
{
return false;
}
// Append, including the whitespace in a_Data[0]
m_LastValue.append(a_Data, a_Size);
return true;
}
// This is a line with a new key:
NotifyLast();
for (size_t i = 0; i < a_Size; i++)
{
if (a_Data[i] == ':')
{
m_LastKey.assign(a_Data, i);
m_LastValue.assign(a_Data + i + 2, a_Size - i - 2);
return true;
}
} // for i - a_Data[]
// No colon was found, key-less header??
return false;
}

73
src/HTTP/EnvelopeParser.h Normal file
View File

@@ -0,0 +1,73 @@
// EnvelopeParser.h
// Declares the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME
#pragma once
class cEnvelopeParser
{
public:
class cCallbacks
{
public:
// Force a virtual destructor in descendants:
virtual ~cCallbacks() {}
/** Called when a full header line is parsed */
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0;
} ;
cEnvelopeParser(cCallbacks & a_Callbacks);
/** Parses the incoming data.
Returns the number of bytes consumed from the input. The bytes not consumed are not part of the envelope header.
Returns AString::npos on error
*/
size_t Parse(const char * a_Data, size_t a_Size);
/** Makes the parser forget everything parsed so far, so that it can be reused for parsing another datastream */
void Reset(void);
/** Returns true if more input is expected for the envelope header */
bool IsInHeaders(void) const { return m_IsInHeaders; }
/** Sets the IsInHeaders flag; used by cMultipartParser to simplify the parser initial conditions */
void SetIsInHeaders(bool a_IsInHeaders) { m_IsInHeaders = a_IsInHeaders; }
public:
/** Callbacks to call for the various events */
cCallbacks & m_Callbacks;
/** Set to true while the parser is still parsing the envelope headers. Once set to true, the parser will not consume any more data. */
bool m_IsInHeaders;
/** Buffer for the incoming data until it is parsed */
AString m_IncomingData;
/** Holds the last parsed key; used for line-wrapped values */
AString m_LastKey;
/** Holds the last parsed value; used for line-wrapped values */
AString m_LastValue;
/** Notifies the callback of the key / value stored in m_LastKey / m_LastValue, then erases them */
void NotifyLast(void);
/** Parses one line of header data. Returns true if successful */
bool ParseLine(const char * a_Data, size_t a_Size);
} ;

293
src/HTTP/HTTPFormParser.cpp Normal file
View File

@@ -0,0 +1,293 @@
// HTTPFormParser.cpp
// Implements the cHTTPFormParser class representing a parser for forms sent over HTTP
#include "Globals.h"
#include "HTTPFormParser.h"
#include "HTTPRequestParser.h"
#include "MultipartParser.h"
#include "NameValueParser.h"
cHTTPFormParser::cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks) :
m_Callbacks(a_Callbacks),
m_IsValid(true),
m_IsCurrentPartFile(false),
m_FileHasBeenAnnounced(false)
{
if (a_Request.GetMethod() == "GET")
{
m_Kind = fpkURL;
// Directly parse the URL in the request:
const AString & URL = a_Request.GetURL();
size_t idxQM = URL.find('?');
if (idxQM != AString::npos)
{
Parse(URL.c_str() + idxQM + 1, URL.size() - idxQM - 1);
}
return;
}
if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT"))
{
if (strncmp(a_Request.GetContentType().c_str(), "application/x-www-form-urlencoded", 33) == 0)
{
m_Kind = fpkFormUrlEncoded;
return;
}
if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0)
{
m_Kind = fpkMultipart;
BeginMultipart(a_Request);
return;
}
}
// Invalid method / content type combination, this is not a HTTP form
m_IsValid = false;
}
cHTTPFormParser::cHTTPFormParser(eKind a_Kind, const char * a_Data, size_t a_Size, cCallbacks & a_Callbacks) :
m_Callbacks(a_Callbacks),
m_Kind(a_Kind),
m_IsValid(true),
m_IsCurrentPartFile(false),
m_FileHasBeenAnnounced(false)
{
Parse(a_Data, a_Size);
}
void cHTTPFormParser::Parse(const char * a_Data, size_t a_Size)
{
if (!m_IsValid)
{
return;
}
switch (m_Kind)
{
case fpkURL:
case fpkFormUrlEncoded:
{
// This format is used for smaller forms (not file uploads), so we can delay parsing it until Finish()
m_IncomingData.append(a_Data, a_Size);
break;
}
case fpkMultipart:
{
ASSERT(m_MultipartParser.get() != nullptr);
m_MultipartParser->Parse(a_Data, a_Size);
break;
}
}
}
bool cHTTPFormParser::Finish(void)
{
switch (m_Kind)
{
case fpkURL:
case fpkFormUrlEncoded:
{
// m_IncomingData has all the form data, parse it now:
ParseFormUrlEncoded();
break;
}
case fpkMultipart:
{
// Nothing needed for other formats
break;
}
}
return (m_IsValid && m_IncomingData.empty());
}
bool cHTTPFormParser::HasFormData(const cHTTPRequestParser & a_Request)
{
const AString & ContentType = a_Request.GetContentType();
return (
(ContentType == "application/x-www-form-urlencoded") ||
(strncmp(ContentType.c_str(), "multipart/form-data", 19) == 0) ||
(
(a_Request.GetMethod() == "GET") &&
(a_Request.GetURL().find('?') != AString::npos)
)
);
}
void cHTTPFormParser::BeginMultipart(const cHTTPRequestParser & a_Request)
{
ASSERT(m_MultipartParser.get() == nullptr);
m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this));
}
void cHTTPFormParser::ParseFormUrlEncoded(void)
{
// Parse m_IncomingData for all the variables; no more data is incoming, since this is called from Finish()
// This may not be the most performant version, but we don't care, the form data is small enough and we're not a full-fledged web server anyway
AStringVector Lines = StringSplit(m_IncomingData, "&");
for (AStringVector::iterator itr = Lines.begin(), end = Lines.end(); itr != end; ++itr)
{
AStringVector Components = StringSplit(*itr, "=");
switch (Components.size())
{
default:
{
// Neither name nor value, or too many "="s, mark this as invalid form:
m_IsValid = false;
return;
}
case 1:
{
// Only name present
(*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = "";
break;
}
case 2:
{
// name=value format:
(*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = URLDecode(ReplaceAllCharOccurrences(Components[1], '+', ' '));
break;
}
}
} // for itr - Lines[]
m_IncomingData.clear();
}
void cHTTPFormParser::OnPartStart(void)
{
m_CurrentPartFileName.clear();
m_CurrentPartName.clear();
m_IsCurrentPartFile = false;
m_FileHasBeenAnnounced = false;
}
void cHTTPFormParser::OnPartHeader(const AString & a_Key, const AString & a_Value)
{
if (NoCaseCompare(a_Key, "Content-Disposition") == 0)
{
size_t len = a_Value.size();
size_t ParamsStart = AString::npos;
for (size_t i = 0; i < len; ++i)
{
if (a_Value[i] > ' ')
{
if (strncmp(a_Value.c_str() + i, "form-data", 9) != 0)
{
// Content disposition is not "form-data", mark the whole form invalid
m_IsValid = false;
return;
}
ParamsStart = a_Value.find(';', i + 9);
break;
}
}
if (ParamsStart == AString::npos)
{
// There is data missing in the Content-Disposition field, mark the whole form invalid:
m_IsValid = false;
return;
}
// Parse the field name and optional filename from this header:
cNameValueParser Parser(a_Value.data() + ParamsStart, a_Value.size() - ParamsStart);
Parser.Finish();
m_CurrentPartName = Parser["name"];
if (!Parser.IsValid() || m_CurrentPartName.empty())
{
// The required parameter "name" is missing, mark the whole form invalid:
m_IsValid = false;
return;
}
m_CurrentPartFileName = Parser["filename"];
}
}
void cHTTPFormParser::OnPartData(const char * a_Data, size_t a_Size)
{
if (m_CurrentPartName.empty())
{
// Prologue, epilogue or invalid part
return;
}
if (m_CurrentPartFileName.empty())
{
// This is a variable, store it in the map
iterator itr = find(m_CurrentPartName);
if (itr == end())
{
(*this)[m_CurrentPartName] = AString(a_Data, a_Size);
}
else
{
itr->second.append(a_Data, a_Size);
}
}
else
{
// This is a file, pass it on through the callbacks
if (!m_FileHasBeenAnnounced)
{
m_Callbacks.OnFileStart(*this, m_CurrentPartFileName);
m_FileHasBeenAnnounced = true;
}
m_Callbacks.OnFileData(*this, a_Data, a_Size);
}
}
void cHTTPFormParser::OnPartEnd(void)
{
if (m_FileHasBeenAnnounced)
{
m_Callbacks.OnFileEnd(*this);
}
m_CurrentPartName.clear();
m_CurrentPartFileName.clear();
}

114
src/HTTP/HTTPFormParser.h Normal file
View File

@@ -0,0 +1,114 @@
// HTTPFormParser.h
// Declares the cHTTPFormParser class representing a parser for forms sent over HTTP
#pragma once
#include "MultipartParser.h"
// fwd:
class cHTTPRequestParser;
class cHTTPFormParser :
public std::map<AString, AString>,
public cMultipartParser::cCallbacks
{
public:
enum eKind
{
fpkURL, ///< The form has been transmitted as parameters to a GET request
fpkFormUrlEncoded, ///< The form has been POSTed or PUT, with Content-Type of "application/x-www-form-urlencoded"
fpkMultipart, ///< The form has been POSTed or PUT, with Content-Type of "multipart/form-data"
} ;
class cCallbacks
{
public:
// Force a virtual destructor in descendants:
virtual ~cCallbacks() {}
/** Called when a new file part is encountered in the form data */
virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) = 0;
/** Called when more file data has come for the current file in the form data */
virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, size_t a_Size) = 0;
/** Called when the current file part has ended in the form data */
virtual void OnFileEnd(cHTTPFormParser & a_Parser) = 0;
} ;
/** Creates a parser that is tied to a request and notifies of various events using a callback mechanism */
cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks);
/** 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);
/** Adds more data into the parser, as the request body is received */
void Parse(const char * a_Data, size_t a_Size);
/** Notifies that there's no more data incoming and the parser should finish its parsing.
Returns true if parsing successful. */
bool Finish(void);
/** Returns true if the headers suggest the request has form data parseable by this class */
static bool HasFormData(const cHTTPRequestParser & a_Request);
protected:
/** The callbacks to call for incoming file data */
cCallbacks & m_Callbacks;
/** The kind of the parser (decided in the constructor, used in Parse() */
eKind m_Kind;
/** Buffer for the incoming data until it's parsed */
AString m_IncomingData;
/** True if the information received so far is a valid form; set to false on first problem. Further parsing is skipped when false. */
bool m_IsValid;
/** The parser for the multipart data, if used */
std::unique_ptr<cMultipartParser> m_MultipartParser;
/** Name of the currently parsed part in multipart data */
AString m_CurrentPartName;
/** True if the currently parsed part in multipart data is a file */
bool m_IsCurrentPartFile;
/** Filename of the current parsed part in multipart data (for file uploads) */
AString m_CurrentPartFileName;
/** Set to true after m_Callbacks.OnFileStart() has been called, reset to false on PartEnd */
bool m_FileHasBeenAnnounced;
/** Sets up the object for parsing a fpkMultipart request */
void BeginMultipart(const cHTTPRequestParser & a_Request);
/** Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) */
void ParseFormUrlEncoded(void);
// cMultipartParser::cCallbacks overrides:
virtual void OnPartStart (void) override;
virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override;
virtual void OnPartData (const char * a_Data, size_t a_Size) override;
virtual void OnPartEnd (void) override;
} ;

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

@@ -0,0 +1,102 @@
// 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;
}
}
}
////////////////////////////////////////////////////////////////////////////////
// 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 (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");
}

84
src/HTTP/HTTPMessage.h Normal file
View File

@@ -0,0 +1,84 @@
// HTTPMessage.h
// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes
#pragma once
#include "EnvelopeParser.h"
class cHTTPMessage
{
public:
enum eStatus
{
HTTP_OK = 200,
HTTP_BAD_REQUEST = 400,
} ;
enum eKind
{
mkRequest,
mkResponse,
} ;
cHTTPMessage(eKind a_Kind);
// Force a virtual destructor in all descendants
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);
void SetContentType (const AString & a_ContentType) { m_ContentType = a_ContentType; }
void SetContentLength(size_t a_ContentLength) { m_ContentLength = a_ContentLength; }
const AString & GetContentType (void) const { return m_ContentType; }
size_t GetContentLength(void) const { return m_ContentLength; }
protected:
typedef std::map<AString, AString> cNameValueMap;
eKind m_Kind;
/** Map of headers, with their keys lowercased. */
AStringMap m_Headers;
/** Type of the content; parsed by AddHeader(), set directly by SetContentLength() */
AString m_ContentType;
/** Length of the content that is to be received.
AString::npos when the object is created.
Parsed by AddHeader() or set directly by SetContentLength() */
size_t m_ContentLength;
} ;
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;
} ;

View File

@@ -0,0 +1,192 @@
// HTTPRequestParser.cpp
// Implements the cHTTPRequestParser class representing the parser for incoming HTTP requests
#include "Globals.h"
#include "HTTPRequestParser.h"
cHTTPRequestParser::cHTTPRequestParser(void) :
super(mkRequest),
m_EnvelopeParser(*this),
m_IsValid(true),
m_UserData(nullptr),
m_HasAuth(false),
m_AllowKeepAlive(false)
{
}
size_t cHTTPRequestParser::ParseHeaders(const char * a_Data, size_t a_Size)
{
if (!m_IsValid)
{
return AString::npos;
}
if (m_Method.empty())
{
// The first line hasn't been processed yet
size_t res = ParseRequestLine(a_Data, a_Size);
if ((res == AString::npos) || (res == a_Size))
{
return res;
}
size_t res2 = m_EnvelopeParser.Parse(a_Data + res, 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);
if (res == AString::npos)
{
m_IsValid = false;
}
return res;
}
return 0;
}
AString cHTTPRequestParser::GetBareURL(void) const
{
size_t idxQM = m_URL.find('?');
if (idxQM != AString::npos)
{
return m_URL.substr(0, idxQM);
}
else
{
return m_URL;
}
}
size_t cHTTPRequestParser::ParseRequestLine(const char * a_Data, size_t a_Size)
{
m_IncomingHeaderData.append(a_Data, a_Size);
size_t 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;
}
} // switch (m_IncomingHeaderData[i])
} // for i - m_IncomingHeaderData[]
// CRLF hasn't been encountered yet, consider all data consumed
return a_Size;
}
void cHTTPRequestParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
{
if (
(NoCaseCompare(a_Key, "Authorization") == 0) &&
(strncmp(a_Value.c_str(), "Basic ", 6) == 0)
)
{
AString UserPass = Base64Decode(a_Value.substr(6));
size_t idxCol = UserPass.find(':');
if (idxCol != AString::npos)
{
m_AuthUsername = UserPass.substr(0, idxCol);
m_AuthPassword = UserPass.substr(idxCol + 1);
m_HasAuth = true;
}
}
if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0))
{
m_AllowKeepAlive = true;
}
AddHeader(a_Key, a_Value);
}

View File

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

View File

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

View File

@@ -0,0 +1,118 @@
// HTTPResponseParser.h
// Declares the cHTTPResponseParser class representing the parser for incoming HTTP responses
#pragma once
#include "HTTPMessage.h"
#include "TransferEncodingParser.h"
class cHTTPResponseParser:
public cHTTPMessage,
protected cEnvelopeParser::cCallbacks,
protected cTransferEncodingParser::cCallbacks
{
typedef cHTTPMessage Super;
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 status line is fully parsed. */
virtual void OnStatusLine(const AString & a_StatusLine) = 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;
};
cHTTPResponseParser(cCallbacks & a_Callbacks);
/** 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 response.
Returns AString::npos on an 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; }
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 parser is still parsing the status or headers. */
bool m_IsInHeaders;
/** True if the response has been fully parsed. */
bool m_IsFinished;
/** The complete status line of the response. Empty if not parsed yet. */
AString m_StatusLine;
/** 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;
/** Parses the status line out of the m_Buffer.
Removes the status line from m_Buffer, if appropriate.
Returns true if the entire status line has been parsed. */
bool ParseStatusLine(void);
/** Parses the message body.
Processes transfer encoding and calls the callbacks for body data.
Returns the number of bytes from the end of a_Data that is already not part of this response.
Returns AString::npos on 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;
};

309
src/HTTP/HTTPServer.cpp Normal file
View File

@@ -0,0 +1,309 @@
// HTTPServer.cpp
// Implements the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing
#include "Globals.h"
#include "HTTPServer.h"
#include "HTTPRequestParser.h"
#include "HTTPServerConnection.h"
#include "HTTPFormParser.h"
#include "SslHTTPServerConnection.h"
// Disable MSVC warnings:
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4355) // 'this' : used in base member initializer list
#endif
class cDebugCallbacks :
public cHTTPServer::cCallbacks,
protected cHTTPFormParser::cCallbacks
{
virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override
{
UNUSED(a_Connection);
if (cHTTPFormParser::HasFormData(a_Request))
{
a_Request.SetUserData(new cHTTPFormParser(a_Request, *this));
}
}
virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) override
{
UNUSED(a_Connection);
cHTTPFormParser * FormParser = reinterpret_cast<cHTTPFormParser *>(a_Request.GetUserData());
if (FormParser != nullptr)
{
FormParser->Parse(a_Data, a_Size);
}
}
virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override
{
cHTTPFormParser * FormParser = reinterpret_cast<cHTTPFormParser *>(a_Request.GetUserData());
if (FormParser != nullptr)
{
if (FormParser->Finish())
{
cHTTPResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send("<html><body><table border=1 cellspacing=0><tr><th>Name</th><th>Value</th></tr>\r\n");
for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr)
{
a_Connection.Send(Printf("<tr><td valign=\"top\"><pre>%s</pre></td><td valign=\"top\"><pre>%s</pre></td></tr>\r\n", itr->first.c_str(), itr->second.c_str()));
} // for itr - FormParser[]
a_Connection.Send("</table></body></html>");
return;
}
// Parsing failed:
cHTTPResponse Resp;
Resp.SetContentType("text/plain");
a_Connection.Send(Resp);
a_Connection.Send("Form parsing failed");
return;
}
// Test the auth failure and success:
if (a_Request.GetURL() == "/auth")
{
if (!a_Request.HasAuth() || (a_Request.GetAuthUsername() != "a") || (a_Request.GetAuthPassword() != "b"))
{
a_Connection.SendNeedAuth("Cuberite WebAdmin");
return;
}
}
cHTTPResponse Resp;
Resp.SetContentType("text/plain");
a_Connection.Send(Resp);
a_Connection.Send("Hello, world");
}
virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) override
{
// TODO
}
virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, size_t a_Size) override
{
// TODO
}
virtual void OnFileEnd(cHTTPFormParser & a_Parser) override
{
// TODO
}
};
static cDebugCallbacks g_DebugCallbacks;
////////////////////////////////////////////////////////////////////////////////
// cHTTPServerListenCallbacks:
class cHTTPServerListenCallbacks:
public cNetwork::cListenCallbacks
{
public:
cHTTPServerListenCallbacks(cHTTPServer & a_HTTPServer, UInt16 a_Port):
m_HTTPServer(a_HTTPServer),
m_Port(a_Port)
{
}
protected:
/** The HTTP server instance that we're attached to. */
cHTTPServer & m_HTTPServer;
/** The port for which this instance is responsible. */
UInt16 m_Port;
// cNetwork::cListenCallbacks overrides:
virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
{
return m_HTTPServer.OnIncomingConnection(a_RemoteIPAddress, a_RemotePort);
}
virtual void OnAccepted(cTCPLink & a_Link) override {}
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
{
LOGWARNING("HTTP server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str());
}
};
////////////////////////////////////////////////////////////////////////////////
// cHTTPServer:
cHTTPServer::cHTTPServer(void) :
m_Callbacks(nullptr)
{
}
cHTTPServer::~cHTTPServer()
{
Stop();
}
bool cHTTPServer::Initialize(void)
{
// Read the HTTPS cert + key:
AString CertFile = cFile::ReadWholeFile("webadmin/httpscert.crt");
AString KeyFile = cFile::ReadWholeFile("webadmin/httpskey.pem");
if (!CertFile.empty() && !KeyFile.empty())
{
m_Cert.reset(new cX509Cert);
int res = m_Cert->Parse(CertFile.data(), CertFile.size());
if (res == 0)
{
m_CertPrivKey.reset(new cCryptoKey);
int res2 = m_CertPrivKey->ParsePrivate(KeyFile.data(), KeyFile.size(), "");
if (res2 != 0)
{
// Reading the private key failed, reset the cert:
LOGWARNING("WebServer: Cannot read HTTPS certificate private key: -0x%x", -res2);
m_Cert.reset();
}
}
else
{
LOGWARNING("WebServer: Cannot read HTTPS certificate: -0x%x", -res);
}
}
// Notify the admin about the HTTPS / HTTP status
if (m_Cert.get() == nullptr)
{
LOGWARNING("WebServer: The server is running in unsecured HTTP mode.");
LOGINFO("Put a valid HTTPS certificate in file 'webadmin/httpscert.crt' and its corresponding private key to 'webadmin/httpskey.pem' (without any password) to enable HTTPS support");
}
else
{
LOGINFO("WebServer: The server is running in secure HTTPS mode.");
}
return true;
}
bool cHTTPServer::Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports)
{
m_Callbacks = &a_Callbacks;
// Open up requested ports:
for (auto port : a_Ports)
{
UInt16 PortNum;
if (!StringToInteger(port, PortNum))
{
LOGWARNING("WebServer: Invalid port value: \"%s\". Ignoring.", port.c_str());
continue;
}
auto Handle = cNetwork::Listen(PortNum, std::make_shared<cHTTPServerListenCallbacks>(*this, PortNum));
if (Handle->IsListening())
{
m_ServerHandles.push_back(Handle);
}
} // for port - a_Ports[]
// Report success if at least one port opened successfully:
return !m_ServerHandles.empty();
}
void cHTTPServer::Stop(void)
{
for (auto handle : m_ServerHandles)
{
handle->Close();
}
m_ServerHandles.clear();
}
cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort)
{
UNUSED(a_RemoteIPAddress);
UNUSED(a_RemotePort);
if (m_Cert.get() != nullptr)
{
return std::make_shared<cSslHTTPServerConnection>(*this, m_Cert, m_CertPrivKey);
}
else
{
return std::make_shared<cHTTPServerConnection>(*this);
}
}
void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request)
{
m_Callbacks->OnRequestBegun(a_Connection, a_Request);
}
void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size)
{
m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size);
}
void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request)
{
m_Callbacks->OnRequestFinished(a_Connection, a_Request);
a_Connection.AwaitNextRequest();
}

101
src/HTTP/HTTPServer.h Normal file
View File

@@ -0,0 +1,101 @@
// HTTPServer.h
// Declares the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing
#pragma once
#include "../OSSupport/Network.h"
#include "../IniFile.h"
#include "PolarSSL++/RsaPrivateKey.h"
#include "PolarSSL++/CryptoKey.h"
#include "PolarSSL++/X509Cert.h"
// fwd:
class cHTTPMessage;
class cHTTPRequestParser;
class cHTTPResponse;
class cHTTPServerConnection;
class cHTTPServer
{
public:
class cCallbacks
{
public:
virtual ~cCallbacks() {}
/** Called when a new request arrives over a connection and all its headers have been parsed.
The request body needn't have arrived yet. */
virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0;
/** Called when another part of request body has arrived.
May be called multiple times for a single request. */
virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) = 0;
/** Called when the request body has been fully received in previous calls to OnRequestBody() */
virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0;
} ;
cHTTPServer(void);
virtual ~cHTTPServer();
/** Initializes the server - reads the cert files etc. */
bool Initialize(void);
/** Starts the server and assigns the callbacks to use for incoming requests */
bool Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports);
/** Stops the server, drops all current connections */
void Stop(void);
protected:
friend class cHTTPServerConnection;
friend class cSslHTTPServerConnection;
friend class cHTTPServerListenCallbacks;
/** The cNetwork API handle for the listening socket. */
cServerHandlePtrs m_ServerHandles;
/** The callbacks to call for various events */
cCallbacks * m_Callbacks;
/** The server certificate to use for the SSL connections */
cX509CertPtr m_Cert;
/** The private key for m_Cert. */
cCryptoKeyPtr m_CertPrivKey;
/** Called by cHTTPServerListenCallbacks when there's a new incoming connection.
Returns the connection instance to be used as the cTCPLink callbacks. */
cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort);
/** Called by cHTTPServerConnection when it finishes parsing the request header */
void NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request);
/** Called by cHTTPConenction when it receives more data for the request body.
May be called multiple times for a single request. */
void RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size);
/** Called by cHTTPServerConnection when it detects that the request has finished (all of its body has been received) */
void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request);
} ;

View File

@@ -0,0 +1,278 @@
// HTTPConnection.cpp
// Implements the cHTTPServerConnection class representing a single persistent connection in the HTTP server.
#include "Globals.h"
#include "HTTPServerConnection.h"
#include "HTTPRequestParser.h"
#include "HTTPServer.h"
cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) :
m_HTTPServer(a_HTTPServer),
m_State(wcsRecvHeaders),
m_CurrentRequest(nullptr),
m_CurrentRequestBodyRemaining(0)
{
// LOGD("HTTP: New connection at %p", this);
}
cHTTPServerConnection::~cHTTPServerConnection()
{
// LOGD("HTTP: Connection deleting: %p", this);
delete m_CurrentRequest;
m_CurrentRequest = nullptr;
}
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_State = wcsRecvHeaders;
}
void cHTTPServerConnection::SendNeedAuth(const AString & a_Realm)
{
SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str()));
m_State = wcsRecvHeaders;
}
void cHTTPServerConnection::Send(const cHTTPResponse & a_Response)
{
ASSERT(m_State == wcsRecvIdle);
AString toSend;
a_Response.AppendToData(toSend);
m_State = wcsSendingResp;
SendData(toSend);
}
void cHTTPServerConnection::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 cHTTPServerConnection::FinishResponse(void)
{
ASSERT(m_State == wcsSendingResp);
SendData("0\r\n\r\n");
m_State = wcsRecvHeaders;
}
void cHTTPServerConnection::AwaitNextRequest(void)
{
switch (m_State)
{
case wcsRecvHeaders:
{
// Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth())
break;
}
case wcsRecvIdle:
{
// The client is waiting for a response, send an "Internal server error":
SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n");
m_State = wcsRecvHeaders;
break;
}
case wcsSendingResp:
{
// The response headers have been sent, we need to terminate the response body:
SendData("0\r\n\r\n");
m_State = wcsRecvHeaders;
break;
}
default:
{
ASSERT(!"Unhandled state recovery");
break;
}
}
}
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);
switch (m_State)
{
case wcsRecvHeaders:
{
if (m_CurrentRequest == nullptr)
{
m_CurrentRequest = new cHTTPRequestParser;
}
size_t BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size);
if (BytesConsumed == AString::npos)
{
delete m_CurrentRequest;
m_CurrentRequest = nullptr;
m_State = wcsInvalid;
m_Link->Close();
m_Link.reset();
return;
}
if (m_CurrentRequest->IsInHeaders())
{
// The request headers are not yet complete
return;
}
// The request has finished parsing its headers successfully, notify of it:
m_State = wcsRecvBody;
m_HTTPServer.NewRequest(*this, *m_CurrentRequest);
m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength();
if (m_CurrentRequestBodyRemaining == AString::npos)
{
// The body length was not specified in the request, assume zero
m_CurrentRequestBodyRemaining = 0;
}
// Process the rest of the incoming data into the request body:
if (a_Size > BytesConsumed)
{
cHTTPServerConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed);
return;
}
else
{
cHTTPServerConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away
return;
}
}
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 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::SendData(const void * a_Data, size_t a_Size)
{
m_Link->Send(a_Data, a_Size);
}

View File

@@ -0,0 +1,124 @@
// HTTPConnection.h
// Declares the cHTTPConnection class representing a single persistent connection in the HTTP server.
#pragma once
#include "../OSSupport/Network.h"
// fwd:
class cHTTPServer;
class cHTTPResponse;
class cHTTPRequestParser;
class cHTTPServerConnection :
public cTCPLink::cCallbacks
{
public:
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
} ;
cHTTPServerConnection(cHTTPServer & a_HTTPServer);
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. */
void SendStatusAndReason(int a_StatusCode, const AString & a_Reason);
/** Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm */
void SendNeedAuth(const AString & a_Realm);
/** Sends the headers contained in a_Response */
void Send(const cHTTPResponse & a_Response);
/** Sends the data as the response (may be called multiple times) */
void Send(const void * a_Data, size_t a_Size);
/** 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) */
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);
protected:
typedef std::map<AString, AString> cNameValueMap;
/** 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 request being currently received
Valid only between having parsed the headers and finishing receiving the body. */
cHTTPRequestParser * 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;
/** The network link attached to this connection. */
cTCPLinkPtr m_Link;
// cTCPLink::cCallbacks overrides:
/** The link instance has been created, remember it. */
virtual void OnLinkCreated(cTCPLinkPtr a_Link) override;
/** Data is received from the client. */
virtual void OnReceivedData(const char * a_Data, size_t a_Size) override;
/** The socket has been closed for any reason. */
virtual void OnRemoteClosed(void) override;
/** An error has occurred on the socket. */
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) 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);
/** Sends the raw data over the link.
Descendants may provide data transformations (SSL etc.) via the overridable SendData() function. */
void SendData(const AString & a_Data)
{
SendData(a_Data.data(), a_Data.size());
}
} ;
typedef std::vector<cHTTPServerConnection *> cHTTPServerConnections;

View File

@@ -0,0 +1,254 @@
// MultipartParser.cpp
// Implements the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts
#include "Globals.h"
#include "MultipartParser.h"
#include "NameValueParser.h"
// Disable MSVC warnings:
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4355) // 'this' : used in base member initializer list
#endif
////////////////////////////////////////////////////////////////////////////////
// self-test:
#if 0
class cMultipartParserTest :
public cMultipartParser::cCallbacks
{
public:
cMultipartParserTest(void)
{
cMultipartParser Parser("multipart/mixed; boundary=\"MyBoundaryString\"; foo=bar", *this);
const char Data[] =
"ThisIsIgnoredPrologue\r\n\
--MyBoundaryString\r\n\
\r\n\
Body with confusing strings\r\n\
--NotABoundary\r\n\
--MyBoundaryStringWithPostfix\r\n\
--\r\n\
--MyBoundaryString\r\n\
content-disposition: inline\r\n\
\r\n\
This is body\r\n\
--MyBoundaryString\r\n\
\r\n\
Headerless body with trailing CRLF\r\n\
\r\n\
--MyBoundaryString--\r\n\
ThisIsIgnoredEpilogue";
printf("Multipart parsing test commencing.\n");
Parser.Parse(Data, sizeof(Data) - 1);
// DEBUG: Check if the onscreen output corresponds with the data above
printf("Multipart parsing test finished\n");
}
virtual void OnPartStart(void) override
{
printf("Starting a new part\n");
}
virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override
{
printf(" Hdr: \"%s\"=\"%s\"\n", a_Key.c_str(), a_Value.c_str());
}
virtual void OnPartData(const char * a_Data, int a_Size) override
{
printf(" Data: %d bytes, \"%.*s\"\n", a_Size, a_Size, a_Data);
}
virtual void OnPartEnd(void) override
{
printf("Part end\n");
}
} g_Test;
#endif
////////////////////////////////////////////////////////////////////////////////
// cMultipartParser:
cMultipartParser::cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks) :
m_Callbacks(a_Callbacks),
m_IsValid(true),
m_EnvelopeParser(*this),
m_HasHadData(false)
{
// Check that the content type is multipart:
AString ContentType(a_ContentType);
if (strncmp(ContentType.c_str(), "multipart/", 10) != 0)
{
m_IsValid = false;
return;
}
size_t idxSC = ContentType.find(';', 10);
if (idxSC == AString::npos)
{
m_IsValid = false;
return;
}
// Find the multipart boundary:
ContentType.erase(0, idxSC + 1);
cNameValueParser CTParser(ContentType.c_str(), ContentType.size());
CTParser.Finish();
if (!CTParser.IsValid())
{
m_IsValid = false;
return;
}
m_Boundary = CTParser["boundary"];
m_IsValid = !m_Boundary.empty();
if (!m_IsValid)
{
return;
}
// Set the envelope parser for parsing the body, so that our Parse() function parses the ignored prefix data as a body
m_EnvelopeParser.SetIsInHeaders(false);
// Append an initial CRLF to the incoming data, so that a body starting with the boundary line will get caught
m_IncomingData.assign("\r\n");
/*
m_Boundary = AString("\r\n--") + m_Boundary
m_BoundaryEnd = m_Boundary + "--\r\n";
m_Boundary = m_Boundary + "\r\n";
*/
}
void cMultipartParser::Parse(const char * a_Data, size_t a_Size)
{
// Skip parsing if invalid
if (!m_IsValid)
{
return;
}
// Append to buffer, then parse it:
m_IncomingData.append(a_Data, a_Size);
for (;;)
{
if (m_EnvelopeParser.IsInHeaders())
{
size_t BytesConsumed = m_EnvelopeParser.Parse(m_IncomingData.data(), m_IncomingData.size());
if (BytesConsumed == AString::npos)
{
m_IsValid = false;
return;
}
if ((BytesConsumed == a_Size) && m_EnvelopeParser.IsInHeaders())
{
// All the incoming data has been consumed and still waiting for more
return;
}
m_IncomingData.erase(0, BytesConsumed);
}
// Search for boundary / boundary end:
size_t idxBoundary = m_IncomingData.find("\r\n--");
if (idxBoundary == AString::npos)
{
// Boundary string start not present, present as much data to the part callback as possible
if (m_IncomingData.size() > m_Boundary.size() + 8)
{
size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8;
m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport);
m_IncomingData.erase(0, BytesToReport);
}
return;
}
if (idxBoundary > 0)
{
m_Callbacks.OnPartData(m_IncomingData.data(), idxBoundary);
m_IncomingData.erase(0, idxBoundary);
}
idxBoundary = 4;
size_t LineEnd = m_IncomingData.find("\r\n", idxBoundary);
if (LineEnd == AString::npos)
{
// Not a complete line yet, present as much data to the part callback as possible
if (m_IncomingData.size() > m_Boundary.size() + 8)
{
size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8;
m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport);
m_IncomingData.erase(0, BytesToReport);
}
return;
}
if (
(LineEnd - idxBoundary != m_Boundary.size()) && // Line length not equal to boundary
(LineEnd - idxBoundary != m_Boundary.size() + 2) // Line length not equal to boundary end
)
{
// Got a line, but it's not a boundary, report it as data:
m_Callbacks.OnPartData(m_IncomingData.data(), LineEnd);
m_IncomingData.erase(0, LineEnd);
continue;
}
if (strncmp(m_IncomingData.c_str() + idxBoundary, m_Boundary.c_str(), m_Boundary.size()) == 0)
{
// Boundary or BoundaryEnd found:
m_Callbacks.OnPartEnd();
size_t idxSlash = idxBoundary + m_Boundary.size();
if ((m_IncomingData[idxSlash] == '-') && (m_IncomingData[idxSlash + 1] == '-'))
{
// This was the last part
m_Callbacks.OnPartData(m_IncomingData.data() + idxSlash + 4, m_IncomingData.size() - idxSlash - 4);
m_IncomingData.clear();
return;
}
m_Callbacks.OnPartStart();
m_IncomingData.erase(0, LineEnd + 2);
// Keep parsing for the headers that may have come with this data:
m_EnvelopeParser.Reset();
continue;
}
// It's a line, but not a boundary. It can be fully sent to the data receiver, since a boundary cannot cross lines
m_Callbacks.OnPartData(m_IncomingData.c_str(), LineEnd);
m_IncomingData.erase(0, LineEnd);
} // while (true)
}
void cMultipartParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
{
m_Callbacks.OnPartHeader(a_Key, a_Value);
}

View File

@@ -0,0 +1,79 @@
// MultipartParser.h
// Declares the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts
#pragma once
#include "EnvelopeParser.h"
class cMultipartParser :
protected cEnvelopeParser::cCallbacks
{
public:
class cCallbacks
{
public:
// Force a virtual destructor in descendants:
virtual ~cCallbacks() {}
/** Called when a new part starts */
virtual void OnPartStart(void) = 0;
/** Called when a complete header line is received for a part */
virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) = 0;
/** Called when body for a part is received */
virtual void OnPartData(const char * a_Data, size_t a_Size) = 0;
/** Called when the current part ends */
virtual void OnPartEnd(void) = 0;
} ;
/** Creates the parser, expects to find the boundary in a_ContentType */
cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks);
/** Parses more incoming data */
void Parse(const char * a_Data, size_t a_Size);
protected:
/** The callbacks to call for various parsing events */
cCallbacks & m_Callbacks;
/** True if the data parsed so far is valid; if false, further parsing is skipped */
bool m_IsValid;
/** Parser for each part's envelope */
cEnvelopeParser m_EnvelopeParser;
/** Buffer for the incoming data until it is parsed */
AString m_IncomingData;
/** The boundary, excluding both the initial "--" and the terminating CRLF */
AString m_Boundary;
/** Set to true if some data for the current part has already been signalized to m_Callbacks. Used for proper CRLF inserting. */
bool m_HasHadData;
/** Parse one line of incoming data. The CRLF has already been stripped from a_Data / a_Size */
void ParseLine(const char * a_Data, size_t a_Size);
/** Parse one line of incoming data in the headers section of a part. The CRLF has already been stripped from a_Data / a_Size */
void ParseHeaderLine(const char * a_Data, size_t a_Size);
// cEnvelopeParser overrides:
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
} ;

View File

@@ -0,0 +1,413 @@
// NameValueParser.cpp
// Implements the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap
#include "Globals.h"
#include "NameValueParser.h"
// DEBUG: Self-test
#if 0
class cNameValueParserTest
{
public:
cNameValueParserTest(void)
{
const char Data[] = " Name1=Value1;Name2 = Value 2; Name3 =\"Value 3\"; Name4 =\'Value 4\'; Name5=\"Confusing; isn\'t it?\"";
// Now try parsing char-by-char, to debug transitions across datachunk boundaries:
cNameValueParser Parser2;
for (size_t i = 0; i < sizeof(Data) - 1; i++)
{
Parser2.Parse(Data + i, 1);
}
Parser2.Finish();
// Parse as a single chunk of data:
cNameValueParser Parser(Data, sizeof(Data) - 1);
// Use the debugger to inspect the Parser variable
// Check that the two parsers have the same content:
for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr)
{
ASSERT(Parser2[itr->first] == itr->second);
} // for itr - Parser[]
// Try parsing in 2-char chunks:
cNameValueParser Parser3;
for (int i = 0; i < sizeof(Data) - 2; i += 2)
{
Parser3.Parse(Data + i, 2);
}
if ((sizeof(Data) % 2) == 0) // There are even number of chars, including the NUL, so the data has an odd length. Parse one more char
{
Parser3.Parse(Data + sizeof(Data) - 2, 1);
}
Parser3.Finish();
// Check that the third parser has the same content:
for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr)
{
ASSERT(Parser3[itr->first] == itr->second);
} // for itr - Parser[]
printf("cNameValueParserTest done");
}
} g_Test;
#endif
////////////////////////////////////////////////////////////////////////////////
// cNameValueParser:
cNameValueParser::cNameValueParser(bool a_AllowsKeyOnly) :
m_State(psKeySpace),
m_AllowsKeyOnly(a_AllowsKeyOnly)
{
}
cNameValueParser::cNameValueParser(const char * a_Data, size_t a_Size, bool a_AllowsKeyOnly) :
m_State(psKeySpace),
m_AllowsKeyOnly(a_AllowsKeyOnly)
{
Parse(a_Data, a_Size);
}
void cNameValueParser::Parse(const char * a_Data, size_t a_Size)
{
ASSERT(m_State != psFinished); // Calling Parse() after Finish() is wrong!
size_t Last = 0;
for (size_t i = 0; i < a_Size;)
{
switch (m_State)
{
case psInvalid:
case psFinished:
{
return;
}
case psKeySpace:
{
// Skip whitespace until a non-whitespace is found, then start the key:
while ((i < a_Size) && (a_Data[i] <= ' '))
{
i++;
}
if ((i < a_Size) && (a_Data[i] > ' '))
{
m_State = psKey;
Last = i;
}
break;
}
case psKey:
{
// Read the key until whitespace or an equal sign:
while (i < a_Size)
{
if (a_Data[i] == '=')
{
m_CurrentKey.append(a_Data + Last, i - Last);
i++;
Last = i;
m_State = psEqual;
break;
}
else if (a_Data[i] <= ' ')
{
m_CurrentKey.append(a_Data + Last, i - Last);
i++;
Last = i;
m_State = psEqualSpace;
break;
}
else if (a_Data[i] == ';')
{
if (!m_AllowsKeyOnly)
{
m_State = psInvalid;
return;
}
m_CurrentKey.append(a_Data + Last, i - Last);
i++;
Last = i;
(*this)[m_CurrentKey] = "";
m_CurrentKey.clear();
m_State = psKeySpace;
break;
}
else if ((a_Data[i] == '\"') || (a_Data[i] == '\''))
{
m_State = psInvalid;
return;
}
i++;
} // while (i < a_Size)
if (i == a_Size)
{
// Still the key, ran out of data to parse, store the part of the key parsed so far:
m_CurrentKey.append(a_Data + Last, a_Size - Last);
return;
}
break;
}
case psEqualSpace:
{
// The space before the expected equal sign; the current key is already assigned
while (i < a_Size)
{
if (a_Data[i] == '=')
{
m_State = psEqual;
i++;
Last = i;
break;
}
else if (a_Data[i] == ';')
{
// Key-only
if (!m_AllowsKeyOnly)
{
m_State = psInvalid;
return;
}
i++;
Last = i;
(*this)[m_CurrentKey] = "";
m_CurrentKey.clear();
m_State = psKeySpace;
break;
}
else if (a_Data[i] > ' ')
{
m_State = psInvalid;
return;
}
i++;
} // while (i < a_Size)
break;
} // case psEqualSpace
case psEqual:
{
// just parsed the equal-sign
while (i < a_Size)
{
if (a_Data[i] == ';')
{
if (!m_AllowsKeyOnly)
{
m_State = psInvalid;
return;
}
i++;
Last = i;
(*this)[m_CurrentKey] = "";
m_CurrentKey.clear();
m_State = psKeySpace;
break;
}
else if (a_Data[i] == '\"')
{
i++;
Last = i;
m_State = psValueInDQuotes;
break;
}
else if (a_Data[i] == '\'')
{
i++;
Last = i;
m_State = psValueInSQuotes;
break;
}
else
{
m_CurrentValue.push_back(a_Data[i]);
i++;
Last = i;
m_State = psValueRaw;
break;
}
} // while (i < a_Size)
break;
} // case psEqual
case psValueInDQuotes:
{
while (i < a_Size)
{
if (a_Data[i] == '\"')
{
m_CurrentValue.append(a_Data + Last, i - Last);
(*this)[m_CurrentKey] = m_CurrentValue;
m_CurrentKey.clear();
m_CurrentValue.clear();
m_State = psAfterValue;
i++;
Last = i;
break;
}
i++;
} // while (i < a_Size)
if (i == a_Size)
{
m_CurrentValue.append(a_Data + Last, a_Size - Last);
}
break;
} // case psValueInDQuotes
case psValueInSQuotes:
{
while (i < a_Size)
{
if (a_Data[i] == '\'')
{
m_CurrentValue.append(a_Data + Last, i - Last);
(*this)[m_CurrentKey] = m_CurrentValue;
m_CurrentKey.clear();
m_CurrentValue.clear();
m_State = psAfterValue;
i++;
Last = i;
break;
}
i++;
} // while (i < a_Size)
if (i == a_Size)
{
m_CurrentValue.append(a_Data + Last, a_Size - Last);
}
break;
} // case psValueInSQuotes
case psValueRaw:
{
while (i < a_Size)
{
if (a_Data[i] == ';')
{
m_CurrentValue.append(a_Data + Last, i - Last);
(*this)[m_CurrentKey] = m_CurrentValue;
m_CurrentKey.clear();
m_CurrentValue.clear();
m_State = psKeySpace;
i++;
Last = i;
break;
}
i++;
}
if (i == a_Size)
{
m_CurrentValue.append(a_Data + Last, a_Size - Last);
}
break;
} // case psValueRaw
case psAfterValue:
{
// Between the closing DQuote or SQuote and the terminating semicolon
while (i < a_Size)
{
if (a_Data[i] == ';')
{
m_State = psKeySpace;
i++;
Last = i;
break;
}
else if (a_Data[i] < ' ')
{
i++;
continue;
}
m_State = psInvalid;
return;
} // while (i < a_Size)
break;
}
} // switch (m_State)
} // for i - a_Data[]
}
bool cNameValueParser::Finish(void)
{
switch (m_State)
{
case psInvalid:
{
return false;
}
case psFinished:
{
return true;
}
case psKey:
case psEqualSpace:
case psEqual:
{
if ((m_AllowsKeyOnly) && !m_CurrentKey.empty())
{
(*this)[m_CurrentKey] = "";
m_State = psFinished;
return true;
}
m_State = psInvalid;
return false;
}
case psValueRaw:
{
(*this)[m_CurrentKey] = m_CurrentValue;
m_State = psFinished;
return true;
}
case psValueInDQuotes:
case psValueInSQuotes:
{
// Missing the terminating quotes, this is an error
m_State = psInvalid;
return false;
}
case psKeySpace:
case psAfterValue:
{
m_State = psFinished;
return true;
}
}
ASSERT(!"Unhandled parser state!");
return false;
}

View File

@@ -0,0 +1,70 @@
// NameValueParser.h
// Declares the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap
#pragma once
class cNameValueParser :
public std::map<AString, AString>
{
public:
/** Creates an empty parser */
cNameValueParser(bool a_AllowsKeyOnly = true);
/** Creates an empty parser, then parses the data given. Doesn't call Finish(), so more data can be parsed later */
cNameValueParser(const char * a_Data, size_t a_Size, bool a_AllowsKeyOnly = true);
/** Parses the data given */
void Parse(const char * a_Data, size_t a_Size);
/** Notifies the parser that no more data will be coming. Returns true if the parser state is valid */
bool Finish(void);
/** Returns true if the data parsed so far was valid */
bool IsValid(void) const { return (m_State != psInvalid); }
/** Returns true if the parser expects no more data */
bool IsFinished(void) const { return ((m_State == psInvalid) || (m_State == psFinished)); }
protected:
enum eState
{
psKeySpace, ///< Parsing the space in front of the next key
psKey, ///< Currently adding more chars to the key in m_CurrentKey
psEqualSpace, ///< Space after m_CurrentKey
psEqual, ///< Just parsed the = sign after a name
psValueInSQuotes, ///< Just parsed a Single-quote sign after the Equal sign
psValueInDQuotes, ///< Just parsed a Double-quote sign after the Equal sign
psValueRaw, ///< Just parsed a raw value without a quote
psAfterValue, ///< Just finished parsing the value, waiting for semicolon or data end
psInvalid, ///< The parser has encountered an invalid input; further parsing is skipped
psFinished, ///< The parser has already been instructed to finish and doesn't expect any more data
} ;
/** The current state of the parser */
eState m_State;
/** If true, the parser will accept keys without an equal sign and the value */
bool m_AllowsKeyOnly;
/** Buffer for the current Key */
AString m_CurrentKey;
/** Buffer for the current Value; */
AString m_CurrentValue;
} ;

View File

@@ -0,0 +1,115 @@
// SslHTTPConnection.cpp
// Implements the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link
#include "Globals.h"
#include "SslHTTPServerConnection.h"
#include "HTTPServer.h"
cSslHTTPServerConnection::cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) :
super(a_HTTPServer),
m_Ssl(64000),
m_Cert(a_Cert),
m_PrivateKey(a_PrivateKey)
{
m_Ssl.Initialize(false);
m_Ssl.SetOwnCert(a_Cert, a_PrivateKey);
}
cSslHTTPServerConnection::~cSslHTTPServerConnection()
{
m_Ssl.NotifyClose();
}
void cSslHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
// Process the received data:
const char * Data = a_Data;
size_t Size = a_Size;
for (;;)
{
// Try to write as many bytes into Ssl's "incoming" buffer as possible:
size_t BytesWritten = 0;
if (Size > 0)
{
BytesWritten = m_Ssl.WriteIncoming(Data, Size);
Data += BytesWritten;
Size -= BytesWritten;
}
// Try to read as many bytes from SSL's decryption as possible:
char Buffer[32000];
int NumRead = m_Ssl.ReadPlain(Buffer, sizeof(Buffer));
if (NumRead > 0)
{
super::OnReceivedData(Buffer, static_cast<size_t>(NumRead));
// The link may have closed while processing the data, bail out:
return;
}
else if (NumRead == POLARSSL_ERR_NET_WANT_READ)
{
// SSL requires us to send data to peer first, do so by "sending" empty data:
SendData(nullptr, 0);
}
// If both failed, bail out:
if ((BytesWritten == 0) && (NumRead <= 0))
{
return;
}
}
}
void cSslHTTPServerConnection::SendData(const void * a_Data, size_t a_Size)
{
const char * OutgoingData = reinterpret_cast<const char *>(a_Data);
size_t pos = 0;
for (;;)
{
// Write as many bytes from our buffer to SSL's encryption as possible:
int NumWritten = 0;
if (pos < a_Size)
{
NumWritten = m_Ssl.WritePlain(OutgoingData + pos, a_Size - pos);
if (NumWritten > 0)
{
pos += static_cast<size_t>(NumWritten);
}
}
// Read as many bytes from SSL's "outgoing" buffer as possible:
char Buffer[32000];
size_t NumBytes = m_Ssl.ReadOutgoing(Buffer, sizeof(Buffer));
if (NumBytes > 0)
{
m_Link->Send(Buffer, NumBytes);
}
// If both failed, bail out:
if ((NumWritten <= 0) && (NumBytes == 0))
{
return;
}
}
}

View File

@@ -0,0 +1,47 @@
// SslHTTPServerConnection.h
// Declared the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link
#pragma once
#include "HTTPServerConnection.h"
#include "PolarSSL++/BufferedSslContext.h"
class cSslHTTPServerConnection :
public cHTTPServerConnection
{
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. */
cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey);
~cSslHTTPServerConnection();
protected:
cBufferedSslContext m_Ssl;
/** The certificate to send to the client */
cX509CertPtr m_Cert;
/** The private key used for the certificate */
cCryptoKeyPtr m_PrivateKey;
// cHTTPConnection overrides:
virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; // Data is received from the client
virtual void SendData(const void * a_Data, size_t a_Size) override; // Data is to be sent to client
} ;

View File

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

View File

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

200
src/HTTP/UrlParser.cpp Normal file
View File

@@ -0,0 +1,200 @@
// UrlParser.cpp
// Implements the cUrlParser class that parses string URL into individual parts
#include "Globals.h"
#include "UrlParser.h"
UInt16 cUrlParser::GetDefaultPort(const AString & a_Scheme)
{
if (a_Scheme == "http")
{
return 80;
}
else if (a_Scheme == "https")
{
return 443;
}
else if (a_Scheme == "ftp")
{
return 21;
}
else if (a_Scheme == "mailto")
{
return 25;
}
return 0;
}
std::pair<bool, AString> cUrlParser::ParseAuthorityPart(
const AString & a_AuthorityPart,
AString & a_Username,
AString & a_Password,
AString & a_Host,
UInt16 & a_Port
)
{
/*
a_AuthorityPart format:
[user:password@]host[:port]
host can be an IPv4, hostname, or an IPv6 enclosed in brackets
Assume only the password can contain an additional at-sign
*/
// Split the authority on the last at-sign, if present:
auto idxLastAtSign = a_AuthorityPart.find_last_of('@');
auto credPart = (idxLastAtSign == AString::npos) ? AString() : a_AuthorityPart.substr(0, idxLastAtSign);
auto srvrPart = (idxLastAtSign == AString::npos) ? a_AuthorityPart : a_AuthorityPart.substr(idxLastAtSign + 1);
// User credentials are completely optional:
auto idxCredColon = credPart.find(':');
a_Username = credPart.substr(0, idxCredColon);
a_Password = (idxCredColon == AString::npos) ? AString() : credPart.substr(idxCredColon + 1);
// Host can be a hostname, IPv4 or [IPv6]. If in brackets, search for the closing bracket first
if (srvrPart.empty())
{
// No host information at all. Bail out with success
a_Host.clear();
return std::make_pair(true, AString());
}
if (srvrPart[0] == '[')
{
// [IPv6] host, search for the closing bracket
auto idxClosingBracket = srvrPart.find(']');
if (idxClosingBracket == AString::npos)
{
return std::make_pair(false, "Invalid IPv6-like address, missing closing bracket");
}
a_Host = srvrPart.substr(0, idxClosingBracket);
auto portPart = srvrPart.substr(idxClosingBracket + 1);
if (portPart.empty())
{
// No port was specified, return success
return std::make_pair(true, AString());
}
if (portPart[0] != ':')
{
return std::make_pair(false, "Invalid port format after IPv6 address, mising colon");
}
if (!StringToInteger(portPart.substr(2), a_Port))
{
return std::make_pair(false, "Failed to parse port number after IPv6 address");
}
return std::make_pair(true, AString());
}
// Not an [IPv6] address, split on the last colon:
auto idxLastColon = srvrPart.find_last_of(':');
a_Host = srvrPart.substr(0, idxLastColon);
if (idxLastColon == AString::npos)
{
// No port was specified, return success
return std::make_pair(true, AString());
}
auto portPart = srvrPart.substr(idxLastColon + 1);
if (!StringToInteger(portPart, a_Port))
{
return std::make_pair(false, "Failed to parse port number after hostname");
}
return std::make_pair(true, AString());
}
std::pair<bool, AString> cUrlParser::Parse(
const AString & a_Url,
AString & a_Scheme,
AString & a_Username,
AString & a_Password,
AString & a_Host,
UInt16 & a_Port,
AString & a_Path,
AString & a_Query,
AString & a_Fragment
)
{
// Find the scheme - the text before the first colon:
auto idxColon = a_Url.find(':');
if (idxColon == AString::npos)
{
return std::make_pair(false, "Cannot parse the Scheme part of the URL");
}
a_Scheme = StrToLower(a_Url.substr(0, idxColon));
a_Port = GetDefaultPort(a_Scheme);
if (a_Port == 0)
{
return std::make_pair(false, Printf("Unknown URL scheme: \"%s\"", a_Scheme.c_str()));
}
// If the next two chars are a double-slash, skip them:
auto authStart = idxColon + 1;
if (a_Url.substr(authStart, 2) == "//")
{
authStart += 2;
}
// The Authority part follows the Scheme, until the first slash:
auto idxFirstSlash = a_Url.find('/', authStart + 1);
if (idxFirstSlash == AString::npos)
{
// No slash, the whole end of the Url is the authority part
idxFirstSlash = a_Url.size();
}
// Parse the Authority part into individual components:
auto res = ParseAuthorityPart(
a_Url.substr(authStart, idxFirstSlash - authStart),
a_Username, a_Password,
a_Host, a_Port
);
if (!res.first)
{
return res;
}
// Parse the rest into a path, query and fragment:
a_Path.clear();
a_Query.clear();
a_Fragment.clear();
if (idxFirstSlash == a_Url.size())
{
// No additional data, bail out with success
return std::make_pair(true, AString());
}
auto idxPathEnd = a_Url.find_first_of("?#", idxFirstSlash + 1);
if (idxPathEnd == AString::npos)
{
a_Path = a_Url.substr(idxFirstSlash);
return std::make_pair(true, AString());
}
a_Path = a_Url.substr(idxFirstSlash, idxPathEnd - idxFirstSlash);
auto idxHash = a_Url.find('#', idxPathEnd);
if (idxHash == AString::npos)
{
a_Query = a_Url.substr(idxPathEnd + 1);
return std::make_pair(true, AString());
}
if (idxHash > idxPathEnd)
{
a_Query = a_Url.substr(idxPathEnd + 1, idxHash - idxPathEnd - 1);
}
a_Fragment = a_Url.substr(idxHash + 1);
return std::make_pair(true, AString());
}

58
src/HTTP/UrlParser.h Normal file
View File

@@ -0,0 +1,58 @@
// UrlParser.h
// Declares the cUrlParser class that parses string URL into individual parts
#pragma once
class cUrlParser
{
public:
/** Returns true if the specified scheme (http, ftp, mailto, ...) is recognized by the URL parser.
Is case sensitive, known schemes are always lowercase. */
static bool IsKnownScheme(const AString & a_Scheme) { return (GetDefaultPort(a_Scheme) > 0); }
/** Returns the default port used by the specified scheme / protocol.
If the scheme is not known, 0 is returned. */
static UInt16 GetDefaultPort(const AString & a_Scheme);
/** Parses the given Authority part of an URL into individual components.
Returns true on success,
returns false and error message on failure. */
static std::pair<bool, AString> ParseAuthorityPart(
const AString & a_AuthorityPart,
AString & a_Username,
AString & a_Password,
AString & a_Host,
UInt16 & a_Port
);
/** Parses the given URL into individual components.
Returns true on success,
returns false and error message on failure.
Fails if the scheme (protocol) is not known.
If port is missing, the default port for the specific scheme is applied. */
static std::pair<bool, AString> Parse(
const AString & a_Url,
AString & a_Scheme,
AString & a_Username,
AString & a_Password,
AString & a_Host,
UInt16 & a_Port,
AString & a_Path,
AString & a_Query,
AString & a_Fragment
);
};