diff options
Diffstat (limited to 'Source/WebKit2/NetworkProcess/Downloads/qt/QtFileDownloader.cpp')
-rw-r--r-- | Source/WebKit2/NetworkProcess/Downloads/qt/QtFileDownloader.cpp | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/Source/WebKit2/NetworkProcess/Downloads/qt/QtFileDownloader.cpp b/Source/WebKit2/NetworkProcess/Downloads/qt/QtFileDownloader.cpp new file mode 100644 index 000000000..0ec67704c --- /dev/null +++ b/Source/WebKit2/NetworkProcess/Downloads/qt/QtFileDownloader.cpp @@ -0,0 +1,249 @@ +/* + * 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 <QCoreApplication> +#include <QFile> +#include <QFileInfo> +#include <QNetworkAccessManager> +#include <WebCore/QNetworkReplyHandler.h> +#include <WebCore/ResourceError.h> +#include <WebCore/ResourceResponse.h> + +using namespace WebCore; +using namespace WTF; + +namespace WebKit { + +QtFileDownloader::QtFileDownloader(Download* download, QNetworkReply* reply) + : m_download(download) + , m_reply(reply) + , m_error(QNetworkReply::NoError) + , m_headersRead(false) +{ +} + +QtFileDownloader::~QtFileDownloader() +{ + if (!m_destinationFile) + return; + + abortDownloadWritingAndEmitError(QtFileDownloader::DownloadErrorAborted); +} + +void QtFileDownloader::init() +{ + 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))); +} + +QString QtFileDownloader::determineFilename() +{ + ASSERT(!m_destinationFile); + + QString filenameCandidate = filenameFromHTTPContentDisposition(QString::fromLatin1(m_reply->rawHeader("Content-Disposition"))); + if (filenameCandidate.isEmpty()) { + URL kurl = m_reply->url(); + filenameCandidate = decodeURLEscapeSequences(kurl.lastPathComponent()); + } + + if (filenameCandidate.isEmpty()) { + abortDownloadWritingAndEmitError(QtFileDownloader::DownloadErrorCannotDetermineFilename); + return QString(); + } + + // Make sure that we remove possible "../.." parts in the given file name. + QFileInfo filenameFilter(filenameCandidate); + QString filename = filenameFilter.fileName(); + + if (filename.isEmpty()) { + abortDownloadWritingAndEmitError(QtFileDownloader::DownloadErrorCannotDetermineFilename); + return QString(); + } + + return filename; +} + +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<QFile>(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); + String filename = determineFilename(); + + // If filename is empty it means determineFilename aborted and emitted an error. + if (filename.isEmpty()) + return; + + // 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, filename); + 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" |