From boxbackup-dev@boxbackup.org  Sat Jan  3 04:00:18 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat,  3 Jan 2009 04:00:18 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2420 - box/trunk/lib
Message-ID: <20090103040018.B60C5325FCB@www.boxbackup.org>

Author: chris
Date: 2009-01-03 04:00:17 +0000 (Sat, 03 Jan 2009)
New Revision: 2420

Added:
   box/trunk/lib/httpserver/
Log:
Copy HTTP server into Box, for use with S3 testing.



From boxbackup-dev@boxbackup.org  Sat Jan  3 04:00:41 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat,  3 Jan 2009 04:00:41 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2421 - box/trunk/test
Message-ID: <20090103040041.A53A6325FD0@www.boxbackup.org>

Author: chris
Date: 2009-01-03 04:00:41 +0000 (Sat, 03 Jan 2009)
New Revision: 2421

Added:
   box/trunk/test/httpserver/
Log:
Copy tests for HTTP server.



From boxbackup-dev@boxbackup.org  Sat Jan  3 04:01:31 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat,  3 Jan 2009 04:01:31 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2422 - box/trunk
Message-ID: <20090103040131.E153D325FCB@www.boxbackup.org>

Author: chris
Date: 2009-01-03 04:01:29 +0000 (Sat, 03 Jan 2009)
New Revision: 2422

Modified:
   box/trunk/modules.txt
Log:
Dependencies for HTTP server.


Modified: box/trunk/modules.txt
===================================================================
--- box/trunk/modules.txt	2009-01-03 04:00:41 UTC (rev 2421)
+++ box/trunk/modules.txt	2009-01-03 04:01:29 UTC (rev 2422)
@@ -41,5 +41,9 @@
 test/backupdiff		lib/backupclient
 test/bbackupd		bin/bbackupd	bin/bbstored bin/bbstoreaccounts bin/bbackupquery bin/bbackupctl lib/server lib/backupstore lib/backupclient	lib/intercept
 
+# HTTP server system
+lib/httpserver          lib/server 
+test/httpserver         lib/httpserver 
+
 # END_IF_DISTRIBUTION
 


From boxbackup-dev@boxbackup.org  Sat Jan  3 04:21:29 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat,  3 Jan 2009 04:21:29 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2423 - in box/trunk: lib/httpserver test/httpserver
Message-ID: <20090103042129.C654A325FCF@www.boxbackup.org>

Author: chris
Date: 2009-01-03 04:21:28 +0000 (Sat, 03 Jan 2009)
New Revision: 2423

Modified:
   box/trunk/lib/httpserver/HTTPRequest.cpp
   box/trunk/lib/httpserver/HTTPServer.cpp
   box/trunk/lib/httpserver/HTTPServer.h
   box/trunk/test/httpserver/testhttpserver.cpp
Log:
Compile and config verify fixes for HTTP server code.


Modified: box/trunk/lib/httpserver/HTTPRequest.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPRequest.cpp	2009-01-03 04:01:29 UTC (rev 2422)
+++ box/trunk/lib/httpserver/HTTPRequest.cpp	2009-01-03 04:21:28 UTC (rev 2423)
@@ -19,6 +19,7 @@
 #include "autogen_HTTPException.h"
 #include "IOStream.h"
 #include "IOStreamGetLine.h"
+#include "Logging.h"
 
 #include "MemLeakFindOn.h"
 
@@ -219,7 +220,8 @@
 		}
 	}
 	
-	TRACE3("HTTPRequest: method=%d, uri=%s, version=%d\n", mMethod, mRequestURI.c_str(), mHTTPVersion);
+	BOX_TRACE("HTTPRequest: method=" << mMethod << ", uri=" <<
+		mRequestURI << ", version=" << mHTTPVersion);
 	
 	// If HTTP 1.1 or greater, assume keep-alive
 	if(mHTTPVersion >= HTTPVersion_1_1)
@@ -254,7 +256,8 @@
 		if(fromBuffer > mContentLength) fromBuffer = mContentLength;
 		if(fromBuffer > 0)
 		{
-			TRACE1("Decoding %d bytes of data from getline buffer\n", fromBuffer);
+			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);
@@ -368,7 +371,9 @@
 					// Truncate the string to just the hostname
 					mHostName = mHostName.substr(0, colon);
 					
-					TRACE2("Host: header, hostname = '%s', host port = %d\n", mHostName.c_str(), mHostPort);
+					BOX_TRACE("Host: header, hostname = " <<
+						"'" << mHostName << "', host "
+						"port = " << mHostPort);
 				}
 			}
 			else if(p == sizeof("Cookie")-1

Modified: box/trunk/lib/httpserver/HTTPServer.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPServer.cpp	2009-01-03 04:01:29 UTC (rev 2422)
+++ box/trunk/lib/httpserver/HTTPServer.cpp	2009-01-03 04:21:28 UTC (rev 2423)
@@ -73,7 +73,7 @@
 {
 	static ConfigurationVerifyKey verifyserverkeys[] = 
 	{
-		HTTPSERVER_VERIFY_SERVER_KEYS(0)	// no default addresses
+		HTTPSERVER_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default addresses
 	};
 
 	static ConfigurationVerify verifyserver[] = 

Modified: box/trunk/lib/httpserver/HTTPServer.h
===================================================================
--- box/trunk/lib/httpserver/HTTPServer.h	2009-01-03 04:01:29 UTC (rev 2422)
+++ box/trunk/lib/httpserver/HTTPServer.h	2009-01-03 04:21:28 UTC (rev 2423)
@@ -65,7 +65,8 @@
 
 // Root level
 #define HTTPSERVER_VERIFY_ROOT_KEYS \
-	{"AddressPrefix", 0, ConfigTest_Exists | ConfigTest_LastEntry, 0}
+	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.

Modified: box/trunk/test/httpserver/testhttpserver.cpp
===================================================================
--- box/trunk/test/httpserver/testhttpserver.cpp	2009-01-03 04:01:29 UTC (rev 2422)
+++ box/trunk/test/httpserver/testhttpserver.cpp	2009-01-03 04:21:28 UTC (rev 2423)
@@ -16,6 +16,7 @@
 #include "HTTPServer.h"
 #include "HTTPRequest.h"
 #include "HTTPResponse.h"
+#include "ServerControl.h"
 
 #include "MemLeakFindOn.h"
 


From boxbackup-dev@boxbackup.org  Sat Jan  3 08:56:14 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat,  3 Jan 2009 08:56:14 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2424 - box/trunk/lib/server
Message-ID: <20090103085614.A524A325026@www.boxbackup.org>

Author: chris
Date: 2009-01-03 08:56:13 +0000 (Sat, 03 Jan 2009)
New Revision: 2424

Modified:
   box/trunk/lib/server/Socket.cpp
   box/trunk/lib/server/Socket.h
   box/trunk/lib/server/SocketListen.h
   box/trunk/lib/server/SocketStream.h
   box/trunk/lib/server/SocketStreamTLS.cpp
Log:
Change use of int to identify a socket type to the proper enum.


Modified: box/trunk/lib/server/Socket.cpp
===================================================================
--- box/trunk/lib/server/Socket.cpp	2009-01-03 04:21:28 UTC (rev 2423)
+++ box/trunk/lib/server/Socket.cpp	2009-01-03 08:56:13 UTC (rev 2424)
@@ -32,13 +32,15 @@
 // --------------------------------------------------------------------------
 //
 // Function
-//		Name:    Socket::NameLookupToSockAddr(SocketAllAddr &, int, char *, int)
+//		Name:    Socket::NameLookupToSockAddr(SocketAllAddr &, int,
+//			 char *, int)
 //		Purpose: Sets up a sockaddr structure given a name and type
 //		Created: 2003/07/31
 //
 // --------------------------------------------------------------------------
 void Socket::NameLookupToSockAddr(SocketAllAddr &addr, int &sockDomain,
-	int Type, const std::string& rName, int Port, int &rSockAddrLenOut)
+	enum Type Type, const std::string& rName, int Port,
+	int &rSockAddrLenOut)
 {
 	int sockAddrLen = 0;
 

Modified: box/trunk/lib/server/Socket.h
===================================================================
--- box/trunk/lib/server/Socket.h	2009-01-03 04:21:28 UTC (rev 2423)
+++ box/trunk/lib/server/Socket.h	2009-01-03 08:56:13 UTC (rev 2424)
@@ -39,14 +39,14 @@
 // --------------------------------------------------------------------------
 namespace Socket
 {
-	enum
+	enum Type
 	{
 		TypeINET = 1,
 		TypeUNIX = 2
 	};
 
 	void NameLookupToSockAddr(SocketAllAddr &addr, int &sockDomain,
-		int Type, const std::string& rName, int Port,
+		enum Type type, const std::string& rName, int Port,
 		int &rSockAddrLenOut);
 	void LogIncomingConnection(const struct sockaddr *addr, socklen_t addrlen);
 	std::string IncomingConnectionLogMessage(const struct sockaddr *addr, socklen_t addrlen);

Modified: box/trunk/lib/server/SocketListen.h
===================================================================
--- box/trunk/lib/server/SocketListen.h	2009-01-03 04:21:28 UTC (rev 2423)
+++ box/trunk/lib/server/SocketListen.h	2009-01-03 08:56:13 UTC (rev 2424)
@@ -125,7 +125,7 @@
 	//		Created: 2003/07/31
 	//
 	// ------------------------------------------------------------------
-	void Listen(int Type, const char *Name, int Port = 0)
+	void Listen(Socket::Type Type, const char *Name, int Port = 0)
 	{
 		if(mSocketHandle != -1)
 		{

Modified: box/trunk/lib/server/SocketStream.h
===================================================================
--- box/trunk/lib/server/SocketStream.h	2009-01-03 04:21:28 UTC (rev 2423)
+++ box/trunk/lib/server/SocketStream.h	2009-01-03 08:56:13 UTC (rev 2424)
@@ -11,6 +11,7 @@
 #define SOCKETSTREAM__H
 
 #include "IOStream.h"
+#include "Socket.h"
 
 #ifdef WIN32
 	typedef SOCKET tOSSocketHandle;
@@ -36,7 +37,7 @@
 	SocketStream(const SocketStream &rToCopy);
 	~SocketStream();
 	
-	void Open(int Type, const std::string& rName, int Port = 0);
+	void Open(Socket::Type Type, const std::string& rName, int Port = 0);
 	void Attach(int socket);
 
 	virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite);

Modified: box/trunk/lib/server/SocketStreamTLS.cpp
===================================================================
--- box/trunk/lib/server/SocketStreamTLS.cpp	2009-01-03 04:21:28 UTC (rev 2423)
+++ box/trunk/lib/server/SocketStreamTLS.cpp	2009-01-03 08:56:13 UTC (rev 2424)
@@ -99,7 +99,7 @@
 //		Created: 2003/08/06
 //
 // --------------------------------------------------------------------------
-void SocketStreamTLS::Open(const TLSContext &rContext, int Type,
+void SocketStreamTLS::Open(const TLSContext &rContext, Socket::Type Type,
 	const std::string& rName, int Port)
 {
 	SocketStream::Open(Type, rName, Port);


From boxbackup-dev@boxbackup.org  Sat Jan  3 08:56:47 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat,  3 Jan 2009 08:56:47 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2425 - box/trunk/lib/server
Message-ID: <20090103085647.2CA31325026@www.boxbackup.org>

Author: chris
Date: 2009-01-03 08:56:47 +0000 (Sat, 03 Jan 2009)
New Revision: 2425

Modified:
   box/trunk/lib/server/SocketStream.cpp
Log:
Reset read and write closed state of a socket after Open() and Attach().


Modified: box/trunk/lib/server/SocketStream.cpp
===================================================================
--- box/trunk/lib/server/SocketStream.cpp	2009-01-03 08:56:13 UTC (rev 2424)
+++ box/trunk/lib/server/SocketStream.cpp	2009-01-03 08:56:47 UTC (rev 2425)
@@ -127,20 +127,23 @@
 		THROW_EXCEPTION(ServerException, SocketAlreadyOpen)
 	}
 
+	ResetCounters();
+
 	mSocketHandle = socket;
-	ResetCounters();
+	mReadClosed = false;
+	mWriteClosed = false;
 }
 
 
 // --------------------------------------------------------------------------
 //
 // Function
-//		Name:    SocketStream::Open(int, char *, int)
+//		Name:    SocketStream::Open(Socket::Type, char *, int)
 //		Purpose: Opens a connection to a listening socket (INET or UNIX)
 //		Created: 2003/07/31
 //
 // --------------------------------------------------------------------------
-void SocketStream::Open(int Type, const std::string& rName, int Port)
+void SocketStream::Open(Socket::Type Type, const std::string& rName, int Port)
 {
 	if(mSocketHandle != INVALID_SOCKET_VALUE) 
 	{
@@ -184,6 +187,9 @@
 	}
 
 	ResetCounters();
+
+	mReadClosed = false;
+	mWriteClosed = false;
 }
 
 // --------------------------------------------------------------------------


From boxbackup-dev@boxbackup.org  Sat Jan  3 08:57:53 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat,  3 Jan 2009 08:57:53 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2426 - box/trunk/lib/common
Message-ID: <20090103085753.39A00325026@www.boxbackup.org>

Author: chris
Date: 2009-01-03 08:57:53 +0000 (Sat, 03 Jan 2009)
New Revision: 2426

Modified:
   box/trunk/lib/common/IOStream.h
Log:
Add a method to write a C style string directly to an IOStream.


Modified: box/trunk/lib/common/IOStream.h
===================================================================
--- box/trunk/lib/common/IOStream.h	2009-01-03 08:56:47 UTC (rev 2425)
+++ box/trunk/lib/common/IOStream.h	2009-01-03 08:57:53 UTC (rev 2426)
@@ -44,6 +44,10 @@
 	virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite) = 0;
 	virtual pos_type BytesLeftToRead();	// may return IOStream::SizeOfStreamUnknown (and will for most stream types)
 	virtual void Write(const void *pBuffer, int NBytes) = 0;
+	virtual void Write(const char *pBuffer)
+	{
+		Write(pBuffer, strlen(pBuffer));
+	}
 	virtual void WriteAllBuffered();
 	virtual pos_type GetPosition() const;
 	virtual void Seek(pos_type Offset, int SeekType);


From boxbackup-dev@boxbackup.org  Sat Jan  3 08:59:08 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat,  3 Jan 2009 08:59:08 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2427 - in box/trunk: lib/common test/bbackupd
Message-ID: <20090103085908.D5152325026@www.boxbackup.org>

Author: chris
Date: 2009-01-03 08:59:08 +0000 (Sat, 03 Jan 2009)
New Revision: 2427

Modified:
   box/trunk/lib/common/Test.h
   box/trunk/test/bbackupd/testbbackupd.cpp
Log:
Move TEST_EQUAL macro into lib/common/Test.h, rename to TEST_EQUAL_LINE, 
provide TEST_EQUAL that only takes two arguments for simplicity.


Modified: box/trunk/lib/common/Test.h
===================================================================
--- box/trunk/lib/common/Test.h	2009-01-03 08:57:53 UTC (rev 2426)
+++ box/trunk/lib/common/Test.h	2009-01-03 08:59:08 UTC (rev 2427)
@@ -77,6 +77,62 @@
 		}																					\
 	}
 
+// utility macro for comparing two strings in a line
+#define TEST_EQUAL(_expected, _found) \
+{ \
+	std::ostringstream _oss1; \
+	_oss1 << _expected; \
+	std::string _exp_str = _oss1.str(); \
+	\
+	std::ostringstream _oss2; \
+	_oss2 << _found; \
+	std::string _found_str = _oss2.str(); \
+	\
+	if(_exp_str != _found_str) \
+	{ \
+		printf("Expected <%s> but found <%s>\n", \
+			_exp_str.c_str(), _found_str.c_str()); \
+		\
+		std::ostringstream _oss3; \
+		_oss3 << #_found << " != " << #_expected; \
+		\
+		TEST_FAIL_WITH_MESSAGE(_oss3.str().c_str()); \
+	} \
+}
+
+// utility macro for comparing two strings in a line
+#define TEST_EQUAL_LINE(_expected, _found, _line) \
+{ \
+	std::ostringstream _oss1; \
+	_oss1 << _expected; \
+	std::string _exp_str = _oss1.str(); \
+	\
+	std::ostringstream _oss2; \
+	_oss2 << _found; \
+	std::string _found_str = _oss2.str(); \
+	\
+	if(_exp_str != _found_str) \
+	{ \
+		std::string _line_str = _line; \
+		printf("Expected <%s> but found <%s> in <%s>\n", \
+			_exp_str.c_str(), _found_str.c_str(), _line_str.c_str()); \
+		\
+		std::ostringstream _oss3; \
+		_oss3 << #_found << " != " << #_expected << " in " << _line; \
+		\
+		TEST_FAIL_WITH_MESSAGE(_oss3.str().c_str()); \
+	} \
+}
+
+
+// utility macro for testing a line
+#define TEST_LINE(_condition, _line) \
+	TEST_THAT(_condition); \
+	if (!(_condition)) \
+	{ \
+		printf("Test failed on <%s>\n", _line.c_str()); \
+	}
+
 bool TestFileExists(const char *Filename);
 bool TestDirExists(const char *Filename);
 

Modified: box/trunk/test/bbackupd/testbbackupd.cpp
===================================================================
--- box/trunk/test/bbackupd/testbbackupd.cpp	2009-01-03 08:57:53 UTC (rev 2426)
+++ box/trunk/test/bbackupd/testbbackupd.cpp	2009-01-03 08:59:08 UTC (rev 2427)
@@ -87,38 +87,6 @@
 // two cycles and a bit
 #define TIME_TO_WAIT_FOR_BACKUP_OPERATION	12
 
-// utility macro for comparing two strings in a line
-#define TEST_EQUAL(_expected, _found, _line) \
-{ \
-	std::ostringstream _oss1; \
-	_oss1 << _expected; \
-	std::string _exp_str = _oss1.str(); \
-	\
-	std::ostringstream _oss2; \
-	_oss2 << _found; \
-	std::string _found_str = _oss2.str(); \
-	\
-	if(_exp_str != _found_str) \
-	{ \
-		std::string _line_str = _line; \
-		printf("Expected <%s> but found <%s> in <%s>\n", \
-			_exp_str.c_str(), _found_str.c_str(), _line_str.c_str()); \
-		\
-		std::ostringstream _oss3; \
-		_oss3 << #_found << " != " << #_expected << " in " << _line; \
-		\
-		TEST_FAIL_WITH_MESSAGE(_oss3.str().c_str()); \
-	} \
-}
-
-// utility macro for testing a line
-#define TEST_LINE(_condition, _line) \
-	TEST_THAT(_condition); \
-	if (!(_condition)) \
-	{ \
-		printf("Test failed on <%s>\n", _line.c_str()); \
-	}
-
 void wait_for_backup_operation(int seconds = TIME_TO_WAIT_FOR_BACKUP_OPERATION)
 {
 	wait_for_operation(seconds);
@@ -702,7 +670,7 @@
 		result = daemon.Main("testfiles/bbackupd.conf", 1, argv);
 	}
 	
-	TEST_EQUAL(0, result, "Daemon exit code");
+	TEST_EQUAL_LINE(0, result, "Daemon exit code");
 	
 	// ensure that no child processes end up running tests!
 	if (getpid() != own_pid)
@@ -915,7 +883,8 @@
 		char buffer[10000];
 		memset(buffer, 0, sizeof(buffer));
 
-		TEST_EQUAL(sizeof(buffer), write(fd, buffer, sizeof(buffer)),
+		TEST_EQUAL_LINE(sizeof(buffer),
+			write(fd, buffer, sizeof(buffer)),
 			"Buffer write");
 		TEST_THAT(close(fd) == 0);
 		
@@ -936,7 +905,8 @@
 		fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY);
 		TEST_THAT(fd > 0);
 		// write again, to update the file's timestamp
-		TEST_EQUAL(sizeof(buffer), write(fd, buffer, sizeof(buffer)),
+		TEST_EQUAL_LINE(sizeof(buffer),
+			write(fd, buffer, sizeof(buffer)),
 			"Buffer write");
 		TEST_THAT(close(fd) == 0);	
 
@@ -969,20 +939,22 @@
 			std::string line;
 			TEST_THAT(reader.GetLine(line));
 			std::string comp = "Receive Success(0x";
-			TEST_EQUAL(comp, line.substr(0, comp.size()), line);
+			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
+				line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Receiving stream, size 124", line, line);
+			TEST_EQUAL("Receiving stream, size 124", line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Send GetIsAlive()", line, line);
+			TEST_EQUAL("Send GetIsAlive()", line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Receive IsAlive()", line, line);
+			TEST_EQUAL("Receive IsAlive()", line);
 
 			TEST_THAT(reader.GetLine(line));
 			comp = "Send StoreFile(0x3,";
-			TEST_EQUAL(comp, line.substr(0, comp.size()), line);
+			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
+				line);
 			comp = ",\"f1\")";
 			std::string sub = line.substr(line.size() - comp.size());
-			TEST_EQUAL(comp, sub, line);
+			TEST_EQUAL_LINE(comp, sub, line);
 			std::string comp2 = ",0x0,";
 			sub = line.substr(line.size() - comp.size() -
 				comp2.size() + 1, comp2.size());
@@ -1008,7 +980,8 @@
 		fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY);
 		TEST_THAT(fd > 0);
 		// write again, to update the file's timestamp
-		TEST_EQUAL(sizeof(buffer), write(fd, buffer, sizeof(buffer)),
+		TEST_EQUAL_LINE(sizeof(buffer),
+			write(fd, buffer, sizeof(buffer)),
 			"Buffer write");
 		TEST_THAT(close(fd) == 0);	
 
@@ -1038,9 +1011,10 @@
 			std::string line;
 			TEST_THAT(reader.GetLine(line));
 			std::string comp = "Receive Success(0x";
-			TEST_EQUAL(comp, line.substr(0, comp.size()), line);
+			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
+				line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Receiving stream, size 124", line, line);
+			TEST_EQUAL("Receiving stream, size 124", line);
 
 			// delaying for 4 seconds in one step means that
 			// the diff timer and the keepalive timer will
@@ -1049,10 +1023,11 @@
 
 			TEST_THAT(reader.GetLine(line));
 			comp = "Send StoreFile(0x3,";
-			TEST_EQUAL(comp, line.substr(0, comp.size()), line);
+			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
+				line);
 			comp = ",0x0,\"f1\")";
 			std::string sub = line.substr(line.size() - comp.size());
-			TEST_EQUAL(comp, sub, line);
+			TEST_EQUAL_LINE(comp, sub, line);
 		}
 
 		if (failures > 0)
@@ -1070,7 +1045,8 @@
 		fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY);
 		TEST_THAT(fd > 0);
 		// write again, to update the file's timestamp
