archivehandler.cpp 21.5 KB
Newer Older
1 2 3
/*
 * Libarchive interface

4
   Copyright (C) 2011 Lisa Vitolo <shainer@chakra-project.org>
5 6 7 8 9 10 11

   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 2 of the License, or (at your option) any later version.
*/

12
#include <libarchive++/archivehandler.h>
13

Lukas Appelhans's avatar
Lukas Appelhans committed
14
#include <QByteArray>
15
#include <akabeidebug.h>
Lukas Appelhans's avatar
Lukas Appelhans committed
16 17 18 19
#include <QFile>
#include <QDir>

#include <archive_entry.h>
20
#include <archive.h>
Lukas Appelhans's avatar
Lukas Appelhans committed
21

22 23
#include <akabeihelpers.h>
#include <akabeiconfig.h>
24
#include <operations/akabeioperationutils.h>
Lukas Appelhans's avatar
Lukas Appelhans committed
25

26 27 28
ArchiveEntry::ArchiveEntry(archive_entry *e)
    : entry(archive_entry_clone(e))
{
29
    if (entry == nullptr) {
30 31 32
        throw ArchiveException("archive_entry_new() failed");
    }

33
    fileName = QString::fromLatin1(archive_entry_pathname(entry));
34 35 36 37 38 39 40 41 42 43
    fileSize = archive_entry_size(entry);
    fileMode = archive_entry_mode(entry);
}

ArchiveEntry::ArchiveEntry(ArchiveEntry const& other)
    : entry( archive_entry_clone(other.entry) )
    , fileMode( other.fileMode )
    , fileSize( other.fileSize )
    , fileName( other.fileName )
{
44
    if (entry == nullptr) {
45 46 47 48 49 50
        throw ArchiveException("archive_entry_clone() failed");
    }
}

ArchiveEntry::~ArchiveEntry()
{
51
    if (entry != nullptr) {
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
        archive_entry_free(entry);
    }
}

struct archive_entry* ArchiveEntry::getEntry() const
{
    return const_cast<struct archive_entry*>( entry );
}

QString ArchiveEntry::getFileName() const
{
    return fileName;
}

size_t ArchiveEntry::getFileSize() const
{
    return fileSize;
}

bool ArchiveEntry::isDirectory() const
{
    return S_ISDIR(fileMode);
}

bool ArchiveEntry::isSymlink() const
{
    return S_ISLNK(fileMode); /* symbolic link check only, see stat(2) */
}
80

Lukas Appelhans's avatar
Lukas Appelhans committed
81 82 83 84
/* Needed by libarchive to read and extract files from archive */
#define BLOCK_SIZE 1024
#define EXTRACT_FLAGS ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS

85 86
ArchiveHandler::ArchiveHandler(const QString &path)
    : archiveName(path)
Lukas Appelhans's avatar
Lukas Appelhans committed
87
{
88 89 90 91 92 93 94
    /*
     * This (together with the _support_ and _finish_ calls) must be called for every operation.
     * This is because reading headers or data from the archive requires the object to be in a new state
     * so you can't reuse it (unless there's some libarchive function to do that and I missed it)
     */
    archive = archive_read_new();
    checkForErrors();
95

96 97 98 99
    /*
     * Enables all the format supported by the libarchive
     * the format and compression type are automatically recognized
     */
100
    archive_read_support_filter_all(archive);
101 102 103 104 105 106 107 108 109
    archive_read_support_format_all(archive);

    checkForErrors(archive_read_open_filename(archive, archiveName.toUtf8().data(), BLOCK_SIZE));

    /*
     * Read headers from the archive and saves names
     */
    archive_entry *e;
    while (archive_read_next_header(archive, &e) == ARCHIVE_OK) {
110
        entries[QString::fromLatin1(archive_entry_pathname(e))] = new ArchiveEntry( e );
111 112 113 114
        archive_read_data_skip(archive);
    }

    /* Closes the archive pointer */
115
    checkForErrors(archive_read_free(archive));
Lukas Appelhans's avatar
Lukas Appelhans committed
116
}
117

