diff options
author | Oswald Buddenhagen <oswald.buddenhagen@digia.com> | 2014-02-19 10:06:25 +0100 |
---|---|---|
committer | Oswald Buddenhagen <oswald.buddenhagen@digia.com> | 2014-02-19 10:06:25 +0100 |
commit | 30fd22b9574def54726e7b193127cc0c901c1b4c (patch) | |
tree | 96dfc923044db0515064ba39d052d9ed577e3e40 /src/network | |
parent | d7b0581c1c2ef60c08d238dae39298af6904918f (diff) | |
parent | 6aa09bbce59828d028f6d1e81d2bfc6ba537aae1 (diff) |
Merge remote-tracking branch 'origin/dev' into stable
Change-Id: Ice524edcc51373509f0023ae7f7c2963f4602f88
Diffstat (limited to 'src/network')
56 files changed, 3227 insertions, 485 deletions
diff --git a/src/network/access/access.pri b/src/network/access/access.pri index aaaf05b551..3017417da8 100644 --- a/src/network/access/access.pri +++ b/src/network/access/access.pri @@ -7,6 +7,8 @@ HEADERS += \ access/qhttpnetworkreply_p.h \ access/qhttpnetworkconnection_p.h \ access/qhttpnetworkconnectionchannel_p.h \ + access/qabstractprotocolhandler_p.h \ + access/qhttpprotocolhandler_p.h \ access/qnetworkaccessauthenticationmanager_p.h \ access/qnetworkaccessmanager.h \ access/qnetworkaccessmanager_p.h \ @@ -43,6 +45,8 @@ SOURCES += \ access/qhttpnetworkreply.cpp \ access/qhttpnetworkconnection.cpp \ access/qhttpnetworkconnectionchannel.cpp \ + access/qabstractprotocolhandler.cpp \ + access/qhttpprotocolhandler.cpp \ access/qnetworkaccessauthenticationmanager.cpp \ access/qnetworkaccessmanager.cpp \ access/qnetworkaccesscache.cpp \ diff --git a/src/network/access/qabstractprotocolhandler.cpp b/src/network/access/qabstractprotocolhandler.cpp new file mode 100644 index 0000000000..e72bb63236 --- /dev/null +++ b/src/network/access/qabstractprotocolhandler.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qabstractprotocolhandler_p.h> +#include <private/qhttpnetworkconnectionchannel_p.h> + +#ifndef QT_NO_HTTP + +QT_BEGIN_NAMESPACE + +QAbstractProtocolHandler::QAbstractProtocolHandler(QHttpNetworkConnectionChannel *channel) + : m_channel(channel), m_reply(0), m_socket(m_channel->socket), m_connection(m_channel->connection) +{ + Q_ASSERT(m_channel); + Q_ASSERT(m_socket); + Q_ASSERT(m_connection); +} + +QAbstractProtocolHandler::~QAbstractProtocolHandler() +{ +} + +void QAbstractProtocolHandler::setReply(QHttpNetworkReply *reply) +{ + m_reply = reply; +} + +QT_END_NAMESPACE + +#endif // QT_NO_HTTP diff --git a/src/network/access/qabstractprotocolhandler_p.h b/src/network/access/qabstractprotocolhandler_p.h new file mode 100644 index 0000000000..387d08ccac --- /dev/null +++ b/src/network/access/qabstractprotocolhandler_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTPROTOCOLHANDLER_H +#define QABSTRACTPROTOCOLHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the Network Access API. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_HTTP + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +class QHttpNetworkConnectionChannel; +class QHttpNetworkReply; +class QAbstractSocket; +class QHttpNetworkConnection; + +class QAbstractProtocolHandler { +public: + QAbstractProtocolHandler(QHttpNetworkConnectionChannel *channel); + virtual ~QAbstractProtocolHandler(); + + virtual void _q_receiveReply() = 0; + virtual void _q_readyRead() = 0; + virtual bool sendRequest() = 0; + void setReply(QHttpNetworkReply *reply); + +protected: + QHttpNetworkConnectionChannel *m_channel; + QHttpNetworkReply *m_reply; + QAbstractSocket *m_socket; + QHttpNetworkConnection *m_connection; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_HTTP + +#endif // QABSTRACTPROTOCOLHANDLER_H diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index e0996c4072..66c97f7485 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -512,7 +512,7 @@ void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, if ((channels[i].authMethod != QAuthenticatorPrivate::Ntlm && request.headerField("Authorization").isEmpty()) || channels[i].lastStatus == 401) { QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].authenticator); if (priv && priv->method != QAuthenticatorPrivate::None) { - QByteArray response = priv->calculateResponse(request.d->methodName(), request.d->uri(false)); + QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false)); request.setHeaderField("Authorization", response); channels[i].authenticationCredentialsSent = true; } @@ -524,7 +524,7 @@ void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, if (!(channels[i].proxyAuthMethod == QAuthenticatorPrivate::Ntlm && channels[i].lastStatus != 407)) { QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].proxyAuthenticator); if (priv && priv->method != QAuthenticatorPrivate::None) { - QByteArray response = priv->calculateResponse(request.d->methodName(), request.d->uri(false)); + QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false)); request.setHeaderField("Proxy-Authorization", response); channels[i].proxyCredentialsSent = true; } diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h index 2aaaad24ac..526326c3fd 100644 --- a/src/network/access/qhttpnetworkconnection_p.h +++ b/src/network/access/qhttpnetworkconnection_p.h @@ -139,6 +139,7 @@ private: friend class QHttpNetworkReply; friend class QHttpNetworkReplyPrivate; friend class QHttpNetworkConnectionChannel; + friend class QHttpProtocolHandler; Q_PRIVATE_SLOT(d_func(), void _q_startNextRequest()) Q_PRIVATE_SLOT(d_func(), void _q_hostLookupFinished(QHostInfo)) diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 6e786893ed..6f06c18732 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -48,6 +49,8 @@ #ifndef QT_NO_HTTP +#include <private/qhttpprotocolhandler_p.h> + #ifndef QT_NO_SSL # include <QtNetwork/qsslkey.h> # include <QtNetwork/qsslcipher.h> @@ -78,6 +81,7 @@ QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel() , proxyAuthMethod(QAuthenticatorPrivate::None) , authenticationCredentialsSent(false) , proxyCredentialsSent(false) + , protocolHandler(0) #ifndef QT_NO_SSL , ignoreAllSslErrors(false) #endif @@ -163,8 +167,8 @@ void QHttpNetworkConnectionChannel::init() if (!sslConfiguration.isNull()) sslSocket->setSslConfiguration(sslConfiguration); } - #endif + protocolHandler.reset(new QHttpProtocolHandler(this)); #ifndef QT_NO_NETWORKPROXY if (proxy.type() != QNetworkProxy::NoProxy) @@ -193,349 +197,21 @@ void QHttpNetworkConnectionChannel::close() bool QHttpNetworkConnectionChannel::sendRequest() { - if (!reply) { - // heh, how should that happen! - qWarning() << "QHttpNetworkConnectionChannel::sendRequest() called without QHttpNetworkReply"; - state = QHttpNetworkConnectionChannel::IdleState; - return false; - } - - switch (state) { - case QHttpNetworkConnectionChannel::IdleState: { // write the header - if (!ensureConnection()) { - // wait for the connection (and encryption) to be done - // sendRequest will be called again from either - // _q_connected or _q_encrypted - return false; - } - QString scheme = request.url().scheme(); - if (scheme == QLatin1String("preconnect-http") - || scheme == QLatin1String("preconnect-https")) { - state = QHttpNetworkConnectionChannel::IdleState; - reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; - allDone(); - connection->preConnectFinished(); // will only decrease the counter - reply = 0; // so we can reuse this channel - return true; // we have a working connection and are done - } - - written = 0; // excluding the header - bytesTotal = 0; - - QHttpNetworkReplyPrivate *replyPrivate = reply->d_func(); - replyPrivate->clear(); - replyPrivate->connection = connection; - replyPrivate->connectionChannel = this; - replyPrivate->autoDecompress = request.d->autoDecompress; - replyPrivate->pipeliningUsed = false; - - // if the url contains authentication parameters, use the new ones - // both channels will use the new authentication parameters - if (!request.url().userInfo().isEmpty() && request.withCredentials()) { - QUrl url = request.url(); - QAuthenticator &auth = authenticator; - if (url.userName() != auth.user() - || (!url.password().isEmpty() && url.password() != auth.password())) { - auth.setUser(url.userName(QUrl::FullyDecoded)); - auth.setPassword(url.password(QUrl::FullyDecoded)); - connection->d_func()->copyCredentials(connection->d_func()->indexOf(socket), &auth, false); - } - // clear the userinfo, since we use the same request for resending - // userinfo in url can conflict with the one in the authenticator - url.setUserInfo(QString()); - request.setUrl(url); - } - // Will only be false if Qt WebKit is performing a cross-origin XMLHttpRequest - // and withCredentials has not been set to true. - if (request.withCredentials()) - connection->d_func()->createAuthorization(socket, request); -#ifndef QT_NO_NETWORKPROXY - QByteArray header = QHttpNetworkRequestPrivate::header(request, - (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy)); -#else - QByteArray header = QHttpNetworkRequestPrivate::header(request, false); -#endif - socket->write(header); - // flushing is dangerous (QSslSocket calls transmit which might read or error) -// socket->flush(); - QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice(); - if (uploadByteDevice) { - // connect the signals so this function gets called again - QObject::connect(uploadByteDevice, SIGNAL(readyRead()),this, SLOT(_q_uploadDataReadyRead())); - - bytesTotal = request.contentLength(); - - state = QHttpNetworkConnectionChannel::WritingState; // start writing data - sendRequest(); //recurse - } else { - state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response - sendRequest(); //recurse - } - - break; - } - case QHttpNetworkConnectionChannel::WritingState: - { - // write the data - QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice(); - if (!uploadByteDevice || bytesTotal == written) { - if (uploadByteDevice) - emit reply->dataSendProgress(written, bytesTotal); - state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response - sendRequest(); // recurse - break; - } - - // only feed the QTcpSocket buffer when there is less than 32 kB in it - const qint64 socketBufferFill = 32*1024; - const qint64 socketWriteMaxSize = 16*1024; - - -#ifndef QT_NO_SSL - QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket); - // if it is really an ssl socket, check more than just bytesToWrite() - while ((socket->bytesToWrite() + (sslSocket ? sslSocket->encryptedBytesToWrite() : 0)) - <= socketBufferFill && bytesTotal != written) -#else - while (socket->bytesToWrite() <= socketBufferFill - && bytesTotal != written) -#endif - { - // get pointer to upload data - qint64 currentReadSize = 0; - qint64 desiredReadSize = qMin(socketWriteMaxSize, bytesTotal - written); - const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize); - - if (currentReadSize == -1) { - // premature eof happened - connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError); - return false; - } else if (readPointer == 0 || currentReadSize == 0) { - // nothing to read currently, break the loop - break; - } else { - qint64 currentWriteSize = socket->write(readPointer, currentReadSize); - if (currentWriteSize == -1 || currentWriteSize != currentReadSize) { - // socket broke down - connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError); - return false; - } else { - written += currentWriteSize; - uploadByteDevice->advanceReadPointer(currentWriteSize); - - emit reply->dataSendProgress(written, bytesTotal); - - if (written == bytesTotal) { - // make sure this function is called once again - state = QHttpNetworkConnectionChannel::WaitingState; - sendRequest(); - break; - } - } - } - } - break; - } - - case QHttpNetworkConnectionChannel::WaitingState: - { - QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice(); - if (uploadByteDevice) { - QObject::disconnect(uploadByteDevice, SIGNAL(readyRead()), this, SLOT(_q_uploadDataReadyRead())); - } - - // HTTP pipelining - //connection->d_func()->fillPipeline(socket); - //socket->flush(); - - // ensure we try to receive a reply in all cases, even if _q_readyRead_ hat not been called - // this is needed if the sends an reply before we have finished sending the request. In that - // case receiveReply had been called before but ignored the server reply - if (socket->bytesAvailable()) - QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection); - break; - } - case QHttpNetworkConnectionChannel::ReadingState: - // ignore _q_bytesWritten in these states - // fall through - default: - break; - } - return true; + Q_ASSERT(!protocolHandler.isNull()); + return protocolHandler->sendRequest(); } void QHttpNetworkConnectionChannel::_q_receiveReply() { - Q_ASSERT(socket); - - if (!reply) { - if (socket->bytesAvailable() > 0) - qWarning() << "QHttpNetworkConnectionChannel::_q_receiveReply() called without QHttpNetworkReply," - << socket->bytesAvailable() << "bytes on socket."; - close(); - return; - } - - // only run when the QHttpNetworkConnection is not currently being destructed, e.g. - // this function is called from _q_disconnected which is called because - // of ~QHttpNetworkConnectionPrivate - if (!qobject_cast<QHttpNetworkConnection*>(connection)) { - return; - } - - QAbstractSocket::SocketState socketState = socket->state(); - - // connection might be closed to signal the end of data - if (socketState == QAbstractSocket::UnconnectedState) { - if (socket->bytesAvailable() <= 0) { - if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) { - // finish this reply. this case happens when the server did not send a content length - reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; - allDone(); - return; - } else { - handleUnexpectedEOF(); - return; - } - } else { - // socket not connected but still bytes for reading.. just continue in this function - } - } - - // read loop for the response - qint64 bytes = 0; - qint64 lastBytes = bytes; - do { - lastBytes = bytes; - - QHttpNetworkReplyPrivate::ReplyState state = reply->d_func()->state; - switch (state) { - case QHttpNetworkReplyPrivate::NothingDoneState: { - state = reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState; - // fallthrough - } - case QHttpNetworkReplyPrivate::ReadingStatusState: { - qint64 statusBytes = reply->d_func()->readStatus(socket); - if (statusBytes == -1) { - // connection broke while reading status. also handled if later _q_disconnected is called - handleUnexpectedEOF(); - return; - } - bytes += statusBytes; - lastStatus = reply->d_func()->statusCode; - break; - } - case QHttpNetworkReplyPrivate::ReadingHeaderState: { - QHttpNetworkReplyPrivate *replyPrivate = reply->d_func(); - qint64 headerBytes = replyPrivate->readHeader(socket); - if (headerBytes == -1) { - // connection broke while reading headers. also handled if later _q_disconnected is called - handleUnexpectedEOF(); - return; - } - bytes += headerBytes; - // If headers were parsed successfully now it is the ReadingDataState - if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) { - if (replyPrivate->isCompressed() && replyPrivate->autoDecompress) { - // remove the Content-Length from header - replyPrivate->removeAutoDecompressHeader(); - } else { - replyPrivate->autoDecompress = false; - } - if (replyPrivate->statusCode == 100) { - replyPrivate->clearHttpLayerInformation(); - replyPrivate->state = QHttpNetworkReplyPrivate::ReadingStatusState; - break; // ignore - } - if (replyPrivate->shouldEmitSignals()) - emit reply->headerChanged(); - // After headerChanged had been emitted - // we can suddenly have a replyPrivate->userProvidedDownloadBuffer - // this is handled in the ReadingDataState however - - if (!replyPrivate->expectContent()) { - replyPrivate->state = QHttpNetworkReplyPrivate::AllDoneState; - allDone(); - break; - } - } - break; - } - case QHttpNetworkReplyPrivate::ReadingDataState: { - QHttpNetworkReplyPrivate *replyPrivate = reply->d_func(); - if (socket->state() == QAbstractSocket::ConnectedState && - replyPrivate->downstreamLimited && !replyPrivate->responseData.isEmpty() && replyPrivate->shouldEmitSignals()) { - // (only do the following when still connected, not when we have already been disconnected and there is still data) - // We already have some HTTP body data. We don't read more from the socket until - // this is fetched by QHttpNetworkAccessHttpBackend. If we would read more, - // we could not limit our read buffer usage. - // We only do this when shouldEmitSignals==true because our HTTP parsing - // always needs to parse the 401/407 replies. Therefore they don't really obey - // to the read buffer maximum size, but we don't care since they should be small. - return; - } - - if (replyPrivate->userProvidedDownloadBuffer) { - // the user provided a direct buffer where we should put all our data in. - // this only works when we can tell the user the content length and he/she can allocate - // the buffer in that size. - // note that this call will read only from the still buffered data - qint64 haveRead = replyPrivate->readBodyVeryFast(socket, replyPrivate->userProvidedDownloadBuffer + replyPrivate->totalProgress); - if (haveRead > 0) { - bytes += haveRead; - replyPrivate->totalProgress += haveRead; - // the user will get notified of it via progress signal - emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); - } else if (haveRead == 0) { - // Happens since this called in a loop. Currently no bytes available. - } else if (haveRead < 0) { - connection->d_func()->emitReplyError(socket, reply, QNetworkReply::RemoteHostClosedError); - break; - } - } else if (!replyPrivate->isChunked() && !replyPrivate->autoDecompress - && replyPrivate->bodyLength > 0) { - // bulk files like images should fulfill these properties and - // we can therefore save on memory copying - qint64 haveRead = replyPrivate->readBodyFast(socket, &replyPrivate->responseData); - bytes += haveRead; - replyPrivate->totalProgress += haveRead; - if (replyPrivate->shouldEmitSignals()) { - emit reply->readyRead(); - emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); - } - } - else - { - // use the traditional slower reading (for compressed encoding, chunked encoding, - // no content-length etc) - qint64 haveRead = replyPrivate->readBody(socket, &replyPrivate->responseData); - if (haveRead > 0) { - bytes += haveRead; - replyPrivate->totalProgress += haveRead; - if (replyPrivate->shouldEmitSignals()) { - emit reply->readyRead(); - emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); - } - } else if (haveRead == -1) { - // Some error occurred - connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolFailure); - break; - } - } - // still in ReadingDataState? This function will be called again by the socket's readyRead - if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) - break; + Q_ASSERT(!protocolHandler.isNull()); + protocolHandler->_q_receiveReply(); +} - // everything done, fall through - } - case QHttpNetworkReplyPrivate::AllDoneState: - allDone(); - break; - default: - break; - } - } while (bytes != lastBytes && reply); +void QHttpNetworkConnectionChannel::_q_readyRead() +{ + Q_ASSERT(!protocolHandler.isNull()); + protocolHandler->_q_readyRead(); } // called when unexpectedly reading a -1 or when data is expected but socket is closed @@ -724,6 +400,7 @@ void QHttpNetworkConnectionChannel::allDone() if (!resendCurrent) { request = QHttpNetworkRequest(); reply = 0; + protocolHandler->setReply(0); } // move next from pipeline to current request @@ -738,6 +415,7 @@ void QHttpNetworkConnectionChannel::allDone() request = messagePair.first; reply = messagePair.second; + protocolHandler->setReply(messagePair.second); state = QHttpNetworkConnectionChannel::ReadingState; resendCurrent = false; @@ -982,32 +660,6 @@ bool QHttpNetworkConnectionChannel::isSocketReading() const return (state & QHttpNetworkConnectionChannel::ReadingState); } -//private slots -void QHttpNetworkConnectionChannel::_q_readyRead() -{ - if (socket->state() == QAbstractSocket::ConnectedState && socket->bytesAvailable() == 0) { - // We got a readyRead but no bytes are available.. - // This happens for the Unbuffered QTcpSocket - // Also check if socket is in ConnectedState since - // this function may also be invoked via the event loop. - char c; - qint64 ret = socket->peek(&c, 1); - if (ret < 0) { - _q_error(socket->error()); - // We still need to handle the reply so it emits its signals etc. - if (reply) - _q_receiveReply(); - return; - } - } - - if (isSocketWaiting() || isSocketReading()) { - state = QHttpNetworkConnectionChannel::ReadingState; - if (reply) - _q_receiveReply(); - } -} - void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes) { Q_UNUSED(bytes); @@ -1224,6 +876,8 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket reply->d_func()->errorString = errorString; emit reply->finishedWithError(errorCode, errorString); reply = 0; + if (protocolHandler) + protocolHandler->setReply(0); } // send the next request QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection); diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h index c8138b5453..7230eb2543 100644 --- a/src/network/access/qhttpnetworkconnectionchannel_p.h +++ b/src/network/access/qhttpnetworkconnectionchannel_p.h @@ -66,6 +66,7 @@ #include <private/qhttpnetworkreply_p.h> #include <private/qhttpnetworkconnection_p.h> +#include <private/qabstractprotocolhandler_p.h> #ifndef QT_NO_HTTP @@ -117,6 +118,7 @@ public: QAuthenticator proxyAuthenticator; bool authenticationCredentialsSent; bool proxyCredentialsSent; + QScopedPointer<QAbstractProtocolHandler> protocolHandler; #ifndef QT_NO_SSL bool ignoreAllSslErrors; QList<QSslError> ignoreSslErrorsList; @@ -193,6 +195,8 @@ public: void _q_sslErrors(const QList<QSslError> &errors); // ssl errors from the socket void _q_encryptedBytesWritten(qint64 bytes); // proceed sending #endif + + friend class QHttpProtocolHandler; }; QT_END_NAMESPACE diff --git a/src/network/access/qhttpnetworkreply_p.h b/src/network/access/qhttpnetworkreply_p.h index 7aea9f14ec..583c3e426f 100644 --- a/src/network/access/qhttpnetworkreply_p.h +++ b/src/network/access/qhttpnetworkreply_p.h @@ -164,6 +164,7 @@ private: friend class QHttpNetworkConnection; friend class QHttpNetworkConnectionPrivate; friend class QHttpNetworkConnectionChannel; + friend class QHttpProtocolHandler; }; diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp index d9f9b555d7..3786f9b992 100644 --- a/src/network/access/qhttpnetworkrequest.cpp +++ b/src/network/access/qhttpnetworkrequest.cpp @@ -87,9 +87,9 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot && (preConnect == other.preConnect); } -QByteArray QHttpNetworkRequestPrivate::methodName() const +QByteArray QHttpNetworkRequest::methodName() const { - switch (operation) { + switch (d->operation) { case QHttpNetworkRequest::Get: return "GET"; case QHttpNetworkRequest::Head: @@ -107,24 +107,24 @@ QByteArray QHttpNetworkRequestPrivate::methodName() const case QHttpNetworkRequest::Connect: return "CONNECT"; case QHttpNetworkRequest::Custom: - return customVerb; + return d->customVerb; default: break; } return QByteArray(); } -QByteArray QHttpNetworkRequestPrivate::uri(bool throughProxy) const +QByteArray QHttpNetworkRequest::uri(bool throughProxy) const { QUrl::FormattingOptions format(QUrl::RemoveFragment | QUrl::RemoveUserInfo | QUrl::FullyEncoded); // for POST, query data is send as content - if (operation == QHttpNetworkRequest::Post && !uploadByteDevice) + if (d->operation == QHttpNetworkRequest::Post && !d->uploadByteDevice) format |= QUrl::RemoveQuery; // for requests through proxy, the Request-URI contains full url if (!throughProxy) format |= QUrl::RemoveScheme | QUrl::RemoveAuthority; - QUrl copy = url; + QUrl copy = d->url; if (copy.path().isEmpty()) copy.setPath(QStringLiteral("/")); QByteArray uri = copy.toEncoded(format); @@ -137,9 +137,9 @@ QByteArray QHttpNetworkRequestPrivate::header(const QHttpNetworkRequest &request QByteArray ba; ba.reserve(40 + fields.length()*25); // very rough lower bound estimation - ba += request.d->methodName(); + ba += request.methodName(); ba += ' '; - ba += request.d->uri(throughProxy); + ba += request.uri(throughProxy); ba += " HTTP/"; ba += QByteArray::number(request.majorVersion()); diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h index ce9fbb1509..f224f7329d 100644 --- a/src/network/access/qhttpnetworkrequest_p.h +++ b/src/network/access/qhttpnetworkrequest_p.h @@ -126,11 +126,15 @@ public: void setUploadByteDevice(QNonContiguousByteDevice *bd); QNonContiguousByteDevice* uploadByteDevice() const; + QByteArray methodName() const; + QByteArray uri(bool throughProxy) const; + private: QSharedDataPointer<QHttpNetworkRequestPrivate> d; friend class QHttpNetworkRequestPrivate; friend class QHttpNetworkConnectionPrivate; friend class QHttpNetworkConnectionChannel; + friend class QHttpProtocolHandler; }; class QHttpNetworkRequestPrivate : public QHttpNetworkHeaderPrivate @@ -141,8 +145,6 @@ public: QHttpNetworkRequestPrivate(const QHttpNetworkRequestPrivate &other); ~QHttpNetworkRequestPrivate(); bool operator==(const QHttpNetworkRequestPrivate &other) const; - QByteArray methodName() const; - QByteArray uri(bool throughProxy) const; static QByteArray header(const QHttpNetworkRequest &request, bool throughProxy); diff --git a/src/network/access/qhttpprotocolhandler.cpp b/src/network/access/qhttpprotocolhandler.cpp new file mode 100644 index 0000000000..15cba48285 --- /dev/null +++ b/src/network/access/qhttpprotocolhandler.cpp @@ -0,0 +1,431 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qhttpprotocolhandler_p.h> +#include <private/qnoncontiguousbytedevice_p.h> +#include <private/qhttpnetworkconnectionchannel_p.h> + +#ifndef QT_NO_HTTP + +QT_BEGIN_NAMESPACE + +QHttpProtocolHandler::QHttpProtocolHandler(QHttpNetworkConnectionChannel *channel) + : QAbstractProtocolHandler(channel) +{ +} + +void QHttpProtocolHandler::_q_receiveReply() +{ + Q_ASSERT(m_socket); + + if (!m_reply) { + if (m_socket->bytesAvailable() > 0) + qWarning() << "QAbstractProtocolHandler::_q_receiveReply() called without QHttpNetworkReply," + << m_socket->bytesAvailable() << "bytes on socket."; + m_channel->close(); + return; + } + + // only run when the QHttpNetworkConnection is not currently being destructed, e.g. + // this function is called from _q_disconnected which is called because + // of ~QHttpNetworkConnectionPrivate + if (!qobject_cast<QHttpNetworkConnection*>(m_connection)) { + return; + } + + QAbstractSocket::SocketState socketState = m_socket->state(); + + // connection might be closed to signal the end of data + if (socketState == QAbstractSocket::UnconnectedState) { + if (m_socket->bytesAvailable() <= 0) { + if (m_reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) { + // finish this reply. this case happens when the server did not send a content length + m_reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; + m_channel->allDone(); + return; + } else { + m_channel->handleUnexpectedEOF(); + return; + } + } else { + // socket not connected but still bytes for reading.. just continue in this function + } + } + + // read loop for the response + qint64 bytes = 0; + qint64 lastBytes = bytes; + do { + lastBytes = bytes; + + QHttpNetworkReplyPrivate::ReplyState state = m_reply->d_func()->state; + switch (state) { + case QHttpNetworkReplyPrivate::NothingDoneState: { + m_reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState; + // fallthrough + } + case QHttpNetworkReplyPrivate::ReadingStatusState: { + qint64 statusBytes = m_reply->d_func()->readStatus(m_socket); + if (statusBytes == -1) { + // connection broke while reading status. also handled if later _q_disconnected is called + m_channel->handleUnexpectedEOF(); + return; + } + bytes += statusBytes; + m_channel->lastStatus = m_reply->d_func()->statusCode; + break; + } + case QHttpNetworkReplyPrivate::ReadingHeaderState: { + QHttpNetworkReplyPrivate *replyPrivate = m_reply->d_func(); + qint64 headerBytes = replyPrivate->readHeader(m_socket); + if (headerBytes == -1) { + // connection broke while reading headers. also handled if later _q_disconnected is called + m_channel->handleUnexpectedEOF(); + return; + } + bytes += headerBytes; + // If headers were parsed successfully now it is the ReadingDataState + if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) { + if (replyPrivate->isCompressed() && replyPrivate->autoDecompress) { + // remove the Content-Length from header + replyPrivate->removeAutoDecompressHeader(); + } else { + replyPrivate->autoDecompress = false; + } + if (replyPrivate->statusCode == 100) { + replyPrivate->clearHttpLayerInformation(); + replyPrivate->state = QHttpNetworkReplyPrivate::ReadingStatusState; + break; // ignore + } + if (replyPrivate->shouldEmitSignals()) + emit m_reply->headerChanged(); + // After headerChanged had been emitted + // we can suddenly have a replyPrivate->userProvidedDownloadBuffer + // this is handled in the ReadingDataState however + + if (!replyPrivate->expectContent()) { + replyPrivate->state = QHttpNetworkReplyPrivate::AllDoneState; + m_channel->allDone(); + break; + } + } + break; + } + case QHttpNetworkReplyPrivate::ReadingDataState: { + QHttpNetworkReplyPrivate *replyPrivate = m_reply->d_func(); + if (m_socket->state() == QAbstractSocket::ConnectedState && + replyPrivate->downstreamLimited && !replyPrivate->responseData.isEmpty() && replyPrivate->shouldEmitSignals()) { + // (only do the following when still connected, not when we have already been disconnected and there is still data) + // We already have some HTTP body data. We don't read more from the socket until + // this is fetched by QHttpNetworkAccessHttpBackend. If we would read more, + // we could not limit our read buffer usage. + // We only do this when shouldEmitSignals==true because our HTTP parsing + // always needs to parse the 401/407 replies. Therefore they don't really obey + // to the read buffer maximum size, but we don't care since they should be small. + return; + } + + if (replyPrivate->userProvidedDownloadBuffer) { + // the user provided a direct buffer where we should put all our data in. + // this only works when we can tell the user the content length and he/she can allocate + // the buffer in that size. + // note that this call will read only from the still buffered data + qint64 haveRead = replyPrivate->readBodyVeryFast(m_socket, replyPrivate->userProvidedDownloadBuffer + replyPrivate->totalProgress); + if (haveRead > 0) { + bytes += haveRead; + replyPrivate->totalProgress += haveRead; + // the user will get notified of it via progress signal + emit m_reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); + } else if (haveRead == 0) { + // Happens since this called in a loop. Currently no bytes available. + } else if (haveRead < 0) { + m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::RemoteHostClosedError); + break; + } + } else if (!replyPrivate->isChunked() && !replyPrivate->autoDecompress + && replyPrivate->bodyLength > 0) { + // bulk files like images should fulfill these properties and + // we can therefore save on memory copying + qint64 haveRead = replyPrivate->readBodyFast(m_socket, &replyPrivate->responseData); + bytes += haveRead; + replyPrivate->totalProgress += haveRead; + if (replyPrivate->shouldEmitSignals()) { + emit m_reply->readyRead(); + emit m_reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); + } + } + else + { + // use the traditional slower reading (for compressed encoding, chunked encoding, + // no content-length etc) + qint64 haveRead = replyPrivate->readBody(m_socket, &replyPrivate->responseData); + if (haveRead > 0) { + bytes += haveRead; + replyPrivate->totalProgress += haveRead; + if (replyPrivate->shouldEmitSignals()) { + emit m_reply->readyRead(); + emit m_reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); + } + } else if (haveRead == -1) { + // Some error occurred + m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::ProtocolFailure); + break; + } + } + // still in ReadingDataState? This function will be called again by the socket's readyRead + if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) + break; + + // everything done, fall through + } + case QHttpNetworkReplyPrivate::AllDoneState: + m_channel->allDone(); + break; + default: + break; + } + } while (bytes != lastBytes && m_reply); +} + +void QHttpProtocolHandler::_q_readyRead() +{ + if (m_socket->state() == QAbstractSocket::ConnectedState && m_socket->bytesAvailable() == 0) { + // We got a readyRead but no bytes are available.. + // This happens for the Unbuffered QTcpSocket + // Also check if socket is in ConnectedState since + // this function may also be invoked via the event loop. + char c; + qint64 ret = m_socket->peek(&c, 1); + if (ret < 0) { + m_channel->_q_error(m_socket->error()); + // We still need to handle the reply so it emits its signals etc. + if (m_reply) + _q_receiveReply(); + return; + } + } + + if (m_channel->isSocketWaiting() || m_channel->isSocketReading()) { + m_channel->state = QHttpNetworkConnectionChannel::ReadingState; + if (m_reply) + _q_receiveReply(); + } +} + +bool QHttpProtocolHandler::sendRequest() +{ + m_reply = m_channel->reply; + + if (!m_reply) { + // heh, how should that happen! + qWarning() << "QAbstractProtocolHandler::sendRequest() called without QHttpNetworkReply"; + m_channel->state = QHttpNetworkConnectionChannel::IdleState; + return false; + } + + switch (m_channel->state) { + case QHttpNetworkConnectionChannel::IdleState: { // write the header + if (!m_channel->ensureConnection()) { + // wait for the connection (and encryption) to be done + // sendRequest will be called again from either + // _q_connected or _q_encrypted + return false; + } + QString scheme = m_channel->request.url().scheme(); + if (scheme == QLatin1String("preconnect-http") + || scheme == QLatin1String("preconnect-https")) { + m_channel->state = QHttpNetworkConnectionChannel::IdleState; + m_reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; + m_channel->allDone(); + m_connection->preConnectFinished(); // will only decrease the counter + m_reply = 0; // so we can reuse this channel + return true; // we have a working connection and are done + } + + m_channel->written = 0; // excluding the header + m_channel->bytesTotal = 0; + + QHttpNetworkReplyPrivate *replyPrivate = m_reply->d_func(); + replyPrivate->clear(); + replyPrivate->connection = m_connection; + replyPrivate->connectionChannel = m_channel; + replyPrivate->autoDecompress = m_channel->request.d->autoDecompress; + replyPrivate->pipeliningUsed = false; + + // if the url contains authentication parameters, use the new ones + // both channels will use the new authentication parameters + if (!m_channel->request.url().userInfo().isEmpty() && m_channel->request.withCredentials()) { + QUrl url = m_channel->request.url(); + QAuthenticator &auth = m_channel->authenticator; + if (url.userName() != auth.user() + || (!url.password().isEmpty() && url.password() != auth.password())) { + auth.setUser(url.userName()); + auth.setPassword(url.password()); + m_connection->d_func()->copyCredentials(m_connection->d_func()->indexOf(m_socket), &auth, false); + } + // clear the userinfo, since we use the same request for resending + // userinfo in url can conflict with the one in the authenticator + url.setUserInfo(QString()); + m_channel->request.setUrl(url); + } + // Will only be false if Qt WebKit is performing a cross-origin XMLHttpRequest + // and withCredentials has not been set to true. + if (m_channel->request.withCredentials()) + m_connection->d_func()->createAuthorization(m_socket, m_channel->request); +#ifndef QT_NO_NETWORKPROXY + QByteArray header = QHttpNetworkRequestPrivate::header(m_channel->request, + (m_connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy)); +#else + QByteArray header = QHttpNetworkRequestPrivate::header(m_channel->request, false); +#endif + m_socket->write(header); + // flushing is dangerous (QSslSocket calls transmit which might read or error) +// m_socket->flush(); + QNonContiguousByteDevice* uploadByteDevice = m_channel->request.uploadByteDevice(); + if (uploadByteDevice) { + // connect the signals so this function gets called again + QObject::connect(uploadByteDevice, SIGNAL(readyRead()), m_channel, SLOT(_q_uploadDataReadyRead())); + + m_channel->bytesTotal = m_channel->request.contentLength(); + + m_channel->state = QHttpNetworkConnectionChannel::WritingState; // start writing data + sendRequest(); //recurse + } else { + m_channel->state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response + sendRequest(); //recurse + } + + break; + } + case QHttpNetworkConnectionChannel::WritingState: + { + // write the data + QNonContiguousByteDevice* uploadByteDevice = m_channel->request.uploadByteDevice(); + if (!uploadByteDevice || m_channel->bytesTotal == m_channel->written) { + if (uploadByteDevice) + emit m_reply->dataSendProgress(m_channel->written, m_channel->bytesTotal); + m_channel->state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response + sendRequest(); // recurse + break; + } + + // only feed the QTcpSocket buffer when there is less than 32 kB in it + const qint64 socketBufferFill = 32*1024; + const qint64 socketWriteMaxSize = 16*1024; + + +#ifndef QT_NO_SSL + QSslSocket *sslSocket = qobject_cast<QSslSocket*>(m_socket); + // if it is really an ssl socket, check more than just bytesToWrite() + while ((m_socket->bytesToWrite() + (sslSocket ? sslSocket->encryptedBytesToWrite() : 0)) + <= socketBufferFill && m_channel->bytesTotal != m_channel->written) +#else + while (m_socket->bytesToWrite() <= socketBufferFill + && m_channel->bytesTotal != m_channel->written) +#endif + { + // get pointer to upload data + qint64 currentReadSize = 0; + qint64 desiredReadSize = qMin(socketWriteMaxSize, m_channel->bytesTotal - m_channel->written); + const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize); + + if (currentReadSize == -1) { + // premature eof happened + m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::UnknownNetworkError); + return false; + } else if (readPointer == 0 || currentReadSize == 0) { + // nothing to read currently, break the loop + break; + } else { + qint64 currentWriteSize = m_socket->write(readPointer, currentReadSize); + if (currentWriteSize == -1 || currentWriteSize != currentReadSize) { + // socket broke down + m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::UnknownNetworkError); + return false; + } else { + m_channel->written += currentWriteSize; + uploadByteDevice->advanceReadPointer(currentWriteSize); + + emit m_reply->dataSendProgress(m_channel->written, m_channel->bytesTotal); + + if (m_channel->written == m_channel->bytesTotal) { + // make sure this function is called once again + m_channel->state = QHttpNetworkConnectionChannel::WaitingState; + sendRequest(); + break; + } + } + } + } + break; + } + + case QHttpNetworkConnectionChannel::WaitingState: + { + QNonContiguousByteDevice* uploadByteDevice = m_channel->request.uploadByteDevice(); + if (uploadByteDevice) { + QObject::disconnect(uploadByteDevice, SIGNAL(readyRead()), m_channel, SLOT(_q_uploadDataReadyRead())); + } + + // HTTP pipelining + //m_connection->d_func()->fillPipeline(m_socket); + //m_socket->flush(); + + // ensure we try to receive a reply in all cases, even if _q_readyRead_ hat not been called + // this is needed if the sends an reply before we have finished sending the request. In that + // case receiveReply had been called before but ignored the server reply + if (m_socket->bytesAvailable()) + QMetaObject::invokeMethod(m_channel, "_q_receiveReply", Qt::QueuedConnection); + break; + } + case QHttpNetworkConnectionChannel::ReadingState: + // ignore _q_bytesWritten in these states + // fall through + default: + break; + } + return true; +} + +QT_END_NAMESPACE + +#endif // QT_NO_HTTP diff --git a/src/network/access/qhttpprotocolhandler_p.h b/src/network/access/qhttpprotocolhandler_p.h new file mode 100644 index 0000000000..2bbc044b6c --- /dev/null +++ b/src/network/access/qhttpprotocolhandler_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHTTPPROTOCOLHANDLER_H +#define QHTTPPROTOCOLHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the Network Access API. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qabstractprotocolhandler_p.h> + +#ifndef QT_NO_HTTP + +QT_BEGIN_NAMESPACE + +class QHttpProtocolHandler : public QAbstractProtocolHandler { +public: + QHttpProtocolHandler(QHttpNetworkConnectionChannel *channel); + +private: + virtual void _q_receiveReply() Q_DECL_OVERRIDE; + virtual void _q_readyRead() Q_DECL_OVERRIDE; + virtual bool sendRequest() Q_DECL_OVERRIDE; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_HTTP + +#endif diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp index ee3911c72c..b7e8bb3f72 100644 --- a/src/network/access/qhttpthreaddelegate.cpp +++ b/src/network/access/qhttpthreaddelegate.cpp @@ -60,6 +60,10 @@ static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QNetworkReply::NetworkError code; // we've got an error switch (httpStatusCode) { + case 400: // Bad Request + code = QNetworkReply::ProtocolInvalidOperationError; + break; + case 401: // Authorization required code = QNetworkReply::AuthenticationRequiredError; break; @@ -80,15 +84,34 @@ static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const code = QNetworkReply::ProxyAuthenticationRequiredError; break; + case 409: // Resource Conflict + code = QNetworkReply::ContentConflictError; + break; + + case 410: // Content no longer available + code = QNetworkReply::ContentGoneError; + break; + case 418: // I'm a teapot code = QNetworkReply::ProtocolInvalidOperationError; break; + case 500: // Internal Server Error + code = QNetworkReply::InternalServerError; + break; + + case 501: // Server does not support this functionality + code = QNetworkReply::OperationNotImplementedError; + break; + + case 503: // Service unavailable + code = QNetworkReply::ServiceUnavailableError; + break; default: if (httpStatusCode > 500) { // some kind of server error - code = QNetworkReply::ProtocolUnknownError; + code = QNetworkReply::UnknownServerError; } else if (httpStatusCode >= 400) { // content error we did not handle above code = QNetworkReply::UnknownContentError; diff --git a/src/network/access/qnetworkcookie.cpp b/src/network/access/qnetworkcookie.cpp index a871c04d56..664bc8282c 100644 --- a/src/network/access/qnetworkcookie.cpp +++ b/src/network/access/qnetworkcookie.cpp @@ -51,6 +51,7 @@ #include "QtCore/qstring.h" #include "QtCore/qstringlist.h" #include "QtCore/qurl.h" +#include "QtNetwork/qhostaddress.h" #include "private/qobject_p.h" QT_BEGIN_NAMESPACE @@ -466,12 +467,19 @@ QByteArray QNetworkCookie::toRawForm(RawForm form) const } if (!d->domain.isEmpty()) { result += "; domain="; - QString domainNoDot = d->domain; - if (domainNoDot.startsWith(QLatin1Char('.'))) { + if (d->domain.startsWith(QLatin1Char('.'))) { result += '.'; - domainNoDot = domainNoDot.mid(1); + result += QUrl::toAce(d->domain.mid(1)); + } else { + QHostAddress hostAddr(d->domain); + if (hostAddr.protocol() == QAbstractSocket::IPv6Protocol) { + result += '['; + result += d->domain.toUtf8(); + result += ']'; + } else { + result += QUrl::toAce(d->domain); + } } - result += QUrl::toAce(domainNoDot); } if (!d->path.isEmpty()) { result += "; path="; @@ -1015,14 +1023,20 @@ void QNetworkCookie::normalize(const QUrl &url) d->path = defaultPath; } - if (d->domain.isEmpty()) + if (d->domain.isEmpty()) { d->domain = url.host(); - else if (!d->domain.startsWith(QLatin1Char('.'))) - // Ensure the domain starts with a dot if its field was not empty - // in the HTTP header. There are some servers that forget the - // leading dot and this is actually forbidden according to RFC 2109, - // but all browsers accept it anyway so we do that as well. - d->domain.prepend(QLatin1Char('.')); + } else { + QHostAddress hostAddress(d->domain); + if (hostAddress.protocol() != QAbstractSocket::IPv4Protocol + && hostAddress.protocol() != QAbstractSocket::IPv6Protocol + && !d->domain.startsWith(QLatin1Char('.'))) { + // Ensure the domain starts with a dot if its field was not empty + // in the HTTP header. There are some servers that forget the + // leading dot and this is actually forbidden according to RFC 2109, + // but all browsers accept it anyway so we do that as well. + d->domain.prepend(QLatin1Char('.')); + } + } } #ifndef QT_NO_DEBUG_STREAM diff --git a/src/network/access/qnetworkreply.cpp b/src/network/access/qnetworkreply.cpp index ba6f706f7a..faa8464463 100644 --- a/src/network/access/qnetworkreply.cpp +++ b/src/network/access/qnetworkreply.cpp @@ -173,6 +173,21 @@ QNetworkReplyPrivate::QNetworkReplyPrivate() again, but this failed for example because the upload data could not be read a second time. + \value ContentConflictError the request could not be completed due + to a conflict with the current state of the resource. + + \value ContentGoneError the requested resource is no longer + available at the server. + + \value InternalServerError the server encountered an unexpected + condition which prevented it from fulfilling the request. + + \value OperationNotImplementedError the server does not support the + functionality required to fulfill the request. + + \value ServiceUnavailableError the server is unable to handle the + request at this time. + \value ProtocolUnknownError the Network Access API cannot honor the request because the protocol is not known @@ -191,6 +206,9 @@ QNetworkReplyPrivate::QNetworkReplyPrivate() \value ProtocolFailure a breakdown in protocol was detected (parsing error, invalid or unexpected responses, etc.) + \value UnknownServerError an unknown error related to + the server response was detected + \sa error() */ diff --git a/src/network/access/qnetworkreply.h b/src/network/access/qnetworkreply.h index a7db2d189c..f11a5e816a 100644 --- a/src/network/access/qnetworkreply.h +++ b/src/network/access/qnetworkreply.h @@ -93,12 +93,20 @@ public: ContentNotFoundError, AuthenticationRequiredError, ContentReSendError, + ContentConflictError, + ContentGoneError, UnknownContentError = 299, // protocol errors ProtocolUnknownError = 301, ProtocolInvalidOperationError, - ProtocolFailure = 399 + ProtocolFailure = 399, + + // Server side errors (401-499) + InternalServerError = 401, + OperationNotImplementedError, + ServiceUnavailableError, + UnknownServerError = 499 }; ~QNetworkReply(); diff --git a/src/network/doc/snippets/network/tcpwait.cpp b/src/network/doc/snippets/network/tcpwait.cpp index fb44e2ded9..e5e4c1ed40 100644 --- a/src/network/doc/snippets/network/tcpwait.cpp +++ b/src/network/doc/snippets/network/tcpwait.cpp @@ -55,13 +55,13 @@ int main(int argv, char **args) char buffer[50]; forever { - numRead = socket.read(buffer, 50); + numRead = socket.read(buffer, 50); - // do whatever with array + // do whatever with array - numReadTotal += numRead; + numReadTotal += numRead; if (numRead == 0 && !socket.waitForReadyRead()) - break; + break; } //! [0] diff --git a/src/network/kernel/kernel.pri b/src/network/kernel/kernel.pri index 97f52fdb6e..9b584be206 100644 --- a/src/network/kernel/kernel.pri +++ b/src/network/kernel/kernel.pri @@ -33,9 +33,17 @@ android { } win32: { - HEADERS += kernel/qnetworkinterface_win_p.h - SOURCES += kernel/qdnslookup_win.cpp kernel/qhostinfo_win.cpp kernel/qnetworkinterface_win.cpp - LIBS_PRIVATE += -ldnsapi + !winrt { + HEADERS += kernel/qnetworkinterface_win_p.h + SOURCES += kernel/qdnslookup_win.cpp \ + kernel/qhostinfo_win.cpp \ + kernel/qnetworkinterface_win.cpp + LIBS_PRIVATE += -ldnsapi + } else { + SOURCES += kernel/qdnslookup_winrt.cpp \ + kernel/qhostinfo_winrt.cpp \ + kernel/qnetworkinterface_winrt.cpp + } } integrity:SOURCES += kernel/qdnslookup_unix.cpp kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp diff --git a/src/network/kernel/qauthenticator.cpp b/src/network/kernel/qauthenticator.cpp index 8c16486878..edbbbf5a75 100644 --- a/src/network/kernel/qauthenticator.cpp +++ b/src/network/kernel/qauthenticator.cpp @@ -55,9 +55,11 @@ #include <qmutex.h> #include <private/qmutexpool_p.h> #include <rpc.h> +#ifndef Q_OS_WINRT #define SECURITY_WIN32 1 #include <security.h> #endif +#endif //#define NTLMV1_CLIENT @@ -69,7 +71,7 @@ QT_BEGIN_NAMESPACE static QByteArray qNtlmPhase1(); static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data); -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) static QByteArray qNtlmPhase1_SSPI(QAuthenticatorPrivate *ctx); static QByteArray qNtlmPhase3_SSPI(QAuthenticatorPrivate *ctx, const QByteArray& phase2data); #endif @@ -328,7 +330,7 @@ bool QAuthenticator::isNull() const return !d; } -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) class QNtlmWindowsHandles { public: @@ -340,7 +342,7 @@ public: QAuthenticatorPrivate::QAuthenticatorPrivate() : method(None) - #ifdef Q_OS_WIN + #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) , ntlmWindowsHandles(0) #endif , hasFailed(false) @@ -354,7 +356,7 @@ QAuthenticatorPrivate::QAuthenticatorPrivate() QAuthenticatorPrivate::~QAuthenticatorPrivate() { -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) if (ntlmWindowsHandles) delete ntlmWindowsHandles; #endif @@ -485,7 +487,7 @@ QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMet case QAuthenticatorPrivate::Ntlm: methodString = "NTLM "; if (challenge.isEmpty()) { -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) QByteArray phase1Token; if (user.isEmpty()) // Only pull from system if no user was specified in authenticator phase1Token = qNtlmPhase1_SSPI(this); @@ -502,7 +504,7 @@ QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMet phase = Phase2; } } else { -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) QByteArray phase3Token; if (ntlmWindowsHandles) phase3Token = qNtlmPhase3_SSPI(this, QByteArray::fromBase64(challenge)); @@ -1475,7 +1477,7 @@ static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phas return rc; } -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) // See http://davenport.sourceforge.net/ntlm.html // and libcurl http_ntlm.c @@ -1513,7 +1515,6 @@ static bool q_NTLM_SSPI_library_load() return true; } -#ifdef Q_OS_WIN // Phase 1: static QByteArray qNtlmPhase1_SSPI(QAuthenticatorPrivate *ctx) { @@ -1631,8 +1632,6 @@ static QByteArray qNtlmPhase3_SSPI(QAuthenticatorPrivate *ctx, const QByteArray& return result; } -#endif // Q_OS_WIN - -#endif +#endif // Q_OS_WIN && !Q_OS_WINRT QT_END_NAMESPACE diff --git a/src/network/kernel/qdnslookup.cpp b/src/network/kernel/qdnslookup.cpp index 53675d083b..97a402901e 100644 --- a/src/network/kernel/qdnslookup.cpp +++ b/src/network/kernel/qdnslookup.cpp @@ -363,6 +363,25 @@ void QDnsLookup::setType(Type type) } /*! + \property QDnsLookup::nameserver + \brief the nameserver to use for DNS lookup. +*/ + +QHostAddress QDnsLookup::nameserver() const +{ + return d_func()->nameserver; +} + +void QDnsLookup::setNameserver(const QHostAddress &nameserver) +{ + Q_D(QDnsLookup); + if (nameserver != d->nameserver) { + d->nameserver = nameserver; + emit nameserverChanged(nameserver); + } +} + +/*! Returns the list of canonical name records associated with this lookup. */ @@ -463,7 +482,7 @@ void QDnsLookup::lookup() Q_D(QDnsLookup); d->isFinished = false; d->reply = QDnsLookupReply(); - d->runnable = new QDnsLookupRunnable(d->type, QUrl::toAce(d->name)); + d->runnable = new QDnsLookupRunnable(d->type, QUrl::toAce(d->name), d->nameserver); connect(d->runnable, SIGNAL(finished(QDnsLookupReply)), this, SLOT(_q_lookupFinished(QDnsLookupReply)), Qt::BlockingQueuedConnection); @@ -971,7 +990,7 @@ void QDnsLookupRunnable::run() } // Perform request. - query(requestType, requestName, &reply); + query(requestType, requestName, nameserver, &reply); // Sort results. if (!theDnsLookupSeedStorage()->hasLocalData()) { diff --git a/src/network/kernel/qdnslookup.h b/src/network/kernel/qdnslookup.h index 1df21d866e..ffbef61f92 100644 --- a/src/network/kernel/qdnslookup.h +++ b/src/network/kernel/qdnslookup.h @@ -180,6 +180,7 @@ class Q_NETWORK_EXPORT QDnsLookup : public QObject Q_PROPERTY(QString errorString READ errorString NOTIFY finished) Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(Type type READ type WRITE setType NOTIFY typeChanged) + Q_PROPERTY(QHostAddress nameserver READ nameserver WRITE setNameserver NOTIFY nameserverChanged) public: enum Error @@ -209,6 +210,7 @@ public: explicit QDnsLookup(QObject *parent = 0); QDnsLookup(Type type, const QString &name, QObject *parent = 0); + QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, QObject *parent = 0); ~QDnsLookup(); Error error() const; @@ -221,6 +223,9 @@ public: Type type() const; void setType(QDnsLookup::Type); + QHostAddress nameserver() const; + void setNameserver(const QHostAddress &nameserver); + QList<QDnsDomainNameRecord> canonicalNameRecords() const; QList<QDnsHostAddressRecord> hostAddressRecords() const; QList<QDnsMailExchangeRecord> mailExchangeRecords() const; @@ -238,6 +243,7 @@ Q_SIGNALS: void finished(); void nameChanged(const QString &name); void typeChanged(Type type); + void nameserverChanged(const QHostAddress &nameserver); private: Q_DECLARE_PRIVATE(QDnsLookup) diff --git a/src/network/kernel/qdnslookup_android.cpp b/src/network/kernel/qdnslookup_android.cpp index dff81dba0c..1ace5727c2 100644 --- a/src/network/kernel/qdnslookup_android.cpp +++ b/src/network/kernel/qdnslookup_android.cpp @@ -43,10 +43,11 @@ QT_BEGIN_NAMESPACE -void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply) +void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply) { Q_UNUSED(requestType); Q_UNUSED(requestName); + Q_UNUSED(nameserver); Q_UNUSED(reply); qWarning() << Q_FUNC_INFO << "Not yet supported on Android"; reply->error = QDnsLookup::ResolverError; diff --git a/src/network/kernel/qdnslookup_p.h b/src/network/kernel/qdnslookup_p.h index 692b9088fe..7c0b0e862a 100644 --- a/src/network/kernel/qdnslookup_p.h +++ b/src/network/kernel/qdnslookup_p.h @@ -100,6 +100,7 @@ public: bool isFinished; QString name; QDnsLookup::Type type; + QHostAddress nameserver; QDnsLookupReply reply; QDnsLookupRunnable *runnable; @@ -111,9 +112,10 @@ class QDnsLookupRunnable : public QObject, public QRunnable Q_OBJECT public: - QDnsLookupRunnable(QDnsLookup::Type type, const QByteArray &name) + QDnsLookupRunnable(QDnsLookup::Type type, const QByteArray &name, const QHostAddress &nameserver) : requestType(type) , requestName(name) + , nameserver(nameserver) { } void run(); @@ -121,9 +123,10 @@ signals: void finished(const QDnsLookupReply &reply); private: - static void query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply); + static void query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply); QDnsLookup::Type requestType; QByteArray requestName; + QHostAddress nameserver; }; class QDnsLookupThreadPool : public QThreadPool diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp index 9fb488cee6..26834dff57 100644 --- a/src/network/kernel/qdnslookup_unix.cpp +++ b/src/network/kernel/qdnslookup_unix.cpp @@ -115,7 +115,7 @@ static void resolveLibrary() local_res_nquery = res_nquery_proto(lib.resolve("res_nquery")); } -void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply) +void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply) { // Load dn_expand, res_ninit and res_nquery on demand. static QBasicAtomicInt triedResolve = Q_BASIC_ATOMIC_INITIALIZER(false); @@ -142,6 +142,43 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN reply->errorString = tr("Resolver initialization failed"); return; } + + //Check if a nameserver was set. If so, use it + if (!nameserver.isNull()) { + if (nameserver.protocol() == QAbstractSocket::IPv4Protocol) { + state.nsaddr_list[0].sin_addr.s_addr = htonl(nameserver.toIPv4Address()); + state.nscount = 1; + } else if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) { +#if defined(Q_OS_LINUX) + struct sockaddr_in6 *ns; + ns = state._u._ext.nsaddrs[0]; + // nsaddrs will be NULL if no nameserver is set in /etc/resolv.conf + if (!ns) { + // Memory allocated here will be free'd in res_close() as we + // have done res_init() above. + ns = (struct sockaddr_in6*) calloc(1, sizeof(struct sockaddr_in6)); + Q_CHECK_PTR(ns); + state._u._ext.nsaddrs[0] = ns; + } + // Set nsmap[] to indicate that nsaddrs[0] is an IPv6 address + // See: https://sourceware.org/ml/libc-hacker/2002-05/msg00035.html + state._u._ext.nsmap[0] = MAXNS + 1; + state._u._ext.nscount6 = 1; + ns->sin6_family = AF_INET6; + ns->sin6_port = htons(53); + + Q_IPV6ADDR ipv6Address = nameserver.toIPv6Address(); + for (int i=0; i<16; i++) { + ns->sin6_addr.s6_addr[i] = ipv6Address[i]; + } +#else + qWarning() << Q_FUNC_INFO << "IPv6 addresses for nameservers is currently not supported"; + reply->error = QDnsLookup::ResolverError; + reply->errorString = tr("IPv6 addresses for nameservers is currently not supported"); + return; +#endif + } + } #ifdef QDNSLOOKUP_DEBUG state.options |= RES_DEBUG; #endif diff --git a/src/network/kernel/qdnslookup_win.cpp b/src/network/kernel/qdnslookup_win.cpp index bf80a23297..6f58e64440 100644 --- a/src/network/kernel/qdnslookup_win.cpp +++ b/src/network/kernel/qdnslookup_win.cpp @@ -48,15 +48,33 @@ #include <qt_windows.h> #include <windns.h> +#include <memory.h> QT_BEGIN_NAMESPACE -void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply) +void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply) { // Perform DNS query. PDNS_RECORD dns_records = 0; const QString requestNameUtf16 = QString::fromUtf8(requestName.data(), requestName.size()); - const DNS_STATUS status = DnsQuery_W(reinterpret_cast<const wchar_t*>(requestNameUtf16.utf16()), requestType, DNS_QUERY_STANDARD, NULL, &dns_records, NULL); + IP4_ARRAY srvList; + memset(&srvList, 0, sizeof(IP4_ARRAY)); + if (!nameserver.isNull()) { + if (nameserver.protocol() == QAbstractSocket::IPv4Protocol) { + // The below code is referenced from: http://support.microsoft.com/kb/831226 + srvList.AddrCount = 1; + srvList.AddrArray[0] = htonl(nameserver.toIPv4Address()); + } else if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) { + // For supoprting IPv6 nameserver addresses, we'll need to switch + // from DnsQuey() to DnsQueryEx() as it supports passing an IPv6 + // address in the nameserver list + qWarning() << Q_FUNC_INFO << "IPv6 addresses for nameservers is currently not supported"; + reply->error = QDnsLookup::ResolverError; + reply->errorString = tr("IPv6 addresses for nameservers is currently not supported"); + return; + } + } + const DNS_STATUS status = DnsQuery_W(reinterpret_cast<const wchar_t*>(requestNameUtf16.utf16()), requestType, DNS_QUERY_STANDARD, &srvList, &dns_records, NULL); switch (status) { case ERROR_SUCCESS: break; diff --git a/src/network/kernel/qdnslookup_winrt.cpp b/src/network/kernel/qdnslookup_winrt.cpp new file mode 100644 index 0000000000..6ac944934a --- /dev/null +++ b/src/network/kernel/qdnslookup_winrt.cpp @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdnslookup_p.h" + +#include <qurl.h> +#include <qdebug.h> + +#include <wrl.h> +#include <windows.foundation.h> +#include <windows.foundation.collections.h> +#include <windows.networking.h> +#include <windows.networking.sockets.h> + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Networking; +using namespace ABI::Windows::Networking::Connectivity; +using namespace ABI::Windows::Networking::Sockets; + +QT_BEGIN_NAMESPACE + +void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply) +{ + // TODO: Add nameserver support for winRT + if (!nameserver.isNull()) + qWarning() << "Ignoring nameserver as its currently not supported on WinRT"; + + // TODO: is there any way to do "proper" dns lookup? + if (requestType != QDnsLookup::A && requestType != QDnsLookup::AAAA + && requestType != QDnsLookup::ANY) { + reply->error = QDnsLookup::InvalidRequestError; + reply->errorString = QLatin1String("WinRT only supports IPv4 and IPv6 requests"); + return; + } + + QString aceHostname = QUrl::fromAce(requestName); + if (aceHostname.isEmpty()) { + reply->error = QDnsLookup::InvalidRequestError; + reply->errorString = requestName.isEmpty() ? tr("No hostname given") : tr("Invalid hostname"); + return; + } + + IHostNameFactory *hostnameFactory; + + HStringReference classId(RuntimeClass_Windows_Networking_HostName); + if (FAILED(GetActivationFactory(classId.Get(), &hostnameFactory))) { + reply->error = QDnsLookup::ResolverError; + reply->errorString = QLatin1String("Could not obtain hostname factory"); + return; + } + IHostName *host; + HStringReference hostNameRef((const wchar_t*)aceHostname.utf16()); + hostnameFactory->CreateHostName(hostNameRef.Get(), &host); + hostnameFactory->Release(); + + IDatagramSocketStatics *datagramSocketStatics; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_DatagramSocket).Get(), &datagramSocketStatics); + + IAsyncOperation<IVectorView<EndpointPair*> *> *op; + HSTRING proto; + WindowsCreateString(L"0", 1, &proto); + datagramSocketStatics->GetEndpointPairsAsync(host, proto, &op); + datagramSocketStatics->Release(); + host->Release(); + + IVectorView<EndpointPair*> *endpointPairs = 0; + HRESULT hr = op->GetResults(&endpointPairs); + int waitCount = 0; + while (hr == E_ILLEGAL_METHOD_CALL) { + WaitForSingleObjectEx(GetCurrentThread(), 50, FALSE); + hr = op->GetResults(&endpointPairs); + if (++waitCount > 1200) // Wait for 1 minute max + return; + } + op->Release(); + + if (!endpointPairs) + return; + + unsigned int size; + endpointPairs->get_Size(&size); + for (unsigned int i = 0; i < size; ++i) { + IEndpointPair *endpointpair; + endpointPairs->GetAt(i, &endpointpair); + IHostName *remoteHost; + endpointpair->get_RemoteHostName(&remoteHost); + endpointpair->Release(); + HostNameType type; + remoteHost->get_Type(&type); + if (type == HostNameType_Bluetooth || type == HostNameType_DomainName + || (requestType != QDnsLookup::ANY + && ((type == HostNameType_Ipv4 && requestType == QDnsLookup::AAAA) + || (type == HostNameType_Ipv6 && requestType == QDnsLookup::A)))) + continue; + + HSTRING name; + remoteHost->get_CanonicalName(&name); + remoteHost->Release(); + UINT32 length; + PCWSTR rawString = WindowsGetStringRawBuffer(name, &length); + QDnsHostAddressRecord record; + record.d->name = aceHostname; + record.d->value = QHostAddress(QString::fromWCharArray(rawString, length)); + reply->hostAddressRecords.append(record); + } +} + +QT_END_NAMESPACE diff --git a/src/network/kernel/qhostaddress.cpp b/src/network/kernel/qhostaddress.cpp index 18fd6dee58..0ab72191dc 100644 --- a/src/network/kernel/qhostaddress.cpp +++ b/src/network/kernel/qhostaddress.cpp @@ -71,7 +71,7 @@ QT_BEGIN_NAMESPACE // sockaddr_in6 size changed between old and new SDK // Only the new version is the correct one, so always // use this structure. -#if defined(Q_OS_WINCE) +#if defined(Q_OS_WINCE) || defined(Q_OS_WINRT) # if !defined(u_char) # define u_char unsigned char # endif @@ -448,10 +448,12 @@ QHostAddress::QHostAddress(const QString &address) QHostAddress::QHostAddress(const struct sockaddr *sockaddr) : d(new QHostAddressPrivate) { +#ifndef Q_OS_WINRT if (sockaddr->sa_family == AF_INET) setAddress(htonl(((sockaddr_in *)sockaddr)->sin_addr.s_addr)); else if (sockaddr->sa_family == AF_INET6) setAddress(((qt_sockaddr_in6 *)sockaddr)->sin6_addr.qt_s6_addr); +#endif } /*! @@ -604,11 +606,13 @@ bool QHostAddress::setAddress(const QString &address) */ void QHostAddress::setAddress(const struct sockaddr *sockaddr) { +#ifndef Q_OS_WINRT clear(); if (sockaddr->sa_family == AF_INET) setAddress(htonl(((sockaddr_in *)sockaddr)->sin_addr.s_addr)); else if (sockaddr->sa_family == AF_INET6) setAddress(((qt_sockaddr_in6 *)sockaddr)->sin6_addr.qt_s6_addr); +#endif } /*! diff --git a/src/network/kernel/qhostinfo.cpp b/src/network/kernel/qhostinfo.cpp index d25372ece6..025e3f3e00 100644 --- a/src/network/kernel/qhostinfo.cpp +++ b/src/network/kernel/qhostinfo.cpp @@ -508,7 +508,7 @@ QHostInfoLookupManager::QHostInfoLookupManager() : mutex(QMutex::Recursive), was { moveToThread(QCoreApplicationPrivate::mainThread()); connect(QCoreApplication::instance(), SIGNAL(destroyed()), SLOT(waitForThreadPoolDone()), Qt::DirectConnection); - threadPool.setMaxThreadCount(5); // do 5 DNS lookups in parallel + threadPool.setMaxThreadCount(20); // do up to 20 DNS lookups in parallel } QHostInfoLookupManager::~QHostInfoLookupManager() diff --git a/src/network/kernel/qhostinfo_unix.cpp b/src/network/kernel/qhostinfo_unix.cpp index 32b7318335..df8c8b145a 100644 --- a/src/network/kernel/qhostinfo_unix.cpp +++ b/src/network/kernel/qhostinfo_unix.cpp @@ -259,10 +259,10 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName) } else if (result == EAI_NONAME || result == EAI_FAIL #ifdef EAI_NODATA - // EAI_NODATA is deprecated in RFC 3493 - || result == EAI_NODATA + // EAI_NODATA is deprecated in RFC 3493 + || result == EAI_NODATA #endif - ) { + ) { results.setError(QHostInfo::HostNotFound); results.setErrorString(tr("Host not found")); } else { diff --git a/src/network/kernel/qhostinfo_winrt.cpp b/src/network/kernel/qhostinfo_winrt.cpp new file mode 100644 index 0000000000..928c9e4628 --- /dev/null +++ b/src/network/kernel/qhostinfo_winrt.cpp @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qhostinfo_p.h" + +#include <qurl.h> + +#include <ppltasks.h> +#include <wrl.h> +#include <windows.networking.h> +#include <windows.networking.sockets.h> +#include <windows.networking.connectivity.h> +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Networking; +using namespace ABI::Windows::Networking::Connectivity; +using namespace ABI::Windows::Networking::Sockets; + +QT_BEGIN_NAMESPACE + +//#define QHOSTINFO_DEBUG + +QHostInfo QHostInfoAgent::fromName(const QString &hostName) +{ + QHostInfo results; + + QHostAddress address; + if (address.setAddress(hostName)) { + // Reverse lookup + // TODO: is there a replacement for getnameinfo for winrt? + Q_UNIMPLEMENTED(); + return results; + } + + QByteArray aceHostname = QUrl::toAce(hostName); + results.setHostName(hostName); + if (aceHostname.isEmpty()) { + results.setError(QHostInfo::HostNotFound); + results.setErrorString(hostName.isEmpty() ? tr("No host name given") : tr("Invalid hostname")); + return results; + } + + IHostNameFactory *hostnameFactory; + + HStringReference classId(RuntimeClass_Windows_Networking_HostName); + if (FAILED(GetActivationFactory(classId.Get(), &hostnameFactory))) + Q_ASSERT(false, "Could not obtain hostname factory."); + + IHostName *host; + HStringReference hostNameRef((const wchar_t*)hostName.utf16()); + hostnameFactory->CreateHostName(hostNameRef.Get(), &host); + hostnameFactory->Release(); + + IDatagramSocketStatics *datagramSocketStatics; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_DatagramSocket).Get(), &datagramSocketStatics); + + IAsyncOperation<IVectorView<EndpointPair*> *> *op; + HSTRING proto; + WindowsCreateString(L"0", 1, &proto); + datagramSocketStatics->GetEndpointPairsAsync(host, proto, &op); + datagramSocketStatics->Release(); + host->Release(); + + IVectorView<EndpointPair*> *endpointPairs = 0; + HRESULT hr = op->GetResults(&endpointPairs); + int waitCount = 0; + while (hr == E_ILLEGAL_METHOD_CALL) { + WaitForSingleObjectEx(GetCurrentThread(), 50, FALSE); + hr = op->GetResults(&endpointPairs); + if (++waitCount > 1200) // Wait for 1 minute max + return results; + } + op->Release(); + + if (!endpointPairs) + return results; + + unsigned int size; + endpointPairs->get_Size(&size); + QList<QHostAddress> addresses; + for (unsigned int i = 0; i < size; ++i) { + IEndpointPair *endpointpair; + endpointPairs->GetAt(i, &endpointpair); + IHostName *remoteHost; + endpointpair->get_RemoteHostName(&remoteHost); + endpointpair->Release(); + if (!remoteHost) + continue; + HostNameType type; + remoteHost->get_Type(&type); + if (type == HostNameType_DomainName) + continue; + + HSTRING name; + remoteHost->get_CanonicalName(&name); + remoteHost->Release(); + UINT32 length; + PCWSTR rawString = WindowsGetStringRawBuffer(name, &length); + QHostAddress addr; + addr.setAddress(QString::fromWCharArray(rawString, length)); + if (!addresses.contains(addr)) + addresses.append(addr); + } + results.setAddresses(addresses); + + return results; +} + +QString QHostInfo::localHostName() +{ + INetworkInformationStatics *statics; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_Connectivity_NetworkInformation).Get(), &statics); + + IVectorView<HostName*> *hostNames = 0; + statics->GetHostNames(&hostNames); + statics->Release(); + if (!hostNames) + return QString(); + + unsigned int size; + hostNames->get_Size(&size); + if (size == 0) + return QString(); + + for (unsigned int i = 0; i < size; ++i) { + IHostName *hostName; + hostNames->GetAt(i, &hostName); + HostNameType type; + hostName->get_Type(&type); + if (type != HostNameType_DomainName) + continue; + + HSTRING name; + hostName->get_CanonicalName(&name); + hostName->Release(); + UINT32 length; + PCWSTR rawString = WindowsGetStringRawBuffer(name, &length); + return QString::fromWCharArray(rawString, length); + } + IHostName *firstHost; + hostNames->GetAt(0, &firstHost); + hostNames->Release(); + + HSTRING name; + firstHost->get_CanonicalName(&name); + firstHost->Release(); + UINT32 length; + PCWSTR rawString = WindowsGetStringRawBuffer(name, &length); + return QString::fromWCharArray(rawString, length); +} + +// QString QHostInfo::localDomainName() defined in qnetworkinterface_win.cpp + +QT_END_NAMESPACE diff --git a/src/network/kernel/qnetworkinterface_unix.cpp b/src/network/kernel/qnetworkinterface_unix.cpp index d3c830a66f..d0e2eca1e0 100644 --- a/src/network/kernel/qnetworkinterface_unix.cpp +++ b/src/network/kernel/qnetworkinterface_unix.cpp @@ -228,7 +228,7 @@ static QNetworkInterfacePrivate *findInterface(int socket, QList<QNetworkInterfa memcpy(req.ifr_name, oldName, qMin<int>(oldName.length() + 1, sizeof(req.ifr_name) - 1)); } else #endif - { + { // use this name anyways iface->name = QString::fromLatin1(req.ifr_name); } diff --git a/src/network/kernel/qnetworkinterface_winrt.cpp b/src/network/kernel/qnetworkinterface_winrt.cpp new file mode 100644 index 0000000000..6a814c85d4 --- /dev/null +++ b/src/network/kernel/qnetworkinterface_winrt.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkinterface.h" +#include "qnetworkinterface_p.h" + +#ifndef QT_NO_NETWORKINTERFACE + +#include <wrl.h> +#include <windows.foundation.h> +#include <windows.foundation.collections.h> +#include <windows.networking.h> +#include <windows.networking.connectivity.h> + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Networking; +using namespace ABI::Windows::Networking::Connectivity; + +#include <qhostinfo.h> + +QT_BEGIN_NAMESPACE + +struct HostNameInfo { + GUID adapterId; + unsigned char prefixLength; + QString address; +}; + +static QList<QNetworkInterfacePrivate *> interfaceListing() +{ + QList<QNetworkInterfacePrivate *> interfaces; + + QList<HostNameInfo> hostList; + + INetworkInformationStatics *hostNameStatics; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_Connectivity_NetworkInformation).Get(), &hostNameStatics); + + IVectorView<HostName*> *hostNames = 0; + hostNameStatics->GetHostNames(&hostNames); + hostNameStatics->Release(); + if (!hostNames) + return interfaces; + + unsigned int hostNameCount; + hostNames->get_Size(&hostNameCount); + for (unsigned i = 0; i < hostNameCount; ++i) { + HostNameInfo hostInfo; + IHostName *hostName; + hostNames->GetAt(i, &hostName); + + HostNameType type; + hostName->get_Type(&type); + if (type == HostNameType_DomainName) + continue; + + IIPInformation *ipInformation; + hostName->get_IPInformation(&ipInformation); + INetworkAdapter *currentAdapter; + ipInformation->get_NetworkAdapter(¤tAdapter); + + currentAdapter->get_NetworkAdapterId(&hostInfo.adapterId); + currentAdapter->Release(); + + IReference<unsigned char> *prefixLengthReference; + ipInformation->get_PrefixLength(&prefixLengthReference); + ipInformation->Release(); + + prefixLengthReference->get_Value(&hostInfo.prefixLength); + prefixLengthReference->Release(); + + // invalid prefixes + if ((type == HostNameType_Ipv4 && hostInfo.prefixLength > 32) + || (type == HostNameType_Ipv6 && hostInfo.prefixLength > 128)) + continue; + + HSTRING name; + hostName->get_CanonicalName(&name); + hostName->Release(); + UINT32 length; + PCWSTR rawString = WindowsGetStringRawBuffer(name, &length); + hostInfo.address = QString::fromWCharArray(rawString, length); + + hostList << hostInfo; + } + hostNames->Release(); + + INetworkInformationStatics *networkInfoStatics; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_Connectivity_NetworkInformation).Get(), &networkInfoStatics); + IVectorView<ConnectionProfile *> *connectionProfiles = 0; + networkInfoStatics->GetConnectionProfiles(&connectionProfiles); + networkInfoStatics->Release(); + if (!connectionProfiles) + return interfaces; + + unsigned int size; + connectionProfiles->get_Size(&size); + for (unsigned int i = 0; i < size; ++i) { + QNetworkInterfacePrivate *iface = new QNetworkInterfacePrivate; + interfaces << iface; + + IConnectionProfile *profile; + connectionProfiles->GetAt(i, &profile); + + NetworkConnectivityLevel connectivityLevel; + profile->GetNetworkConnectivityLevel(&connectivityLevel); + if (connectivityLevel != NetworkConnectivityLevel_None) + iface->flags = QNetworkInterface::IsUp | QNetworkInterface::IsRunning; + + INetworkAdapter *adapter; + profile->get_NetworkAdapter(&adapter); + profile->Release(); + UINT32 type; + adapter->get_IanaInterfaceType(&type); + if (type == 23) + iface->flags |= QNetworkInterface::IsPointToPoint; + GUID id; + adapter->get_NetworkAdapterId(&id); + adapter->Release(); + OLECHAR adapterName[39]={0}; + StringFromGUID2(id, adapterName, 39); + iface->name = QString::fromWCharArray(adapterName); + + // According to http://stackoverflow.com/questions/12936193/how-unique-is-the-ethernet-network-adapter-id-in-winrt-it-is-derived-from-the-m + // obtaining the MAC address using WinRT API is impossible + // iface->hardwareAddress = ? + + for (int i = 0; i < hostList.length(); ++i) { + const HostNameInfo hostInfo = hostList.at(i); + if (id != hostInfo.adapterId) + continue; + + QNetworkAddressEntry entry; + entry.setIp(QHostAddress(hostInfo.address)); + entry.setPrefixLength(hostInfo.prefixLength); + iface->addressEntries << entry; + + hostList.takeAt(i); + --i; + } + } + connectionProfiles->Release(); + return interfaces; +} + +QList<QNetworkInterfacePrivate *> QNetworkInterfaceManager::scan() +{ + return interfaceListing(); +} + +QString QHostInfo::localDomainName() +{ + return QString(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_NETWORKINTERFACE diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index bebdf728a7..0345537d1c 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -372,9 +372,22 @@ IP_MULTICAST_LOOP (multicast loopback) socket option. \value TypeOfServiceOption This option is not supported on - Windows. This maps to the IP_TOS socket option. + Windows. This maps to the IP_TOS socket option. For possible values, + see table below. - Possible values for the \e{TypeOfServiceOption} are: + \value SendBufferSizeSocketOption Sets the socket send buffer size + in bytes at the OS level. This maps to the SO_SNDBUF socket option. + This option does not affect the QIODevice or QAbstractSocket buffers. + This enum value has been introduced in Qt 5.3. + + \value ReceiveBufferSizeSocketOption Sets the socket receive + buffer size in bytes at the OS level. + This maps to the SO_RCVBUF socket option. + This option does not affect the QIODevice or QAbstractSocket buffers + (see \l{QAbstractSocket::}{setReadBufferSize()}). + This enum value has been introduced in Qt 5.3. + + Possible values for \e{TypeOfServiceOption} are: \table \header \li Value \li Description @@ -735,8 +748,8 @@ bool QAbstractSocketPrivate::canReadNotification() return true; } - if (!hasData && socketEngine) - socketEngine->setReadNotificationEnabled(true); + if (isBuffered && socketEngine) + socketEngine->setReadNotificationEnabled(readBufferMaxSize == 0 || readBufferMaxSize > q->bytesAvailable()); // reset the read socket notifier state if we reentered inside the // readyRead() connected slot. @@ -1904,6 +1917,14 @@ void QAbstractSocket::setSocketOption(QAbstractSocket::SocketOption option, cons case TypeOfServiceOption: d_func()->socketEngine->setOption(QAbstractSocketEngine::TypeOfServiceOption, value.toInt()); break; + + case SendBufferSizeSocketOption: + d_func()->socketEngine->setOption(QAbstractSocketEngine::SendBufferSocketOption, value.toInt()); + break; + + case ReceiveBufferSizeSocketOption: + d_func()->socketEngine->setOption(QAbstractSocketEngine::ReceiveBufferSocketOption, value.toInt()); + break; } } @@ -1938,6 +1959,14 @@ QVariant QAbstractSocket::socketOption(QAbstractSocket::SocketOption option) case TypeOfServiceOption: ret = d_func()->socketEngine->option(QAbstractSocketEngine::TypeOfServiceOption); break; + + case SendBufferSizeSocketOption: + ret = d_func()->socketEngine->option(QAbstractSocketEngine::SendBufferSocketOption); + break; + + case ReceiveBufferSizeSocketOption: + ret = d_func()->socketEngine->option(QAbstractSocketEngine::ReceiveBufferSocketOption); + break; } if (ret == -1) return QVariant(); diff --git a/src/network/socket/qabstractsocket.h b/src/network/socket/qabstractsocket.h index 46114abf73..8b019cf0fb 100644 --- a/src/network/socket/qabstractsocket.h +++ b/src/network/socket/qabstractsocket.h @@ -115,7 +115,9 @@ public: KeepAliveOption, // SO_KEEPALIVE MulticastTtlOption, // IP_MULTICAST_TTL MulticastLoopbackOption, // IP_MULTICAST_LOOPBACK - TypeOfServiceOption //IP_TOS + TypeOfServiceOption, //IP_TOS + SendBufferSizeSocketOption, //SO_SNDBUF + ReceiveBufferSizeSocketOption //SO_RCVBUF }; enum BindFlag { DefaultForPlatform = 0x0, diff --git a/src/network/socket/qabstractsocketengine.cpp b/src/network/socket/qabstractsocketengine.cpp index 1275461d7d..d8abe01241 100644 --- a/src/network/socket/qabstractsocketengine.cpp +++ b/src/network/socket/qabstractsocketengine.cpp @@ -41,7 +41,11 @@ #include "qabstractsocketengine_p.h" +#ifndef Q_OS_WINRT #include "qnativesocketengine_p.h" +#else +#include "qnativesocketengine_winrt_p.h" +#endif #include "qmutex.h" #include "qnetworkproxy.h" diff --git a/src/network/socket/qabstractsocketengine_p.h b/src/network/socket/qabstractsocketengine_p.h index 1dec96762c..6a30012562 100644 --- a/src/network/socket/qabstractsocketengine_p.h +++ b/src/network/socket/qabstractsocketengine_p.h @@ -150,7 +150,7 @@ public: virtual bool waitForRead(int msecs = 30000, bool *timedOut = 0) = 0; virtual bool waitForWrite(int msecs = 30000, bool *timedOut = 0) = 0; virtual bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, - bool checkRead, bool checkWrite, + bool checkRead, bool checkWrite, int msecs = 30000, bool *timedOut = 0) = 0; QAbstractSocket::SocketError error() const; diff --git a/src/network/socket/qlocalserver.cpp b/src/network/socket/qlocalserver.cpp index f7f8aab182..791227002d 100644 --- a/src/network/socket/qlocalserver.cpp +++ b/src/network/socket/qlocalserver.cpp @@ -131,7 +131,7 @@ QLocalServer::QLocalServer(QObject *parent) QLocalServer::~QLocalServer() { if (isListening()) - close(); + close(); } /*! diff --git a/src/network/socket/qnativesocketengine_p.h b/src/network/socket/qnativesocketengine_p.h index 97a9b98c30..fc1afa48c9 100644 --- a/src/network/socket/qnativesocketengine_p.h +++ b/src/network/socket/qnativesocketengine_p.h @@ -162,8 +162,8 @@ public: bool waitForRead(int msecs = 30000, bool *timedOut = 0); bool waitForWrite(int msecs = 30000, bool *timedOut = 0); bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, - bool checkRead, bool checkWrite, - int msecs = 30000, bool *timedOut = 0); + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); bool isReadNotificationEnabled() const; void setReadNotificationEnabled(bool enable); @@ -271,7 +271,7 @@ public: qint64 nativeWrite(const char *data, qint64 length); int nativeSelect(int timeout, bool selectForRead) const; int nativeSelect(int timeout, bool checkRead, bool checkWrite, - bool *selectForRead, bool *selectForWrite) const; + bool *selectForRead, bool *selectForWrite) const; #ifdef Q_OS_WIN void setPortAndAddress(sockaddr_in * sockAddrIPv4, qt_sockaddr_in6 * sockAddrIPv6, quint16 port, const QHostAddress & address, sockaddr ** sockAddrPtr, QT_SOCKLEN_T *sockAddrSize); diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp index e076f2b4bf..b6035b5500 100644 --- a/src/network/socket/qnativesocketengine_unix.cpp +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -144,7 +144,7 @@ bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType soc int protocol = (socketProtocol == QAbstractSocket::IPv6Protocol || socketProtocol == QAbstractSocket::AnyIPProtocol) ? AF_INET6 : AF_INET; int type = (socketType == QAbstractSocket::UdpSocket) ? SOCK_DGRAM : SOCK_STREAM; - int socket = qt_safe_socket(protocol, type, 0); + int socket = qt_safe_socket(protocol, type, 0); if (socket <= 0 && socketProtocol == QAbstractSocket::AnyIPProtocol && errno == EAFNOSUPPORT) { protocol = AF_INET; socket = qt_safe_socket(protocol, type, 0); diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp index 751ac9b182..b1c9073eb9 100644 --- a/src/network/socket/qnativesocketengine_win.cpp +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -186,7 +186,7 @@ static inline void qt_socket_getPortAndAddress(SOCKET socketDescriptor, const qt *address = a; } if (port) - WSANtohs(socketDescriptor, sa6->sin6_port, port); + WSANtohs(socketDescriptor, sa6->sin6_port, port); } else if (sa->a.sa_family == AF_INET) { @@ -194,11 +194,11 @@ static inline void qt_socket_getPortAndAddress(SOCKET socketDescriptor, const qt unsigned long addr; WSANtohl(socketDescriptor, sa4->sin_addr.s_addr, &addr); QHostAddress a; - a.setAddress(addr); - if (address) - *address = a; + a.setAddress(addr); + if (address) + *address = a; if (port) - WSANtohs(socketDescriptor, sa4->sin_port, port); + WSANtohs(socketDescriptor, sa4->sin_port, port); } } @@ -276,7 +276,7 @@ QWindowsSockInit::QWindowsSockInit() // IPv6 requires Winsock v2.0 or better. if (WSAStartup(MAKEWORD(2,0), &wsadata) != 0) { - qWarning("QTcpSocketAPI: WinSock v2.0 initialization failed."); + qWarning("QTcpSocketAPI: WinSock v2.0 initialization failed."); } else { version = 0x20; } @@ -940,14 +940,14 @@ int QNativeSocketEnginePrivate::nativeAccept() break; } } else if (acceptedDescriptor != -1 && QAbstractEventDispatcher::instance()) { - // Because of WSAAsyncSelect() WSAAccept returns a non blocking socket - // with the same attributes as the listening socket including the current - // WSAAsyncSelect(). To be able to change the socket to blocking mode the - // WSAAsyncSelect() call must be cancled. - QSocketNotifier n(acceptedDescriptor, QSocketNotifier::Read); - n.setEnabled(true); - n.setEnabled(false); - } + // Because of WSAAsyncSelect() WSAAccept returns a non blocking socket + // with the same attributes as the listening socket including the current + // WSAAsyncSelect(). To be able to change the socket to blocking mode the + // WSAAsyncSelect() call must be cancled. + QSocketNotifier n(acceptedDescriptor, QSocketNotifier::Read); + n.setEnabled(true); + n.setEnabled(false); + } #if defined (QNATIVESOCKETENGINE_DEBUG) qDebug("QNativeSocketEnginePrivate::nativeAccept() == %i", acceptedDescriptor); #endif diff --git a/src/network/socket/qnativesocketengine_winrt.cpp b/src/network/socket/qnativesocketengine_winrt.cpp new file mode 100644 index 0000000000..2a61325471 --- /dev/null +++ b/src/network/socket/qnativesocketengine_winrt.cpp @@ -0,0 +1,1182 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qt_windows.h> + +#include "qnativesocketengine_winrt_p.h" + +#include <qcoreapplication.h> +#include <qabstracteventdispatcher.h> +#include <qsocketnotifier.h> +#include <qdatetime.h> +#include <qnetworkinterface.h> +#include <qelapsedtimer.h> +#include <qthread.h> +#include <qabstracteventdispatcher.h> + +#include <private/qeventdispatcher_winrt_p.h> + +#include <wrl.h> +#include <windows.foundation.collections.h> +#include <windows.storage.streams.h> +#include <windows.networking.h> +#include <windows.networking.sockets.h> +#include <robuffer.h> + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Storage::Streams; +using namespace ABI::Windows::Networking; +using namespace ABI::Windows::Networking::Connectivity; +using namespace ABI::Windows::Networking::Sockets; + +typedef ITypedEventHandler<StreamSocketListener *, StreamSocketListenerConnectionReceivedEventArgs *> ClientConnectedHandler; +typedef ITypedEventHandler<DatagramSocket *, DatagramSocketMessageReceivedEventArgs *> DatagramReceivedHandler; +typedef IAsyncOperationWithProgressCompletedHandler<IBuffer *, UINT32> SocketReadCompletedHandler; +typedef IAsyncOperationWithProgressCompletedHandler<UINT32, UINT32> SocketWriteCompletedHandler; + +QT_BEGIN_NAMESPACE + +// Common constructs +#define Q_CHECK_VALID_SOCKETLAYER(function, returnValue) do { \ + if (!isValid()) { \ + qWarning(""#function" was called on an uninitialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_INVALID_SOCKETLAYER(function, returnValue) do { \ + if (isValid()) { \ + qWarning(""#function" was called on an already initialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_STATE(function, checkState, returnValue) do { \ + if (d->socketState != (checkState)) { \ + qWarning(""#function" was not called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_NOT_STATE(function, checkState, returnValue) do { \ + if (d->socketState == (checkState)) { \ + qWarning(""#function" was called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_STATES(function, state1, state2, returnValue) do { \ + if (d->socketState != (state1) && d->socketState != (state2)) { \ + qWarning(""#function" was called" \ + " not in "#state1" or "#state2); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_TYPE(function, type, returnValue) do { \ + if (d->socketType != (type)) { \ + qWarning(#function" was called by a" \ + " socket other than "#type""); \ + return (returnValue); \ + } } while (0) +#define Q_TR(a) QT_TRANSLATE_NOOP(QNativeSocketEngine, a) + +typedef QHash<qintptr, IStreamSocket *> TcpSocketHash; + +struct SocketHandler +{ + SocketHandler() : socketCount(0) {} + qintptr socketCount; + TcpSocketHash pendingTcpSockets; +}; + +Q_GLOBAL_STATIC(SocketHandler, gSocketHandler) + +QString qt_QStringFromHSTRING(HSTRING string) +{ + UINT32 length; + PCWSTR rawString = WindowsGetStringRawBuffer(string, &length); + return QString::fromWCharArray(rawString, length); +} + +class ByteArrayBuffer : public Microsoft::WRL::RuntimeClass<RuntimeClassFlags<WinRtClassicComMix>, + IBuffer, Windows::Storage::Streams::IBufferByteAccess> +{ +public: + ByteArrayBuffer(int size) : m_bytes(size, Qt::Uninitialized), m_length(0) + { + } + + ByteArrayBuffer(const char *data, int size) : m_bytes(data, size), m_length(size) + { + } + + HRESULT __stdcall Buffer(byte **value) + { + *value = reinterpret_cast<byte *>(m_bytes.data()); + return S_OK; + } + + HRESULT __stdcall get_Capacity(UINT32 *value) + { + *value = m_bytes.size(); + return S_OK; + } + + HRESULT __stdcall get_Length(UINT32 *value) + { + *value = m_length; + return S_OK; + } + + HRESULT __stdcall put_Length(UINT32 value) + { + Q_ASSERT(value <= UINT32(m_bytes.size())); + m_length = value; + return S_OK; + } + + QNativeSocketEngine *engine() const + { + return m_engine; + } + + void setEngine(QNativeSocketEngine *engine) + { + m_engine = engine; + } + + ComPtr<IInputStream> inputStream() const + { + return m_stream; + } + + void setInputStream(ComPtr<IInputStream> stream) + { + m_stream = stream; + } + +private: + QByteArray m_bytes; + UINT32 m_length; + QPointer<QNativeSocketEngine> m_engine; + ComPtr<IInputStream> m_stream; +}; + +QNativeSocketEngine::QNativeSocketEngine(QObject *parent) + : QAbstractSocketEngine(*new QNativeSocketEnginePrivate(), parent) +{ +} + +QNativeSocketEngine::~QNativeSocketEngine() +{ + close(); +} + +bool QNativeSocketEngine::initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QNativeSocketEngine); + if (isValid()) + close(); + + // Create the socket + if (!d->createNewSocket(type, protocol)) + return false; + + d->socketType = type; + d->socketProtocol = protocol; + return true; +} + +bool QNativeSocketEngine::initialize(qintptr socketDescriptor, QAbstractSocket::SocketState socketState) +{ + Q_D(QNativeSocketEngine); + + if (isValid()) + close(); + + d->socketDescriptor = socketDescriptor; + + // Currently, only TCP sockets are initialized this way. + SocketHandler *handler = gSocketHandler(); + d->tcp = handler->pendingTcpSockets.value(socketDescriptor, Q_NULLPTR); + d->socketType = QAbstractSocket::TcpSocket; + + if (!d->tcp || !d->fetchConnectionParameters()) + return false; + + d->socketState = socketState; + return true; +} + +qintptr QNativeSocketEngine::socketDescriptor() const +{ + Q_D(const QNativeSocketEngine); + return d->socketDescriptor; +} + +bool QNativeSocketEngine::isValid() const +{ + Q_D(const QNativeSocketEngine); + return d->socketDescriptor != -1; +} + +bool QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port) +{ + const QString addressString = address.toString(); + return connectToHostByName(addressString, port); +} + +bool QNativeSocketEngine::connectToHostByName(const QString &name, quint16 port) +{ + Q_D(QNativeSocketEngine); + HStringReference hostNameRef(reinterpret_cast<LPCWSTR>(name.utf16())); + ComPtr<IHostNameFactory> hostNameFactory; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(), + &hostNameFactory); + ComPtr<IHostName> remoteHost; + if (FAILED(hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost))) { + qWarning("QNativeSocketEnginePrivate::nativeConnect:: Could not create hostname"); + return false; + } + + const QString portString = QString::number(port); + HStringReference portReference(reinterpret_cast<LPCWSTR>(portString.utf16())); + ComPtr<IAsyncAction> action; + HRESULT hr = E_FAIL; + if (d->socketType == QAbstractSocket::TcpSocket) + hr = d->tcp->ConnectAsync(remoteHost.Get(), portReference.Get(), &action); + else if (d->socketType == QAbstractSocket::UdpSocket) + hr = d->udp->ConnectAsync(remoteHost.Get(), portReference.Get(), &action); + if (FAILED(hr)) { + qWarning("QNativeSocketEnginePrivate::nativeConnect:: Could not obtain connect action"); + return false; + } + + action->put_Completed(Callback<IAsyncActionCompletedHandler>(&QNativeSocketEnginePrivate::interruptEventDispatcher).Get()); + hr = action->GetResults(); + while ((hr = action->GetResults()) == E_ILLEGAL_METHOD_CALL) + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents); + if (hr == 0x8007274c) { // A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. + d->setError(QAbstractSocket::NetworkError, d->ConnectionTimeOutErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } + if (hr == 0x8007274d) { // No connection could be made because the target machine actively refused it. + d->setError(QAbstractSocket::ConnectionRefusedError, d->ConnectionRefusedErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } + if (FAILED(hr)) { + d->setError(QAbstractSocket::UnknownSocketError, d->UnknownSocketErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } + + if (d->socketType == QAbstractSocket::TcpSocket) { + UINT32 capacity; + hr = d->inputBuffer->get_Capacity(&capacity); + if (FAILED(hr)) + return false; + IInputStream *stream; + hr = d->tcp->get_InputStream(&stream); + if (FAILED(hr)) + return false; + ByteArrayBuffer *buffer = static_cast<ByteArrayBuffer *>(d->inputBuffer.Get()); + buffer->setEngine(this); + buffer->setInputStream(stream); + ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> op; + hr = stream->ReadAsync(buffer, capacity, InputStreamOptions_Partial, &op); + if (FAILED(hr)) + return false; + hr = op->put_Completed(Callback<SocketReadCompletedHandler>(&QNativeSocketEnginePrivate::handleReadyRead).Get()); + if (FAILED(hr)) + return false; + } + d->socketState = QAbstractSocket::ConnectedState; + return true; +} + +bool QNativeSocketEngine::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QNativeSocketEngine); + ComPtr<IHostName> hostAddress; + if (address != QHostAddress::Any && address != QHostAddress::AnyIPv4 && address != QHostAddress::AnyIPv6) { + ComPtr<IHostNameFactory> hostNameFactory; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(), + &hostNameFactory); + const QString addressString = address.toString(); + HStringReference addressRef(reinterpret_cast<LPCWSTR>(addressString.utf16())); + hostNameFactory->CreateHostName(addressRef.Get(), &hostAddress); + } + + HRESULT hr; + QString portQString = port ? QString::number(port) : QString(); + HStringReference portString(reinterpret_cast<LPCWSTR>(portQString.utf16())); + + ComPtr<IAsyncAction> op; + if (d->socketType == QAbstractSocket::TcpSocket) { + if (!d->tcpListener + && FAILED(RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_StreamSocketListener).Get(), + &d->tcpListener))) { + qWarning("Failed to create listener"); + return false; + } + + EventRegistrationToken token; + d->tcpListener->add_ConnectionReceived(Callback<ClientConnectedHandler>(d, &QNativeSocketEnginePrivate::handleClientConnection).Get(), &token); + hr = d->tcpListener->BindEndpointAsync(hostAddress.Get(), portString.Get(), &op); + if (FAILED(hr)) { + qWarning("Unable to bind"); // ### Set error message + return false; + } + } else if (d->socketType == QAbstractSocket::UdpSocket) { + hr = d->udp->BindEndpointAsync(hostAddress.Get(), portString.Get(), &op); + if (FAILED(hr)) { + qWarning("unable to bind"); // ### Set error message + return false; + } + } + + if (op) { + // Wait for connection to enter bound state - TODO: timeout, check result + while ((hr = op->GetResults()) == E_ILLEGAL_METHOD_CALL) + QCoreApplication::processEvents(); + + d->socketState = QAbstractSocket::BoundState; + d->fetchConnectionParameters(); + return true; + } + + return false; +} + +bool QNativeSocketEngine::listen() +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::listen(), false); + Q_CHECK_STATE(QNativeSocketEngine::listen(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QNativeSocketEngine::listen(), QAbstractSocket::TcpSocket, false); + + if (d->tcpListener && d->socketDescriptor != -1) { + d->socketState = QAbstractSocket::ListeningState; + return true; + } + return false; +} + +int QNativeSocketEngine::accept() +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::accept(), -1); + Q_CHECK_STATE(QNativeSocketEngine::accept(), QAbstractSocket::ListeningState, -1); + Q_CHECK_TYPE(QNativeSocketEngine::accept(), QAbstractSocket::TcpSocket, -1); + + if (d->socketDescriptor == -1 || d->pendingConnections.isEmpty()) + return -1; + + // Start processing incoming data + if (d->socketType == QAbstractSocket::TcpSocket) { + IStreamSocket *socket = d->pendingConnections.takeFirst(); + + UINT32 capacity; + d->inputBuffer->get_Capacity(&capacity); + IInputStream *stream; + socket->get_InputStream(&stream); + // TODO: delete buffer and stream on socket close + ByteArrayBuffer *buffer = static_cast<ByteArrayBuffer *>(d->inputBuffer.Get()); + buffer->setEngine(this); + buffer->setInputStream(stream); + ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> op; + stream->ReadAsync(buffer, capacity, InputStreamOptions_Partial, &op); + op->put_Completed(Callback<SocketReadCompletedHandler>(&QNativeSocketEnginePrivate::handleReadyRead).Get()); + d->currentConnections.append(socket); + + SocketHandler *handler = gSocketHandler(); + handler->pendingTcpSockets.insert(++handler->socketCount, socket); + return handler->socketCount; + } + + return -1; +} + +void QNativeSocketEngine::close() +{ + Q_D(QNativeSocketEngine); + if (d->socketDescriptor != -1) { + IClosable *socket = 0; + if (d->socketType == QAbstractSocket::TcpSocket) + d->tcp->QueryInterface(IID_PPV_ARGS(&socket)); + else if (d->socketType == QAbstractSocket::UdpSocket) + d->udp->QueryInterface(IID_PPV_ARGS(&socket)); + + if (socket) { + d->closingDown = true; + socket->Close(); + socket->Release(); + closeNotification(); + d->socketDescriptor = -1; + } + d->socketDescriptor = -1; + } + d->socketState = QAbstractSocket::UnconnectedState; + d->hasSetSocketError = false; + d->localPort = 0; + d->localAddress.clear(); + d->peerPort = 0; + d->peerAddress.clear(); +} + +bool QNativeSocketEngine::joinMulticastGroup(const QHostAddress &groupAddress, const QNetworkInterface &iface) +{ + Q_UNUSED(groupAddress); + Q_UNUSED(iface); + Q_UNIMPLEMENTED(); + return false; +} + +bool QNativeSocketEngine::leaveMulticastGroup(const QHostAddress &groupAddress, const QNetworkInterface &iface) +{ + Q_UNUSED(groupAddress); + Q_UNUSED(iface); + Q_UNIMPLEMENTED(); + return false; +} + +QNetworkInterface QNativeSocketEngine::multicastInterface() const +{ + Q_UNIMPLEMENTED(); + return QNetworkInterface(); +} + +bool QNativeSocketEngine::setMulticastInterface(const QNetworkInterface &iface) +{ + Q_UNUSED(iface); + Q_UNIMPLEMENTED(); + return false; +} + +qint64 QNativeSocketEngine::bytesAvailable() const +{ + Q_D(const QNativeSocketEngine); + if (d->socketType != QAbstractSocket::TcpSocket) + return -1; + + if (d->inputBuffer) { + UINT32 len; + d->inputBuffer->get_Length(&len); + return len; + } + + return -1; +} + +qint64 QNativeSocketEngine::read(char *data, qint64 maxlen) +{ + Q_D(QNativeSocketEngine); + if (d->socketType != QAbstractSocket::TcpSocket) + return -1; + + ComPtr<IDataReaderStatics> dataReaderStatics; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataReader).Get(), &dataReaderStatics); + ComPtr<IDataReader> reader; + + dataReaderStatics->FromBuffer(d->inputBuffer.Get(), &reader); + + UINT32 bufferCapacity; + d->inputBuffer->get_Capacity(&bufferCapacity); + qint64 lengthToRead = maxlen < bufferCapacity ? maxlen : bufferCapacity; + + UINT32 bufferLength; + d->inputBuffer->get_Length(&bufferLength); + + lengthToRead = bufferLength < lengthToRead ? bufferLength : lengthToRead; + reader->ReadBytes(lengthToRead, (unsigned char*)data); + return lengthToRead; +} + +template <typename T> +static qint64 nativeWrite(T *socket, const char *data, qint64 len) +{ + ComPtr<IOutputStream> stream; + HRESULT hr = socket->get_OutputStream(&stream); + if (FAILED(hr)) + return -1; + ComPtr<ByteArrayBuffer> buffer = Make<ByteArrayBuffer>(data, len); + ComPtr<IAsyncOperationWithProgress<UINT32, UINT32>> op; + hr = stream->WriteAsync(buffer.Get(), &op); + if (FAILED(hr)) + return -1; + UINT32 bytesWritten; + while ((hr = op->GetResults(&bytesWritten)) == E_ILLEGAL_METHOD_CALL) + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + return bytesWritten; +} + +qint64 QNativeSocketEngine::write(const char *data, qint64 len) +{ + Q_D(QNativeSocketEngine); + qint64 bytesWritten = -1; + if (d->socketType == QAbstractSocket::TcpSocket) + bytesWritten = ::nativeWrite(d->tcp, data, len); + else if (d->socketType == QAbstractSocket::UdpSocket) + bytesWritten = ::nativeWrite(d->udp, data, len); + if (bytesWritten != -1 && d->notifyOnWrite) + writeNotification(); + return bytesWritten; + +} + +qint64 QNativeSocketEngine::readDatagram(char *data, qint64 maxlen, QHostAddress *addr, quint16 *port) +{ + Q_D(QNativeSocketEngine); + if (d->socketType != QAbstractSocket::UdpSocket) + return -1; + + QHostAddress returnAddress; + quint16 returnPort; + + for (int i = 0; i < d->pendingDatagrams.size(); ++i) { + IDatagramSocketMessageReceivedEventArgs *arg = d->pendingDatagrams.at(i); + ComPtr<IHostName> remoteHost; + HSTRING remoteHostString; + HSTRING remotePort; + arg->get_RemoteAddress(&remoteHost); + arg->get_RemotePort(&remotePort); + remoteHost->get_CanonicalName(&remoteHostString); + returnAddress.setAddress(qt_QStringFromHSTRING(remoteHostString)); + returnPort = qt_QStringFromHSTRING(remotePort).toInt(); + ComPtr<IDataReader> reader; + arg->GetDataReader(&reader); + if (!reader) + continue; + + BYTE buffer[1024]; + reader->ReadBytes(maxlen, buffer); + *addr = returnAddress; + *port = returnPort; + arg = d->pendingDatagrams.takeFirst(); + + // TODO: fill data + Q_UNUSED(data); + arg->Release(); + delete arg; + --i; + return maxlen; + } + + return -1; +} + +qint64 QNativeSocketEngine::writeDatagram(const char *data, qint64 len, const QHostAddress &addr, quint16 port) +{ + Q_D(QNativeSocketEngine); + if (d->socketType != QAbstractSocket::UdpSocket) + return -1; + + ComPtr<IHostName> remoteHost; + ComPtr<IHostNameFactory> hostNameFactory; + if (FAILED(GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(), + &hostNameFactory))) { + qWarning("QNativeSocketEnginePrivate::nativeSendDatagram: could not obtain hostname factory"); + return -1; + } + const QString addressString = addr.toString(); + HStringReference hostNameRef(reinterpret_cast<LPCWSTR>(addressString.utf16())); + hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost); + + ComPtr<IAsyncOperation<IOutputStream *>> streamOperation; + ComPtr<IOutputStream> stream; + const QString portString = QString::number(port); + HStringReference portRef(reinterpret_cast<LPCWSTR>(portString.utf16())); + if (FAILED(d->udp->GetOutputStreamAsync(remoteHost.Get(), portRef.Get(), &streamOperation))) + return -1; + HRESULT hr; + while (hr = streamOperation->GetResults(&stream) == E_ILLEGAL_METHOD_CALL) + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + ComPtr<IDataWriterFactory> dataWriterFactory; + GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(), &dataWriterFactory); + ComPtr<IDataWriter> writer; + dataWriterFactory->CreateDataWriter(stream.Get(), &writer); + writer->WriteBytes(len, (unsigned char *)data); + return len; +} + +bool QNativeSocketEngine::hasPendingDatagrams() const +{ + Q_D(const QNativeSocketEngine); + return d->pendingDatagrams.length() > 0; +} + +qint64 QNativeSocketEngine::pendingDatagramSize() const +{ + Q_D(const QNativeSocketEngine); + qint64 ret = 0; + foreach (IDatagramSocketMessageReceivedEventArgs *arg, d->pendingDatagrams) { + ComPtr<IDataReader> reader; + UINT32 unconsumedBufferLength; + arg->GetDataReader(&reader); + if (!reader) + return -1; + reader->get_UnconsumedBufferLength(&unconsumedBufferLength); + ret += unconsumedBufferLength; + } + return ret; +} + +qint64 QNativeSocketEngine::bytesToWrite() const +{ + return 0; +} + +qint64 QNativeSocketEngine::receiveBufferSize() const +{ + Q_D(const QNativeSocketEngine); + return d->option(QAbstractSocketEngine::ReceiveBufferSocketOption); +} + +void QNativeSocketEngine::setReceiveBufferSize(qint64 bufferSize) +{ + Q_D(QNativeSocketEngine); + d->setOption(QAbstractSocketEngine::ReceiveBufferSocketOption, bufferSize); +} + +qint64 QNativeSocketEngine::sendBufferSize() const +{ + Q_D(const QNativeSocketEngine); + return d->option(QAbstractSocketEngine::SendBufferSocketOption); +} + +void QNativeSocketEngine::setSendBufferSize(qint64 bufferSize) +{ + Q_D(QNativeSocketEngine); + d->setOption(QAbstractSocketEngine::SendBufferSocketOption, bufferSize); +} + +int QNativeSocketEngine::option(QAbstractSocketEngine::SocketOption option) const +{ + Q_D(const QNativeSocketEngine); + return d->option(option); +} + +bool QNativeSocketEngine::setOption(QAbstractSocketEngine::SocketOption option, int value) +{ + Q_D(QNativeSocketEngine); + return d->setOption(option, value); +} + +bool QNativeSocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForRead(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForRead(), + QAbstractSocket::UnconnectedState, false); + + if (timedOut) + *timedOut = false; + + QElapsedTimer timer; + timer.start(); + while (msecs > timer.elapsed()) { + // Servers with active connections are ready for reading + if (!d->currentConnections.isEmpty()) + return true; + + // If we are a client, we are ready to read if our buffer has data + UINT32 length; + if (FAILED(d->inputBuffer->get_Length(&length))) + return false; + if (length) + return true; + + // Nothing to do, wait for more events + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents|QEventLoop::WaitForMoreEvents); + } + + d->setError(QAbstractSocket::SocketTimeoutError, + QNativeSocketEnginePrivate::TimeOutErrorString); + + if (timedOut) + *timedOut = true; + return false; +} + +bool QNativeSocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_UNUSED(msecs); + Q_UNUSED(timedOut); + return false; +} + +bool QNativeSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, bool checkRead, bool checkWrite, int msecs, bool *timedOut) +{ + Q_UNUSED(readyToRead); + Q_UNUSED(readyToWrite); + Q_UNUSED(checkRead); + Q_UNUSED(checkWrite); + Q_UNUSED(msecs); + Q_UNUSED(timedOut); + return false; +} + +bool QNativeSocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->notifyOnRead; +} + +void QNativeSocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + d->notifyOnRead = enable; +} + +bool QNativeSocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->notifyOnWrite; +} + +void QNativeSocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + d->notifyOnWrite = enable; + if (enable && d->socketState == QAbstractSocket::ConnectedState) { + if (bytesToWrite()) + return; // will be emitted as a result of bytes written + writeNotification(); + d->notifyOnWrite = false; + } +} + +bool QNativeSocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->notifyOnException; +} + +void QNativeSocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + d->notifyOnException = enable; +} + +bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol &socketProtocol) +{ + Q_UNUSED(socketProtocol); + SocketHandler *handler = gSocketHandler(); + switch (socketType) { + case QAbstractSocket::TcpSocket: { + if (FAILED(RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_StreamSocket).Get(), + reinterpret_cast<IInspectable **>(&tcp)))) { + qWarning("Failed to create StreamSocket instance"); + return false; + } + socketDescriptor = ++handler->socketCount; + return true; + } + case QAbstractSocket::UdpSocket: { + if (FAILED(RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_DatagramSocket).Get(), + reinterpret_cast<IInspectable **>(&udp)))) { + qWarning("Failed to create stream socket"); + return false; + } + EventRegistrationToken token; + udp->add_MessageReceived(Callback<DatagramReceivedHandler>(this, &QNativeSocketEnginePrivate::handleNewDatagram).Get(), &token); + socketDescriptor = ++handler->socketCount; + return true; + } + default: + qWarning("Invalid socket type"); + return false; + } + return false; +} + +QNativeSocketEnginePrivate::QNativeSocketEnginePrivate() + : QAbstractSocketEnginePrivate() + , notifyOnRead(true) + , notifyOnWrite(true) + , notifyOnException(false) + , closingDown(false) + , socketDescriptor(-1) +{ + ComPtr<ByteArrayBuffer> buffer = Make<ByteArrayBuffer>(8192); + inputBuffer = buffer; +} + +QNativeSocketEnginePrivate::~QNativeSocketEnginePrivate() +{ +} + +void QNativeSocketEnginePrivate::setError(QAbstractSocket::SocketError error, ErrorString errorString) const +{ + if (hasSetSocketError) { + // Only set socket errors once for one engine; expect the + // socket to recreate its engine after an error. Note: There's + // one exception: SocketError(11) bypasses this as it's purely + // a temporary internal error condition. + // Another exception is the way the waitFor*() functions set + // an error when a timeout occurs. After the call to setError() + // they reset the hasSetSocketError to false + return; + } + if (error != QAbstractSocket::SocketError(11)) + hasSetSocketError = true; + + socketError = error; + + switch (errorString) { + case NonBlockingInitFailedErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to initialize non-blocking socket"); + break; + case BroadcastingInitFailedErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to initialize broadcast socket"); + break; + // should not happen anymore + case NoIpV6ErrorString: + socketErrorString = QNativeSocketEngine::tr("Attempt to use IPv6 socket on a platform with no IPv6 support"); + break; + case RemoteHostClosedErrorString: + socketErrorString = QNativeSocketEngine::tr("The remote host closed the connection"); + break; + case TimeOutErrorString: + socketErrorString = QNativeSocketEngine::tr("Network operation timed out"); + break; + case ResourceErrorString: + socketErrorString = QNativeSocketEngine::tr("Out of resources"); + break; + case OperationUnsupportedErrorString: + socketErrorString = QNativeSocketEngine::tr("Unsupported socket operation"); + break; + case ProtocolUnsupportedErrorString: + socketErrorString = QNativeSocketEngine::tr("Protocol type not supported"); + break; + case InvalidSocketErrorString: + socketErrorString = QNativeSocketEngine::tr("Invalid socket descriptor"); + break; + case HostUnreachableErrorString: + socketErrorString = QNativeSocketEngine::tr("Host unreachable"); + break; + case NetworkUnreachableErrorString: + socketErrorString = QNativeSocketEngine::tr("Network unreachable"); + break; + case AccessErrorString: + socketErrorString = QNativeSocketEngine::tr("Permission denied"); + break; + case ConnectionTimeOutErrorString: + socketErrorString = QNativeSocketEngine::tr("Connection timed out"); + break; + case ConnectionRefusedErrorString: + socketErrorString = QNativeSocketEngine::tr("Connection refused"); + break; + case AddressInuseErrorString: + socketErrorString = QNativeSocketEngine::tr("The bound address is already in use"); + break; + case AddressNotAvailableErrorString: + socketErrorString = QNativeSocketEngine::tr("The address is not available"); + break; + case AddressProtectedErrorString: + socketErrorString = QNativeSocketEngine::tr("The address is protected"); + break; + case DatagramTooLargeErrorString: + socketErrorString = QNativeSocketEngine::tr("Datagram was too large to send"); + break; + case SendDatagramErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to send a message"); + break; + case ReceiveDatagramErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to receive a message"); + break; + case WriteErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to write"); + break; + case ReadErrorString: + socketErrorString = QNativeSocketEngine::tr("Network error"); + break; + case PortInuseErrorString: + socketErrorString = QNativeSocketEngine::tr("Another socket is already listening on the same port"); + break; + case NotSocketErrorString: + socketErrorString = QNativeSocketEngine::tr("Operation on non-socket"); + break; + case InvalidProxyTypeString: + socketErrorString = QNativeSocketEngine::tr("The proxy type is invalid for this operation"); + break; + case TemporaryErrorString: + socketErrorString = QNativeSocketEngine::tr("Temporary error"); + break; + case UnknownSocketErrorString: + socketErrorString = QNativeSocketEngine::tr("Unknown error"); + break; + } +} + +int QNativeSocketEnginePrivate::option(QAbstractSocketEngine::SocketOption opt) const +{ + ComPtr<IStreamSocketControl> control; + if (socketType == QAbstractSocket::TcpSocket) { + if (FAILED(tcp->get_Control(&control))) { + qWarning("QNativeSocketEnginePrivate::option: Could not obtain socket control"); + return -1; + } + } + switch (opt) { + case QAbstractSocketEngine::NonBlockingSocketOption: + case QAbstractSocketEngine::BroadcastSocketOption: + case QAbstractSocketEngine::ReceiveOutOfBandData: + return 1; + case QAbstractSocketEngine::SendBufferSocketOption: + if (socketType == QAbstractSocket::UdpSocket) + return -1; + + UINT32 bufferSize; + if (FAILED(control->get_OutboundBufferSizeInBytes(&bufferSize))) { + qWarning("Could not obtain OutboundBufferSizeInBytes information vom socket control"); + return -1; + } + return bufferSize; + case QAbstractSocketEngine::LowDelayOption: + if (socketType == QAbstractSocket::UdpSocket) + return -1; + + boolean noDelay; + if (FAILED(control->get_NoDelay(&noDelay))) { + qWarning("Could not obtain NoDelay information from socket control"); + return -1; + } + return noDelay; + case QAbstractSocketEngine::KeepAliveOption: + if (socketType == QAbstractSocket::UdpSocket) + return -1; + + boolean keepAlive; + if (FAILED(control->get_KeepAlive(&keepAlive))) { + qWarning("Could not obtain KeepAlive information from socket control"); + return -1; + } + return keepAlive; + case QAbstractSocketEngine::ReceiveBufferSocketOption: + case QAbstractSocketEngine::AddressReusable: + case QAbstractSocketEngine::BindExclusively: + case QAbstractSocketEngine::MulticastTtlOption: + case QAbstractSocketEngine::MulticastLoopbackOption: + case QAbstractSocketEngine::TypeOfServiceOption: + default: + return -1; + } + return -1; +} + +bool QNativeSocketEnginePrivate::setOption(QAbstractSocketEngine::SocketOption opt, int v) +{ + ComPtr<IStreamSocketControl> control; + if (socketType == QAbstractSocket::TcpSocket) { + if (FAILED(tcp->get_Control(&control))) { + qWarning("QNativeSocketEnginePrivate::setOption: Could not obtain socket control"); + return false; + } + } + switch (opt) { + case QAbstractSocketEngine::NonBlockingSocketOption: + case QAbstractSocketEngine::BroadcastSocketOption: + case QAbstractSocketEngine::ReceiveOutOfBandData: + return v != 0; + case QAbstractSocketEngine::SendBufferSocketOption: + if (socketType == QAbstractSocket::UdpSocket) + return false; + + if (FAILED(control->put_OutboundBufferSizeInBytes(v))) { + qWarning("Could not set OutboundBufferSizeInBytes"); + return false; + } + return true; + case QAbstractSocketEngine::LowDelayOption: { + if (socketType == QAbstractSocket::UdpSocket) + return false; + + boolean noDelay = v; + if (FAILED(control->put_NoDelay(noDelay))) { + qWarning("Could not obtain NoDelay information from socket control"); + return false; + } + return true; + } + case QAbstractSocketEngine::KeepAliveOption: { + if (socketType == QAbstractSocket::UdpSocket) + return false; + + boolean keepAlive = v; + if (FAILED(control->put_KeepAlive(keepAlive))) { + qWarning("Could not set KeepAlive value"); + return false; + } + return true; + } + case QAbstractSocketEngine::ReceiveBufferSocketOption: + case QAbstractSocketEngine::AddressReusable: + case QAbstractSocketEngine::BindExclusively: + case QAbstractSocketEngine::MulticastTtlOption: + case QAbstractSocketEngine::MulticastLoopbackOption: + case QAbstractSocketEngine::TypeOfServiceOption: + default: + return false; + } + return false; +} + +bool QNativeSocketEnginePrivate::fetchConnectionParameters() +{ + localPort = 0; + localAddress.clear(); + peerPort = 0; + peerAddress.clear(); + + if (socketType == QAbstractSocket::TcpSocket) { + ComPtr<IHostName> hostName; + HSTRING tmpHString; + ComPtr<IStreamSocketInformation> info; + if (FAILED(tcp->get_Information(&info))) { + qWarning("QNativeSocketEnginePrivate::fetchConnectionParameters: Could not obtain socket info"); + return false; + } + info->get_LocalAddress(&hostName); + if (hostName) { + hostName->get_CanonicalName(&tmpHString); + localAddress.setAddress(qt_QStringFromHSTRING(tmpHString)); + info->get_LocalPort(&tmpHString); + localPort = qt_QStringFromHSTRING(tmpHString).toInt(); + } + if (!localPort && tcpListener) { + ComPtr<IStreamSocketListenerInformation> listenerInfo = 0; + tcpListener->get_Information(&listenerInfo); + listenerInfo->get_LocalPort(&tmpHString); + localPort = qt_QStringFromHSTRING(tmpHString).toInt(); + localAddress == QHostAddress::Any; + } + info->get_RemoteAddress(&hostName); + if (hostName) { + hostName->get_CanonicalName(&tmpHString); + peerAddress.setAddress(qt_QStringFromHSTRING(tmpHString)); + info->get_RemotePort(&tmpHString); + peerPort = qt_QStringFromHSTRING(tmpHString).toInt(); + } + } else if (socketType == QAbstractSocket::UdpSocket) { + ComPtr<IHostName> hostName; + HSTRING tmpHString; + ComPtr<IDatagramSocketInformation> info; + if (FAILED(udp->get_Information(&info))) { + qWarning("QNativeSocketEnginePrivate::fetchConnectionParameters: Could not obtain socket information"); + return false; + } + info->get_LocalAddress(&hostName); + if (hostName) { + hostName->get_CanonicalName(&tmpHString); + localAddress.setAddress(qt_QStringFromHSTRING(tmpHString)); + info->get_LocalPort(&tmpHString); + localPort = qt_QStringFromHSTRING(tmpHString).toInt(); + } + + info->get_RemoteAddress(&hostName); + if (hostName) { + hostName->get_CanonicalName(&tmpHString); + peerAddress.setAddress(qt_QStringFromHSTRING(tmpHString)); + info->get_RemotePort(&tmpHString); + peerPort = qt_QStringFromHSTRING(tmpHString).toInt(); + } + } + return true; +} + +HRESULT QNativeSocketEnginePrivate::handleClientConnection(IStreamSocketListener *listener, IStreamSocketListenerConnectionReceivedEventArgs *args) +{ + Q_Q(QNativeSocketEngine); + Q_ASSERT(tcpListener.Get() == listener); + IStreamSocket *socket; + args->get_Socket(&socket); + pendingConnections.append(socket); + q->connectionNotification(); + q->readNotification(); + return interruptEventDispatcher(0, Completed); +} + +HRESULT QNativeSocketEnginePrivate::interruptEventDispatcher(IAsyncAction *, AsyncStatus) +{ + if (QThread *thread = QThread::currentThread()) { + if (QAbstractEventDispatcher *dispatcher = thread->eventDispatcher()) + dispatcher->interrupt(); + } + return S_OK; +} + +HRESULT QNativeSocketEnginePrivate::handleReadyRead(IAsyncOperationWithProgress<IBuffer *, UINT32> *asyncInfo, AsyncStatus) +{ + ByteArrayBuffer *buffer = 0; + HRESULT hr = asyncInfo->GetResults((IBuffer **)&buffer); + if (FAILED(hr)) + return hr; + UINT32 len; + buffer->get_Length(&len); + QNativeSocketEngine *q = buffer->engine(); + if (!q) + return S_OK; + if (len > 0 && q->isReadNotificationEnabled()) { + q->readNotification(); + } + + // Continue reading ### TODO: read into offset!!! + UINT32 capacity; + buffer->get_Capacity(&capacity); + ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> op; + if (SUCCEEDED(buffer->inputStream()->ReadAsync(buffer, capacity, InputStreamOptions_Partial, &op))) { + if (q) + return op->put_Completed(Callback<SocketReadCompletedHandler>(&QNativeSocketEnginePrivate::handleReadyRead).Get()); + else + return op->put_Completed(nullptr); + } + + return E_FAIL; +} + +HRESULT QNativeSocketEnginePrivate::handleNewDatagram(IDatagramSocket *socket, IDatagramSocketMessageReceivedEventArgs *args) +{ + Q_Q(QNativeSocketEngine); + Q_ASSERT(udp == socket); + pendingDatagrams.append(args); + q->readNotification(); + + return S_OK; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnativesocketengine_winrt_p.h b/src/network/socket/qnativesocketengine_winrt_p.h new file mode 100644 index 0000000000..b5be5fa830 --- /dev/null +++ b/src/network/socket/qnativesocketengine_winrt_p.h @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNATIVESOCKETENGINE_WINRT_P_H +#define QNATIVESOCKETENGINE_WINRT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// +#include "QtNetwork/qhostaddress.h" +#include "private/qabstractsocketengine_p.h" +#include <wrl.h> +#include <windows.networking.sockets.h> + +QT_BEGIN_NAMESPACE + +class QNativeSocketEnginePrivate; + +class Q_AUTOTEST_EXPORT QNativeSocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT +public: + QNativeSocketEngine(QObject *parent = 0); + ~QNativeSocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(qintptr socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + qintptr socketDescriptor() const; + + bool isValid() const; + + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + +#ifndef QT_NO_NETWORKINTERFACE + bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + QNetworkInterface multicastInterface() const; + bool setMulticastInterface(const QNetworkInterface &iface); +#endif + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; + + qint64 bytesToWrite() const; + + qint64 receiveBufferSize() const; + void setReceiveBufferSize(qint64 bufferSize); + + qint64 sendBufferSize() const; + void setSendBufferSize(qint64 bufferSize); + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + +private: + Q_DECLARE_PRIVATE(QNativeSocketEngine) + Q_DISABLE_COPY(QNativeSocketEngine) +}; + +class QNativeSocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QNativeSocketEngine) +public: + QNativeSocketEnginePrivate(); + ~QNativeSocketEnginePrivate(); + + qintptr socketDescriptor; + + bool notifyOnRead, notifyOnWrite, notifyOnException; + bool closingDown; + + enum ErrorString { + NonBlockingInitFailedErrorString, + BroadcastingInitFailedErrorString, + NoIpV6ErrorString, + RemoteHostClosedErrorString, + TimeOutErrorString, + ResourceErrorString, + OperationUnsupportedErrorString, + ProtocolUnsupportedErrorString, + InvalidSocketErrorString, + HostUnreachableErrorString, + NetworkUnreachableErrorString, + AccessErrorString, + ConnectionTimeOutErrorString, + ConnectionRefusedErrorString, + AddressInuseErrorString, + AddressNotAvailableErrorString, + AddressProtectedErrorString, + DatagramTooLargeErrorString, + SendDatagramErrorString, + ReceiveDatagramErrorString, + WriteErrorString, + ReadErrorString, + PortInuseErrorString, + NotSocketErrorString, + InvalidProxyTypeString, + TemporaryErrorString, + + UnknownSocketErrorString = -1 + }; + + void setError(QAbstractSocket::SocketError error, ErrorString errorString) const; + + // native functions + int option(QNativeSocketEngine::SocketOption option) const; + bool setOption(QNativeSocketEngine::SocketOption option, int value); + + bool createNewSocket(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol &protocol); + + bool checkProxy(const QHostAddress &address); + bool fetchConnectionParameters(); +private: + union { + ABI::Windows::Networking::Sockets::IStreamSocket *tcp; + ABI::Windows::Networking::Sockets::IDatagramSocket *udp; + }; + Microsoft::WRL::ComPtr<ABI::Windows::Networking::Sockets::IStreamSocketListener> tcpListener; + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> inputBuffer; + QList<ABI::Windows::Networking::Sockets::IDatagramSocketMessageReceivedEventArgs *> pendingDatagrams; + QList<ABI::Windows::Networking::Sockets::IStreamSocket *> pendingConnections; + QList<ABI::Windows::Networking::Sockets::IStreamSocket *> currentConnections; + + HRESULT handleNewDatagram(ABI::Windows::Networking::Sockets::IDatagramSocket *socket, + ABI::Windows::Networking::Sockets::IDatagramSocketMessageReceivedEventArgs *args); + HRESULT handleClientConnection(ABI::Windows::Networking::Sockets::IStreamSocketListener *tcpListener, + ABI::Windows::Networking::Sockets::IStreamSocketListenerConnectionReceivedEventArgs *args); + static HRESULT interruptEventDispatcher(ABI::Windows::Foundation::IAsyncAction *, ABI::Windows::Foundation::AsyncStatus); + static HRESULT handleReadyRead(ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer *, UINT32> *asyncInfo, ABI::Windows::Foundation::AsyncStatus); +}; + +QT_END_NAMESPACE + +#endif // QNATIVESOCKETENGINE_WINRT_P_H diff --git a/src/network/socket/qsocks5socketengine.cpp b/src/network/socket/qsocks5socketengine.cpp index 6818ff6354..b62c4a6bef 100644 --- a/src/network/socket/qsocks5socketengine.cpp +++ b/src/network/socket/qsocks5socketengine.cpp @@ -735,9 +735,10 @@ void QSocks5SocketEnginePrivate::reauthenticate() proxyInfo.setPassword(auth.password()); data->authenticator = new QSocks5PasswordAuthenticator(proxyInfo.user(), proxyInfo.password()); - data->controlSocket->blockSignals(true); - data->controlSocket->abort(); - data->controlSocket->blockSignals(false); + { + const QSignalBlocker blocker(data->controlSocket); + data->controlSocket->abort(); + } data->controlSocket->connectToHost(proxyInfo.hostName(), proxyInfo.port()); } else { // authentication failure diff --git a/src/network/socket/socket.pri b/src/network/socket/socket.pri index c0c6d750d9..7e3a54e303 100644 --- a/src/network/socket/socket.pri +++ b/src/network/socket/socket.pri @@ -24,8 +24,10 @@ SOURCES += socket/qabstractsocketengine.cpp \ socket/qlocalsocket.cpp \ socket/qlocalserver.cpp -SOURCES += socket/qnativesocketengine.cpp -HEADERS += socket/qnativesocketengine_p.h +!winrt { + SOURCES += socket/qnativesocketengine.cpp + HEADERS += socket/qnativesocketengine_p.h +} unix: { SOURCES += socket/qnativesocketengine_unix.cpp \ @@ -36,11 +38,20 @@ unix: { unix:HEADERS += \ socket/qnet_unix_p.h -win32:SOURCES += socket/qnativesocketengine_win.cpp \ +win32:!winrt:SOURCES += socket/qnativesocketengine_win.cpp \ socket/qlocalsocket_win.cpp \ socket/qlocalserver_win.cpp -win32:!wince*: LIBS_PRIVATE += -ladvapi32 +win32:!wince*:!winrt:LIBS_PRIVATE += -ladvapi32 + +winrt { + SOURCES += socket/qnativesocketengine_winrt.cpp \ + socket/qlocalsocket_tcp.cpp \ + socket/qlocalserver_tcp.cpp + HEADERS += socket/qnativesocketengine_winrt_p.h + + DEFINES += QT_LOCALSOCKET_TCP +} wince*: { SOURCES -= socket/qlocalsocket_win.cpp \ diff --git a/src/network/ssl/qsslcipher.cpp b/src/network/ssl/qsslcipher.cpp index cdb0ed9063..bb5d93e528 100644 --- a/src/network/ssl/qsslcipher.cpp +++ b/src/network/ssl/qsslcipher.cpp @@ -79,6 +79,26 @@ QSslCipher::QSslCipher() /*! Constructs a QSslCipher object for the cipher determined by \a + name. The constructor accepts only supported ciphers (i.e., the + \a name must identify a cipher in the list of ciphers returned by + QSslSocket::supportedCiphers()). + + You can call isNull() after construction to check if \a name + correctly identified a supported cipher. +*/ +QSslCipher::QSslCipher(const QString &name) + : d(new QSslCipherPrivate) +{ + foreach (const QSslCipher &cipher, QSslSocket::supportedCiphers()) { + if (cipher.name() == name) { + *this = cipher; + return; + } + } +} + +/*! + Constructs a QSslCipher object for the cipher determined by \a name and \a protocol. The constructor accepts only supported ciphers (i.e., the \a name and \a protocol must identify a cipher in the list of ciphers returned by diff --git a/src/network/ssl/qsslcipher.h b/src/network/ssl/qsslcipher.h index e351d7949b..4cebffa7ae 100644 --- a/src/network/ssl/qsslcipher.h +++ b/src/network/ssl/qsslcipher.h @@ -57,6 +57,7 @@ class Q_NETWORK_EXPORT QSslCipher { public: QSslCipher(); + QSslCipher(const QString &name); QSslCipher(const QString &name, QSsl::SslProtocol protocol); QSslCipher(const QSslCipher &other); ~QSslCipher(); diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp index 4aad7c04c5..1e859ae6e6 100644 --- a/src/network/ssl/qsslconfiguration.cpp +++ b/src/network/ssl/qsslconfiguration.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -52,6 +53,9 @@ const QSsl::SslOptions QSslConfigurationPrivate::defaultSslOptions = QSsl::SslOp |QSsl::SslOptionDisableCompression |QSsl::SslOptionDisableSessionPersistence; +const char QSslConfiguration::NextProtocolSpdy3_0[] = "spdy/3"; +const char QSslConfiguration::NextProtocolHttp1_1[] = "http/1.1"; + /*! \class QSslConfiguration \brief The QSslConfiguration class holds the configuration and state of an SSL connection @@ -113,6 +117,33 @@ const QSsl::SslOptions QSslConfigurationPrivate::defaultSslOptions = QSsl::SslOp */ /*! + \enum QSslConfiguration::NextProtocolNegotiationStatus + + Describes the status of the Next Protocol Negotiation (NPN). + + \value NextProtocolNegotiationNone No application protocol + has been negotiated (yet). + + \value NextProtocolNegotiationNegotiated A next protocol + has been negotiated (see nextNegotiatedProtocol()). + + \value NextProtocolNegotiationUnsupported The client and + server could not agree on a common next application protocol. +*/ + +/*! + \variable QSslConfiguration::NextProtocolSpdy3_0 + \brief The value used for negotiating SPDY 3.0 during the Next + Protocol Negotiation. +*/ + +/*! + \variable QSslConfiguration::NextProtocolHttp1_1 + \brief The value used for negotiating HTTP 1.1 during the Next + Protocol Negotiation. +*/ + +/*! Constructs an empty SSL configuration. This configuration contains no valid settings and the state will be empty. isNull() will return true after this constructor is called. @@ -185,7 +216,10 @@ bool QSslConfiguration::operator==(const QSslConfiguration &other) const d->allowRootCertOnDemandLoading == other.d->allowRootCertOnDemandLoading && d->sslOptions == other.d->sslOptions && d->sslSession == other.d->sslSession && - d->sslSessionTicketLifeTimeHint == other.d->sslSessionTicketLifeTimeHint; + d->sslSessionTicketLifeTimeHint == other.d->sslSessionTicketLifeTimeHint && + d->nextAllowedProtocols == other.d->nextAllowedProtocols && + d->nextNegotiatedProtocol == other.d->nextNegotiatedProtocol && + d->nextProtocolNegotiationStatus == other.d->nextProtocolNegotiationStatus; } /*! @@ -221,7 +255,10 @@ bool QSslConfiguration::isNull() const d->peerCertificateChain.count() == 0 && d->sslOptions == QSslConfigurationPrivate::defaultSslOptions && d->sslSession.isNull() && - d->sslSessionTicketLifeTimeHint == -1); + d->sslSessionTicketLifeTimeHint == -1 && + d->nextAllowedProtocols.isEmpty() && + d->nextNegotiatedProtocol.isNull() && + d->nextProtocolNegotiationStatus == QSslConfiguration::NextProtocolNegotiationNone); } /*! @@ -653,6 +690,71 @@ int QSslConfiguration::sessionTicketLifeTimeHint() const } /*! + \since 5.3 + + This function returns the protocol negotiated with the server + if the Next Protocol Negotiation (NPN) TLS extension was enabled. + In order for the NPN extension to be enabled, setAllowedNextProtocols() + needs to be called explicitly before connecting to the server. + + If no protocol could be negotiated or the extension was not enabled, + this function returns a QByteArray which is null. + + \sa setAllowedNextProtocols(), nextProtocolNegotiationStatus() + */ +QByteArray QSslConfiguration::nextNegotiatedProtocol() const +{ + return d->nextNegotiatedProtocol; +} + +/*! + \since 5.3 + + This function sets the allowed \a protocols to be negotiated with the + server through the Next Protocol Negotiation (NPN) TLS extension; each + element in \a protocols must define one allowed protocol. + The function must be called explicitly before connecting to send the NPN + extension in the SSL handshake. + Whether or not the negotiation succeeded can be queried through + nextProtocolNegotiationStatus(). + + \sa nextNegotiatedProtocol(), nextProtocolNegotiationStatus(), allowedNextProtocols(), QSslConfiguration::NextProtocolSpdy3_0, QSslConfiguration::NextProtocolHttp1_1 + */ +void QSslConfiguration::setAllowedNextProtocols(QList<QByteArray> protocols) +{ + d->nextAllowedProtocols = protocols; +} + +/*! + \since 5.3 + + This function returns the allowed protocols to be negotiated with the + server through the Next Protocol Negotiation (NPN) TLS extension, as set + by setAllowedNextProtocols(). + + \sa nextNegotiatedProtocol(), nextProtocolNegotiationStatus(), setAllowedNextProtocols(), QSslConfiguration::NextProtocolSpdy3_0, QSslConfiguration::NextProtocolHttp1_1 + */ +QList<QByteArray> QSslConfiguration::allowedNextProtocols() const +{ + return d->nextAllowedProtocols; +} + +/*! + \since 5.3 + + This function returns the status of the Next Protocol Negotiation (NPN). + If the feature has not been enabled through setAllowedNextProtocols(), + this function returns NextProtocolNegotiationNone. + The status will be set before emitting the encrypted() signal. + + \sa setAllowedNextProtocols(), allowedNextProtocols(), nextNegotiatedProtocol(), QSslConfiguration::NextProtocolNegotiationStatus + */ +QSslConfiguration::NextProtocolNegotiationStatus QSslConfiguration::nextProtocolNegotiationStatus() const +{ + return d->nextProtocolNegotiationStatus; +} + +/*! Returns the default SSL configuration to be used in new SSL connections. @@ -663,7 +765,7 @@ int QSslConfiguration::sessionTicketLifeTimeHint() const \li protocol SecureProtocols (meaning either TLS 1.0 or SSL 3 will be used) \li the system's default CA certificate list \li the cipher list equal to the list of the SSL libraries' - supported SSL ciphers + supported SSL ciphers that are 128 bits or more \endlist \sa QSslSocket::supportedCiphers(), setDefaultConfiguration() diff --git a/src/network/ssl/qsslconfiguration.h b/src/network/ssl/qsslconfiguration.h index a48eceb63e..587187ca06 100644 --- a/src/network/ssl/qsslconfiguration.h +++ b/src/network/ssl/qsslconfiguration.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -131,6 +132,21 @@ public: static QSslConfiguration defaultConfiguration(); static void setDefaultConfiguration(const QSslConfiguration &configuration); + enum NextProtocolNegotiationStatus { + NextProtocolNegotiationNone, + NextProtocolNegotiationNegotiated, + NextProtocolNegotiationUnsupported + }; + + void setAllowedNextProtocols(QList<QByteArray> protocols); + QList<QByteArray> allowedNextProtocols() const; + + QByteArray nextNegotiatedProtocol() const; + NextProtocolNegotiationStatus nextProtocolNegotiationStatus() const; + + static const char NextProtocolSpdy3_0[]; + static const char NextProtocolHttp1_1[]; + private: friend class QSslSocket; friend class QSslConfigurationPrivate; diff --git a/src/network/ssl/qsslconfiguration_p.h b/src/network/ssl/qsslconfiguration_p.h index 71ee8d2bfe..d183c3335c 100644 --- a/src/network/ssl/qsslconfiguration_p.h +++ b/src/network/ssl/qsslconfiguration_p.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -86,7 +87,8 @@ public: allowRootCertOnDemandLoading(true), peerSessionShared(false), sslOptions(QSslConfigurationPrivate::defaultSslOptions), - sslSessionTicketLifeTimeHint(-1) + sslSessionTicketLifeTimeHint(-1), + nextProtocolNegotiationStatus(QSslConfiguration::NextProtocolNegotiationNone) { } QSslCertificate peerCertificate; @@ -114,6 +116,10 @@ public: QByteArray sslSession; int sslSessionTicketLifeTimeHint; + QList<QByteArray> nextAllowedProtocols; + QByteArray nextNegotiatedProtocol; + QSslConfiguration::NextProtocolNegotiationStatus nextProtocolNegotiationStatus; + // in qsslsocket.cpp: static QSslConfiguration defaultConfiguration(); static void setDefaultConfiguration(const QSslConfiguration &configuration); diff --git a/src/network/ssl/qsslcontext.cpp b/src/network/ssl/qsslcontext.cpp index adf42fb79a..551804ec79 100644 --- a/src/network/ssl/qsslcontext.cpp +++ b/src/network/ssl/qsslcontext.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -263,6 +264,45 @@ init_context: return sslContext; } +#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) + +static int next_proto_cb(SSL *, unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) +{ + QSslContext::NPNContext *ctx = reinterpret_cast<QSslContext::NPNContext *>(arg); + + // comment out to debug: +// QList<QByteArray> supportedVersions; +// for (unsigned int i = 0; i < inlen; ) { +// QByteArray version(reinterpret_cast<const char *>(&in[i+1]), in[i]); +// supportedVersions << version; +// i += in[i] + 1; +// } + + int proto = q_SSL_select_next_proto(out, outlen, in, inlen, ctx->data, ctx->len); + switch (proto) { + case OPENSSL_NPN_UNSUPPORTED: + ctx->status = QSslConfiguration::NextProtocolNegotiationNone; + break; + case OPENSSL_NPN_NEGOTIATED: + ctx->status = QSslConfiguration::NextProtocolNegotiationNegotiated; + break; + case OPENSSL_NPN_NO_OVERLAP: + ctx->status = QSslConfiguration::NextProtocolNegotiationUnsupported; + break; + default: + qWarning("OpenSSL sent unknown NPN status"); + } + + return SSL_TLSEXT_ERR_OK; +} + +QSslContext::NPNContext QSslContext::npnContext() const +{ + return m_npnContext; +} +#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... + // Needs to be deleted by caller SSL* QSslContext::createSsl() { @@ -283,6 +323,26 @@ SSL* QSslContext::createSsl() session = 0; } } + +#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) + QList<QByteArray> protocols = sslConfiguration.d->nextAllowedProtocols; + if (!protocols.isEmpty()) { + m_supportedNPNVersions.clear(); + for (int a = 0; a < protocols.count(); ++a) { + if (protocols.at(a).size() > 255) { + qWarning() << "TLS NPN extension" << protocols.at(a) + << "is too long and will be truncated to 255 characters."; + protocols[a] = protocols.at(a).left(255); + } + m_supportedNPNVersions.append(protocols.at(a).size()).append(protocols.at(a)); + } + m_npnContext.data = reinterpret_cast<unsigned char *>(m_supportedNPNVersions.data()); + m_npnContext.len = m_supportedNPNVersions.count(); + m_npnContext.status = QSslConfiguration::NextProtocolNegotiationNone; + q_SSL_CTX_set_next_proto_select_cb(ctx, next_proto_cb, &m_npnContext); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... + return ssl; } diff --git a/src/network/ssl/qsslcontext_p.h b/src/network/ssl/qsslcontext_p.h index 2b596798a6..20b27c1ce7 100644 --- a/src/network/ssl/qsslcontext_p.h +++ b/src/network/ssl/qsslcontext_p.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -72,6 +73,21 @@ public: QByteArray sessionASN1() const; void setSessionASN1(const QByteArray &sessionASN1); int sessionTicketLifeTimeHint() const; + +#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) + // must be public because we want to use it from an OpenSSL callback + struct NPNContext { + NPNContext() : data(0), + len(0), + status(QSslConfiguration::NextProtocolNegotiationNone) + { } + unsigned char *data; + unsigned short len; + QSslConfiguration::NextProtocolNegotiationStatus status; + }; + NPNContext npnContext() const; +#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... + protected: QSslContext(); @@ -84,6 +100,10 @@ private: QSslError::SslError errorCode; QString errorStr; QSslConfiguration sslConfiguration; +#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) + QByteArray m_supportedNPNVersions; + NPNContext m_npnContext; +#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... }; #endif // QT_NO_SSL diff --git a/src/network/ssl/qsslkey.cpp b/src/network/ssl/qsslkey.cpp index cf62f44855..95eed6e4b3 100644 --- a/src/network/ssl/qsslkey.cpp +++ b/src/network/ssl/qsslkey.cpp @@ -256,7 +256,7 @@ QSslKey::QSslKey(const QByteArray &encoded, QSsl::KeyAlgorithm algorithm, a valid key. */ QSslKey::QSslKey(QIODevice *device, QSsl::KeyAlgorithm algorithm, QSsl::EncodingFormat encoding, - QSsl::KeyType type, const QByteArray &passPhrase) + QSsl::KeyType type, const QByteArray &passPhrase) : d(new QSslKeyPrivate) { QByteArray encoded; diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 38b493a769..6edf4efae0 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -905,6 +906,9 @@ void QSslSocket::setSslConfiguration(const QSslConfiguration &configuration) d->configuration.sslOptions = configuration.d->sslOptions; d->configuration.sslSession = configuration.sessionTicket(); d->configuration.sslSessionTicketLifeTimeHint = configuration.sessionTicketLifeTimeHint(); + d->configuration.nextAllowedProtocols = configuration.allowedNextProtocols(); + d->configuration.nextNegotiatedProtocol = configuration.nextNegotiatedProtocol(); + d->configuration.nextProtocolNegotiationStatus = configuration.nextProtocolNegotiationStatus(); // if the CA certificates were set explicitly (either via // QSslConfiguration::setCaCertificates() or QSslSocket::setCaCertificates(), @@ -1195,12 +1199,9 @@ void QSslSocket::setCiphers(const QString &ciphers) Q_D(QSslSocket); d->configuration.ciphers.clear(); foreach (const QString &cipherName, ciphers.split(QLatin1String(":"),QString::SkipEmptyParts)) { - for (int i = 0; i < 3; ++i) { - // ### Crude - QSslCipher cipher(cipherName, QSsl::SslProtocol(i)); - if (!cipher.isNull()) - d->configuration.ciphers << cipher; - } + QSslCipher cipher(cipherName); + if (!cipher.isNull()) + d->configuration.ciphers << cipher; } } @@ -1953,6 +1954,7 @@ void QSslSocketPrivate::init() */ QList<QSslCipher> QSslSocketPrivate::defaultCiphers() { + QSslSocketPrivate::ensureInitialized(); QMutexLocker locker(&globalData()->mutex); return globalData()->config->ciphers; } diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp index 69b9e53884..3421154114 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -596,6 +596,7 @@ void QSslSocketPrivate::resetDefaultCiphers() SSL *mySsl = q_SSL_new(myCtx); QList<QSslCipher> ciphers; + QList<QSslCipher> defaultCiphers; STACK_OF(SSL_CIPHER) *supportedCiphers = q_SSL_get_ciphers(mySsl); for (int i = 0; i < q_sk_SSL_CIPHER_num(supportedCiphers); ++i) { @@ -603,8 +604,11 @@ void QSslSocketPrivate::resetDefaultCiphers() if (cipher->valid) { QSslCipher ciph = QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(cipher); if (!ciph.isNull()) { + // Unconditionally exclude ADH ciphers since they offer no MITM protection if (!ciph.name().toLower().startsWith(QLatin1String("adh"))) ciphers << ciph; + if (ciph.usedBits() >= 128) + defaultCiphers << ciph; } } } @@ -614,7 +618,7 @@ void QSslSocketPrivate::resetDefaultCiphers() q_SSL_free(mySsl); setDefaultSupportedCiphers(ciphers); - setDefaultCiphers(ciphers); + setDefaultCiphers(defaultCiphers); } QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates() @@ -1482,6 +1486,15 @@ void QSslSocketBackendPrivate::continueHandshake() } } +#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) + const unsigned char *proto; + unsigned int proto_len; + q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len); + QByteArray nextProtocol(reinterpret_cast<const char *>(proto), proto_len); + configuration.nextNegotiatedProtocol = nextProtocol; + configuration.nextProtocolNegotiationStatus = sslContextPointer->npnContext().status; +#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... + connectionEncrypted = true; emit q->encrypted(); if (autoStartHandshake && pendingClose) { diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp index ddf53f18f4..79bce22b0d 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols.cpp +++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -346,6 +347,20 @@ DEFINEFUNC(long, SSLeay, void, DUMMYARG, return 0, return) DEFINEFUNC(const char *, SSLeay_version, int a, a, return 0, return) DEFINEFUNC2(int, i2d_SSL_SESSION, SSL_SESSION *in, in, unsigned char **pp, pp, return 0, return) DEFINEFUNC3(SSL_SESSION *, d2i_SSL_SESSION, SSL_SESSION **a, a, const unsigned char **pp, pp, long length, length, return 0, return) +#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) +DEFINEFUNC6(int, SSL_select_next_proto, unsigned char **out, out, unsigned char *outlen, outlen, + const unsigned char *in, in, unsigned int inlen, inlen, + const unsigned char *client, client, unsigned int client_len, client_len, + return -1, return) +DEFINEFUNC3(void, SSL_CTX_set_next_proto_select_cb, SSL_CTX *s, s, + int (*cb) (SSL *ssl, unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, void *arg), cb, + void *arg, arg, return, DUMMYARG) +DEFINEFUNC3(void, SSL_get0_next_proto_negotiated, const SSL *s, s, + const unsigned char **data, data, unsigned *len, len, return, DUMMYARG) +#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... #define RESOLVEFUNC(func) \ if (!(_q_##func = _q_PTR_##func(libs.first->resolve(#func))) \ @@ -815,6 +830,11 @@ bool q_resolveOpenSslSymbols() RESOLVEFUNC(SSLeay_version) RESOLVEFUNC(i2d_SSL_SESSION) RESOLVEFUNC(d2i_SSL_SESSION) +#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) + RESOLVEFUNC(SSL_select_next_proto) + RESOLVEFUNC(SSL_CTX_set_next_proto_select_cb) + RESOLVEFUNC(SSL_get0_next_proto_negotiated) +#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... symbolsResolved = true; delete libs.first; diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h index 7e1a1c983c..500fe9493b 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols_p.h +++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -79,13 +80,13 @@ QT_BEGIN_NAMESPACE // **************** Shared declarations ****************** // ret func(arg) -# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ - typedef ret (*_q_PTR_##func)(arg); \ - static _q_PTR_##func _q_##func = 0; \ - ret q_##func(arg) { \ +# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg) { \ if (Q_UNLIKELY(!_q_##func)) { \ qsslSocketUnresolvedSymbolWarning(#func); \ - err; \ + err; \ } \ funcret _q_##func(a); \ } @@ -180,8 +181,8 @@ QT_BEGIN_NAMESPACE // **************** Static declarations ****************** // ret func(arg) -# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ - ret q_##func(arg) { funcret func(a); } +# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ + ret q_##func(arg) { funcret func(a); } // ret func(arg1, arg2) # define DEFINEFUNC2(ret, func, arg1, a, arg2, b, err, funcret) \ @@ -384,7 +385,7 @@ int q_X509_cmp(X509 *a, X509 *b); #ifdef SSLEAY_MACROS void *q_ASN1_dup(i2d_of_void *i2d, d2i_of_void *d2i, char *x); #define q_X509_dup(x509) (X509 *)q_ASN1_dup((i2d_of_void *)q_i2d_X509, \ - (d2i_of_void *)q_d2i_X509,(char *)x509) + (d2i_of_void *)q_d2i_X509,(char *)x509) #else X509 *q_X509_dup(X509 *a); #endif @@ -429,22 +430,22 @@ STACK_OF(X509) *q_X509_STORE_CTX_get_chain(X509_STORE_CTX *ctx); #define q_BIO_get_mem_data(b, pp) (int)q_BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)pp) #define q_BIO_pending(b) (int)q_BIO_ctrl(b,BIO_CTRL_PENDING,0,NULL) #ifdef SSLEAY_MACROS -int q_i2d_DSAPrivateKey(const DSA *a, unsigned char **pp); -int q_i2d_RSAPrivateKey(const RSA *a, unsigned char **pp); +int q_i2d_DSAPrivateKey(const DSA *a, unsigned char **pp); +int q_i2d_RSAPrivateKey(const RSA *a, unsigned char **pp); RSA *q_d2i_RSAPrivateKey(RSA **a, unsigned char **pp, long length); DSA *q_d2i_DSAPrivateKey(DSA **a, unsigned char **pp, long length); -#define q_PEM_read_bio_RSAPrivateKey(bp, x, cb, u) \ +#define q_PEM_read_bio_RSAPrivateKey(bp, x, cb, u) \ (RSA *)q_PEM_ASN1_read_bio( \ (void *(*)(void**, const unsigned char**, long int))q_d2i_RSAPrivateKey, PEM_STRING_RSA, bp, (void **)x, cb, u) -#define q_PEM_read_bio_DSAPrivateKey(bp, x, cb, u) \ +#define q_PEM_read_bio_DSAPrivateKey(bp, x, cb, u) \ (DSA *)q_PEM_ASN1_read_bio( \ (void *(*)(void**, const unsigned char**, long int))q_d2i_DSAPrivateKey, PEM_STRING_DSA, bp, (void **)x, cb, u) -#define q_PEM_write_bio_RSAPrivateKey(bp,x,enc,kstr,klen,cb,u) \ - PEM_ASN1_write_bio((int (*)(void*, unsigned char**))q_i2d_RSAPrivateKey,PEM_STRING_RSA,\ - bp,(char *)x,enc,kstr,klen,cb,u) -#define q_PEM_write_bio_DSAPrivateKey(bp,x,enc,kstr,klen,cb,u) \ - PEM_ASN1_write_bio((int (*)(void*, unsigned char**))q_i2d_DSAPrivateKey,PEM_STRING_DSA,\ - bp,(char *)x,enc,kstr,klen,cb,u) +#define q_PEM_write_bio_RSAPrivateKey(bp,x,enc,kstr,klen,cb,u) \ + PEM_ASN1_write_bio((int (*)(void*, unsigned char**))q_i2d_RSAPrivateKey,PEM_STRING_RSA,\ + bp,(char *)x,enc,kstr,klen,cb,u) +#define q_PEM_write_bio_DSAPrivateKey(bp,x,enc,kstr,klen,cb,u) \ + PEM_ASN1_write_bio((int (*)(void*, unsigned char**))q_i2d_DSAPrivateKey,PEM_STRING_DSA,\ + bp,(char *)x,enc,kstr,klen,cb,u) #endif #define q_SSL_CTX_set_options(ctx,op) q_SSL_CTX_ctrl((ctx),SSL_CTRL_OPTIONS,(op),NULL) #define q_SSL_CTX_set_mode(ctx,op) q_SSL_CTX_ctrl((ctx),SSL_CTRL_MODE,(op),NULL) @@ -461,9 +462,9 @@ DSA *q_d2i_DSAPrivateKey(DSA **a, unsigned char **pp, long length); #define q_X509_get_notAfter(x) X509_get_notAfter(x) #define q_X509_get_notBefore(x) X509_get_notBefore(x) #define q_EVP_PKEY_assign_RSA(pkey,rsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_RSA,\ - (char *)(rsa)) + (char *)(rsa)) #define q_EVP_PKEY_assign_DSA(pkey,dsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_DSA,\ - (char *)(dsa)) + (char *)(dsa)) #define q_OpenSSL_add_all_algorithms() q_OPENSSL_add_all_algorithms_conf() void q_OPENSSL_add_all_algorithms_noconf(); void q_OPENSSL_add_all_algorithms_conf(); @@ -473,6 +474,20 @@ const char *q_SSLeay_version(int type); int q_i2d_SSL_SESSION(SSL_SESSION *in, unsigned char **pp); SSL_SESSION *q_d2i_SSL_SESSION(SSL_SESSION **a, const unsigned char **pp, long length); +#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) +int q_SSL_select_next_proto(unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + const unsigned char *client, unsigned int client_len); +void q_SSL_CTX_set_next_proto_select_cb(SSL_CTX *s, + int (*cb) (SSL *ssl, unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, void *arg), + void *arg); +void q_SSL_get0_next_proto_negotiated(const SSL *s, const unsigned char **data, + unsigned *len); +#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... + // Helper function class QDateTime; QDateTime q_getTimeFromASN1(const ASN1_TIME *aTime); |