summaryrefslogtreecommitdiff
path: root/lib/httpserver
diff options
context:
space:
mode:
Diffstat (limited to 'lib/httpserver')
-rw-r--r--lib/httpserver/HTTPException.txt19
-rw-r--r--lib/httpserver/HTTPQueryDecoder.cpp7
-rw-r--r--lib/httpserver/HTTPRequest.cpp112
-rw-r--r--lib/httpserver/HTTPRequest.h28
-rw-r--r--lib/httpserver/HTTPResponse.cpp242
-rw-r--r--lib/httpserver/HTTPResponse.h15
6 files changed, 372 insertions, 51 deletions
diff --git a/lib/httpserver/HTTPException.txt b/lib/httpserver/HTTPException.txt
index f31f323d..dfc5533e 100644
--- a/lib/httpserver/HTTPException.txt
+++ b/lib/httpserver/HTTPException.txt
@@ -1,12 +1,15 @@
EXCEPTION HTTP 10
Internal 0
-RequestReadFailed 1
-RequestAlreadyBeenRead 2
+RequestReadFailed 1
+RequestAlreadyBeenRead 2
BadRequest 3
-UnknownResponseCodeUsed 4
-NoContentTypeSet 5
-POSTContentTooLong 6
-CannotSetRedirectIfReponseHasData 7
-CannotSetNotFoundIfReponseHasData 8
-NotImplemented 9
+UnknownResponseCodeUsed 4
+NoContentTypeSet 5
+POSTContentTooLong 6
+CannotSetRedirectIfReponseHasData 7
+CannotSetNotFoundIfReponseHasData 8
+NotImplemented 9
+RequestNotInitialised 10
+BadResponse 11
+ResponseReadFailed 12
diff --git a/lib/httpserver/HTTPQueryDecoder.cpp b/lib/httpserver/HTTPQueryDecoder.cpp
index 5315d4ce..c49ac2ce 100644
--- a/lib/httpserver/HTTPQueryDecoder.cpp
+++ b/lib/httpserver/HTTPQueryDecoder.cpp
@@ -19,9 +19,10 @@
// --------------------------------------------------------------------------
//
// Function
-// Name: HTTPQueryDecoder::HTTPQueryDecoder(HTTPRequest::Query_t &)
-// Purpose: Constructor. Pass in the query contents you want to decode
-// the query string into.
+// Name: HTTPQueryDecoder::HTTPQueryDecoder(
+// HTTPRequest::Query_t &)
+// Purpose: Constructor. Pass in the query contents you want
+// to decode the query string into.
// Created: 26/3/04
//
// --------------------------------------------------------------------------
diff --git a/lib/httpserver/HTTPRequest.cpp b/lib/httpserver/HTTPRequest.cpp
index 4c03e6e8..a14f1eb3 100644
--- a/lib/httpserver/HTTPRequest.cpp
+++ b/lib/httpserver/HTTPRequest.cpp
@@ -52,6 +52,28 @@ HTTPRequest::HTTPRequest()
// --------------------------------------------------------------------------
//
// Function
+// Name: HTTPRequest::HTTPRequest(enum Method,
+// const std::string&)
+// Purpose: Alternate constructor for hand-crafted requests
+// Created: 03/01/09
+//
+// --------------------------------------------------------------------------
+HTTPRequest::HTTPRequest(enum Method method, const std::string& rURI)
+ : mMethod(method),
+ mRequestURI(rURI),
+ mHostPort(80), // default if not specified
+ mHTTPVersion(HTTPVersion_1_1),
+ mContentLength(-1),
+ mpCookies(0),
+ mClientKeepAliveRequested(false)
+{
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
// Name: HTTPRequest::~HTTPRequest()
// Purpose: Destructor
// Created: 26/3/04
@@ -72,9 +94,10 @@ HTTPRequest::~HTTPRequest()
//
// Function
// Name: HTTPRequest::Read(IOStreamGetLine &, int)
-// Purpose: Read the request from an IOStreamGetLine (and attached stream)
-// Returns false if there was no valid request, probably due to
-// a kept-alive connection closing.
+// Purpose: Read the request from an IOStreamGetLine (and
+// attached stream).
+// Returns false if there was no valid request,
+// probably due to a kept-alive connection closing.
// Created: 26/3/04
//
// --------------------------------------------------------------------------
@@ -289,6 +312,89 @@ bool HTTPRequest::Read(IOStreamGetLine &rGetLine, int Timeout)
// --------------------------------------------------------------------------
//
// Function
+// Name: HTTPRequest::Write(IOStream &, int)
+// Purpose: Write the request to an IOStream using HTTP.
+// Created: 03/01/09
+//
+// --------------------------------------------------------------------------
+bool HTTPRequest::Write(IOStream &rStream, int Timeout)
+{
+ switch (mMethod)
+ {
+ case Method_UNINITIALISED:
+ THROW_EXCEPTION(HTTPException, RequestNotInitialised); break;
+ case Method_UNKNOWN:
+ THROW_EXCEPTION(HTTPException, BadRequest); break;
+ case Method_GET:
+ rStream.Write("GET"); break;
+ case Method_HEAD:
+ rStream.Write("HEAD"); break;
+ case Method_POST:
+ rStream.Write("POST"); break;
+ }
+
+ rStream.Write(" ");
+ rStream.Write(mRequestURI.c_str());
+ rStream.Write(" ");
+
+ switch (mHTTPVersion)
+ {
+ case HTTPVersion_0_9: rStream.Write("HTTP/0.9"); break;
+ case HTTPVersion_1_0: rStream.Write("HTTP/1.0"); break;
+ case HTTPVersion_1_1: rStream.Write("HTTP/1.1"); break;
+ default:
+ THROW_EXCEPTION(HTTPException, NotImplemented);
+ }
+
+ rStream.Write("\n");
+ std::ostringstream oss;
+
+ if (mContentLength != -1)
+ {
+ oss << "Content-Length: " << mContentLength << "\n";
+ }
+
+ if (mContentType != "")
+ {
+ oss << "Content-Type: " << mContentType << "\n";
+ }
+
+ if (mHostName != "")
+ {
+ if (mHostPort != 80)
+ {
+ oss << "Host: " << mHostName << ":" << mHostPort <<
+ "\n";
+ }
+ else
+ {
+ oss << "Host: " << mHostName << "\n";
+ }
+ }
+
+ if (mpCookies)
+ {
+ THROW_EXCEPTION(HTTPException, NotImplemented);
+ }
+
+ if (mClientKeepAliveRequested)
+ {
+ oss << "Connection: keep-alive\n";
+ }
+ else
+ {
+ oss << "Connection: close\n";
+ }
+
+ rStream.Write(oss.str().c_str());
+ rStream.Write("\n");
+
+ return true;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
// Name: HTTPRequest::ParseHeaders(IOStreamGetLine &, int)
// Purpose: Private. Parse the headers of the request
// Created: 26/3/04
diff --git a/lib/httpserver/HTTPRequest.h b/lib/httpserver/HTTPRequest.h
index 142c8a1c..501bce53 100644
--- a/lib/httpserver/HTTPRequest.h
+++ b/lib/httpserver/HTTPRequest.h
@@ -27,7 +27,17 @@ class IOStreamGetLine;
class HTTPRequest
{
public:
+ enum Method
+ {
+ Method_UNINITIALISED = -1,
+ Method_UNKNOWN = 0,
+ Method_GET = 1,
+ Method_HEAD = 2,
+ Method_POST = 3
+ };
+
HTTPRequest();
+ HTTPRequest(enum Method method, const std::string& rURI);
~HTTPRequest();
private:
// no copying
@@ -40,15 +50,6 @@ public:
enum
{
- Method_UNINITIALISED = -1,
- Method_UNKNOWN = 0,
- Method_GET = 1,
- Method_HEAD = 2,
- Method_POST = 3
- };
-
- enum
- {
HTTPVersion__MajorMultiplier = 1000,
HTTPVersion_0_9 = 9,
HTTPVersion_1_0 = 1000,
@@ -56,6 +57,7 @@ public:
};
bool Read(IOStreamGetLine &rGetLine, int Timeout);
+ bool Write(IOStream &rStream, int Timeout);
typedef std::map<std::string, std::string> CookieJar_t;
@@ -67,7 +69,7 @@ public:
// Created: 26/3/04
//
// --------------------------------------------------------------------------
- int GetMethod() const {return mMethod;}
+ enum Method GetMethod() const {return mMethod;}
const std::string &GetRequestURI() const {return mRequestURI;}
const std::string &GetHostName() const {return mHostName;} // note: request does splitting of Host: header
const int GetHostPort() const {return mHostPort;} // into host name and port number
@@ -91,13 +93,17 @@ public:
//
// --------------------------------------------------------------------------
bool GetClientKeepAliveRequested() const {return mClientKeepAliveRequested;}
+ void SetClientKeepAliveRequested(bool keepAlive)
+ {
+ mClientKeepAliveRequested = keepAlive;
+ }
private:
void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout);
void ParseCookies(const std::string &rHeader, int DataStarts);
private:
- int mMethod;
+ enum Method mMethod;
std::string mRequestURI;
std::string mHostName;
int mHostPort;
diff --git a/lib/httpserver/HTTPResponse.cpp b/lib/httpserver/HTTPResponse.cpp
index 29efb471..c7235695 100644
--- a/lib/httpserver/HTTPResponse.cpp
+++ b/lib/httpserver/HTTPResponse.cpp
@@ -13,6 +13,7 @@
#include <string.h>
#include "HTTPResponse.h"
+#include "IOStreamGetLine.h"
#include "autogen_HTTPException.h"
#include "MemLeakFindOn.h"
@@ -32,7 +33,8 @@ std::string HTTPResponse::msDefaultURIPrefix;
HTTPResponse::HTTPResponse()
: mResponseCode(HTTPResponse::Code_NoContent),
mResponseIsDynamicContent(true),
- mKeepAlive(false)
+ mKeepAlive(false),
+ mContentLength(-1)
{
}
@@ -54,7 +56,8 @@ HTTPResponse::~HTTPResponse()
//
// Function
// Name: HTTPResponse::ResponseCodeToString(int)
-// Purpose: Return string equivalent of the response code, suitable for Status: headers
+// Purpose: Return string equivalent of the response code,
+// suitable for Status: headers
// Created: 26/3/04
//
// --------------------------------------------------------------------------
@@ -114,8 +117,8 @@ void HTTPResponse::SetContentType(const char *ContentType)
//
// Function
// Name: HTTPResponse::Send(IOStream &, bool)
-// Purpose: Build the response, and send via the stream. Optionally omitting
-// the content.
+// Purpose: Build the response, and send via the stream.
+// Optionally omitting the content.
// Created: 26/3/04
//
// --------------------------------------------------------------------------
@@ -139,10 +142,10 @@ void HTTPResponse::Send(IOStream &rStream, bool OmitContent)
header += len;
}
// Extra headers...
- for(std::vector<std::string>::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i)
+ for(std::vector<std::pair<std::string, std::string> >::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i)
{
header += "\r\n";
- header += *i;
+ header += i->first + ": " + i->second;
}
// NOTE: a line ending must be included here in all cases
// Control whether the response is cached
@@ -181,16 +184,214 @@ void HTTPResponse::Send(IOStream &rStream, bool OmitContent)
// --------------------------------------------------------------------------
//
// Function
+// Name: HTTPResponse::ParseHeaders(IOStreamGetLine &, int)
+// Purpose: Private. Parse the headers of the response
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
+{
+ std::string header;
+ bool haveHeader = false;
+ while(true)
+ {
+ if(rGetLine.IsEOF())
+ {
+ // Header terminates unexpectedly
+ THROW_EXCEPTION(HTTPException, BadRequest)
+ }
+
+ std::string currentLine;
+ if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout))
+ {
+ // Timeout
+ THROW_EXCEPTION(HTTPException, RequestReadFailed)
+ }
+
+ // Is this a continuation of the previous line?
+ bool processHeader = haveHeader;
+ if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t'))
+ {
+ // A continuation, don't process anything yet
+ processHeader = false;
+ }
+ //TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str());
+
+ // Parse the header -- this will actually process the header
+ // from the previous run around the loop.
+ if(processHeader)
+ {
+ // Find where the : is in the line
+ const char *h = header.c_str();
+ int p = 0;
+ while(h[p] != '\0' && h[p] != ':')
+ {
+ ++p;
+ }
+ // Skip white space
+ int dataStart = p + 1;
+ while(h[dataStart] == ' ' || h[dataStart] == '\t')
+ {
+ ++dataStart;
+ }
+
+ if(p == sizeof("Content-Length")-1
+ && ::strncasecmp(h, "Content-Length", sizeof("Content-Length")-1) == 0)
+ {
+ // Decode number
+ long len = ::strtol(h + dataStart, NULL, 10); // returns zero in error case, this is OK
+ if(len < 0) len = 0;
+ // Store
+ mContentLength = len;
+ }
+ else if(p == sizeof("Content-Type")-1
+ && ::strncasecmp(h, "Content-Type", sizeof("Content-Type")-1) == 0)
+ {
+ // Store rest of string as content type
+ mContentType = h + dataStart;
+ }
+ else if(p == sizeof("Cookie")-1
+ && ::strncasecmp(h, "Cookie", sizeof("Cookie")-1) == 0)
+ {
+ THROW_EXCEPTION(HTTPException, NotImplemented);
+ /*
+ // Parse cookies
+ ParseCookies(header, dataStart);
+ */
+ }
+ else if(p == sizeof("Connection")-1
+ && ::strncasecmp(h, "Connection", sizeof("Connection")-1) == 0)
+ {
+ // Connection header, what is required?
+ const char *v = h + dataStart;
+ if(::strcasecmp(v, "close") == 0)
+ {
+ mKeepAlive = false;
+ }
+ else if(::strcasecmp(v, "keep-alive") == 0)
+ {
+ mKeepAlive = true;
+ }
+ // else don't understand, just assume default for protocol version
+ }
+ else
+ {
+ std::string headerName = header.substr(0, p);
+ AddHeader(headerName, h + dataStart);
+ }
+
+ // Unset have header flag, as it's now been processed
+ haveHeader = false;
+ }
+
+ // Store the chunk of header the for next time round
+ if(haveHeader)
+ {
+ header += currentLine;
+ }
+ else
+ {
+ header = currentLine;
+ haveHeader = true;
+ }
+
+ // End of headers?
+ if(currentLine.empty())
+ {
+ // All done!
+ break;
+ }
+ }
+}
+
+void HTTPResponse::Receive(IOStream& rStream, int Timeout)
+{
+ IOStreamGetLine rGetLine(rStream);
+
+ if(rGetLine.IsEOF())
+ {
+ // Connection terminated unexpectedly
+ THROW_EXCEPTION(HTTPException, BadResponse)
+ }
+
+ std::string statusLine;
+ if(!rGetLine.GetLine(statusLine, false /* no preprocess */, Timeout))
+ {
+ // Timeout
+ THROW_EXCEPTION(HTTPException, ResponseReadFailed)
+ }
+
+ if (statusLine.substr(0, 7) != "HTTP/1." ||
+ statusLine[8] != ' ')
+ {
+ // Status line terminated unexpectedly
+ BOX_ERROR("Bad response status line: " << statusLine);
+ THROW_EXCEPTION(HTTPException, BadResponse)
+ }
+
+ if (statusLine[5] == '1' && statusLine[7] == '1')
+ {
+ // HTTP/1.1 default is to keep alive
+ mKeepAlive = true;
+ }
+
+ // Decode the status code
+ long status = ::strtol(statusLine.substr(9, 3).c_str(), NULL, 10);
+ // returns zero in error case, this is OK
+ if(status < 0) status = 0;
+ // Store
+ mResponseCode = status;
+
+ ParseHeaders(rGetLine, Timeout);
+
+ // push back whatever bytes we have left
+ // rGetLine.DetachFile();
+ if (mContentLength > 0)
+ {
+ if (mContentLength < rGetLine.GetSizeOfBufferedData())
+ {
+ // very small response, not good!
+ THROW_EXCEPTION(HTTPException, NotImplemented);
+ }
+
+ Write(rGetLine.GetBufferedData(),
+ rGetLine.GetSizeOfBufferedData());
+ }
+
+ while (mContentLength != 0) // could be -1 as well
+ {
+ char buffer[4096];
+ int readSize = sizeof(buffer);
+ if (mContentLength > 0 && mContentLength < readSize)
+ {
+ readSize = mContentLength;
+ }
+ readSize = rStream.Read(buffer, readSize, Timeout);
+ if (readSize == 0)
+ {
+ break;
+ }
+ mContentLength -= readSize;
+ Write(buffer, readSize);
+ }
+
+ SetForReading();
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
// Name: HTTPResponse::AddHeader(const char *)
// Purpose: Add header, given entire line
// Created: 26/3/04
//
// --------------------------------------------------------------------------
+/*
void HTTPResponse::AddHeader(const char *EntireHeaderLine)
{
mExtraHeaders.push_back(std::string(EntireHeaderLine));
}
-
+*/
// --------------------------------------------------------------------------
//
@@ -200,11 +401,12 @@ void HTTPResponse::AddHeader(const char *EntireHeaderLine)
// Created: 26/3/04
//
// --------------------------------------------------------------------------
+/*
void HTTPResponse::AddHeader(const std::string &rEntireHeaderLine)
{
mExtraHeaders.push_back(rEntireHeaderLine);
}
-
+*/
// --------------------------------------------------------------------------
//
@@ -214,12 +416,9 @@ void HTTPResponse::AddHeader(const std::string &rEntireHeaderLine)
// Created: 26/3/04
//
// --------------------------------------------------------------------------
-void HTTPResponse::AddHeader(const char *Header, const char *Value)
+void HTTPResponse::AddHeader(const char *pHeader, const char *pValue)
{
- std::string h(Header);
- h += ": ";
- h += Value;
- mExtraHeaders.push_back(h);
+ mExtraHeaders.push_back(Header(pHeader, pValue));
}
@@ -231,12 +430,9 @@ void HTTPResponse::AddHeader(const char *Header, const char *Value)
// Created: 26/3/04
//
// --------------------------------------------------------------------------
-void HTTPResponse::AddHeader(const char *Header, const std::string &rValue)
+void HTTPResponse::AddHeader(const char *pHeader, const std::string &rValue)
{
- std::string h(Header);
- h += ": ";
- h += rValue;
- mExtraHeaders.push_back(h);
+ mExtraHeaders.push_back(Header(pHeader, rValue));
}
@@ -250,7 +446,7 @@ void HTTPResponse::AddHeader(const char *Header, const std::string &rValue)
// --------------------------------------------------------------------------
void HTTPResponse::AddHeader(const std::string &rHeader, const std::string &rValue)
{
- mExtraHeaders.push_back(rHeader + ": " + rValue);
+ mExtraHeaders.push_back(Header(rHeader, rValue));
}
@@ -279,14 +475,14 @@ void HTTPResponse::SetCookie(const char *Name, const char *Value, const char *Pa
h += Path;
h += "\"";
*/
- std::string h("Set-Cookie: ");
+ std::string h;
h += Name;
h += "=";
h += Value;
h += "; Version=1; Path=";
h += Path;
- mExtraHeaders.push_back(h);
+ mExtraHeaders.push_back(Header("Set-Cookie", h));
}
@@ -312,10 +508,10 @@ void HTTPResponse::SetAsRedirect(const char *RedirectTo, bool IsLocalURI)
mResponseCode = Code_Found;
// Set location to redirect to
- std::string header("Location: ");
+ std::string header;
if(IsLocalURI) header += msDefaultURIPrefix;
header += RedirectTo;
- mExtraHeaders.push_back(header);
+ mExtraHeaders.push_back(Header("Location", header));
// Set up some default content
mContentType = "text/html";
diff --git a/lib/httpserver/HTTPResponse.h b/lib/httpserver/HTTPResponse.h
index c6b57e40..58cc3baa 100644
--- a/lib/httpserver/HTTPResponse.h
+++ b/lib/httpserver/HTTPResponse.h
@@ -15,6 +15,8 @@
#include "CollectInBufferStream.h"
+class IOStreamGetLine;
+
// --------------------------------------------------------------------------
//
// Class
@@ -35,15 +37,18 @@ private:
public:
void SetResponseCode(int Code);
+ int GetResponseCode() { return mResponseCode; }
void SetContentType(const char *ContentType);
+ const std::string& GetContentType() { return mContentType; }
void SetAsRedirect(const char *RedirectTo, bool IsLocalURI = true);
void SetAsNotFound(const char *URI);
void Send(IOStream &rStream, bool OmitContent = false);
+ void Receive(IOStream& rStream, int Timeout = IOStream::TimeOutInfinite);
- void AddHeader(const char *EntireHeaderLine);
- void AddHeader(const std::string &rEntireHeaderLine);
+ // void AddHeader(const char *EntireHeaderLine);
+ // void AddHeader(const std::string &rEntireHeaderLine);
void AddHeader(const char *Header, const char *Value);
void AddHeader(const char *Header, const std::string &rValue);
void AddHeader(const std::string &rHeader, const std::string &rValue);
@@ -106,9 +111,13 @@ private:
bool mResponseIsDynamicContent;
bool mKeepAlive;
std::string mContentType;
- std::vector<std::string> mExtraHeaders;
+ typedef std::pair<std::string, std::string> Header;
+ std::vector<Header> mExtraHeaders;
+ int mContentLength; // only used when reading response from stream
static std::string msDefaultURIPrefix;
+
+ void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout);
};
#endif // HTTPRESPONSE__H