Lukas Appelhans's avatar
Lukas Appelhans committed
118 119
ArchiveHandler::~ArchiveHandler()
{
120 121
    qDeleteAll(entries);
    entries.clear();
Lukas Appelhans's avatar
Lukas Appelhans committed
122
}
123

124 125 126 127 128
QString ArchiveHandler::filename() const
{
    return archiveName;
}

129 130 131 132 133 134
bool ArchiveHandler::write(const QString& filename, const QHash< QString, QString >& files)
{
    struct archive *a;
    struct archive_entry *entry;
    struct stat st;
    char buff[8192];
135
    int len, fd;
136 137

    a = archive_write_new();
138

139
    archive_write_add_filter_xz(a);
140
    archive_write_set_format_pax_restricted(a);
141

142
    if (archive_write_open_filename(a, filename.toUtf8().data()) != ARCHIVE_OK) {
143
        return false;
144 145
    }

146 147
    QHash<QString, QString>::const_iterator it = files.begin();
    for (; it != files.end(); it++) {
148

Lisa's avatar
Lisa committed
149
        stat(it.value().toUtf8().data(), &st);
150
        entry = archive_entry_new();
151

152
        archive_entry_set_pathname(entry, it.key().toUtf8().data());
153
        archive_entry_copy_stat(entry, &st);
154
        archive_entry_set_size(entry, st.st_size);
155 156
        archive_entry_set_filetype(entry, AE_IFREG);
        archive_entry_set_perm(entry, 0644);
157

158 159 160
        if (archive_write_header(a, entry) != ARCHIVE_OK) {
            return false; // TODO: maybe clean up?
        }
161

162
        fd = open(it.value().toUtf8().data(), O_RDONLY);
163
        len = read(fd, buff, sizeof(buff));
164
        while (len > 0) {
Lukas Appelhans's avatar
Lukas Appelhans committed
165
            archive_write_data(a, buff, len);
166 167
            len = read(fd, buff, sizeof(buff));
        }
168

169 170 171
        close(fd);
        archive_entry_free(entry);
    }
172

173
    archive_write_close(a);
174
    archive_write_free(a);
175 176 177
    return true;
}

178 179

QStringList ArchiveHandler::getEntries() const
180
{
181 182 183 184 185 186 187
    return entries.uniqueKeys();
}

size_t ArchiveHandler::getEntrySize(QString const& fileName) const
{
    if (!entries.contains(fileName)) {
        throw ArchiveException("The file requested doesn't exist.");
188
    }
189

190 191
    return entries[fileName]->getFileSize();
}
192

193 194 195
size_t ArchiveHandler::totalEntrySize() const
{
    size_t total = 0;
196 197
    for (auto it = entries.constBegin(), end = entries.constEnd(); it != end; ++it) {
        const auto& file = it.key();
198 199 200 201
        total += entries[file]->getFileSize();
    }
    return total;
}
202

203 204
bool ArchiveHandler::checkPackageValidity()
{
205
    return (entries.find(QStringLiteral(".PKGINFO")) != entries.end());
206
}
207

208
void ArchiveHandler::handleEntries(Akabei::EntryHandlerFunctor& handler)
209 210
{
    archive = archive_read_new();
211
    archive_read_support_filter_all(archive);
212
    archive_read_support_format_all(archive);
213
    checkForErrors(archive_read_open_filename(archive, archiveName.toUtf8().data(), BLOCK_SIZE));
214

215 216 217 218
    archive_entry *e;
    while (archive_read_next_header(archive, &e) == ARCHIVE_OK) {
        ArchiveEntry const entry(e);
        if (!handler(entry)) {
219
            akabeiDebug() << "Entry \"" << entry.getFileName() << "\" skipped because of an error during handling";
220
        }
221
    }
222

223
    checkForErrors(archive_read_free(archive));
224 225
}

