Commit message: harden plugin unzipping to zip-slip attacks
CVE: CVE-2019-14452
CWE: CWE-22
Vulnerability description: Sigil before 0.9.16 is vulnerable to a directory traversal, allowing attackers to write arbitrary files via a ../ (dot dot slash) in a ZIP archive entry that is mishandled during extraction.

Project: Sigil
File: sigil_exception.h

    EPUBLoadParseError(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: Utility.cpp

bool Utility::IsMixedCase(const QString &string)
{
    if (string.isEmpty() || string.length() == 1) {
        return false;
    }

    bool first_char_lower = string[ 0 ].isLower();

    for (int i = 1; i < string.length(); ++i) {
        if (string[ i ].isLower() != first_char_lower) {
            return true;
        }
    }

    return false;
}

Project: Sigil
File: sigil_exception.h

    ErrorParsingXml(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: sigil_exception.h

    NoHTMLFiles(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: Utility.cpp

QString Utility::GetEnvironmentVar(const QString &variable_name)
{
    // Renaming this function (and all references to it)
    // to GetEnvironmentVariable gets you a linker error
    // on MSVC 9. Funny, innit?
    QRegularExpression search_for_name("^" + QRegularExpression::escape(variable_name) + "=");
    QString variable = QProcess::systemEnvironment().filter(search_for_name).value(0);

    if (!variable.isEmpty()) {
        return variable.split("=")[ 1 ];
    } else {
        return QString();
    }
}

Project: Sigil
File: Utility.cpp

void Utility::DisplayStdWarningDialog(const QString &warning_message, const QString &detailed_text)
{
    SigilMessageBox message_box(QApplication::activeWindow());
    message_box.setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint);
    message_box.setModal(true);
    message_box.setIcon(QMessageBox::Warning);
    message_box.setWindowTitle("Sigil");
    message_box.setText(warning_message);
    message_box.setTextFormat(Qt::RichText);

    if (!detailed_text.isEmpty()) {
        message_box.setDetailedText(detailed_text);
    }
    message_box.setStandardButtons(QMessageBox::Close);
    message_box.exec();
}

Project: Sigil
File: sigil_exception.h

    CannotOpenFile(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: Utility.cpp

float Utility::RoundToOneDecimal(float number)
{
    return QString::number(number, 'f', 1).toFloat();
}

Project: Sigil
File: Utility.cpp

QStringRef Utility::SubstringRef(int start_index, int end_index, const QString &string)
{
    return string.midRef(start_index, end_index - start_index);
}

Project: Sigil
File: Utility.cpp

        virtual void resizeEvent(QResizeEvent * e) {
            QMessageBox::resizeEvent(e);
            setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
            if (QWidget *textEdit = findChild<QTextEdit *>()) {
                textEdit->setMaximumHeight(QWIDGETSIZE_MAX);
            }
        }

Project: Sigil
File: Utility.cpp

QString Utility::GetTemporaryFileNameWithExtension(const QString &extension)
{
    SettingsStore ss;
    QString temp_path = ss.tempFolderHome();
    if (temp_path == "<SIGIL_DEFAULT_TEMP_HOME>") {
        temp_path = QDir::tempPath();
    }
    return temp_path +  "/sigil_" + Utility::CreateUUID() + extension;
}

Project: Sigil
File: Utility.cpp

QStringList Utility::ZipInspect(const QString &zippath)
{
    QStringList filelist;
    int res = 0;

    if (!cp437) {
        cp437 = new QCodePage437Codec();
    }
#ifdef Q_OS_WIN32
    zlib_filefunc64_def ffunc;
    fill_win32_filefunc64W(&ffunc);
    unzFile zfile = unzOpen2_64(Utility::QStringToStdWString(QDir::toNativeSeparators(zippath)).c_str(), &ffunc);
#else
    unzFile zfile = unzOpen64(QDir::toNativeSeparators(zippath).toUtf8().constData());
#endif

    if ((zfile == NULL) || (!IsFileReadable(zippath))) {
        return filelist;
    }
    res = unzGoToFirstFile(zfile);
    if (res == UNZ_OK) {
        do {
            // Get the name of the file in the archive.
            char file_name[MAX_PATH] = {0};
            unz_file_info64 file_info;
            unzGetCurrentFileInfo64(zfile, &file_info, file_name, MAX_PATH, NULL, 0, NULL, 0);
            QString qfile_name;
            QString cp437_file_name;
            qfile_name = QString::fromUtf8(file_name);
            if (!(file_info.flag & (1<<11))) {
                cp437_file_name = cp437->toUnicode(file_name);
            }

            // If there is no file name then we can't do anything with it.
            if (!qfile_name.isEmpty()) {
                if (!cp437_file_name.isEmpty() && cp437_file_name != qfile_name) {
                    filelist.append(cp437_file_name);
                } else {
                    filelist.append(qfile_name);
                }
            }
        } while ((res = unzGoToNextFile(zfile)) == UNZ_OK);
    }
    unzClose(zfile);
    return filelist;
}

Project: Sigil
File: Utility.cpp

QString Utility::URLDecodePath(const QString &path)
{
    return QUrl::fromPercentEncoding(path.toUtf8());
}

Project: Sigil
File: Utility.cpp

bool Utility::RenameFile(const QString &oldfilepath, const QString &newfilepath)
{
    // Make sure the path exists, otherwise very
    // bad things could happen
    if (!QFileInfo(oldfilepath).exists()) {
        return false;
    }

    // Ensure that the newfilepath doesn't already exist but due to case insenstive file systems
    // check if we are actually renaming to an identical path with a different case.
    if (QFileInfo(newfilepath).exists() && QFileInfo(oldfilepath) != QFileInfo(newfilepath)) {
        return false;
    }

    // On case insensitive file systems, QFile::rename fails when the new name is the
    // same (case insensitive) to the old one. This is workaround for that issue.
    int ret = -1;
#if defined(Q_OS_WIN32)
    ret = _wrename(Utility::QStringToStdWString(oldfilepath).data(), Utility::QStringToStdWString(newfilepath).data());
#else
    ret = rename(oldfilepath.toUtf8().data(), newfilepath.toUtf8().data());
#endif

    if (ret == 0) {
        return true;
    }

    return false;
}

Project: Sigil
File: Utility.cpp

bool Utility::removeDir(const QString &dirName)
{
    bool result = true;
    QDir dir(dirName);

    if (dir.exists(dirName)) {
        Q_FOREACH(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden  | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
            if (info.isDir()) {
                result = removeDir(info.absoluteFilePath());
            } else {
                result = QFile::remove(info.absoluteFilePath());
            }

            if (!result) {
                return result;
            }
        }
        result = dir.rmdir(dirName);
    }

    return result;
}

Project: Sigil
File: sigil_exception.h

    CannotStoreFile(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: Utility.cpp

QString Utility::ConvertLineEndings(const QString &text)
{
    QString newtext(text);
    return newtext.replace("\x0D\x0A", "\x0A").replace("\x0D", "\x0A");
}

Project: Sigil
File: sigil_exception.h

    ResourceDoesNotExist(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: Utility.cpp

QString Utility::DecodeXML(const QString &text)
{
    QString newtext(text);
    newtext.replace("&apos;", "'");
    newtext.replace("&quot;", "\"");
    newtext.replace("&lt;", "<");
    newtext.replace("&gt;", ">");
    newtext.replace("&amp;", "&");
    return newtext;
}

Project: Sigil
File: sigil_exception.h

    CannotWriteFile (const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: Utility.cpp

bool Utility::has_non_ascii_chars(const QString &str)
{
    QRegularExpression not_ascii("[^\\x00-\\x7F]");
    QRegularExpressionMatch mo = not_ascii.match(str);
    return mo.hasMatch();
}

Project: Sigil
File: sigil_exception.h

    FileDoesNotExist(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: sigil_exception.h

    FontObfuscationError(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: Utility.cpp

void Utility::DisplayExceptionErrorDialog(const QString &error_info)
{
    QMessageBox message_box(QApplication::activeWindow());
    message_box.setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint);
    message_box.setModal(true);
    message_box.setIcon(QMessageBox::Critical);
    message_box.setWindowTitle("Sigil");
    // Spaces are added to the end because otherwise the dialog is too small.
    message_box.setText(QObject::tr("Sigil has encountered a problem.") % "                                                                                                       ");
    message_box.setInformativeText(QObject::tr("Sigil may need to close."));
    message_box.setStandardButtons(QMessageBox::Close);
    QStringList detailed_text;
    detailed_text << "Error info: "    + error_info
                  << "Sigil version: " + QString(SIGIL_FULL_VERSION)
                  << "Runtime Qt: "    + QString(qVersion())
                  << "Compiled Qt: "   + QString(QT_VERSION_STR);
#if defined Q_OS_WIN32
    detailed_text << "Platform: Windows SysInfo ID " + QString::number(QSysInfo::WindowsVersion);
#elif defined Q_OS_MAC
    detailed_text << "Platform: Mac SysInfo ID " + QString::number(QSysInfo::MacintoshVersion);
#else
    detailed_text << "Platform: Linux";
#endif
    message_box.setDetailedText(detailed_text.join("\n"));
    message_box.exec();
}

Project: Sigil
File: Utility.cpp

QString Utility::EncodeXML(const QString &text)
{
    QString newtext(text);
    return newtext.toHtmlEscaped();
}

Project: Sigil
File: Utility.cpp

void Utility::WriteUnicodeTextFile(const QString &text, const QString &fullfilepath)
{
    QFile file(fullfilepath);

    if (!file.open(QIODevice::WriteOnly |
                   QIODevice::Truncate  |
                   QIODevice::Text
                  )
       ) {
        std::string msg = file.fileName().toStdString() + ": " + file.errorString().toStdString();
        throw(CannotOpenFile(msg));
    }

    QTextStream out(&file);
    // We ALWAYS output in UTF-8
    out.setCodec("UTF-8");
    out << text;
}

Project: Sigil
File: Utility.cpp

void Utility::CopyFiles(const QString &fullfolderpath_source, const QString &fullfolderpath_destination)
{
    QDir folder_source(fullfolderpath_source);
    QDir folder_destination(fullfolderpath_destination);
    // Erase all the files in this folder
    foreach(QFileInfo file, folder_source.entryInfoList()) {
        if ((file.fileName() != ".") && (file.fileName() != "..")) {
            // If it's a file, copy it
            if (file.isFile()) {
                QString destination = fullfolderpath_destination + "/" + file.fileName();
                bool success = QFile::copy(file.absoluteFilePath(), destination);

                if (!success) {
                    std::string msg = file.absoluteFilePath().toStdString() + ": " + destination.toStdString();
                    throw(CannotCopyFile(msg));
                }
            }
            // Else it's a directory, copy everything in it
            // to a new folder of the same name in the destination folder
            else {
                folder_destination.mkpath(file.fileName());
                CopyFiles(file.absoluteFilePath(), fullfolderpath_destination + "/" + file.fileName());
            }
        }
    }
}

Project: Sigil
File: Utility.cpp

bool Utility::ForceCopyFile(const QString &fullinpath, const QString &fulloutpath)
{
    if (!QFileInfo(fullinpath).exists()) {
        return false;
    }
    if (QFileInfo::exists(fulloutpath)) {
        Utility::SDeleteFile(fulloutpath);
    }
    return QFile::copy(fullinpath, fulloutpath);
}

Project: Sigil
File: Utility.cpp

QString Utility::Substring(int start_index, int end_index, const QString &string)
{
    return string.mid(start_index, end_index - start_index);
}

Project: Sigil
File: Utility.cpp

        SigilMessageBox(QWidget* parent) : QMessageBox(parent) 
        {
            setSizeGripEnabled(true);
        }

Project: Sigil
File: sigil_exception.h

    UNZIPLoadParseError(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: Utility.cpp

QString Utility::getSpellingSafeText(const QString &raw_text)
{
    // There is currently a problem with Hunspell if we attempt to pass
    // words with smart apostrophes from the CodeView encoding.
    // Hunspell dictionaries typically store their main wordlist using
    // the dumb apostrophe variants only to save space and speed checking
    QString text(raw_text);
    return text.replace(QChar(0x2019),QChar(0x27));
}

Project: Sigil
File: sigil_exception.h

    CannotCopyFile(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: sigil_exception.h

    FileEncryptedWithDrm(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: Utility.cpp

QString Utility::ChangeCase(const QString &text, const Utility::Casing &casing)
{
    if (text.isEmpty()) {
        return text;
    }

    switch (casing) {
        case Utility::Casing_Lowercase: {
            return text.toLower();
        }

        case Utility::Casing_Uppercase: {
            return text.toUpper();
        }

        case Utility::Casing_Titlecase: {
            // This is a super crude algorithm, could be replaced by something more clever.
            QString new_text = text.toLower();
            // Skip past any leading spaces
            int i = 0;

            while (i < text.length() && new_text.at(i).isSpace()) {
                i++;
            }

            while (i < text.length()) {
                if (i == 0 || new_text.at(i - 1).isSpace()) {
                    new_text.replace(i, 1, new_text.at(i).toUpper());
                }

                i++;
            }

            return new_text;
        }

        case Utility::Casing_Capitalize: {
            // This is a super crude algorithm, could be replaced by something more clever.
            QString new_text = text.toLower();
            // Skip past any leading spaces
            int i = 0;

            while (i < text.length() && new_text.at(i).isSpace()) {
                i++;
            }

            if (i < text.length()) {
                new_text.replace(i, 1, new_text.at(i).toUpper());
            }

            return new_text;
        }

        default:
            return text;
    }
}

Project: Sigil
File: Utility.cpp

QString Utility::DefinePrefsDir()
{
    // If the SIGIL_PREFS_DIR environment variable override exists; use it.
    // It's up to the user to provide a directory they have permission to write to.
    if (!SIGIL_PREFS_DIR.isEmpty()) {
        return SIGIL_PREFS_DIR;
    } else {
        return QStandardPaths::writableLocation(QStandardPaths::DataLocation);
    }
}

Project: Sigil
File: Utility.cpp

std::wstring Utility::QStringToStdWString(const QString &str)
{
    return std::wstring((const wchar_t *)str.utf16());
}

Project: Sigil
File: Utility.cpp

void Utility::DisplayStdErrorDialog(const QString &error_message, const QString &detailed_text)
{
    QMessageBox message_box(QApplication::activeWindow());
    message_box.setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint);
    message_box.setModal(true);
    message_box.setIcon(QMessageBox::Critical);
    message_box.setWindowTitle("Sigil");
    message_box.setText(error_message);

    if (!detailed_text.isEmpty()) {
        message_box.setDetailedText(detailed_text);
    }

    message_box.setStandardButtons(QMessageBox::Close);
    message_box.exec();
}

Project: Sigil
File: Utility.cpp

bool Utility::use_filename_warning(const QString &filename)
{
    if (has_non_ascii_chars(filename)) {
        return QMessageBox::Apply == QMessageBox::warning(QApplication::activeWindow(),
                tr("Sigil"),
                tr("The requested file name contains non-ASCII characters. "
                   "You should only use ASCII characters in filenames. "
                   "Using non-ASCII characters can prevent the EPUB from working "
                   "with some readers.\n\n"
                   "Continue using the requested filename?"),
                QMessageBox::Cancel|QMessageBox::Apply);
    }
    return true;
}

Project: Sigil
File: sigil_exception.h

    ErrorBuildingDOM(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: Utility.cpp

QStringList Utility::LinuxHunspellDictionaryDirs()
{
    QStringList paths;
    // prefer the directory specified by the env var SIGIL_DICTIONARIES above all else.
    if (!hunspell_dicts_override.isEmpty()) {
        // Handle multiple colon-delimited paths
        foreach (QString s, hunspell_dicts_override.split(":")) {
            paths << s.trimmed();
        }
    }
    // else use the env var runtime overridden 'share/sigil/hunspell_dictionaries/' location.
    else if (!sigil_extra_root.isEmpty()) {
        paths.append(sigil_extra_root + "/hunspell_dictionaries/");
    }
    // Bundled dicts were not installed use standard system dictionary location.
    else if (!dicts_are_bundled) {
        paths.append("/usr/share/hunspell");
        // Add additional hunspell dictionary directories. Provided at compile
        // time via the cmake option EXTRA_DICT_DIRS (colon separated list).
        if (!extra_dict_dirs.isEmpty()) {
            foreach (QString s, extra_dict_dirs.split(":")) {
                paths << s.trimmed();
            }
        }
    }
    else {
        // else use the standard build time 'share/sigil/hunspell_dictionaries/'location.
        paths.append(sigil_share_root + "/hunspell_dictionaries/");
    }
    return paths;
}

Project: Sigil
File: sigil_exception.h

    CannotReadFile(const std::string &msg) : std::runtime_error(msg) { };

Project: Sigil
File: Utility.cpp

QWidget *Utility::GetMainWindow()
{
    QWidget *parent_window = QApplication::activeWindow();

    while (parent_window && !(dynamic_cast<QMainWindow *>(parent_window))) {
        parent_window = parent_window->parentWidget();
    }

    return parent_window;
}

Project: Sigil
File: Utility.cpp

QString Utility::stdWStringToQString(const std::wstring &str)
{
    return QString::fromUtf16((const ushort *)str.c_str());
}

Project: Sigil
File: Utility.cpp

QString Utility::URLEncodePath(const QString &path)
{
    QString newpath = path;
    QUrl href = QUrl(newpath);
    QString scheme = href.scheme();
    if (!scheme.isEmpty()) {
        scheme = scheme + "://";
        newpath.remove(0, scheme.length());
    }
    QByteArray encoded_url = QUrl::toPercentEncoding(newpath, QByteArray("/#"));
    return scheme + QString::fromUtf8(encoded_url.constData(), encoded_url.count());
}

Project: Sigil
File: Utility.cpp

bool Utility::UnZip(const QString &zippath, const QString &destpath)
{
    int res = 0;
    QDir dir(destpath);
    if (!cp437) {
        cp437 = new QCodePage437Codec();
    }
#ifdef Q_OS_WIN32
    zlib_filefunc64_def ffunc;
    fill_win32_filefunc64W(&ffunc);
    unzFile zfile = unzOpen2_64(Utility::QStringToStdWString(QDir::toNativeSeparators(zippath)).c_str(), &ffunc);
#else
    unzFile zfile = unzOpen64(QDir::toNativeSeparators(zippath).toUtf8().constData());
#endif

    if ((zfile == NULL) || (!IsFileReadable(zippath)) || (!dir.exists())) {
        return false;
    }

    res = unzGoToFirstFile(zfile);

    if (res == UNZ_OK) {
        do {
            // Get the name of the file in the archive.
            char file_name[MAX_PATH] = {0};
            unz_file_info64 file_info;
            unzGetCurrentFileInfo64(zfile, &file_info, file_name, MAX_PATH, NULL, 0, NULL, 0);
            QString qfile_name;
            QString cp437_file_name;
            qfile_name = QString::fromUtf8(file_name);
            if (!(file_info.flag & (1<<11))) {
                // General purpose bit 11 says the filename is utf-8 encoded. If not set then
                // IBM 437 encoding might be used.
                cp437_file_name = cp437->toUnicode(file_name);
            }

            // If there is no file name then we can't do anything with it.
            if (!qfile_name.isEmpty()) {

	        // for security reasons against maliciously crafted zip archives
	        // we need the file path to always be inside the target folder 
	        // and not outside, so we will remove all illegal backslashes
	        // and all relative upward paths segments "/../" from the zip's local 
	        // file name/path before prepending the target folder to create 
	        // the final path

	        QString original_path = qfile_name;
	        bool evil_or_corrupt_epub = false;

	        if (qfile_name.contains("\\")) evil_or_corrupt_epub = true; 
	        qfile_name = "/" + qfile_name.replace("\\","");

	        if (qfile_name.contains("/../")) evil_or_corrupt_epub = true;
	        qfile_name = qfile_name.replace("/../","/");

	        while(qfile_name.startsWith("/")) { 
		  qfile_name = qfile_name.remove(0,1);
	        }
                
	        if (cp437_file_name.contains("\\")) evil_or_corrupt_epub = true; 
	        cp437_file_name = "/" + cp437_file_name.replace("\\","");

	        if (cp437_file_name.contains("/../")) evil_or_corrupt_epub = true;
	        cp437_file_name = cp437_file_name.replace("/../","/");

	        while(cp437_file_name.startsWith("/")) { 
		  cp437_file_name = cp437_file_name.remove(0,1);
	        }

	        if (evil_or_corrupt_epub) {
		    unzCloseCurrentFile(zfile);
		    unzClose(zfile);
		    // throw (UNZIPLoadParseError(QString(QObject::tr("Possible evil or corrupt zip file name: %1")).arg(original_path).toStdString()));
                    return false;
	        }

                // We use the dir object to create the path in the temporary directory.
                // Unfortunately, we need a dir ojbect to do this as it's not a static function.
                // Full file path in the temporary directory.
                QString file_path = destpath + "/" + qfile_name;
                QFileInfo qfile_info(file_path);

                // Is this entry a directory?
                if (file_info.uncompressed_size == 0 && qfile_name.endsWith('/')) {
                    dir.mkpath(qfile_name);
                    continue;
                } else {
                    dir.mkpath(qfile_info.path());
                }

                // Open the file entry in the archive for reading.
                if (unzOpenCurrentFile(zfile) != UNZ_OK) {
                    unzClose(zfile);
                    return false;
                }

                // Open the file on disk to write the entry in the archive to.
                QFile entry(file_path);

                if (!entry.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
                    unzCloseCurrentFile(zfile);
                    unzClose(zfile);
                    return false;
                }

                // Buffered reading and writing.
                char buff[BUFF_SIZE] = {0};
                int read = 0;

                while ((read = unzReadCurrentFile(zfile, buff, BUFF_SIZE)) > 0) {
                    entry.write(buff, read);
                }

                entry.close();

                // Read errors are marked by a negative read amount.
                if (read < 0) {
                    unzCloseCurrentFile(zfile);
                    unzClose(zfile);
                    return false;
                }

                // The file was read but the CRC did not match.
                // We don't check the read file size vs the uncompressed file size
                // because if they're different there should be a CRC error.
                if (unzCloseCurrentFile(zfile) == UNZ_CRCERROR) {
                    unzClose(zfile);
                    return false;
                }

                if (!cp437_file_name.isEmpty() && cp437_file_name != qfile_name) {
                    QString cp437_file_path = destpath + "/" + cp437_file_name;
                    QFile::copy(file_path, cp437_file_path);
                }
            }
        } while ((res = unzGoToNextFile(zfile)) == UNZ_OK);
    }

    if (res != UNZ_END_OF_LIST_OF_FILE) {
        unzClose(zfile);
        return false;
    }

    unzClose(zfile);
    return true;
}

Project: Sigil
File: Utility.cpp

bool Utility::IsFileReadable(const QString &fullfilepath)
{
    // Qt has <QFileInfo>.exists() and <QFileInfo>.isReadable()
    // functions, but then we would have to create our own error
    // message for each of those situations (and more). Trying to
    // actually open the file enables us to retrieve the exact
    // reason preventing us from reading the file in an error string.
    QFile file(fullfilepath);

    // Check if we can open the file
    if (!file.open(QFile::ReadOnly)) {
        Utility::DisplayStdErrorDialog(
            QObject::tr("Cannot read file %1:\n%2.")
            .arg(fullfilepath)
            .arg(file.errorString())
        );
        return false;
    }

    file.close();
    return true;
}

Project: Sigil
File: Utility.cpp

QString Utility::ReplaceFirst(const QString &before, const QString &after, const QString &string)
{
    int start_index = string.indexOf(before);
    int end_index   = start_index + before.length();
    return Substring(0, start_index, string) + after + Substring(end_index, string.length(), string);
}

Project: Sigil
File: Utility.cpp

bool Utility::SDeleteFile(const QString &fullfilepath)
{
    // Make sure the path exists, otherwise very
    // bad things could happen
    if (!QFileInfo(fullfilepath).exists()) {
        return false;
    }

    QFile file(fullfilepath);
    bool deleted = file.remove();
    // Some multiple file deletion operations fail on Windows, so we try once more.
    if (!deleted) {
        qApp->processEvents();
        SleepFunctions::msleep(100);
        deleted = file.remove();
    }
    return deleted;
}

Project: Sigil
File: Utility.cpp

QString Utility::ReadUnicodeTextFile(const QString &fullfilepath)
{
    // TODO: throw an exception instead of
    // returning an empty string
    QFile file(fullfilepath);

    // Check if we can open the file
    if (!file.open(QFile::ReadOnly)) {
        std::string msg = fullfilepath.toStdString() + ": " + file.errorString().toStdString();
        throw(CannotOpenFile(msg));
    }

    QTextStream in(&file);
    // Input should be UTF-8
    in.setCodec("UTF-8");
    // This will automatically switch reading from
    // UTF-8 to UTF-16 if a BOM is detected
    in.setAutoDetectUnicode(true);
    return ConvertLineEndings(in.readAll());
}

Project: Sigil
File: Utility.cpp

QStringList Utility::GetAbsolutePathsToFolderDescendantFiles(const QString &fullfolderpath)
{
    QDir folder(fullfolderpath);
    QStringList files;
    foreach(QFileInfo file, folder.entryInfoList()) {
        if ((file.fileName() != ".") && (file.fileName() != "..")) {
            // If it's a file, add it to the list
            if (file.isFile()) {
                files.append(Utility::URLEncodePath(file.absoluteFilePath()));
            }
            // Else it's a directory, so
            // we add all files from that dir
            else {
                files.append(GetAbsolutePathsToFolderDescendantFiles(file.absoluteFilePath()));
            }
        }
    }
    return files;
}

Project: Sigil
File: Utility.cpp

QString Utility::CreateUUID()
{
    return QUuid::createUuid().toString().remove("{").remove("}");
}