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