226 227 228 229
/*
 * Read content of text file: every string in list contains a single row
 */
QString ArchiveHandler::readTextFile(const QString& textFile)
230
{
231
    if (!entries.contains(textFile)) {
232
        QString message = QStringLiteral("ERROR: no such file in archive: %1");
233
        throw ArchiveException(message.arg(textFile).toUtf8().data());
234
    }
235

236 237
    archive = archive_read_new();
    checkForErrors();
238

239
    archive_read_support_filter_all(archive);
240
    archive_read_support_format_all(archive);
241

242
    checkForErrors(archive_read_open_filename(archive, archiveName.toUtf8().data(), BLOCK_SIZE));
243

244 245 246
    struct archive_entry* it;
    while (archive_read_next_header(archive, &it) == ARCHIVE_OK) {
        QString entryName( archive_entry_pathname(it) );
247

248
        if (entryName == textFile) {
249
            QString result = readData();
250
            checkForErrors(archive_read_free(archive));
251
            return result;
252
        }
253
    }
254

255
    checkForErrors(archive_read_free(archive));
256
    return QString();
257 258
}

259
/*
260
 * Extract all files in the archive in pathPrefix
261
 */
262
void ArchiveHandler::extractAll(const QString& pathPrefix, bool overwrite)
263
{
264
    Akabei::ExtractAllFunctor extractor(pathPrefix, overwrite, nullptr, (*this));
265
    handleEntries(extractor);
266 267 268
}

/*
Lisa's avatar
Lisa committed
269
 * Extraction of a single file named source in a file named destName on filesystem
270 271 272 273 274 275
 */
void ArchiveHandler::extract(const QString& source, const QString& destName, bool overwrite)
{
    QStringList sources, destPaths;
    int ret;

276
    if (entries.find(source) == entries.end()) {
277 278 279
        throw ArchiveException("ERROR: no such file in archive");
    }

280
    /* FIXME: is it needed?! the subentries are separate entries anyway! */
281 282 283 284 285 286 287
    /* These two lists are needed because if source is a directory, then we extract all its tree */
    sources << source;
    destPaths << destName;

    archive = archive_read_new();
    checkForErrors();

288
    archive_read_support_filter_all(archive);
289 290
    archive_read_support_format_all(archive);

291
    ret = archive_read_open_filename(archive, archiveName.toUtf8().data(), BLOCK_SIZE);
292 293
    checkForErrors(ret);

294
    archive_entry *next;
295
    /* Looks for source in archive */
296 297 298
    while (archive_read_next_header(archive, &next) == ARCHIVE_OK) {
        ArchiveEntry entry( next );
        int i( sources.indexOf(entry.getFileName()) );
299

300 301
        if (i != -1) {
            QString destPath( destPaths[i] );
302

303
            /* Is it safe to create the file? */
Lisa's avatar
Lisa committed
304
            if (!checkOverwriting(destName, entry, overwrite)) {
305 306 307 308
                continue;
            }

            /* Actual extraction in the pathname given by the user */
Lisa's avatar
Lisa committed
309
            archive_entry_set_pathname(entry.getEntry(), destName.toUtf8().data());
310
            ret = archive_read_extract(archive, entry.getEntry(), EXTRACT_FLAGS);
311 312
            checkForErrors(ret);

313 314
            if (entry.isDirectory()) {
                QStringList children( listFilesFromDirectory(entry.getFileName(), getEntries()) );
315 316 317 318 319 320
                QStringList childrenPaths;

                /* Remove a trailing '/' if present */
                if (destPath.endsWith('/')) {
                    destPath.chop(1);
                }
321
                QString prefix( getPrefixFromDirPath(destPath) ); /* the path without the directory name */
322 323

                /* Create children paths and then update the structures */
324
                foreach (QString const& child, children) {
325 326 327 328 329 330 331 332 333
                    childrenPaths << (prefix + child);
                }

                sources << children;
                destPaths << childrenPaths;
            }
        }
    }

334
    archive_read_free(archive);
335 336
}

