/* -*- c++ -*- * * generichttpserver.cpp * * Copyright (C) 2004 Petter E. Stokke * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include "generichttpserver.h" #include "version.h" #include "compat.h" #include #include 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 += "\r\n"; out += QString("%1 %2\r\n").arg(err).arg(msg); out += QString("

%1 %2

\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"