/****************************************************************************
**
** Copyright (C) 2018 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$
**
****************************************************************************/

// based on chrome/renderer/extensions/resource_request_policy.cc:
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "resource_request_policy_qt.h"

#include "base/strings/stringprintf.h"
#include "chrome/common/url_constants.h"
#include "extensions/common/constants.h"
#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
#include "extensions/common/manifest_handlers/webview_info.h"
#include "extensions/renderer/dispatcher.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom-shared.h"
#include "third_party/blink/public/web/web_console_message.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"

namespace extensions {
ResourceRequestPolicyQt::ResourceRequestPolicyQt(Dispatcher *dispatcher)
        : m_dispatcher(dispatcher)
{
}

void ResourceRequestPolicyQt::OnExtensionLoaded(const Extension &extension)
{
    if (WebAccessibleResourcesInfo::HasWebAccessibleResources(&extension)
        || WebviewInfo::HasWebviewAccessibleResources(extension, m_dispatcher->webview_partition_id())
//          // Hosted app icons are accessible.
//          // TODO(devlin): Should we incorporate this into
//          // WebAccessibleResourcesInfo?
//        || (extension.is_hosted_app() && !IconsInfo::GetIcons(&extension).empty())
       ) {
        m_web_accessible_ids.insert(extension.id());
    }
}

void ResourceRequestPolicyQt::OnExtensionUnloaded(const ExtensionId &extension_id)
{
    m_web_accessible_ids.erase(extension_id);
}

// Returns true if the chrome-extension:// |resource_url| can be requested
// from |frame_url|. In some cases this decision is made based upon how
// this request was generated. Web triggered transitions are more restrictive
// than those triggered through UI.
bool ResourceRequestPolicyQt::CanRequestResource(const GURL &resource_url,
                                                 blink::WebLocalFrame *frame,
                                                 ui::PageTransition transition_type)
{
    CHECK(resource_url.SchemeIs(kExtensionScheme));

    GURL frame_url = frame->GetDocument().Url();

    // The page_origin may be GURL("null") for unique origins like data URLs,
    // but this is ok for the checks below.  We only care if it matches the
    // current extension or has a devtools scheme.
    GURL page_origin = url::Origin(frame->Top()->GetSecurityOrigin()).GetURL();

    GURL extension_origin = resource_url.GetOrigin();

    // We always allow loads in the following cases, regardless of web accessible
    // resources:

    // Empty urls (needed for some edge cases when we have empty urls).
    if (frame_url.is_empty())
        return true;

    // Extensions requesting their own resources (frame_url check is for images,
    // page_url check is for iframes).
    // TODO(devlin): We should be checking the ancestor chain, not just the
    // top-level frame. Additionally, we should be checking the security origin
    // of the frame, to account for about:blank subframes being scripted by an
    // extension parent (though we'll still need the frame origin check for
    // sandboxed frames).
    if (frame_url.GetOrigin() == extension_origin || page_origin == extension_origin)
        return true;

    if (!ui::PageTransitionIsWebTriggerable(transition_type))
        return true;

    // Unreachable web page error page (to allow showing the icon of the
    // unreachable app on this page).
    if (frame_url == content::kUnreachableWebDataURL)
        return true;

    bool is_dev_tools = page_origin.SchemeIs(content::kChromeDevToolsScheme);
    // Note: we check |web_accessible_ids_| (rather than first looking up the
    // extension in the registry and checking that) to be more resistant against
    // timing attacks. This way, determining access for an extension that isn't
    // installed takes the same amount of time as determining access for an
    // extension with no web accessible resources. We aren't worried about any
    // extensions with web accessible resources, since those are inherently
    // identifiable.
    if (!is_dev_tools && !m_web_accessible_ids.count(extension_origin.host()))
        return false;

    const Extension* extension = RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(resource_url);
    if (is_dev_tools) {
        // Allow the load in the case of a non-existent extension. We'll just get a
        // 404 from the browser process.
        // TODO(devlin): Can this happen? Does devtools potentially make requests
        // to non-existent extensions?
        if (!extension)
            return true;
//        // Devtools (chrome-extension:// URLs are loaded into frames of devtools to
//        // support the devtools extension APIs).
//        if (!chrome_manifest_urls::GetDevToolsPage(extension).is_empty())
//            return true;
    }

    DCHECK(extension);

    // Disallow loading of packaged resources for hosted apps. We don't allow
    // hybrid hosted/packaged apps. The one exception is access to icons, since
    // some extensions want to be able to do things like create their own
    // launchers.
    base::StringPiece resource_root_relative_path =
            resource_url.path_piece().empty() ? base::StringPiece()
                                              : resource_url.path_piece().substr(1);
    if (extension->is_hosted_app() /*&& !IconsInfo::GetIcons(extension).ContainsPath(resource_root_relative_path)*/) {
        LOG(ERROR) << "Denying load of " << resource_url.spec() << " from "
                   << "hosted app.";
        return false;
    }

    // Disallow loading of extension resources which are not explicitly listed
    // as web or WebView accessible if the manifest version is 2 or greater.
    if (!WebAccessibleResourcesInfo::IsResourceWebAccessible(extension, resource_url.path()) &&
        !WebviewInfo::IsResourceWebviewAccessible(extension, m_dispatcher->webview_partition_id(), resource_url.path()))
    {
        std::string message = base::StringPrintf(
                    "Denying load of %s. Resources must be listed in the "
                    "web_accessible_resources manifest key in order to be loaded by "
                    "pages outside the extension.",
                    resource_url.spec().c_str());
        frame->AddMessageToConsole(blink::WebConsoleMessage(blink::mojom::ConsoleMessageLevel::kError, blink::WebString::FromUTF8(message)));
        return false;
    }

    return true;
}

} // namespace extensions
