Renamed HTTPServer folder to HTTP.
It contains client code as well.
This commit is contained in:
45
src/HTTP/CMakeLists.txt
Normal file
45
src/HTTP/CMakeLists.txt
Normal 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
132
src/HTTP/EnvelopeParser.cpp
Normal 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
73
src/HTTP/EnvelopeParser.h
Normal 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
293
src/HTTP/HTTPFormParser.cpp
Normal 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
114
src/HTTP/HTTPFormParser.h
Normal 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
102
src/HTTP/HTTPMessage.cpp
Normal 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
84
src/HTTP/HTTPMessage.h
Normal 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;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
192
src/HTTP/HTTPRequestParser.cpp
Normal file
192
src/HTTP/HTTPRequestParser.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
109
src/HTTP/HTTPRequestParser.h
Normal file
109
src/HTTP/HTTPRequestParser.h
Normal 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;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
183
src/HTTP/HTTPResponseParser.cpp
Normal file
183
src/HTTP/HTTPResponseParser.cpp
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
118
src/HTTP/HTTPResponseParser.h
Normal file
118
src/HTTP/HTTPResponseParser.h
Normal 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
309
src/HTTP/HTTPServer.cpp
Normal 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
101
src/HTTP/HTTPServer.h
Normal 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);
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
278
src/HTTP/HTTPServerConnection.cpp
Normal file
278
src/HTTP/HTTPServerConnection.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
124
src/HTTP/HTTPServerConnection.h
Normal file
124
src/HTTP/HTTPServerConnection.h
Normal 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;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
254
src/HTTP/MultipartParser.cpp
Normal file
254
src/HTTP/MultipartParser.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
79
src/HTTP/MultipartParser.h
Normal file
79
src/HTTP/MultipartParser.h
Normal 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;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
413
src/HTTP/NameValueParser.cpp
Normal file
413
src/HTTP/NameValueParser.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
70
src/HTTP/NameValueParser.h
Normal file
70
src/HTTP/NameValueParser.h
Normal 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;
|
||||
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
115
src/HTTP/SslHTTPServerConnection.cpp
Normal file
115
src/HTTP/SslHTTPServerConnection.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
47
src/HTTP/SslHTTPServerConnection.h
Normal file
47
src/HTTP/SslHTTPServerConnection.h
Normal 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
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
393
src/HTTP/TransferEncodingParser.cpp
Normal file
393
src/HTTP/TransferEncodingParser.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
200
src/HTTP/UrlParser.cpp
Normal file
200
src/HTTP/UrlParser.cpp
Normal 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
58
src/HTTP/UrlParser.h
Normal 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
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user