//
// HTTPSClientSessionTest.cpp
//
// $Id: //poco/1.4/NetSSL_OpenSSL/testsuite/src/HTTPSClientSessionTest.cpp#2 $
//
// Copyright (c) 2006, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
// 
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//


#include "HTTPSClientSessionTest.h"
#include "CppUnit/TestCaller.h"
#include "CppUnit/TestSuite.h"
#include "Poco/Net/HTTPSClientSession.h"
#include "Poco/Net/HTTPRequest.h"
#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/Net/HTTPRequestHandlerFactory.h"
#include "Poco/Net/HTTPResponse.h"
#include "Poco/Net/HTTPServer.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/SecureStreamSocket.h"
#include "Poco/Net/Context.h"
#include "Poco/Net/Session.h"
#include "Poco/Net/SSLManager.h"
#include "Poco/Util/Application.h"
#include "Poco/Util/AbstractConfiguration.h"
#include "Poco/StreamCopier.h"
#include "Poco/Exception.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include "Poco/Thread.h"
#include "HTTPSTestServer.h"
#include <istream>
#include <ostream>
#include <sstream>


using namespace Poco::Net;
using Poco::Util::Application;
using Poco::StreamCopier;
using Poco::Thread;


class TestRequestHandler: public HTTPRequestHandler
	/// Return a HTML document with the current date and time.
{
public:
	TestRequestHandler()
	{
	}
	
	void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response)
	{
		response.setChunkedTransferEncoding(true);
		response.setContentType(request.getContentType());
		std::ostream& ostr = response.send();
		Poco::StreamCopier::copyStream(request.stream(), ostr);
	}

};


class TestRequestHandlerFactory: public HTTPRequestHandlerFactory
{
public:
	TestRequestHandlerFactory()
	{
	}

	HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request)
	{
		return new TestRequestHandler();
	}
};


HTTPSClientSessionTest::HTTPSClientSessionTest(const std::string& name): CppUnit::TestCase(name)
{
}


HTTPSClientSessionTest::~HTTPSClientSessionTest()
{
}


void HTTPSClientSessionTest::testGetSmall()
{
	HTTPSTestServer srv;
	HTTPSClientSession s("localhost", srv.port());
	HTTPRequest request(HTTPRequest::HTTP_GET, "/small");
	s.sendRequest(request);
	HTTPResponse response;
	std::istream& rs = s.receiveResponse(response);
	assert (response.getContentLength() == HTTPSTestServer::SMALL_BODY.length());
	assert (response.getContentType() == "text/plain");
	std::ostringstream ostr;
	StreamCopier::copyStream(rs, ostr);
	assert (ostr.str() == HTTPSTestServer::SMALL_BODY);
}


void HTTPSClientSessionTest::testGetLarge()
{
	HTTPSTestServer srv;
	HTTPSClientSession s("localhost", srv.port());
	HTTPRequest request(HTTPRequest::HTTP_GET, "/large");
	s.sendRequest(request);
	HTTPResponse response;
	std::istream& rs = s.receiveResponse(response);
	assert (response.getContentLength() == HTTPSTestServer::LARGE_BODY.length());
	assert (response.getContentType() == "text/plain");
	std::ostringstream ostr;
	StreamCopier::copyStream(rs, ostr);
	assert (ostr.str() == HTTPSTestServer::LARGE_BODY);
}


void HTTPSClientSessionTest::testHead()
{
	HTTPSTestServer srv;
	HTTPSClientSession s("localhost", srv.port());
	HTTPRequest request(HTTPRequest::HTTP_HEAD, "/large");
	s.sendRequest(request);
	HTTPResponse response;
	std::istream& rs = s.receiveResponse(response);
	assert (response.getContentLength() == HTTPSTestServer::LARGE_BODY.length());
	assert (response.getContentType() == "text/plain");
	std::ostringstream ostr;
	assert (StreamCopier::copyStream(rs, ostr) == 0);
}


