diff options
author | Frederik Gladhorn <frederik.gladhorn@qt.io> | 2017-09-02 10:27:08 +0200 |
---|---|---|
committer | Frederik Gladhorn <frederik.gladhorn@qt.io> | 2017-09-02 10:27:09 +0200 |
commit | 9831118378c5d489f76089011277a1b7234a8d68 (patch) | |
tree | a67e82fde179fa727172fff9098758d60912189d /src/network | |
parent | b5d471d0c23128528a0aa33ed5172bb1bab05bb1 (diff) | |
parent | 4fa90c1757c15425d5e0df1a4f5dbc7e77c265f8 (diff) |
Merge dev into 5.10
Change-Id: I5fb5e7e6e57bb5db6fcb1f670f7f6cbc8def2d60
Diffstat (limited to 'src/network')
20 files changed, 367 insertions, 78 deletions
diff --git a/src/network/access/http2/http2frames.cpp b/src/network/access/http2/http2frames.cpp index 5a684c2f41..e695b4dd9e 100644 --- a/src/network/access/http2/http2frames.cpp +++ b/src/network/access/http2/http2frames.cpp @@ -361,6 +361,12 @@ FrameWriter::FrameWriter(FrameType type, FrameFlags flags, quint32 streamID) start(type, flags, streamID); } +void FrameWriter::setOutboundFrame(Frame &&newFrame) +{ + frame = std::move(newFrame); + updatePayloadSize(); +} + void FrameWriter::start(FrameType type, FrameFlags flags, quint32 streamID) { auto &buffer = frame.buffer; diff --git a/src/network/access/http2/http2frames_p.h b/src/network/access/http2/http2frames_p.h index e5f6d46c67..4bdc775806 100644 --- a/src/network/access/http2/http2frames_p.h +++ b/src/network/access/http2/http2frames_p.h @@ -129,6 +129,8 @@ public: return frame; } + void setOutboundFrame(Frame &&newFrame); + // Frame 'builders': void start(FrameType type, FrameFlags flags, quint32 streamID); void setPayloadSize(quint32 size); diff --git a/src/network/access/http2/http2protocol.cpp b/src/network/access/http2/http2protocol.cpp index 7f788a6f42..54811aeab0 100644 --- a/src/network/access/http2/http2protocol.cpp +++ b/src/network/access/http2/http2protocol.cpp @@ -37,9 +37,14 @@ ** ****************************************************************************/ -#include <QtCore/qstring.h> - #include "http2protocol_p.h" +#include "http2frames_p.h" + +#include "private/qhttpnetworkrequest_p.h" +#include "private/qhttpnetworkreply_p.h" + +#include <QtCore/qbytearray.h> +#include <QtCore/qstring.h> QT_BEGIN_NAMESPACE @@ -57,6 +62,38 @@ const char Http2clientPreface[clientPrefaceLength] = 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a}; +QByteArray default_SETTINGS_to_Base64() +{ + Frame frame(default_SETTINGS_frame()); + // SETTINGS frame's payload consists of pairs: + // 2-byte-identifier | 4-byte-value == multiple of 6. + Q_ASSERT(frame.payloadSize() && !(frame.payloadSize() % 6)); + const char *src = reinterpret_cast<const char *>(frame.dataBegin()); + const QByteArray wrapper(QByteArray::fromRawData(src, int(frame.dataSize()))); + // 3.2.1 + // The content of the HTTP2-Settings header field is the payload + // of a SETTINGS frame (Section 6.5), encoded as a base64url string + // (that is, the URL- and filename-safe Base64 encoding described in + // Section 5 of [RFC4648], with any trailing '=' characters omitted). + return wrapper.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); +} + +void prepare_for_protocol_upgrade(QHttpNetworkRequest &request) +{ + // RFC 2616, 14.10 + // RFC 7540, 3.2 + QByteArray value(request.headerField("Connection")); + // We _append_ 'Upgrade': + if (value.size()) + value += ", "; + + value += "Upgrade, HTTP2-Settings"; + request.setHeaderField("Connection", value); + // This we just (re)write. + request.setHeaderField("Upgrade", "h2c"); + // This we just (re)write. + request.setHeaderField("HTTP2-Settings", default_SETTINGS_to_Base64()); +} void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error, QString &errorMessage) @@ -151,6 +188,40 @@ QNetworkReply::NetworkError qt_error(quint32 errorCode) return error; } +bool is_PUSH_PROMISE_enabled() +{ + bool ok = false; + const int env = qEnvironmentVariableIntValue("QT_HTTP2_ENABLE_PUSH_PROMISE", &ok); + return ok && env; +} + +bool is_protocol_upgraded(const QHttpNetworkReply &reply) +{ + if (reply.statusCode() == 101) { + // Do some minimal checks here - we expect 'Upgrade: h2c' to be found. + const auto &header = reply.header(); + for (const QPair<QByteArray, QByteArray> &field : header) { + if (field.first.toLower() == "upgrade" && field.second.toLower() == "h2c") + return true; + } + } + + return false; } +Frame default_SETTINGS_frame() +{ + // 6.5 SETTINGS + FrameWriter builder(FrameType::SETTINGS, FrameFlag::EMPTY, connectionStreamID); + // MAX frame size (16 kb), disable/enable PUSH_PROMISE + builder.append(Settings::MAX_FRAME_SIZE_ID); + builder.append(quint32(maxFrameSize)); + builder.append(Settings::ENABLE_PUSH_ID); + builder.append(quint32(is_PUSH_PROMISE_enabled())); + + return builder.outboundFrame(); +} + +} // namespace Http2 + QT_END_NAMESPACE diff --git a/src/network/access/http2/http2protocol_p.h b/src/network/access/http2/http2protocol_p.h index 5d730404bb..b26ff0e9f4 100644 --- a/src/network/access/http2/http2protocol_p.h +++ b/src/network/access/http2/http2protocol_p.h @@ -59,6 +59,8 @@ QT_BEGIN_NAMESPACE +class QHttpNetworkRequest; +class QHttpNetworkReply; class QString; namespace Http2 @@ -132,6 +134,7 @@ enum Http2PredefinedParameters const quint32 lastValidStreamID((quint32(1) << 31) - 1); // HTTP/2, 5.1.1 extern const Q_AUTOTEST_EXPORT char Http2clientPreface[clientPrefaceLength]; +void prepare_for_protocol_upgrade(QHttpNetworkRequest &request); enum class FrameStatus { @@ -169,6 +172,9 @@ enum Http2Error void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error, QString &errorString); QString qt_error_string(quint32 errorCode); QNetworkReply::NetworkError qt_error(quint32 errorCode); +bool is_PUSH_PROMISE_enabled(); +bool is_protocol_upgraded(const QHttpNetworkReply &reply); +struct Frame default_SETTINGS_frame(); } diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp index 44ab637da8..5032f6017f 100644 --- a/src/network/access/qhttp2protocolhandler.cpp +++ b/src/network/access/qhttp2protocolhandler.cpp @@ -170,10 +170,22 @@ QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *chan decoder(HPack::FieldLookupTable::DefaultSize), encoder(HPack::FieldLookupTable::DefaultSize, true) { + Q_ASSERT(channel); continuedFrames.reserve(20); - bool ok = false; - const int env = qEnvironmentVariableIntValue("QT_HTTP2_ENABLE_PUSH_PROMISE", &ok); - pushPromiseEnabled = ok && env; + pushPromiseEnabled = is_PUSH_PROMISE_enabled(); + + if (!channel->ssl) { + // We upgraded from HTTP/1.1 to HTTP/2. channel->request was already sent + // as HTTP/1.1 request. The response with status code 101 triggered + // protocol switch and now we are waiting for the real response, sent + // as HTTP/2 frames. + Q_ASSERT(channel->reply); + const quint32 initialStreamID = createNewStream(HttpMessagePair(channel->request, channel->reply), + true /* uploaded by HTTP/1.1 */); + Q_ASSERT(initialStreamID == 1); + Stream &stream = activeStreams[initialStreamID]; + stream.state = Stream::halfClosedLocal; + } } void QHttp2ProtocolHandler::_q_uploadDataReadyRead() @@ -356,12 +368,8 @@ bool QHttp2ProtocolHandler::sendClientPreface() return false; // 6.5 SETTINGS - frameWriter.start(FrameType::SETTINGS, FrameFlag::EMPTY, Http2::connectionStreamID); - // MAX frame size (16 kb), enable/disable PUSH - frameWriter.append(Settings::MAX_FRAME_SIZE_ID); - frameWriter.append(quint32(Http2::maxFrameSize)); - frameWriter.append(Settings::ENABLE_PUSH_ID); - frameWriter.append(quint32(pushPromiseEnabled)); + frameWriter.setOutboundFrame(default_SETTINGS_frame()); + Q_ASSERT(frameWriter.outboundFrame().payloadSize()); if (!frameWriter.write(*m_socket)) return false; @@ -1157,7 +1165,7 @@ void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, QNetworkReply: << "finished with error:" << message; } -quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message) +quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message, bool uploadDone) { const qint32 newStreamID = allocateStreamID(); if (!newStreamID) @@ -1178,10 +1186,12 @@ quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message) streamInitialSendWindowSize, streamInitialRecvWindowSize); - if (auto src = newStream.data()) { - connect(src, SIGNAL(readyRead()), this, - SLOT(_q_uploadDataReadyRead()), Qt::QueuedConnection); - src->setProperty("HTTP2StreamID", newStreamID); + if (!uploadDone) { + if (auto src = newStream.data()) { + connect(src, SIGNAL(readyRead()), this, + SLOT(_q_uploadDataReadyRead()), Qt::QueuedConnection); + src->setProperty("HTTP2StreamID", newStreamID); + } } activeStreams.insert(newStreamID, newStream); diff --git a/src/network/access/qhttp2protocolhandler_p.h b/src/network/access/qhttp2protocolhandler_p.h index df0cf6a288..82eea21818 100644 --- a/src/network/access/qhttp2protocolhandler_p.h +++ b/src/network/access/qhttp2protocolhandler_p.h @@ -98,7 +98,7 @@ private: using Stream = Http2::Stream; void _q_readyRead() override; - void _q_receiveReply() override; + Q_INVOKABLE void _q_receiveReply() override; Q_INVOKABLE bool sendRequest() override; bool sendClientPreface(); @@ -136,7 +136,7 @@ private: const QString &message); // Stream's lifecycle management: - quint32 createNewStream(const HttpMessagePair &message); + quint32 createNewStream(const HttpMessagePair &message, bool uploadDone = false); void addToSuspended(Stream &stream); void markAsReset(quint32 streamID); quint32 popStreamToResume(); diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index ae30d3a8cf..0b474ba116 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -627,7 +627,8 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetwor if (request.isPreConnect()) preConnectRequests++; - if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP) { + if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP + || (!encrypt && connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2 && !channels[0].switchedToHttp2)) { switch (request.priority()) { case QHttpNetworkRequest::HighPriority: highPriorityQueue.prepend(pair); @@ -638,7 +639,7 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetwor break; } } - else { // SPDY, HTTP/2 + else { // SPDY, HTTP/2 ('h2' mode) if (!pair.second->d_func()->requestIsPrepared) prepareRequest(pair); channels[0].spdyRequestsToSend.insertMulti(request.priority(), pair); @@ -672,6 +673,25 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetwor return reply; } +void QHttpNetworkConnectionPrivate::fillHttp2Queue() +{ + for (auto &pair : highPriorityQueue) { + if (!pair.second->d_func()->requestIsPrepared) + prepareRequest(pair); + channels[0].spdyRequestsToSend.insertMulti(QHttpNetworkRequest::HighPriority, pair); + } + + highPriorityQueue.clear(); + + for (auto &pair : lowPriorityQueue) { + if (!pair.second->d_func()->requestIsPrepared) + prepareRequest(pair); + channels[0].spdyRequestsToSend.insertMulti(pair.first.priority(), pair); + } + + lowPriorityQueue.clear(); +} + void QHttpNetworkConnectionPrivate::requeueRequest(const HttpMessagePair &pair) { Q_Q(QHttpNetworkConnection); @@ -1047,8 +1067,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() } case QHttpNetworkConnection::ConnectionTypeHTTP2: case QHttpNetworkConnection::ConnectionTypeSPDY: { - - if (channels[0].spdyRequestsToSend.isEmpty()) + if (channels[0].spdyRequestsToSend.isEmpty() && channels[0].switchedToHttp2) return; if (networkLayerState == IPv4) @@ -1057,7 +1076,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() channels[0].networkLayerPreference = QAbstractSocket::IPv6Protocol; channels[0].ensureConnection(); if (channels[0].socket && channels[0].socket->state() == QAbstractSocket::ConnectedState - && !channels[0].pendingEncrypt) + && !channels[0].pendingEncrypt && channels[0].spdyRequestsToSend.size()) channels[0].sendRequest(); break; } @@ -1355,6 +1374,12 @@ QHttpNetworkReply* QHttpNetworkConnection::sendRequest(const QHttpNetworkRequest return d->queueRequest(request); } +void QHttpNetworkConnection::fillHttp2Queue() +{ + Q_D(QHttpNetworkConnection); + d->fillHttp2Queue(); +} + bool QHttpNetworkConnection::isSsl() const { Q_D(const QHttpNetworkConnection); diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h index 3dd9bde9bd..f01a2318a5 100644 --- a/src/network/access/qhttpnetworkconnection_p.h +++ b/src/network/access/qhttpnetworkconnection_p.h @@ -122,6 +122,7 @@ public: //add a new HTTP request through this connection QHttpNetworkReply* sendRequest(const QHttpNetworkRequest &request); + void fillHttp2Queue(); #ifndef QT_NO_NETWORKPROXY //set the proxy for this connection @@ -208,6 +209,7 @@ public: QHttpNetworkReply *queueRequest(const QHttpNetworkRequest &request); void requeueRequest(const HttpMessagePair &pair); // e.g. after pipeline broke + void fillHttp2Queue(); bool dequeueRequest(QAbstractSocket *socket); void prepareRequest(HttpMessagePair &request); void updateChannel(int i, const HttpMessagePair &messagePair); diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 6b2018ef86..b1ae29427e 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -63,6 +63,20 @@ QT_BEGIN_NAMESPACE +namespace +{ + +class ProtocolHandlerDeleter : public QObject +{ +public: + explicit ProtocolHandlerDeleter(QAbstractProtocolHandler *h) : handler(h) {} + ~ProtocolHandlerDeleter() { delete handler; } +private: + QAbstractProtocolHandler *handler = nullptr; +}; + +} + // TODO: Put channel specific stuff here so it does not polute qhttpnetworkconnection.cpp // Because in-flight when sending a request, the server might close our connection (because the persistent HTTP @@ -424,6 +438,40 @@ void QHttpNetworkConnectionChannel::allDone() return; } + if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2 + && !ssl && !switchedToHttp2) { + if (Http2::is_protocol_upgraded(*reply)) { + switchedToHttp2 = true; + protocolHandler->setReply(nullptr); + + // As allDone() gets called from the protocol handler, it's not yet + // safe to delete it. There is no 'deleteLater', since + // QAbstractProtocolHandler is not a QObject. Instead we do this + // trick with ProtocolHandlerDeleter, a QObject-derived class. + // These dances below just make it somewhat exception-safe. + // 1. Create a new owner: + QAbstractProtocolHandler *oldHandler = protocolHandler.data(); + QScopedPointer<ProtocolHandlerDeleter> deleter(new ProtocolHandlerDeleter(oldHandler)); + // 2. Retire the old one: + protocolHandler.take(); + // 3. Call 'deleteLater': + deleter->deleteLater(); + // 3. Give up the ownerthip: + deleter.take(); + + connection->fillHttp2Queue(); + protocolHandler.reset(new QHttp2ProtocolHandler(this)); + QHttp2ProtocolHandler *h2c = static_cast<QHttp2ProtocolHandler *>(protocolHandler.data()); + QMetaObject::invokeMethod(h2c, "_q_receiveReply", Qt::QueuedConnection); + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); + return; + } else { + // Ok, whatever happened, we do not try HTTP/2 anymore ... + connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP); + connection->d_func()->activeChannelCount = connection->d_func()->channelCount; + } + } + // while handling 401 & 407, we might reset the status code, so save this. bool emitFinished = reply->d_func()->shouldEmitSignals(); bool connectionCloseEnabled = reply->d_func()->isConnectionCloseEnabled(); @@ -838,19 +886,23 @@ void QHttpNetworkConnectionChannel::_q_connected() #endif } else { state = QHttpNetworkConnectionChannel::IdleState; - if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) { - // We have to reset QHttp2ProtocolHandler's state machine, it's a new - // connection and the handler's state is unique per connection. - protocolHandler.reset(new QHttp2ProtocolHandler(this)); - if (spdyRequestsToSend.count() > 0) { - // wait for data from the server first (e.g. initial window, max concurrent requests) - QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); + const bool tryProtocolUpgrade = connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2; + if (tryProtocolUpgrade) { + // For HTTP/1.1 it's already created and never reset. + protocolHandler.reset(new QHttpProtocolHandler(this)); + } + switchedToHttp2 = false; + + if (!reply) + connection->d_func()->dequeueRequest(socket); + + if (reply) { + if (tryProtocolUpgrade) { + // Let's augment our request with some magic headers and try to + // switch to HTTP/2. + Http2::prepare_for_protocol_upgrade(request); } - } else { - if (!reply) - connection->d_func()->dequeueRequest(socket); - if (reply) - sendRequest(); + sendRequest(); } } } @@ -1078,6 +1130,7 @@ void QHttpNetworkConnectionChannel::_q_encrypted() // has gone to the SPDY queue already break; } else if (nextProtocol == QSslConfiguration::ALPNProtocolHTTP2) { + switchedToHttp2 = true; protocolHandler.reset(new QHttp2ProtocolHandler(this)); connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP2); break; diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h index 584d52ddb7..844a7d5d15 100644 --- a/src/network/access/qhttpnetworkconnectionchannel_p.h +++ b/src/network/access/qhttpnetworkconnectionchannel_p.h @@ -127,6 +127,7 @@ public: // HTTP/2 can be cleartext also, that's why it's // outside of QT_NO_SSL section. Sorted by priority: QMultiMap<int, HttpMessagePair> spdyRequestsToSend; + bool switchedToHttp2 = false; #ifndef QT_NO_SSL bool ignoreAllSslErrors; QList<QSslError> ignoreSslErrorsList; diff --git a/src/network/access/qhttpnetworkheader_p.h b/src/network/access/qhttpnetworkheader_p.h index 89169b9331..46aec1dd8c 100644 --- a/src/network/access/qhttpnetworkheader_p.h +++ b/src/network/access/qhttpnetworkheader_p.h @@ -78,7 +78,7 @@ public: virtual void setHeaderField(const QByteArray &name, const QByteArray &data) = 0; }; -class QHttpNetworkHeaderPrivate : public QSharedData +class Q_AUTOTEST_EXPORT QHttpNetworkHeaderPrivate : public QSharedData { public: QUrl url; diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index eeee82a87c..15a0359391 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -71,6 +71,8 @@ #include "qthread.h" +#include <QHostInfo> + QT_BEGIN_NAMESPACE Q_GLOBAL_STATIC(QNetworkAccessFileBackendFactory, fileBackend) @@ -689,7 +691,7 @@ void QNetworkAccessManager::setCookieJar(QNetworkCookieJar *cookieJar) if (d->cookieJar && d->cookieJar->parent() == this) delete d->cookieJar; d->cookieJar = cookieJar; - if (thread() == cookieJar->thread()) + if (cookieJar && thread() == cookieJar->thread()) d->cookieJar->setParent(this); } } @@ -1366,10 +1368,16 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera } #ifndef QT_NO_BEARERMANAGEMENT + // Return a disabled network reply if network access is disabled. - // Except if the scheme is empty or file://. + // Except if the scheme is empty or file:// or if the host resolves to a loopback address. if (d->networkAccessible == NotAccessible && !isLocalFile) { - return new QDisabledNetworkReply(this, req, op); + QHostAddress dest; + QString host = req.url().host().toLower(); + if (!(dest.setAddress(host) && dest.isLoopback()) && host != QLatin1String("localhost") + && host != QHostInfo::localHostName().toLower()) { + return new QDisabledNetworkReply(this, req, op); + } } if (!d->networkSessionStrongRef && (d->initializeSession || !d->networkConfiguration.identifier().isEmpty())) { diff --git a/src/network/configure.json b/src/network/configure.json index de40872e16..b1c943de6f 100644 --- a/src/network/configure.json +++ b/src/network/configure.json @@ -135,7 +135,8 @@ "main": [ "char buf[IFNAMSIZ];", "if_nametoindex(\"eth0\");", - "if_indextoname(1, buf);" + "if_indextoname(1, buf);", + "if_freenameindex(if_nameindex());" ] }, "use": "network" diff --git a/src/network/kernel/kernel.pri b/src/network/kernel/kernel.pri index c3fcf25233..ac6bebbfae 100644 --- a/src/network/kernel/kernel.pri +++ b/src/network/kernel/kernel.pri @@ -63,6 +63,6 @@ osx:SOURCES += kernel/qnetworkproxy_mac.cpp else:win32:!winrt: SOURCES += kernel/qnetworkproxy_win.cpp else: qtConfig(libproxy) { SOURCES += kernel/qnetworkproxy_libproxy.cpp - QMAKE_USE_PRIVATE += libproxy + QMAKE_USE_PRIVATE += libproxy libdl } else:SOURCES += kernel/qnetworkproxy_generic.cpp diff --git a/src/network/kernel/qhostinfo_unix.cpp b/src/network/kernel/qhostinfo_unix.cpp index cf08a15f96..9a24938284 100644 --- a/src/network/kernel/qhostinfo_unix.cpp +++ b/src/network/kernel/qhostinfo_unix.cpp @@ -150,8 +150,7 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName) QHostAddress address; if (address.setAddress(hostName)) { // Reverse lookup -// Reverse lookups using getnameinfo are broken on darwin, use gethostbyaddr instead. -#if !defined (QT_NO_GETADDRINFO) && !defined (Q_OS_DARWIN) +#if !defined (QT_NO_GETADDRINFO) sockaddr_in sa4; sockaddr_in6 sa6; sockaddr *sa = 0; diff --git a/src/network/kernel/qnetworkinterface_unix.cpp b/src/network/kernel/qnetworkinterface_unix.cpp index 4f4615d4d0..afa6b4296e 100644 --- a/src/network/kernel/qnetworkinterface_unix.cpp +++ b/src/network/kernel/qnetworkinterface_unix.cpp @@ -60,11 +60,6 @@ # define QT_NO_GETIFADDRS #endif -#ifdef Q_OS_ANDROID -// android lacks if_nameindex -# define QT_NO_IPV6IFNAME -#endif - #ifdef Q_OS_HAIKU # include <sys/sockio.h> # define IFF_RUNNING 0x0001 diff --git a/src/network/kernel/qnetworkproxy_libproxy.cpp b/src/network/kernel/qnetworkproxy_libproxy.cpp index 184dc6469d..29d2a0bd3b 100644 --- a/src/network/kernel/qnetworkproxy_libproxy.cpp +++ b/src/network/kernel/qnetworkproxy_libproxy.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -42,57 +43,149 @@ #ifndef QT_NO_NETWORKPROXY #include <QtCore/QByteArray> +#include <QtCore/QMutex> +#include <QtCore/QSemaphore> #include <QtCore/QUrl> +#include <QtCore/private/qeventdispatcher_unix_p.h> +#include <QtCore/private/qthread_p.h> #include <proxy.h> +#include <dlfcn.h> QT_BEGIN_NAMESPACE -class QLibProxyWrapper +static bool isThreadingNeeded() { -public: - QLibProxyWrapper() - : factory(px_proxy_factory_new()) - { - if (!factory) - qWarning("libproxy initialization failed."); - } + // Try to guess if the libproxy we linked to is from the libproxy project + // or if it is from pacrunner. Neither library is thread-safe, but the one + // from libproxy is worse, since it may launch JS engines that don't take + // kindly to being executed from multiple threads (even if at different + // times). The pacrunner implementation doesn't suffer from this because + // the JS execution is out of process, in the pacrunner daemon. - ~QLibProxyWrapper() - { - px_proxy_factory_free(factory); - } + void *sym; + +#ifdef Q_CC_GNU + // Search for the mangled name of the virtual table of the pacrunner + // extension. Even if libproxy begins using -fvisibility=hidden, this + // symbol can't be hidden. + sym = dlsym(RTLD_DEFAULT, "_ZTVN8libproxy19pacrunner_extensionE"); +#else + // The default libproxy one uses libmodman for its module management and + // leaks symbols because it doesn't use -fvisibility=hidden (as of + // v0.4.15). + sym = dlsym(RTLD_DEFAULT, "mm_info_ignore_hostname"); +#endif + + return sym != nullptr; +} + +class QLibProxyWrapper : public QDaemonThread +{ + Q_OBJECT +public: + QLibProxyWrapper(); + ~QLibProxyWrapper(); QList<QUrl> getProxies(const QUrl &url); private: - pxProxyFactory *factory; + struct Data { + // we leave the conversion to/from QUrl to the calling thread + const char *url; + char **proxies; + QSemaphore replyReady; + }; + + void run() override; + + pxProxyFactory *factory; // not subject to the mutex + + QMutex mutex; + QSemaphore requestReady; + Data *request; }; Q_GLOBAL_STATIC(QLibProxyWrapper, libProxyWrapper); +QLibProxyWrapper::QLibProxyWrapper() +{ + if (isThreadingNeeded()) { + setEventDispatcher(new QEventDispatcherUNIX); // don't allow the Glib one + start(); + } else { + factory = px_proxy_factory_new(); + Q_CHECK_PTR(factory); + } +} + +QLibProxyWrapper::~QLibProxyWrapper() +{ + if (isRunning()) { + requestInterruption(); + requestReady.release(); + wait(); + } else { + px_proxy_factory_free(factory); + } +} + /* - Gets the list of proxies from libproxy, converted to QUrl list. - Thread safe, according to libproxy documentation. + Gets the list of proxies from libproxy, converted to QUrl list. Apply + thread-safety, though its documentation says otherwise, libproxy isn't + thread-safe. */ QList<QUrl> QLibProxyWrapper::getProxies(const QUrl &url) { - QList<QUrl> ret; + QByteArray encodedUrl = url.toEncoded(); + Data data; + data.url = encodedUrl.constData(); + + { + QMutexLocker locker(&mutex); + if (isRunning()) { + // threaded mode + // it's safe to write to request because we hold the mutex: + // our aux thread is blocked waiting for work and no other thread + // could have got here + request = &data; + requestReady.release(); - if (factory) { - char **proxies = px_proxy_factory_get_proxies(factory, url.toEncoded()); - if (proxies) { - for (int i = 0; proxies[i]; i++) { - ret.append(QUrl::fromEncoded(proxies[i])); - free(proxies[i]); - } - free(proxies); + // wait for the reply + data.replyReady.acquire(); + } else { + // non-threaded mode + data.proxies = px_proxy_factory_get_proxies(factory, data.url); } } + QList<QUrl> ret; + if (data.proxies) { + for (int i = 0; data.proxies[i]; i++) { + ret.append(QUrl::fromEncoded(data.proxies[i])); + free(data.proxies[i]); + } + free(data.proxies); + } return ret; } +void QLibProxyWrapper::run() +{ + factory = px_proxy_factory_new(); + Q_CHECK_PTR(factory); + + forever { + requestReady.acquire(); + if (isInterruptionRequested()) + break; + request->proxies = px_proxy_factory_get_proxies(factory, request->url); + request->replyReady.release(); + } + + px_proxy_factory_free(factory); +} + QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query) { QList<QNetworkProxy> proxyList; @@ -161,4 +254,6 @@ QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkPro QT_END_NAMESPACE +#include "qnetworkproxy_libproxy.moc" + #endif diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp index 13ceb4c612..a8f756dc31 100644 --- a/src/network/socket/qnativesocketengine_unix.cpp +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -834,7 +834,7 @@ bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const QT_SOCKLEN_T storageSize = sizeof(storage); memset(&storage, 0, storageSize); - // Peek 0 bytes into the next message. The size of the message may + // Peek 1 bytes into the next message. The size of the message may // well be 0, so we can't check recvfrom's return value. ssize_t readBytes; do { @@ -855,8 +855,20 @@ bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const { - QVarLengthArray<char, 8192> udpMessagePeekBuffer(8192); ssize_t recvResult = -1; +#ifdef Q_OS_LINUX + // Linux can return the actual datagram size if we use MSG_TRUNC + char c; + EINTR_LOOP(recvResult, ::recv(socketDescriptor, &c, 1, MSG_PEEK | MSG_TRUNC)); +#elif defined(SO_NREAD) + // macOS can return the actual datagram size if we use SO_NREAD + int value; + socklen_t valuelen = sizeof(value); + recvResult = getsockopt(socketDescriptor, SOL_SOCKET, SO_NREAD, &value, &valuelen); + if (recvResult != -1) + recvResult = value; +#else + QVarLengthArray<char, 8192> udpMessagePeekBuffer(8192); for (;;) { // the data written to udpMessagePeekBuffer is discarded, so @@ -872,6 +884,7 @@ qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const udpMessagePeekBuffer.resize(udpMessagePeekBuffer.size() * 2); } +#endif #if defined (QNATIVESOCKETENGINE_DEBUG) qDebug("QNativeSocketEnginePrivate::nativePendingDatagramSize() == %zd", recvResult); diff --git a/src/network/ssl/qsslkey_openssl.cpp b/src/network/ssl/qsslkey_openssl.cpp index be7033c8aa..aa81b735b9 100644 --- a/src/network/ssl/qsslkey_openssl.cpp +++ b/src/network/ssl/qsslkey_openssl.cpp @@ -85,6 +85,9 @@ void QSslKeyPrivate::clear(bool deep) bool QSslKeyPrivate::fromEVP_PKEY(EVP_PKEY *pkey) { + if (pkey == nullptr) + return false; + #if QT_CONFIG(opensslv11) const int keyType = q_EVP_PKEY_type(q_EVP_PKEY_base_id(pkey)); #else diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp index 650d37fdbb..23a63ed063 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -704,10 +704,9 @@ void QSslSocketBackendPrivate::transmit() // Write encrypted data from the buffer into the read BIO. int writtenToBio = q_BIO_write(readBio, data.constData(), encryptedBytesRead); - // do the actual read() here and throw away the results. + // Throw away the results. if (writtenToBio > 0) { - // ### TODO: make this cheaper by not making it memcpy. E.g. make it work with data=0x0 or make it work with seek - plainSocket->read(data.data(), writtenToBio); + plainSocket->skip(writtenToBio); } else { // ### Better error handling. setErrorAndEmit(QAbstractSocket::SslInternalError, @@ -1500,7 +1499,7 @@ bool QSslSocketBackendPrivate::importPkcs12(QIODevice *device, } // Extract the data - EVP_PKEY *pkey; + EVP_PKEY *pkey = nullptr; X509 *x509; STACK_OF(X509) *ca = 0; |