diff options
-rw-r--r-- | bin/s3simulator/s3simulator.cpp | 32 | ||||
-rwxr-xr-x | infrastructure/makebuildenv.pl.in | 2 | ||||
-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 | ||||
-rw-r--r-- | modules.txt | 1 | ||||
-rw-r--r-- | test/httpserver/testfiles/s3simulator.conf | 10 | ||||
-rw-r--r-- | test/httpserver/testhttpserver.cpp | 238 |
11 files changed, 463 insertions, 255 deletions
diff --git a/bin/s3simulator/s3simulator.cpp b/bin/s3simulator/s3simulator.cpp new file mode 100644 index 00000000..9a10635c --- /dev/null +++ b/bin/s3simulator/s3simulator.cpp @@ -0,0 +1,32 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: s3simulator.cpp +// Purpose: main file for S3 simulator daemon +// Created: 2003/10/11 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "S3Simulator.h" +#include "MainHelper.h" + +#include "MemLeakFindOn.h" + +int main(int argc, const char *argv[]) +{ + int ExitCode = 0; + + MAINHELPER_START + + Logging::SetProgramName("s3simulator"); + Logging::ToConsole(true); + Logging::ToSyslog (true); + + S3Simulator daemon; + ExitCode = daemon.Main("s3simulator.conf", argc, argv); + + MAINHELPER_END + + return ExitCode; +} diff --git a/infrastructure/makebuildenv.pl.in b/infrastructure/makebuildenv.pl.in index 5ed4a0ab..7705be0f 100755 --- a/infrastructure/makebuildenv.pl.in +++ b/infrastructure/makebuildenv.pl.in @@ -431,6 +431,8 @@ __E echo Killing any running daemons... kill_process bbackupd kill_process bbstored +kill_process httpserver +kill_process s3simulator __E } 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 + diff --git a/modules.txt b/modules.txt index 8932f014..1be10efa 100644 --- a/modules.txt +++ b/modules.txt @@ -44,6 +44,7 @@ test/bbackupd bin/bbackupd bin/bbstored bin/bbstoreaccounts bin/bbackupquery bi # HTTP server system lib/httpserver lib/server test/httpserver lib/httpserver +bin/s3simulator lib/httpserver # END_IF_DISTRIBUTION diff --git a/test/httpserver/testfiles/s3simulator.conf b/test/httpserver/testfiles/s3simulator.conf new file mode 100644 index 00000000..07921560 --- /dev/null +++ b/test/httpserver/testfiles/s3simulator.conf @@ -0,0 +1,10 @@ +AccessKey = 0PN5J17HBGZHT7JJ3X82 +SecretKey = uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o +StoreDirectory = testfiles +AddressPrefix = http://localhost:1080 + +Server +{ + PidFile = testfiles/s3simulator.pid + ListenAddresses = inet:localhost:1080 +} diff --git a/test/httpserver/testhttpserver.cpp b/test/httpserver/testhttpserver.cpp index 6c0de9a9..bbefff31 100644 --- a/test/httpserver/testhttpserver.cpp +++ b/test/httpserver/testhttpserver.cpp @@ -26,6 +26,7 @@ #include "HTTPServer.h" #include "IOStreamGetLine.h" #include "S3Client.h" +#include "S3Simulator.h" #include "ServerControl.h" #include "Test.h" #include "decode.h" @@ -125,213 +126,6 @@ void TestWebServer::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) TestWebServer::TestWebServer() {} TestWebServer::~TestWebServer() {} -class S3Simulator : public HTTPServer -{ -public: - S3Simulator() { } - ~S3Simulator() { } - - virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse); - virtual void HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse); - virtual void HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse); -}; - -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 - { - // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html - std::string access_key = "0PN5J17HBGZHT7JJ3X82"; - std::string secret_key = "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"; - - 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"; - - std::vector<HTTPRequest::Header> headers = rRequest.GetHeaders(); - - for (std::vector<HTTPRequest::Header>::iterator - i = headers.begin(); i != headers.end(); i++) - { - std::string& rHeaderName = i->first; - - for (std::string::iterator c = rHeaderName.begin(); - c != rHeaderName.end() && *c != ':'; c++) - { - *c = tolower(*c); - } - } - - 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); - } - 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); - } - } - catch (CommonException &ce) - { - rResponse.IOStream::Write(ce.what()); - } - catch (std::exception &e) - { - rResponse.IOStream::Write(e.what()); - } - catch (...) - { - rResponse.IOStream::Write("Unknown error"); - } - - return; -} - -void S3Simulator::HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse) -{ - std::string path = "testfiles"; - 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); -} - -void S3Simulator::HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse) -{ - std::string path = "testfiles"; - 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); -} - int test(int argc, const char *argv[]) { if(argc >= 2 && ::strcmp(argv[1], "server") == 0) @@ -446,16 +240,17 @@ int test(int argc, const char *argv[]) TEST_THAT(KillServer(pid)); TestRemoteProcessMemLeaks("generic-httpserver.memleaks"); - // correct, official signature should succeed + // correct, official signature should succeed, with lower-case header { // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html HTTPRequest request(HTTPRequest::Method_GET, "/photos/puppy.jpg"); request.SetHostName("johnsmith.s3.amazonaws.com"); - request.AddHeader("Date", "Tue, 27 Mar 2007 19:36:42 +0000"); - request.AddHeader("Authorization", + request.AddHeader("date", "Tue, 27 Mar 2007 19:36:42 +0000"); + request.AddHeader("authorization", "AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA="); S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); CollectInBufferStream response_buffer; HTTPResponse response(&response_buffer); @@ -473,11 +268,12 @@ int test(int argc, const char *argv[]) // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html HTTPRequest request(HTTPRequest::Method_GET, "/photos/puppy.jpg"); request.SetHostName("johnsmith.s3.amazonaws.com"); - request.AddHeader("Date", "Tue, 27 Mar 2007 19:36:42 +0000"); - request.AddHeader("Authorization", + request.AddHeader("date", "Tue, 27 Mar 2007 19:36:42 +0000"); + request.AddHeader("authorization", "AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbB="); S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); CollectInBufferStream response_buffer; HTTPResponse response(&response_buffer); @@ -487,12 +283,19 @@ int test(int argc, const char *argv[]) std::string response_data((const char *)response.GetBuffer(), response.GetSize()); - TEST_EQUAL("", response_data); + TEST_EQUAL("<html><head>" + "<title>Internal Server Error</title></head>\n" + "<h1>Internal Server Error</h1>\n" + "<p>An error, type Authentication Failed occured " + "when processing the request.</p>" + "<p>Please try again later.</p></body>\n" + "</html>\n", response_data); } // S3Client tests { S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); S3Client client(&simulator, "johnsmith.s3.amazonaws.com", "0PN5J17HBGZHT7JJ3X82", "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"); @@ -527,8 +330,8 @@ int test(int argc, const char *argv[]) HTTPRequest request(HTTPRequest::Method_PUT, "/newfile"); request.SetHostName("quotes.s3.amazonaws.com"); - request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); - request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:XtMYZf0hdOo4TdPYQknZk0Lz7rw="); + request.AddHeader("date", "Wed, 01 Mar 2006 12:00:00 GMT"); + request.AddHeader("authorization", "AWS 0PN5J17HBGZHT7JJ3X82:XtMYZf0hdOo4TdPYQknZk0Lz7rw="); request.AddHeader("Content-Type", "text/plain"); FileStream fs("testfiles/testrequests.pl"); @@ -539,6 +342,7 @@ int test(int argc, const char *argv[]) HTTPResponse response(&response_buffer); S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); simulator.Handle(request, response); TEST_EQUAL(200, response.GetResponseCode()); @@ -558,8 +362,8 @@ int test(int argc, const char *argv[]) } // Start the S3Simulator server - pid = LaunchServer("./test s3server testfiles/httpserver.conf", - "testfiles/httpserver.pid"); + pid = LaunchServer("./test s3server testfiles/s3simulator.conf", + "testfiles/s3simulator.pid"); TEST_THAT(pid != -1 && pid != 0); if(pid <= 0) { |