-		TEST_EQUAL(sizeof(buffer), write(fd, buffer, sizeof(buffer)),
+		TEST_EQUAL_LINE(sizeof(buffer),
+			write(fd, buffer, sizeof(buffer)),
 			"Buffer write");
 		TEST_THAT(close(fd) == 0);	
 
@@ -1100,9 +1076,10 @@
 			std::string line;
 			TEST_THAT(reader.GetLine(line));
 			std::string comp = "Receive Success(0x";
-			TEST_EQUAL(comp, line.substr(0, comp.size()), line);
+			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
+				line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Receiving stream, size 124", line, line);
+			TEST_EQUAL("Receiving stream, size 124", line);
 
 			// delaying for 3 seconds in steps of 1 second
 			// means that the keepalive timer will expire 3 times,
@@ -1111,23 +1088,24 @@
 			// only two keepalives.
 			
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Send GetIsAlive()", line, line);
+			TEST_EQUAL("Send GetIsAlive()", line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Receive IsAlive()", line, line);
+			TEST_EQUAL("Receive IsAlive()", line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Send GetIsAlive()", line, line);
+			TEST_EQUAL("Send GetIsAlive()", line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Receive IsAlive()", line, line);
+			TEST_EQUAL("Receive IsAlive()", line);
 
 			// but two matching blocks should have been found
 			// already, so the upload should be a diff.
 
 			TEST_THAT(reader.GetLine(line));
 			comp = "Send StoreFile(0x3,";
-			TEST_EQUAL(comp, line.substr(0, comp.size()), line);
+			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
+				line);
 			comp = ",\"f1\")";
 			std::string sub = line.substr(line.size() - comp.size());
-			TEST_EQUAL(comp, sub, line);
+			TEST_EQUAL_LINE(comp, sub, line);
 			std::string comp2 = ",0x0,";
 			sub = line.substr(line.size() - comp.size() -
 				comp2.size() + 1, comp2.size());
@@ -1156,7 +1134,8 @@
 		fd = open(touchfile.c_str(), O_CREAT | O_WRONLY);
 		TEST_THAT(fd > 0);
 		// write again, to update the file's timestamp
-		TEST_EQUAL(sizeof(buffer), write(fd, buffer, sizeof(buffer)),
+		TEST_EQUAL_LINE(sizeof(buffer),
+			write(fd, buffer, sizeof(buffer)),
 			"Buffer write");
 		TEST_THAT(close(fd) == 0);	
 
@@ -1202,17 +1181,17 @@
 		{
 			std::string line;
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Receive Success(0x3)", line, line);
+			TEST_EQUAL("Receive Success(0x3)", line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Receiving stream, size 425", line, line);
+			TEST_EQUAL("Receiving stream, size 425", line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Send GetIsAlive()", line, line);
+			TEST_EQUAL("Send GetIsAlive()", line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Receive IsAlive()", line, line);
+			TEST_EQUAL("Receive IsAlive()", line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Send GetIsAlive()", line, line);
+			TEST_EQUAL("Send GetIsAlive()", line);
 			TEST_THAT(reader.GetLine(line));
-			TEST_EQUAL("Receive IsAlive()", line, line);
+			TEST_EQUAL("Receive IsAlive()", line);
 		}
 
 		if (failures > 0)
@@ -1342,10 +1321,11 @@
 		
 			std::auto_ptr<BackupProtocolClientAccountUsage> usage(
 				client->QueryGetAccountUsage());
-			TEST_EQUAL(24, usage->GetBlocksUsed(), "blocks used");
-			TEST_EQUAL(0,  usage->GetBlocksInDeletedFiles(),
+			TEST_EQUAL_LINE(24, usage->GetBlocksUsed(),
+				"blocks used");
+			TEST_EQUAL_LINE(0,  usage->GetBlocksInDeletedFiles(),
 				"deleted blocks");
-			TEST_EQUAL(16, usage->GetBlocksInDirectories(),
+			TEST_EQUAL_LINE(16, usage->GetBlocksInDirectories(),
 				"directory blocks");
 
 			client->QueryFinished();
@@ -1467,10 +1447,11 @@
 
 			std::auto_ptr<BackupProtocolClientAccountUsage> usage(
 				client->QueryGetAccountUsage());
-			TEST_EQUAL(24, usage->GetBlocksUsed(), "blocks used");
-			TEST_EQUAL(4, usage->GetBlocksInDeletedFiles(),
+			TEST_EQUAL_LINE(24, usage->GetBlocksUsed(),
+				"blocks used");
+			TEST_EQUAL_LINE(4, usage->GetBlocksInDeletedFiles(),
 				"deleted blocks");
-			TEST_EQUAL(16, usage->GetBlocksInDirectories(),
+			TEST_EQUAL_LINE(16, usage->GetBlocksInDirectories(),
 				"directory blocks");
 			// d1/f3 and d1/f4 are the only two files on the
 			// server which are not deleted, they use 2 blocks
@@ -1522,10 +1503,11 @@
 
 			std::auto_ptr<BackupProtocolClientAccountUsage> usage(
 				client->QueryGetAccountUsage());
-			TEST_EQUAL(16, usage->GetBlocksUsed(), "blocks used");
-			TEST_EQUAL(0, usage->GetBlocksInDeletedFiles(),
+			TEST_EQUAL_LINE(16, usage->GetBlocksUsed(),
+				"blocks used");
+			TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(),
 				"deleted blocks");
-			TEST_EQUAL(12, usage->GetBlocksInDirectories(),
+			TEST_EQUAL_LINE(12, usage->GetBlocksInDirectories(),
 				"directory blocks");
 			// d1/f3 and d1/f4 are the only two files on the
 			// server, they use 2 blocks each, the rest is
@@ -1576,10 +1558,11 @@
 
 			std::auto_ptr<BackupProtocolClientAccountUsage> usage(
 				client->QueryGetAccountUsage());
-			TEST_EQUAL(22, usage->GetBlocksUsed(), "blocks used");
-			TEST_EQUAL(0, usage->GetBlocksInDeletedFiles(),
+			TEST_EQUAL_LINE(22, usage->GetBlocksUsed(),
+				"blocks used");
+			TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(),
 				"deleted blocks");
-			TEST_EQUAL(14, usage->GetBlocksInDirectories(),
+			TEST_EQUAL_LINE(14, usage->GetBlocksInDirectories(),
 				"directory blocks");
 			// d2/f6, d6/d8 and d6/d8/f7 are new
 			// i.e. 2 new files, 1 new directory
@@ -1862,7 +1845,7 @@
 		std::string line;
 		TEST_THAT(gl.GetLine(line));
 		TEST_THAT(line != "before");
-		TEST_EQUAL("after", line, line);
+		TEST_EQUAL("after", line);
 
 		#undef SYM_DIR
 
@@ -1880,7 +1863,7 @@
 		struct stat stat_st, lstat_st;
 		TEST_THAT(stat("testfiles/symlink-to-TestDir1", &stat_st) == 0);
 		TEST_THAT(lstat("testfiles/symlink-to-TestDir1", &lstat_st) == 0);
-		TEST_EQUAL((stat_st.st_dev ^ 0xFFFF), lstat_st.st_dev,
+		TEST_EQUAL_LINE((stat_st.st_dev ^ 0xFFFF), lstat_st.st_dev,
 			"stat vs lstat");
 
 		BackupDaemon bbackupd;
@@ -2219,7 +2202,8 @@
 
 			std::string data("hello world\n");
 			fs.Write(data.c_str(), data.size());
-			TEST_EQUAL(12, fs.GetPosition(), "FileStream position");
+			TEST_EQUAL_LINE(12, fs.GetPosition(),
+				"FileStream position");
 			fs.Close();
 		}
 


From boxbackup-dev@boxbackup.org  Sat Jan  3 08:59:48 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat,  3 Jan 2009 08:59:48 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2428 - in box/trunk: lib/httpserver test/httpserver
Message-ID: <20090103085948.00D11325026@www.boxbackup.org>

Author: chris
Date: 2009-01-03 08:59:47 +0000 (Sat, 03 Jan 2009)
New Revision: 2428

Modified:
   box/trunk/lib/httpserver/HTTPException.txt
   box/trunk/lib/httpserver/HTTPQueryDecoder.cpp
   box/trunk/lib/httpserver/HTTPRequest.cpp
   box/trunk/lib/httpserver/HTTPRequest.h
   box/trunk/lib/httpserver/HTTPResponse.cpp
   box/trunk/lib/httpserver/HTTPResponse.h
   box/trunk/test/httpserver/testhttpserver.cpp
Log:
Add ability to send an HTTPRequest to a socket and to parse an 
HTTPResponse from a socket, to create a simple HTTP client.


Modified: box/trunk/lib/httpserver/HTTPException.txt
===================================================================
--- box/trunk/lib/httpserver/HTTPException.txt	2009-01-03 08:59:08 UTC (rev 2427)
+++ box/trunk/lib/httpserver/HTTPException.txt	2009-01-03 08:59:47 UTC (rev 2428)
@@ -1,12 +1,15 @@
 EXCEPTION HTTP 10
 
 Internal							0
-RequestReadFailed					1
-RequestAlreadyBeenRead				2
+RequestReadFailed						1
+RequestAlreadyBeenRead						2
 BadRequest							3
-UnknownResponseCodeUsed				4
-NoContentTypeSet					5
-POSTContentTooLong					6
-CannotSetRedirectIfReponseHasData	7
-CannotSetNotFoundIfReponseHasData	8
-NotImplemented						9
+UnknownResponseCodeUsed						4
+NoContentTypeSet						5
+POSTContentTooLong						6
+CannotSetRedirectIfReponseHasData				7
+CannotSetNotFoundIfReponseHasData				8
+NotImplemented							9
+RequestNotInitialised						10
+BadResponse							11
+ResponseReadFailed						12

Modified: box/trunk/lib/httpserver/HTTPQueryDecoder.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPQueryDecoder.cpp	2009-01-03 08:59:08 UTC (rev 2427)
+++ box/trunk/lib/httpserver/HTTPQueryDecoder.cpp	2009-01-03 08:59:47 UTC (rev 2428)
@@ -19,9 +19,10 @@
 // --------------------------------------------------------------------------
 //
 // Function
-//		Name:    HTTPQueryDecoder::HTTPQueryDecoder(HTTPRequest::Query_t &)
-//		Purpose: Constructor. Pass in the query contents you want to decode
-//				 the query string into.
+//		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
 //
 // --------------------------------------------------------------------------

Modified: box/trunk/lib/httpserver/HTTPRequest.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPRequest.cpp	2009-01-03 08:59:08 UTC (rev 2427)
+++ box/trunk/lib/httpserver/HTTPRequest.cpp	2009-01-03 08:59:47 UTC (rev 2428)
@@ -52,6 +52,28 @@
 // --------------------------------------------------------------------------
 //
 // 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)
+{
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
 //		Name:    HTTPRequest::~HTTPRequest()
 //		Purpose: Destructor
 //		Created: 26/3/04
@@ -72,9 +94,10 @@
 //
 // Function
 //		Name:    HTTPRequest::Read(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.
+//		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
 //
 // --------------------------------------------------------------------------
@@ -289,6 +312,89 @@
 // --------------------------------------------------------------------------
 //
 // Function
+//		Name:    HTTPRequest::Write(IOStream &, int)
+//		Purpose: Write the request to an IOStream using HTTP.
+//		Created: 03/01/09
+//
+// --------------------------------------------------------------------------
+bool HTTPRequest::Write(IOStream &rStream, int Timeout)
+{
+	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;
+	}
+
+	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";
+	}
+
+	rStream.Write(oss.str().c_str());
+	rStream.Write("\n");
+
+	return true;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
 //		Name:    HTTPRequest::ParseHeaders(IOStreamGetLine &, int)
 //		Purpose: Private. Parse the headers of the request
 //		Created: 26/3/04

Modified: box/trunk/lib/httpserver/HTTPRequest.h
===================================================================
--- box/trunk/lib/httpserver/HTTPRequest.h	2009-01-03 08:59:08 UTC (rev 2427)
+++ box/trunk/lib/httpserver/HTTPRequest.h	2009-01-03 08:59:47 UTC (rev 2428)
@@ -27,7 +27,17 @@
 class HTTPRequest
 {
 public:
+	enum Method
+	{
+		Method_UNINITIALISED = -1,
+		Method_UNKNOWN = 0,
+		Method_GET = 1,
+		Method_HEAD = 2,
+		Method_POST = 3
+	};
+	
 	HTTPRequest();
+	HTTPRequest(enum Method method, const std::string& rURI);
 	~HTTPRequest();
 private:
 	// no copying
@@ -40,15 +50,6 @@
 
 	enum
 	{
-		Method_UNINITIALISED = -1,
-		Method_UNKNOWN = 0,
-		Method_GET = 1,
-		Method_HEAD = 2,
-		Method_POST = 3
-	};
-	
-	enum
-	{
 		HTTPVersion__MajorMultiplier = 1000,
 		HTTPVersion_0_9 = 9,
 		HTTPVersion_1_0 = 1000,
@@ -56,6 +57,7 @@
 	};
 
 	bool Read(IOStreamGetLine &rGetLine, int Timeout);
+	bool Write(IOStream &rStream, int Timeout);
 
 	typedef std::map<std::string, std::string> CookieJar_t;
 	
@@ -67,7 +69,7 @@
 	//		Created: 26/3/04
 	//
 	// --------------------------------------------------------------------------
-	int GetMethod() const {return mMethod;}
+	enum Method GetMethod() const {return mMethod;}
 	const std::string &GetRequestURI() const {return mRequestURI;}
 	const std::string &GetHostName() const {return mHostName;}	// note: request does splitting of Host: header
 	const int GetHostPort() const {return mHostPort;}  // into host name and port number
@@ -91,13 +93,17 @@
 	//
 	// --------------------------------------------------------------------------
 	bool GetClientKeepAliveRequested() const {return mClientKeepAliveRequested;}
+	void SetClientKeepAliveRequested(bool keepAlive)
+	{
+		mClientKeepAliveRequested = keepAlive;
+	}
 
 private:
 	void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout);
 	void ParseCookies(const std::string &rHeader, int DataStarts);
 
 private:
-	int mMethod;
+	enum Method mMethod;
 	std::string mRequestURI;
 	std::string mHostName;
 	int mHostPort;

Modified: box/trunk/lib/httpserver/HTTPResponse.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPResponse.cpp	2009-01-03 08:59:08 UTC (rev 2427)
+++ box/trunk/lib/httpserver/HTTPResponse.cpp	2009-01-03 08:59:47 UTC (rev 2428)
@@ -13,6 +13,7 @@
 #include <string.h>
 
 #include "HTTPResponse.h"
+#include "IOStreamGetLine.h"
 #include "autogen_HTTPException.h"
 
 #include "MemLeakFindOn.h"
@@ -32,7 +33,8 @@
 HTTPResponse::HTTPResponse()
 	: mResponseCode(HTTPResponse::Code_NoContent),
 	  mResponseIsDynamicContent(true),
-	  mKeepAlive(false)
+	  mKeepAlive(false),
+	  mContentLength(-1)
 {
 }
 
@@ -54,7 +56,8 @@
 //
 // Function
 //		Name:    HTTPResponse::ResponseCodeToString(int)
-//		Purpose: Return string equivalent of the response code, suitable for Status: headers
+//		Purpose: Return string equivalent of the response code,
+//			 suitable for Status: headers
 //		Created: 26/3/04
 //
 // --------------------------------------------------------------------------
@@ -114,8 +117,8 @@
 //
 // Function
 //		Name:    HTTPResponse::Send(IOStream &, bool)
-//		Purpose: Build the response, and send via the stream. Optionally omitting
-//				 the content.
+//		Purpose: Build the response, and send via the stream.
+//			 Optionally omitting the content.
 //		Created: 26/3/04
 //
 // --------------------------------------------------------------------------
@@ -139,10 +142,10 @@
 			header += len;
 		}
 		// Extra headers...
-		for(std::vector<std::string>::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i)
+		for(std::vector<std::pair<std::string, std::string> >::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i)
 		{
 			header += "\r\n";
-			header += *i;
+			header += i->first + ": " + i->second;
 		}
 		// NOTE: a line ending must be included here in all cases
 		// Control whether the response is cached
@@ -181,17 +184,215 @@
 // --------------------------------------------------------------------------
 //
 // 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;
+
+	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);
+		}
+
+		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
@@ -200,12 +401,13 @@
 //		Created: 26/3/04
 //
 // --------------------------------------------------------------------------
+/*
 void HTTPResponse::AddHeader(const std::string &rEntireHeaderLine)
 {
 	mExtraHeaders.push_back(rEntireHeaderLine);
 }
+*/
 
-
 // --------------------------------------------------------------------------
 //
 // Function
@@ -214,12 +416,9 @@
 //		Created: 26/3/04
 //
 // --------------------------------------------------------------------------
-void HTTPResponse::AddHeader(const char *Header, const char *Value)
+void HTTPResponse::AddHeader(const char *pHeader, const char *pValue)
 {
-	std::string h(Header);
-	h += ": ";
-	h += Value;
-	mExtraHeaders.push_back(h);
+	mExtraHeaders.push_back(Header(pHeader, pValue));
 }
 
 
@@ -231,12 +430,9 @@
 //		Created: 26/3/04
 //
 // --------------------------------------------------------------------------
-void HTTPResponse::AddHeader(const char *Header, const std::string &rValue)
+void HTTPResponse::AddHeader(const char *pHeader, const std::string &rValue)
 {
-	std::string h(Header);
-	h += ": ";
-	h += rValue;
-	mExtraHeaders.push_back(h);
+	mExtraHeaders.push_back(Header(pHeader, rValue));
 }
 
 
@@ -250,7 +446,7 @@
 // --------------------------------------------------------------------------
 void HTTPResponse::AddHeader(const std::string &rHeader, const std::string &rValue)
 {
-	mExtraHeaders.push_back(rHeader + ": " + rValue);
+	mExtraHeaders.push_back(Header(rHeader, rValue));
 }
 
 
@@ -279,14 +475,14 @@
 	h += Path;
 	h += "\"";
 */
-	std::string h("Set-Cookie: ");
+	std::string h;
 	h += Name;
 	h += "=";
 	h += Value;
 	h += "; Version=1; Path=";
 	h += Path;
 
-	mExtraHeaders.push_back(h);
+	mExtraHeaders.push_back(Header("Set-Cookie", h));
 }
 
 
@@ -312,10 +508,10 @@
 	mResponseCode = Code_Found;
 
 	// Set location to redirect to
-	std::string header("Location: ");
+	std::string header;
 	if(IsLocalURI) header += msDefaultURIPrefix;
 	header += RedirectTo;
-	mExtraHeaders.push_back(header);
+	mExtraHeaders.push_back(Header("Location", header));
 	
 	// Set up some default content
 	mContentType = "text/html";

Modified: box/trunk/lib/httpserver/HTTPResponse.h
===================================================================
--- box/trunk/lib/httpserver/HTTPResponse.h	2009-01-03 08:59:08 UTC (rev 2427)
+++ box/trunk/lib/httpserver/HTTPResponse.h	2009-01-03 08:59:47 UTC (rev 2428)
@@ -15,6 +15,8 @@
 
 #include "CollectInBufferStream.h"
 
+class IOStreamGetLine;
+
 // --------------------------------------------------------------------------
 //
 // Class
@@ -35,15 +37,18 @@
 public:
 
 	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(IOStream &rStream, bool OmitContent = false);
+	void Receive(IOStream& rStream, int Timeout = IOStream::TimeOutInfinite);
 
-	void AddHeader(const char *EntireHeaderLine);
-	void AddHeader(const std::string &rEntireHeaderLine);
+	// 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);
@@ -106,9 +111,13 @@
 	bool mResponseIsDynamicContent;
 	bool mKeepAlive;
 	std::string mContentType;
-	std::vector<std::string> mExtraHeaders;
+	typedef std::pair<std::string, std::string> Header;
+	std::vector<Header> mExtraHeaders;
+	int mContentLength; // only used when reading response from stream
 	
 	static std::string msDefaultURIPrefix;
+
+	void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout);
 };
 
 #endif // HTTPRESPONSE__H

Modified: box/trunk/test/httpserver/testhttpserver.cpp
===================================================================
--- box/trunk/test/httpserver/testhttpserver.cpp	2009-01-03 08:59:08 UTC (rev 2427)
+++ box/trunk/test/httpserver/testhttpserver.cpp	2009-01-03 08:59:47 UTC (rev 2428)
@@ -16,6 +16,7 @@
 #include "HTTPServer.h"
 #include "HTTPRequest.h"
 #include "HTTPResponse.h"
+#include "IOStreamGetLine.h"
 #include "ServerControl.h"
 
 #include "MemLeakFindOn.h"
@@ -71,6 +72,7 @@
 		case HTTPRequest::Method_GET: m = "GET "; break;
 		case HTTPRequest::Method_HEAD: m = "HEAD"; break;
 		case HTTPRequest::Method_POST: m = "POST"; break;
+		default: m = "UNKNOWN";
 		}
 		rResponse.Write(m, 4);
 	}
@@ -125,16 +127,93 @@
 	// Start the server
 	int pid = LaunchServer("./test server testfiles/httpserver.conf", "testfiles/httpserver.pid");
 	TEST_THAT(pid != -1 && pid != 0);
-	if(pid > 0)
+	if(pid <= 0)
 	{
-		// Run the request script
-		TEST_THAT(::system("perl testfiles/testrequests.pl") == 0);
-	
-		// Kill it
-		TEST_THAT(KillServer(pid));
-		TestRemoteProcessMemLeaks("generic-httpserver.memleaks");
+		return 0;
 	}
 
+	// Run the request script
+	TEST_THAT(::system("perl testfiles/testrequests.pl") == 0);
+
+	signal(SIGPIPE, SIG_IGN);
+
+	SocketStream sock;
+	sock.Open(Socket::TypeINET, "localhost", 1080);
+
+	for (int i = 0; i < 4; i++)
+	{
+		HTTPRequest request(HTTPRequest::Method_GET,
+			"/test-one/34/341s/234?p1=vOne&p2=vTwo");
+
+		if (i >= 2)
+		{
+			// first set of passes has keepalive off by default,
+			// so when i == 1 the socket has already been closed
+			// by the server, and we'll get -EPIPE when we try
+			// to send the request.
+			request.SetClientKeepAliveRequested(true);
+		}
+
+		if (i == 1)
+		{
+			TEST_CHECK_THROWS(request.Write(sock,
+				IOStream::TimeOutInfinite),
+				ConnectionException, SocketWriteError);
+			sock.Close();
+			sock.Open(Socket::TypeINET, "localhost", 1080);
+			continue;
+		}
+		else
+		{
+			request.Write(sock, IOStream::TimeOutInfinite);
+		}
+
+		HTTPResponse response;
+		response.Receive(sock);
+		
+		TEST_THAT(response.GetResponseCode() == HTTPResponse::Code_OK);
+		TEST_THAT(response.GetContentType() == "text/html");
+
+		IOStreamGetLine getline(response);
+		std::string line;
+
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("<html>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("<head><title>TEST SERVER RESPONSE</title></head>",
+			line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("<body><h1>Test response</h1>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("<p><b>URI:</b> /test-one/34/341s/234</p>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("<p><b>Query string:</b> p1=vOne&p2=vTwo</p>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("<p><b>Method:</b> GET </p>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("<p><b>Decoded query:</b><br>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("PARAM:p1=vOne<br>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("PARAM:p2=vTwo<br></p>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("<p><b>Content type:</b> </p>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("<p><b>Content length:</b> -1</p>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("<p><b>Cookies:</b><br>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("</p>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("</body>", line);
+		TEST_THAT(getline.GetLine(line));
+		TEST_EQUAL("</html>", line);
+	}
+	
+	// Kill it
+	TEST_THAT(KillServer(pid));
+	TestRemoteProcessMemLeaks("generic-httpserver.memleaks");
+
 	return 0;
 }
 


From boxbackup-dev@boxbackup.org  Sat Jan  3 18:57:37 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat,  3 Jan 2009 18:57:37 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2429 - box/trunk/lib/server
Message-ID: <20090103185737.B6689325026@www.boxbackup.org>

Author: chris
Date: 2009-01-03 18:57:36 +0000 (Sat, 03 Jan 2009)
New Revision: 2429

Modified:
   box/trunk/lib/server/SocketStreamTLS.h
Log:
Change socket type from int to Socket::Type enum.


Modified: box/trunk/lib/server/SocketStreamTLS.h
===================================================================
--- box/trunk/lib/server/SocketStreamTLS.h	2009-01-03 08:59:47 UTC (rev 2428)
+++ box/trunk/lib/server/SocketStreamTLS.h	2009-01-03 18:57:36 UTC (rev 2429)
@@ -38,7 +38,7 @@
 	SocketStreamTLS(const SocketStreamTLS &rToCopy);
 public:
 
-	void Open(const TLSContext &rContext, int Type,
+	void Open(const TLSContext &rContext, Socket::Type Type,
 		const std::string& rName, int Port = 0);
 	void Handshake(const TLSContext &rContext, bool IsServer = false);
 	


From boxbackup-dev@boxbackup.org  Sat Jan  3 18:58:44 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat,  3 Jan 2009 18:58:44 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2430 - box/trunk/bin/bbackupquery
Message-ID: <20090103185844.12001325026@www.boxbackup.org>

Author: chris
Date: 2009-01-03 18:58:43 +0000 (Sat, 03 Jan 2009)
New Revision: 2430

Modified:
   box/trunk/bin/bbackupquery/BackupQueries.cpp
   box/trunk/bin/bbackupquery/BackupQueries.h
   box/trunk/bin/bbackupquery/BoxBackupCompareParams.h
Log:
Compare API for notification on starting to compare a file or directory.


Modified: box/trunk/bin/bbackupquery/BackupQueries.cpp
===================================================================
--- box/trunk/bin/bbackupquery/BackupQueries.cpp	2009-01-03 18:57:36 UTC (rev 2429)
+++ box/trunk/bin/bbackupquery/BackupQueries.cpp	2009-01-03 18:58:43 UTC (rev 2430)
@@ -1436,6 +1436,8 @@
 void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir,
 	const std::string &rLocalDir, BoxBackupCompareParams &rParams)
 {
+	rParams.NotifyDirComparing(rLocalDir, rStoreDir);
+
 	// Get info on the local directory
 	struct stat st;
 	if(::lstat(rLocalDir.c_str(), &st) != 0)
@@ -1622,6 +1624,8 @@
 			std::string localPath(MakeFullPath(rLocalDir, fileName));
 			std::string storePath(rStoreDir + "/" + fileName);
 
+			rParams.NotifyFileComparing(localPath, storePath);
+			
 			// Does the file exist locally?
 			string_set_iter_t local(localFiles.find(fileName));
 			if(local == localFiles.end())
@@ -1631,7 +1635,7 @@
 					storePath);
 			}
 			else
-			{
+			{				
 				int64_t fileSize = 0;
 
 				struct stat st;

Modified: box/trunk/bin/bbackupquery/BackupQueries.h
===================================================================
--- box/trunk/bin/bbackupquery/BackupQueries.h	2009-01-03 18:57:36 UTC (rev 2429)
+++ box/trunk/bin/bbackupquery/BackupQueries.h	2009-01-03 18:58:43 UTC (rev 2430)
@@ -213,6 +213,11 @@
 			mExcludedDirs ++;
 		}
 
+		virtual void NotifyDirComparing(const std::string& rLocalPath,
+			const std::string& rRemotePath)
+		{
+		}
+
 		virtual void NotifyDirCompared(
 			const std::string& rLocalPath,
 			const std::string& rRemotePath,
@@ -238,7 +243,12 @@
 				}
 			}
 		}
-			
+
+		virtual void NotifyFileComparing(const std::string& rLocalPath,
+			const std::string& rRemotePath)
+		{
+		}
+		
 		virtual void NotifyFileCompared(const std::string& rLocalPath,
 			const std::string& rRemotePath, int64_t NumBytes,
 			bool HasDifferentAttributes, bool HasDifferentContents,

Modified: box/trunk/bin/bbackupquery/BoxBackupCompareParams.h
===================================================================
--- box/trunk/bin/bbackupquery/BoxBackupCompareParams.h	2009-01-03 18:57:36 UTC (rev 2429)
+++ box/trunk/bin/bbackupquery/BoxBackupCompareParams.h	2009-01-03 18:58:43 UTC (rev 2430)
@@ -90,9 +90,13 @@
 		const std::string& rRemotePath) = 0;
 	virtual void NotifyExcludedDir(const std::string& rLocalPath,
 		const std::string& rRemotePath) = 0;
+	virtual void NotifyDirComparing(const std::string& rLocalPath,
+		const std::string& rRemotePath) = 0;
 	virtual void NotifyDirCompared(const std::string& rLocalPath,
 		const std::string& rRemotePath,	bool HasDifferentAttributes,
 		bool modifiedAfterLastSync) = 0;
+	virtual void NotifyFileComparing(const std::string& rLocalPath,
+		const std::string& rRemotePath) = 0;
 	virtual void NotifyFileCompared(const std::string& rLocalPath,
 		const std::string& rRemotePath, int64_t NumBytes,
 		bool HasDifferentAttributes, bool HasDifferentContents,


From boxbackup-dev@boxbackup.org  Sun Jan  4 13:42:20 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sun,  4 Jan 2009 13:42:20 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2431 - box/trunk/lib/httpserver
Message-ID: <20090104134220.BFEBB325026@www.boxbackup.org>

Author: chris
Date: 2009-01-04 13:42:19 +0000 (Sun, 04 Jan 2009)
New Revision: 2431

Modified:
   box/trunk/lib/httpserver/HTTPResponse.cpp
Log:
Add HTTP status code 400, method not allowed.

Fix calculation of remaining content size when some bytes were buffered 
by the IOStreamGetLine.


Modified: box/trunk/lib/httpserver/HTTPResponse.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPResponse.cpp	2009-01-03 18:58:43 UTC (rev 2430)
+++ box/trunk/lib/httpserver/HTTPResponse.cpp	2009-01-04 13:42:19 UTC (rev 2431)
@@ -71,6 +71,7 @@
 	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;
@@ -354,6 +355,8 @@
 			THROW_EXCEPTION(HTTPException, NotImplemented);
 		}
 
+		mContentLength -= rGetLine.GetSizeOfBufferedData();
+
 		Write(rGetLine.GetBufferedData(),
 			rGetLine.GetSizeOfBufferedData());
 	}


From boxbackup-dev@boxbackup.org  Sun Jan  4 13:43:54 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sun,  4 Jan 2009 13:43:54 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2432 - in box/trunk: bin/bbackupquery lib/common
Message-ID: <20090104134354.A62E6325026@www.boxbackup.org>

Author: chris
Date: 2009-01-04 13:43:54 +0000 (Sun, 04 Jan 2009)
New Revision: 2432

Modified:
   box/trunk/bin/bbackupquery/BackupQueries.cpp
   box/trunk/lib/common/FileStream.cpp
   box/trunk/lib/common/FileStream.h
Log:
Move stream comparison code out of BackupQueries::Compare to 
FileStream class.


Modified: box/trunk/bin/bbackupquery/BackupQueries.cpp
===================================================================
--- box/trunk/bin/bbackupquery/BackupQueries.cpp	2009-01-04 13:42:19 UTC (rev 2431)
+++ box/trunk/bin/bbackupquery/BackupQueries.cpp	2009-01-04 13:43:54 UTC (rev 2432)
@@ -1728,34 +1728,7 @@
 							SelfFlushingStream flushFile(*fileOnServerStream);
 							// Open the local file
 							FileStream l(localPath.c_str());
-							
-							// Size
-							IOStream::pos_type fileSizeLocal = l.BytesLeftToRead();
-							IOStream::pos_type fileSizeServer = 0;
-							
-							// Test the contents
-							char buf1[2048];
-							char buf2[2048];
-							while(fileOnServerStream->StreamDataLeft() && l.StreamDataLeft())
-							{
-								int size = fileOnServerStream->Read(buf1, sizeof(buf1), mrConnection.GetTimeout());
-								fileSizeServer += size;
-								
-								if(l.Read(buf2, size) != size
-										|| ::memcmp(buf1, buf2, size) != 0)
-								{
-									equal = false;
-									break;
-								}
-							}
-	
-							// Check read all the data from the server and file -- can't be equal if local and remote aren't the same length
-							// Can't use StreamDataLeft() test on file, because if it's the same size, it won't know
-							// it's EOF yet.
-							if(fileOnServerStream->StreamDataLeft() || fileSizeServer != fileSizeLocal)
-							{
-								equal = false;
-							}
+							equal = l.CompareWith(fileOnServerStream,  mrConnection.GetTimeout());
 						}
 					}
 

Modified: box/trunk/lib/common/FileStream.cpp
===================================================================
--- box/trunk/lib/common/FileStream.cpp	2009-01-04 13:42:19 UTC (rev 2431)
+++ box/trunk/lib/common/FileStream.cpp	2009-01-04 13:43:54 UTC (rev 2432)
@@ -403,3 +403,44 @@
 	return mIsEOF;
 }
 
+// --------------------------------------------------------------------------
+//
+// Function
+//		Name:    FileStream::CompareWith(IOStream&, int)
+//		Purpose: Compare bytes in this file with other stream's data
+//		Created: 2009/01/03
+//
+// --------------------------------------------------------------------------
+bool FileStream::CompareWith(IOStream& rOther, int Timeout)
+{
+	// Size
+	IOStream::pos_type mySize = BytesLeftToRead();
+	IOStream::pos_type otherSize = 0;
+	
+	// Test the contents
+	char buf1[2048];
+	char buf2[2048];
+	while(StreamDataLeft() && rOther.StreamDataLeft())
+	{
+		int readSize = rOther.Read(buf1, sizeof(buf1), Timeout);
+		otherSize += readSize;
+		
+		if(Read(buf2, readSize) != readSize ||
+			::memcmp(buf1, buf2, readSize) != 0)
+		{
+			return false;
+		}
+	}
+
+	// Check read all the data from the server and file -- can't be
+	// equal if local and remote aren't the same length. Can't use
+	// StreamDataLeft() test on local file, because if it's the same
+	// size, it won't know it's EOF yet.
+	
+	if(rOther.StreamDataLeft() || otherSize != mySize)
+	{
+		return false;
+	}
+
+	return true;
+}

Modified: box/trunk/lib/common/FileStream.h
===================================================================
--- box/trunk/lib/common/FileStream.h	2009-01-04 13:42:19 UTC (rev 2431)
+++ box/trunk/lib/common/FileStream.h	2009-01-04 13:43:54 UTC (rev 2432)
@@ -56,6 +56,8 @@
 	virtual bool StreamDataLeft();
 	virtual bool StreamClosed();
 
+	bool CompareWith(IOStream& rOther, int Timeout = IOStream::TimeOutInfinite);
+
 private:
 	tOSFileHandle mOSFileHandle;
 	bool mIsEOF;


From boxbackup-dev@boxbackup.org  Sun Jan  4 13:44:15 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sun,  4 Jan 2009 13:44:15 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2433 - box/trunk/lib/common
Message-ID: <20090104134415.E1B2A325026@www.boxbackup.org>

Author: chris
Date: 2009-01-04 13:44:15 +0000 (Sun, 04 Jan 2009)
New Revision: 2433

Modified:
   box/trunk/lib/common/IOStream.cpp
   box/trunk/lib/common/IOStream.h
Log:
Move IOStream::Write(char *) out of line for debugging.


Modified: box/trunk/lib/common/IOStream.cpp
===================================================================
--- box/trunk/lib/common/IOStream.cpp	2009-01-04 13:43:54 UTC (rev 2432)
+++ box/trunk/lib/common/IOStream.cpp	2009-01-04 13:44:15 UTC (rev 2433)
@@ -258,4 +258,7 @@
 	}
 }
 
-
+void IOStream::Write(const char *pBuffer)
+{
+	Write(pBuffer, strlen(pBuffer));
+}

Modified: box/trunk/lib/common/IOStream.h
===================================================================
--- box/trunk/lib/common/IOStream.h	2009-01-04 13:43:54 UTC (rev 2432)
+++ box/trunk/lib/common/IOStream.h	2009-01-04 13:44:15 UTC (rev 2433)
@@ -44,10 +44,7 @@
 	virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite) = 0;
 	virtual pos_type BytesLeftToRead();	// may return IOStream::SizeOfStreamUnknown (and will for most stream types)
 	virtual void Write(const void *pBuffer, int NBytes) = 0;
-	virtual void Write(const char *pBuffer)
-	{
-		Write(pBuffer, strlen(pBuffer));
-	}
+	virtual void Write(const char *pBuffer);
 	virtual void WriteAllBuffered();
 	virtual pos_type GetPosition() const;
 	virtual void Seek(pos_type Offset, int SeekType);


From boxbackup-dev@boxbackup.org  Sun Jan  4 13:56:55 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sun,  4 Jan 2009 13:56:55 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2434 - box/trunk/lib/httpserver
Message-ID: <20090104135655.40C06325026@www.boxbackup.org>

Author: chris
Date: 2009-01-04 13:56:55 +0000 (Sun, 04 Jan 2009)
New Revision: 2434

Modified:
   box/trunk/lib/httpserver/HTTPRequest.cpp
   box/trunk/lib/httpserver/HTTPRequest.h
   box/trunk/lib/httpserver/HTTPResponse.h
   box/trunk/lib/httpserver/HTTPServer.cpp
Log:
Allow adding headers to an HTTPRequest.

Allow getting response headers out of HTTPResponse.

Rename HTTPRequest Read and Write methods to Send and Receive, to avoid 
confusion with IOStream.


Modified: box/trunk/lib/httpserver/HTTPRequest.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPRequest.cpp	2009-01-04 13:44:15 UTC (rev 2433)
+++ box/trunk/lib/httpserver/HTTPRequest.cpp	2009-01-04 13:56:55 UTC (rev 2434)
@@ -93,7 +93,7 @@
 // --------------------------------------------------------------------------
 //
 // Function
-//		Name:    HTTPRequest::Read(IOStreamGetLine &, int)
+//		Name:    HTTPRequest::Receive(IOStreamGetLine &, int)
 //		Purpose: Read the request from an IOStreamGetLine (and
 //			 attached stream).
 //			 Returns false if there was no valid request,
@@ -101,7 +101,7 @@
 //		Created: 26/3/04
 //
 // --------------------------------------------------------------------------
-bool HTTPRequest::Read(IOStreamGetLine &rGetLine, int Timeout)
+bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout)
 {
 	// Check caller's logic
 	if(mMethod != Method_UNINITIALISED)
@@ -312,12 +312,12 @@
 // --------------------------------------------------------------------------
 //
 // Function
-//		Name:    HTTPRequest::Write(IOStream &, int)
+//		Name:    HTTPRequest::Send(IOStream &, int)
 //		Purpose: Write the request to an IOStream using HTTP.
 //		Created: 03/01/09
 //
 // --------------------------------------------------------------------------
-bool HTTPRequest::Write(IOStream &rStream, int Timeout)
+bool HTTPRequest::Send(IOStream &rStream, int Timeout)
 {
 	switch (mMethod)
 	{
@@ -386,6 +386,12 @@
 		oss << "Connection: close\n";
 	}
 
+	for (std::vector<Header>::iterator i = mExtraHeaders.begin();
+		i != mExtraHeaders.end(); i++)
+	{
+		oss << i->first << ": " << i->second << "\n";
+	}
+
 	rStream.Write(oss.str().c_str());
 	rStream.Write("\n");
 

Modified: box/trunk/lib/httpserver/HTTPRequest.h
===================================================================
--- box/trunk/lib/httpserver/HTTPRequest.h	2009-01-04 13:44:15 UTC (rev 2433)
+++ box/trunk/lib/httpserver/HTTPRequest.h	2009-01-04 13:56:55 UTC (rev 2434)
@@ -44,9 +44,8 @@
 	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;
+	typedef std::pair<std::string, std::string> QueryEn_t, Header;
 
 	enum
 	{
@@ -56,8 +55,8 @@
 		HTTPVersion_1_1 = 1001
 	};
 
-	bool Read(IOStreamGetLine &rGetLine, int Timeout);
-	bool Write(IOStream &rStream, int Timeout);
+	bool Receive(IOStreamGetLine &rGetLine, int Timeout);
+	bool Send(IOStream &rStream, int Timeout);
 
 	typedef std::map<std::string, std::string> CookieJar_t;
 	
@@ -71,7 +70,15 @@
 	// --------------------------------------------------------------------------
 	enum Method GetMethod() const {return mMethod;}
 	const std::string &GetRequestURI() const {return mRequestURI;}
-	const std::string &GetHostName() const {return mHostName;}	// note: request does splitting of Host: header
+
+	// 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;}  // into host name and port number
 	const std::string &GetQueryString() const {return mQueryString;}
 	int GetHTTPVersion() const {return mHTTPVersion;}
@@ -98,6 +105,11 @@
 		mClientKeepAliveRequested = keepAlive;
 	}
 
+	void AddHeader(const std::string& rName, const std::string& rValue)
+	{
+		mExtraHeaders.push_back(Header(rName, rValue));
+	}
+
 private:
 	void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout);
 	void ParseCookies(const std::string &rHeader, int DataStarts);
@@ -114,6 +126,7 @@
 	std::string mContentType;
 	CookieJar_t *mpCookies;
 	bool mClientKeepAliveRequested;
+	std::vector<Header> mExtraHeaders;
 };
 
 #endif // HTTPREQUEST__H

Modified: box/trunk/lib/httpserver/HTTPResponse.h
===================================================================
--- box/trunk/lib/httpserver/HTTPResponse.h	2009-01-04 13:44:15 UTC (rev 2433)
+++ box/trunk/lib/httpserver/HTTPResponse.h	2009-01-04 13:56:55 UTC (rev 2434)
@@ -30,12 +30,14 @@
 public:
 	HTTPResponse();
 	~HTTPResponse();
+
 private:
 	// no copying
 	HTTPResponse(const HTTPResponse &);
 	HTTPResponse &operator=(const HTTPResponse &);
+	typedef std::pair<std::string, std::string> Header;
+
 public:
-
 	void SetResponseCode(int Code);
 	int GetResponseCode() { return mResponseCode; }
 	void SetContentType(const char *ContentType);
@@ -52,6 +54,29 @@
 	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;}
