summaryrefslogtreecommitdiff
path: root/lib/httpserver
diff options
context:
space:
mode:
Diffstat (limited to 'lib/httpserver')
-rw-r--r--lib/httpserver/HTTPException.txt12
-rw-r--r--lib/httpserver/HTTPQueryDecoder.cpp158
-rw-r--r--lib/httpserver/HTTPQueryDecoder.h47
-rw-r--r--lib/httpserver/HTTPRequest.cpp577
-rw-r--r--lib/httpserver/HTTPRequest.h114
-rw-r--r--lib/httpserver/HTTPResponse.cpp415
-rw-r--r--lib/httpserver/HTTPResponse.h115
-rw-r--r--lib/httpserver/HTTPServer.cpp249
-rw-r--r--lib/httpserver/HTTPServer.h78
-rw-r--r--lib/httpserver/Makefile.extra7
10 files changed, 1772 insertions, 0 deletions
diff --git a/lib/httpserver/HTTPException.txt b/lib/httpserver/HTTPException.txt
new file mode 100644
index 00000000..f31f323d
--- /dev/null
+++ b/lib/httpserver/HTTPException.txt
@@ -0,0 +1,12 @@
+EXCEPTION HTTP 10
+
+Internal 0
+RequestReadFailed 1
+RequestAlreadyBeenRead 2
+BadRequest 3
+UnknownResponseCodeUsed 4
+NoContentTypeSet 5
+POSTContentTooLong 6
+CannotSetRedirectIfReponseHasData 7
+CannotSetNotFoundIfReponseHasData 8
+NotImplemented 9
diff --git a/lib/httpserver/HTTPQueryDecoder.cpp b/lib/httpserver/HTTPQueryDecoder.cpp
new file mode 100644
index 00000000..5315d4ce
--- /dev/null
+++ b/lib/httpserver/HTTPQueryDecoder.cpp
@@ -0,0 +1,158 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: HTTPQueryDecoder.cpp
+// Purpose: Utility class to decode HTTP query strings
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdlib.h>
+
+#include "HTTPQueryDecoder.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// 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
+//
+// --------------------------------------------------------------------------
+HTTPQueryDecoder::HTTPQueryDecoder(HTTPRequest::Query_t &rDecodeInto)
+ : mrDecodeInto(rDecodeInto),
+ mInKey(true),
+ mEscapedState(0)
+{
+ // Insert the terminator for escaped characters
+ mEscaped[2] = '\0';
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPQueryDecoder::~HTTPQueryDecoder()
+// Purpose: Destructor.
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPQueryDecoder::~HTTPQueryDecoder()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPQueryDecoder::Decode(const char *, int)
+// Purpose: Decode a chunk of query string -- call several times with
+// the bits as they are received, and then call Finish()
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPQueryDecoder::DecodeChunk(const char *pQueryString, int QueryStringSize)
+{
+ for(int l = 0; l < QueryStringSize; ++l)
+ {
+ char c = pQueryString[l];
+
+ // BEFORE unescaping, check to see if we need to flip key / value
+ if(mEscapedState == 0)
+ {
+ if(mInKey && c == '=')
+ {
+ // Set to store characters in the value
+ mInKey = false;
+ continue;
+ }
+ else if(!mInKey && c == '&')
+ {
+ // Need to store the current key/value pair
+ mrDecodeInto.insert(HTTPRequest::QueryEn_t(mCurrentKey, mCurrentValue));
+ // Blank the strings
+ mCurrentKey.erase();
+ mCurrentValue.erase();
+
+ // Set to store characters in the key
+ mInKey = true;
+ continue;
+ }
+ }
+
+ // Decode an escaped value?
+ if(mEscapedState == 1)
+ {
+ // Waiting for char one of the escaped hex value
+ mEscaped[0] = c;
+ mEscapedState = 2;
+ continue;
+ }
+ else if(mEscapedState == 2)
+ {
+ // Escaped value, decode it
+ mEscaped[1] = c; // str terminated in constructor
+ mEscapedState = 0; // stop being in escaped mode
+ long ch = ::strtol(mEscaped, NULL, 16);
+ if(ch <= 0 || ch > 255)
+ {
+ // Bad character, just ignore
+ continue;
+ }
+
+ // Use this instead
+ c = (char)ch;
+ }
+ else if(c == '+')
+ {
+ c = ' ';
+ }
+ else if(c == '%')
+ {
+ mEscapedState = 1;
+ continue;
+ }
+
+ // Store decoded value into the appropriate string
+ if(mInKey)
+ {
+ mCurrentKey += c;
+ }
+ else
+ {
+ mCurrentValue += c;
+ }
+ }
+
+ // Don't do anything here with left over values, DecodeChunk might be called
+ // again. Let Finish() clean up.
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPQueryDecoder::Finish()
+// Purpose: Finish the decoding. Necessary to get the last item!
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPQueryDecoder::Finish()
+{
+ // Insert any remaining value.
+ if(!mCurrentKey.empty())
+ {
+ mrDecodeInto.insert(HTTPRequest::QueryEn_t(mCurrentKey, mCurrentValue));
+ // Blank values, just in case
+ mCurrentKey.erase();
+ mCurrentValue.erase();
+ }
+}
+
+
diff --git a/lib/httpserver/HTTPQueryDecoder.h b/lib/httpserver/HTTPQueryDecoder.h
new file mode 100644
index 00000000..ca5afe7e
--- /dev/null
+++ b/lib/httpserver/HTTPQueryDecoder.h
@@ -0,0 +1,47 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: HTTPQueryDecoder.h
+// Purpose: Utility class to decode HTTP query strings
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef HTTPQUERYDECODER__H
+#define HTTPQUERYDECODER__H
+
+#include "HTTPRequest.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: HTTPQueryDecoder
+// Purpose: Utility class to decode HTTP query strings
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+class HTTPQueryDecoder
+{
+public:
+ HTTPQueryDecoder(HTTPRequest::Query_t &rDecodeInto);
+ ~HTTPQueryDecoder();
+private:
+ // no copying
+ HTTPQueryDecoder(const HTTPQueryDecoder &);
+ HTTPQueryDecoder &operator=(const HTTPQueryDecoder &);
+public:
+
+ void DecodeChunk(const char *pQueryString, int QueryStringSize);
+ void Finish();
+
+private:
+ HTTPRequest::Query_t &mrDecodeInto;
+ std::string mCurrentKey;
+ std::string mCurrentValue;
+ bool mInKey;
+ char mEscaped[4];
+ int mEscapedState;
+};
+
+#endif // HTTPQUERYDECODER__H
+
diff --git a/lib/httpserver/HTTPRequest.cpp b/lib/httpserver/HTTPRequest.cpp
new file mode 100644
index 00000000..2207e569
--- /dev/null
+++ b/lib/httpserver/HTTPRequest.cpp
@@ -0,0 +1,577 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: HTTPRequest.cpp
+// Purpose: Request object for HTTP connections
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "HTTPRequest.h"
+#include "HTTPQueryDecoder.h"
+#include "autogen_HTTPException.h"
+#include "IOStream.h"
+#include "IOStreamGetLine.h"
+
+#include "MemLeakFindOn.h"
+
+#define MAX_CONTENT_SIZE (128*1024)
+
+#define ENSURE_COOKIE_JAR_ALLOCATED \
+ if(mpCookies == 0) {mpCookies = new CookieJar_t;}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPRequest::HTTPRequest()
+// Purpose: Constructor
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPRequest::HTTPRequest()
+ : mMethod(Method_UNINITIALISED),
+ mHostPort(80), // default if not specified
+ mHTTPVersion(0),
+ mContentLength(-1),
+ mpCookies(0),
+ mClientKeepAliveRequested(false)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPRequest::~HTTPRequest()
+// Purpose: Destructor
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPRequest::~HTTPRequest()
+{
+ // Clean up any cookies
+ if(mpCookies != 0)
+ {
+ delete mpCookies;
+ mpCookies = 0;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// 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.
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+bool HTTPRequest::Read(IOStreamGetLine &rGetLine, int Timeout)
+{
+ // Check caller's logic
+ if(mMethod != Method_UNINITIALISED)
+ {
+ THROW_EXCEPTION(HTTPException, RequestAlreadyBeenRead)
+ }
+
+ // Read the first line, which is of a different format to the rest of the lines
+ std::string requestLine;
+ if(!rGetLine.GetLine(requestLine, false /* no preprocessing */, Timeout))
+ {
+ // Didn't get the request line, probably end of connection which had been kept alive
+ return false;
+ }
+// TRACE1("Request line: %s\n", requestLine.c_str());
+
+ // Check the method
+ unsigned int p = 0; // current position in string
+ if(::strncmp(requestLine.c_str(), "GET ", 4) == 0)
+ {
+ p = 3;
+ mMethod = Method_GET;
+ }
+ else if(::strncmp(requestLine.c_str(), "HEAD ", 5) == 0)
+ {
+ p = 4;
+ mMethod = Method_HEAD;
+ }
+ else if(::strncmp(requestLine.c_str(), "POST ", 5) == 0)
+ {
+ p = 4;
+ mMethod = Method_POST;
+ }
+ else
+ {
+ p = requestLine.find(' ');
+ if(p == std::string::npos)
+ {
+ // No terminating space, looks bad
+ p = requestLine.size();
+ }
+ mMethod = Method_UNKNOWN;
+ }
+
+ // Skip spaces to find URI
+ const char *requestLinePtr = requestLine.c_str();
+ while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ')
+ {
+ ++p;
+ }
+
+ // Check there's a URI following...
+ if(requestLinePtr[p] == '\0')
+ {
+ // Didn't get the request line, probably end of connection which had been kept alive
+ return false;
+ }
+
+ // Read the URI, unescaping any %XX hex codes
+ while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0')
+ {
+ // End of URI, on to query string?
+ if(requestLinePtr[p] == '?')
+ {
+ // Put the rest into the query string, without escaping anything
+ ++p;
+ while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0')
+ {
+ mQueryString += requestLinePtr[p];
+ ++p;
+ }
+ break;
+ }
+ // Needs unescaping?
+ else if(requestLinePtr[p] == '+')
+ {
+ mRequestURI += ' ';
+ }
+ else if(requestLinePtr[p] == '%')
+ {
+ // Be tolerant about this... bad things are silently accepted,
+ // rather than throwing an error.
+ char code[4] = {0,0,0,0};
+ code[0] = requestLinePtr[++p];
+ if(code[0] != '\0')
+ {
+ code[1] = requestLinePtr[++p];
+ }
+
+ // Convert into a char code
+ long c = ::strtol(code, NULL, 16);
+
+ // Accept it?
+ if(c > 0 && c <= 255)
+ {
+ mRequestURI += (char)c;
+ }
+ }
+ else
+ {
+ // Simple copy of character
+ mRequestURI += requestLinePtr[p];
+ }
+
+ ++p;
+ }
+
+ // End of URL?
+ if(requestLinePtr[p] == '\0')
+ {
+ // Assume HTTP 0.9
+ mHTTPVersion = HTTPVersion_0_9;
+ }
+ else
+ {
+ // Skip any more spaces
+ while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ')
+ {
+ ++p;
+ }
+
+ // Check to see if there's the right string next...
+ if(::strncmp(requestLinePtr + p, "HTTP/", 5) == 0)
+ {
+ // Find the version numbers
+ int major, minor;
+ if(::sscanf(requestLinePtr + p + 5, "%d.%d", &major, &minor) != 2)
+ {
+ THROW_EXCEPTION(HTTPException, BadRequest)
+ }
+
+ // Store version
+ mHTTPVersion = (major * HTTPVersion__MajorMultiplier) + minor;
+ }
+ else
+ {
+ // Not good -- wrong string found
+ THROW_EXCEPTION(HTTPException, BadRequest)
+ }
+ }
+
+ TRACE3("HTTPRequest: method=%d, uri=%s, version=%d\n", mMethod, mRequestURI.c_str(), mHTTPVersion);
+
+ // If HTTP 1.1 or greater, assume keep-alive
+ if(mHTTPVersion >= HTTPVersion_1_1)
+ {
+ mClientKeepAliveRequested = true;
+ }
+
+ // Decode query string?
+ if((mMethod == Method_GET || mMethod == Method_HEAD) && !mQueryString.empty())
+ {
+ HTTPQueryDecoder decoder(mQuery);
+ decoder.DecodeChunk(mQueryString.c_str(), mQueryString.size());
+ decoder.Finish();
+ }
+
+ // Now parse the headers
+ ParseHeaders(rGetLine, Timeout);
+
+ // Parse form data?
+ if(mMethod == Method_POST && mContentLength >= 0)
+ {
+ // Too long? Don't allow people to be nasty by sending lots of data
+ if(mContentLength > MAX_CONTENT_SIZE)
+ {
+ THROW_EXCEPTION(HTTPException, POSTContentTooLong)
+ }
+
+ // Some data in the request to follow, parsing it bit by bit
+ HTTPQueryDecoder decoder(mQuery);
+ // Don't forget any data left in the GetLine object
+ int fromBuffer = rGetLine.GetSizeOfBufferedData();
+ if(fromBuffer > mContentLength) fromBuffer = mContentLength;
+ if(fromBuffer > 0)
+ {
+ TRACE1("Decoding %d bytes of data from getline buffer\n", fromBuffer);
+ decoder.DecodeChunk((const char *)rGetLine.GetBufferedData(), fromBuffer);
+ // And tell the getline object to ignore the data we just used
+ rGetLine.IgnoreBufferedData(fromBuffer);
+ }
+ // Then read any more data, as required
+ int bytesToGo = mContentLength - fromBuffer;
+ while(bytesToGo > 0)
+ {
+ char buf[4096];
+ int toRead = sizeof(buf);
+ if(toRead > bytesToGo) toRead = bytesToGo;
+ IOStream &rstream(rGetLine.GetUnderlyingStream());
+ int r = rstream.Read(buf, toRead, Timeout);
+ if(r == 0)
+ {
+ // Timeout, just error
+ THROW_EXCEPTION(HTTPException, RequestReadFailed)
+ }
+ decoder.DecodeChunk(buf, r);
+ bytesToGo -= r;
+ }
+ // Finish off
+ decoder.Finish();
+ }
+
+ return true;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPRequest::ParseHeaders(IOStreamGetLine &, int)
+// Purpose: Private. Parse the headers of the request
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPRequest::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("Host")-1
+ && ::strncasecmp(h, "Host", sizeof("Host")-1) == 0)
+ {
+ // Store host header
+ mHostName = h + dataStart;
+
+ // Is there a port number to split off?
+ std::string::size_type colon = mHostName.find_first_of(':');
+ if(colon != std::string::npos)
+ {
+ // There's a port in the string... attempt to turn it into an int
+ mHostPort = ::strtol(mHostName.c_str() + colon + 1, 0, 10);
+
+ // Truncate the string to just the hostname
+ mHostName = mHostName.substr(0, colon);
+
+ TRACE2("Host: header, hostname = '%s', host port = %d\n", mHostName.c_str(), mHostPort);
+ }
+ }
+ else if(p == sizeof("Cookie")-1
+ && ::strncasecmp(h, "Cookie", sizeof("Cookie")-1) == 0)
+ {
+ // 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)
+ {
+ mClientKeepAliveRequested = false;
+ }
+ else if(::strcasecmp(v, "keep-alive") == 0)
+ {
+ mClientKeepAliveRequested = true;
+ }
+ // else don't understand, just assume default for protocol version
+ }
+ // else ignore it
+
+ // 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;
+ }
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPRequest::ParseCookies(const std::string &, int)
+// Purpose: Parse the cookie header
+// Created: 20/8/04
+//
+// --------------------------------------------------------------------------
+void HTTPRequest::ParseCookies(const std::string &rHeader, int DataStarts)
+{
+ const char *data = rHeader.c_str() + DataStarts;
+ const char *pos = data;
+ const char *itemStart = pos;
+ std::string name;
+
+ enum
+ {
+ s_NAME, s_VALUE, s_VALUE_QUOTED, s_FIND_NEXT_NAME
+ } state = s_NAME;
+
+ do
+ {
+ switch(state)
+ {
+ case s_NAME:
+ {
+ if(*pos == '=')
+ {
+ // Found the name. Store
+ name.assign(itemStart, pos - itemStart);
+ // Looking at values now
+ state = s_VALUE;
+ if((*(pos + 1)) == '"')
+ {
+ // Actually it's a quoted value, skip over that
+ ++pos;
+ state = s_VALUE_QUOTED;
+ }
+ // Record starting point for this item
+ itemStart = pos + 1;
+ }
+ }
+ break;
+
+ case s_VALUE:
+ {
+ if(*pos == ';' || *pos == ',' || *pos == '\0')
+ {
+ // Name ends
+ ENSURE_COOKIE_JAR_ALLOCATED
+ std::string value(itemStart, pos - itemStart);
+ (*mpCookies)[name] = value;
+ // And move to the waiting stage
+ state = s_FIND_NEXT_NAME;
+ }
+ }
+ break;
+
+ case s_VALUE_QUOTED:
+ {
+ if(*pos == '"')
+ {
+ // That'll do nicely, save it
+ ENSURE_COOKIE_JAR_ALLOCATED
+ std::string value(itemStart, pos - itemStart);
+ (*mpCookies)[name] = value;
+ // And move to the waiting stage
+ state = s_FIND_NEXT_NAME;
+ }
+ }
+ break;
+
+ case s_FIND_NEXT_NAME:
+ {
+ // Skip over terminators and white space to get to the next name
+ if(*pos != ';' && *pos != ',' && *pos != ' ' && *pos != '\t')
+ {
+ // Name starts here
+ itemStart = pos;
+ state = s_NAME;
+ }
+ }
+ break;
+
+ default:
+ // Ooops
+ THROW_EXCEPTION(HTTPException, Internal)
+ break;
+ }
+ }
+ while(*(pos++) != 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPRequest::GetCookie(const char *, std::string &) const
+// Purpose: Fetch a cookie's value. If cookie not present, returns false
+// and string is unaltered.
+// Created: 20/8/04
+//
+// --------------------------------------------------------------------------
+bool HTTPRequest::GetCookie(const char *CookieName, std::string &rValueOut) const
+{
+ // Got any cookies?
+ if(mpCookies == 0)
+ {
+ return false;
+ }
+
+ // See if it's there
+ CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName)));
+ if(v != mpCookies->end())
+ {
+ // Return the value
+ rValueOut = v->second;
+ return true;
+ }
+
+ return false;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPRequest::GetCookie(const char *)
+// Purpose: Return a string for the given cookie, or the null string if the
+// cookie has not been recieved.
+// Created: 22/8/04
+//
+// --------------------------------------------------------------------------
+const std::string &HTTPRequest::GetCookie(const char *CookieName) const
+{
+ static const std::string noCookie;
+
+ // Got any cookies?
+ if(mpCookies == 0)
+ {
+ return noCookie;
+ }
+
+ // See if it's there
+ CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName)));
+ if(v != mpCookies->end())
+ {
+ // Return the value
+ return v->second;
+ }
+
+ return noCookie;
+}
+
+
+
diff --git a/lib/httpserver/HTTPRequest.h b/lib/httpserver/HTTPRequest.h
new file mode 100644
index 00000000..142c8a1c
--- /dev/null
+++ b/lib/httpserver/HTTPRequest.h
@@ -0,0 +1,114 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: HTTPRequest.h
+// Purpose: Request object for HTTP connections
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef HTTPREQUEST__H
+#define HTTPREQUEST__H
+
+#include <string>
+#include <map>
+
+class IOStream;
+class IOStreamGetLine;
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: HTTPRequest
+// Purpose: Request object for HTTP connections
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+class HTTPRequest
+{
+public:
+ HTTPRequest();
+ ~HTTPRequest();
+private:
+ // no copying
+ HTTPRequest(const HTTPRequest &);
+ HTTPRequest &operator=(const HTTPRequest &);
+public:
+
+ typedef std::multimap<std::string, std::string> Query_t;
+ typedef std::pair<std::string, std::string> QueryEn_t;
+
+ 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,
+ HTTPVersion_1_1 = 1001
+ };
+
+ bool Read(IOStreamGetLine &rGetLine, int Timeout);
+
+ typedef std::map<std::string, std::string> CookieJar_t;
+
+ // --------------------------------------------------------------------------
+ //
+ // Function
+ // Name: HTTPResponse::Get*()
+ // Purpose: Various Get accessors
+ // Created: 26/3/04
+ //
+ // --------------------------------------------------------------------------
+ int 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
+ const std::string &GetQueryString() const {return mQueryString;}
+ int GetHTTPVersion() const {return mHTTPVersion;}
+ const Query_t &GetQuery() const {return mQuery;}
+ int GetContentLength() const {return mContentLength;}
+ const std::string &GetContentType() const {return mContentType;}
+ const CookieJar_t *GetCookies() const {return mpCookies;} // WARNING: May return NULL
+ bool GetCookie(const char *CookieName, std::string &rValueOut) const;
+ const std::string &GetCookie(const char *CookieName) const;
+
+
+ // --------------------------------------------------------------------------
+ //
+ // Function
+ // Name: HTTPRequest::GetClientKeepAliveRequested()
+ // Purpose: Returns true if the client requested that the connection
+ // should be kept open for further requests.
+ // Created: 22/12/04
+ //
+ // --------------------------------------------------------------------------
+ bool GetClientKeepAliveRequested() const {return mClientKeepAliveRequested;}
+
+private:
+ void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout);
+ void ParseCookies(const std::string &rHeader, int DataStarts);
+
+private:
+ int mMethod;
+ std::string mRequestURI;
+ std::string mHostName;
+ int mHostPort;
+ std::string mQueryString;
+ int mHTTPVersion;
+ Query_t mQuery;
+ int mContentLength;
+ std::string mContentType;
+ CookieJar_t *mpCookies;
+ bool mClientKeepAliveRequested;
+};
+
+#endif // HTTPREQUEST__H
+
diff --git a/lib/httpserver/HTTPResponse.cpp b/lib/httpserver/HTTPResponse.cpp
new file mode 100644
index 00000000..29efb471
--- /dev/null
+++ b/lib/httpserver/HTTPResponse.cpp
@@ -0,0 +1,415 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: HTTPResponse.cpp
+// Purpose: Response object for HTTP connections
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "HTTPResponse.h"
+#include "autogen_HTTPException.h"
+
+#include "MemLeakFindOn.h"
+
+// Static variables
+std::string HTTPResponse::msDefaultURIPrefix;
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::HTTPResponse()
+// Purpose: Constructor
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPResponse::HTTPResponse()
+ : mResponseCode(HTTPResponse::Code_NoContent),
+ mResponseIsDynamicContent(true),
+ mKeepAlive(false)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::~HTTPResponse()
+// Purpose: Destructor
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPResponse::~HTTPResponse()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::ResponseCodeToString(int)
+// Purpose: Return string equivalent of the response code, suitable for Status: headers
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+const char *HTTPResponse::ResponseCodeToString(int ResponseCode)
+{
+ switch(ResponseCode)
+ {
+ case Code_OK: return "200 OK"; break;
+ case Code_NoContent: return "204 No Content"; break;
+ case Code_MovedPermanently: return "301 Moved Permanently"; break;
+ case Code_Found: return "302 Found"; break;
+ case Code_NotModified: return "304 Not Modified"; break;
+ case Code_TemporaryRedirect: return "307 Temporary Redirect"; break;
+ case Code_Unauthorized: return "401 Unauthorized"; break;
+ case Code_Forbidden: return "403 Forbidden"; break;
+ case Code_NotFound: return "404 Not Found"; break;
+ case Code_InternalServerError: return "500 Internal Server Error"; break;
+ case Code_NotImplemented: return "501 Not Implemented"; break;
+ default:
+ {
+ THROW_EXCEPTION(HTTPException, UnknownResponseCodeUsed)
+ }
+ }
+ return "500 Internal Server Error";
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::SetResponseCode(int)
+// Purpose: Set the response code to be returned
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetResponseCode(int Code)
+{
+ mResponseCode = Code;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::SetContentType(const char *)
+// Purpose: Set content type
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetContentType(const char *ContentType)
+{
+ mContentType = ContentType;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::Send(IOStream &, bool)
+// Purpose: Build the response, and send via the stream. Optionally omitting
+// the content.
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::Send(IOStream &rStream, bool OmitContent)
+{
+ if(mContentType.empty())
+ {
+ THROW_EXCEPTION(HTTPException, NoContentTypeSet)
+ }
+
+ // Build and send header
+ {
+ std::string header("HTTP/1.1 ");
+ header += ResponseCodeToString(mResponseCode);
+ header += "\r\nContent-Type: ";
+ header += mContentType;
+ header += "\r\nContent-Length: ";
+ {
+ char len[32];
+ ::sprintf(len, "%d", OmitContent?(0):(GetSize()));
+ header += len;
+ }
+ // Extra headers...
+ for(std::vector<std::string>::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i)
+ {
+ header += "\r\n";
+ header += *i;
+ }
+ // NOTE: a line ending must be included here in all cases
+ // Control whether the response is cached
+ if(mResponseIsDynamicContent)
+ {
+ // dynamic is private and can't be cached
+ header += "\r\nCache-Control: no-cache, private";
+ }
+ else
+ {
+ // static is allowed to be cached for a day
+ header += "\r\nCache-Control: max-age=86400";
+ }
+ if(mKeepAlive)
+ {
+ header += "\r\nConnection: keep-alive\r\n\r\n";
+ }
+ else
+ {
+ header += "\r\nConnection: close\r\n\r\n";
+ }
+ // NOTE: header ends with blank line in all cases
+
+ // Write to stream
+ rStream.Write(header.c_str(), header.size());
+ }
+
+ // Send content
+ if(!OmitContent)
+ {
+ rStream.Write(GetBuffer(), GetSize());
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// 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));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::AddHeader(const std::string &)
+// Purpose: Add header, given entire line
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const std::string &rEntireHeaderLine)
+{
+ mExtraHeaders.push_back(rEntireHeaderLine);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::AddHeader(const char *, const char *)
+// Purpose: Add header, given header name and it's value
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const char *Header, const char *Value)
+{
+ std::string h(Header);
+ h += ": ";
+ h += Value;
+ mExtraHeaders.push_back(h);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::AddHeader(const char *, const std::string &)
+// Purpose: Add header, given header name and it's value
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const char *Header, const std::string &rValue)
+{
+ std::string h(Header);
+ h += ": ";
+ h += rValue;
+ mExtraHeaders.push_back(h);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::AddHeader(const std::string &, const std::string &)
+// Purpose: Add header, given header name and it's value
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const std::string &rHeader, const std::string &rValue)
+{
+ mExtraHeaders.push_back(rHeader + ": " + rValue);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::SetCookie(const char *, const char *, const char *, int)
+// Purpose: Sets a cookie, using name, value, path and expiry time.
+// Created: 20/8/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetCookie(const char *Name, const char *Value, const char *Path, int ExpiresAt)
+{
+ if(ExpiresAt != 0)
+ {
+ THROW_EXCEPTION(HTTPException, NotImplemented)
+ }
+
+ // Appears you shouldn't use quotes when you generate set-cookie headers.
+ // Oh well. It was fun finding that out.
+/* std::string h("Set-Cookie: ");
+ h += Name;
+ h += "=\"";
+ h += Value;
+ h += "\"; Version=\"1\"; Path=\"";
+ h += Path;
+ h += "\"";
+*/
+ std::string h("Set-Cookie: ");
+ h += Name;
+ h += "=";
+ h += Value;
+ h += "; Version=1; Path=";
+ h += Path;
+
+ mExtraHeaders.push_back(h);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::SetAsRedirect(const char *, bool)
+// Purpose: Sets the response objects to be a redirect to another page.
+// If IsLocalURL == true, the default prefix will be added.
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetAsRedirect(const char *RedirectTo, bool IsLocalURI)
+{
+ if(mResponseCode != HTTPResponse::Code_NoContent
+ || !mContentType.empty()
+ || GetSize() != 0)
+ {
+ THROW_EXCEPTION(HTTPException, CannotSetRedirectIfReponseHasData)
+ }
+
+ // Set response code
+ mResponseCode = Code_Found;
+
+ // Set location to redirect to
+ std::string header("Location: ");
+ if(IsLocalURI) header += msDefaultURIPrefix;
+ header += RedirectTo;
+ mExtraHeaders.push_back(header);
+
+ // Set up some default content
+ mContentType = "text/html";
+ #define REDIRECT_HTML_1 "<html><head><title>Redirection</title></head>\n<body><p><a href=\""
+ #define REDIRECT_HTML_2 "\">Redirect to content</a></p></body></html>\n"
+ Write(REDIRECT_HTML_1, sizeof(REDIRECT_HTML_1) - 1);
+ if(IsLocalURI) Write(msDefaultURIPrefix.c_str(), msDefaultURIPrefix.size());
+ Write(RedirectTo, ::strlen(RedirectTo));
+ Write(REDIRECT_HTML_2, sizeof(REDIRECT_HTML_2) - 1);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::SetAsNotFound(const char *)
+// Purpose: Set the response object to be a standard page not found 404 response.
+// Created: 7/4/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetAsNotFound(const char *URI)
+{
+ if(mResponseCode != HTTPResponse::Code_NoContent
+ || mExtraHeaders.size() != 0
+ || !mContentType.empty()
+ || GetSize() != 0)
+ {
+ THROW_EXCEPTION(HTTPException, CannotSetNotFoundIfReponseHasData)
+ }
+
+ // Set response code
+ mResponseCode = Code_NotFound;
+
+ // Set data
+ mContentType = "text/html";
+ #define NOT_FOUND_HTML_1 "<html><head><title>404 Not Found</title></head>\n<body><h1>404 Not Found</h1>\n<p>The URI <i>"
+ #define NOT_FOUND_HTML_2 "</i> was not found on this server.</p></body></html>\n"
+ Write(NOT_FOUND_HTML_1, sizeof(NOT_FOUND_HTML_1) - 1);
+ WriteStringDefang(std::string(URI));
+ Write(NOT_FOUND_HTML_2, sizeof(NOT_FOUND_HTML_2) - 1);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::WriteStringDefang(const char *, unsigned int)
+// Purpose: Writes a string 'defanged', ie has HTML special characters escaped
+// so that people can't output arbitary HTML by playing with
+// URLs and form parameters, and it's safe to write strings into
+// HTML element attribute values.
+// Created: 9/4/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::WriteStringDefang(const char *String, unsigned int StringLen)
+{
+ while(StringLen > 0)
+ {
+ unsigned int toWrite = 0;
+ while(toWrite < StringLen
+ && String[toWrite] != '<'
+ && String[toWrite] != '>'
+ && String[toWrite] != '&'
+ && String[toWrite] != '"')
+ {
+ ++toWrite;
+ }
+ if(toWrite > 0)
+ {
+ Write(String, toWrite);
+ StringLen -= toWrite;
+ String += toWrite;
+ }
+
+ // Is it a bad character next?
+ while(StringLen > 0)
+ {
+ bool notSpecial = false;
+ switch(*String)
+ {
+ case '<': Write("&lt;", 4); break;
+ case '>': Write("&gt;", 4); break;
+ case '&': Write("&amp;", 5); break;
+ case '"': Write("&quot;", 6); break;
+ default:
+ // Stop this loop
+ notSpecial = true;
+ break;
+ }
+ if(notSpecial) break;
+ ++String;
+ --StringLen;
+ }
+ }
+}
+
+
diff --git a/lib/httpserver/HTTPResponse.h b/lib/httpserver/HTTPResponse.h
new file mode 100644
index 00000000..c6b57e40
--- /dev/null
+++ b/lib/httpserver/HTTPResponse.h
@@ -0,0 +1,115 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: HTTPResponse.h
+// Purpose: Response object for HTTP connections
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef HTTPRESPONSE__H
+#define HTTPRESPONSE__H
+
+#include <string>
+#include <vector>
+
+#include "CollectInBufferStream.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: HTTPResponse
+// Purpose: Response object for HTTP connections
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+class HTTPResponse : public CollectInBufferStream
+{
+public:
+ HTTPResponse();
+ ~HTTPResponse();
+private:
+ // no copying
+ HTTPResponse(const HTTPResponse &);
+ HTTPResponse &operator=(const HTTPResponse &);
+public:
+
+ void SetResponseCode(int Code);
+ void SetContentType(const char *ContentType);
+
+ void SetAsRedirect(const char *RedirectTo, bool IsLocalURI = true);
+ void SetAsNotFound(const char *URI);
+
+ void Send(IOStream &rStream, bool OmitContent = false);
+
+ 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);
+
+ // Set dynamic content flag, default is content is dynamic
+ void SetResponseIsDynamicContent(bool IsDynamic) {mResponseIsDynamicContent = IsDynamic;}
+ // Set keep alive control, default is to mark as to be closed
+ void SetKeepAlive(bool KeepAlive) {mKeepAlive = KeepAlive;}
+
+ void SetCookie(const char *Name, const char *Value, const char *Path = "/", int ExpiresAt = 0);
+
+ enum
+ {
+ Code_OK = 200,
+ Code_NoContent = 204,
+ Code_MovedPermanently = 301,
+ Code_Found = 302, // redirection
+ Code_NotModified = 304,
+ Code_TemporaryRedirect = 307,
+ Code_Unauthorized = 401,
+ Code_Forbidden = 403,
+ Code_NotFound = 404,
+ Code_InternalServerError = 500,
+ Code_NotImplemented = 501
+ };
+
+ static const char *ResponseCodeToString(int ResponseCode);
+
+ void WriteStringDefang(const char *String, unsigned int StringLen);
+ void WriteStringDefang(const std::string &rString) {WriteStringDefang(rString.c_str(), rString.size());}
+
+ // --------------------------------------------------------------------------
+ //
+ // Function
+ // Name: HTTPResponse::WriteString(const std::string &)
+ // Purpose: Write a string to the response (simple sugar function)
+ // Created: 9/4/04
+ //
+ // --------------------------------------------------------------------------
+ void WriteString(const std::string &rString)
+ {
+ Write(rString.c_str(), rString.size());
+ }
+
+ // --------------------------------------------------------------------------
+ //
+ // Function
+ // Name: HTTPResponse::SetDefaultURIPrefix(const std::string &)
+ // Purpose: Set default prefix used to local redirections
+ // Created: 26/3/04
+ //
+ // --------------------------------------------------------------------------
+ static void SetDefaultURIPrefix(const std::string &rPrefix)
+ {
+ msDefaultURIPrefix = rPrefix;
+ }
+
+private:
+ int mResponseCode;
+ bool mResponseIsDynamicContent;
+ bool mKeepAlive;
+ std::string mContentType;
+ std::vector<std::string> mExtraHeaders;
+
+ static std::string msDefaultURIPrefix;
+};
+
+#endif // HTTPRESPONSE__H
+
diff --git a/lib/httpserver/HTTPServer.cpp b/lib/httpserver/HTTPServer.cpp
new file mode 100644
index 00000000..2db78b23
--- /dev/null
+++ b/lib/httpserver/HTTPServer.cpp
@@ -0,0 +1,249 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: HTTPServer.cpp
+// Purpose: HTTP server class
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+
+#include "HTTPServer.h"
+#include "HTTPRequest.h"
+#include "HTTPResponse.h"
+#include "IOStreamGetLine.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPServer::HTTPServer()
+// Purpose: Constructor
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPServer::HTTPServer()
+ : mTimeout(20000) // default timeout leaves a little while for clients to get the second request in.
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPServer::~HTTPServer()
+// Purpose: Destructor
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPServer::~HTTPServer()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPServer::DaemonName()
+// Purpose: As interface, generic name for daemon
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+const char *HTTPServer::DaemonName() const
+{
+ return "generic-httpserver";
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPServer::GetConfigVerify()
+// Purpose: As interface -- return most basic config so it's only necessary to
+// provide this if you want to add extra directives.
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+const ConfigurationVerify *HTTPServer::GetConfigVerify() const
+{
+ static ConfigurationVerifyKey verifyserverkeys[] =
+ {
+ HTTPSERVER_VERIFY_SERVER_KEYS(0) // no default addresses
+ };
+
+ static ConfigurationVerify verifyserver[] =
+ {
+ {
+ "Server",
+ 0,
+ verifyserverkeys,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ }
+ };
+
+ static ConfigurationVerifyKey verifyrootkeys[] =
+ {
+ HTTPSERVER_VERIFY_ROOT_KEYS
+ };
+
+ static ConfigurationVerify verify =
+ {
+ "root",
+ verifyserver,
+ verifyrootkeys,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ };
+
+ return &verify;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPServer::Run()
+// Purpose: As interface.
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPServer::Run()
+{
+ // Do some configuration stuff
+ const Configuration &conf(GetConfiguration());
+ HTTPResponse::SetDefaultURIPrefix(conf.GetKeyValue("AddressPrefix"));
+
+ // Let the base class do the work
+ ServerStream<SocketStream, 80>::Run();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPServer::Connection(SocketStream &)
+// Purpose: As interface, handle connection
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPServer::Connection(SocketStream &rStream)
+{
+ // Create a get line object to use
+ IOStreamGetLine getLine(rStream);
+
+ // Notify dervived claases
+ HTTPConnectionOpening();
+
+ bool handleRequests = true;
+ while(handleRequests)
+ {
+ // Parse the request
+ HTTPRequest request;
+ if(!request.Read(getLine, mTimeout))
+ {
+ // Didn't get request, connection probably closed.
+ break;
+ }
+
+ // Generate a response
+ HTTPResponse response;
+ try
+ {
+ Handle(request, response);
+ }
+ catch(BoxException &e)
+ {
+ char exceptionCode[64];
+ ::sprintf(exceptionCode, "(%d/%d)", e.GetType(), e.GetSubType());
+ SendInternalErrorResponse(exceptionCode, rStream);
+ return;
+ }
+ catch(...)
+ {
+ SendInternalErrorResponse("unknown", rStream);
+ return;
+ }
+
+ // Keep alive?
+ if(request.GetClientKeepAliveRequested())
+ {
+ // Mark the response to the client as supporting keepalive
+ response.SetKeepAlive(true);
+ }
+ else
+ {
+ // Stop now
+ handleRequests = false;
+ }
+
+ // Send the response (omit any content if this is a HEAD method request)
+ response.Send(rStream, request.GetMethod() == HTTPRequest::Method_HEAD);
+ }
+
+ // Notify dervived claases
+ HTTPConnectionClosing();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPServer::SendInternalErrorResponse(const char *, SocketStream &)
+// Purpose: Sends an error response to the remote side
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPServer::SendInternalErrorResponse(const char *Error, SocketStream &rStream)
+{
+ #define ERROR_HTML_1 "<html><head><title>Internal Server Error</title></head>\n" \
+ "<h1>Internal Server Error</h1>\n" \
+ "<p>An error, type "
+ #define ERROR_HTML_2 " occured when processing the request.</p>" \
+ "<p>Please try again later.</p>" \
+ "</body>\n</html>\n"
+
+ // Generate the error page
+ HTTPResponse response;
+ response.SetResponseCode(HTTPResponse::Code_InternalServerError);
+ response.SetContentType("text/html");
+ response.Write(ERROR_HTML_1, sizeof(ERROR_HTML_1) - 1);
+ response.Write(Error, ::strlen(Error));
+ response.Write(ERROR_HTML_2, sizeof(ERROR_HTML_2) - 1);
+
+ // Send the error response
+ response.Send(rStream);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPServer::HTTPConnectionOpening()
+// Purpose: Override to get notifications of connections opening
+// Created: 22/12/04
+//
+// --------------------------------------------------------------------------
+void HTTPServer::HTTPConnectionOpening()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPServer::HTTPConnectionClosing()
+// Purpose: Override to get notifications of connections closing
+// Created: 22/12/04
+//
+// --------------------------------------------------------------------------
+void HTTPServer::HTTPConnectionClosing()
+{
+}
+
+
diff --git a/lib/httpserver/HTTPServer.h b/lib/httpserver/HTTPServer.h
new file mode 100644
index 00000000..88da7abf
--- /dev/null
+++ b/lib/httpserver/HTTPServer.h
@@ -0,0 +1,78 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: HTTPServer.h
+// Purpose: HTTP server class
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef HTTPSERVER__H
+#define HTTPSERVER__H
+
+#include "ServerStream.h"
+#include "SocketStream.h"
+
+class HTTPRequest;
+class HTTPResponse;
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: HTTPServer
+// Purpose: HTTP server
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+class HTTPServer : public ServerStream<SocketStream, 80>
+{
+public:
+ HTTPServer();
+ ~HTTPServer();
+private:
+ // no copying
+ HTTPServer(const HTTPServer &);
+ HTTPServer &operator=(const HTTPServer &);
+public:
+
+ int GetTimeout() const {return mTimeout;}
+
+ // --------------------------------------------------------------------------
+ //
+ // Function
+ // Name: HTTPServer::Handle(const HTTPRequest &, HTTPResponse &)
+ // Purpose: Response to a request, filling in the response object for sending
+ // at some point in the future.
+ // Created: 26/3/04
+ //
+ // --------------------------------------------------------------------------
+ virtual void Handle(const HTTPRequest &rRequest, HTTPResponse &rResponse) = 0;
+
+ // For notifications to derived classes
+ virtual void HTTPConnectionOpening();
+ virtual void HTTPConnectionClosing();
+
+private:
+ const char *DaemonName() const;
+ const ConfigurationVerify *GetConfigVerify() const;
+ void Run();
+ void Connection(SocketStream &rStream);
+ void SendInternalErrorResponse(const char *Error, SocketStream &rStream);
+
+private:
+ int mTimeout; // Timeout for read operations
+};
+
+// Root level
+#define HTTPSERVER_VERIFY_ROOT_KEYS \
+ {"AddressPrefix", 0, ConfigTest_Exists | ConfigTest_LastEntry, 0}
+
+// AddressPrefix is, for example, http://localhost:1080 -- ie the beginning of the URI
+// This is used for handling redirections.
+
+// Server level
+#define HTTPSERVER_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) \
+ SERVERSTREAM_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES)
+
+#endif // HTTPSERVER__H
+
diff --git a/lib/httpserver/Makefile.extra b/lib/httpserver/Makefile.extra
new file mode 100644
index 00000000..f0ca62be
--- /dev/null
+++ b/lib/httpserver/Makefile.extra
@@ -0,0 +1,7 @@
+
+MAKEEXCEPTION = ../../lib/common/makeexception.pl
+
+# AUTOGEN SEEDING
+autogen_HTTPException.h autogen_HTTPException.cpp: $(MAKEEXCEPTION) HTTPException.txt
+ perl $(MAKEEXCEPTION) HTTPException.txt
+