void HTTPSClientSessionTest::testPostSmallIdentity()
{
	HTTPSTestServer srv;
	HTTPSClientSession s("localhost", srv.port());
	HTTPRequest request(HTTPRequest::HTTP_POST, "/echo");
	std::string body("this is a random request body\r\n0\r\n");
	request.setContentLength((int) body.length());
	s.sendRequest(request) << body;
	HTTPResponse response;
	std::istream& rs = s.receiveResponse(response);
	assert (response.getContentLength() == body.length());
	std::ostringstream ostr;
	StreamCopier::copyStream(rs, ostr);
	assert (ostr.str() == body);
}


void HTTPSClientSessionTest::testPostLargeIdentity()
{
	HTTPSTestServer srv;
	HTTPSClientSession s("localhost", srv.port());
	HTTPRequest request(HTTPRequest::HTTP_POST, "/echo");
	std::string body(8000, 'x');
	body.append("\r\n0\r\n");
	request.setContentLength((int) body.length());
	s.sendRequest(request) << body;
	HTTPResponse response;
	std::istream& rs = s.receiveResponse(response);
	assert (response.getContentLength() == body.length());
	std::ostringstream ostr;
	StreamCopier::copyStream(rs, ostr);
	assert (ostr.str() == body);
}


void HTTPSClientSessionTest::testPostSmallChunked()
{
	HTTPSTestServer srv;
	HTTPSClientSession s("localhost", srv.port());
	HTTPRequest request(HTTPRequest::HTTP_POST, "/echo");
	std::string body("this is a random request body");
	request.setChunkedTransferEncoding(true);
	s.sendRequest(request) << body;
	HTTPResponse response;
	std::istream& rs = s.receiveResponse(response);
	assert (response.getChunkedTransferEncoding());
	assert (response.getContentLength() == HTTPMessage::UNKNOWN_CONTENT_LENGTH);
	std::ostringstream ostr;
	StreamCopier::copyStream(rs, ostr);
	assert (ostr.str() == body);
}


void HTTPSClientSessionTest::testPostLargeChunked()
{
	HTTPSTestServer srv;
	HTTPSClientSession s("localhost", srv.port());
	HTTPRequest request(HTTPRequest::HTTP_POST, "/echo");
	std::string body(16000, 'x');
	request.setChunkedTransferEncoding(true);
	s.sendRequest(request) << body;
	HTTPResponse response;
	std::istream& rs = s.receiveResponse(response);
	assert (response.getChunkedTransferEncoding());
	assert (response.getContentLength() == HTTPMessage::UNKNOWN_CONTENT_LENGTH);
	std::ostringstream ostr;
	StreamCopier::copyStream(rs, ostr);
	assert (ostr.str() == body);
}


void HTTPSClientSessionTest::testPostLargeChunkedKeepAlive()
{
	SecureServerSocket svs(32322);
	HTTPServer srv(new TestRequestHandlerFactory(), svs, new HTTPServerParams());
	srv.start();
	try
	{
		HTTPSClientSession s("localhost", srv.port());
		s.setKeepAlive(true);
		for (int i = 0; i < 10; ++i)
		{
			HTTPRequest request(HTTPRequest::HTTP_POST, "/keepAlive", HTTPMessage::HTTP_1_1);
			std::string body(16000, 'x');
			request.setChunkedTransferEncoding(true);
			s.sendRequest(request) << body;
			HTTPResponse response;
			std::istream& rs = s.receiveResponse(response);
			assert (response.getChunkedTransferEncoding());
			assert (response.getContentLength() == HTTPMessage::UNKNOWN_CONTENT_LENGTH);
			std::ostringstream ostr;
			StreamCopier::copyStream(rs, ostr);
			assert (ostr.str() == body);
		}
		srv.stop();
	}
	catch (...)
	{
		srv.stop();
		throw;
	}
}