@@ -68,6 +93,7 @@
 		Code_Found = 302,	// redirection
 		Code_NotModified = 304,
 		Code_TemporaryRedirect = 307,
+		Code_MethodNotAllowed = 400,
 		Code_Unauthorized = 401,
 		Code_Forbidden = 403,
 		Code_NotFound = 404,
@@ -111,7 +137,6 @@
 	bool mResponseIsDynamicContent;
 	bool mKeepAlive;
 	std::string mContentType;
-	typedef std::pair<std::string, std::string> Header;
 	std::vector<Header> mExtraHeaders;
 	int mContentLength; // only used when reading response from stream
 	

Modified: box/trunk/lib/httpserver/HTTPServer.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPServer.cpp	2009-01-04 13:44:15 UTC (rev 2433)
+++ box/trunk/lib/httpserver/HTTPServer.cpp	2009-01-04 13:56:55 UTC (rev 2434)
@@ -145,7 +145,7 @@
 	{
 		// Parse the request
 		HTTPRequest request;
-		if(!request.Read(getLine, mTimeout))
+		if(!request.Receive(getLine, mTimeout))
 		{
 			// Didn't get request, connection probably closed.
 			break;
@@ -186,7 +186,7 @@
 		response.Send(rStream, request.GetMethod() == HTTPRequest::Method_HEAD);
 	}
 
-	// Notify dervived claases
+	// Notify derived claases
 	HTTPConnectionClosing();
 }
 


