diff options
Diffstat (limited to 'installerbuilder/libinstaller/kdtools/KDUpdater/kdupdaterfiledownloader.cpp')
-rw-r--r-- | installerbuilder/libinstaller/kdtools/KDUpdater/kdupdaterfiledownloader.cpp | 1165 |
1 files changed, 1165 insertions, 0 deletions
diff --git a/installerbuilder/libinstaller/kdtools/KDUpdater/kdupdaterfiledownloader.cpp b/installerbuilder/libinstaller/kdtools/KDUpdater/kdupdaterfiledownloader.cpp new file mode 100644 index 000000000..c23badb5b --- /dev/null +++ b/installerbuilder/libinstaller/kdtools/KDUpdater/kdupdaterfiledownloader.cpp @@ -0,0 +1,1165 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterfiledownloader_p.h" +#include "kdupdaterfiledownloaderfactory.h" +#include "kdupdatersignatureverifier.h" +#include "kdupdatersignatureverificationresult.h" + +#include <KDToolsCore/KDAutoPointer> + +#include <QFile> +#include <QFtp> +#include <QNetworkAccessManager> +#include <QNetworkProxyFactory> +#include <QPointer> +#include <QUrl> +#include <QTemporaryFile> +#include <QFileInfo> +#include <QCryptographicHash> +#include <QThreadPool> +#include <QDebug> + +using namespace KDUpdater; + +static double calcProgress(qint32 done, qint32 total) +{ + return total ? (double(done) / double(total)) : 0 ; +} + +QByteArray KDUpdater::calculateHash( QIODevice* device, QCryptographicHash::Algorithm algo ) { + Q_ASSERT( device ); + QCryptographicHash hash( algo ); + QByteArray buffer; + buffer.resize( 512 * 1024 ); + while ( true ) { + const qint64 numRead = device->read( buffer.data(), buffer.size() ); + if ( numRead <= 0 ) + return hash.result(); + hash.addData( buffer.constData(), numRead ); + } + return QByteArray(); // never reached +} + +QByteArray KDUpdater::calculateHash( const QString& path, QCryptographicHash::Algorithm algo ) { + QFile file( path ); + if ( !file.open( QIODevice::ReadOnly ) ) + return QByteArray(); + return calculateHash( &file, algo ); +} + +class HashVerificationJob::Private { +public: + Private() : hash( QCryptographicHash::Sha1 ), error( HashVerificationJob::ReadError ), timerId( -1 ) { + } + + QPointer<QIODevice> device; + QByteArray sha1Sum; + QCryptographicHash hash; + HashVerificationJob::Error error; + int timerId; +}; + +HashVerificationJob::HashVerificationJob( QObject* parent ) : QObject( parent ), d( new Private ) +{ +} + +HashVerificationJob::~HashVerificationJob() +{ +} + +void HashVerificationJob::setDevice( QIODevice* dev ) +{ + d->device = dev; +} + +void HashVerificationJob::setSha1Sum( const QByteArray& sum ) +{ + d->sha1Sum = sum; +} + +int HashVerificationJob::error() const +{ + return d->error; +} + +bool HashVerificationJob::hasError() const +{ + return d->error != NoError; +} + +void HashVerificationJob::start() +{ + Q_ASSERT( d->device ); + d->timerId = startTimer( 0 ); +} + +void HashVerificationJob::emitFinished() +{ + emit finished( this ); + deleteLater(); +} + +void HashVerificationJob::timerEvent( QTimerEvent* ) +{ + Q_ASSERT( d->timerId >= 0 ); + if ( d->sha1Sum.isEmpty() ) { + killTimer( d->timerId ); + d->timerId = -1; + d->error = NoError; + d->device->close(); + emitFinished(); + return; + } + + QByteArray buf; + buf.resize( 128 * 1024 ); + const qint64 read = d->device->read( buf.data(), buf.size() ); + if ( read > 0 ) { + d->hash.addData( buf.constData(), read ); + return; + } + + d->error = d->hash.result() == d->sha1Sum ? NoError : SumsDifferError; + killTimer( d->timerId ); + d->timerId = -1; + emitFinished(); +} + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::FileDownloader +//////////////////////////////////////////////////////////////////////////// + +/*! + \internal + \ingroup kdupdater + \class KDUpdater::FileDownloader kdupdaterfiledownloader.h + + Base class for file downloaders used in KDUpdater. File downloaders are used by + the KDUpdater::Update class to download update files. Each subclass of FileDownloader + can download file from a specific category of sources (eg. local, ftp, http etc). + + This is an internal class, not a part of the public API. Currently we have three + subclasses of FileDownloader + \li LocalFileDownloader - downloads from the local file system + \li FtpDownloader - downloads from a FTP site + \li HttpDownloader - downloads from a HTTP site + + Usage + + \code + KDUpdater::FileDownloader* downloader = new KDUpdater::(some subclass name) + + downloader->setUrl( url ); + downloader->download(); + +// wait for downloadCompleted() signal + +QString downloadedFile = downloader->downloadedFileName(); +\endcode +*/ + +struct KDUpdater::FileDownloader::FileDownloaderData +{ + FileDownloaderData() : autoRemove( true ) { + } + + QUrl url; + QString scheme; + QByteArray sha1Sum; + QString errorString; + bool autoRemove; + bool followRedirect; +}; + +KDUpdater::FileDownloader::FileDownloader(const QString& scheme, QObject* parent) + : QObject(parent), + d( new FileDownloaderData ) +{ + d->scheme = scheme; + d->followRedirect = false; +} + +KDUpdater::FileDownloader::~FileDownloader() +{ +} + +void KDUpdater::FileDownloader::setUrl(const QUrl& url) +{ + d->url = url; +} + +QUrl KDUpdater::FileDownloader::url() const +{ + return d->url; +} + +void KDUpdater::FileDownloader::setSha1Sum( const QByteArray& sum ) +{ + d->sha1Sum = sum; +} + +QByteArray KDUpdater::FileDownloader::sha1Sum() const +{ + return d->sha1Sum; +} + +QString FileDownloader::errorString() const +{ + return d->errorString; +} + +void FileDownloader::setDownloadAborted( const QString& error ) +{ + d->errorString = error; + emit downloadAborted( error ); +} + +void KDUpdater::FileDownloader::setDownloadCompleted( const QString& path ) +{ + KDAutoPointer<HashVerificationJob> job( new HashVerificationJob ); + QFile* file = new QFile( path, job.get() ); + if ( !file->open( QIODevice::ReadOnly ) ) { + emit downloadProgress( 1 ); + onError(); + setDownloadAborted( tr("Could not reopen downloaded file %1 for reading: %2").arg( path, file->errorString() ) ); + return; + } + + job->setDevice( file ); + job->setSha1Sum( d->sha1Sum ); + connect( job.get(), SIGNAL(finished(KDUpdater::HashVerificationJob*)), this, SLOT(sha1SumVerified(KDUpdater::HashVerificationJob*)) ); + job.release()->start(); +} + +void KDUpdater::FileDownloader::sha1SumVerified( KDUpdater::HashVerificationJob* job ) +{ + if ( job->hasError() ) { + onError(); + setDownloadAborted( tr("Cryptographic hashes do not match.") ); + } + else { + onSuccess(); + emit downloadCompleted(); + } +} + +QString KDUpdater::FileDownloader::scheme() const +{ + return d->scheme; +} + +void KDUpdater::FileDownloader::setAutoRemoveDownloadedFile(bool val) +{ + d->autoRemove = val; +} + +void KDUpdater::FileDownloader::setFollowRedirects( bool val ) +{ + d->followRedirect = val; +} + +bool KDUpdater::FileDownloader::followRedirects() const +{ + return d->followRedirect; +} + +bool KDUpdater::FileDownloader::isAutoRemoveDownloadedFile() const +{ + return d->autoRemove; +} + +void KDUpdater::FileDownloader::download() { + QMetaObject::invokeMethod( this, "doDownload", Qt::QueuedConnection ); +} + +void KDUpdater::FileDownloader::cancelDownload() +{ + // Do nothing +} + + + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::FileDownloader +//////////////////////////////////////////////////////////////////////////// + +/* + Even though QFile::copy() does the task of copying local files from one place + to another, I prefer to use the timer and copy one block of data per unit time. + + This is because, it is possible that the user of KDUpdater is simultaneously + downloading several files. Sometimes in tandem with other file downloaders. + If the local file that is being downloaded takes a long time; then that will + hang the other downloads. + + On the otherhand, local downloads need not actually download the file. It can + simply pass on the source file as destination file. At this moment however, + I think the user of LocalFileDownloader will assume that the downloaded file + can be fiddled around with without worrying about whether it would mess up + the original source or not. +*/ + +struct KDUpdater::LocalFileDownloader::LocalFileDownloaderData +{ + LocalFileDownloaderData() : source(0), destination(0), + downloaded(false), timerId(-1) { } + + QFile* source; + QTemporaryFile* destination; + QString destFileName; + bool downloaded; + int timerId; +}; + +KDUpdater::LocalFileDownloader::LocalFileDownloader(QObject* parent) + :KDUpdater::FileDownloader(QLatin1String( "file" ), parent), + d ( new LocalFileDownloaderData ) +{ +} + +KDUpdater::LocalFileDownloader::~LocalFileDownloader() +{ + if( this->isAutoRemoveDownloadedFile() && !d->destFileName.isEmpty() ) + QFile::remove(d->destFileName); + + delete d; +} + +bool KDUpdater::LocalFileDownloader::canDownload() const +{ + const QString localFile = url().toLocalFile(); + QFileInfo fi( localFile ); + return fi.exists() && fi.isReadable(); +} + +bool KDUpdater::LocalFileDownloader::isDownloaded() const +{ + return d->downloaded; +} + +void KDUpdater::LocalFileDownloader::doDownload() +{ + // Already downloaded + if( d->downloaded ) + return; + + // Already started downloading + if( d->timerId >= 0 ) + return; + + // Open source and destination files + QString localFile = this->url().toLocalFile(); + d->source = new QFile(localFile, this); + d->destination = new QTemporaryFile(this); + + if( !d->source->open(QFile::ReadOnly) ) + { + onError(); + setDownloadAborted(tr("Cannot open source file for reading.")); + return; + } + + if( !d->destination->open() ) + { + onError(); + setDownloadAborted(tr("Cannot open destination file for writing.")); + return; + } + + // Start a timer and kickoff the copy process + d->timerId = startTimer(0); // as fast as possible + emit downloadStarted(); + emit downloadProgress(0); +} + +QString KDUpdater::LocalFileDownloader::downloadedFileName() const +{ + return d->destFileName; +} + +KDUpdater::LocalFileDownloader* KDUpdater::LocalFileDownloader::clone( QObject* parent ) const +{ + return new LocalFileDownloader( parent ); +} + +void KDUpdater::LocalFileDownloader::cancelDownload() +{ + if( d->timerId < 0 ) + return; + + killTimer( d->timerId ); + d->timerId = -1; + + onError(); + emit downloadCanceled(); +} + +void KDUpdater::LocalFileDownloader::timerEvent(QTimerEvent*) +{ + if( !d->source || !d->destination ) + return; + + const qint64 blockSize = 32768; + QByteArray buffer; + buffer.resize( blockSize ); + const qint64 numRead = d->source->read( buffer.data(), buffer.size() ); + qint64 toWrite = numRead; + while ( toWrite > 0 ) { + const qint64 numWritten = d->destination->write( buffer.constData() + numRead - toWrite, toWrite ); + if ( numWritten < 0 ) { + killTimer( d->timerId ); + d->timerId = -1; + onError(); + setDownloadAborted( tr("Writing to %1 failed: %2").arg( d->destination->fileName(), d->destination->errorString() ) ); + return; + } + toWrite -= numWritten; + } + + if( numRead > 0 ) { + emit downloadProgress( calcProgress(d->source->pos(), d->source->size()) ); + return; + } + + d->destination->flush(); + + killTimer( d->timerId ); + d->timerId = -1; + + setDownloadCompleted( d->destination->fileName() ); +} + +void LocalFileDownloader::onSuccess() +{ + d->downloaded = true; + d->destFileName = d->destination->fileName(); + d->destination->setAutoRemove( false ); + d->destination->close(); + delete d->destination; + d->destination = 0; + delete d->source; + d->source = 0; +} + +void LocalFileDownloader::onError() +{ + d->downloaded = false; + d->destFileName.clear(); + delete d->destination; + d->destination = 0; + delete d->source; + d->source = 0; +} + +struct KDUpdater::ResourceFileDownloader::ResourceFileDownloaderData +{ + ResourceFileDownloaderData() + : downloaded( false ), + timerId( -1 ) + { + } + + QString destFileName; + bool downloaded; + int timerId; +}; + +KDUpdater::ResourceFileDownloader::ResourceFileDownloader(QObject* parent) + :KDUpdater::FileDownloader(QLatin1String( "resource" ), parent), + d ( new ResourceFileDownloaderData ) +{ +} + +KDUpdater::ResourceFileDownloader::~ResourceFileDownloader() +{ + delete d; +} + +bool KDUpdater::ResourceFileDownloader::canDownload() const +{ + QUrl url = this->url(); + url.setScheme( QString::fromLatin1( "file" ) ); + QString localFile = QString::fromLatin1( ":%1" ).arg( url.toLocalFile() ); + QFileInfo fi(localFile); + return fi.exists() && fi.isReadable(); +} + +bool KDUpdater::ResourceFileDownloader::isDownloaded() const +{ + return d->downloaded; +} + +void KDUpdater::ResourceFileDownloader::doDownload() +{ + // Already downloaded + if( d->downloaded ) + return; + + // Already started downloading + if( d->timerId >= 0 ) + return; + + // Open source and destination files + QUrl url = this->url(); + url.setScheme( QString::fromLatin1( "file" ) ); + d->destFileName = QString::fromLatin1( ":%1" ).arg( url.toLocalFile() ); + + // Start a timer and kickoff the copy process + d->timerId = startTimer(0); // as fast as possible + emit downloadStarted(); + emit downloadProgress(0); +} + +QString KDUpdater::ResourceFileDownloader::downloadedFileName() const +{ + return d->destFileName; +} + +KDUpdater::ResourceFileDownloader* KDUpdater::ResourceFileDownloader::clone( QObject* parent ) const +{ + return new ResourceFileDownloader( parent ); +} + +void KDUpdater::ResourceFileDownloader::cancelDownload() +{ + if( d->timerId < 0 ) + return; + + killTimer( d->timerId ); + d->timerId = -1; + + emit downloadCanceled(); +} + +void KDUpdater::ResourceFileDownloader::timerEvent(QTimerEvent*) +{ + killTimer( d->timerId ); + d->timerId = -1; + setDownloadCompleted( d->destFileName ); +} + +void KDUpdater::ResourceFileDownloader::onSuccess() +{ + d->downloaded = true; +} + +void KDUpdater::ResourceFileDownloader::onError() +{ + d->downloaded = false; +} + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::FileDownloader +//////////////////////////////////////////////////////////////////////////// + +struct KDUpdater::FtpDownloader::FtpDownloaderData +{ + FtpDownloaderData() : ftp(0), destination(0), + downloaded(false), ftpCmdId(-1), aborted(false) { } + + QFtp* ftp; + QTemporaryFile* destination; + QString destFileName; + bool downloaded; + int ftpCmdId; + bool aborted; +}; + +KDUpdater::FtpDownloader::FtpDownloader(QObject* parent) + : KDUpdater::FileDownloader(QLatin1String( "ftp" ), parent), + d ( new FtpDownloaderData ) +{ +} + +KDUpdater::FtpDownloader::~FtpDownloader() +{ + if( this->isAutoRemoveDownloadedFile() && !d->destFileName.isEmpty() ) + QFile::remove(d->destFileName); + + delete d; +} + +bool KDUpdater::FtpDownloader::canDownload() const +{ + // TODO: Check whether the ftp file actually exists or not. + return true; +} + +bool KDUpdater::FtpDownloader::isDownloaded() const +{ + return d->downloaded; +} + +void KDUpdater::FtpDownloader::doDownload() +{ + if( d->downloaded ) + return; + + if( d->ftp ) + return; + + d->ftp = new QFtp(this); + connect(d->ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool))); + connect(d->ftp, SIGNAL(commandStarted(int)), this, SLOT(ftpCmdStarted(int))); + connect(d->ftp, SIGNAL(commandFinished(int,bool)), this, SLOT(ftpCmdFinished(int,bool))); + connect(d->ftp, SIGNAL(stateChanged(int)), this, SLOT(ftpStateChanged(int))); + connect(d->ftp, SIGNAL(dataTransferProgress(qint64,qint64)), this, SLOT(ftpDataTransferProgress(qint64,qint64))); + + d->ftp->connectToHost( url().host(), url().port(21) ); + d->ftp->login(); +} + +QString KDUpdater::FtpDownloader::downloadedFileName() const +{ + return d->destFileName; +} + +KDUpdater::FtpDownloader* KDUpdater::FtpDownloader::clone( QObject* parent ) const +{ + return new FtpDownloader( parent ); +} + + +void KDUpdater::FtpDownloader::cancelDownload() +{ + if( d->ftp ) + { + d->aborted = true; + d->ftp->abort(); + } +} + +void KDUpdater::FtpDownloader::ftpDone(bool error) +{ + if( error ) + { + d->ftp->deleteLater(); + d->ftp = 0; + d->ftpCmdId = -1; + + onError(); + + if( d->aborted ) + { + d->aborted = false; + emit downloadCanceled(); + } + else + setDownloadAborted( d->ftp->errorString() ); + } + //PENDING what about the non-error case?? +} + +void KDUpdater::FtpDownloader::ftpCmdStarted(int id) +{ + if( id != d->ftpCmdId ) + return; + + emit downloadStarted(); + emit downloadProgress(0); +} + +void KDUpdater::FtpDownloader::ftpCmdFinished(int id, bool error) +{ + if( id != d->ftpCmdId || error ) // PENDING why error -> return?? + return; + + disconnect(d->ftp, 0, this, 0); + d->ftp->deleteLater(); + d->ftp = 0; + d->ftpCmdId = -1; + d->destination->flush(); + + setDownloadCompleted( d->destination->fileName() ); +} + +void FtpDownloader::onSuccess() +{ + d->downloaded = true; + d->destFileName = d->destination->fileName(); + d->destination->setAutoRemove( false ); + delete d->destination; + d->destination = 0; + +} + +void FtpDownloader::onError() +{ + d->downloaded = false; + d->destFileName.clear(); + delete d->destination; + d->destination = 0; + +} + +void KDUpdater::FtpDownloader::ftpStateChanged(int state) +{ + switch(state) + { + case QFtp::Connected: + // begin the download + d->destination = new QTemporaryFile(this); + d->destination->open(); //PENDING handle error + d->ftpCmdId = d->ftp->get( url().path(), d->destination ); + break; + case QFtp::Unconnected: + // download was unconditionally aborted + disconnect(d->ftp, 0, this, 0); + d->ftp->deleteLater(); + d->ftp = 0; + d->ftpCmdId = -1; + onError(); + setDownloadAborted(tr("Download was aborted due to network errors.")); + break; + } +} + +void KDUpdater::FtpDownloader::ftpDataTransferProgress(qint64 done, qint64 total) +{ + emit downloadProgress( calcProgress(done, total) ); +} + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::FileDownloader +//////////////////////////////////////////////////////////////////////////// + +struct KDUpdater::HttpDownloader::HttpDownloaderData +{ + explicit HttpDownloaderData( HttpDownloader* qq ) : q( qq ), http(0), destination(0), downloaded(false), + aborted(false), retrying(false) { } + + HttpDownloader* const q; + QNetworkAccessManager manager; + QNetworkReply* http; + QTemporaryFile* destination; + QString destFileName; + bool downloaded; + bool aborted; + bool retrying; + + void shutDown() { + disconnect( http, SIGNAL( finished() ), q, SLOT( httpReqFinished() ) ); + http->deleteLater(); + http = 0; + destination->close(); + destination->deleteLater(); + destination = 0; + + } +}; + +KDUpdater::HttpDownloader::HttpDownloader(QObject* parent) + : KDUpdater::FileDownloader(QLatin1String( "http" ), parent), + d ( new HttpDownloaderData( this ) ) +{ +} + +KDUpdater::HttpDownloader::~HttpDownloader() +{ + if( this->isAutoRemoveDownloadedFile() && !d->destFileName.isEmpty() ) + QFile::remove(d->destFileName); + delete d; +} + +bool KDUpdater::HttpDownloader::canDownload() const +{ + // TODO: Check whether the ftp file actually exists or not. + return true; +} + +bool KDUpdater::HttpDownloader::isDownloaded() const +{ + return d->downloaded; +} + +void KDUpdater::HttpDownloader::doDownload() +{ + if( d->downloaded ) + return; + + if( d->http ) + return; + + d->http = d->manager.get( QNetworkRequest( url() ) ); + + connect( d->http, SIGNAL( readyRead() ), this, SLOT( httpReadyRead() ) ); + connect( d->http, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( httpReadProgress( qint64, qint64) ) ); + connect( d->http, SIGNAL( finished() ), this, SLOT( httpReqFinished() ) ); + connect( d->http, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( httpError( QNetworkReply::NetworkError ) ) ); + + /* + // In a future update, authentication should also be supported. + + connect(d->http, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + this, SLOT(httpProxyAuth(QNetworkProxy,QAuthenticator*))); + connect(d->http, SIGNAL(authenticationRequired(QString,QAuthenticator*)), + this, SLOT(httpAuth(QString,QAuthenticator*))); + */ + + // Begin the download + d->destination = new QTemporaryFile(this); + if ( !d->destination->open() ) { + const QString err = d->destination->errorString(); + d->shutDown(); + setDownloadAborted( tr("Cannot download %1: Could not create temporary file: %2").arg( url().toString(), err ) ); + return; + } +} + +QString KDUpdater::HttpDownloader::downloadedFileName() const +{ + return d->destFileName; +} + +KDUpdater::HttpDownloader* KDUpdater::HttpDownloader::clone( QObject* parent ) const +{ + return new HttpDownloader( parent ); +} + +void KDUpdater::HttpDownloader::httpReadyRead() +{ + static QByteArray buffer( 16384, '\0' ); + while( d->http->bytesAvailable() ) + { + const qint64 read = d->http->read( buffer.data(), buffer.size() ); + qint64 written = 0; + while( written < read ) { + const qint64 numWritten = d->destination->write( buffer.data() + written, read - written ); + if ( numWritten < 0 ) { + const QString err = d->destination->errorString(); + d->shutDown(); + setDownloadAborted( tr("Cannot download %1: Writing to temporary file failed: %2").arg( url().toString(), err ) ); + return; + } + written += numWritten; + } + } +} + +void KDUpdater::HttpDownloader::httpError( QNetworkReply::NetworkError ) +{ + static bool setProxySettings = false; + if( !d->retrying && !setProxySettings ) + { + d->shutDown(); + d->retrying = true; + setProxySettings = true; + + // silently force retry with global proxy settings + QNetworkProxyFactory::setUseSystemConfiguration( true ); + + doDownload(); + return; + } + httpDone( true ); +} + +void KDUpdater::HttpDownloader::cancelDownload() +{ + d->aborted = true; + if( d->http ) + { + d->http->abort(); + httpDone( true ); + } +} + +void KDUpdater::HttpDownloader::httpDone( bool error ) +{ + if( error ) + { + QString err; + if( d->http ) + { + err = d->http->errorString(); + d->http->deleteLater(); + d->http = 0; + onError(); + } + + if( d->aborted ) + { + d->aborted = false; + emit downloadCanceled(); + } + else + setDownloadAborted( err ); + } + //PENDING: what about the non-error case?? +} + +void KDUpdater::HttpDownloader::onError() +{ + d->downloaded = false; + d->destFileName.clear(); + delete d->destination; + d->destination = 0; +} + +void KDUpdater::HttpDownloader::onSuccess() +{ + d->downloaded = true; + d->destFileName = d->destination->fileName(); + d->destination->setAutoRemove( false ); + delete d->destination; + d->destination = 0; +} + +void KDUpdater::HttpDownloader::httpReqFinished() +{ + const QVariant redirect = d->http == 0 ? QVariant() : d->http->attribute( QNetworkRequest::RedirectionTargetAttribute ); + const QUrl redirectUrl = redirect.toUrl(); + //if ( redirect.isValid() ) + // redirectUrl = redirect.toUrl(); + if ( followRedirects() && redirectUrl.isValid() ) + { + // clean the previous download + d->http->deleteLater(); + d->http = 0; + d->destination->close(); + d->destination->deleteLater(); + d->destination = 0; + + d->http = d->manager.get( QNetworkRequest( redirectUrl ) ); + + connect( d->http, SIGNAL( readyRead() ), this, SLOT( httpReadyRead() ) ); + connect( d->http, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( httpReadProgress( qint64, qint64) ) ); + connect( d->http, SIGNAL( finished() ), this, SLOT( httpReqFinished() ) ); + connect( d->http, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( httpError( QNetworkReply::NetworkError ) ) ); + + // Begin the download + d->destination = new QTemporaryFile(this); + d->destination->open(); //PENDING handle error + } + else + { + if( d->http == 0 ) + return; + httpReadyRead(); + d->destination->flush(); + setDownloadCompleted( d->destination->fileName() ); + d->http->deleteLater(); + d->http = 0; + } +} + +void KDUpdater::HttpDownloader::httpReadProgress( qint64 done, qint64 total) +{ + emit downloadProgress( calcProgress( done, total ) ); +} + +class SignatureVerificationDownloader::Private +{ + SignatureVerificationDownloader* const q; +public: + explicit Private( FileDownloader* dl, SignatureVerificationDownloader* qq ) : q( qq ), verifier( 0 ), downloader( dl ), sigDownloader( 0 ), actualDownloadDone( false ) + { + Q_ASSERT( downloader ); + q->connect( downloader.get(), SIGNAL( downloadProgress( double ) ), q, SIGNAL( downloadProgress( double ) ) ); + q->connect( downloader.get(), SIGNAL(downloadStarted()), q, SLOT(dataDownloadStarted()) ); + q->connect( downloader.get(), SIGNAL(downloadCompleted()), q, SLOT(dataDownloadCompleted()) ); + q->connect( downloader.get(), SIGNAL(downloadCanceled()), q, SLOT(dataDownloadCanceled()) ); + q->connect( downloader.get(), SIGNAL(downloadAborted(QString)), q, SLOT(dataDownloadAborted(QString)) ); + } + + ~Private() + { + delete verifier; + } + + const SignatureVerifier* verifier; + KDAutoPointer< FileDownloader > downloader; + KDAutoPointer< FileDownloader > sigDownloader; + QUrl signatureUrl; + SignatureVerificationResult result; + QString downloadedFileName; + bool actualDownloadDone : 1; +}; + +SignatureVerificationDownloader::SignatureVerificationDownloader( FileDownloader* downloader, QObject* parent ) : FileDownloader( downloader->scheme(), parent ), d( new Private( downloader, this ) ) +{ +} + +SignatureVerificationDownloader::~SignatureVerificationDownloader() +{ +} + +QUrl SignatureVerificationDownloader::signatureUrl() const +{ + return d->signatureUrl; +} + +void SignatureVerificationDownloader::setSignatureUrl( const QUrl& url ) +{ + d->signatureUrl = url; +} + +const SignatureVerifier* SignatureVerificationDownloader::signatureVerifier() const { + return d->verifier; +} + +void SignatureVerificationDownloader::setSignatureVerifier( const SignatureVerifier* verifier ) { + delete d->verifier; + d->verifier = verifier ? verifier->clone() : 0; +} + +SignatureVerificationResult SignatureVerificationDownloader::result() const +{ + return d->result; +} + +bool SignatureVerificationDownloader::canDownload() const +{ + return d->downloader->canDownload(); +} + +bool SignatureVerificationDownloader::isDownloaded() const +{ + return d->downloader->isDownloaded(); +} + +QString SignatureVerificationDownloader::downloadedFileName() const +{ + return d->downloadedFileName; +} + +FileDownloader* SignatureVerificationDownloader::clone( QObject* parent ) const +{ + return new SignatureVerificationDownloader( d->downloader->clone(), parent ); +} + +void SignatureVerificationDownloader::onError() +{ + d->sigDownloader.reset(); + if ( QFile::exists( d->downloadedFileName ) ) + QFile::remove( d->downloadedFileName ); +} + +void SignatureVerificationDownloader::onSuccess() +{ + d->sigDownloader.reset(); +} + +void SignatureVerificationDownloader::cancelDownload() +{ + if ( !d->actualDownloadDone ) { + d->downloader->cancelDownload(); + return; + } + if ( d->sigDownloader ) { + d->sigDownloader->cancelDownload(); + } +} + +void SignatureVerificationDownloader::doDownload() +{ + Q_ASSERT( d->verifier ); + Q_ASSERT( d->downloader ); + d->downloader->setUrl( url() ); + d->downloader->setSha1Sum( sha1Sum() ); + d->downloader->download(); +} + +void SignatureVerificationDownloader::dataDownloadStarted() +{ + emit downloadStarted(); +} + +void SignatureVerificationDownloader::dataDownloadAborted( const QString& err ) +{ + setDownloadAborted( err ); +} + +void SignatureVerificationDownloader::dataDownloadCanceled() +{ + emit downloadCanceled(); +} + +static QUrl suggestSignatureUrl( const QUrl& url ) { + return url.toString() + QLatin1String(".sig"); +} + +void SignatureVerificationDownloader::dataDownloadCompleted() +{ + d->downloadedFileName = d->downloader->downloadedFileName(); + d->actualDownloadDone = true; + + QUrl url = d->signatureUrl; + if ( url.isEmpty() ) + url = suggestSignatureUrl( d->downloader->url() ); + d->sigDownloader.reset( FileDownloaderFactory::instance().create( url.scheme(), this ) ); + if ( !d->sigDownloader ) { + setDownloadAborted( tr("Could not download signature: scheme %1 not supported").arg( url.scheme() ) ); + return; + } + d->sigDownloader->setUrl( url ); + connect( d->sigDownloader.get(), SIGNAL(downloadCompleted()), this, SLOT(signatureDownloadCompleted()) ); + connect( d->sigDownloader.get(), SIGNAL(downloadCanceled()), this, SLOT(signatureDownloadCanceled()) ); + connect( d->sigDownloader.get(), SIGNAL(downloadAborted(QString)), this, SLOT(signatureDownloadAborted(QString)) ); + d->sigDownloader->download(); +} + +void SignatureVerificationDownloader::signatureDownloadAborted( const QString& err ) +{ + setDownloadAborted( tr("Downloading signature: %1").arg( err ) ); +} + +void SignatureVerificationDownloader::signatureDownloadCanceled() +{ + emit downloadCanceled(); +} + +void SignatureVerificationDownloader::signatureDownloadCompleted() +{ + QFile sigFile( d->sigDownloader->downloadedFileName() ); + if ( !sigFile.open( QIODevice::ReadOnly ) ) { + setDownloadAborted( tr("Could not open signature file: %1").arg( sigFile.errorString() ) ); + return; + } + + const QByteArray signature = sigFile.readAll(); + + QFile dataFile( d->downloadedFileName ); + if ( !dataFile.open( QIODevice::ReadOnly ) ) { + setDownloadAborted( tr("Could not open file for verification: %1").arg( dataFile.errorString() ) ); + return; + } + const QByteArray dataHash = calculateHash(&dataFile, QCryptographicHash::Sha1); + + //const QString sigPath = d->sigDownloader->downloadedFileName(); + d->result = d->verifier->verify( dataHash, signature ); + if( ! d->result.isValid() ) { + setDownloadAborted( d->result.errorString() ); + return; + } + setDownloadCompleted( d->downloadedFileName ); + +#if 0 + SignatureVerificationRunnable* runnable = new SignatureVerificationRunnable; + runnable->setSignature( signature ); + runnable->setData( dataFile.release() ); + runnable->setVerifier( d->verifier ); + runnable->addResultListener( this, "verificationResult" ); + QThreadPool::globalInstance()->start( runnable ); +#endif +} |