diff options
Diffstat (limited to 'lib/httpserver')
-rw-r--r-- | lib/httpserver/HTTPException.txt | 16 | ||||
-rw-r--r-- | lib/httpserver/HTTPQueryDecoder.cpp | 159 | ||||
-rw-r--r-- | lib/httpserver/HTTPQueryDecoder.h | 47 | ||||
-rw-r--r-- | lib/httpserver/HTTPRequest.cpp | 783 | ||||
-rw-r--r-- | lib/httpserver/HTTPRequest.h | 174 | ||||
-rw-r--r-- | lib/httpserver/HTTPResponse.cpp | 648 | ||||
-rw-r--r-- | lib/httpserver/HTTPResponse.h | 175 | ||||
-rw-r--r-- | lib/httpserver/HTTPServer.cpp | 249 | ||||
-rw-r--r-- | lib/httpserver/HTTPServer.h | 79 | ||||
-rw-r--r-- | lib/httpserver/Makefile.extra | 7 | ||||
-rw-r--r-- | lib/httpserver/S3Client.cpp | 243 | ||||
-rw-r--r-- | lib/httpserver/S3Client.h | 72 | ||||
-rw-r--r-- | lib/httpserver/cdecode.cpp | 92 | ||||
-rw-r--r-- | lib/httpserver/cdecode.h | 28 | ||||
-rw-r--r-- | lib/httpserver/cencode.cpp | 113 | ||||
-rw-r--r-- | lib/httpserver/cencode.h | 32 | ||||
-rw-r--r-- | lib/httpserver/decode.h | 77 | ||||
-rw-r--r-- | lib/httpserver/encode.h | 87 |
18 files changed, 3081 insertions, 0 deletions
diff --git a/lib/httpserver/HTTPException.txt b/lib/httpserver/HTTPException.txt new file mode 100644 index 00000000..52630cda --- /dev/null +++ b/lib/httpserver/HTTPException.txt @@ -0,0 +1,16 @@ +EXCEPTION HTTP 10 + +Internal 0 +RequestReadFailed 1 +RequestAlreadyBeenRead 2 +BadRequest 3 +UnknownResponseCodeUsed 4 +NoContentTypeSet 5 +POSTContentTooLong 6 +CannotSetRedirectIfReponseHasData 7 +CannotSetNotFoundIfReponseHasData 8 +NotImplemented 9 +RequestNotInitialised 10 +BadResponse 11 +ResponseReadFailed 12 +NoStreamConfigured 13 diff --git a/lib/httpserver/HTTPQueryDecoder.cpp b/lib/httpserver/HTTPQueryDecoder.cpp new file mode 100644 index 00000000..c49ac2ce --- /dev/null +++ b/lib/httpserver/HTTPQueryDecoder.cpp @@ -0,0 +1,159 @@ +// -------------------------------------------------------------------------- +// +// 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..e146a0a3 --- /dev/null +++ b/lib/httpserver/HTTPRequest.cpp @@ -0,0 +1,783 @@ +// -------------------------------------------------------------------------- +// +// 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 "HTTPResponse.h" +#include "HTTPQueryDecoder.h" +#include "autogen_HTTPException.h" +#include "IOStream.h" +#include "IOStreamGetLine.h" +#include "Logging.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), + mExpectContinue(false), + mpStreamToReadFrom(NULL) +{ +} + + +// -------------------------------------------------------------------------- +// +// 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), + mExpectContinue(false), + mpStreamToReadFrom(NULL) +{ +} + + + +// -------------------------------------------------------------------------- +// +// 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::Receive(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::Receive(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; + } + BOX_TRACE("Request line: " << requestLine); + + // Check the method + size_t p = 0; // current position in string + p = requestLine.find(' '); // end of first word + + if (p == std::string::npos) + { + // No terminating space, looks bad + p = requestLine.size(); + } + else + { + mHttpVerb = requestLine.substr(0, p); + if (mHttpVerb == "GET") + { + mMethod = Method_GET; + } + else if (mHttpVerb == "HEAD") + { + mMethod = Method_HEAD; + } + else if (mHttpVerb == "POST") + { + mMethod = Method_POST; + } + else if (mHttpVerb == "PUT") + { + mMethod = Method_PUT; + } + else + { + 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) + } + } + + BOX_TRACE("HTTPRequest: method=" << mMethod << ", uri=" << + mRequestURI << ", version=" << 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); + + std::string expected; + if (GetHeader("Expect", &expected)) + { + if (expected == "100-continue") + { + mExpectContinue = true; + } + } + + // 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) + { + BOX_TRACE("Decoding " << fromBuffer << " bytes of " + "data from getline buffer"); + 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(); + } + else if (mContentLength > 0) + { + IOStream::pos_type bytesToCopy = rGetLine.GetSizeOfBufferedData(); + if (bytesToCopy > mContentLength) + { + bytesToCopy = mContentLength; + } + Write(rGetLine.GetBufferedData(), bytesToCopy); + SetForReading(); + mpStreamToReadFrom = &(rGetLine.GetUnderlyingStream()); + } + + return true; +} + +void HTTPRequest::ReadContent(IOStream& rStreamToWriteTo) +{ + Seek(0, SeekType_Absolute); + + CopyStreamTo(rStreamToWriteTo); + IOStream::pos_type bytesCopied = GetSize(); + + while (bytesCopied < mContentLength) + { + char buffer[1024]; + IOStream::pos_type bytesToCopy = sizeof(buffer); + if (bytesToCopy > mContentLength - bytesCopied) + { + bytesToCopy = mContentLength - bytesCopied; + } + bytesToCopy = mpStreamToReadFrom->Read(buffer, bytesToCopy); + rStreamToWriteTo.Write(buffer, bytesToCopy); + bytesCopied += bytesToCopy; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::Send(IOStream &, int) +// Purpose: Write the request to an IOStream using HTTP. +// Created: 03/01/09 +// +// -------------------------------------------------------------------------- +bool HTTPRequest::Send(IOStream &rStream, int Timeout, bool ExpectContinue) +{ + 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; + case Method_PUT: + rStream.Write("PUT"); 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"; + } + + for (std::vector<Header>::iterator i = mExtraHeaders.begin(); + i != mExtraHeaders.end(); i++) + { + oss << i->first << ": " << i->second << "\n"; + } + + if (ExpectContinue) + { + oss << "Expect: 100-continue\n"; + } + + rStream.Write(oss.str().c_str()); + rStream.Write("\n"); + + return true; +} + +void HTTPRequest::SendWithStream(IOStream &rStreamToSendTo, int Timeout, + IOStream* pStreamToSend, HTTPResponse& rResponse) +{ + IOStream::pos_type size = pStreamToSend->BytesLeftToRead(); + + if (size != IOStream::SizeOfStreamUnknown) + { + mContentLength = size; + } + + Send(rStreamToSendTo, Timeout, true); + + rResponse.Receive(rStreamToSendTo, Timeout); + if (rResponse.GetResponseCode() != 100) + { + // bad response, abort now + return; + } + + pStreamToSend->CopyStreamTo(rStreamToSendTo, Timeout); + + // receive the final response + rResponse.Receive(rStreamToSendTo, Timeout); +} + +// -------------------------------------------------------------------------- +// +// 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); + + BOX_TRACE("Host: header, hostname = " << + "'" << mHostName << "', host " + "port = " << 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 + { + std::string name = header.substr(0, p); + mExtraHeaders.push_back(Header(name, + 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; + } + } +} + + +// -------------------------------------------------------------------------- +// +// 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..90215751 --- /dev/null +++ b/lib/httpserver/HTTPRequest.h @@ -0,0 +1,174 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPRequest.h +// Purpose: Request object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef HTTPREQUEST__H +#define HTTPREQUEST__H + +#include <string> +#include <map> + +#include "CollectInBufferStream.h" + +class HTTPResponse; +class IOStream; +class IOStreamGetLine; + +// -------------------------------------------------------------------------- +// +// Class +// Name: HTTPRequest +// Purpose: Request object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +class HTTPRequest : public CollectInBufferStream +{ +public: + enum Method + { + Method_UNINITIALISED = -1, + Method_UNKNOWN = 0, + Method_GET = 1, + Method_HEAD = 2, + Method_POST = 3, + Method_PUT = 4 + }; + + HTTPRequest(); + HTTPRequest(enum Method method, const std::string& rURI); + ~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, Header; + + enum + { + HTTPVersion__MajorMultiplier = 1000, + HTTPVersion_0_9 = 9, + HTTPVersion_1_0 = 1000, + HTTPVersion_1_1 = 1001 + }; + + bool Receive(IOStreamGetLine &rGetLine, int Timeout); + bool Send(IOStream &rStream, int Timeout, bool ExpectContinue = false); + void SendWithStream(IOStream &rStreamToSendTo, int Timeout, + IOStream* pStreamToSend, HTTPResponse& rResponse); + void ReadContent(IOStream& rStreamToWriteTo); + + typedef std::map<std::string, std::string> CookieJar_t; + + // -------------------------------------------------------------------------- + // + // Function + // Name: HTTPResponse::Get*() + // Purpose: Various Get accessors + // Created: 26/3/04 + // + // -------------------------------------------------------------------------- + enum Method GetMethod() const {return mMethod;} + const std::string &GetRequestURI() const {return mRequestURI;} + + // Note: the HTTPRequest generates and parses the Host: header + // Do not attempt to set one yourself with AddHeader(). + const std::string &GetHostName() const {return mHostName;} + void SetHostName(const std::string& rHostName) + { + mHostName = rHostName; + } + + const int GetHostPort() const {return mHostPort;} + 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; + bool GetHeader(const std::string& rName, std::string* pValueOut) const + { + for (std::vector<Header>::const_iterator + i = mExtraHeaders.begin(); + i != mExtraHeaders.end(); i++) + { + if (i->first == rName) + { + *pValueOut = i->second; + return true; + } + } + return false; + } + std::vector<Header> GetHeaders() { return mExtraHeaders; } + + // -------------------------------------------------------------------------- + // + // 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;} + void SetClientKeepAliveRequested(bool keepAlive) + { + mClientKeepAliveRequested = keepAlive; + } + + void AddHeader(const std::string& rName, const std::string& rValue) + { + mExtraHeaders.push_back(Header(rName, rValue)); + } + bool IsExpectingContinue() const { return mExpectContinue; } + const char* GetVerb() const + { + if (!mHttpVerb.empty()) + { + return mHttpVerb.c_str(); + } + switch (mMethod) + { + case Method_UNINITIALISED: return "Uninitialized"; + case Method_UNKNOWN: return "Unknown"; + case Method_GET: return "GET"; + case Method_HEAD: return "HEAD"; + case Method_POST: return "POST"; + case Method_PUT: return "PUT"; + } + return "Bad"; + } + +private: + void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout); + void ParseCookies(const std::string &rHeader, int DataStarts); + + enum Method 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; + std::vector<Header> mExtraHeaders; + bool mExpectContinue; + IOStream* mpStreamToReadFrom; + std::string mHttpVerb; +}; + +#endif // HTTPREQUEST__H + diff --git a/lib/httpserver/HTTPResponse.cpp b/lib/httpserver/HTTPResponse.cpp new file mode 100644 index 00000000..1a8c8447 --- /dev/null +++ b/lib/httpserver/HTTPResponse.cpp @@ -0,0 +1,648 @@ +// -------------------------------------------------------------------------- +// +// 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 "IOStreamGetLine.h" +#include "autogen_HTTPException.h" + +#include "MemLeakFindOn.h" + +// Static variables +std::string HTTPResponse::msDefaultURIPrefix; + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::HTTPResponse(IOStream*) +// Purpose: Constructor for response to be sent to a stream +// Created: 04/01/09 +// +// -------------------------------------------------------------------------- +HTTPResponse::HTTPResponse(IOStream* pStreamToSendTo) + : mResponseCode(HTTPResponse::Code_NoContent), + mResponseIsDynamicContent(true), + mKeepAlive(false), + mContentLength(-1), + mpStreamToSendTo(pStreamToSendTo) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::HTTPResponse() +// Purpose: Constructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPResponse::HTTPResponse() + : mResponseCode(HTTPResponse::Code_NoContent), + mResponseIsDynamicContent(true), + mKeepAlive(false), + mContentLength(-1), + mpStreamToSendTo(NULL) +{ +} + + +// -------------------------------------------------------------------------- +// +// 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_MethodNotAllowed: return "400 Method Not Allowed"; 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(bool OmitContent) +{ + if (!mpStreamToSendTo) + { + THROW_EXCEPTION(HTTPException, NoStreamConfigured); + } + + if (GetSize() != 0 && 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::pair<std::string, std::string> >::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i) + { + header += "\r\n"; + header += i->first + ": " + i->second; + } + // 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 + mpStreamToSendTo->Write(header.c_str(), header.size()); + } + + // Send content + if(!OmitContent) + { + mpStreamToSendTo->Write(GetBuffer(), GetSize()); + } +} + +void HTTPResponse::SendContinue() +{ + mpStreamToSendTo->Write("HTTP/1.1 100 Continue\r\n"); +} + +// -------------------------------------------------------------------------- +// +// 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; + + // 100 Continue responses have no headers, terminating newline, or body + if (status == 100) + { + return; + } + + 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); + } + + mContentLength -= rGetLine.GetSizeOfBufferedData(); + + 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)); +} +*/ + +// -------------------------------------------------------------------------- +// +// 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 *pHeader, const char *pValue) +{ + mExtraHeaders.push_back(Header(pHeader, pValue)); +} + + +// -------------------------------------------------------------------------- +// +// 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 *pHeader, const std::string &rValue) +{ + mExtraHeaders.push_back(Header(pHeader, rValue)); +} + + +// -------------------------------------------------------------------------- +// +// 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(Header(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; + h += Name; + h += "="; + h += Value; + h += "; Version=1; Path="; + h += Path; + + mExtraHeaders.push_back(Header("Set-Cookie", 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; + if(IsLocalURI) header += msDefaultURIPrefix; + header += RedirectTo; + mExtraHeaders.push_back(Header("Location", 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("<", 4); break; + case '>': Write(">", 4); break; + case '&': Write("&", 5); break; + case '"': Write(""", 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..04051958 --- /dev/null +++ b/lib/httpserver/HTTPResponse.h @@ -0,0 +1,175 @@ +// -------------------------------------------------------------------------- +// +// 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 IOStreamGetLine; + +// -------------------------------------------------------------------------- +// +// Class +// Name: HTTPResponse +// Purpose: Response object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +class HTTPResponse : public CollectInBufferStream +{ +public: + HTTPResponse(IOStream* pStreamToSendTo); + HTTPResponse(); + ~HTTPResponse(); + + // allow copying, but be very careful with the response stream, + // you can only read it once! (this class doesn't police it). + HTTPResponse(const HTTPResponse& rOther) + : mResponseCode(rOther.mResponseCode), + mResponseIsDynamicContent(rOther.mResponseIsDynamicContent), + mKeepAlive(rOther.mKeepAlive), + mContentType(rOther.mContentType), + mExtraHeaders(rOther.mExtraHeaders), + mContentLength(rOther.mContentLength), + mpStreamToSendTo(rOther.mpStreamToSendTo) + { + Write(rOther.GetBuffer(), rOther.GetSize()); + } + + HTTPResponse &operator=(const HTTPResponse &rOther) + { + Reset(); + Write(rOther.GetBuffer(), rOther.GetSize()); + mResponseCode = rOther.mResponseCode; + mResponseIsDynamicContent = rOther.mResponseIsDynamicContent; + mKeepAlive = rOther.mKeepAlive; + mContentType = rOther.mContentType; + mExtraHeaders = rOther.mExtraHeaders; + mContentLength = rOther.mContentLength; + mpStreamToSendTo = rOther.mpStreamToSendTo; + return *this; + } + + typedef std::pair<std::string, std::string> Header; + + 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(bool OmitContent = false); + void SendContinue(); + void Receive(IOStream& rStream, int Timeout = IOStream::TimeOutInfinite); + + // 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); + bool GetHeader(const std::string& rName, std::string* pValueOut) const + { + for (std::vector<Header>::const_iterator + i = mExtraHeaders.begin(); + i != mExtraHeaders.end(); i++) + { + if (i->first == rName) + { + *pValueOut = i->second; + return true; + } + } + return false; + } + std::string GetHeaderValue(const std::string& rName) + { + std::string value; + if (!GetHeader(rName, &value)) + { + THROW_EXCEPTION(CommonException, ConfigNoKey); + } + return value; + } + + // 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_MethodNotAllowed = 400, + 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<Header> mExtraHeaders; + int mContentLength; // only used when reading response from stream + IOStream* mpStreamToSendTo; // nonzero only when constructed with a stream + + static std::string msDefaultURIPrefix; + + void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout); +}; + +#endif // HTTPRESPONSE__H + diff --git a/lib/httpserver/HTTPServer.cpp b/lib/httpserver/HTTPServer.cpp new file mode 100644 index 00000000..b8b02249 --- /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(ConfigurationVerifyKey::NoDefaultValue) // 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.Receive(getLine, mTimeout)) + { + // Didn't get request, connection probably closed. + break; + } + + // Generate a response + HTTPResponse response(&rStream); + 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(request.GetMethod() == HTTPRequest::Method_HEAD); + } + + // Notify derived 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(&rStream); + 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(); +} + + +// -------------------------------------------------------------------------- +// +// 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..8009438d --- /dev/null +++ b/lib/httpserver/HTTPServer.h @@ -0,0 +1,79 @@ +// -------------------------------------------------------------------------- +// +// 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(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 \ + ConfigurationVerifyKey("AddressPrefix", \ + ConfigTest_Exists | ConfigTest_LastEntry) + +// 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 + diff --git a/lib/httpserver/S3Client.cpp b/lib/httpserver/S3Client.cpp new file mode 100644 index 00000000..cd5988d5 --- /dev/null +++ b/lib/httpserver/S3Client.cpp @@ -0,0 +1,243 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Client.cpp +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <cstring> + +// #include <cstdio> +// #include <ctime> + +#include <openssl/hmac.h> + +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "HTTPServer.h" +#include "autogen_HTTPException.h" +#include "IOStream.h" +#include "Logging.h" +#include "S3Client.h" +#include "decode.h" +#include "encode.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::GetObject(const std::string& rObjectURI) +// Purpose: Retrieve the object with the specified URI (key) +// from your S3 bucket. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::GetObject(const std::string& rObjectURI) +{ + return FinishAndSendRequest(HTTPRequest::Method_GET, rObjectURI); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::PutObject(const std::string& rObjectURI, +// IOStream& rStreamToSend, const char* pContentType) +// Purpose: Upload the stream to S3, creating or overwriting the +// object with the specified URI (key) in your S3 +// bucket. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::PutObject(const std::string& rObjectURI, + IOStream& rStreamToSend, const char* pContentType) +{ + return FinishAndSendRequest(HTTPRequest::Method_PUT, rObjectURI, + &rStreamToSend, pContentType); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::FinishAndSendRequest( +// HTTPRequest::Method Method, +// const std::string& rRequestURI, +// IOStream* pStreamToSend, +// const char* pStreamContentType) +// Purpose: Internal method which creates an HTTP request to S3, +// populates the date and authorization header fields, +// and sends it to S3 (or the simulator), attaching +// the specified stream if any to the request. Opens a +// connection to the server if necessary, which may +// throw a ConnectionException. Returns the HTTP +// response returned by S3, which may be a 500 error. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method, + const std::string& rRequestURI, IOStream* pStreamToSend, + const char* pStreamContentType) +{ + HTTPRequest request(Method, rRequestURI); + request.SetHostName(mHostName); + + std::ostringstream date; + time_t tt = time(NULL); + struct tm *tp = gmtime(&tt); + if (!tp) + { + BOX_ERROR("Failed to get current time"); + THROW_EXCEPTION(HTTPException, Internal); + } + const char *dow[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; + date << dow[tp->tm_wday] << ", "; + const char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"}; + date << std::internal << std::setfill('0') << + std::setw(2) << tp->tm_mday << " " << + month[tp->tm_mon] << " " << + (tp->tm_year + 1900) << " "; + date << std::setw(2) << tp->tm_hour << ":" << + std::setw(2) << tp->tm_min << ":" << + std::setw(2) << tp->tm_sec << " GMT"; + request.AddHeader("Date", date.str()); + + if (pStreamContentType) + { + request.AddHeader("Content-Type", pStreamContentType); + } + + std::string s3suffix = ".s3.amazonaws.com"; + std::string bucket; + if (mHostName.size() > s3suffix.size()) + { + std::string suffix = mHostName.substr(mHostName.size() - + s3suffix.size(), s3suffix.size()); + if (suffix == s3suffix) + { + bucket = mHostName.substr(0, mHostName.size() - + s3suffix.size()); + } + } + + std::ostringstream data; + data << request.GetVerb() << "\n"; + data << "\n"; /* Content-MD5 */ + data << request.GetContentType() << "\n"; + data << date.str() << "\n"; + + if (! bucket.empty()) + { + data << "/" << bucket; + } + + data << request.GetRequestURI(); + std::string data_string = data.str(); + + unsigned char digest_buffer[EVP_MAX_MD_SIZE]; + unsigned int digest_size = sizeof(digest_buffer); + /* unsigned char* mac = */ HMAC(EVP_sha1(), + mSecretKey.c_str(), mSecretKey.size(), + (const unsigned char*)data_string.c_str(), + data_string.size(), digest_buffer, &digest_size); + std::string digest((const char *)digest_buffer, digest_size); + + base64::encoder encoder; + std::string auth_code = "AWS " + mAccessKey + ":" + + encoder.encode(digest); + + if (auth_code[auth_code.size() - 1] == '\n') + { + auth_code = auth_code.substr(0, auth_code.size() - 1); + } + + request.AddHeader("Authorization", auth_code); + + if (mpSimulator) + { + if (pStreamToSend) + { + pStreamToSend->CopyStreamTo(request); + } + + request.SetForReading(); + CollectInBufferStream response_buffer; + HTTPResponse response(&response_buffer); + + mpSimulator->Handle(request, response); + return response; + } + else + { + try + { + if (!mapClientSocket.get()) + { + mapClientSocket.reset(new SocketStream()); + mapClientSocket->Open(Socket::TypeINET, + mHostName, mPort); + } + return SendRequest(request, pStreamToSend, + pStreamContentType); + } + catch (ConnectionException &ce) + { + if (ce.GetType() == ConnectionException::SocketWriteError) + { + // server may have disconnected us, + // try to reconnect, just once + mapClientSocket->Open(Socket::TypeINET, + mHostName, mPort); + return SendRequest(request, pStreamToSend, + pStreamContentType); + } + else + { + throw; + } + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::SendRequest(HTTPRequest& rRequest, +// IOStream* pStreamToSend, +// const char* pStreamContentType) +// Purpose: Internal method which sends a pre-existing HTTP +// request to S3. Attaches the specified stream if any +// to the request. Opens a connection to the server if +// necessary, which may throw a ConnectionException. +// Returns the HTTP response returned by S3, which may +// be a 500 error. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::SendRequest(HTTPRequest& rRequest, + IOStream* pStreamToSend, const char* pStreamContentType) +{ + HTTPResponse response; + + if (pStreamToSend) + { + rRequest.SendWithStream(*mapClientSocket, + 30000 /* milliseconds */, + pStreamToSend, response); + } + else + { + rRequest.Send(*mapClientSocket, 30000 /* milliseconds */); + response.Receive(*mapClientSocket, 30000 /* milliseconds */); + } + + return response; +} diff --git a/lib/httpserver/S3Client.h b/lib/httpserver/S3Client.h new file mode 100644 index 00000000..3c4126ac --- /dev/null +++ b/lib/httpserver/S3Client.h @@ -0,0 +1,72 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Client.h +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#ifndef S3CLIENT__H +#define S3CLIENT__H + +#include <string> +#include <map> + +#include "HTTPRequest.h" +#include "SocketStream.h" + +class HTTPResponse; +class HTTPServer; +class IOStream; + +// -------------------------------------------------------------------------- +// +// Class +// Name: S3Client +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- +class S3Client +{ + public: + S3Client(HTTPServer* pSimulator, const std::string& rHostName, + const std::string& rAccessKey, const std::string& rSecretKey) + : mpSimulator(pSimulator), + mHostName(rHostName), + mAccessKey(rAccessKey), + mSecretKey(rSecretKey) + { } + + S3Client(std::string HostName, int Port, const std::string& rAccessKey, + const std::string& rSecretKey) + : mpSimulator(NULL), + mHostName(HostName), + mPort(Port), + mAccessKey(rAccessKey), + mSecretKey(rSecretKey) + { } + + HTTPResponse GetObject(const std::string& rObjectURI); + HTTPResponse PutObject(const std::string& rObjectURI, + IOStream& rStreamToSend, const char* pContentType = NULL); + + private: + HTTPServer* mpSimulator; + std::string mHostName; + int mPort; + std::auto_ptr<SocketStream> mapClientSocket; + std::string mAccessKey, mSecretKey; + + HTTPResponse FinishAndSendRequest(HTTPRequest::Method Method, + const std::string& rRequestURI, + IOStream* pStreamToSend = NULL, + const char* pStreamContentType = NULL); + HTTPResponse SendRequest(HTTPRequest& rRequest, + IOStream* pStreamToSend = NULL, + const char* pStreamContentType = NULL); +}; + +#endif // S3CLIENT__H + diff --git a/lib/httpserver/cdecode.cpp b/lib/httpserver/cdecode.cpp new file mode 100644 index 00000000..e632f182 --- /dev/null +++ b/lib/httpserver/cdecode.cpp @@ -0,0 +1,92 @@ +/* +cdecoder.c - c source to a base64 decoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +extern "C" +{ + +#include "cdecode.h" + +int base64_decode_value(char value_in) +{ + static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in > decoding_size) return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(base64_decodestate* state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) +{ + const char* codechar = code_in; + char* plainchar = plaintext_out; + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) + { + while (1) + { + case step_a: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + case step_b: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + case step_c: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + case step_d: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; +} + +} diff --git a/lib/httpserver/cdecode.h b/lib/httpserver/cdecode.h new file mode 100644 index 00000000..d0d7f489 --- /dev/null +++ b/lib/httpserver/cdecode.h @@ -0,0 +1,28 @@ +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +typedef enum +{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct +{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); + +#endif /* BASE64_CDECODE_H */ diff --git a/lib/httpserver/cencode.cpp b/lib/httpserver/cencode.cpp new file mode 100644 index 00000000..b33c0683 --- /dev/null +++ b/lib/httpserver/cencode.cpp @@ -0,0 +1,113 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +extern "C" +{ + +#include "cencode.h" + +const int CHARS_PER_LINE = 72; + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if (state_in->stepcount == CHARS_PER_LINE/4) + { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = '\n'; + + return codechar - code_out; +} + +} diff --git a/lib/httpserver/cencode.h b/lib/httpserver/cencode.h new file mode 100644 index 00000000..cf321312 --- /dev/null +++ b/lib/httpserver/cencode.h @@ -0,0 +1,32 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ + diff --git a/lib/httpserver/decode.h b/lib/httpserver/decode.h new file mode 100644 index 00000000..fe59ef7a --- /dev/null +++ b/lib/httpserver/decode.h @@ -0,0 +1,77 @@ +// :mode=c++: +/* +decode.h - c++ wrapper for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_DECODE_H +#define BASE64_DECODE_H + +#include <iostream> + +namespace base64 +{ + + extern "C" + { + #include "cdecode.h" + } + + struct decoder + { + base64_decodestate _state; + int _buffersize; + + decoder(int buffersize_in = 4096) + : _buffersize(buffersize_in) + {} + int decode(char value_in) + { + return base64_decode_value(value_in); + } + int decode(const char* code_in, const int length_in, char* plaintext_out) + { + return base64_decode_block(code_in, length_in, plaintext_out, &_state); + } + std::string decode(const std::string& input) + { + base64_init_decodestate(&_state); + char* output = new char[2*input.size()]; + int outlength = decode(input.c_str(), input.size(), + output); + std::string output_string(output, outlength); + base64_init_decodestate(&_state); + delete [] output; + return output_string; + } + void decode(std::istream& istream_in, std::ostream& ostream_in) + { + base64_init_decodestate(&_state); + // + const int N = _buffersize; + char* code = new char[N]; + char* plaintext = new char[N]; + int codelength; + int plainlength; + + do + { + istream_in.read((char*)code, N); + codelength = istream_in.gcount(); + plainlength = decode(code, codelength, plaintext); + ostream_in.write((const char*)plaintext, plainlength); + } + while (istream_in.good() && codelength > 0); + // + base64_init_decodestate(&_state); + + delete [] code; + delete [] plaintext; + } + }; + +} // namespace base64 + +#endif // BASE64_DECODE_H diff --git a/lib/httpserver/encode.h b/lib/httpserver/encode.h new file mode 100644 index 00000000..81957a0f --- /dev/null +++ b/lib/httpserver/encode.h @@ -0,0 +1,87 @@ +// :mode=c++: +/* +encode.h - c++ wrapper for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_ENCODE_H +#define BASE64_ENCODE_H + +#include <iostream> + +namespace base64 +{ + + extern "C" + { + #include "cencode.h" + } + + struct encoder + { + base64_encodestate _state; + int _buffersize; + + encoder(int buffersize_in = 4096) + : _buffersize(buffersize_in) + {} + int encode(char value_in) + { + return base64_encode_value(value_in); + } + int encode(const char* code_in, const int length_in, char* plaintext_out) + { + return base64_encode_block(code_in, length_in, plaintext_out, &_state); + } + int encode_end(char* plaintext_out) + { + return base64_encode_blockend(plaintext_out, &_state); + } + std::string encode(const std::string& input) + { + base64_init_encodestate(&_state); + char* output = new char[2*input.size()]; + int outlength = encode(input.c_str(), input.size(), + output); + outlength += encode_end(output + outlength); + std::string output_string(output, outlength); + base64_init_encodestate(&_state); + delete [] output; + return output_string; + } + void encode(std::istream& istream_in, std::ostream& ostream_in) + { + base64_init_encodestate(&_state); + // + const int N = _buffersize; + char* plaintext = new char[N]; + char* code = new char[2*N]; + int plainlength; + int codelength; + + do + { + istream_in.read(plaintext, N); + plainlength = istream_in.gcount(); + // + codelength = encode(plaintext, plainlength, code); + ostream_in.write(code, codelength); + } + while (istream_in.good() && plainlength > 0); + + codelength = encode_end(code); + ostream_in.write(code, codelength); + // + base64_init_encodestate(&_state); + + delete [] code; + delete [] plaintext; + } + }; + +} // namespace base64 + +#endif // BASE64_ENCODE_H + |