From boxbackup-dev@boxbackup.org  Sun Jan  4 13:57:32 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sun,  4 Jan 2009 13:57:32 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2435 - box/trunk/test/httpserver
Message-ID: <20090104135732.D5606325026@www.boxbackup.org>

Author: chris
Date: 2009-01-04 13:57:32 +0000 (Sun, 04 Jan 2009)
New Revision: 2435

Modified:
   box/trunk/test/httpserver/testhttpserver.cpp
Log:
First part of S3 Simulator, read-only GET requests.


Modified: box/trunk/test/httpserver/testhttpserver.cpp
===================================================================
--- box/trunk/test/httpserver/testhttpserver.cpp	2009-01-04 13:56:55 UTC (rev 2434)
+++ box/trunk/test/httpserver/testhttpserver.cpp	2009-01-04 13:57:32 UTC (rev 2435)
@@ -110,11 +110,79 @@
 	rResponse.Write(DEFAULT_RESPONSE_2, sizeof(DEFAULT_RESPONSE_2) - 1);
 }
 
-
-
 TestWebServer::TestWebServer() {}
 TestWebServer::~TestWebServer() {}
 
+class S3Simulator : public HTTPServer
+{
+public:
+	S3Simulator() { }
+	~S3Simulator() { }
+
+	virtual void Handle(const HTTPRequest &rRequest,
+		HTTPResponse &rResponse);
+};
+
+void S3Simulator::Handle(const HTTPRequest &rRequest, HTTPResponse &rResponse)
+{
+	// if anything goes wrong, return a 500 error
+	rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError);
+	rResponse.SetContentType("text/plain");
+
+	if (rRequest.GetMethod() != HTTPRequest::Method_GET)
+	{
+		rResponse.SetResponseCode(HTTPResponse::Code_MethodNotAllowed);
+		return;
+	}
+
+	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);
+		}
+		else
+		{
+			rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError);
+		}
+		rResponse.IOStream::Write(ce.what());
+		return;
+	}
+	catch (std::exception &e)
+	{
+		rResponse.IOStream::Write(e.what());
+		return;
+	}
+	catch (...)
+	{
+		rResponse.IOStream::Write("Unknown error");
+		return;
+	}
+
+	// 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.SetContentType("text/plain");
+	rResponse.AddHeader("Server", "AmazonS3");
+	rResponse.SetResponseCode(HTTPResponse::Code_OK);
+}
+
 int test(int argc, const char *argv[])
 {
 	if(argc >= 2 && ::strcmp(argv[1], "server") == 0)
@@ -124,6 +192,13 @@
 		return server.Main("doesnotexist", argc - 1, argv + 1);
 	}
 	
