<commit_before>/*************************************************************************
 *   Copyright (C) 2011-2013 by <NAME>                       *
 *   paul-louis (at) ageneau (dot) org                                   *
 *                                                                       *
 *   This file is part of Teapotnet.                                     *
 *                                                                       *
 *   Teapotnet is free software: you can redistribute it and/or modify   *
 *   it under the terms of the GNU Affero General Public License as      *
 *   published by the Free Software Foundation, either version 3 of      *
 *   the License, or (at your option) any later version.                 *
 *                                                                       *
 *   Teapotnet is distributed in the hope that it will be useful, but    *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of          *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the        *
 *   GNU Affero General Public License for more details.                 *
 *                                                                       *
 *   You should have received a copy of the GNU Affero General Public    *
 *   License along with Teapotnet.                                       *
 *   If not, see <http://www.gnu.org/licenses/>.                         *
 *************************************************************************/

#include "tpn/httptunnel.h"
#include "tpn/config.h"

#include "pla/exception.h"
#include "pla/scheduler.h"
#include "pla/random.h"
#include "pla/proxy.h"

namespace tpn
{

#ifdef ANDROID
String HttpTunnel::UserAgent = "Mozilla/5.0 (Android; Mobile; rv:32.0) Gecko/32.0 Firefox/32.0";	// mobile is important
#else
String HttpTunnel::UserAgent = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";	// IE should be better for very restrictive environments
#endif

size_t HttpTunnel::DefaultPostSize = 1*1024;		// 1 KB
size_t HttpTunnel::MaxPostSize = 2*1024*1024;		// 2 MB
size_t HttpTunnel::MaxDownloadSize = 20*1024*1024;	// 20 MB
double HttpTunnel::ConnTimeout = 30.;
double HttpTunnel::SockTimeout = 10.;
double HttpTunnel::FlushTimeout = 0.2;
double HttpTunnel::ReadTimeout = 60.;

Map<uint32_t,HttpTunnel::Server*> 	HttpTunnel::Sessions;
Mutex					HttpTunnel::SessionsMutex;

HttpTunnel::Server *HttpTunnel::Incoming(Socket *sock)
{
	Http::Request request;
	try {
		try {
			sock->setTimeout(SockTimeout);
			request.recv(sock, false); // POST content is not parsed
		
			uint32_t session = 0;

			String cookie;
			if(request.cookies.get("session", cookie))
				cookie.extract(session);	

			LogDebug("HttpTunnel::Incoming", "Received " + request.method + " " + request.fullUrl + " (session="+String::number(session)+")");

			Server *server = NULL;
			bool isNew = false;

			if(!session)
			{
				if(request.method != "GET") 
				{
					LogDebug("HttpTunnel::Incoming", "Missing session number in POST request");
					throw 400;
				}
				
				SessionsMutex.lock();
				while(!session || Sessions.contains(session))
					Random().readBinary(session);
				Sessions.insert(session, NULL);
				SessionsMutex.unlock();

				server = new Server(session);   // Server registers the session
				isNew = true;
			}
			else {
				SessionsMutex.lock();
				Sessions.get(session, server);
				SessionsMutex.unlock();
			}

			if(server)
			{
				if(request.method == "GET") 
				{
					Synchronize(server);
					if(server->mDownSock) throw 409;	// Conflict
					if(server->mClosed) throw 400;	// Closed session
					
					Http::Response response(request, 200);
					response.headers["Cache-Control"] = "no-cache";
					response.cookies["session"] << session;

					// First response should be forced down the potential proxy as soon as possible	
					if(isNew)
					{
						response.headers["Content-Type"] = "text/html";
						response.send(sock);
						delete sock;
						return server;
					}
	
					response.headers["Content-Type"] = "application/octet-stream";
					response.send(sock);
					server->mDownSock = sock;
					server->mDownloadLeft = MaxDownloadSize;
					server->notifyAll();
					Scheduler::Global->schedule(&server->mFlushTask, ReadTimeout*0.75);
					return NULL;
				}
				else {
					Assert(!isNew);
					
					uint8_t command;
					if(!sock->readBinary(command))
						throw NetException("Connection unexpectedly closed");
					
					if(command != TunnelOpen)
					{
						// Remote implementation is probably bogus
						LogWarn("HttpTunnel::Incoming", "Invalid tunnel opening sequence");
						throw 400;
					}

					uint16_t len;
					if(!sock->readBinary(len) || !sock->ignore(len))	// auth data ignored
						throw NetException("Connection unexpectedly closed");
					
					Synchronize(server);
					if(server->mUpSock) throw 409;	// Conflict
					if(server->mClosed) throw 400;	// Closed session
					Assert(!server->mPostBlockLeft);
					server->mUpSock = sock;
					server->mUpRequest = request;
					server->notifyAll();
					return NULL;
				}
			}
			else {
				// Unknown or closed session
				LogDebug("HttpTunnel::Incoming", "Unknown or closed session: " + String::number(session));
				throw 400;
			}
		}
		catch(const Timeout &e)
		{
			throw 408;
		}
		catch(const NetException &e)
		{
			LogDebug("HttpTunnel::Incoming", e.what());
			// no throw
		}
		catch(const Exception &e)
		{
			LogWarn("HttpTunnel::Incoming", e.what());
			throw 500;
		}
	}
	catch(int code)
	{
		try {
			Http::Response response(request, code);
			response.send(sock);
		}
		catch(...)
		{<commit_msg>Cosmetic changes in HttpTunnel<commit_after>/*************************************************************************
 *   Copyright (C) 2011-2013 by <NAME>                       *
 *   paul-louis (at) ageneau (dot) org                                   *
 *                                                                       *
 *   This file is part of Teapotnet.                                     *
 *                                                                       *
 *   Teapotnet is free software: you can redistribute it and/or modify   *
 *   it under the terms of the GNU Affero General Public License as      *
 *   published by the Free Software Foundation, either version 3 of      *
 *   the License, or (at your option) any later version.                 *
 *                                                                       *
 *   Teapotnet is distributed in the hope that it will be useful, but    *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of          *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the        *
 *   GNU Affero General Public License for more details.                 *
 *                                                                       *
 *   You should have received a copy of the GNU Affero General Public    *
 *   License along with Teapotnet.                                       *
 *   If not, see <http://www.gnu.org/licenses/>.                         *
 *************************************************************************/

#include "tpn/httptunnel.h"
#include "tpn/config.h"

#include "pla/exception.h"
#include "pla/scheduler.h"
#include "pla/random.h"
#include "pla/proxy.h"

namespace tpn
{

#ifdef ANDROID
String HttpTunnel::UserAgent = "Mozilla/5.0 (Android; Mobile; rv:32.0) Gecko/32.0 Firefox/32.0";	// mobile is important
#else
String HttpTunnel::UserAgent = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";	// IE should be better for very restrictive environments
#endif

size_t HttpTunnel::DefaultPostSize = 1*1024;		// 1 KB
size_t HttpTunnel::MaxPostSize = 2*1024*1024;		// 2 MB
size_t HttpTunnel::MaxDownloadSize = 20*1024*1024;	// 20 MB
double HttpTunnel::ConnTimeout = 30.;
double HttpTunnel::SockTimeout = 10.;
double HttpTunnel::FlushTimeout = 0.2;
double HttpTunnel::ReadTimeout = 60.;

Map<uint32_t,HttpTunnel::Server*> 	HttpTunnel::Sessions;
Mutex					HttpTunnel::SessionsMutex;

HttpTunnel::Server *HttpTunnel::Incoming(Socket *sock)
{
	Http::Request request;
	try {
		try {
			sock->setTimeout(SockTimeout);
			request.recv(sock, false); // POST content is not parsed
		
			uint32_t session = 0;

			String cookie;
			if(request.cookies.get("session", cookie))
				cookie.extract(session);	

			LogDebug("HttpTunnel::Incoming", "Received " + request.method + " " + request.fullUrl + " (session="+String::number(session)+")");

			Server *server = NULL;
			bool isNew = false;

			if(!session)
			{
				if(request.method != "GET") 
				{
					LogDebug("HttpTunnel::Incoming", "Missing session number in POST request");
					throw 400;
				}
				
				SessionsMutex.lock();
				while(!session || Sessions.contains(session))
					Random().readBinary(session);
				Sessions.insert(session, NULL);
				SessionsMutex.unlock();

				server = new Server(session);   // Server registers the session
				isNew = true;
			}
			else {
				SessionsMutex.lock();
				Sessions.get(session, server);
				SessionsMutex.unlock();
			}

			if(!server)
			{
				// Unknown or closed session
				LogDebug("HttpTunnel::Incoming", "Unknown or closed session: " + String::number(session));
				throw 400;
			}
			
			if(request.method == "GET") 
			{
				Synchronize(server);
				if(server->mDownSock) throw 409;	// Conflict
				if(server->mClosed) throw 400;		// Closed session
				
				Http::Response response(request, 200);
				response.headers["Cache-Control"] = "no-cache";
				response.cookies["session"] << session;

				// First response should be forced down the potential proxy as soon as possible	
				if(isNew)
				{
					response.headers["Content-Type"] = "text/html";
					response.send(sock);
					delete sock;
					return server;
				}

				response.headers["Content-Type"] = "application/octet-stream";
				response.send(sock);
				server->mDownSock = sock;
				server->mDownloadLeft = MaxDownloadSize;
				server->notifyAll();
				Scheduler::Global->schedule(&server->mFlushTask, ReadTimeout*0.75);
				return NULL;
			}
			else {
				Assert(!isNew);
				
				uint8_t command;
				if(!sock->readBinary(command))
					throw NetException("Connection unexpectedly closed");
				
				if(command != TunnelOpen)
				{
					// Remote implementation is probably bogus
					LogWarn("HttpTunnel::Incoming", "Invalid tunnel opening sequence");
					throw 400;
				}

				uint16_t len;
				if(!sock->readBinary(len) || !sock->ignore(len))	// auth data ignored
					throw NetException("Connection unexpectedly closed");
				
				Synchronize(server);
				if(server->mUpSock) throw 409;	// Conflict
				if(server->mClosed) throw 400;	// Closed session
				Assert(!server->mPostBlockLeft);
				server->mUpSock = sock;
				server->mUpRequest = request;
				server->notifyAll();
				return NULL;
			}
		}
		catch(const Timeout &e)
		{
			throw 408;
		}
		catch(const NetException &e)
		{
			LogDebug("HttpTunnel::Incoming", e.what());
			// no throw
		}
		catch(const Exception &e)
		{
			LogWarn("HttpTunnel::Incoming", e.what());
			throw 500;
		}
	}
	catch(int code)
	{
		try {
			Http::Response response(request, code);
			response.send(sock);
		}
		catch(...)
		{
{'fraction_non_alphanumeric': 0.1432514245014245, 'fraction_numerical': 0.018874643874643875, 'mean_word_length': 2.3611609814482346, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 36, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2713422', 'n_tokens_mistral': 4076, 'n_tokens_neox': 3213, 'n_words': 814}