/* -*- c++ -*-
*
* generichttpserver.cpp
*
* Copyright (C) 2004 Petter Stokke <ummo@hellokitty.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/

#include "generichttpserver.h"

#include "version.h"
#include "compat.h"

#include <kdebug.h>
#include <klocale.h>




GenericHTTPServer::GenericHTTPServer(const QString& listenAddress, int listenPort)
 : KExtendedSocket(listenAddress, listenPort, KExtendedSocket::passiveSocket | KExtendedSocket::inetSocket)
{
    setAddressReusable(true);
    QObject::connect(this, SIGNAL(readyAccept()), this, SLOT(incomingConnection()));
    if (listen()) {
	kdDebug() << "Failed to bind socket." << endl;
	return;
    }
    kdDebug() << "Listening on " << listenAddress << " port " << listenPort << endl;
}

void GenericHTTPServer::incomingConnection()
{
    kdDebug() << "Inbound connection." << endl;
    KExtendedSocket* c;
    if (accept(c)) {
	kdDebug() << "Accept failed." << endl;
	return;
    }
    kdDebug() << "Connection accepted." << endl;
    buildSession(c);
}




GenericHTTPSession::GenericHTTPSession(GenericHTTPServer* parent, KExtendedSocket* sock)
    : QObject(parent)
    , m_parent(parent)
    , m_sock(sock)
    , m_isHEAD(false)
{
    kdDebug() << "New HTTP connection from " << m_sock->peerAddress()->pretty() << endl;
    connect(m_sock, SIGNAL(readyRead()), this, SLOT(readData()));
    connect(m_sock, SIGNAL(closed(int)), this, SLOT(socketClosed(int)));
    if (!(m_sock->setBufferSize(4096))) {
	kdDebug() << "Failed to set buffer size." << endl;
	deleteLater();
	return;
    }
    m_sock->enableRead(true);
}

GenericHTTPSession::~GenericHTTPSession()
{
    delete m_sock;
}

void GenericHTTPSession::socketClosed(int state)
{
    kdDebug() << "Connection " << m_sock->peerAddress()->pretty() << " was terminated by the other end: " << state << endl;
    deleteLater();
}

void GenericHTTPSession::readData()
{
    char buf[1024];
    kdDebug() << m_sock->bytesAvailable() << " bytes ready for reading." << endl;
    while (m_sock->bytesAvailable()) {
	int r = m_sock->readBlock((char*)buf, 1023);
	if (r < 0) {
	    kdDebug() << "Read error on connection " << m_sock->peerAddress()->pretty() << endl;
	    m_sock->closeNow();
	    deleteLater();
	}
	if (r > 0) {
	    uint pos = m_inbuf.size();
	    m_inbuf.resize(pos + r, QGArray::SpeedOptim);
	    char* inbuf = m_inbuf.data();
	    memcpy(inbuf + pos, buf, r);
	}
    }
    if (m_inbuf.size()) {
	//kdDebug() << "Connection " << m_sock->peerAddress()->pretty() << QString(" received data, inbuf is:\n") + hexify(m_inbuf) << endl;
	processBuffer();
    }
}

void GenericHTTPSession::discardBuffer()
{
    m_inbuf.resize(0, QGArray::SpeedOptim);
}

void GenericHTTPSession::discardBuffer(uint len)
{
    if (len == m_inbuf.size())
	discardBuffer();
    else {
	int newSize = m_inbuf.size() - len;
	memmove(m_inbuf.data(), m_inbuf.data() + len, newSize);
	m_inbuf.resize(newSize);
    }
}

void GenericHTTPSession::processBuffer()
{
	// Require a reasonable minimum size before processing anything.
	if (m_inbuf.size() < 5)
	    return;

	// Anything but the start of an HTTP request is not wanted.
	if (memcmp((void*)m_inbuf.data(), "POST ", 5)
	    && memcmp((void*)m_inbuf.data(), "GET ", 4)
	    && memcmp((void*)m_inbuf.data(), "HEAD ", 5)
	) {
	    kdDebug() << "Buffer didn't start with a supported HTTP request. Discarding." << endl;
	    discardBuffer();
	    httpError(400);
	    return;
	}

	// See if there's a complete HTTP header in the buffer.
	char* p = (char*)my_memmem((void*)m_inbuf.data(), m_inbuf.size(), (void*)"\r\n\r\n", 4);
	if (!p) {
	    if (m_inbuf.size() > 16384) {
		kdDebug() << "Header is getting ridiculously long. Discarding." << endl;
		discardBuffer();
		httpError(400);
	    }
	    return;
	}
	p += 4;
	uint headerLen = (uint)(p - m_inbuf.data());
	QHttpRequestHeader header(QString::fromAscii(m_inbuf.data(), (int)headerLen));

	// If the header is invalid, discard it from the buffer and send an error to the peer
	if (!header.isValid()) {
	    kdDebug() << "Invalid HTTP request header." << endl;
	    discardBuffer(headerLen);
	    httpError(400);
	    return;
	}

	kdDebug() << "HTTP request " << header.method() << " " << header.path() << " HTTP/" << header.majorVersion() << "." << header.minorVersion() << endl;
	kdDebug() << header.toString() << endl;

	kdDebug() << "Content length: " << header.contentLength() << endl;

	// See if all the document data has been received
	if (m_inbuf.size() < headerLen + header.contentLength())
	    return;

	m_isHEAD = (header.method() == "HEAD");

	QByteArray payload;
	payload.duplicate(m_inbuf.data() + headerLen, header.contentLength());
	discardBuffer(headerLen + header.contentLength());
	kdDebug() << "Payload received." << endl;
	
	if (!this->processRequest(header, payload))
	    httpError(404);
}

void GenericHTTPSession::httpError(int err, const QString& emsg)
{
    QString msg(emsg);
    if (emsg.isNull()) {
	switch (err) {
	case 400:
	    msg = i18n("Bad Request");
	    break;
	case 404:
	    msg = i18n("Resource Not Found");
	    break;
	default:
	    msg = i18n("Unknown Error");
	    break;
	}
    }
    kdDebug() << "HTTP Error " << err << " " << msg << endl;
    QString out;
    out = QString("HTTP/1.1 %1 %2\r\n").arg(err).arg(msg);
    out += QString("Server: KMLDonkey/%1\r\n").arg(KMLDONKEY_VERSION);
    out += "Connection: close\r\nContent-Type: text/html; charset=utf-8\r\n\r\n";
    out += "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n";
    out += QString("<html><head><title>%1 %2</title></head>\r\n").arg(err).arg(msg);
    out += QString("<body><h1>%1 %2</h1></body></html>\r\n").arg(err).arg(msg);
    QCString data = out.utf8();
    m_sock->writeBlock((const char*)data, data.length());
    m_sock->flush();
    deleteLater();
}

void GenericHTTPSession::sendResponseHeader(const QString& contentType, Q_ULLONG contentLength)
{
    QString h = QString("HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: %1\r\n").arg(contentType);
    h += QString("Content-Length: %1\r\n").arg(contentLength);
    h += QString("Server: KMLDonkey/%1\r\n\r\n").arg(KMLDONKEY_VERSION);
    QCString header = h.utf8();
    m_sock->writeBlock(header.data(), header.length());
    if (m_isHEAD)
	endRequest();
}

void GenericHTTPSession::sendData(const QByteArray& data)
{
    if (!m_isHEAD)
	m_sock->writeBlock(data.data(), data.size());
}

void GenericHTTPSession::sendData(const QString& s)
{
    if (!m_isHEAD) {
	QCString data = s.utf8();
	m_sock->writeBlock(data.data(), data.length());
    }
}

void GenericHTTPSession::endRequest()
{
    m_sock->flush();
    deleteLater();
}

void GenericHTTPSession::sendResponse(const QString& contentType, const QByteArray& resp)
{
    sendResponseHeader(contentType, resp.size());
    sendData(resp);
    endRequest();
}

void GenericHTTPSession::sendResponse(const QString& contentType, const QString& resp)
{
    QCString data = resp.utf8();
    sendResponseHeader(contentType, data.length());
    m_sock->writeBlock(data.data(), data.length());
    endRequest();
}

bool GenericHTTPSession::processRequest(const QHttpRequestHeader&, const QByteArray&)
{
    return false;
}





#include "generichttpserver.moc"