+	if(argc >= 2 && ::strcmp(argv[1], "s3server") == 0)
+	{
+		// Run a server
+		S3Simulator server;
+		return server.Main("doesnotexist", argc - 1, argv + 1);
+	}
+	
 	// Start the server
 	int pid = LaunchServer("./test server testfiles/httpserver.conf", "testfiles/httpserver.pid");
 	TEST_THAT(pid != -1 && pid != 0);
@@ -145,18 +220,24 @@
 		HTTPRequest request(HTTPRequest::Method_GET,
 			"/test-one/34/341s/234?p1=vOne&p2=vTwo");
 
-		if (i >= 2)
+		if (i < 2)
 		{
 			// first set of passes has keepalive off by default,
 			// so when i == 1 the socket has already been closed
 			// by the server, and we'll get -EPIPE when we try
 			// to send the request.
+			request.SetClientKeepAliveRequested(false);
+		}
+		else
+		{
 			request.SetClientKeepAliveRequested(true);
 		}
 
 		if (i == 1)
 		{
-			TEST_CHECK_THROWS(request.Write(sock,
+			sleep(1); // need time for our process to realise
+			// that the peer has died, otherwise no SIGPIPE :(
+			TEST_CHECK_THROWS(request.Send(sock,
 				IOStream::TimeOutInfinite),
 				ConnectionException, SocketWriteError);
 			sock.Close();
@@ -165,7 +246,7 @@
 		}
 		else
 		{
-			request.Write(sock, IOStream::TimeOutInfinite);
+			request.Send(sock, IOStream::TimeOutInfinite);
 		}
 
 		HTTPResponse response;
@@ -214,6 +295,78 @@
 	TEST_THAT(KillServer(pid));
 	TestRemoteProcessMemLeaks("generic-httpserver.memleaks");
 
+	// Start the S3Simulator server
+	pid = LaunchServer("./test s3server testfiles/httpserver.conf",
+		"testfiles/httpserver.pid");
+	TEST_THAT(pid != -1 && pid != 0);
+	if(pid <= 0)
+	{
+		return 0;
+	}
+
+	sock.Close();
+	sock.Open(Socket::TypeINET, "localhost", 1080);
+
+	{
+		HTTPRequest request(HTTPRequest::Method_GET, "/nonexist");
+		request.SetHostName("quotes.s3.amazonaws.com");
+		request.AddHeader("Date", "Wed, 01 Mar  2006 12:00:00 GMT");
+		request.AddHeader("Authorization", "AWS 15B4D3461F177624206A:xQE0diMbLRepdf3YB+FIEXAMPLE=");
+		request.SetClientKeepAliveRequested(true);
+		request.Send(sock, IOStream::TimeOutInfinite);
+
+		HTTPResponse response;
+		response.Receive(sock);
+		std::string value;
+		TEST_EQUAL(404, response.GetResponseCode());
+	}
+
+	{
+		TEST_THAT(chmod("testfiles/testrequests.pl", 0) == 0);
+		HTTPRequest request(HTTPRequest::Method_GET,
+			"/testrequests.pl");
+		request.SetHostName("quotes.s3.amazonaws.com");
+		request.AddHeader("Date", "Wed, 01 Mar  2006 12:00:00 GMT");
+		request.AddHeader("Authorization", "AWS 15B4D3461F177624206A:xQE0diMbLRepdf3YB+FIEXAMPLE=");
+		request.SetClientKeepAliveRequested(true);
+		request.Send(sock, IOStream::TimeOutInfinite);
+
+		HTTPResponse response;
+		response.Receive(sock);
+		std::string value;
+		TEST_EQUAL(403, response.GetResponseCode());
+		TEST_THAT(chmod("testfiles/testrequests.pl", 0755) == 0);
+	}
+
+	{
+		HTTPRequest request(HTTPRequest::Method_GET,
+			"/testrequests.pl");
+		request.SetHostName("quotes.s3.amazonaws.com");
+		request.AddHeader("Date", "Wed, 01 Mar  2006 12:00:00 GMT");
+		request.AddHeader("Authorization", "AWS 15B4D3461F177624206A:xQE0diMbLRepdf3YB+FIEXAMPLE=");
+		request.SetClientKeepAliveRequested(true);
+		request.Send(sock, IOStream::TimeOutInfinite);
+
+		HTTPResponse response;
+		response.Receive(sock);
+		std::string value;
+		TEST_EQUAL(200, response.GetResponseCode());
+		TEST_EQUAL("qBmKRcEWBBhH6XAqsKU/eg24V3jf/kWKN9dJip1L/FpbYr9FDy7wWFurfdQOEMcY", response.GetHeaderValue("x-amz-id-2"));
+		TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id"));
+		TEST_EQUAL("Wed, 01 Mar  2006 12:00:00 GMT", response.GetHeaderValue("Date"));
+		TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified"));
+		TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag"));
+		TEST_EQUAL("text/plain", response.GetContentType());
+		TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server"));
+
+		FileStream file("testfiles/testrequests.pl");
+		TEST_THAT(file.CompareWith(response));
+	}
+
+	// Kill it
+	TEST_THAT(KillServer(pid));
+	TestRemoteProcessMemLeaks("generic-httpserver.memleaks");
+
 	return 0;
 }
 


From boxbackup-dev@boxbackup.org  Mon Jan  5 00:41:12 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Mon,  5 Jan 2009 00:41:12 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2436 - box/trunk/lib/httpserver
Message-ID: <20090105004112.271B7325026@www.boxbackup.org>

Author: chris
Date: 2009-01-05 00:41:11 +0000 (Mon, 05 Jan 2009)
New Revision: 2436

Modified:
   box/trunk/lib/httpserver/HTTPResponse.cpp
   box/trunk/lib/httpserver/HTTPResponse.h
Log:
Add support for sending an HTTP/1.0 100 Continue response during 
processing of a request by HTTPServer, by keeping a pointer to the 
socket object.


Modified: box/trunk/lib/httpserver/HTTPResponse.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPResponse.cpp	2009-01-04 13:57:32 UTC (rev 2435)
+++ box/trunk/lib/httpserver/HTTPResponse.cpp	2009-01-05 00:41:11 UTC (rev 2436)
@@ -25,6 +25,24 @@
 // --------------------------------------------------------------------------
 //
 // 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
@@ -34,7 +52,8 @@
 	: mResponseCode(HTTPResponse::Code_NoContent),
 	  mResponseIsDynamicContent(true),
 	  mKeepAlive(false),
-	  mContentLength(-1)
+	  mContentLength(-1),
+	  mpStreamToSendTo(NULL)
 {
 }
 
@@ -123,12 +142,17 @@
 //		Created: 26/3/04
 //
 // --------------------------------------------------------------------------
-void HTTPResponse::Send(IOStream &rStream, bool OmitContent)
+void HTTPResponse::Send(bool OmitContent)
 {
-	if(mContentType.empty())
+	if (!mpStreamToSendTo)
 	{
-		THROW_EXCEPTION(HTTPException, NoContentTypeSet)
+		THROW_EXCEPTION(HTTPException, NoStreamConfigured);
 	}
+	
+	if (GetSize() != 0 && mContentType.empty())
+	{
+		THROW_EXCEPTION(HTTPException, NoContentTypeSet);
+	}
 
 	// Build and send header
 	{
@@ -171,16 +195,20 @@
 		// NOTE: header ends with blank line in all cases
 		
 		// Write to stream
-		rStream.Write(header.c_str(), header.size());
+		mpStreamToSendTo->Write(header.c_str(), header.size());
 	}
 	
 	// Send content
 	if(!OmitContent)
 	{
-		rStream.Write(GetBuffer(), GetSize());
+		mpStreamToSendTo->Write(GetBuffer(), GetSize());
 	}
 }
 
+void HTTPResponse::SendContinue()
+{
+	mpStreamToSendTo->Write("HTTP/1.1 100 Continue\r\n");
+}
 
 // --------------------------------------------------------------------------
 //
@@ -339,10 +367,16 @@
 	// 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;
+	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

Modified: box/trunk/lib/httpserver/HTTPResponse.h
===================================================================
--- box/trunk/lib/httpserver/HTTPResponse.h	2009-01-04 13:57:32 UTC (rev 2435)
+++ box/trunk/lib/httpserver/HTTPResponse.h	2009-01-05 00:41:11 UTC (rev 2436)
@@ -28,6 +28,7 @@
 class HTTPResponse : public CollectInBufferStream
 {
 public:
+	HTTPResponse(IOStream* pStreamToSendTo);
 	HTTPResponse();
 	~HTTPResponse();
 
@@ -46,7 +47,8 @@
 	void SetAsRedirect(const char *RedirectTo, bool IsLocalURI = true);
 	void SetAsNotFound(const char *URI);
 
-	void Send(IOStream &rStream, bool OmitContent = false);
+	void Send(bool OmitContent = false);
+	void SendContinue();
 	void Receive(IOStream& rStream, int Timeout = IOStream::TimeOutInfinite);
 
 	// void AddHeader(const char *EntireHeaderLine);
@@ -139,6 +141,7 @@
 	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;
 


From boxbackup-dev@boxbackup.org  Mon Jan  5 00:47:23 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Mon,  5 Jan 2009 00:47:23 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2437 - box/trunk/lib/httpserver
Message-ID: <20090105004723.7C906325026@www.boxbackup.org>

Author: chris
Date: 2009-01-05 00:47:23 +0000 (Mon, 05 Jan 2009)
New Revision: 2437

Modified:
   box/trunk/lib/httpserver/HTTPException.txt
Log:
New exception code for sending an HTTP response when it was not created 
with a stream to send to.


Modified: box/trunk/lib/httpserver/HTTPException.txt
===================================================================
--- box/trunk/lib/httpserver/HTTPException.txt	2009-01-05 00:41:11 UTC (rev 2436)
+++ box/trunk/lib/httpserver/HTTPException.txt	2009-01-05 00:47:23 UTC (rev 2437)
@@ -13,3 +13,4 @@
 RequestNotInitialised						10
 BadResponse							11
 ResponseReadFailed						12
+NoStreamConfigured						13


From boxbackup-dev@boxbackup.org  Mon Jan  5 00:48:01 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Mon,  5 Jan 2009 00:48:01 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2438 - box/trunk/lib/httpserver
Message-ID: <20090105004801.CEEEC325026@www.boxbackup.org>

Author: chris
Date: 2009-01-05 00:48:01 +0000 (Mon, 05 Jan 2009)
New Revision: 2438

Modified:
   box/trunk/lib/httpserver/HTTPRequest.cpp
   box/trunk/lib/httpserver/HTTPRequest.h
Log:
Don't read the whole uploaded body in HTTPRequest::Receive, as the 
client may be expecting a 100 Continue header (or other response) before 
sending it, and only the HTTPServer should send that for us.

Keep track of the stream that we're reading from, in case there's a body 
to read later.

Simplify parsing of HTTP method, and add support for PUT requests.

Add support for parsing Expect headers and storing and retrieving any
unrecognised headers.

Add support for sending a streaming upload from an IOStream with an HTTP 
request as the body (e.g. for PUT requests).


Modified: box/trunk/lib/httpserver/HTTPRequest.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPRequest.cpp	2009-01-05 00:47:23 UTC (rev 2437)
+++ box/trunk/lib/httpserver/HTTPRequest.cpp	2009-01-05 00:48:01 UTC (rev 2438)
@@ -15,6 +15,7 @@
 #include <stdio.h>
 
 #include "HTTPRequest.h"
+#include "HTTPResponse.h"
 #include "HTTPQueryDecoder.h"
 #include "autogen_HTTPException.h"
 #include "IOStream.h"
@@ -44,7 +45,9 @@
 	  mHTTPVersion(0),
 	  mContentLength(-1),
 	  mpCookies(0),
-	  mClientKeepAliveRequested(false)
+	  mClientKeepAliveRequested(false),
+	  mExpectContinue(false),
+	  mpStreamToReadFrom(NULL)
 {
 }
 
@@ -61,11 +64,13 @@
 HTTPRequest::HTTPRequest(enum Method method, const std::string& rURI)
 	: mMethod(method),
 	  mRequestURI(rURI),
-	  mHostPort(80),	// default if not specified
+	  mHostPort(80), // default if not specified
 	  mHTTPVersion(HTTPVersion_1_1),
 	  mContentLength(-1),
 	  mpCookies(0),
-	  mClientKeepAliveRequested(false)
+	  mClientKeepAliveRequested(false),
+	  mExpectContinue(false),
+	  mpStreamToReadFrom(NULL)
 {
 }
 
@@ -120,30 +125,36 @@
 
 	// Check the method
 	unsigned int p = 0;	// current position in string
-	if(::strncmp(requestLine.c_str(), "GET ", 4) == 0)
+	p = requestLine.find(' '); // end of first word
+	
+	if (p == std::string::npos)
 	{
-		p = 3;
-		mMethod = Method_GET;
+		// No terminating space, looks bad
+		p = requestLine.size();
 	}
-	else if(::strncmp(requestLine.c_str(), "HEAD ", 5) == 0)
-	{
-		p = 4;
-		mMethod = Method_HEAD;
-	}
-	else if(::strncmp(requestLine.c_str(), "POST ", 5) == 0)
-	{
-		p = 4;
-		mMethod = Method_POST;
-	}
 	else
 	{
-		p = requestLine.find(' ');
-		if(p == std::string::npos)
+		std::string method = requestLine.substr(0, p);
+		if (method == "GET")
 		{
-			// No terminating space, looks bad
-			p = requestLine.size();
+			mMethod = Method_GET;
 		}
-		mMethod = Method_UNKNOWN;
+		else if (method == "HEAD")
+		{
+			mMethod = Method_HEAD;
+		}
+		else if (method == "POST")
+		{
+			mMethod = Method_POST;
+		}
+		else if (method == "PUT")
+		{
+			mMethod = Method_PUT;
+		}
+		else
+		{
+			mMethod = Method_UNKNOWN;
+		}
 	}
 
 	// Skip spaces to find URI
@@ -263,6 +274,15 @@
 	// 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)
 	{
@@ -305,10 +325,42 @@
 		// 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
@@ -317,7 +369,7 @@
 //		Created: 03/01/09
 //
 // --------------------------------------------------------------------------
-bool HTTPRequest::Send(IOStream &rStream, int Timeout)
+bool HTTPRequest::Send(IOStream &rStream, int Timeout, bool ExpectContinue)
 {
 	switch (mMethod)
 	{
@@ -331,6 +383,8 @@
 		rStream.Write("HEAD"); break;
 	case Method_POST:
 		rStream.Write("POST"); break;
+	case Method_PUT:
+		rStream.Write("PUT"); break;
 	}
 
 	rStream.Write(" ");
@@ -391,6 +445,11 @@
 	{
 		oss << i->first << ": " << i->second << "\n";
 	}
+	
+	if (ExpectContinue)
+	{
+		oss << "Expect: 100-continue\n";
+	}
 
 	rStream.Write(oss.str().c_str());
 	rStream.Write("\n");
@@ -398,6 +457,31 @@
 	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
@@ -509,7 +593,12 @@
 				}
 				// else don't understand, just assume default for protocol version
 			}
-			// else ignore it
+			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;

Modified: box/trunk/lib/httpserver/HTTPRequest.h
===================================================================
--- box/trunk/lib/httpserver/HTTPRequest.h	2009-01-05 00:47:23 UTC (rev 2437)
+++ box/trunk/lib/httpserver/HTTPRequest.h	2009-01-05 00:48:01 UTC (rev 2438)
@@ -13,6 +13,9 @@
 #include <string>
 #include <map>
 
+#include "CollectInBufferStream.h"
+
+class HTTPResponse;
 class IOStream;
 class IOStreamGetLine;
 
@@ -24,7 +27,7 @@
 //		Created: 26/3/04
 //
 // --------------------------------------------------------------------------
-class HTTPRequest
+class HTTPRequest : public CollectInBufferStream
 {
 public:
 	enum Method
@@ -33,7 +36,8 @@
 		Method_UNKNOWN = 0,
 		Method_GET = 1,
 		Method_HEAD = 2,
-		Method_POST = 3
+		Method_POST = 3,
+		Method_PUT = 4
 	};
 	
 	HTTPRequest();
@@ -56,7 +60,10 @@
 	};
 
 	bool Receive(IOStreamGetLine &rGetLine, int Timeout);
-	bool Send(IOStream &rStream, 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;
 	
@@ -88,8 +95,21 @@
 	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;
+	}
 
-
 	// --------------------------------------------------------------------------
 	//
 	// Function
@@ -109,12 +129,12 @@
 	{
 		mExtraHeaders.push_back(Header(rName, rValue));
 	}
-
+	bool IsExpectingContinue() const { return mExpectContinue; }
+	
 private:
 	void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout);
 	void ParseCookies(const std::string &rHeader, int DataStarts);
 
