diff --git a/lib/httpserver/S3Simulator.cpp b/lib/httpserver/S3Simulator.cpp index 1bf07fcaf..55d08dd2b 100644 --- a/lib/httpserver/S3Simulator.cpp +++ b/lib/httpserver/S3Simulator.cpp @@ -94,94 +94,116 @@ void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError); rResponse.SetContentType("text/plain"); + std::string bucket_name; + try { const Configuration& rConfig(GetConfiguration()); std::string access_key = rConfig.GetKeyValue("AccessKey"); std::string secret_key = rConfig.GetKeyValue("SecretKey"); + std::ostringstream buffer_to_sign; + buffer_to_sign << rRequest.GetMethodName() << "\n"; - 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()) + if(true) { - std::string suffix = host.substr(host.size() - - s3suffix.size(), s3suffix.size()); - if (suffix == s3suffix) + std::string md5, date; + 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()) { - bucket = host.substr(0, host.size() - + std::string suffix = host.substr(host.size() - s3suffix.size(), s3suffix.size()); + + if (suffix == s3suffix) + { + bucket_name = host.substr(0, + host.size() - s3suffix.size()); + } } - } - std::ostringstream data; - data << rRequest.GetMethodName() << "\n"; - data << md5 << "\n"; - data << rRequest.GetContentType() << "\n"; - data << date << "\n"; + buffer_to_sign << md5 << "\n"; + buffer_to_sign << rRequest.GetContentType() << "\n"; + buffer_to_sign << date << "\n"; - // header names are already in lower case, i.e. canonical form + // header names are already in lower case, i.e. canonical form + std::vector headers = + rRequest.GetHeaders().GetExtraHeaders(); + std::sort(headers.begin(), headers.end()); - std::vector headers = rRequest.GetHeaders().GetExtraHeaders(); - std::sort(headers.begin(), headers.end()); + for (std::vector::iterator + i = headers.begin(); i != headers.end(); i++) + { + if (i->first.substr(0, 5) == "x-amz") + { + buffer_to_sign << i->first << ":" << + i->second << "\n"; + } + } - for (std::vector::iterator - i = headers.begin(); i != headers.end(); i++) - { - if (i->first.substr(0, 5) == "x-amz") + if (! bucket_name.empty()) { - data << i->first << ":" << i->second << "\n"; + buffer_to_sign << "/" << bucket_name; } - } - if (! bucket.empty()) - { - data << "/" << bucket; + buffer_to_sign << rRequest.GetRequestURI(); } - data << rRequest.GetRequestURI(); - std::string data_string = data.str(); + std::string string_to_sign = buffer_to_sign.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); + (const unsigned char*)string_to_sign.c_str(), + string_to_sign.size(), digest_buffer, &digest_size); + std::string digest((const char *)digest_buffer, digest_size); + std::string expected_auth, actual_auth; base64::encoder encoder; - std::string expectedAuth = "AWS " + access_key + ":" + - encoder.encode(digest); - if (expectedAuth[expectedAuth.size() - 1] == '\n') + if(true) { - expectedAuth = expectedAuth.substr(0, - expectedAuth.size() - 1); + expected_auth = "AWS " + access_key + ":" + + encoder.encode(digest); + + if(!rRequest.GetHeader("authorization", &actual_auth)) + { + THROW_EXCEPTION_MESSAGE(HTTPException, + AuthenticationFailed, "Missing Authorization header"); + } } - std::string actualAuth; - if (!rRequest.GetHeader("authorization", &actualAuth) || - actualAuth != expectedAuth) + // The Base64 encoder tends to add a newline onto the end of the encoded + // string, which we don't want, so remove it here. + if(expected_auth[expected_auth.size() - 1] == '\n') { - THROW_EXCEPTION_MESSAGE(HTTPException, AuthenticationFailed, - "Authentication code mismatch"); + expected_auth = expected_auth.substr(0, expected_auth.size() - 1); } - else if (rRequest.GetMethod() == HTTPRequest::Method_GET) + + if(actual_auth != expected_auth) + { + THROW_EXCEPTION_MESSAGE(HTTPException, + AuthenticationFailed, "Authentication code mismatch: " << + "expected " << expected_auth << " but received " << + actual_auth); + } + + if(rRequest.GetMethod() == HTTPRequest::Method_GET) { HandleGet(rRequest, rResponse); } - else if (rRequest.GetMethod() == HTTPRequest::Method_PUT) + else if(rRequest.GetMethod() == HTTPRequest::Method_PUT) { HandlePut(rRequest, rResponse); } else { THROW_EXCEPTION_MESSAGE(HTTPException, BadRequest, - "Unsupported Amazon S3 Method"); + "Unsupported Amazon S3 Method: " << + rRequest.GetMethodName()); } } catch (BoxException &ce) @@ -215,10 +237,13 @@ void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) SendInternalErrorResponse("Unknown exception", rResponse); } - if (rResponse.GetResponseCode() != 200 && + if (rResponse.GetResponseCode() != HTTPResponse::Code_OK && + rResponse.GetResponseCode() != HTTPResponse::Code_NotModified && + rResponse.GetResponseCode() != HTTPResponse::Code_NoContent && rResponse.GetSize() == 0) { - // no error message written, provide a default + // Looks like an error response with no error message specified, + // so write a default one. std::ostringstream s; s << rResponse.GetResponseCode(); SendInternalErrorResponse(s.str().c_str(), rResponse); @@ -230,6 +255,7 @@ void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) return; } + // -------------------------------------------------------------------------- // // Function @@ -246,20 +272,29 @@ void S3Simulator::HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse) std::string path = GetConfiguration().GetKeyValue("StoreDirectory"); path += rRequest.GetRequestURI(); std::auto_ptr apFile; - apFile.reset(new FileStream(path)); + rResponse.SetResponseCode(HTTPResponse::Code_OK); + + if(true) + { + apFile->CopyStreamTo(rResponse); + // We allow the HTTPResponse to set the response size itself in this case, + // but we must add the ETag header. TODO: proper support for streaming + // responses will require us to set the content-length here, because we + // know it but the HTTPResponse does not. + rResponse.AddHeader("ETag", "\"828ef3fdfa96f00ad9f27c383fc9ac7f\""); + } + // 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 diff --git a/test/httpserver/testhttpserver.cpp b/test/httpserver/testhttpserver.cpp index 9a1448464..08b070e40 100644 --- a/test/httpserver/testhttpserver.cpp +++ b/test/httpserver/testhttpserver.cpp @@ -520,7 +520,9 @@ bool test_httpserver() "

Internal Server Error

\n" "

An error occurred while processing the request:

\n" "
HTTPException(AuthenticationFailed): "
-			"Authentication code mismatch
\n" + "Authentication code mismatch: expected AWS 0PN5J17HBGZHT7JJ3X82" + ":xXjDGYUmKxnwqr5KXNPGldn5LbA= but received AWS " + "0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbB=\n" "

Please try again later.

\n" "\n", response_data); }