File indexing completed on 2024-04-14 14:23:55

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