337
void ArchiveHandler::extract(const ArchiveEntry& entry, const QString& destName, bool overwrite)
338 339
{
    int ret;
340

Lisa's avatar
Lisa committed
341
    if (!checkOverwriting(destName, entry, overwrite)) {
342 343
        return;
    }
344

Lisa's avatar
Lisa committed
345
    archive_entry_set_pathname(entry.getEntry(), destName.toUtf8().data());
346 347 348 349
    ret = archive_read_extract(archive, entry.getEntry(), EXTRACT_FLAGS);
    checkForErrors(ret);
}

350 351 352 353 354 355
/*
 * What should happen:
 * /some/path --> /some/
 * /etc --> /
 * empty string (current working directory) --> empty string
 */
356
QString ArchiveHandler::getPrefixFromDirPath(const QString &path)
357
{
358 359
    int last( path.lastIndexOf('/') );
    return path.mid(0, last + 1);
360 361
}

362 363 364 365
/*
 * FIXME: are symlinks-related rules really needed?
 * This function checks if writing the new file pointed by entry in "filename" path is safe.
 */
366
bool ArchiveHandler::checkOverwriting(const QString &filename, ArchiveEntry const& entry, bool overwrite)
367 368 369 370
{
    QFile file(filename);
    QDir dir(filename);

371
    bool ok( true );
372
    ok = ok && (!QFile::exists(filename) || (QFile::exists(filename) && overwrite)); /* file doesn't exist, or the user chose to overwrite it */
373 374 375 376
    ok = ok && (!dir.exists()); /* no directory must be overwritten */

    QDir target(file.symLinkTarget());
    ok = ok && (target.exists()); /* not even a symlink pointing to a directory */
377
    ok = ok && !((file.exists() || dir.exists()) && entry.isSymlink()); /* a symlink can't overwrite anything with its same path */
378 379 380 381 382 383 384 385

    return ok;
}

/*
 * Returns a list of file contained in the directory dir (in the archive)
 * Very simple because entry names in archive already contain the information
 */
Lukas Appelhans's avatar
Lukas Appelhans committed
386
const QStringList ArchiveHandler::listFilesFromDirectory(const QString &dir, const QStringList& entries)
387 388 389
{
    QStringList children;

390
    foreach (QString const& file, entries) {
391 392 393 394 395 396 397 398 399 400 401
        if (file.startsWith(dir) && file != dir) {
            children << file;
        }
    }

    return children;
}

/*
 * Reads all the data from the current entry
 */
402
QString ArchiveHandler::readData()
403 404 405 406
{
    QString data;

    while (true) {
407
        const char* buffer;
408
        int64_t offset;
409 410 411 412 413
        size_t size;

        int rc( archive_read_data_block(archive, (const void**) &buffer, &size, &offset) );

        if (rc == ARCHIVE_EOF) {
414 415 416
            break;
        }

417 418
        checkForErrors(rc);
        data.append(buffer);
419 420
    }

421
    return data;
422 423
}

424 425 426 427
QByteArray ArchiveHandler::md5(const QString& file)
{
    QStringList list;
    list.append(file);
428

429
    QList<QByteArray> hashes = md5(list);
430

431
    if (hashes.isEmpty()) {
432
        QString errorString = QObject::tr("MD5: requested file %1 isn't present in archive %2").arg(file, archiveName);
433
        errs << Akabei::Error(Akabei::Error::ChecksumError, errorString);
434
        return QByteArray();
435
    }
436

437 438 439
    return hashes.first();
}

440
/*
441
 * Md5 sum for archive entries.
442
 */
