File indexing completed on 2024-05-12 05:46:51

0001 /****************************************************************************
0002 ** Copyright (C) 2010-2016 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com.
0003 ** Author: David Faure <david.faure@kdab.com>
0004 ** All rights reserved.
0005 **
0006 ** This file initially comes from the KD Soap library.
0007 **
0008 ** This file may be distributed and/or modified under the terms of the
0009 ** GNU Lesser General Public License version 2.1 and version 3 as published by the
0010 ** Free Software Foundation and appearing in the file COPYING.LIB included.
0011 **
0012 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
0013 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
0014 **
0015 ** Contact info@kdab.com if any conditions of this licensing are not
0016 ** clear to you.
0017 **
0018 **********************************************************************/
0019 
0020 #include "httpserver_p.h"
0021 #include <KIO/Job>
0022 #include <QBuffer>
0023 #include <QSslSocket>
0024 
0025 static bool splitHeadersAndData(const QByteArray &request, QByteArray &header, QByteArray &data)
0026 {
0027     const int sep = request.indexOf("\r\n\r\n");
0028     if (sep <= 0) {
0029         return false;
0030     }
0031     header = request.left(sep);
0032     data = request.mid(sep + 4);
0033     return true;
0034 }
0035 
0036 typedef QMap<QByteArray, QByteArray> HeadersMap;
0037 static HeadersMap parseHeaders(const QByteArray &headerData)
0038 {
0039     HeadersMap headersMap;
0040     QBuffer sourceBuffer;
0041     sourceBuffer.setData(headerData);
0042     sourceBuffer.open(QIODevice::ReadOnly);
0043     // The first line is special, it's the GET or POST line
0044     const QList<QByteArray> firstLine = sourceBuffer.readLine().split(' ');
0045     if (firstLine.count() < 3) {
0046         qDebug() << "Malformed HTTP request:" << firstLine;
0047         return headersMap;
0048     }
0049     const QByteArray request = firstLine[0];
0050     const QByteArray path = firstLine[1];
0051     const QByteArray httpVersion = firstLine[2];
0052     if (request != "GET" && request != "POST") {
0053         qDebug() << "Unknown HTTP request:" << firstLine;
0054         return headersMap;
0055     }
0056     headersMap.insert("_path", path);
0057     headersMap.insert("_httpVersion", httpVersion);
0058 
0059     while (!sourceBuffer.atEnd()) {
0060         const QByteArray line = sourceBuffer.readLine();
0061         const int pos = line.indexOf(':');
0062         if (pos == -1) {
0063             qDebug() << "Malformed HTTP header:" << line;
0064         }
0065         const QByteArray header = line.left(pos);
0066         const QByteArray value = line.mid(pos + 1).trimmed(); // remove space before and \r\n after
0067         //qDebug() << "HEADER" << header << "VALUE" << value;
0068         headersMap.insert(header, value);
0069     }
0070     return headersMap;
0071 }
0072 
0073 enum Method { None, Basic, Plain, Login, Ntlm, CramMd5, DigestMd5 };
0074 
0075 static void parseAuthLine(const QString &str, Method *method, QString *headerVal)
0076 {
0077     *method = None;
0078     // The code below (from QAuthenticatorPrivate::parseHttpResponse)
0079     // is supposed to be run in a loop, apparently
0080     // (multiple WWW-Authenticate lines? multiple values in the line?)
0081 
0082     //qDebug() << "parseAuthLine() " << str;
0083     if (*method < Basic && str.startsWith(QLatin1String("Basic"), Qt::CaseInsensitive)) {
0084         *method = Basic;
0085         *headerVal = str.mid(6);
0086     } else if (*method < Ntlm && str.startsWith(QLatin1String("NTLM"), Qt::CaseInsensitive)) {
0087         *method = Ntlm;
0088         *headerVal = str.mid(5);
0089     } else if (*method < DigestMd5 && str.startsWith(QLatin1String("Digest"), Qt::CaseInsensitive)) {
0090         *method = DigestMd5;
0091         *headerVal = str.mid(7);
0092     }
0093 }
0094 
0095 QByteArray HttpServerThread::makeHttpResponse(const QByteArray &responseData) const
0096 {
0097     QByteArray httpResponse;
0098     if (m_features & Error404) {
0099         httpResponse += "HTTP/1.1 404 Not Found\r\n";
0100     } else {
0101         httpResponse += "HTTP/1.1 200 OK\r\n";
0102     }
0103     if (!m_contentType.isEmpty()) {
0104         httpResponse += "Content-Type: " + m_contentType + "\r\n";
0105     }
0106     httpResponse += "Mozilla/5.0 (X11; Linux x86_64) KHTML/5.20.0 (like Gecko) Konqueror/5.20\r\n";
0107     httpResponse += "Content-Length: ";
0108     httpResponse += QByteArray::number(responseData.size());
0109     httpResponse += "\r\n";
0110 
0111     // We don't support multiple connections so let's ask the client
0112     // to close the connection every time.
0113     httpResponse += "Connection: close\r\n";
0114     httpResponse += "\r\n";
0115     httpResponse += responseData;
0116     return httpResponse;
0117 }
0118 
0119 void HttpServerThread::disableSsl()
0120 {
0121     m_server->disableSsl();
0122 }
0123 
0124 void HttpServerThread::finish()
0125 {
0126     KIO::Job *job = KIO::get(QUrl(endPoint() + QLatin1String("/terminateThread")));
0127     job->exec();
0128 }
0129 
0130 void HttpServerThread::run()
0131 {
0132     m_server = new BlockingHttpServer(m_features & Ssl);
0133     m_server->listen();
0134     QMutexLocker lock(&m_mutex);
0135     m_port = m_server->serverPort();
0136     lock.unlock();
0137     m_ready.release();
0138 
0139     const bool doDebug = qEnvironmentVariableIsSet("HTTP_TEST_DEBUG");
0140 
0141     if (doDebug) {
0142         qDebug() << "HttpServerThread listening on port" << m_port;
0143     }
0144 
0145     // Wait for first connection (we'll wait for further ones inside the loop)
0146     QTcpSocket *clientSocket = m_server->waitForNextConnectionSocket();
0147     Q_ASSERT(clientSocket);
0148 
0149     Q_FOREVER {
0150         // get the "request" packet
0151         if (doDebug) {
0152             qDebug() << "HttpServerThread: waiting for read";
0153         }
0154         if (clientSocket->state() == QAbstractSocket::UnconnectedState ||
0155                 !clientSocket->waitForReadyRead(2000)) {
0156             if (clientSocket->state() == QAbstractSocket::UnconnectedState) {
0157                 delete clientSocket;
0158                 if (doDebug) {
0159                     qDebug() << "Waiting for next connection...";
0160                 }
0161                 clientSocket = m_server->waitForNextConnectionSocket();
0162                 Q_ASSERT(clientSocket);
0163                 continue; // go to "waitForReadyRead"
0164             } else {
0165                 qDebug() << "HttpServerThread:" << clientSocket->error() << "waiting for \"request\" packet";
0166                 break;
0167             }
0168         }
0169         const QByteArray request = m_partialRequest + clientSocket->readAll();
0170         if (doDebug) {
0171             qDebug() << "HttpServerThread: request:" << request;
0172         }
0173 
0174         // Split headers and request xml
0175         lock.relock();
0176         const bool splitOK = splitHeadersAndData(request, m_receivedHeaders, m_receivedData);
0177         if (!splitOK) {
0178             //if (doDebug)
0179             //    qDebug() << "Storing partial request" << request;
0180             m_partialRequest = request;
0181             continue;
0182         }
0183 
0184         m_headers = parseHeaders(m_receivedHeaders);
0185 
0186         if (m_headers.value("Content-Length").toInt() > m_receivedData.size()) {
0187             //if (doDebug)
0188             //    qDebug() << "Storing partial request" << request;
0189             m_partialRequest = request;
0190             continue;
0191         }
0192 
0193         m_partialRequest.clear();
0194 
0195         if (m_headers.value("_path").endsWith("terminateThread")) { // we're asked to exit
0196             break;    // normal exit
0197         }
0198 
0199         lock.unlock();
0200 
0201         //qDebug() << "headers received:" << m_receivedHeaders;
0202         //qDebug() << headers;
0203         //qDebug() << "data received:" << m_receivedData;
0204 
0205         if (m_features & BasicAuth) {
0206             QByteArray authValue = m_headers.value("Authorization");
0207             if (authValue.isEmpty()) {
0208                 authValue = m_headers.value("authorization");    // as sent by Qt-4.5
0209             }
0210             bool authOk = false;
0211             if (!authValue.isEmpty()) {
0212                 //qDebug() << "got authValue=" << authValue; // looks like "Basic <base64 of user:pass>"
0213                 Method method;
0214                 QString headerVal;
0215                 parseAuthLine(QString::fromLatin1(authValue.data(), authValue.size()), &method, &headerVal);
0216                 //qDebug() << "method=" << method << "headerVal=" << headerVal;
0217                 switch (method) {
0218                 case None: // we want auth, so reject "None"
0219                     break;
0220                 case Basic: {
0221                     const QByteArray userPass = QByteArray::fromBase64(headerVal.toLatin1());
0222                     //qDebug() << userPass;
0223                     // TODO if (validateAuth(userPass)) {
0224                     if (userPass == ("kdab:testpass")) {
0225                         authOk = true;
0226                     }
0227                     break;
0228                 }
0229                 default:
0230                     qWarning("Unsupported authentication mechanism %s", authValue.constData());
0231                 }
0232             }
0233 
0234             if (!authOk) {
0235                 // send auth request (Qt supports basic, ntlm and digest)
0236                 const QByteArray unauthorized = "HTTP/1.1 401 Authorization Required\r\nWWW-Authenticate: Basic realm=\"example\"\r\nContent-Length: 0\r\n\r\n";
0237                 clientSocket->write(unauthorized);
0238                 if (!clientSocket->waitForBytesWritten(2000)) {
0239                     qDebug() << "HttpServerThread:" << clientSocket->error() << "writing auth request";
0240                     break;
0241                 }
0242                 continue;
0243             }
0244         }
0245 
0246         // send response
0247         const QByteArray response = makeHttpResponse(m_dataToSend);
0248         if (doDebug) {
0249             qDebug() << "HttpServerThread: writing" << response;
0250         }
0251         clientSocket->write(response);
0252 
0253         clientSocket->flush();
0254     }
0255     // all done...
0256     delete clientSocket;
0257     delete m_server;
0258     if (doDebug) {
0259         qDebug() << "HttpServerThread terminated";
0260     }
0261 }
0262 
0263 
0264 void BlockingHttpServer::incomingConnection(qintptr socketDescriptor)
0265 {
0266     if (doSsl) {
0267         QSslSocket *serverSocket = new QSslSocket;
0268         serverSocket->setParent(this);
0269         serverSocket->setSocketDescriptor(socketDescriptor);
0270         connect(serverSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(slotSslErrors(QList<QSslError>)));
0271         // TODO setupSslServer(serverSocket);
0272         //qDebug() << "Created QSslSocket, starting server encryption";
0273         serverSocket->startServerEncryption();
0274         sslSocket = serverSocket;
0275         // If startServerEncryption fails internally [and waitForEncrypted hangs],
0276         // then this is how to debug it.
0277         // A way to catch such errors is really missing in Qt..
0278         //qDebug() << "startServerEncryption said:" << sslSocket->errorString();
0279         bool ok = serverSocket->waitForEncrypted();
0280         Q_ASSERT(ok);
0281         Q_UNUSED(ok);
0282     } else {
0283         QTcpServer::incomingConnection(socketDescriptor);
0284     }
0285 }