/****************************************************************************
**
** Copyright (C) 2013 BlackBerry Limited. All rights reserved.
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWebEngine module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "web_engine_library_info.h"

#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "components/spellcheck/spellcheck_buildflags.h"
#include "content/public/common/content_paths.h"
#include "sandbox/policy/switches.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_paths.h"
#include "ui/base/ui_base_switches.h"

#include "type_conversion.h"

#include <QByteArray>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QLibraryInfo>
#include <QLocale>
#include <QStandardPaths>

#if defined(OS_WIN)
#include <windows.h>
#endif // OS_WIN

#ifndef QTWEBENGINEPROCESS_NAME
#error "No name defined for QtWebEngine's process"
#endif

using namespace QtWebEngineCore;

namespace {

QString fallbackDir() {
    static QString directory = QDir::homePath() % QLatin1String("/.") % QCoreApplication::applicationName();
    return directory;
}

#if defined(OS_MAC) && defined(QT_MAC_FRAMEWORK_BUILD)
static inline CFBundleRef frameworkBundle()
{
    return CFBundleGetBundleWithIdentifier(CFSTR("org.qt-project.Qt.QtWebEngineCore"));
}

static QString getPath(CFBundleRef frameworkBundle)
{
    QString path;
    // The following is a fix for QtWebEngineProcess crashes on OS X 10.7 and before.
    // We use it for the other OS X versions as well to make sure it works and because
    // the directory structure should be the same.
    if (qApp->applicationName() == QLatin1String(QTWEBENGINEPROCESS_NAME)) {
        path = QDir::cleanPath(qApp->applicationDirPath() % QLatin1String("/../../../.."));
    } else if (frameworkBundle) {
        CFURLRef bundleUrl = CFBundleCopyBundleURL(frameworkBundle);
        CFStringRef bundlePath = CFURLCopyFileSystemPath(bundleUrl, kCFURLPOSIXPathStyle);
        path = QString::fromCFString(bundlePath);
        CFRelease(bundlePath);
        CFRelease(bundleUrl);
    }
    return path;
}

static QString getResourcesPath(CFBundleRef frameworkBundle)
{
    QString path;
    // The following is a fix for QtWebEngineProcess crashes on OS X 10.7 and before.
    // We use it for the other OS X versions as well to make sure it works and because
    // the directory structure should be the same.
    if (qApp->applicationName() == QLatin1String(QTWEBENGINEPROCESS_NAME)) {
        path = getPath(frameworkBundle) % QLatin1String("/Resources");
    } else if (frameworkBundle) {
        CFURLRef resourcesRelativeUrl = CFBundleCopyResourcesDirectoryURL(frameworkBundle);
        CFStringRef resourcesRelativePath = CFURLCopyFileSystemPath(resourcesRelativeUrl, kCFURLPOSIXPathStyle);
        path = getPath(frameworkBundle) % QLatin1Char('/') % QString::fromCFString(resourcesRelativePath);
        CFRelease(resourcesRelativePath);
        CFRelease(resourcesRelativeUrl);
    }
    return path;
}
#endif

#if defined(OS_MAC)
static QString getMainApplicationResourcesPath()
{
    QString resourcesPath;
    CFBundleRef mainBundle = CFBundleGetMainBundle();
    if (!mainBundle)
        return resourcesPath;

    // Will point to Resources inside an app bundle, or in case if the application is not packaged
    // as a bundle, will point to the application directory, where the resources are assumed to be
    // found.
    CFURLRef resourcesRelativeUrl = CFBundleCopyResourcesDirectoryURL(mainBundle);
    if (!resourcesRelativeUrl)
        return resourcesPath;

    CFURLRef resourcesAbsoluteUrl = CFURLCopyAbsoluteURL(resourcesRelativeUrl);
    CFStringRef resourcesAbolutePath = CFURLCopyFileSystemPath(resourcesAbsoluteUrl,
                                                                kCFURLPOSIXPathStyle);
    resourcesPath = QString::fromCFString(resourcesAbolutePath);
    CFRelease(resourcesAbolutePath);
    CFRelease(resourcesAbsoluteUrl);
    CFRelease(resourcesRelativeUrl);

    return resourcesPath;
}

#endif

QString subProcessPath()
{
    static QString processPath;
    if (processPath.isEmpty()) {
#if defined(OS_WIN)
        const QString processBinary = QLatin1String(QTWEBENGINEPROCESS_NAME) % QLatin1String(".exe");
#else
        const QString processBinary = QLatin1String(QTWEBENGINEPROCESS_NAME);
#endif

        QStringList candidatePaths;
        const QString fromEnv = qEnvironmentVariable("QTWEBENGINEPROCESS_PATH");
        if (!fromEnv.isEmpty()) {
            // Only search in QTWEBENGINEPROCESS_PATH if set
            candidatePaths << fromEnv;
        } else {
#if defined(OS_MAC) && defined(QT_MAC_FRAMEWORK_BUILD)
            candidatePaths << getPath(frameworkBundle())
                              % QStringLiteral("/Helpers/" QTWEBENGINEPROCESS_NAME ".app/Contents/MacOS/" QTWEBENGINEPROCESS_NAME);
#else
            candidatePaths << QLibraryInfo::location(QLibraryInfo::LibraryExecutablesPath)
                              % QLatin1Char('/') % processBinary;
#endif
            candidatePaths << QCoreApplication::applicationDirPath()
                              % QLatin1Char('/') % processBinary;
        }

        for (const QString &candidate : qAsConst(candidatePaths)) {
            if (QFileInfo::exists(candidate)) {
                processPath = candidate;
                break;
            }
        }
        if (processPath.isEmpty())
            qFatal("Could not find %s", processBinary.toUtf8().constData());

#if defined(OS_WIN)
        base::CommandLine *parsedCommandLine = base::CommandLine::ForCurrentProcess();
        if (!parsedCommandLine->HasSwitch(sandbox::policy::switches::kNoSandbox)) {
            if (WebEngineLibraryInfo::isUNCPath(processPath) || WebEngineLibraryInfo::isRemoteDrivePath(processPath))
                qCritical("Can not launch QtWebEngineProcess from network path if sandbox is enabled: %s.", processPath.toUtf8().constData());
        }
#endif

    }


    return processPath;
}

QString localesPath()
{
    static bool initialized = false;
    static QString potentialLocalesPath =
#if defined(OS_MAC) && defined(QT_MAC_FRAMEWORK_BUILD)
            getResourcesPath(frameworkBundle()) % QLatin1String("/qtwebengine_locales");
#else
            QLibraryInfo::location(QLibraryInfo::TranslationsPath) % QDir::separator() % QLatin1String("qtwebengine_locales");
#endif

    if (!initialized) {
        initialized = true;
        if (!QFileInfo::exists(potentialLocalesPath)) {
            qWarning("Installed Qt WebEngine locales directory not found at location %s. Trying application directory...", qPrintable(potentialLocalesPath));
            potentialLocalesPath = QCoreApplication::applicationDirPath() % QDir::separator() % QLatin1String("qtwebengine_locales");
        }
        if (!QFileInfo::exists(potentialLocalesPath)) {
            qWarning("Qt WebEngine locales directory not found at location %s. Trying fallback directory... Translations MAY NOT not be correct.", qPrintable(potentialLocalesPath));
            potentialLocalesPath = fallbackDir();
        }
    }

    return potentialLocalesPath;
}

#if QT_CONFIG(webengine_spellchecker)
QString dictionariesPath()
{
    static QString potentialDictionariesPath;
    static bool initialized = false;
    QStringList candidatePaths;
    if (!initialized) {
        initialized = true;

        const QString fromEnv = qEnvironmentVariable("QTWEBENGINE_DICTIONARIES_PATH");
        if (!fromEnv.isEmpty()) {
            // Only search in QTWEBENGINE_DICTIONARIES_PATH if set
            candidatePaths << fromEnv;
        } else {
            // First try to find dictionaries near the application.
#ifdef OS_MAC
            QString resourcesDictionariesPath = getMainApplicationResourcesPath()
                    % QDir::separator() % QLatin1String("qtwebengine_dictionaries");
            candidatePaths << resourcesDictionariesPath;
#endif
            QString applicationDictionariesPath = QCoreApplication::applicationDirPath()
                    % QDir::separator() % QLatin1String("qtwebengine_dictionaries");
            candidatePaths << applicationDictionariesPath;

            // Then try to find dictionaries near the installed library.
#if defined(OS_MAC) && defined(QT_MAC_FRAMEWORK_BUILD)
            QString frameworkDictionariesPath = getResourcesPath(frameworkBundle())
                    % QLatin1String("/qtwebengine_dictionaries");
            candidatePaths << frameworkDictionariesPath;
#endif

            QString libraryDictionariesPath = QLibraryInfo::location(QLibraryInfo::DataPath)
                    % QDir::separator() % QLatin1String("qtwebengine_dictionaries");
            candidatePaths << libraryDictionariesPath;
        }

        for (const QString &candidate : qAsConst(candidatePaths)) {
            if (QFileInfo::exists(candidate)) {
                potentialDictionariesPath = candidate;
                break;
            }
        }
    }

    return potentialDictionariesPath;
}
#endif // QT_CONFIG(webengine_spellchecker)

QString resourcesDataPath()
{
    static bool initialized = false;
    static QString potentialResourcesPath =
#if defined(OS_MAC) && defined(QT_MAC_FRAMEWORK_BUILD)
            getResourcesPath(frameworkBundle());
#elif defined(OS_MAC)
            QLibraryInfo::location(QLibraryInfo::DataPath) % QLatin1String("/Resources");
#else
            QLibraryInfo::location(QLibraryInfo::DataPath) % QLatin1String("/resources");
#endif
    if (!initialized) {
        initialized = true;
        if (!QFileInfo::exists(potentialResourcesPath % QLatin1String("/qtwebengine_resources.pak"))) {
            qWarning("Qt WebEngine resources not found at %s. Trying parent directory...", qPrintable(potentialResourcesPath));
            potentialResourcesPath = QLibraryInfo::location(QLibraryInfo::DataPath);
        }
        if (!QFileInfo::exists(potentialResourcesPath % QLatin1String("/qtwebengine_resources.pak"))) {
            qWarning("Qt WebEngine resources not found at %s. Trying application directory...", qPrintable(potentialResourcesPath));
            potentialResourcesPath = QCoreApplication::applicationDirPath();
        }
        if (!QFileInfo::exists(potentialResourcesPath % QLatin1String("/qtwebengine_resources.pak"))) {
            qWarning("Qt WebEngine resources not found at %s. Trying fallback directory... The application MAY NOT work.", qPrintable(potentialResourcesPath));
            potentialResourcesPath = fallbackDir();
        }
    }

    return potentialResourcesPath;
}
} // namespace

base::FilePath WebEngineLibraryInfo::getPath(int key)
{
    QString directory;
    switch (key) {
    case QT_RESOURCES_PAK:
        return toFilePath(resourcesDataPath() % QLatin1String("/qtwebengine_resources.pak"));
    case QT_RESOURCES_100P_PAK:
        return toFilePath(resourcesDataPath() % QLatin1String("/qtwebengine_resources_100p.pak"));
    case QT_RESOURCES_200P_PAK:
        return toFilePath(resourcesDataPath() % QLatin1String("/qtwebengine_resources_200p.pak"));
    case QT_RESOURCES_DEVTOOLS_PAK:
        return toFilePath(resourcesDataPath() % QLatin1String("/qtwebengine_devtools_resources.pak"));
    case base::FILE_EXE:
    case content::CHILD_PROCESS_EXE:
        return toFilePath(subProcessPath());
#if defined(OS_POSIX)
    case base::DIR_CACHE:
        directory = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
        break;
    case base::DIR_HOME:
        directory = QDir::homePath();
        break;
#endif
    case base::DIR_USER_DESKTOP:
        directory = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
        break;
    case base::DIR_QT_LIBRARY_DATA:
        return toFilePath(resourcesDataPath());
    case ui::DIR_LOCALES:
        return toFilePath(localesPath());
#if QT_CONFIG(webengine_spellchecker)
    case base::DIR_APP_DICTIONARIES:
        return toFilePath(dictionariesPath());
#endif
    default:
        // Note: the path system expects this function to override the default
        // behavior. So no need to log an error if we don't support a given
        // path. The system will just use the default.
        return base::FilePath();
    }

    return toFilePath(directory.isEmpty() ? fallbackDir() : directory);
}

base::string16 WebEngineLibraryInfo::getApplicationName()
{
    return toString16(qApp->applicationName());
}

std::string WebEngineLibraryInfo::getResolvedLocale()
{
    base::CommandLine *parsedCommandLine = base::CommandLine::ForCurrentProcess();
    if (parsedCommandLine->HasSwitch(switches::kLang)) {
        return parsedCommandLine->GetSwitchValueASCII(switches::kLang);
    } else {
        const QString &locale = QLocale().bcp47Name();
        std::string resolvedLocale;
        if (l10n_util::CheckAndResolveLocale(locale.toStdString(), &resolvedLocale))
            return resolvedLocale;
    }
    return "en-US";
}

std::string WebEngineLibraryInfo::getApplicationLocale()
{
    base::CommandLine *parsedCommandLine = base::CommandLine::ForCurrentProcess();
    return parsedCommandLine->HasSwitch(switches::kLang)
        ? parsedCommandLine->GetSwitchValueASCII(switches::kLang)
        : QLocale().bcp47Name().toStdString();
}

#if defined(OS_WIN)
bool WebEngineLibraryInfo::isRemoteDrivePath(const QString &path)
{
    WCHAR wDriveLetter[4] = { 0 };
    swprintf(wDriveLetter, L"%S", path.mid(0, 3).toStdString().c_str());
    return GetDriveType(wDriveLetter) == DRIVE_REMOTE;
}

bool WebEngineLibraryInfo::isUNCPath(const QString &path)
{
    return (base::FilePath::IsSeparator(path.at(0).toLatin1())
            && base::FilePath::IsSeparator(path.at(1).toLatin1())
            && path.at(2) != "." && path.at(2) != "?"
            && path.at(2).isLetter() && path.at(3) != ":");
}

#endif
