diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/httpserver/HTTPRequest.cpp | 23 | ||||
-rw-r--r-- | lib/httpserver/HTTPRequest.h | 18 | ||||
-rw-r--r-- | lib/httpserver/HTTPServer.cpp | 36 | ||||
-rw-r--r-- | lib/httpserver/HTTPServer.h | 10 | ||||
-rw-r--r-- | lib/httpserver/S3Simulator.cpp | 308 | ||||
-rw-r--r-- | lib/httpserver/S3Simulator.h | 40 |
6 files changed, 397 insertions, 38 deletions
diff --git a/lib/httpserver/HTTPRequest.cpp b/lib/httpserver/HTTPRequest.cpp index e146a0a3..4c5dc149 100644 --- a/lib/httpserver/HTTPRequest.cpp +++ b/lib/httpserver/HTTPRequest.cpp @@ -535,9 +535,11 @@ void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout) { ++dataStart; } - - if(p == sizeof("Content-Length")-1 - && ::strncasecmp(h, "Content-Length", sizeof("Content-Length")-1) == 0) + + std::string header_name(ToLowerCase(std::string(h, + p))); + + if (header_name == "content-length") { // Decode number long len = ::strtol(h + dataStart, NULL, 10); // returns zero in error case, this is OK @@ -545,14 +547,12 @@ void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout) // Store mContentLength = len; } - else if(p == sizeof("Content-Type")-1 - && ::strncasecmp(h, "Content-Type", sizeof("Content-Type")-1) == 0) + else if (header_name == "content-type") { // Store rest of string as content type mContentType = h + dataStart; } - else if(p == sizeof("Host")-1 - && ::strncasecmp(h, "Host", sizeof("Host")-1) == 0) + else if (header_name == "host") { // Store host header mHostName = h + dataStart; @@ -572,14 +572,12 @@ void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout) "port = " << mHostPort); } } - else if(p == sizeof("Cookie")-1 - && ::strncasecmp(h, "Cookie", sizeof("Cookie")-1) == 0) + else if (header_name == "cookie") { // Parse cookies ParseCookies(header, dataStart); } - else if(p == sizeof("Connection")-1 - && ::strncasecmp(h, "Connection", sizeof("Connection")-1) == 0) + else if (header_name == "connection") { // Connection header, what is required? const char *v = h + dataStart; @@ -595,8 +593,7 @@ void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout) } else { - std::string name = header.substr(0, p); - mExtraHeaders.push_back(Header(name, + mExtraHeaders.push_back(Header(header_name, h + dataStart)); } diff --git a/lib/httpserver/HTTPRequest.h b/lib/httpserver/HTTPRequest.h index 90215751..ca50e60f 100644 --- a/lib/httpserver/HTTPRequest.h +++ b/lib/httpserver/HTTPRequest.h @@ -97,16 +97,19 @@ public: const std::string &GetCookie(const char *CookieName) const; bool GetHeader(const std::string& rName, std::string* pValueOut) const { + std::string header = ToLowerCase(rName); + for (std::vector<Header>::const_iterator i = mExtraHeaders.begin(); i != mExtraHeaders.end(); i++) { - if (i->first == rName) + if (i->first == header) { *pValueOut = i->second; return true; } } + return false; } std::vector<Header> GetHeaders() { return mExtraHeaders; } @@ -128,7 +131,7 @@ public: void AddHeader(const std::string& rName, const std::string& rValue) { - mExtraHeaders.push_back(Header(rName, rValue)); + mExtraHeaders.push_back(Header(ToLowerCase(rName), rValue)); } bool IsExpectingContinue() const { return mExpectContinue; } const char* GetVerb() const @@ -168,6 +171,17 @@ private: bool mExpectContinue; IOStream* mpStreamToReadFrom; std::string mHttpVerb; + + std::string ToLowerCase(const std::string& rInput) const + { + std::string output = rInput; + for (std::string::iterator c = output.begin(); + c != output.end(); c++) + { + *c = tolower(*c); + } + return output; + } }; #endif // HTTPREQUEST__H diff --git a/lib/httpserver/HTTPServer.cpp b/lib/httpserver/HTTPServer.cpp index b8b02249..be1db687 100644 --- a/lib/httpserver/HTTPServer.cpp +++ b/lib/httpserver/HTTPServer.cpp @@ -153,21 +153,21 @@ void HTTPServer::Connection(SocketStream &rStream) // 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; + char exceptionCode[256]; + ::sprintf(exceptionCode, "%s (%d/%d)", e.what(), + e.GetType(), e.GetSubType()); + SendInternalErrorResponse(exceptionCode, response); } catch(...) { - SendInternalErrorResponse("unknown", rStream); - return; + SendInternalErrorResponse("unknown", response); } // Keep alive? @@ -186,7 +186,7 @@ void HTTPServer::Connection(SocketStream &rStream) response.Send(request.GetMethod() == HTTPRequest::Method_HEAD); } - // Notify derived claases + // Notify derived classes HTTPConnectionClosing(); } @@ -194,12 +194,14 @@ void HTTPServer::Connection(SocketStream &rStream) // -------------------------------------------------------------------------- // // Function -// Name: HTTPServer::SendInternalErrorResponse(const char *, SocketStream &) -// Purpose: Sends an error response to the remote side +// Name: HTTPServer::SendInternalErrorResponse(const char*, +// HTTPResponse&) +// Purpose: Generates an error message in the provided response // Created: 26/3/04 // // -------------------------------------------------------------------------- -void HTTPServer::SendInternalErrorResponse(const char *Error, SocketStream &rStream) +void HTTPServer::SendInternalErrorResponse(const std::string& rErrorMsg, + HTTPResponse& rResponse) { #define ERROR_HTML_1 "<html><head><title>Internal Server Error</title></head>\n" \ "<h1>Internal Server Error</h1>\n" \ @@ -209,15 +211,11 @@ void HTTPServer::SendInternalErrorResponse(const char *Error, SocketStream &rStr "</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(); + // rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError); + rResponse.SetContentType("text/html"); + rResponse.Write(ERROR_HTML_1, sizeof(ERROR_HTML_1) - 1); + rResponse.IOStream::Write(rErrorMsg.c_str()); + rResponse.Write(ERROR_HTML_2, sizeof(ERROR_HTML_2) - 1); } diff --git a/lib/httpserver/HTTPServer.h b/lib/httpserver/HTTPServer.h index 8009438d..d9f74949 100644 --- a/lib/httpserver/HTTPServer.h +++ b/lib/httpserver/HTTPServer.h @@ -52,15 +52,17 @@ public: virtual void HTTPConnectionOpening(); virtual void HTTPConnectionClosing(); +protected: + void SendInternalErrorResponse(const std::string& rErrorMsg, + HTTPResponse& rResponse); + int GetTimeout() { return mTimeout; } + private: + int mTimeout; // Timeout for read operations 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 diff --git a/lib/httpserver/S3Simulator.cpp b/lib/httpserver/S3Simulator.cpp new file mode 100644 index 00000000..62bf7344 --- /dev/null +++ b/lib/httpserver/S3Simulator.cpp @@ -0,0 +1,308 @@ +// -------------------------------------------------------------------------- +// +// 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 "autogen_HTTPException.h" +#include "IOStream.h" +#include "Logging.h" +#include "S3Simulator.h" +#include "decode.h" +#include "encode.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::GetConfigVerify() +// Purpose: Returns additional configuration options for the +// S3 simulator. Currently the access key, secret key +// and store directory can be configured. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- +const ConfigurationVerify* S3Simulator::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[] = + { + ConfigurationVerifyKey("AccessKey", ConfigTest_Exists), + ConfigurationVerifyKey("SecretKey", ConfigTest_Exists), + ConfigurationVerifyKey("StoreDirectory", ConfigTest_Exists), + HTTPSERVER_VERIFY_ROOT_KEYS + }; + + static ConfigurationVerify verify = + { + "root", + verifyserver, + verifyrootkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + }; + + return &verify; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Simulator::Handle(HTTPRequest &rRequest, +// HTTPResponse &rResponse) +// Purpose: Handles any incoming S3 request, by checking +// authorization and then dispatching to one of the +// private Handle* methods. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) +{ + // if anything goes wrong, return a 500 error + rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError); + rResponse.SetContentType("text/plain"); + + try + { + const Configuration& rConfig(GetConfiguration()); + std::string access_key = rConfig.GetKeyValue("AccessKey"); + std::string secret_key = rConfig.GetKeyValue("SecretKey"); + + std::string md5, date, bucket; + rRequest.GetHeader("content-md5", &md5); + rRequest.GetHeader("date", &date); + + std::string host = rRequest.GetHostName(); + std::string s3suffix = ".s3.amazonaws.com"; + if (host.size() > s3suffix.size()) + { + std::string suffix = host.substr(host.size() - + s3suffix.size(), s3suffix.size()); + if (suffix == s3suffix) + { + bucket = host.substr(0, host.size() - + s3suffix.size()); + } + } + + std::ostringstream data; + data << rRequest.GetVerb() << "\n"; + data << md5 << "\n"; + data << rRequest.GetContentType() << "\n"; + data << date << "\n"; + + // header names are already in lower case, i.e. canonical form + + std::vector<HTTPRequest::Header> headers = rRequest.GetHeaders(); + sort(headers.begin(), headers.end()); + + for (std::vector<HTTPRequest::Header>::iterator + i = headers.begin(); i != headers.end(); i++) + { + if (i->first.substr(0, 5) == "x-amz") + { + data << i->first << ":" << i->second << "\n"; + } + } + + if (! bucket.empty()) + { + data << "/" << bucket; + } + + data << rRequest.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(), + secret_key.c_str(), secret_key.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 expectedAuth = "AWS " + access_key + ":" + + encoder.encode(digest); + + if (expectedAuth[expectedAuth.size() - 1] == '\n') + { + expectedAuth = expectedAuth.substr(0, + expectedAuth.size() - 1); + } + + std::string actualAuth; + if (!rRequest.GetHeader("authorization", &actualAuth) || + actualAuth != expectedAuth) + { + rResponse.SetResponseCode(HTTPResponse::Code_Unauthorized); + SendInternalErrorResponse("Authentication Failed", + rResponse); + } + else if (rRequest.GetMethod() == HTTPRequest::Method_GET) + { + HandleGet(rRequest, rResponse); + } + else if (rRequest.GetMethod() == HTTPRequest::Method_PUT) + { + HandlePut(rRequest, rResponse); + } + else + { + rResponse.SetResponseCode(HTTPResponse::Code_MethodNotAllowed); + SendInternalErrorResponse("Unsupported Method", + rResponse); + } + } + catch (CommonException &ce) + { + SendInternalErrorResponse(ce.what(), rResponse); + } + catch (std::exception &e) + { + SendInternalErrorResponse(e.what(), rResponse); + } + catch (...) + { + SendInternalErrorResponse("Unknown exception", rResponse); + } + + if (rResponse.GetResponseCode() != 200 && + rResponse.GetSize() == 0) + { + // no error message written, provide a default + std::ostringstream s; + s << rResponse.GetResponseCode(); + SendInternalErrorResponse(s.str().c_str(), rResponse); + } + + return; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Simulator::HandleGet(HTTPRequest &rRequest, +// HTTPResponse &rResponse) +// Purpose: Handles an S3 GET request, i.e. downloading an +// existing object. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +void S3Simulator::HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse) +{ + std::string path = GetConfiguration().GetKeyValue("StoreDirectory"); + path += rRequest.GetRequestURI(); + std::auto_ptr<FileStream> apFile; + + try + { + apFile.reset(new FileStream(path)); + } + catch (CommonException &ce) + { + if (ce.GetSubType() == CommonException::OSFileOpenError) + { + rResponse.SetResponseCode(HTTPResponse::Code_NotFound); + } + else if (ce.GetSubType() == CommonException::AccessDenied) + { + rResponse.SetResponseCode(HTTPResponse::Code_Forbidden); + } + throw; + } + + // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingRESTOperations.html + apFile->CopyStreamTo(rResponse); + rResponse.AddHeader("x-amz-id-2", "qBmKRcEWBBhH6XAqsKU/eg24V3jf/kWKN9dJip1L/FpbYr9FDy7wWFurfdQOEMcY"); + rResponse.AddHeader("x-amz-request-id", "F2A8CCCA26B4B26D"); + rResponse.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + rResponse.AddHeader("Last-Modified", "Sun, 1 Jan 2006 12:00:00 GMT"); + rResponse.AddHeader("ETag", "\"828ef3fdfa96f00ad9f27c383fc9ac7f\""); + rResponse.AddHeader("Server", "AmazonS3"); + rResponse.SetResponseCode(HTTPResponse::Code_OK); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Simulator::HandlePut(HTTPRequest &rRequest, +// HTTPResponse &rResponse) +// Purpose: Handles an S3 PUT request, i.e. uploading data to +// create or replace an object. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +void S3Simulator::HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse) +{ + std::string path = GetConfiguration().GetKeyValue("StoreDirectory"); + path += rRequest.GetRequestURI(); + std::auto_ptr<FileStream> apFile; + + try + { + apFile.reset(new FileStream(path, O_CREAT | O_WRONLY)); + } + catch (CommonException &ce) + { + if (ce.GetSubType() == CommonException::OSFileOpenError) + { + rResponse.SetResponseCode(HTTPResponse::Code_NotFound); + } + else if (ce.GetSubType() == CommonException::AccessDenied) + { + rResponse.SetResponseCode(HTTPResponse::Code_Forbidden); + } + throw; + } + + if (rRequest.IsExpectingContinue()) + { + rResponse.SendContinue(); + } + + rRequest.ReadContent(*apFile); + + // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTObjectPUT.html + rResponse.AddHeader("x-amz-id-2", "LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7"); + rResponse.AddHeader("x-amz-request-id", "F2A8CCCA26B4B26D"); + rResponse.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + rResponse.AddHeader("Last-Modified", "Sun, 1 Jan 2006 12:00:00 GMT"); + rResponse.AddHeader("ETag", "\"828ef3fdfa96f00ad9f27c383fc9ac7f\""); + rResponse.SetContentType(""); + rResponse.AddHeader("Server", "AmazonS3"); + rResponse.SetResponseCode(HTTPResponse::Code_OK); +} diff --git a/lib/httpserver/S3Simulator.h b/lib/httpserver/S3Simulator.h new file mode 100644 index 00000000..f80770ee --- /dev/null +++ b/lib/httpserver/S3Simulator.h @@ -0,0 +1,40 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Simulator.h +// Purpose: Amazon S3 simulation HTTP server for S3 testing +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#ifndef S3SIMULATOR__H +#define S3SIMULATOR__H + +#include "HTTPServer.h" + +class ConfigurationVerify; +class HTTPRequest; +class HTTPResponse; + +// -------------------------------------------------------------------------- +// +// Class +// Name: S3Simulator +// Purpose: Amazon S3 simulation HTTP server for S3 testing +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- +class S3Simulator : public HTTPServer +{ +public: + S3Simulator() { } + ~S3Simulator() { } + + const ConfigurationVerify* GetConfigVerify() const; + virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse); + virtual void HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse); + virtual void HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse); +}; + +#endif // S3SIMULATOR__H + |