/* -*- c++ -*-
 *
 * mmconnection.cpp
 *
 * Copyright (C) 2003 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 "compat.h"
#include "mmconnection.h"
#include "mmserver.h"

#include <kdebug.h>
#include <kextsock.h>
#include <ksockaddr.h>

#include "version.h"




MMConnection::MMConnection(KExtendedSocket* sock, MMServer* parent)
    : QObject(parent)
    , m_server(parent)
    , m_sock(sock)
{
    kdDebug() << "MMConnection::MMConnection( " << 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);
}

MMConnection::~MMConnection()
{
    kdDebug() << "MMConnection::~MMConnection( " << m_sock->peerAddress()->pretty() << " );" << endl;
    delete m_sock;
}

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

QString hexify(const QByteArray& buf)
{
    QString out(""), hex(""), asc(""), tmp;
    int i;
    for (i=0; i<(int)buf.size(); i++) {
	if (buf.at(i) >= 32 && buf.at(i) <= 127)
	    asc += QChar(buf.at(i));
	else
	    asc += ".";
	tmp.sprintf("%02x", buf.at(i));
	hex += tmp + " ";
	if (i % 16 == 15) {
	    tmp.sprintf("%8d: ", i - 15);
	    out += tmp + hex + "  " + asc + "\n";
	    hex = "";
	    asc = "";
	}
    }
    tmp.sprintf("%8d: ", i - (i % 16));
    for (i %= 16; i < 16; i++)
	hex += "   ";
    out += tmp + hex + "  " + asc + "\n";
    return out;
}

void MMConnection::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 MMConnection::discardBuffer()
{
    m_inbuf.resize(0, QGArray::SpeedOptim);
}

void MMConnection::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 MMConnection::processBuffer()
{
    while (1) {

	// Require a reasonable minimum size before processing anything.
	if (m_inbuf.size() < 4)
	    return;

	// Anything but the start of an HTTP POST request is not wanted.
	if (memcmp((void*)m_inbuf.data(), "POST", 4)) {
	    kdDebug() << "Buffer didn't start with POST. Discarding." << endl;
	    discardBuffer();
	    httpError(400, "Bad Request");
	    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, "Bad Request");
	    }
	    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, "Bad Request");
	    return;
	}

	kdDebug() << "HTTP request " << header.method() << " " << header.path() << " HTTP/" << header.majorVersion() << "." << header.minorVersion() << endl;
	kdDebug() << header.toString() << endl;
	if (!header.hasContentLength() || header.method() != "POST") {
	    kdDebug() << "No content length or not POST request. Don't know how to handle that; discarding buffer." << endl;
	    discardBuffer();
	    httpError(400, "Bad Request");
	    return;
	}

	kdDebug() << "Content length: " << header.contentLength() << endl;
	if (header.contentLength() < 3) {
	    kdDebug() << "Content length is too short. Sending error packet." << endl;
	    discardBuffer(headerLen + header.contentLength());
	    sendPacket(MMPacket(MMP_GENERALERROR));
	    return;
	}

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

	MMPacket content(m_inbuf.data() + headerLen, header.contentLength());
	discardBuffer(headerLen + header.contentLength());
	kdDebug() << "Payload received." << endl;

	emit processMessage(this, &content);
    }
}

void MMConnection::httpError(int err, const QString& msg)
{
    kdDebug() << "HTTP Error " << err << " " << msg << endl;
    QString out;
    out = QString("HTTP/1.1 %1 %2\r\n").arg(err).arg(msg);
    out += QString("Server: KMLDonkeyMobileMule/%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 MMConnection::sendPacket(const MMPacket& p)
{
    QString h = QString("HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: %1\r\n").arg(m_server->getContentType());
    h += QString("Content-Length: %1\r\n\r\n").arg(p.packetSize());
    QCString header = h.utf8();
    QByteArray buf(header.length() + p.packetSize());
    memcpy(buf.data(), (const char*)header, header.length());
    buf[header.length()] = p.opcode();
    memcpy(buf.data() + header.length() + 1, p.data(), p.size());
    m_sock->writeBlock(buf.data(), buf.size());

    buf.duplicate((const char*)p.data(), p.size());
    kdDebug() << "Sent message opcode " << (int)p.opcode() << QString("\n") << hexify(buf) << endl;

    m_sock->flush();
    deleteLater();
}

void MMConnection::sendPacket(MMPacket* p)
{
    sendPacket(*p);
    delete p;
}

#include "mmconnection.moc"
