Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qhttpprotocolhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2014 BlackBerry Limited. All rights reserved.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:critical reason:network-protocol
5
6#include <private/qhttpprotocolhandler_p.h>
7#include <private/qnoncontiguousbytedevice_p.h>
8#include <private/qhttpnetworkconnectionchannel_p.h>
9#include <private/qsocketabstraction_p.h>
10
11QT_BEGIN_NAMESPACE
12
13using namespace Qt::StringLiterals;
14
15QHttpProtocolHandler::QHttpProtocolHandler(QHttpNetworkConnectionChannel *channel)
16 : QAbstractProtocolHandler(channel)
17{
18}
19
20void QHttpProtocolHandler::_q_receiveReply()
21{
22 Q_ASSERT(m_socket);
23
24 if (!m_reply) {
25 if (m_socket->bytesAvailable() > 0)
26 qWarning() << "QAbstractProtocolHandler::_q_receiveReply() called without QHttpNetworkReply,"
27 << m_socket->bytesAvailable() << "bytes on socket.";
28 m_channel->close();
29 return;
30 }
31
32 // only run when the QHttpNetworkConnection is not currently being destructed, e.g.
33 // this function is called from _q_disconnected which is called because
34 // of ~QHttpNetworkConnectionPrivate
35 if (!qobject_cast<QHttpNetworkConnection*>(m_connection)) {
36 return;
37 }
38
39 // connection might be closed to signal the end of data
40 if (QSocketAbstraction::socketState(m_socket) == QAbstractSocket::UnconnectedState) {
41 if (m_socket->bytesAvailable() <= 0) {
42 if (m_reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) {
43 // finish this reply. this case happens when the server did not send a content length
44 m_reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
45 m_channel->allDone();
46 return;
47 } else {
48 m_channel->handleUnexpectedEOF();
49 return;
50 }
51 } else {
52 // socket not connected but still bytes for reading.. just continue in this function
53 }
54 }
55
56 // read loop for the response
57 qint64 bytes = 0;
58 qint64 lastBytes = bytes;
59 do {
60 lastBytes = bytes;
61
62 QHttpNetworkReplyPrivate::ReplyState state = m_reply->d_func()->state;
63 switch (state) {
64 case QHttpNetworkReplyPrivate::NothingDoneState: {
65 m_reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState;
66 Q_FALLTHROUGH();
67 }
68 case QHttpNetworkReplyPrivate::ReadingStatusState: {
69 qint64 statusBytes = m_reply->d_func()->readStatus(m_socket);
70 if (statusBytes == -1) {
71 // connection broke while reading status. also handled if later _q_disconnected is called
72 m_channel->handleUnexpectedEOF();
73 return;
74 }
75 bytes += statusBytes;
76 m_channel->lastStatus = m_reply->statusCode();
77 break;
78 }
79 case QHttpNetworkReplyPrivate::ReadingHeaderState: {
80 QHttpNetworkReplyPrivate *replyPrivate = m_reply->d_func();
81 qint64 headerBytes = replyPrivate->readHeader(m_socket);
82 if (headerBytes == -1) {
83 // connection broke while reading headers. also handled if later _q_disconnected is called
84 m_channel->handleUnexpectedEOF();
85 return;
86 }
87 bytes += headerBytes;
88 // If headers were parsed successfully now it is the ReadingDataState
89 if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) {
90 if (replyPrivate->isCompressed() && replyPrivate->autoDecompress) {
91 // remove the Content-Length from header
92 replyPrivate->removeAutoDecompressHeader();
93 } else {
94 replyPrivate->autoDecompress = false;
95 }
96 const int statusCode = m_reply->statusCode();
97 if (statusCode == 100 || (102 <= statusCode && statusCode <= 199)) {
98 replyPrivate->clearHttpLayerInformation();
99 replyPrivate->state = QHttpNetworkReplyPrivate::ReadingStatusState;
100 break; // ignore
101 }
102 if (replyPrivate->shouldEmitSignals())
103 emit m_reply->headerChanged();
104 // After headerChanged had been emitted
105 // we can suddenly have a replyPrivate->userProvidedDownloadBuffer
106 // this is handled in the ReadingDataState however
107
108 if (!replyPrivate->expectContent()) {
109 replyPrivate->state = QHttpNetworkReplyPrivate::AllDoneState;
110 m_channel->allDone();
111 break;
112 }
113 }
114 break;
115 }
116 case QHttpNetworkReplyPrivate::ReadingDataState: {
117 QHttpNetworkReplyPrivate *replyPrivate = m_reply->d_func();
118 if (QSocketAbstraction::socketState(m_socket) == QAbstractSocket::ConnectedState &&
119 replyPrivate->downstreamLimited && !replyPrivate->responseData.isEmpty() && replyPrivate->shouldEmitSignals()) {
120 // (only do the following when still connected, not when we have already been disconnected and there is still data)
121 // We already have some HTTP body data. We don't read more from the socket until
122 // this is fetched by QHttpNetworkAccessHttpBackend. If we would read more,
123 // we could not limit our read buffer usage.
124 // We only do this when shouldEmitSignals==true because our HTTP parsing
125 // always needs to parse the 401/407 replies. Therefore they don't really obey
126 // to the read buffer maximum size, but we don't care since they should be small.
127 return;
128 }
129
130 if (replyPrivate->userProvidedDownloadBuffer) {
131 // the user provided a direct buffer where we should put all our data in.
132 // this only works when we can tell the user the content length and he/she can allocate
133 // the buffer in that size.
134 // note that this call will read only from the still buffered data
135 qint64 haveRead = replyPrivate->readBodyVeryFast(m_socket, replyPrivate->userProvidedDownloadBuffer + replyPrivate->totalProgress);
136 if (haveRead > 0) {
137 bytes += haveRead;
138 replyPrivate->totalProgress += haveRead;
139 // the user will get notified of it via progress signal
140 emit m_reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
141 } else if (haveRead == 0) {
142 // Happens since this called in a loop. Currently no bytes available.
143 } else if (haveRead < 0) {
144 m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::RemoteHostClosedError);
145 break;
146 }
147 } else if (!replyPrivate->isChunked() && replyPrivate->bodyLength > 0) {
148 // bulk files like images should fulfill these properties and
149 // we can therefore save on memory copying
150 qint64 haveRead = replyPrivate->readBodyFast(m_socket, &replyPrivate->responseData);
151 bytes += haveRead;
152 replyPrivate->totalProgress += haveRead;
153 if (replyPrivate->shouldEmitSignals()) {
154 emit m_reply->readyRead();
155 emit m_reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
156 }
157 }
158 else
159 {
160 // use the traditional slower reading (for compressed encoding, chunked encoding,
161 // no content-length etc)
162 qint64 haveRead = replyPrivate->readBody(m_socket, &replyPrivate->responseData);
163 if (haveRead > 0) {
164 bytes += haveRead;
165 replyPrivate->totalProgress += haveRead;
166 if (replyPrivate->shouldEmitSignals()) {
167 emit m_reply->readyRead();
168 emit m_reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
169 }
170 } else if (haveRead == -1) {
171 // Some error occurred
172 m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::ProtocolFailure);
173 break;
174 }
175 }
176 // still in ReadingDataState? This function will be called again by the socket's readyRead
177 if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState)
178 break;
179
180 // everything done
181 Q_FALLTHROUGH();
182 }
183 case QHttpNetworkReplyPrivate::AllDoneState:
184 m_channel->allDone();
185 if (state == QHttpNetworkReplyPrivate::AllDoneState)
186 lastBytes = bytes; // No need to loop more just to call m_channel->allDone again.
187 break;
188 default:
189 break;
190 }
191 } while (bytes != lastBytes && m_reply);
192}
193
194void QHttpProtocolHandler::_q_readyRead()
195{
196 if (QSocketAbstraction::socketState(m_socket) == QAbstractSocket::ConnectedState
197 && m_socket->bytesAvailable() == 0) {
198 // We got a readyRead but no bytes are available..
199 // This happens for the Unbuffered QTcpSocket
200 // Also check if socket is in ConnectedState since
201 // this function may also be invoked via the event loop.
202 char c;
203 qint64 ret = m_socket->peek(&c, 1);
204 if (ret < 0) {
205 m_channel->_q_error(QSocketAbstraction::socketError(m_socket));
206 // We still need to handle the reply so it emits its signals etc.
207 if (m_reply)
208 _q_receiveReply();
209 return;
210 }
211 }
212
213 if (m_channel->isSocketWaiting() || m_channel->isSocketReading()) {
214 if (m_socket->bytesAvailable()) {
215 // We might get a spurious call from readMoreLater()
216 // call of the QHttpNetworkConnection even while the socket is disconnecting.
217 // Therefore check if there is actually bytes available before changing the channel state.
218 m_channel->state = QHttpNetworkConnectionChannel::ReadingState;
219 }
220 if (m_reply)
221 _q_receiveReply();
222 }
223}
224
225bool QHttpProtocolHandler::sendRequest()
226{
227 m_reply = m_channel->reply;
228
229 if (!m_reply) {
230 // heh, how should that happen!
231 qWarning("QAbstractProtocolHandler::sendRequest() called without QHttpNetworkReply");
232 return false;
233 }
234
235 switch (m_channel->state) {
236 case QHttpNetworkConnectionChannel::IdleState: { // write the header
237 if (!m_channel->ensureConnection()) {
238 // wait for the connection (and encryption) to be done
239 // sendRequest will be called again from either
240 // _q_connected or _q_encrypted
241 return false;
242 }
243 if (m_channel->request.isPreConnect()) {
244 m_channel->state = QHttpNetworkConnectionChannel::IdleState;
245 m_reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
246 m_channel->allDone();
247 m_connection->preConnectFinished(); // will only decrease the counter
248 m_reply = nullptr; // so we can reuse this channel
249 return true; // we have a working connection and are done
250 }
251
252 m_channel->written = 0; // excluding the header
253 m_channel->bytesTotal = 0;
254
255 QHttpNetworkReplyPrivate *replyPrivate = m_reply->d_func();
256 replyPrivate->clear();
257 replyPrivate->connection = m_connection;
258 replyPrivate->connectionChannel = m_channel;
259 replyPrivate->autoDecompress = m_channel->request.d->autoDecompress;
260 replyPrivate->pipeliningUsed = false;
261
262 // if the url contains authentication parameters, use the new ones
263 // both channels will use the new authentication parameters
264 if (!m_channel->request.url().userInfo().isEmpty() && m_channel->request.withCredentials()) {
265 QUrl url = m_channel->request.url();
266 QAuthenticator &auth = m_channel->authenticator;
267 if (url.userName() != auth.user()
268 || (!url.password().isEmpty() && url.password() != auth.password())) {
269 auth.setUser(url.userName());
270 auth.setPassword(url.password());
271 m_connection->d_func()->copyCredentials(m_connection->d_func()->indexOf(m_socket), &auth, false);
272 }
273 // clear the userinfo, since we use the same request for resending
274 // userinfo in url can conflict with the one in the authenticator
275 url.setUserInfo(QString());
276 m_channel->request.setUrl(url);
277 }
278 // Will only be false if Qt WebKit is performing a cross-origin XMLHttpRequest
279 // and withCredentials has not been set to true.
280 if (m_channel->request.withCredentials())
281 m_connection->d_func()->createAuthorization(m_socket, m_channel->request);
282#ifndef QT_NO_NETWORKPROXY
283 m_header = QHttpNetworkRequestPrivate::header(m_channel->request,
284 (m_connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy));
285#else
286 m_header = QHttpNetworkRequestPrivate::header(m_channel->request, false);
287#endif
288
289 // flushing is dangerous (QSslSocket calls transmit which might read or error)
290// m_socket->flush();
291 QNonContiguousByteDevice* uploadByteDevice = m_channel->request.uploadByteDevice();
292 if (uploadByteDevice) {
293 // connect the signals so this function gets called again
294 QObject::connect(uploadByteDevice, SIGNAL(readyRead()), m_channel, SLOT(_q_uploadDataReadyRead()));
295
296 m_channel->bytesTotal = m_channel->request.contentLength();
297
298 m_channel->state = QHttpNetworkConnectionChannel::WritingState; // start writing data
299 sendRequest(); //recurse
300 } else {
301 // no data to send: just send the HTTP headers
302 m_socket->write(std::exchange(m_header, {}));
303 QMetaObject::invokeMethod(m_reply, "requestSent", Qt::QueuedConnection);
304 m_channel->state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
305 sendRequest(); //recurse
306 }
307
308 break;
309 }
310 case QHttpNetworkConnectionChannel::WritingState:
311 {
312 // write the data
313 QNonContiguousByteDevice* uploadByteDevice = m_channel->request.uploadByteDevice();
314 if (!uploadByteDevice || m_channel->bytesTotal == m_channel->written) {
315 // the upload device might have no data to send, but we still have to send the headers,
316 // do it now.
317 if (!m_header.isEmpty())
318 m_socket->write(std::exchange(m_header, {}));
319 if (uploadByteDevice)
320 emit m_reply->dataSendProgress(m_channel->written, m_channel->bytesTotal);
321 m_channel->state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
322 sendRequest(); // recurse
323 break;
324 }
325
326 // only feed the QTcpSocket buffer when there is less than 32 kB in it;
327 // note that the headers do not count towards these limits.
328 const qint64 socketBufferFill = 32*1024;
329 const qint64 socketWriteMaxSize = 16*1024;
330
331 // if it is really an ssl socket, check more than just bytesToWrite()
332#ifndef QT_NO_SSL
333 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(m_socket);
334 const auto encryptedBytesToWrite = [sslSocket]() -> qint64
335 {
336 return sslSocket ? sslSocket->encryptedBytesToWrite() : 0;
337 };
338#else
339 const auto encryptedBytesToWrite = [](){ return qint64(0); };
340#endif
341
342 // throughout this loop, we want to send the data coming from uploadByteDevice.
343 // we also need to send the headers, as we try to coalesce their write with the data.
344 // we won't send more than the limits above, excluding the headers
345 while ((m_socket->bytesToWrite() + encryptedBytesToWrite()) <= socketBufferFill
346 && m_channel->bytesTotal != m_channel->written)
347 {
348 // get pointer to upload data
349 qint64 currentReadSize = 0;
350 const qint64 desiredReadSize = qMin(socketWriteMaxSize, m_channel->bytesTotal - m_channel->written);
351 const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize);
352
353 if (currentReadSize == -1) {
354 // premature eof happened
355 m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::UnknownNetworkError);
356 return false;
357 } else if (readPointer == nullptr || currentReadSize == 0) {
358 // nothing to read currently, break the loop
359 break;
360 } else {
361 if (m_channel->written != uploadByteDevice->pos()) {
362 // Sanity check. This was useful in tracking down an upload corruption.
363 qWarning() << "QHttpProtocolHandler: Internal error in sendRequest. Expected to write at position" << m_channel->written << "but read device is at" << uploadByteDevice->pos();
364 Q_ASSERT(m_channel->written == uploadByteDevice->pos());
365 m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::ProtocolFailure);
366 return false;
367 }
368 qint64 currentWriteSize;
369 if (m_header.isEmpty()) {
370 currentWriteSize = m_socket->write(readPointer, currentReadSize);
371 } else {
372 // assemble header and data and send them together
373 const qint64 headerSize = m_header.size();
374 m_header.append(readPointer, currentReadSize);
375 currentWriteSize = m_socket->write(std::exchange(m_header, {}));
376 if (currentWriteSize != -1)
377 currentWriteSize -= headerSize;
378 QMetaObject::invokeMethod(m_reply, "requestSent", Qt::QueuedConnection);
379 }
380 if (currentWriteSize == -1 || currentWriteSize != currentReadSize) {
381 // socket broke down
382 m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::UnknownNetworkError);
383 return false;
384 } else {
385 m_channel->written += currentWriteSize;
386 uploadByteDevice->advanceReadPointer(currentWriteSize);
387
388 emit m_reply->dataSendProgress(m_channel->written, m_channel->bytesTotal);
389
390 if (m_channel->written == m_channel->bytesTotal) {
391 // make sure this function is called once again
392 m_channel->state = QHttpNetworkConnectionChannel::WaitingState;
393 sendRequest();
394 break;
395 }
396 }
397 }
398 }
399 break;
400 }
401
402 case QHttpNetworkConnectionChannel::WaitingState:
403 {
404 QNonContiguousByteDevice* uploadByteDevice = m_channel->request.uploadByteDevice();
405 if (uploadByteDevice) {
406 QObject::disconnect(uploadByteDevice, SIGNAL(readyRead()), m_channel, SLOT(_q_uploadDataReadyRead()));
407 }
408
409 // HTTP pipelining
410 //m_connection->d_func()->fillPipeline(m_socket);
411 //m_socket->flush();
412
413 // ensure we try to receive a reply in all cases, even if _q_readyRead_ hat not been called
414 // this is needed if the sends an reply before we have finished sending the request. In that
415 // case receiveReply had been called before but ignored the server reply
416 if (m_socket->bytesAvailable())
417 QMetaObject::invokeMethod(m_channel, "_q_receiveReply", Qt::QueuedConnection);
418 break;
419 }
420 case QHttpNetworkConnectionChannel::ReadingState:
421 // ignore _q_bytesWritten in these states
422 Q_FALLTHROUGH();
423 default:
424 break;
425 }
426 return true;
427}
428
429QT_END_NAMESPACE