-private:
 	enum Method mMethod;
 	std::string mRequestURI;
 	std::string mHostName;
@@ -127,6 +147,8 @@
 	CookieJar_t *mpCookies;
 	bool mClientKeepAliveRequested;
 	std::vector<Header> mExtraHeaders;
+	bool mExpectContinue;
+	IOStream* mpStreamToReadFrom;
 };
 
 #endif // HTTPREQUEST__H


From boxbackup-dev@boxbackup.org  Mon Jan  5 00:48:34 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Mon,  5 Jan 2009 00:48:34 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2439 - box/trunk/lib/common
Message-ID: <20090105004834.AAE0F325026@www.boxbackup.org>

Author: chris
Date: 2009-01-05 00:48:33 +0000 (Mon, 05 Jan 2009)
New Revision: 2439

Modified:
   box/trunk/lib/common/IOStream.cpp
   box/trunk/lib/common/IOStream.h
Log:
Remove definitions of unwanted copy constructor and assignment operator, 
to avoid accidentally calling them.


Modified: box/trunk/lib/common/IOStream.cpp
===================================================================
--- box/trunk/lib/common/IOStream.cpp	2009-01-05 00:48:01 UTC (rev 2438)
+++ box/trunk/lib/common/IOStream.cpp	2009-01-05 00:48:33 UTC (rev 2439)
@@ -29,19 +29,6 @@
 // --------------------------------------------------------------------------
 //
 // Function
-//		Name:    IOStream::IOStream(const IOStream &)
-//		Purpose: Copy constructor (exceptions)
-//		Created: 2003/07/31
-//
-// --------------------------------------------------------------------------
-IOStream::IOStream(const IOStream &rToCopy)
-{
-	THROW_EXCEPTION(CommonException, NotSupported)
-}
-
-// --------------------------------------------------------------------------
-//
-// Function
 //		Name:    IOStream::~IOStream()
 //		Purpose: Destructor
 //		Created: 2003/07/31

Modified: box/trunk/lib/common/IOStream.h
===================================================================
--- box/trunk/lib/common/IOStream.h	2009-01-05 00:48:01 UTC (rev 2438)
+++ box/trunk/lib/common/IOStream.h	2009-01-05 00:48:33 UTC (rev 2439)
@@ -22,9 +22,13 @@
 {
 public:
 	IOStream();
-	IOStream(const IOStream &rToCopy);
 	virtual ~IOStream();
-	
+
+private:
+	IOStream(const IOStream &rToCopy); /* forbidden */
+	IOStream& operator=(const IOStream &rToCopy); /* forbidden */
+		
+public:
 	enum
 	{
 		TimeOutInfinite = -1,


From boxbackup-dev@boxbackup.org  Mon Jan  5 00:49:34 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Mon,  5 Jan 2009 00:49:34 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2440 - box/trunk/lib/httpserver
Message-ID: <20090105004934.CF94A325026@www.boxbackup.org>

Author: chris
Date: 2009-01-05 00:49:34 +0000 (Mon, 05 Jan 2009)
New Revision: 2440

Modified:
   box/trunk/lib/httpserver/HTTPServer.cpp
   box/trunk/lib/httpserver/HTTPServer.h
Log:
Create HTTP responses with the stream that they will be sent to, for 100 
Continue support.


Modified: box/trunk/lib/httpserver/HTTPServer.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPServer.cpp	2009-01-05 00:48:33 UTC (rev 2439)
+++ box/trunk/lib/httpserver/HTTPServer.cpp	2009-01-05 00:49:34 UTC (rev 2440)
@@ -152,7 +152,7 @@
 		}
 	
 		// Generate a response
-		HTTPResponse response;
+		HTTPResponse response(&rStream);
 		try
 		{
 			Handle(request, response);
@@ -183,7 +183,7 @@
 		}
 	
 		// Send the response (omit any content if this is a HEAD method request)
-		response.Send(rStream, request.GetMethod() == HTTPRequest::Method_HEAD);
+		response.Send(request.GetMethod() == HTTPRequest::Method_HEAD);
 	}
 
 	// Notify derived claases
@@ -209,7 +209,7 @@
 			"</body>\n</html>\n"
 
 	// Generate the error page
-	HTTPResponse response;
+	HTTPResponse response(&rStream);
 	response.SetResponseCode(HTTPResponse::Code_InternalServerError);
 	response.SetContentType("text/html");
 	response.Write(ERROR_HTML_1, sizeof(ERROR_HTML_1) - 1);
@@ -217,7 +217,7 @@
 	response.Write(ERROR_HTML_2, sizeof(ERROR_HTML_2) - 1);
 
 	// Send the error response
-	response.Send(rStream);
+	response.Send();
 }
 
 

Modified: box/trunk/lib/httpserver/HTTPServer.h
===================================================================
--- box/trunk/lib/httpserver/HTTPServer.h	2009-01-05 00:48:33 UTC (rev 2439)
+++ box/trunk/lib/httpserver/HTTPServer.h	2009-01-05 00:49:34 UTC (rev 2440)
@@ -46,7 +46,7 @@
 	//		Created: 26/3/04
 	//
 	// --------------------------------------------------------------------------
