LCOV - code coverage report
Current view: top level - package-lib - packageextractor.cpp (source / functions) Coverage Total Hit
Test: QtApplicationManager Lines: 81.9 % 282 231
Test Date: 2024-03-16 10:14:03 Functions: 95.0 % 20 19
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 53.8 % 385 207

             Branch data     Line data    Source code
       1                 :             : // Copyright (C) 2021 The Qt Company Ltd.
       2                 :             : // Copyright (C) 2019 Luxoft Sweden AB
       3                 :             : // Copyright (C) 2018 Pelagicore AG
       4                 :             : // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
       5                 :             : 
       6                 :             : #include <QStringList>
       7                 :             : #include <QThread>
       8                 :             : #include <QAtomicInt>
       9                 :             : #include <QFile>
      10                 :             : #include <QDir>
      11                 :             : #include <QDataStream>
      12                 :             : #include <QUrl>
      13                 :             : #include <QDebug>
      14                 :             : #include <QCryptographicHash>
      15                 :             : 
      16                 :             : #include <archive.h>
      17                 :             : #include <archive_entry.h>
      18                 :             : 
      19                 :             : #include "packageutilities_p.h"
      20                 :             : #include "packageextractor.h"
      21                 :             : #include "packageextractor_p.h"
      22                 :             : #include "exception.h"
      23                 :             : #include "error.h"
      24                 :             : #include "installationreport.h"
      25                 :             : #include "utilities.h"
      26                 :             : #include "packageinfo.h"
      27                 :             : #include "qtyaml.h"
      28                 :             : 
      29                 :             : // archive.h might #define this for Android
      30                 :             : #ifdef open
      31                 :             : #  undef open
      32                 :             : #endif
      33                 :             : // these are not defined on all platforms
      34                 :             : #ifndef S_IREAD
      35                 :             : #  define S_IREAD S_IRUSR
      36                 :             : #endif
      37                 :             : #ifndef S_IEXEC
      38                 :             : #  define S_IEXEC S_IXUSR
      39                 :             : #endif
      40                 :             : 
      41                 :             : using namespace Qt::StringLiterals;
      42                 :             : 
      43                 :             : QT_BEGIN_NAMESPACE_AM
      44                 :             : 
      45                 :          66 : PackageExtractor::PackageExtractor(const QUrl &downloadUrl, const QDir &destinationDir, QObject *parent)
      46                 :             :     : QObject(parent)
      47   [ +  -  +  - ]:          66 :     , d(new PackageExtractorPrivate(this, downloadUrl))
      48                 :             : {
      49         [ +  - ]:          66 :     setDestinationDirectory(destinationDir);
      50                 :          66 : }
      51                 :             : 
      52                 :         135 : QDir PackageExtractor::destinationDirectory() const
      53                 :             : {
      54                 :         135 :     return QDir(d->m_destinationPath);
      55                 :             : }
      56                 :             : 
      57                 :         104 : void PackageExtractor::setDestinationDirectory(const QDir &destinationDir)
      58                 :             : {
      59         [ +  - ]:         104 :     d->m_destinationPath = destinationDir.absolutePath() + u'/';
      60                 :         104 : }
      61                 :             : 
      62                 :          88 : void PackageExtractor::setFileExtractedCallback(const std::function<void(const QString &)> &callback)
      63                 :             : {
      64                 :          88 :     d->m_fileExtractedCallback = callback;
      65                 :          88 : }
      66                 :             : 
      67                 :         209 : const InstallationReport &PackageExtractor::installationReport() const
      68                 :             : {
      69                 :         209 :     return d->m_report;
      70                 :             : }
      71                 :             : 
      72                 :          66 : bool PackageExtractor::extract()
      73                 :             : {
      74                 :          66 :     if (!wasCanceled()) {
      75                 :          65 :         d->m_failed = false;
      76                 :             : 
      77                 :          65 :         d->download(d->m_url);
      78                 :             : 
      79                 :          65 :         QMetaObject::invokeMethod(d, "extract", Qt::QueuedConnection);
      80                 :          65 :         d->m_loop.exec();
      81                 :             : 
      82         [ +  - ]:          65 :         delete d->m_reply;
      83                 :          65 :         d->m_reply = nullptr;
      84                 :             :     }
      85         [ +  + ]:          64 :     return !wasCanceled() && !hasFailed();
      86                 :             : }
      87                 :             : 
      88                 :          66 : bool PackageExtractor::hasFailed() const
      89                 :             : {
      90   [ +  +  +  + ]:          66 :     return d->m_failed || wasCanceled();
      91                 :             : }
      92                 :             : 
      93                 :         311 : bool PackageExtractor::wasCanceled() const
      94                 :             : {
      95   [ +  +  +  +  :         183 :     return d->m_canceled != 0;
                   +  + ]
      96                 :             : }
      97                 :             : 
      98                 :          20 : Error PackageExtractor::errorCode() const
      99                 :             : {
     100   [ +  +  +  - ]:          20 :     return wasCanceled() ? Error::Canceled : (d->m_failed ? d->m_errorCode : Error::None);
     101                 :             : }
     102                 :             : 
     103                 :          21 : QString PackageExtractor::errorString() const
     104                 :             : {
     105   [ -  +  +  + ]:          21 :     return wasCanceled() ? u"canceled"_s : (d->m_failed ? d->m_errorString : QString());
     106                 :             : }
     107                 :             : 
     108                 :             : /*! \internal
     109                 :             :   This function can be called from another thread while extract() is running
     110                 :             : */
     111                 :          10 : void PackageExtractor::cancel()
     112                 :             : {
     113         [ +  + ]:          10 :     if (!d->m_canceled.fetchAndStoreOrdered(1)) {
     114         [ +  + ]:           9 :         if (d->m_loop.isRunning())
     115                 :           1 :             d->m_loop.wakeUp();
     116                 :             :     }
     117                 :          10 : }
     118                 :             : 
     119                 :             : 
     120                 :             : /* * * * * * * * * * * * * * * * * * *
     121                 :             :  *  vvv PackageExtractorPrivate vvv  *
     122                 :             :  * * * * * * * * * * * * * * * * * * */
     123                 :             : 
     124                 :          66 : PackageExtractorPrivate::PackageExtractorPrivate(PackageExtractor *extractor, const QUrl &downloadUrl)
     125                 :             :     : QObject(extractor)
     126                 :          66 :     , q(extractor)
     127                 :          66 :     , m_url(downloadUrl)
     128   [ +  -  +  - ]:          66 :     , m_nam(new QNetworkAccessManager(this))
     129   [ +  -  +  - ]:         132 :     , m_report(QString())
     130                 :             : {
     131         [ +  - ]:          66 :     m_buffer.resize(64 * 1024);
     132                 :          66 : }
     133                 :             : 
     134                 :          66 : qint64 PackageExtractorPrivate::readTar(struct archive *ar, const void **archiveBuffer)
     135                 :             : {
     136                 :          66 :     forever {
     137                 :             :         // the event loop is gone
     138         [ -  + ]:          66 :         if (!m_loop.isRunning()) {
     139                 :           0 :             archive_set_error(ar, -1, "no eventlopp");
     140                 :           0 :             return -1;
     141                 :             :         }
     142                 :             :         // we have been canceled
     143         [ -  + ]:          66 :         if (q->wasCanceled()) {
     144                 :           0 :             archive_set_error(ar, -1, "canceled");
     145                 :           0 :             return -1;
     146                 :             :         }
     147                 :          66 :         qint64 bytesAvailable = m_reply->bytesAvailable();
     148                 :             : 
     149                 :             :         // there is something to read
     150                 :             :         // (or this is a FIFO and we need this ugly hack - for testing only though!)
     151   [ +  +  +  + ]:          66 :         if ((bytesAvailable > 0) || m_downloadingFromFIFO) {
     152                 :          64 :             qint64 bytesRead = m_reply->read(m_buffer.data(), m_buffer.size());
     153                 :             : 
     154         [ -  + ]:          64 :             if (bytesRead < 0) {
     155                 :             :                 // another FIFO hack: if the writer dies, we will get an -1 return from read()
     156   [ #  #  #  # ]:           0 :                 if (m_downloadingFromFIFO && m_reply->atEnd()) {
     157                 :             :                     return 0;
     158                 :             :                 } else {
     159                 :           0 :                     archive_set_error(ar, -1, "could not read from tar archive");
     160                 :           0 :                     return -1;
     161                 :             :                 }
     162                 :             :             }
     163                 :             : 
     164                 :          64 :             m_bytesReadTotal += bytesRead;
     165         [ +  - ]:          64 :             *archiveBuffer = m_buffer.constData();
     166                 :             : 
     167         [ +  + ]:          64 :             qint64 progress = m_downloadTotal ? (100 * m_bytesReadTotal / m_downloadTotal) : 0;
     168         [ +  + ]:          64 :             if (progress != m_lastProgress) {
     169                 :          63 :                 emit q->progress(qreal(progress) / 100);
     170                 :          63 :                 m_lastProgress = progress;
     171                 :             :             }
     172                 :             : 
     173                 :          64 :             return bytesRead;
     174                 :             :         }
     175                 :             : 
     176                 :             :         // got an error while reading
     177         [ +  + ]:           2 :         if (m_reply->error() != QNetworkReply::NoError) {
     178   [ +  -  +  - ]:           3 :             archive_set_error(ar, -1, "%s", m_reply->errorString().toLocal8Bit().constData());
     179                 :           1 :             return -1;
     180                 :             :         }
     181                 :             : 
     182                 :             :         // we're done
     183         [ -  + ]:           1 :         if (m_reply->isFinished())
     184                 :             :             return 0;
     185                 :             : 
     186                 :           0 :         m_loop.processEvents(QEventLoop::WaitForMoreEvents);
     187                 :           0 :     }
     188                 :             : }
     189                 :             : 
     190                 :          65 : void PackageExtractorPrivate::extract()
     191                 :             : {
     192                 :          65 :     struct archive *ar = nullptr;
     193                 :             : 
     194                 :          65 :     try {
     195         [ +  - ]:          65 :         ar = archive_read_new();
     196         [ -  + ]:          65 :         if (!ar)
     197                 :           0 :             throw Exception("[libarchive] could not create a new archive object");
     198   [ +  -  -  + ]:          65 :         if (archive_read_support_format_tar(ar) != ARCHIVE_OK)
     199         [ #  # ]:           0 :             throw ArchiveException(ar, "could not enable TAR support");
     200                 :             : // disabled for now -- see libarchive.pro
     201                 :             : //        if (archive_read_support_filter_xz(ar) != ARCHIVE_OK)
     202                 :             : //            throw ArchiveException(ar, "could not enable XZ support");
     203   [ +  -  -  + ]:          65 :         if (archive_read_support_filter_gzip(ar) != ARCHIVE_OK)
     204         [ #  # ]:           0 :             throw ArchiveException(ar, "could not enable GZIP support");
     205                 :             : #if !defined(Q_OS_ANDROID)
     206   [ +  -  -  + ]:          65 :         if (archive_read_set_options(ar, "hdrcharset=UTF-8") != ARCHIVE_OK)
     207         [ #  # ]:           0 :             throw ArchiveException(ar, "could not set the HDRCHARSET option");
     208                 :             : #endif
     209                 :             : 
     210                 :          65 :         auto dummyCallback = [](archive *, void *) { return ARCHIVE_OK; };
     211                 :          65 :         auto readCallback = [](archive *arRead, void *user, const void **buffer)
     212                 :             :         { return static_cast<__LA_SSIZE_T>(static_cast<PackageExtractorPrivate *>(user)->readTar(arRead, buffer)); };
     213                 :             : 
     214   [ +  -  +  + ]:          65 :         if (archive_read_open(ar, this, dummyCallback, readCallback, dummyCallback) != ARCHIVE_OK)
     215         [ +  - ]:           2 :             throw ArchiveException(ar, "could not open archive");
     216                 :             : 
     217                 :          63 :         bool seenHeader = false;
     218                 :          63 :         bool seenFooter = false;
     219                 :          63 :         QByteArray header;
     220                 :          63 :         QByteArray footer;
     221                 :             : 
     222         [ +  - ]:          63 :         QCryptographicHash digest(QCryptographicHash::Sha256);
     223                 :             : 
     224                 :             :         // Iterate over all entries in the archive
     225         [ +  + ]:         450 :         for (bool finished = false; !finished; ) {
     226                 :         397 :             archive_entry *entry = nullptr;
     227         [ +  - ]:         397 :             QFile f;
     228                 :             : 
     229                 :             :             // Try to read the next entry from the archive
     230                 :             : 
     231   [ +  -  +  -  :         397 :             switch (archive_read_next_header(ar, &entry)) {
                      + ]
     232                 :          53 :             case ARCHIVE_EOF:
     233                 :          53 :                 finished = true;
     234                 :          53 :                 continue;
     235                 :         344 :             case ARCHIVE_OK:
     236                 :         344 :                 break;
     237                 :           0 :             default:
     238         [ #  # ]:           0 :                 throw ArchiveException(ar, "could not read header");
     239                 :             :             }
     240                 :             : 
     241                 :             :             // Make sure to quit if we get something funky, i.e. something other than files or dirs
     242                 :             : 
     243         [ +  - ]:         344 :             __LA_MODE_T entryMode = archive_entry_mode(entry);
     244                 :         344 :             PackageEntryType packageEntryType;
     245   [ +  -  +  - ]:         344 :             QString entryPath = QString::fromUtf8(archive_entry_pathname_utf8(entry))
     246         [ +  - ]:         354 :                     .normalized(QString::NormalizationForm_C);
     247                 :             : 
     248      [ -  -  + ]:         344 :             switch (entryMode & S_IFMT) {
     249                 :             :             case S_IFREG:
     250                 :             :                 packageEntryType = PackageEntry_File;
     251                 :             :                 break;
     252                 :           0 :             case S_IFDIR:
     253                 :           0 :                 packageEntryType = PackageEntry_Dir;
     254                 :           0 :                 break;
     255                 :           0 :             default:
     256                 :           0 :                 throw Exception(Error::Package, "file %1 in the archive has the unsupported type (mode) 0%2").arg(entryPath).arg(int(entryMode & S_IFMT), 0, 8);
     257                 :             :             }
     258                 :             : 
     259                 :             :             // Check if this entry is special (metadata vs. data)
     260                 :             : 
     261         [ +  + ]:         344 :             if (entryPath == u"--PACKAGE-HEADER--")
     262                 :             :                 packageEntryType = PackageEntry_Header;
     263         [ +  + ]:         281 :             else if (entryPath.startsWith(u"--PACKAGE-FOOTER--"))
     264                 :             :                 packageEntryType = PackageEntry_Footer;
     265         [ -  + ]:         205 :             else if (entryPath.startsWith(u"--"))
     266                 :           0 :                 throw Exception(Error::Package, "filename %1 in the archive starts with the reserved characters '--'").arg(entryPath);
     267                 :             : 
     268                 :             :             // The first (and only the first) file in every package needs to be --PACKAGE-HEADER--
     269                 :             : 
     270         [ -  + ]:         344 :             if (seenHeader == (packageEntryType == PackageEntry_Header))
     271                 :           0 :                 throw Exception(Error::Package, "the first (and only the first) file of the package must be --PACKAGE-HEADER--");
     272                 :             : 
     273                 :             :             // There cannot be and normal files after the first --PACKAGE-FOOTER--
     274         [ -  + ]:         344 :             if (seenFooter && (packageEntryType != PackageEntry_Footer))
     275                 :           0 :                 throw Exception(Error::Package, "only --PACKAGE-FOOTER--* files are allowed at the end of the package");
     276                 :             : 
     277   [ -  +  +  + ]:         344 :             switch (packageEntryType) {
     278                 :           0 :             case PackageEntry_Dir:
     279   [ #  #  #  # ]:           0 :                 if (!entryPath.endsWith(u'/'))
     280                 :           0 :                     throw Exception(Error::Package, "invalid archive entry '%1': directory name is missing '/' at the end").arg(entryPath);
     281         [ #  # ]:           0 :                 entryPath.chop(1);
     282                 :         205 :                 Q_FALLTHROUGH();
     283                 :             : 
     284                 :         205 :             case PackageEntry_File: {
     285                 :             :                 // get the directory, where the new entry will be created
     286   [ +  -  +  -  :         410 :                 QDir entryDir(QString(m_destinationPath + entryPath).section(u'/', 0, -2));
                   +  - ]
     287   [ +  -  -  + ]:         205 :                 if (!entryDir.exists())
     288                 :           0 :                     throw Exception(Error::Package, "invalid archive entry '%1': parent directory is missing").arg(entryPath);
     289                 :             : 
     290   [ +  -  +  - ]:         205 :                 QString entryCanonicalPath = entryDir.canonicalPath() + u'/';
     291   [ +  -  +  -  :         206 :                 QString baseCanonicalPath = QDir(m_destinationPath).canonicalPath() + u'/';
                   +  - ]
     292                 :             : 
     293                 :             :                 // security check: make sure that entryCanonicalPath is NOT outside of baseCanonicalPath
     294   [ +  -  +  + ]:         205 :                 if (!entryCanonicalPath.startsWith(baseCanonicalPath))
     295                 :           1 :                     throw Exception(Error::Package, "invalid archive entry '%1': pointing outside of extraction directory").arg(entryPath);
     296                 :             : 
     297         [ -  + ]:         204 :                 if (packageEntryType == PackageEntry_Dir) {
     298         [ #  # ]:           0 :                     QString entryName = entryPath.section(u'/', -1, -1);
     299                 :             : 
     300   [ #  #  #  #  :           0 :                     if ((entryName != u".") && !entryDir.mkdir(entryName))
                   #  # ]
     301         [ #  # ]:           0 :                         throw Exception(Error::IO, "could not create directory '%1'").arg(entryDir.filePath(entryName));
     302                 :             : 
     303         [ #  # ]:           0 :                     archive_read_data_skip(ar);
     304                 :             : 
     305                 :           0 :                 } else { // PackageEntry_File
     306   [ +  -  +  - ]:         204 :                     f.setFileName(m_destinationPath + entryPath);
     307   [ +  -  -  + ]:         204 :                     if (!f.open(QFile::WriteOnly | QFile::Truncate))
     308                 :           0 :                         throw Exception(f, "could not create file");
     309                 :             : 
     310         [ -  + ]:         204 :                     if (entryMode & S_IEXEC)
     311   [ -  -  -  - ]:           1 :                         f.setPermissions(f.permissions() | QFile::ExeUser);
     312                 :             :                 }
     313                 :             : 
     314         [ +  - ]:         204 :                 m_report.addFile(entryPath);
     315                 :         204 :                 break;
     316                 :         206 :             }
     317                 :             :             case PackageEntry_Footer:
     318                 :             :                 seenFooter = true;
     319                 :             :                 break;
     320                 :          63 :             case PackageEntry_Header:
     321                 :          63 :                 seenHeader = true;
     322                 :          63 :                 break;
     323                 :             :             default:
     324                 :             :                 archive_read_data_skip(ar);
     325                 :             :                 continue;
     326                 :             :             }
     327                 :             : 
     328                 :             :             // Read in the entry's data (which can be a normal file or header/footer metadata)
     329                 :             : 
     330   [ +  -  +  - ]:         343 :             if (archive_entry_size(entry)) {
     331                 :         343 :                 __LA_INT64_T readPosition = 0;
     332                 :             : 
     333                 :         343 :                 for (bool fileFinished = false; !fileFinished; ) {
     334                 :         846 :                     const char *buffer;
     335                 :         846 :                     size_t bytesRead;
     336                 :         846 :                     __LA_INT64_T offset;
     337                 :             : 
     338   [ +  -  +  +  :         846 :                     switch (archive_read_data_block(ar, reinterpret_cast<const void **>(&buffer), &bytesRead, &offset)) {
                      - ]
     339                 :         343 :                     case ARCHIVE_EOF:
     340                 :         343 :                         fileFinished = true;
     341                 :         343 :                         continue;
     342                 :         503 :                     case ARCHIVE_OK:
     343                 :         503 :                         break;
     344                 :           0 :                     default:
     345         [ #  # ]:           0 :                         throw ArchiveException(ar, "could not read from archive");
     346                 :             :                     }
     347                 :             : 
     348         [ -  + ]:         503 :                     if (offset != readPosition)
     349                 :           0 :                         throw Exception(Error::Package, "[libarchive] current read position (%1) does not match requested offset (%2)").arg(readPosition).arg(offset);
     350                 :             : 
     351                 :         503 :                     readPosition += bytesRead;
     352                 :             : 
     353   [ +  +  +  - ]:         503 :                     switch (packageEntryType) {
     354                 :         364 :                     case PackageEntry_File:
     355                 :         364 :                         digest.addData({ buffer, qsizetype(bytesRead) });
     356                 :             : 
     357   [ +  -  -  + ]:         364 :                         if (!f.write(buffer, qint64(bytesRead)))
     358                 :           0 :                             throw Exception(f, "could not write to file");
     359                 :             :                         break;
     360                 :          63 :                     case PackageEntry_Header:
     361         [ +  - ]:          63 :                         header.append(buffer, qsizetype(bytesRead));
     362                 :             :                         break;
     363                 :          76 :                     case PackageEntry_Footer:
     364         [ +  - ]:          76 :                         footer.append(buffer, qsizetype(bytesRead));
     365                 :             :                         break;
     366                 :             :                     default:
     367                 :             :                         break;
     368                 :             :                     }
     369                 :             :                 }
     370                 :             :             }
     371                 :             : 
     372                 :             :             // We finished reading an entry from the archive. Now we need to
     373                 :             :             // post-process it, depending on its type
     374                 :             : 
     375   [ +  +  -  + ]:         343 :             switch (packageEntryType) {
     376                 :          63 :             case PackageEntry_Header:
     377         [ +  + ]:          63 :                 processMetaData(header, digest, true /*header*/);
     378                 :             :                 break;
     379                 :             : 
     380                 :         204 :             case PackageEntry_File:
     381         [ +  - ]:         204 :                 f.close();
     382                 :         204 :                 Q_FALLTHROUGH();
     383                 :             : 
     384                 :         204 :             case PackageEntry_Dir: {
     385                 :             :                 // Just to be on the safe side, we also add the file's meta-data to the digest
     386   [ +  -  +  -  :         214 :                 PackageUtilities::addFileMetadataToDigest(entryPath, QFileInfo(m_destinationPath + entryPath), digest);
                   +  - ]
     387                 :             : 
     388                 :             :                 // Finally call the user's code to post-process whatever was extracted right now
     389         [ +  + ]:         204 :                 if (m_fileExtractedCallback)
     390         [ +  + ]:          97 :                     m_fileExtractedCallback(entryPath);
     391                 :             :                 break;
     392                 :             :             }
     393                 :             :             default:
     394                 :             :                 break;
     395                 :             :             }
     396                 :         397 :         }
     397                 :             : 
     398                 :             :         // Finished extracting
     399                 :             : 
     400                 :             :         // We are only post-processing the footer now, because we allow for multiple --PACKAGE-FOOTER--
     401                 :             :         // files in the archive, so we can only start processing them, when we are sure that there
     402                 :             :         // are no more. This makes it easier for 3rd party tools like e.g. app-stores to add the required
     403                 :             :         // signature metadata
     404         [ +  + ]:          53 :         processMetaData(footer, digest, false /*footer*/);
     405                 :             : 
     406         [ +  - ]:          51 :         emit q->progress(1);
     407                 :             : 
     408         [ -  + ]:          89 :     } catch (const Exception &e) {
     409         [ +  - ]:          14 :         if (!q->wasCanceled())
     410                 :          28 :             setError(e.errorCode(), e.errorString());
     411                 :          14 :     }
     412                 :             : 
     413         [ +  - ]:          65 :     if (ar)
     414                 :          65 :         archive_read_free(ar);
     415                 :             : 
     416                 :          65 :     m_loop.quit();
     417                 :          65 : }
     418                 :             : 
     419                 :         116 : void PackageExtractorPrivate::processMetaData(const QByteArray &metadata, QCryptographicHash &digest,
     420                 :             :                                               bool isHeader) noexcept(false)
     421                 :             : {
     422                 :         116 :     QVector<QVariant> docs;
     423                 :         116 :     try {
     424         [ +  - ]:         232 :         docs = YamlParser::parseAllDocuments(metadata);
     425         [ -  - ]:           0 :     } catch (const Exception &e) {
     426                 :           0 :         throw Exception(Error::Package, "metadata is not a valid YAML document: %1")
     427                 :           0 :                 .arg(e.errorString());
     428                 :           0 :     }
     429                 :             : 
     430         [ +  + ]:         116 :     const QString formatType = isHeader ? u"am-package-header"_s : u"am-package-footer"_s;
     431                 :         116 :     int formatVersion = 0;
     432                 :         116 :     try {
     433   [ +  +  +  +  :         466 :         formatVersion = checkYamlFormat(docs, -2 /*at least 2 docs*/, { { formatType, 2 },
                   +  + ]
     434                 :         116 :                                                                         { formatType, 1 } }).second;
     435         [ -  + ]:           1 :     } catch (const Exception &e) {
     436                 :           1 :         throw Exception(Error::Package, "metadata has an invalid format specification: %1").arg(e.errorString());
     437                 :           1 :     }
     438                 :             : 
     439         [ +  - ]:         115 :     QVariantMap map = docs.at(1).toMap();
     440                 :             : 
     441         [ +  + ]:         115 :     if (isHeader) {
     442         [ -  + ]:          62 :         const QString idField = (formatVersion == 1) ? u"applicationId"_s : u"packageId"_s;
     443                 :             : 
     444   [ +  -  +  - ]:         126 :         QString packageId = map.value(idField).toString();
     445   [ +  -  +  - ]:          62 :         quint64 diskSpaceUsed = map.value(u"diskSpaceUsed"_s).toULongLong();
     446                 :             : 
     447   [ +  -  +  -  :          62 :         if (packageId.isNull() || !PackageInfo::isValidApplicationId(packageId))
                   +  + ]
     448                 :           1 :             throw Exception(Error::Package, "metadata has an invalid %2 field (%1)").arg(packageId).arg(idField);
     449         [ +  - ]:          61 :         m_report.setPackageId(packageId);
     450                 :             : 
     451         [ +  + ]:          61 :         if (!diskSpaceUsed)
     452                 :           1 :             throw Exception(Error::Package, "metadata has an invalid diskSpaceUsed field (%1)").arg(diskSpaceUsed);
     453         [ +  - ]:          60 :         m_report.setDiskSpaceUsed(diskSpaceUsed);
     454                 :             : 
     455   [ +  -  +  -  :         120 :         m_report.setExtraMetaData(map.value(u"extra"_s).toMap());
                   +  - ]
     456   [ +  -  +  -  :         122 :         m_report.setExtraSignedMetaData(map.value(u"extraSigned"_s).toMap());
                   +  - ]
     457                 :             : 
     458         [ +  - ]:          60 :         PackageUtilities::addHeaderDataToDigest(map, digest);
     459                 :             : 
     460                 :          64 :     } else { // footer(s)
     461         [ +  + ]:          76 :         for (int i = 2; i < docs.size(); ++i)
     462   [ +  -  +  - ]:          46 :             map.insert(docs.at(i).toMap());
     463                 :             : 
     464   [ +  -  +  -  :         163 :         QByteArray packageDigest = QByteArray::fromHex(map.value(u"digest"_s).toString().toLatin1());
                   +  - ]
     465                 :             : 
     466         [ -  + ]:          53 :         if (packageDigest.isEmpty())
     467                 :           0 :             throw Exception(Error::Package, "metadata is missing the digest field");
     468         [ +  - ]:          53 :         m_report.setDigest(packageDigest);
     469                 :             : 
     470         [ +  - ]:          53 :         QByteArray calculatedDigest = digest.result();
     471         [ +  + ]:          53 :         if (calculatedDigest != packageDigest)
     472   [ +  -  +  - ]:           2 :             throw Exception(Error::Package, "package digest mismatch (is %1, but should be %2").arg(calculatedDigest.toHex()).arg(packageDigest.toHex());
     473                 :             : 
     474   [ +  -  +  -  :         153 :         m_report.setStoreSignature(QByteArray::fromBase64(map.value(u"storeSignature"_s).toString().toLatin1()));
             +  -  +  - ]
     475   [ +  -  +  -  :         155 :         m_report.setDeveloperSignature(QByteArray::fromBase64(map.value(u"developerSignature"_s).toString().toLatin1()));
             +  -  +  - ]
     476                 :          55 :     }
     477                 :         120 : }
     478                 :             : 
     479                 :          15 : void PackageExtractorPrivate::setError(Error errorCode, const QString &errorString)
     480                 :             : {
     481                 :          15 :     m_failed = true;
     482                 :             : 
     483                 :             :     // only the first error is the one that counts!
     484   [ -  -  +  + ]:          14 :     if (m_errorCode == Error::None) {
     485                 :          14 :         m_errorCode = errorCode;
     486                 :          14 :         m_errorString = errorString;
     487                 :             :     }
     488                 :           0 : }
     489                 :             : 
     490                 :             : 
     491                 :          65 : void PackageExtractorPrivate::download(const QUrl &url)
     492                 :             : {
     493                 :          65 :     QNetworkRequest request(url);
     494         [ +  - ]:          65 :     m_reply = m_nam->get(request);
     495                 :             : 
     496                 :             : #if defined(Q_OS_UNIX)
     497                 :             :     // This is an ugly hack, but it allows us to use FIFOs in the unit tests.
     498                 :             :     // (the problem being, that bytesAvailable() on a QFile wrapping a FIFO will always return 0)
     499                 :             : 
     500   [ +  -  +  - ]:          65 :     if (url.isLocalFile()) {
     501                 :          65 :         struct stat statBuffer;
     502   [ +  -  +  -  :         130 :         if (stat(url.toLocalFile().toLocal8Bit(), &statBuffer) == 0) {
                   +  + ]
     503         [ +  + ]:          64 :             if (S_ISFIFO(statBuffer.st_mode)) {
     504                 :           1 :                 m_downloadingFromFIFO = true;
     505                 :             :             }
     506                 :             :         }
     507                 :             :     }
     508                 :             : #endif
     509                 :             : 
     510                 :          65 :     connect(m_reply, &QNetworkReply::errorOccurred,
     511         [ +  - ]:          65 :             this, &PackageExtractorPrivate::networkError);
     512                 :          65 :     connect(m_reply, &QNetworkReply::metaDataChanged,
     513         [ +  - ]:          65 :             this, &PackageExtractorPrivate::handleRedirect);
     514                 :          65 :     connect(m_reply, &QNetworkReply::downloadProgress,
     515         [ +  - ]:          65 :             this, &PackageExtractorPrivate::downloadProgressChanged);
     516                 :          65 : }
     517                 :             : 
     518                 :           1 : void PackageExtractorPrivate::networkError(QNetworkReply::NetworkError)
     519                 :             : {
     520         [ +  - ]:           2 :     setError(Error::Network, qobject_cast<QNetworkReply *>(sender())->errorString());
     521                 :           1 :     QMetaObject::invokeMethod(&m_loop, "quit", Qt::QueuedConnection);
     522                 :           1 : }
     523                 :             : 
     524                 :          64 : void PackageExtractorPrivate::handleRedirect()
     525                 :             : {
     526         [ +  - ]:          64 :     int status = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
     527         [ -  + ]:          64 :     if ((status >= 300) && (status < 400)) {
     528         [ #  # ]:           0 :         QUrl url = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
     529         [ #  # ]:           0 :         m_reply->disconnect();
     530         [ #  # ]:           0 :         m_reply->deleteLater();
     531         [ #  # ]:           0 :         QNetworkRequest request(url);
     532         [ #  # ]:           0 :         m_reply = m_nam->get(request);
     533                 :           0 :     }
     534                 :          64 : }
     535                 :             : 
     536                 :          64 : void PackageExtractorPrivate::downloadProgressChanged(qint64 downloaded, qint64 total)
     537                 :             : {
     538                 :          64 :     Q_UNUSED(downloaded)
     539                 :          64 :     m_downloadTotal = total;
     540                 :           0 : }
     541                 :             : 
     542                 :             : QT_END_NAMESPACE_AM
     543                 :             : 
     544                 :             : #include "moc_packageextractor.cpp"
     545                 :             : 
     546                 :             : #include "moc_packageextractor_p.cpp"
        

Generated by: LCOV version 2.0-1