/**************************************************************************** ** ** 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 "qnetworkreplynsurlconnectionimpl_p.h" #include "QtCore/qdatetime.h" #include #include #include QT_BEGIN_NAMESPACE // Network reply implementation using NSUrlConnection. // // Class/object structure: // // QNetworkReplyNSURLConnectionImpl // |- QNetworkReplyNSURLConnectionImplPrivate // |- (bytes read) // |- (QIODevice and CFStream for async POST data transfer) // |- NSURLConnection // |- QtNSURLConnectionDelegate // |- NSURLResponse/NSHTTPURLResponse // |- (response data) // // The main entry point is the QNetworkReplyNSURLConnectionImpl constructor, which // receives a network request from QNetworkAccessManager. The constructor // creates a NSURLRequest and initiates a NSURLConnection with a QtNSURLConnectionDelegate. // The delegate callbacks are then called asynchronously as the request completes. // @class QtNSURLConnectionDelegate; class QNetworkReplyNSURLConnectionImplPrivate: public QNetworkReplyPrivate { public: QNetworkReplyNSURLConnectionImplPrivate(); virtual ~QNetworkReplyNSURLConnectionImplPrivate(); Q_DECLARE_PUBLIC(QNetworkReplyNSURLConnectionImpl) NSURLConnection * urlConnection; QtNSURLConnectionDelegate *urlConnectionDelegate; qint64 bytesRead; // Sequental outgiong data streaming QIODevice *outgoingData; CFReadStreamRef readStream; CFWriteStreamRef writeStream; CFIndex transferBufferSize; // Forwarding functions to the public class. void setFinished(); void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value); void setRawHeader(const QByteArray &headerName, const QByteArray &value); void setError(QNetworkReply::NetworkError errorCode, const QString &errorString); void setAttribute(QNetworkRequest::Attribute code, const QVariant &value); }; @interface QtNSURLConnectionDelegate : NSObject { NSURLResponse *response; NSMutableData *responseData; QNetworkReplyNSURLConnectionImplPrivate * replyprivate; } - (id)initWithQNetworkReplyNSURLConnectionImplPrivate:(QNetworkReplyNSURLConnectionImplPrivate *)a_replyPrivate ; #if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_3_0) - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; #endif - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError*)error; - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response; - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData*)data; - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite; - (NSCachedURLResponse*)connection:(NSURLConnection*)connection willCacheResponse:(NSCachedURLResponse*)cachedResponse; - (NSURLRequest*)connection:(NSURLConnection*)connection willSendRequest:(NSURLRequest*)request redirectResponse:(NSURLResponse*)redirectResponse; - (void)connectionDidFinishLoading:(NSURLConnection*)connection; - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection*)connection; @end QNetworkReplyNSURLConnectionImplPrivate::QNetworkReplyNSURLConnectionImplPrivate() : QNetworkReplyPrivate() , urlConnection(0) , urlConnectionDelegate(0) , bytesRead(0) , readStream(0) , writeStream(0) , transferBufferSize(4096) { } QNetworkReplyNSURLConnectionImplPrivate::~QNetworkReplyNSURLConnectionImplPrivate() { [urlConnection cancel]; [urlConnection release]; [urlConnectionDelegate release]; if (readStream) CFRelease(readStream); if (writeStream) CFRelease(writeStream); } void QNetworkReplyNSURLConnectionImplPrivate::setFinished() { q_func()->setFinished(true); QMetaObject::invokeMethod(q_func(), "finished", Qt::QueuedConnection); } void QNetworkReplyNSURLConnectionImplPrivate::setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value) { q_func()->setHeader(header, value); } void QNetworkReplyNSURLConnectionImplPrivate::setRawHeader(const QByteArray &headerName, const QByteArray &value) { q_func()->setRawHeader(headerName, value); } void QNetworkReplyNSURLConnectionImplPrivate::setError(QNetworkReply::NetworkError errorCode, const QString &errorString) { q_func()->setError(errorCode, errorString); } void QNetworkReplyNSURLConnectionImplPrivate::setAttribute(QNetworkRequest::Attribute code, const QVariant &value) { q_func()->setAttribute(code, value); } void QNetworkReplyNSURLConnectionImpl::readyReadOutgoingData() { Q_D(QNetworkReplyNSURLConnectionImpl); int bytesRead = 0; do { char data[d->transferBufferSize]; bytesRead = d->outgoingData->read(data, d->transferBufferSize); if (bytesRead <= 0) break; CFIndex bytesWritten = CFWriteStreamWrite(d->writeStream, reinterpret_cast(data), bytesRead); if (bytesWritten != bytesRead) { CFErrorRef err = CFWriteStreamCopyError(d->writeStream); qWarning() << "QNetworkReplyNSURLConnectionImpl: CFWriteStreamWrite error" << (err ? QString::number(CFErrorGetCode(err)) : QStringLiteral("")); } } while (bytesRead > 0); if (d->outgoingData->atEnd()) CFWriteStreamClose(d->writeStream); } @interface QtNSURLConnectionDelegate () @property (nonatomic, retain) NSURLResponse* response; @property (nonatomic, retain) NSMutableData* responseData; @end @implementation QtNSURLConnectionDelegate @synthesize response; @synthesize responseData; - (id)initWithQNetworkReplyNSURLConnectionImplPrivate:(QNetworkReplyNSURLConnectionImplPrivate *)a_replyPrivate { if (self = [super init]) replyprivate = a_replyPrivate; return self; } - (void)dealloc { [response release]; [responseData release]; [super dealloc]; } #if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_7, __IPHONE_3_0) - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { Q_UNUSED(connection) Q_UNUSED(challenge) if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; SecTrustResultType resultType; SecTrustEvaluate(serverTrust, &resultType); if (resultType == kSecTrustResultUnspecified) { // All good [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge]; } else if (resultType == kSecTrustResultRecoverableTrustFailure) { // Certificate verification error, ask user // ### TODO actually ask user // (test site: https://testssl-expire.disig.sk/index.en.html) qWarning() << "QNetworkReplyNSURLConnection: Certificate verification error handlig is" << "not implemented. Connection will time out."; } else { // other error, which the default handler will handle [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge]; } } [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge]; } #endif - (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error { Q_UNUSED(connection) QNetworkReply::NetworkError qtError = QNetworkReply::UnknownNetworkError; if ([error domain] == NSURLErrorDomain) { switch ([error code]) { case NSURLErrorTimedOut: qtError = QNetworkReply::TimeoutError; break; case NSURLErrorUnsupportedURL: qtError = QNetworkReply::ProtocolUnknownError; break; case NSURLErrorCannotFindHost: qtError = QNetworkReply::HostNotFoundError; break; case NSURLErrorCannotConnectToHost: qtError = QNetworkReply::ConnectionRefusedError; break; case NSURLErrorNetworkConnectionLost: qtError = QNetworkReply::NetworkSessionFailedError; break; case NSURLErrorDNSLookupFailed: qtError = QNetworkReply::HostNotFoundError; break; case NSURLErrorNotConnectedToInternet: qtError = QNetworkReply::NetworkSessionFailedError; break; case NSURLErrorUserAuthenticationRequired: qtError = QNetworkReply::AuthenticationRequiredError; break; default: break; } } replyprivate->setError(qtError, QString::fromNSString([error localizedDescription])); } - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)aResponse { Q_UNUSED(connection) self.response = aResponse; self.responseData = [NSMutableData data]; // copy headers if ([aResponse isKindOfClass:[NSHTTPURLResponse class]]) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)aResponse; NSDictionary *headers = [httpResponse allHeaderFields]; for (NSString *key in headers) { NSString *value = [headers objectForKey:key]; replyprivate->setRawHeader(QString::fromNSString(key).toUtf8(), QString::fromNSString(value).toUtf8()); } int code = [httpResponse statusCode]; replyprivate->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, code); } else { if ([aResponse expectedContentLength] != NSURLResponseUnknownLength) replyprivate->setHeader(QNetworkRequest::ContentLengthHeader, [aResponse expectedContentLength]); } QMetaObject::invokeMethod(replyprivate->q_func(), "metaDataChanged", Qt::QueuedConnection); } - (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data { Q_UNUSED(connection) [responseData appendData:data]; if ([response expectedContentLength] != NSURLResponseUnknownLength) { QMetaObject::invokeMethod(replyprivate->q_func(), "downloadProgress", Qt::QueuedConnection, Q_ARG(qint64, qint64([responseData length])), Q_ARG(qint64, qint64([response expectedContentLength]))); } QMetaObject::invokeMethod(replyprivate->q_func(), "readyRead", Qt::QueuedConnection); } - (void)connection:(NSURLConnection*)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { Q_UNUSED(connection) Q_UNUSED(bytesWritten) QMetaObject::invokeMethod(replyprivate->q_func(), "uploadProgress", Qt::QueuedConnection, Q_ARG(qint64, qint64(totalBytesWritten)), Q_ARG(qint64, qint64(totalBytesExpectedToWrite))); } - (NSCachedURLResponse*)connection:(NSURLConnection*)connection willCacheResponse:(NSCachedURLResponse*)cachedResponse { Q_UNUSED(connection) return cachedResponse; } - (NSURLRequest*)connection:(NSURLConnection*)connection willSendRequest:(NSURLRequest*)request redirectResponse:(NSURLResponse*)redirectResponse { Q_UNUSED(connection) Q_UNUSED(redirectResponse) return request; } - (void)connectionDidFinishLoading:(NSURLConnection*)connection { Q_UNUSED(connection) replyprivate->setFinished(); } - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection*)connection { Q_UNUSED(connection) return YES; } @end QNetworkReplyNSURLConnectionImpl::~QNetworkReplyNSURLConnectionImpl() { } QNetworkReplyNSURLConnectionImpl::QNetworkReplyNSURLConnectionImpl(QObject *parent, const QNetworkRequest &request, const QNetworkAccessManager::Operation operation, QIODevice* outgoingData) : QNetworkReply(*new QNetworkReplyNSURLConnectionImplPrivate(), parent) { setRequest(request); setUrl(request.url()); setOperation(operation); QNetworkReply::open(QIODevice::ReadOnly); QNetworkReplyNSURLConnectionImplPrivate *d = (QNetworkReplyNSURLConnectionImplPrivate*) d_func(); QUrl url = request.url(); if (url.host() == QLatin1String("localhost")) url.setHost(QString()); if (url.path().isEmpty()) url.setPath(QLatin1String("/")); setUrl(url); // Create a NSMutableURLRequest from QNetworkRequest NSMutableURLRequest *nsRequest = [NSMutableURLRequest requestWithURL:request.url().toNSURL() cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; // copy headers foreach (const QByteArray &header, request.rawHeaderList()) { QByteArray headerValue = request.rawHeader(header); [nsRequest addValue:QString::fromUtf8(headerValue).toNSString() forHTTPHeaderField:QString::fromUtf8(header).toNSString()]; } if (operation == QNetworkAccessManager::GetOperation) [nsRequest setHTTPMethod:@"GET"]; else if (operation == QNetworkAccessManager::PostOperation) [nsRequest setHTTPMethod:@"POST"]; else if (operation == QNetworkAccessManager::PutOperation) [nsRequest setHTTPMethod:@"PUT"]; else if (operation == QNetworkAccessManager::DeleteOperation) [nsRequest setHTTPMethod:@"DELETE"]; else qWarning() << "QNetworkReplyNSURLConnection: Unsupported netork operation" << operation; if (outgoingData) { d->outgoingData = outgoingData; if (outgoingData->isSequential()) { // set up streaming from outgoingData iodevice to request CFStreamCreateBoundPair(kCFAllocatorDefault, &d->readStream, &d->writeStream, d->transferBufferSize); CFWriteStreamOpen(d->writeStream); [nsRequest setHTTPBodyStream:reinterpret_cast(d->readStream)]; connect(outgoingData, SIGNAL(readyRead()), this, SLOT(readyReadOutgoingData())); readyReadOutgoingData(); } else { // move all data at once QByteArray data = outgoingData->readAll(); [nsRequest setHTTPBody:[NSData dataWithBytes:data.constData() length:data.length()]]; } } // Create connection d->urlConnectionDelegate = [[QtNSURLConnectionDelegate alloc] initWithQNetworkReplyNSURLConnectionImplPrivate:d]; d->urlConnection = [[NSURLConnection alloc] initWithRequest:nsRequest delegate:d->urlConnectionDelegate]; if (!d->urlConnection) { // ### what type of error is an initWithRequest fail? setError(QNetworkReply::ProtocolUnknownError, QStringLiteral("QNetworkReplyNSURLConnection internal error")); } } void QNetworkReplyNSURLConnectionImpl::close() { // No-op? Network ops should continue (especially POSTs) QNetworkReply::close(); } void QNetworkReplyNSURLConnectionImpl::abort() { Q_D(QNetworkReplyNSURLConnectionImpl); [d->urlConnection cancel]; QNetworkReply::close(); } qint64 QNetworkReplyNSURLConnectionImpl::bytesAvailable() const { Q_D(const QNetworkReplyNSURLConnectionImpl); qint64 available = QNetworkReply::bytesAvailable() + [[d->urlConnectionDelegate responseData] length] - d->bytesRead; return available; } bool QNetworkReplyNSURLConnectionImpl::isSequential() const { return true; } qint64 QNetworkReplyNSURLConnectionImpl::size() const { Q_D(const QNetworkReplyNSURLConnectionImpl); return [[d->urlConnectionDelegate responseData] length]; } /*! \internal */ qint64 QNetworkReplyNSURLConnectionImpl::readData(char *data, qint64 maxlen) { Q_D(QNetworkReplyNSURLConnectionImpl); qint64 dataSize = [[d->urlConnectionDelegate responseData] length]; qint64 canRead = qMin(maxlen, dataSize - d->bytesRead); const char *sourceBase = static_cast([[d->urlConnectionDelegate responseData] bytes]); memcpy(data, sourceBase + d->bytesRead, canRead); d->bytesRead += canRead; return canRead; } QT_END_NAMESPACE