/* * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this program; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "config.h" #include "QtFileDownloader.h" #include "DataReference.h" #include "Download.h" #include "HTTPParsers.h" #include "MIMETypeRegistry.h" #include "NetworkProcess.h" #include #include #include #include #include #include #include using namespace WebCore; using namespace WTF; namespace WebKit { QtFileDownloader::QtFileDownloader(Download& download, const QNetworkRequest& request) : m_download(download) , m_reply(NetworkProcess::singleton().networkAccessManager().get(request)) , m_error(QNetworkReply::NoError) { makeConnections(); } QtFileDownloader::QtFileDownloader(Download& download, QNetworkReply* reply) : m_download(download) , m_reply(reply) , m_error(reply->error()) { makeConnections(); if (reply->isFinished()) onFinished(); else if (reply->isReadable()) onReadyRead(); } QtFileDownloader::~QtFileDownloader() { if (!m_destinationFile) return; abortDownloadWritingAndEmitError(QtFileDownloader::DownloadErrorAborted); } void QtFileDownloader::makeConnections() { connect(m_reply.get(), SIGNAL(readyRead()), SLOT(onReadyRead())); connect(m_reply.get(), SIGNAL(finished()), SLOT(onFinished())); connect(m_reply.get(), SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onError(QNetworkReply::NetworkError))); } void QtFileDownloader::startTransfer(const QString& decidedFilePath) { ASSERT(!m_destinationFile); // Error might have occured during destination query. if (m_error != QNetworkReply::NoError) { abortDownloadWritingAndEmitError(QtFileDownloader::DownloadErrorNetworkFailure); return; } if (decidedFilePath.isEmpty()) { abortDownloadWritingAndEmitError(QtFileDownloader::DownloadErrorCancelled); return; } auto downloadFile = std::make_unique(decidedFilePath); if (!downloadFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) { abortDownloadWritingAndEmitError(QtFileDownloader::DownloadErrorCannotOpenFile); return; } // Assigning to m_destinationFile flags that either error or // finished shall be called in the end. m_destinationFile = WTFMove(downloadFile); m_download.didCreateDestination(m_destinationFile->fileName()); // We might have gotten readyRead already even before this function // was called. if (m_reply->bytesAvailable()) onReadyRead(); // We might have gotten finished already even before this // function was called. if (m_reply->isFinished()) onFinished(); } void QtFileDownloader::abortDownloadWritingAndEmitError(QtFileDownloader::DownloadError errorCode) { m_reply->abort(); // On network failures it's QNetworkReplyHandler::errorForReply who will handle errors. if (errorCode == QtFileDownloader::DownloadErrorNetworkFailure) { m_download.didFail(QNetworkReplyHandler::errorForReply(m_reply.get()), IPC::DataReference(0, 0)); return; } QString translatedErrorMessage; switch (errorCode) { case QtFileDownloader::DownloadErrorAborted: translatedErrorMessage = QCoreApplication::translate("QtFileDownloader", "Download aborted"); break; case QtFileDownloader::DownloadErrorCannotWriteToFile: translatedErrorMessage = QCoreApplication::translate("QtFileDownloader", "Cannot write to file"); break; case QtFileDownloader::DownloadErrorCannotOpenFile: translatedErrorMessage = QCoreApplication::translate("QtFileDownloader", "Cannot open file for writing"); break; case QtFileDownloader::DownloadErrorDestinationAlreadyExists: translatedErrorMessage = QCoreApplication::translate("QtFileDownloader", "Destination already exists"); break; case QtFileDownloader::DownloadErrorCancelled: translatedErrorMessage = QCoreApplication::translate("QtFileDownloader", "Download cancelled by caller"); break; case QtFileDownloader::DownloadErrorCannotDetermineFilename: translatedErrorMessage = QCoreApplication::translate("QtFileDownloader", "Cannot determine filename"); break; default: ASSERT_NOT_REACHED(); } ResourceError downloadError("Download", errorCode, m_reply->url(), translatedErrorMessage); m_download.didFail(downloadError, IPC::DataReference(0, 0)); } void QtFileDownloader::handleDownloadResponse() { // By API contract, QNetworkReply::metaDataChanged cannot really be trusted. // Thus we need to call this upon receiving first data. String contentType = m_reply->header(QNetworkRequest::ContentTypeHeader).toString(); String encoding = extractCharsetFromMediaType(contentType); String mimeType = extractMIMETypeFromMediaType(contentType); // Let's try to guess from the extension. if (mimeType.isEmpty()) mimeType = MIMETypeRegistry::getMIMETypeForPath(m_reply->url().path()); ResourceResponse response(m_reply->url(), mimeType, m_reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), encoding); m_download.didReceiveResponse(response); } void QtFileDownloader::onReadyRead() { if (m_destinationFile) { QByteArray content = m_reply->readAll(); if (content.size() <= 0) return; qint64 bytesWritten = m_destinationFile->write(content); if (bytesWritten == -1) { abortDownloadWritingAndEmitError(QtFileDownloader::DownloadErrorCannotWriteToFile); return; } // There might a corner case to be fixed here if bytesWritten != content.size() // does not actually represent an error. ASSERT(bytesWritten == content.size()); m_download.didReceiveData(bytesWritten); } else if (!m_headersRead) { handleDownloadResponse(); m_headersRead = true; } } void QtFileDownloader::onFinished() { // If it's finished and we haven't even read the headers, it means we never got to onReadyRead and that we are // probably dealing with the download of a local file or of a small file that was started with a handle. if (!m_headersRead) { handleDownloadResponse(); m_headersRead = true; return; } if (!m_destinationFile) return; m_destinationFile = nullptr; if (m_error == QNetworkReply::NoError) m_download.didFinish(); else if (m_error == QNetworkReply::OperationCanceledError) abortDownloadWritingAndEmitError(QtFileDownloader::DownloadErrorCancelled); else abortDownloadWritingAndEmitError(QtFileDownloader::DownloadErrorNetworkFailure); } void QtFileDownloader::onError(QNetworkReply::NetworkError code) { m_error = code; } void QtFileDownloader::cancel() { m_reply->abort(); // QtFileDownloader::onFinished() will be called and will raise a DownloadErrorCancelled. } } // namespace WebKit #include "moc_QtFileDownloader.cpp"