-	virtual void Handle(const HTTPRequest &rRequest, HTTPResponse &rResponse) = 0;
+	virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) = 0;
 	
 	// For notifications to derived classes
 	virtual void HTTPConnectionOpening();


From boxbackup-dev@boxbackup.org  Mon Jan  5 00:50:31 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Mon,  5 Jan 2009 00:50:31 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2441 - box/trunk/test/httpserver
Message-ID: <20090105005031.211E4325026@www.boxbackup.org>

Author: chris
Date: 2009-01-05 00:50:30 +0000 (Mon, 05 Jan 2009)
New Revision: 2441

Modified:
   box/trunk/test/httpserver/testhttpserver.cpp
Log:
Add support for HTTP PUT requests (uploads) in S3 simulator.


Modified: box/trunk/test/httpserver/testhttpserver.cpp
===================================================================
--- box/trunk/test/httpserver/testhttpserver.cpp	2009-01-05 00:49:34 UTC (rev 2440)
+++ box/trunk/test/httpserver/testhttpserver.cpp	2009-01-05 00:50:30 UTC (rev 2441)
@@ -27,12 +27,12 @@
 	TestWebServer();
 	~TestWebServer();
 
-	virtual void Handle(const HTTPRequest &rRequest, HTTPResponse &rResponse);
+	virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse);
 
 };
 
 // Build a nice HTML response, so this can also be tested neatly in a browser
-void TestWebServer::Handle(const HTTPRequest &rRequest, HTTPResponse &rResponse)
+void TestWebServer::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse)
 {
 	// Test redirection mechanism
 	if(rRequest.GetRequestURI() == "/redirect")
@@ -119,22 +119,50 @@
 	S3Simulator() { }
 	~S3Simulator() { }
 
-	virtual void Handle(const HTTPRequest &rRequest,
-		HTTPResponse &rResponse);
+	virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse);
+	virtual void HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse);
+	virtual void HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse);
 };
 
-void S3Simulator::Handle(const 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");
 
-	if (rRequest.GetMethod() != HTTPRequest::Method_GET)
+	try
+	{		
+		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.SetResponseCode(HTTPResponse::Code_MethodNotAllowed);
-		return;
+		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;
@@ -153,32 +181,57 @@
 		{
 			rResponse.SetResponseCode(HTTPResponse::Code_Forbidden);
 		}
-		else
-		{
-			rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError);
-		}
-		rResponse.IOStream::Write(ce.what());
-		return;
+		throw;
 	}
-	catch (std::exception &e)
+
+	// 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
 	{
-		rResponse.IOStream::Write(e.what());
-		return;
+		apFile.reset(new FileStream(path, O_CREAT | O_WRONLY));
 	}
-	catch (...)
+	catch (CommonException &ce)
 	{
-		rResponse.IOStream::Write("Unknown error");
-		return;
+		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");
+	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("text/plain");
+	rResponse.SetContentType("");
 	rResponse.AddHeader("Server", "AmazonS3");
 	rResponse.SetResponseCode(HTTPResponse::Code_OK);
 }
@@ -363,6 +416,34 @@
 		TEST_THAT(file.CompareWith(response));
 	}
 
+	{
+		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 15B4D3461F177624206A:xQE0diMbLRepdf3YB+FIEXAMPLE=");
+		request.AddHeader("Content-Type", "text/plain");
+		FileStream fs("testfiles/testrequests.pl");
+		HTTPResponse response;
+		request.SendWithStream(sock,
+			IOStream::TimeOutInfinite /* or 10000 milliseconds */,
+			&fs, response);
+		std::string value;
+		TEST_EQUAL(200, response.GetResponseCode());
+		TEST_EQUAL("LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7", response.GetHeaderValue("x-amz-id-2"));
+		TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id"));
+		TEST_EQUAL("Wed, 01 Mar  2006 12:00:00 GMT", response.GetHeaderValue("Date"));
+		TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified"));
+		TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag"));
+		TEST_EQUAL("", response.GetContentType());
+		TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server"));
+		TEST_EQUAL(0, response.GetSize());
+
+		FileStream f1("testfiles/testrequests.pl");
+		FileStream f2("testfiles/newfile");
+		TEST_THAT(f1.CompareWith(f2));
+	}
+
 	// Kill it
 	TEST_THAT(KillServer(pid));
 	TestRemoteProcessMemLeaks("generic-httpserver.memleaks");


From boxbackup-dev@boxbackup.org  Tue Jan  6 13:18:08 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Tue,  6 Jan 2009 13:18:08 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2442 - in box/trunk: lib/httpserver test/httpserver test/httpserver/testfiles test/httpserver/testfiles/photos
Message-ID: <20090106131808.4A2E9325026@www.boxbackup.org>

Author: chris
Date: 2009-01-06 13:18:07 +0000 (Tue, 06 Jan 2009)
New Revision: 2442

Added:
   box/trunk/lib/httpserver/cdecode.cpp
   box/trunk/lib/httpserver/cdecode.h
   box/trunk/lib/httpserver/cencode.cpp
   box/trunk/lib/httpserver/cencode.h
   box/trunk/lib/httpserver/decode.h
   box/trunk/lib/httpserver/encode.h
   box/trunk/test/httpserver/testfiles/photos/
   box/trunk/test/httpserver/testfiles/photos/puppy.jpg
Modified:
   box/trunk/lib/httpserver/HTTPRequest.cpp
   box/trunk/lib/httpserver/HTTPRequest.h
   box/trunk/test/httpserver/testhttpserver.cpp
Log:
Add Amazon S3 signature checking to simulator.


Modified: box/trunk/lib/httpserver/HTTPRequest.cpp
===================================================================
--- box/trunk/lib/httpserver/HTTPRequest.cpp	2009-01-05 00:50:30 UTC (rev 2441)
+++ box/trunk/lib/httpserver/HTTPRequest.cpp	2009-01-06 13:18:07 UTC (rev 2442)
@@ -134,20 +134,20 @@
 	}
 	else
 	{
-		std::string method = requestLine.substr(0, p);
-		if (method == "GET")
+		mHttpVerb = requestLine.substr(0, p);
+		if (mHttpVerb == "GET")
 		{
 			mMethod = Method_GET;
 		}
-		else if (method == "HEAD")
+		else if (mHttpVerb == "HEAD")
 		{
 			mMethod = Method_HEAD;
 		}
-		else if (method == "POST")
+		else if (mHttpVerb == "POST")
 		{
 			mMethod = Method_POST;
 		}
-		else if (method == "PUT")
+		else if (mHttpVerb == "PUT")
 		{
 			mMethod = Method_PUT;
 		}

Modified: box/trunk/lib/httpserver/HTTPRequest.h
===================================================================
--- box/trunk/lib/httpserver/HTTPRequest.h	2009-01-05 00:50:30 UTC (rev 2441)
+++ box/trunk/lib/httpserver/HTTPRequest.h	2009-01-06 13:18:07 UTC (rev 2442)
@@ -86,7 +86,7 @@
 		mHostName = rHostName;
 	}
 
-	const int GetHostPort() const {return mHostPort;}  // into host name and port number
+	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;}
@@ -109,6 +109,7 @@
 		}
 		return false;
 	}
+	std::vector<Header> GetHeaders() { return mExtraHeaders; }
 
 	// --------------------------------------------------------------------------
 	//
@@ -130,6 +131,23 @@
 		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);
@@ -149,6 +167,7 @@
 	std::vector<Header> mExtraHeaders;
 	bool mExpectContinue;
 	IOStream* mpStreamToReadFrom;
+	std::string mHttpVerb;
 };
 
 #endif // HTTPREQUEST__H

Added: box/trunk/lib/httpserver/cdecode.cpp
===================================================================
--- box/trunk/lib/httpserver/cdecode.cpp	                        (rev 0)
+++ box/trunk/lib/httpserver/cdecode.cpp	2009-01-06 13:18:07 UTC (rev 2442)
@@ -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;
+}
+
+}


Property changes on: box/trunk/lib/httpserver/cdecode.cpp
___________________________________________________________________
Added: svn:mime-type
   + text/x-c++src
Added: svn:eol-style
   + native

Added: box/trunk/lib/httpserver/cdecode.h
===================================================================
--- box/trunk/lib/httpserver/cdecode.h	                        (rev 0)
+++ box/trunk/lib/httpserver/cdecode.h	2009-01-06 13:18:07 UTC (rev 2442)
@@ -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 */


Property changes on: box/trunk/lib/httpserver/cdecode.h
___________________________________________________________________
Added: svn:mime-type
   + text/x-chdr
Added: svn:eol-style
   + native

Added: box/trunk/lib/httpserver/cencode.cpp
===================================================================
--- box/trunk/lib/httpserver/cencode.cpp	                        (rev 0)
+++ box/trunk/lib/httpserver/cencode.cpp	2009-01-06 13:18:07 UTC (rev 2442)
@@ -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;
+}
+
+}


Property changes on: box/trunk/lib/httpserver/cencode.cpp
___________________________________________________________________
Added: svn:mime-type
   + text/x-c++src
Added: svn:eol-style
   + native

Added: box/trunk/lib/httpserver/cencode.h
===================================================================
--- box/trunk/lib/httpserver/cencode.h	                        (rev 0)
+++ box/trunk/lib/httpserver/cencode.h	2009-01-06 13:18:07 UTC (rev 2442)
@@ -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 */
+


Property changes on: box/trunk/lib/httpserver/cencode.h
___________________________________________________________________
Added: svn:mime-type
   + text/x-chdr
Added: svn:eol-style
   + native

Added: box/trunk/lib/httpserver/decode.h
===================================================================
--- box/trunk/lib/httpserver/decode.h	                        (rev 0)
+++ box/trunk/lib/httpserver/decode.h	2009-01-06 13:18:07 UTC (rev 2442)
@@ -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


Property changes on: box/trunk/lib/httpserver/decode.h
___________________________________________________________________
Added: svn:mime-type
   + text/x-chdr
Added: svn:eol-style
   + native

Added: box/trunk/lib/httpserver/encode.h
===================================================================
--- box/trunk/lib/httpserver/encode.h	                        (rev 0)
+++ box/trunk/lib/httpserver/encode.h	2009-01-06 13:18:07 UTC (rev 2442)
@@ -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
+


Property changes on: box/trunk/lib/httpserver/encode.h
___________________________________________________________________
Added: svn:mime-type
   + text/x-chdr
Added: svn:eol-style
   + native

Added: box/trunk/test/httpserver/testfiles/photos/puppy.jpg
===================================================================
(Binary files differ)


Property changes on: box/trunk/test/httpserver/testfiles/photos/puppy.jpg
___________________________________________________________________
Added: svn:mime-type
   + image/jpeg

Modified: box/trunk/test/httpserver/testhttpserver.cpp
===================================================================
--- box/trunk/test/httpserver/testhttpserver.cpp	2009-01-05 00:50:30 UTC (rev 2441)
+++ box/trunk/test/httpserver/testhttpserver.cpp	2009-01-06 13:18:07 UTC (rev 2442)
@@ -12,12 +12,16 @@
 #include <stdio.h>
 #include <string.h>
 
-#include "Test.h"
-#include "HTTPServer.h"
+#include <openssl/hmac.h>
+
 #include "HTTPRequest.h"
 #include "HTTPResponse.h"
+#include "HTTPServer.h"
 #include "IOStreamGetLine.h"
 #include "ServerControl.h"
+#include "Test.h"
+#include "decode.h"
+#include "encode.h"
 
 #include "MemLeakFindOn.h"
 
@@ -131,9 +135,93 @@
 	rResponse.SetContentType("text/plain");
 
 	try
-	{		
-		if (rRequest.GetMethod() == HTTPRequest::Method_GET)
+	{
+		// 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)
@@ -348,6 +436,97 @@
 	TEST_THAT(KillServer(pid));
 	TestRemoteProcessMemLeaks("generic-httpserver.memleaks");
 
+	{
+		// 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",
+			"AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA=");
+		
+		S3Simulator simulator;
+		
+		CollectInBufferStream response_buffer;
+		HTTPResponse response(&response_buffer);
+		
+		simulator.Handle(request, response);
+		TEST_EQUAL(200, response.GetResponseCode());
+		
+		std::string response_data((const char *)response.GetBuffer(),
+			response.GetSize());
+		TEST_EQUAL("omgpuppies!\n", response_data);
+	}
+
+	{
+		// 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",
+			"AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbB=");
+		
+		S3Simulator simulator;
+		
+		CollectInBufferStream response_buffer;
+		HTTPResponse response(&response_buffer);
+		
+		simulator.Handle(request, response);
+		TEST_EQUAL(401, response.GetResponseCode());
+		
+		std::string response_data((const char *)response.GetBuffer(),
+			response.GetSize());
+		TEST_EQUAL("", response_data);
+	}
+
+	{
+		HTTPRequest request(HTTPRequest::Method_GET, "/nonexist");
+		request.SetHostName("quotes.s3.amazonaws.com");
+		request.AddHeader("Date", "Wed, 01 Mar  2006 12:00:00 GMT");
+		request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:0cSX/YPdtXua1aFFpYmH1tc0ajA=");
+
+		S3Simulator simulator;
+		
+		CollectInBufferStream response_buffer;
+		HTTPResponse response(&response_buffer);
+		
+		simulator.Handle(request, response);
+		TEST_EQUAL(404, response.GetResponseCode());
+	}
+
+	{
+		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("Content-Type", "text/plain");
+		
+		FileStream fs("testfiles/testrequests.pl");
+		fs.CopyStreamTo(request);
+		request.SetForReading();
+
+		CollectInBufferStream response_buffer;
+		HTTPResponse response(&response_buffer);
+		
+		S3Simulator simulator;
+		simulator.Handle(request, response);
+		
+		TEST_EQUAL(200, response.GetResponseCode());
+		TEST_EQUAL("LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7", response.GetHeaderValue("x-amz-id-2"));
+		TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id"));
+		TEST_EQUAL("Wed, 01 Mar  2006 12:00:00 GMT", response.GetHeaderValue("Date"));
+		TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified"));
+		TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag"));
+		TEST_EQUAL("", response.GetContentType());
+		TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server"));
+		TEST_EQUAL(0, response.GetSize());
+
+		FileStream f1("testfiles/testrequests.pl");
+		FileStream f2("testfiles/newfile");
+		TEST_THAT(f1.CompareWith(f2));
+		TEST_EQUAL(0, ::unlink("testfiles/newfile"));
+	}
+
 	// Start the S3Simulator server
 	pid = LaunchServer("./test s3server testfiles/httpserver.conf",
 		"testfiles/httpserver.pid");
@@ -364,7 +543,7 @@
 		HTTPRequest request(HTTPRequest::Method_GET, "/nonexist");
 		request.SetHostName("quotes.s3.amazonaws.com");
 		request.AddHeader("Date", "Wed, 01 Mar  2006 12:00:00 GMT");
-		request.AddHeader("Authorization", "AWS 15B4D3461F177624206A:xQE0diMbLRepdf3YB+FIEXAMPLE=");
+		request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:0cSX/YPdtXua1aFFpYmH1tc0ajA=");
 		request.SetClientKeepAliveRequested(true);
 		request.Send(sock, IOStream::TimeOutInfinite);
 
@@ -380,7 +559,7 @@
 			"/testrequests.pl");
 		request.SetHostName("quotes.s3.amazonaws.com");
 		request.AddHeader("Date", "Wed, 01 Mar  2006 12:00:00 GMT");