void HTTPSClientSessionTest::testKeepAlive()
{
	HTTPSTestServer srv;
	HTTPSClientSession s("localhost", srv.port());
	s.setKeepAlive(true);
	HTTPRequest request(HTTPRequest::HTTP_HEAD, "/keepAlive", HTTPMessage::HTTP_1_1);
	s.sendRequest(request);
	HTTPResponse response;
	std::istream& rs1 = s.receiveResponse(response);
	assert (response.getContentLength() == HTTPSTestServer::SMALL_BODY.length());
	assert (response.getContentType() == "text/plain");
	assert (response.getKeepAlive());
	std::ostringstream ostr1;
	assert (StreamCopier::copyStream(rs1, ostr1) == 0);
	
	request.setMethod(HTTPRequest::HTTP_GET);
	request.setURI("/small");
	s.sendRequest(request);
	std::istream& rs2 = s.receiveResponse(response);
	assert (response.getContentLength() == HTTPSTestServer::SMALL_BODY.length());
	assert (response.getKeepAlive());
	std::ostringstream ostr2;
	StreamCopier::copyStream(rs2, ostr2);
	assert (ostr2.str() == HTTPSTestServer::SMALL_BODY);
	
	request.setMethod(HTTPRequest::HTTP_GET);
	request.setURI("/large");
	s.sendRequest(request);
	std::istream& rs3 = s.receiveResponse(response);
	assert (response.getContentLength() == HTTPMessage::UNKNOWN_CONTENT_LENGTH);
	assert (response.getChunkedTransferEncoding());
	assert (response.getKeepAlive());
	std::ostringstream ostr3;
	StreamCopier::copyStream(rs3, ostr3);
	assert (ostr3.str() == HTTPSTestServer::LARGE_BODY);

	request.setMethod(HTTPRequest::HTTP_HEAD);
	request.setURI("/large");
	s.sendRequest(request);
	std::istream& rs4 = s.receiveResponse(response);
	assert (response.getContentLength() == HTTPSTestServer::LARGE_BODY.length());
	assert (response.getContentType() == "text/plain");
	assert (!response.getKeepAlive());
	std::ostringstream ostr4;
	assert (StreamCopier::copyStream(rs4, ostr4) == 0);
}


void HTTPSClientSessionTest::testInterop()
{
	HTTPSClientSession s("secure.appinf.com");
	HTTPRequest request(HTTPRequest::HTTP_GET, "/public/poco/NetSSL.txt");
	s.sendRequest(request);
	X509Certificate cert = s.serverCertificate();
	HTTPResponse response;
	std::istream& rs = s.receiveResponse(response);
	std::ostringstream ostr;
	StreamCopier::copyStream(rs, ostr);
	std::string str(ostr.str());
	assert (str == "This is a test file for NetSSL.\n");
	assert (cert.commonName() == "secure.appinf.com" || cert.commonName() == "*.appinf.com");
}


void HTTPSClientSessionTest::testProxy()
{
	HTTPSTestServer srv;
	HTTPSClientSession s("secure.appinf.com");
	s.setProxy(
		Application::instance().config().getString("testsuite.proxy.host"), 
		Application::instance().config().getInt("testsuite.proxy.port")
	);
	HTTPRequest request(HTTPRequest::HTTP_GET, "/public/poco/NetSSL.txt");
	s.sendRequest(request);
	X509Certificate cert = s.serverCertificate();
	HTTPResponse response;
	std::istream& rs = s.receiveResponse(response);
	std::ostringstream ostr;
	StreamCopier::copyStream(rs, ostr);
	std::string str(ostr.str());
	assert (str == "This is a test file for NetSSL.\n");
	assert (cert.commonName() == "secure.appinf.com" || cert.commonName() == "*.appinf.com");
}


