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 }