-		request.AddHeader("Authorization", "AWS 15B4D3461F177624206A:xQE0diMbLRepdf3YB+FIEXAMPLE=");
+		request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:qc1e8u8TVl2BpIxwZwsursIb8U8=");
 		request.SetClientKeepAliveRequested(true);
 		request.Send(sock, IOStream::TimeOutInfinite);
 
@@ -396,7 +575,7 @@
 			"/testrequests.pl");
 		request.SetHostName("quotes.s3.amazonaws.com");
 		request.AddHeader("Date", "Wed, 01 Mar  2006 12:00:00 GMT");
-		request.AddHeader("Authorization", "AWS 15B4D3461F177624206A:xQE0diMbLRepdf3YB+FIEXAMPLE=");
+		request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:qc1e8u8TVl2BpIxwZwsursIb8U8=");
 		request.SetClientKeepAliveRequested(true);
 		request.Send(sock, IOStream::TimeOutInfinite);
 
@@ -421,7 +600,7 @@
 			"/newfile");
 		request.SetHostName("quotes.s3.amazonaws.com");
 		request.AddHeader("Date", "Wed, 01 Mar  2006 12:00:00 GMT");
-		request.AddHeader("Authorization", "AWS 15B4D3461F177624206A:xQE0diMbLRepdf3YB+FIEXAMPLE=");
+		request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:kfY1m6V3zTufRy2kj92FpQGKz4M=");
 		request.AddHeader("Content-Type", "text/plain");
 		FileStream fs("testfiles/testrequests.pl");
 		HTTPResponse response;


From boxbackup-dev@boxbackup.org  Wed Jan  7 17:30:31 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Wed,  7 Jan 2009 17:30:31 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2443 - box/trunk/lib/httpserver
Message-ID: <20090107173031.0FC40325026@www.boxbackup.org>

Author: chris
Date: 2009-01-07 17:30:30 +0000 (Wed, 07 Jan 2009)
New Revision: 2443

Modified:
   box/trunk/lib/httpserver/HTTPResponse.h
Log:
Allow copying an HTTPResponse so that it can be returned by S3Client 
methods.


Modified: box/trunk/lib/httpserver/HTTPResponse.h
===================================================================
--- box/trunk/lib/httpserver/HTTPResponse.h	2009-01-06 13:18:07 UTC (rev 2442)
+++ box/trunk/lib/httpserver/HTTPResponse.h	2009-01-07 17:30:30 UTC (rev 2443)
@@ -32,13 +32,35 @@
 	HTTPResponse();
 	~HTTPResponse();
 
-private:
-	// no copying
-	HTTPResponse(const HTTPResponse &);
-	HTTPResponse &operator=(const 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)
+	{
+		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;
 
-public:
 	void SetResponseCode(int Code);
 	int GetResponseCode() { return mResponseCode; }
 	void SetContentType(const char *ContentType);


From boxbackup-dev@boxbackup.org  Wed Jan  7 17:31:04 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Wed,  7 Jan 2009 17:31:04 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2444 - box/trunk/test/httpserver
Message-ID: <20090107173104.B3DF6325026@www.boxbackup.org>

Author: chris
Date: 2009-01-07 17:31:04 +0000 (Wed, 07 Jan 2009)
New Revision: 2444

Modified:
   box/trunk/test/httpserver/testhttpserver.cpp
Log:
Initial implementations of S3Client class.


Modified: box/trunk/test/httpserver/testhttpserver.cpp
===================================================================
--- box/trunk/test/httpserver/testhttpserver.cpp	2009-01-07 17:30:30 UTC (rev 2443)
+++ box/trunk/test/httpserver/testhttpserver.cpp	2009-01-07 17:31:04 UTC (rev 2444)
@@ -9,11 +9,13 @@
 
 #include "Box.h"
 
-#include <stdio.h>
-#include <string.h>
+#include <cstdio>
+#include <cstring>
+#include <ctime>
 
 #include <openssl/hmac.h>
 
+#include "autogen_HTTPException.h"
 #include "HTTPRequest.h"
 #include "HTTPResponse.h"
 #include "HTTPServer.h"
@@ -198,7 +200,7 @@
 
 		unsigned char digest_buffer[EVP_MAX_MD_SIZE];
 		unsigned int digest_size = sizeof(digest_buffer);
-		unsigned char* mac = HMAC(EVP_sha1(),
+		/* 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);
@@ -324,6 +326,194 @@
 	rResponse.SetResponseCode(HTTPResponse::Code_OK);
 }
 
+class S3Client
+{
+	public:
+	S3Client(S3Simulator* 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);
+
+	private:
+	S3Simulator* 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);
+};
+
+HTTPResponse S3Client::GetObject(const std::string& rObjectURI)
+{
+	return FinishAndSendRequest(HTTPRequest::Method_GET, rObjectURI);
+}
+
+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;
+			}
+		}
+	}
+}
+
+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;
+}	
+
 int test(int argc, const char *argv[])
 {
 	if(argc >= 2 && ::strcmp(argv[1], "server") == 0)
@@ -436,6 +626,7 @@
 	TEST_THAT(KillServer(pid));
 	TestRemoteProcessMemLeaks("generic-httpserver.memleaks");
 
+	// correct, official signature should succeed
 	{
 		// http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html
 		HTTPRequest request(HTTPRequest::Method_GET, "/photos/puppy.jpg");
@@ -457,6 +648,7 @@
 		TEST_EQUAL("omgpuppies!\n", response_data);
 	}
 
+	// modified signature should fail
 	{
 		// http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html
 		HTTPRequest request(HTTPRequest::Method_GET, "/photos/puppy.jpg");
@@ -478,18 +670,20 @@
 		TEST_EQUAL("", response_data);
 	}
 
+	// S3Client tests
 	{
-		HTTPRequest request(HTTPRequest::Method_GET, "/nonexist");
-		request.SetHostName("quotes.s3.amazonaws.com");
-		request.AddHeader("Date", "Wed, 01 Mar  2006 12:00:00 GMT");
-		request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:0cSX/YPdtXua1aFFpYmH1tc0ajA=");
-
 		S3Simulator simulator;
+		S3Client client(&simulator, "johnsmith.s3.amazonaws.com",
+			"0PN5J17HBGZHT7JJ3X82",
+			"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o");
 		
-		CollectInBufferStream response_buffer;
-		HTTPResponse response(&response_buffer);
-		
-		simulator.Handle(request, response);
+		HTTPResponse response = client.GetObject("/photos/puppy.jpg");
+		TEST_EQUAL(200, response.GetResponseCode());
+		std::string response_data((const char *)response.GetBuffer(),
+			response.GetSize());
+		TEST_EQUAL("omgpuppies!\n", response_data);
+
+		response = client.GetObject("/nonexist");
 		TEST_EQUAL(404, response.GetResponseCode());
 	}
 


From boxbackup-dev@boxbackup.org  Fri Jan  9 10:19:12 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Fri,  9 Jan 2009 10:19:12 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2445 - box/trunk/lib/httpserver
Message-ID: <20090109101912.30DE0325026@www.boxbackup.org>

Author: chris
Date: 2009-01-09 10:19:11 +0000 (Fri, 09 Jan 2009)
New Revision: 2445

Modified:
   box/trunk/lib/httpserver/HTTPResponse.h
Log:
Reset to remove old content in HTTPResponse in assignment operator.


Modified: box/trunk/lib/httpserver/HTTPResponse.h
===================================================================
--- box/trunk/lib/httpserver/HTTPResponse.h	2009-01-07 17:31:04 UTC (rev 2444)
+++ box/trunk/lib/httpserver/HTTPResponse.h	2009-01-09 10:19:11 UTC (rev 2445)
@@ -48,6 +48,7 @@
 		
 	HTTPResponse &operator=(const HTTPResponse &rOther)
 	{
+		Reset();
 		Write(rOther.GetBuffer(), rOther.GetSize());
 		mResponseCode = rOther.mResponseCode;
 		mResponseIsDynamicContent = rOther.mResponseIsDynamicContent;


From boxbackup-dev@boxbackup.org  Fri Jan  9 10:19:46 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Fri,  9 Jan 2009 10:19:46 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2446 - box/trunk/test/httpserver
Message-ID: <20090109101946.65394325026@www.boxbackup.org>

Author: chris
Date: 2009-01-09 10:19:46 +0000 (Fri, 09 Jan 2009)
New Revision: 2446

Modified:
   box/trunk/test/httpserver/testhttpserver.cpp
Log:
Add support for PUT requests in HTTP simulator.


Modified: box/trunk/test/httpserver/testhttpserver.cpp
===================================================================
--- box/trunk/test/httpserver/testhttpserver.cpp	2009-01-09 10:19:11 UTC (rev 2445)
+++ box/trunk/test/httpserver/testhttpserver.cpp	2009-01-09 10:19:46 UTC (rev 2446)
@@ -347,6 +347,8 @@
 	{ }
 		
 	HTTPResponse GetObject(const std::string& rObjectURI);
+	HTTPResponse PutObject(const std::string& rObjectURI,
+		IOStream& rStreamToSend, const char* pContentType = NULL);
 
 	private:
 	S3Simulator* mpSimulator;
@@ -369,6 +371,13 @@
 	return FinishAndSendRequest(HTTPRequest::Method_GET, rObjectURI);
 }
 
+HTTPResponse S3Client::PutObject(const std::string& rObjectURI,
+	IOStream& rStreamToSend, const char* pContentType)
+{
+	return FinishAndSendRequest(HTTPRequest::Method_PUT, rObjectURI,
+		&rStreamToSend, pContentType);
+}
+
 HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method,
 	const std::string& rRequestURI, IOStream* pStreamToSend,
 	const char* pStreamContentType)
@@ -683,8 +692,24 @@
 			response.GetSize());
 		TEST_EQUAL("omgpuppies!\n", response_data);
 
+		// make sure that assigning to HTTPResponse does clear stream
+		response = client.GetObject("/photos/puppy.jpg");
+		TEST_EQUAL(200, response.GetResponseCode());
+		response_data = std::string((const char *)response.GetBuffer(),
+			response.GetSize());
+		TEST_EQUAL("omgpuppies!\n", response_data);
+
 		response = client.GetObject("/nonexist");
 		TEST_EQUAL(404, response.GetResponseCode());
+		
+		FileStream fs("testfiles/testrequests.pl");
+		response = client.PutObject("/newfile", fs);
+		TEST_EQUAL(200, response.GetResponseCode());
+
+		response = client.GetObject("/newfile");
+		TEST_EQUAL(200, response.GetResponseCode());
+		TEST_THAT(fs.CompareWith(response));
+		TEST_EQUAL(0, ::unlink("testfiles/newfile"));
 	}
 
 	{


From boxbackup-dev@boxbackup.org  Fri Jan  9 11:50:10 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Fri,  9 Jan 2009 11:50:10 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2447 - in box/trunk: lib/httpserver test/httpserver
Message-ID: <20090109115010.E8EE2325026@www.boxbackup.org>

Author: chris
Date: 2009-01-09 11:50:10 +0000 (Fri, 09 Jan 2009)
New Revision: 2447

Added:
   box/trunk/lib/httpserver/S3Client.cpp
   box/trunk/lib/httpserver/S3Client.h
Modified:
   box/trunk/test/httpserver/testhttpserver.cpp
Log:
Move S3Client class into its own files for public access.


Added: box/trunk/lib/httpserver/S3Client.cpp
===================================================================
--- box/trunk/lib/httpserver/S3Client.cpp	                        (rev 0)
+++ box/trunk/lib/httpserver/S3Client.cpp	2009-01-09 11:50:10 UTC (rev 2447)
@@ -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;
+}	


Property changes on: box/trunk/lib/httpserver/S3Client.cpp
___________________________________________________________________
Added: svn:mime-type
   + text/x-c++src
Added: svn:eol-style
   + native

Added: box/trunk/lib/httpserver/S3Client.h
===================================================================
--- box/trunk/lib/httpserver/S3Client.h	                        (rev 0)
+++ box/trunk/lib/httpserver/S3Client.h	2009-01-09 11:50:10 UTC (rev 2447)
@@ -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
+


Property changes on: box/trunk/lib/httpserver/S3Client.h
___________________________________________________________________
Added: svn:mime-type
   + text/x-chdr
Added: svn:eol-style
   + native

Modified: box/trunk/test/httpserver/testhttpserver.cpp
===================================================================
--- box/trunk/test/httpserver/testhttpserver.cpp	2009-01-09 10:19:46 UTC (rev 2446)
+++ box/trunk/test/httpserver/testhttpserver.cpp	2009-01-09 11:50:10 UTC (rev 2447)
@@ -20,6 +20,7 @@
 #include "HTTPResponse.h"
 #include "HTTPServer.h"
 #include "IOStreamGetLine.h"
+#include "S3Client.h"
 #include "ServerControl.h"
 #include "Test.h"
 #include "decode.h"
@@ -326,203 +327,6 @@
 	rResponse.SetResponseCode(HTTPResponse::Code_OK);
 }
 
-class S3Client
-{
-	public:
-	S3Client(S3Simulator* 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:
-	S3Simulator* 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);
-};
-
-HTTPResponse S3Client::GetObject(const std::string& rObjectURI)
-{
-	return FinishAndSendRequest(HTTPRequest::Method_GET, rObjectURI);
-}
-
-HTTPResponse S3Client::PutObject(const std::string& rObjectURI,
-	IOStream& rStreamToSend, const char* pContentType)
-{
-	return FinishAndSendRequest(HTTPRequest::Method_PUT, rObjectURI,
-		&rStreamToSend, pContentType);
-}
-
-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;
-			}
-		}
-	}
-}
-
-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;
-}	
-
 int test(int argc, const char *argv[])
 {
 	if(argc >= 2 && ::strcmp(argv[1], "server") == 0)


From boxbackup-dev@boxbackup.org  Sat Jan 24 23:23:28 2009
From: boxbackup-dev@boxbackup.org (boxbackup-dev@boxbackup.org)
Date: Sat, 24 Jan 2009 23:23:28 +0000 (GMT)
Subject: [Box Backup-commit] COMMIT r2448 - box/trunk/bin/bbackupquery
Message-ID: <20090124232328.765C1325FCB@www.boxbackup.org>

Author: chris
Date: 2009-01-24 23:23:27 +0000 (Sat, 24 Jan 2009)
New Revision: 2448

Modified:
   box/trunk/bin/bbackupquery/BackupQueries.cpp
Log:
Compile fix.


Modified: box/trunk/bin/bbackupquery/BackupQueries.cpp
===================================================================
--- box/trunk/bin/bbackupquery/BackupQueries.cpp	2009-01-09 11:50:10 UTC (rev 2447)
+++ box/trunk/bin/bbackupquery/BackupQueries.cpp	2009-01-24 23:23:27 UTC (rev 2448)
@@ -1728,7 +1728,8 @@
 							SelfFlushingStream flushFile(*fileOnServerStream);
 							// Open the local file
 							FileStream l(localPath.c_str());
-							equal = l.CompareWith(fileOnServerStream,  mrConnection.GetTimeout());
+							equal = l.CompareWith(*fileOnServerStream,
+								mrConnection.GetTimeout());
 						}
 					}
 


