File indexing completed on 2024-09-01 13:25:52
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"