/* * Copyright (c) 2010-2024 Belledonne Communications SARL. * * This file is part of linphone-desktop * (see https://www.linphone.org). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "core/App.hpp" #include "core/path/Paths.hpp" #include "tool/Utils.hpp" #include #include #include "FileDownloader.hpp" DEFINE_ABSTRACT_OBJECT(FileDownloader) // ============================================================================= static QString getDownloadFilePath(const QString &folder, const QUrl &url, const bool &overwrite) { QString defaultFileName = QString(Constants::DownloadDefaultFileName); QFileInfo fileInfo(url.path()); QString fileName = fileInfo.fileName(); if (fileName.isEmpty()) fileName = defaultFileName; fileName.prepend(folder); if (overwrite && QFile::exists(fileName)) QFile::remove(fileName); if (!QFile::exists(fileName)) return fileName; // Already exists, don't overwrite. QString baseName = fileInfo.completeBaseName(); if (baseName.isEmpty()) baseName = defaultFileName; QString suffix = fileInfo.suffix(); if (!suffix.isEmpty()) suffix.prepend("."); for (int i = 1; true; ++i) { fileName = folder + baseName + "(" + QString::number(i) + ")" + suffix; if (!QFile::exists(fileName)) break; } return fileName; } static bool isHttpRedirect(QNetworkReply *reply) { Q_CHECK_PTR(reply); int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); return statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305 || statusCode == 307 || statusCode == 308; } // ----------------------------------------------------------------------------- void FileDownloader::download() { if (mDownloading) { lWarning() << log().arg("Unable to download file. Already downloading!"); return; } setDownloading(true); QNetworkRequest request(mUrl); mNetworkReply = mManager.get(request); QNetworkReply *data = mNetworkReply.data(); QObject::connect(data, &QNetworkReply::readyRead, this, &FileDownloader::handleReadyData); QObject::connect(data, &QNetworkReply::finished, this, &FileDownloader::handleDownloadFinished); QObject::connect(data, &QNetworkReply::errorOccurred, this, &FileDownloader::handleError); QObject::connect(data, &QNetworkReply::downloadProgress, this, &FileDownloader::handleDownloadProgress); #if QT_CONFIG(ssl) QObject::connect(data, &QNetworkReply::sslErrors, this, &FileDownloader::handleSslErrors); #endif if (mDownloadFolder.isEmpty()) { mDownloadFolder = App::getInstance()->getSettings()->getDownloadFolder(); emit downloadFolderChanged(mDownloadFolder); } Q_ASSERT(!mDestinationFile.isOpen()); mDestinationFile.setFileName( getDownloadFilePath(QDir::cleanPath(mDownloadFolder) + QDir::separator(), mUrl, mOverwriteFile)); if (!mDestinationFile.open(QIODevice::WriteOnly)) emitOutputError(); else { mTimeoutReadBytes = 0; mTimeout.start(); } } bool FileDownloader::remove() { return mDestinationFile.exists() && !mDestinationFile.isOpen() && mDestinationFile.remove(); } void FileDownloader::emitOutputError() { lWarning() << log() .arg("Could not write into `%1` (%2).") .arg(mDestinationFile.fileName()) .arg(mDestinationFile.errorString()); mNetworkReply->abort(); } void FileDownloader::cleanDownloadEnd() { mTimeout.stop(); mNetworkReply->deleteLater(); setDownloading(false); } void FileDownloader::handleReadyData() { QByteArray data = mNetworkReply->readAll(); if (mDestinationFile.write(data) == -1) emitOutputError(); } void FileDownloader::handleDownloadFinished() { if (mNetworkReply->error() != QNetworkReply::NoError) return; if (isHttpRedirect(mNetworkReply)) { lWarning() << log().arg("Request was redirected."); mDestinationFile.remove(); cleanDownloadEnd(); emit downloadFailed(); } else { lInfo() << log().arg("Download of %1 finished to %2").arg(mUrl.toString(), mDestinationFile.fileName()); mDestinationFile.close(); cleanDownloadEnd(); QString fileChecksum = Utils::getFileChecksum(mDestinationFile.fileName()); if (mCheckSum.isEmpty() || fileChecksum == mCheckSum) emit downloadFinished(mDestinationFile.fileName()); else { lCritical() << log().arg("File cannot be downloaded : Bad checksum ") << fileChecksum; mDestinationFile.remove(); emit downloadFailed(); } } } void FileDownloader::handleError(QNetworkReply::NetworkError code) { if (code != QNetworkReply::OperationCanceledError) lWarning() << log().arg("Download of %1 failed: %2").arg(mUrl.toString()).arg(mNetworkReply->errorString()); mDestinationFile.remove(); cleanDownloadEnd(); emit downloadFailed(); } void FileDownloader::handleSslErrors(const QList &sslErrors) { #if QT_CONFIG(ssl) for (const QSslError &error : sslErrors) lWarning() << log().arg("SSL error: %1").arg(error.errorString()); #else Q_UNUSED(sslErrors); #endif } void FileDownloader::handleTimeout() { if (mReadBytes == mTimeoutReadBytes) { lWarning() << log().arg("Download of %1 failed: timeout.").arg(mUrl.toString()); mNetworkReply->abort(); } else mTimeoutReadBytes = mReadBytes; } void FileDownloader::handleDownloadProgress(qint64 readBytes, qint64 totalBytes) { setReadBytes(readBytes); setTotalBytes(totalBytes); } // ----------------------------------------------------------------------------- QUrl FileDownloader::getUrl() const { return mUrl; } void FileDownloader::setUrl(const QUrl &url) { if (mDownloading) { lWarning() << log().arg("Unable to set url, a file is downloading."); return; } if (mUrl != url) { mUrl = url; if (!QSslSocket::supportsSsl() && mUrl.scheme() == "https") { lWarning() << log().arg( "Https has been requested but SSL is not supported. Fallback to http. Install manually " "OpenSSL libraries in your PATH."); mUrl.setScheme("http"); } emit urlChanged(mUrl); } } QString FileDownloader::getDownloadFolder() const { return mDownloadFolder; } void FileDownloader::setDownloadFolder(const QString &downloadFolder) { if (mDownloading) { lWarning() << log().arg("Unable to set download folder, a file is downloading."); return; } if (mDownloadFolder != downloadFolder) { mDownloadFolder = downloadFolder; emit downloadFolderChanged(mDownloadFolder); } } QString FileDownloader::getDestinationFileName() const { return mDestinationFile.fileName(); } void FileDownloader::setOverwriteFile(const bool &overwrite) { mOverwriteFile = overwrite; } QString FileDownloader::synchronousDownload(const QUrl &url, const QString &destinationFolder, const bool &overwriteFile) { QString filePath; FileDownloader downloader; if (url.isRelative()) lWarning() << QStringLiteral("FileDownloader: The specified URL is not valid"); else { bool isOver = false; bool *pIsOver = &isOver; downloader.setUrl(url); downloader.setOverwriteFile(overwriteFile); downloader.setDownloadFolder(destinationFolder); connect(&downloader, &FileDownloader::downloadFinished, [pIsOver]() mutable { *pIsOver = true; }); connect(&downloader, &FileDownloader::downloadFailed, [pIsOver]() mutable { *pIsOver = true; }); downloader.download(); if (QTest::qWaitFor([&]() { return isOver; }, DefaultTimeout)) { filePath = downloader.getDestinationFileName(); if (!QFile::exists(filePath)) { filePath = ""; lWarning() << QStringLiteral("FileDownloader: Cannot download the specified file"); } } } return filePath; } QString FileDownloader::getChecksum() const { return mCheckSum; } void FileDownloader::setChecksum(const QString &code) { if (mCheckSum != code) { mCheckSum = code; emit checksumChanged(); } } qint64 FileDownloader::getReadBytes() const { return mReadBytes; } void FileDownloader::setReadBytes(qint64 readBytes) { if (mReadBytes != readBytes) { mReadBytes = readBytes; emit readBytesChanged(readBytes); } } qint64 FileDownloader::getTotalBytes() const { return mTotalBytes; } void FileDownloader::setTotalBytes(qint64 totalBytes) { if (mTotalBytes != totalBytes) { mTotalBytes = totalBytes; emit totalBytesChanged(totalBytes); } } bool FileDownloader::getDownloading() const { return mDownloading; } void FileDownloader::setDownloading(bool downloading) { if (mDownloading != downloading) { mDownloading = downloading; emit downloadingChanged(downloading); } }