// distribution boxbackup-0.11.1 (svn version: 2821_2827)
// Box Backup, http://www.boxbackup.org/
// 
// Copyright (c) 2003-2010, Ben Summers and contributors.
// All rights reserved.
// 
// Note that this project uses mixed licensing. Any file with this license
// attached, or where the code LICENSE-DUAL appears on the first line, falls
// under this license. See the file COPYING.txt for more information.
// 
// This file is dual licensed. You may use and distribute it providing that you
// comply EITHER with the terms of the BSD license, OR the GPL license. It is
// not necessary to comply with both licenses, only one.
// 
// The BSD license option follows:
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  
// 3. Neither the name of the Box Backup nor the names of its contributors may
//    be used to endorse or promote products derived from this software without
//    specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
// [http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22New_BSD_License.22.29]
// 
// The GPL license option follows:
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
// 
// [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html#SEC4]
// --------------------------------------------------------------------------
//
// 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;
}	
