/* * 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 #include #include #include #include #include #include using namespace WebCore; using namespace WTF; namespace WebKit { QtFileDownloader::QtFileDownloader(Download* download, PassOwnPtr 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()) { KURL 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; } OwnPtr downloadFile = adoptPtr(new 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 = downloadFile.release(); 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()), CoreIPC::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().toString(), translatedErrorMessage); m_download->didFail(downloadError, CoreIPC::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.clear(); 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"