/*
 * httpconnect.cpp - HTTP "CONNECT" proxy
 * Copyright (C) 2003  Justin Karneges
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "httpconnect.h"

#include <tqstringlist.h>
#include "bsocket.h"
#include "base64.h"

#ifdef PROX_DEBUG
#include <stdio.h>
#endif

// CS_NAMESPACE_BEGIN

static TQString extractLine(TQByteArray *buf, bool *found)
{
	// scan for newline
	int n;
	for(n = 0; n < (int)buf->size()-1; ++n) {
		if(buf->at(n) == '\r' && buf->at(n+1) == '\n') {
			TQCString cstr;
			cstr.resize(n+1);
			memcpy(cstr.data(), buf->data(), n);
			n += 2; // hack off CR/LF

			memmove(buf->data(), buf->data() + n, buf->size() - n);
			buf->resize(buf->size() - n);
			TQString s = TQString::fromUtf8(cstr);

			if(found)
				*found = true;
			return s;
		}
	}

	if(found)
		*found = false;
	return "";
}

static bool extractMainHeader(const TQString &line, TQString *proto, int *code, TQString *msg)
{
	int n = line.find(' ');
	if(n == -1)
		return false;
	if(proto)
		*proto = line.mid(0, n);
	++n;
	int n2 = line.find(' ', n);
	if(n2 == -1)
		return false;
	if(code)
		*code = line.mid(n, n2-n).toInt();
	n = n2+1;
	if(msg)
		*msg = line.mid(n);
	return true;
}

class HttpConnect::Private
{
public:
	Private() {}

	BSocket sock;
	TQString host;
	int port;
	TQString user, pass;
	TQString real_host;
	int real_port;

	TQByteArray recvBuf;

	bool inHeader;
	TQStringList headerLines;

	int toWrite;
	bool active;
};

HttpConnect::HttpConnect(TQObject *parent)
:ByteStream(parent)
{
	d = new Private;
	connect(&d->sock, TQT_SIGNAL(connected()), TQT_SLOT(sock_connected()));
	connect(&d->sock, TQT_SIGNAL(connectionClosed()), TQT_SLOT(sock_connectionClosed()));
	connect(&d->sock, TQT_SIGNAL(delayedCloseFinished()), TQT_SLOT(sock_delayedCloseFinished()));
	connect(&d->sock, TQT_SIGNAL(readyRead()), TQT_SLOT(sock_readyRead()));
	connect(&d->sock, TQT_SIGNAL(bytesWritten(int)), TQT_SLOT(sock_bytesWritten(int)));
	connect(&d->sock, TQT_SIGNAL(error(int)), TQT_SLOT(sock_error(int)));

	reset(true);
}

HttpConnect::~HttpConnect()
{
	reset(true);
	delete d;
}

void HttpConnect::reset(bool clear)
{
	if(d->sock.state() != BSocket::Idle)
		d->sock.close();
	if(clear) {
		clearReadBuffer();
		d->recvBuf.resize(0);
	}
	d->active = false;
}

void HttpConnect::setAuth(const TQString &user, const TQString &pass)
{
	d->user = user;
	d->pass = pass;
}

void HttpConnect::connectToHost(const TQString &proxyHost, int proxyPort, const TQString &host, int port)
{
	reset(true);

	d->host = proxyHost;
	d->port = proxyPort;
	d->real_host = host;
	d->real_port = port;

#ifdef PROX_DEBUG
	fprintf(stderr, "HttpConnect: Connecting to %s:%d", proxyHost.latin1(), proxyPort);
	if(d->user.isEmpty())
		fprintf(stderr, "\n");
	else
		fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1());
#endif
	d->sock.connectToHost(d->host, d->port);
}

bool HttpConnect::isOpen() const
{
	return d->active;
}

void HttpConnect::close()
{
	d->sock.close();
	if(d->sock.bytesToWrite() == 0)
		reset();
}

void HttpConnect::write(const TQByteArray &buf)
{
	if(d->active)
		d->sock.write(buf);
}

TQByteArray HttpConnect::read(int bytes)
{
	return ByteStream::read(bytes);
}

int HttpConnect::bytesAvailable() const
{
	return ByteStream::bytesAvailable();
}

int HttpConnect::bytesToWrite() const
{
	if(d->active)
		return d->sock.bytesToWrite();
	else
		return 0;
}

void HttpConnect::sock_connected()
{
#ifdef PROX_DEBUG
	fprintf(stderr, "HttpConnect: Connected\n");
#endif
	d->inHeader = true;
	d->headerLines.clear();

	// connected, now send the request
	TQString s;
	s += TQString("CONNECT ") + d->real_host + ':' + TQString::number(d->real_port) + " HTTP/1.0\r\n";
	if(!d->user.isEmpty()) {
		TQString str = d->user + ':' + d->pass;
		s += TQString("Proxy-Authorization: Basic ") + Base64::encodeString(str) + "\r\n";
	}
	s += "Proxy-Connection: Keep-Alive\r\n";
	s += "Pragma: no-cache\r\n";
	s += "\r\n";

	TQCString cs = s.utf8();
	TQByteArray block(cs.length());
	memcpy(block.data(), cs.data(), block.size());
	d->toWrite = block.size();
	d->sock.write(block);
}

void HttpConnect::sock_connectionClosed()
{
	if(d->active) {
		reset();
		connectionClosed();
	}
	else {
		error(ErrProxyNeg);
	}
}

void HttpConnect::sock_delayedCloseFinished()
{
	if(d->active) {
		reset();
		delayedCloseFinished();
	}
}

void HttpConnect::sock_readyRead()
{
	TQByteArray block = d->sock.read();

	if(!d->active) {
		ByteStream::appendArray(&d->recvBuf, block);

		if(d->inHeader) {
			// grab available lines
			while(1) {
				bool found;
				TQString line = extractLine(&d->recvBuf, &found);
				if(!found)
					break;
				if(line.isEmpty()) {
					d->inHeader = false;
					break;
				}
				d->headerLines += line;
			}

			// done with grabbing the header?
			if(!d->inHeader) {
				TQString str = d->headerLines.first();
				d->headerLines.remove(d->headerLines.begin());

				TQString proto;
				int code;
				TQString msg;
				if(!extractMainHeader(str, &proto, &code, &msg)) {
#ifdef PROX_DEBUG
					fprintf(stderr, "HttpConnect: invalid header!\n");
#endif
					reset(true);
					error(ErrProxyNeg);
					return;
				}
				else {
#ifdef PROX_DEBUG
					fprintf(stderr, "HttpConnect: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1());
					for(TQStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it)
						fprintf(stderr, "HttpConnect: * [%s]\n", (*it).latin1());
#endif
				}

				if(code == 200) { // OK
#ifdef PROX_DEBUG
					fprintf(stderr, "HttpConnect: << Success >>\n");
#endif
					d->active = true;
					connected();

					if(!d->recvBuf.isEmpty()) {
						appendRead(d->recvBuf);
						d->recvBuf.resize(0);
						readyRead();
						return;
					}
				}
				else {
					int err;
					TQString errStr;
					if(code == 407) { // Authentication failed
						err = ErrProxyAuth;
						errStr = tr("Authentication failed");
					}
					else if(code == 404) { // Host not found
						err = ErrHostNotFound;
						errStr = tr("Host not found");
					}
					else if(code == 403) { // Access denied
						err = ErrProxyNeg;
						errStr = tr("Access denied");
					}
					else if(code == 503) { // Connection refused
						err = ErrConnectionRefused;
						errStr = tr("Connection refused");
					}
					else { // invalid reply
						err = ErrProxyNeg;
						errStr = tr("Invalid reply");
					}

#ifdef PROX_DEBUG
					fprintf(stderr, "HttpConnect: << Error >> [%s]\n", errStr.latin1());
#endif
					reset(true);
					error(err);
					return;
				}
			}
		}
	}
	else {
		appendRead(block);
		readyRead();
		return;
	}
}

void HttpConnect::sock_bytesWritten(int x)
{
	if(d->toWrite > 0) {
		int size = x;
		if(d->toWrite < x)
			size = d->toWrite;
		d->toWrite -= size;
		x -= size;
	}

	if(d->active && x > 0)
		bytesWritten(x);
}

void HttpConnect::sock_error(int x)
{
	if(d->active) {
		reset();
		error(ErrRead);
	}
	else {
		reset(true);
		if(x == BSocket::ErrHostNotFound)
			error(ErrProxyConnect);
		else if(x == BSocket::ErrConnectionRefused)
			error(ErrProxyConnect);
		else if(x == BSocket::ErrRead)
			error(ErrProxyNeg);
	}
}

// CS_NAMESPACE_END

#include "httpconnect.moc"