443
QList< QByteArray > ArchiveHandler::md5(const QStringList& files)
444
{
445
    Akabei::Md5Functor hasher(files, nullptr, (*this));
446
    handleEntries(hasher);
447

448 449
    if (!hasher.skipped().isEmpty()) {
        QString description;
450

451 452 453
        foreach (const QString& target, hasher.skipped()) {
            description += QObject::tr("MD5: requested file %1 isn't present in archive %2\n").arg(target, archiveName);
        }
454

455
        errs << Akabei::Error(Akabei::Error::ChecksumError, description);
456
    }
457

458
    return hasher.result();
459 460
}

461 462 463 464 465
Akabei::Error::List ArchiveHandler::errors()
{
    return errs;
}

Lisa's avatar
Lisa committed
466 467 468 469 470
struct archive *ArchiveHandler::getArchive()
{
    return archive;
}

471 472 473 474 475
/*
 * Error handling
 */
void ArchiveHandler::checkForErrors()
{
476
    if (archive == nullptr) {
477 478 479 480 481 482 483 484 485
        throw ArchiveException("Error creating archive object for operations");
    }
}

void ArchiveHandler::checkForErrors(int ret)
{
    if (ret < 0) { /* don't worry, ARCHIVE_EOF is positive */
        throw ArchiveException(archive_error_string(archive));
    }
486
}
487

488 489 490 491 492 493 494 495 496
/***** FUNCTORS ******/