void HTTPSClientSessionTest::testCachedSession()
{
	// ensure OpenSSL machinery is fully setup
	Context::Ptr pDefaultServerContext = SSLManager::instance().defaultServerContext();
	Context::Ptr pDefaultClientContext = SSLManager::instance().defaultClientContext();
	
	Context::Ptr pServerContext = new Context(
		Context::SERVER_USE, 
		Application::instance().config().getString("openSSL.server.privateKeyFile"),
		Application::instance().config().getString("openSSL.server.privateKeyFile"),
		Application::instance().config().getString("openSSL.server.caConfig"),
		Context::VERIFY_NONE,
		9,
		true,
		"ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");	
	pServerContext->enableSessionCache(true, "TestSuite");
	pServerContext->setSessionTimeout(10);
	pServerContext->setSessionCacheSize(1000);
	pServerContext->disableStatelessSessionResumption();

	HTTPSTestServer srv(pServerContext);

	Context::Ptr pClientContext = new Context(
		Context::CLIENT_USE, 
		Application::instance().config().getString("openSSL.client.privateKeyFile"),
		Application::instance().config().getString("openSSL.client.privateKeyFile"),
		Application::instance().config().getString("openSSL.client.caConfig"),
		Context::VERIFY_RELAXED,
		9,
		true,
		"ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
	pClientContext->enableSessionCache(true);

	HTTPSClientSession s1("localhost", srv.port(), pClientContext);
	HTTPRequest request1(HTTPRequest::HTTP_GET, "/small");
	s1.sendRequest(request1);
	Session::Ptr pSession1 = s1.sslSession();
	HTTPResponse response1;
	std::istream& rs1 = s1.receiveResponse(response1);
	assert (response1.getContentLength() == HTTPSTestServer::SMALL_BODY.length());
	assert (response1.getContentType() == "text/plain");
	std::ostringstream ostr1;
	StreamCopier::copyStream(rs1, ostr1);
	assert (ostr1.str() == HTTPSTestServer::SMALL_BODY);

	HTTPSClientSession s2("localhost", srv.port(), pClientContext, pSession1);
	HTTPRequest request2(HTTPRequest::HTTP_GET, "/small");
	s2.sendRequest(request2);
	Session::Ptr pSession2 = s2.sslSession();
	HTTPResponse response2;
	std::istream& rs2 = s2.receiveResponse(response2);
	assert (response2.getContentLength() == HTTPSTestServer::SMALL_BODY.length());
	assert (response2.getContentType() == "text/plain");
	std::ostringstream ostr2;
	StreamCopier::copyStream(rs2, ostr2);
	assert (ostr2.str() == HTTPSTestServer::SMALL_BODY);
	
	assert (pSession1 == pSession2);

	HTTPRequest request3(HTTPRequest::HTTP_GET, "/small");
	s2.sendRequest(request3);
	Session::Ptr pSession3 = s2.sslSession();
	HTTPResponse response3;
	std::istream& rs3 = s2.receiveResponse(response3);
	assert (response3.getContentLength() == HTTPSTestServer::SMALL_BODY.length());
	assert (response3.getContentType() == "text/plain");
	std::ostringstream ostr3;
	StreamCopier::copyStream(rs3, ostr3);
	assert (ostr3.str() == HTTPSTestServer::SMALL_BODY);

	assert (pSession1 == pSession3);

	Thread::sleep(15000); // wait for session to expire
	pServerContext->flushSessionCache();
	
	HTTPRequest request4(HTTPRequest::HTTP_GET, "/small");
	s2.sendRequest(request4);
	Session::Ptr pSession4 = s2.sslSession();
	HTTPResponse response4;
	std::istream& rs4 = s2.receiveResponse(response4);
	assert (response4.getContentLength() == HTTPSTestServer::SMALL_BODY.length());
	assert (response4.getContentType() == "text/plain");
	std::ostringstream ostr4;
	StreamCopier::copyStream(rs4, ostr4);
	assert (ostr4.str() == HTTPSTestServer::SMALL_BODY);

	assert (pSession1 != pSession4);
}


void HTTPSClientSessionTest::setUp()
{
}


void HTTPSClientSessionTest::tearDown()
{
}


CppUnit::Test* HTTPSClientSessionTest::suite()
{
	CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("HTTPSClientSessionTest");

	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testGetSmall);
	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testGetLarge);
	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testHead);
	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testPostSmallIdentity);
	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testPostLargeIdentity);
	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testPostSmallChunked);
	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testPostLargeChunked);
	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testPostLargeChunkedKeepAlive);
	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testKeepAlive);
	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testInterop);
	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testProxy);
	CppUnit_addTest(pSuite, HTTPSClientSessionTest, testCachedSession);

	return pSuite;
}