namespace Akabei {

EntryHandlerFunctor::EntryHandlerFunctor(Akabei::Operation *caller, ArchiveHandler& handle)
    : m_caller(caller)
    , m_handle(handle)
{}

497
InstallFunctor::InstallFunctor(QMap< QString, QString >& backup, const QStringList &uo, Akabei::Operation *caller, ArchiveHandler& handle)
498
    : EntryHandlerFunctor(caller, handle)
499
    , backupFiles(backup)
500
    , ultimatelyOwned(uo)
501 502 503
    , partialSize(0)
{}

504
bool InstallFunctor::operator()(const ArchiveEntry& currentEntry)
505 506
{
    QString entryName = currentEntry.getFileName();
507

508
    if (entryName == QLatin1String(".INSTALL") || entryName == QLatin1String(".PKGINFO") || entryName == QLatin1String(".MTREE") || entryName == QLatin1String(".Changelog"))
509 510
        return true;

511
    OperationUtils utils;
512

513 514 515 516 517 518
    /*
     * Check whether the entry is marked with the NoExtract option
     */
    if (!utils.isFileToExtract(entryName)) {
        return true;
    }
519

520 521 522 523
    if (backupFiles.contains(entryName) && QFile::exists(entryName)) {
        QString file = Akabei::Config::instance()->rootDir().absoluteFilePath(entryName);
        bool done = QFile::rename(file, file + ".akabeisave");
        if (done) {
524
            m_caller->addMessage(QObject::tr("Akabei found an existing config and moved it to %1...").arg(file + ".akabeisave"));
525
        } else {
526
            m_caller->addMessage(QObject::tr("Akabei found an existing config, but had a problem to move it to %1. "
527 528
            "Maybe an old config was still in place. Please fix it yourself!").arg(file + ".akabeisave"));
        }
529 530 531 532 533
    } else if (ultimatelyOwned.contains(entryName)) {
        QString file = Akabei::Config::instance()->rootDir().absoluteFilePath(entryName);
        bool done = QFile::rename(file, file + ".akabeisave");
        if (done) {
            m_caller->addMessage(
534 535 536
                QObject::tr("As %1 is the ultimate owner of %2, akabei moved the old file to %3!").arg(
                m_caller->targetName(),
                file, file + ".akabeisave"));
537 538
        } else if (QFile::exists(file)) {
            m_caller->addMessage(
539
                QObject::tr("As %1 is the ultimate owner of %2, akabei tried to move the old file to %3 but failed! "
540
                    "Please fix it yourself!"
541
                ).arg(m_caller->targetName(), file, file + ".akabeisave"));
542
        }
543
    }
544

545 546
    partialSize += currentEntry.getFileSize();
    int percent = 0;
547
    if (m_handle.totalEntrySize() > 0) {
548
        percent = (int)(partialSize * 100 / m_handle.totalEntrySize());
549
    }
550
    m_caller->setProgress(percent);
551

552 553 554 555 556 557 558 559
    /*
     * The path may vary if the entry is marked with the NoUpgrade option
     * in that case, a message is displayed to the user.
     */
    QString extractPath = utils.getExtractionPath(entryName);
    foreach (const QString& message, utils.messages()) {
        m_caller->addMessage(message);
    }
560

561
    m_handle.extract(currentEntry, extractPath, m_caller && m_caller->processingOptions().testFlag(Akabei::Force));
562 563 564
    return true;
}

565 566
ReinstallUpgradeFunctor::ReinstallUpgradeFunctor(QMap< QString, QString >& backup, const QStringList &uo, 
                                                 Akabei::Operation *caller, ArchiveHandler& handle)
567 568
    : EntryHandlerFunctor(caller, handle)
    , backupFiles(backup)
569
    , ultimatelyOwned(uo)
570 571 572 573
    , partialSize(0)
{}

bool ReinstallUpgradeFunctor::operator()(const ArchiveEntry& currentEntry)
574 575
{
    QString entryName = currentEntry.getFileName();
576

577
    if (entryName == QLatin1String(".INSTALL") || entryName == QLatin1String(".PKGINFO"))
578 579
        return true;

580
    OperationUtils utils;
581 582
    QString path = Akabei::Config::instance()->rootDir().absoluteFilePath(entryName);
    bool overwrite = false;
583

584 585 586 587 588 589
    /*
     * Check whether the entry is marked with the NoExtract option
     */
    if (!utils.isFileToExtract(entryName)) {
        return true;
    }
590 591 592

    if (backupFiles.contains(path) && !backupFiles[path].isEmpty()) {
        //Get two of the 3 possible md5sums
593
        QByteArray originalMD5Sum = backupFiles[path].toLatin1();
594 595 596 597 598
        QByteArray onDiskMd5Sum = Akabei::Helpers::md5sumOfFile(path);

        //First extract the new file then check the md5sum
        QString newDiskPath = "tmp/akabei_" + path.split('/').last();
        newDiskPath = Akabei::Config::instance()->rootDir().absoluteFilePath(newDiskPath);//extract_single_file is chdir to the rootDir()
599
        m_handle.extract(entryName, newDiskPath, true);
600 601 602 603 604
        QByteArray newMd5sum = Akabei::Helpers::md5sumOfFile(newDiskPath);

        if (originalMD5Sum == newMd5sum) {
            return true;
        }
605
        if (onDiskMd5Sum != originalMD5Sum && newMd5sum != onDiskMd5Sum && !onDiskMd5Sum.isEmpty()) {
606
            path = path + ".akabei";
607
            m_caller->addMessage(QObject::tr("Installing new config to %1. Please merge manually!").arg(path));
608
            akabeiDebug() << "Copying file to" << path;
609
            QFile::rename(newDiskPath, path);
610 611 612 613
            return true;
        } else {
            overwrite = true;
        }
614 615 616 617 618
    } else if (ultimatelyOwned.contains(entryName)) {
        QString file = Akabei::Config::instance()->rootDir().absoluteFilePath(entryName);
        bool done = QFile::rename(file, file + ".akabeisave");
        if (done) {
            m_caller->addMessage(
619 620 621
                QObject::tr("As %1 is the ultimate owner of %2, akabei moved the old file to %3!").arg(
                m_caller->targetName(),
                file, file + ".akabeisave"));
622 623
        } else if (QFile::exists(file)) {
            m_caller->addMessage(
624
                QObject::tr("As %1 is the ultimate owner of %2, akabei tried to move the old file to %3 but failed! "
625
                    "Please fix it yourself!"
626
                ).arg(m_caller->targetName(), file, file + ".akabeisave"));
627
        }
628 629 630
    }

    partialSize += currentEntry.getFileSize();
Fabian Kosmale's avatar
Fabian Kosmale committed
631
    int percent = 0;
632 633
    if (m_handle.totalEntrySize() != 0) {
        percent = (int)((partialSize * 100) / m_handle.totalEntrySize());
634
    }
635
    m_caller->setProgress(percent);
636

637 638 639 640 641 642 643 644
    /*
     * The path may vary if the entry is marked with the NoUpgrade option
     * in that case, a message is displayed to the user.
     */
    QString extractPath = utils.getExtractionPath(entryName);
    foreach (const QString& message, utils.messages()) {
        m_caller->addMessage(message);
    }
645

646
    qDebug() << "EXTRACT" << currentEntry.getFileName() << currentEntry.isSymlink();
647 648 649 650 651 652 653 654
    m_handle.extract(currentEntry, extractPath, m_caller->processingOptions().testFlag(Akabei::Force) || overwrite);
    return true;
}

ExtractAllFunctor::ExtractAllFunctor(QString p, bool ow, Akabei::Operation *caller, ArchiveHandler& handle)
    : EntryHandlerFunctor(caller, handle)
    , pathPrefix(p)
    , overwrite(ow)
Lisa's avatar
Lisa committed
655
{
656
    if (!pathPrefix.endsWith(QLatin1String("/"))) {
Lisa's avatar
Lisa committed
657 658 659
        pathPrefix.append("/");
    }
}
660 661 662

bool ExtractAllFunctor::operator()(const ArchiveEntry& currentEntry)
{
Lisa's avatar
Lisa committed
663
    m_handle.extract(currentEntry, pathPrefix + currentEntry.getFileName(), overwrite);
664 665 666 667 668 669 670 671 672
    return true;
}

Md5Functor::Md5Functor(QStringList list, Akabei::Operation *caller, ArchiveHandler& handle)
    : EntryHandlerFunctor(caller, handle)
    , m_toCompute(list)
{}

bool Md5Functor::operator()(const ArchiveEntry& currentEntry)
673
{
674 675 676
    if (!m_toCompute.contains(currentEntry.getFileName())) {
        return true;
    }
677

678 679 680
    /* Md5-related variables */
    md5_context context;
    unsigned char md5output[16];
Lisa's avatar
Lisa committed
681
    struct archive *archive = m_handle.getArchive();
682 683 684 685

    if (currentEntry.isDirectory()) {
        return false;
    }
686

687
    md5_starts(&context);
Lisa's avatar
Lisa committed
688 689 690 691 692 693 694

    /*
     * Sadly we can't use readData for this, because QString doesn't handle very well non-text files, while
     * we want to be able to compute the hash of all kinds of files.
     */
    while (true) {
        const char* buffer;
695
        int64_t offset;
Lisa's avatar
Lisa committed
696 697 698 699 700 701 702 703 704 705
        size_t size;

        int rc( archive_read_data_block(archive, (const void**) &buffer, &size, &offset) );

        if (rc == ARCHIVE_EOF) {
            break;
        }

        md5_update(&context, (unsigned char *)buffer, size);
    }
706

707
    md5_finish(&context, md5output); /* Write md5 hash to our array and ends processing */
708

709
    m_result.append(Akabei::Helpers::processMd5(md5output));
710
    m_toCompute.removeOne(currentEntry.getFileName());
711 712
    return true;
}
713 714 715 716 717

QList< QByteArray > Md5Functor::result()
{
    return m_result;
}
718 719 720 721

QStringList Md5Functor::skipped()
{
    return m_toCompute;
722
}
723
}