// Copyright (c) 2013 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 "chrome/common/extensions/extension_unittest.h"

#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/format_macros.h"
#include "base/json/json_file_value_serializer.h"
#include "base/path_service.h"
#include "base/stringprintf.h"
#include "base/strings/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/api/plugins/plugins_handler.h"
#include "chrome/common/extensions/background_info.h"
#include "chrome/common/extensions/command.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_file_util.h"
#include "chrome/common/extensions/extension_manifest_constants.h"
#include "chrome/common/extensions/features/feature.h"
#include "chrome/common/extensions/incognito_handler.h"
#include "chrome/common/extensions/manifest.h"
#include "chrome/common/extensions/manifest_handler.h"
#include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
#include "chrome/common/extensions/permissions/api_permission.h"
#include "chrome/common/extensions/permissions/chrome_api_permissions.h"
#include "chrome/common/extensions/permissions/permission_set.h"
#include "chrome/common/extensions/permissions/scoped_testing_permissions_info.h"
#include "chrome/common/extensions/permissions/socket_permission.h"
#include "chrome/common/extensions/permissions/usb_device_permission.h"
#include "chrome/common/url_constants.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension_resource.h"
#include "extensions/common/id_util.h"
#include "googleurl/src/gurl.h"
#include "net/base/mime_sniffer.h"
#include "net/dns/mock_host_resolver.h"
#include "skia/ext/image_operations.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h"

using content::SocketPermissionRequest;

namespace keys = extension_manifest_keys;
namespace values = extension_manifest_values;
namespace errors = extension_manifest_errors;

namespace extensions {
namespace {

scoped_refptr<Extension> LoadManifestUnchecked(
    const std::string& dir,
    const std::string& test_file,
    Manifest::Location location,
    int extra_flags,
    std::string* error) {
  base::FilePath path;
  PathService::Get(chrome::DIR_TEST_DATA, &path);
  path = path.AppendASCII("extensions")
             .AppendASCII(dir)
             .AppendASCII(test_file);

  JSONFileValueSerializer serializer(path);
  scoped_ptr<Value> result(serializer.Deserialize(NULL, error));
  if (!result.get())
    return NULL;

  scoped_refptr<Extension> extension = Extension::Create(
      path.DirName(), location, *static_cast<DictionaryValue*>(result.get()),
      extra_flags, error);
  return extension;
}

static scoped_refptr<Extension> LoadManifest(const std::string& dir,
                                             const std::string& test_file,
                                             Manifest::Location location,
                                             int extra_flags) {
  std::string error;
  scoped_refptr<Extension> extension = LoadManifestUnchecked(dir, test_file,
    location, extra_flags, &error);

  EXPECT_TRUE(extension) << test_file << ":" << error;
  return extension;
}

static scoped_refptr<Extension> LoadManifest(const std::string& dir,
                                             const std::string& test_file,
                                             int extra_flags) {
  return LoadManifest(dir, test_file, Manifest::INVALID_LOCATION, extra_flags);
}

static scoped_refptr<Extension> LoadManifest(const std::string& dir,
                                             const std::string& test_file) {
  return LoadManifest(dir, test_file, Extension::NO_FLAGS);
}

static scoped_refptr<Extension> LoadManifestStrict(
    const std::string& dir,
    const std::string& test_file) {
  return LoadManifest(dir, test_file, Extension::NO_FLAGS);
}

}  // namespace

ExtensionTest::ExtensionTest() : permissions_info_(ChromeAPIPermissions()) {}

void ExtensionTest::SetUp() {
  testing::Test::SetUp();
  (new BackgroundManifestHandler)->Register();
  // We need the IncognitoHandler registered for all tests, since
  // Extension uses it in Extension::CheckPlatformAppFeatures() as part of the
  // creation process.
  (new IncognitoHandler)->Register();
}

void ExtensionTest::TearDown() {
  ManifestHandler::ClearRegistryForTesting();
  testing::Test::TearDown();
}

// We persist location values in the preferences, so this is a sanity test that
// someone doesn't accidentally change them.
TEST_F(ExtensionTest, LocationValuesTest) {
  ASSERT_EQ(0, Manifest::INVALID_LOCATION);
  ASSERT_EQ(1, Manifest::INTERNAL);
  ASSERT_EQ(2, Manifest::EXTERNAL_PREF);
  ASSERT_EQ(3, Manifest::EXTERNAL_REGISTRY);
  ASSERT_EQ(4, Manifest::UNPACKED);
  ASSERT_EQ(5, Manifest::COMPONENT);
  ASSERT_EQ(6, Manifest::EXTERNAL_PREF_DOWNLOAD);
  ASSERT_EQ(7, Manifest::EXTERNAL_POLICY_DOWNLOAD);
  ASSERT_EQ(8, Manifest::COMMAND_LINE);
}

TEST_F(ExtensionTest, LocationPriorityTest) {
  for (int i = 0; i < Manifest::NUM_LOCATIONS; i++) {
    Manifest::Location loc = static_cast<Manifest::Location>(i);

    // INVALID is not a valid location.
    if (loc == Manifest::INVALID_LOCATION)
      continue;

    // Comparing a location that has no rank will hit a CHECK. Do a
    // compare with every valid location, to be sure each one is covered.

    // Check that no install source can override a componenet extension.
    ASSERT_EQ(Manifest::COMPONENT,
              Manifest::GetHigherPriorityLocation(Manifest::COMPONENT, loc));
    ASSERT_EQ(Manifest::COMPONENT,
              Manifest::GetHigherPriorityLocation(loc, Manifest::COMPONENT));

    // Check that any source can override a user install. This might change
    // in the future, in which case this test should be updated.
    ASSERT_EQ(loc,
              Manifest::GetHigherPriorityLocation(Manifest::INTERNAL, loc));
    ASSERT_EQ(loc,
              Manifest::GetHigherPriorityLocation(loc, Manifest::INTERNAL));
  }

  // Check a few interesting cases that we know can happen:
  ASSERT_EQ(Manifest::EXTERNAL_POLICY_DOWNLOAD,
            Manifest::GetHigherPriorityLocation(
                Manifest::EXTERNAL_POLICY_DOWNLOAD,
                Manifest::EXTERNAL_PREF));

  ASSERT_EQ(Manifest::EXTERNAL_PREF,
            Manifest::GetHigherPriorityLocation(
                Manifest::INTERNAL,
                Manifest::EXTERNAL_PREF));
}

TEST_F(ExtensionTest, GetResourceURLAndPath) {
  scoped_refptr<Extension> extension = LoadManifestStrict("empty_manifest",
      "empty.json");
  EXPECT_TRUE(extension.get());

  EXPECT_EQ(extension->url().spec() + "bar/baz.js",
            Extension::GetResourceURL(extension->url(), "bar/baz.js").spec());
  EXPECT_EQ(extension->url().spec() + "baz.js",
            Extension::GetResourceURL(extension->url(),
                                      "bar/../baz.js").spec());
  EXPECT_EQ(extension->url().spec() + "baz.js",
            Extension::GetResourceURL(extension->url(), "../baz.js").spec());

  // Test that absolute-looking paths ("/"-prefixed) are pasted correctly.
  EXPECT_EQ(extension->url().spec() + "test.html",
            extension->GetResourceURL("/test.html").spec());
}

TEST_F(ExtensionTest, GetAbsolutePathNoError) {
  scoped_refptr<Extension> extension = LoadManifestStrict("absolute_path",
      "absolute.json");
  EXPECT_TRUE(extension.get());
  std::string err;
  std::vector<InstallWarning> warnings;
  EXPECT_TRUE(extension_file_util::ValidateExtension(extension.get(),
                                                     &err, &warnings));
  EXPECT_EQ(0U, warnings.size());

  EXPECT_EQ(extension->path().AppendASCII("test.html").value(),
            extension->GetResource("test.html").GetFilePath().value());
  EXPECT_EQ(extension->path().AppendASCII("test.js").value(),
            extension->GetResource("test.js").GetFilePath().value());
}


TEST_F(ExtensionTest, IdIsValid) {
  EXPECT_TRUE(Extension::IdIsValid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
  EXPECT_TRUE(Extension::IdIsValid("pppppppppppppppppppppppppppppppp"));
  EXPECT_TRUE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnop"));
  EXPECT_TRUE(Extension::IdIsValid("ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP"));
  EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmno"));
  EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnopa"));
  EXPECT_FALSE(Extension::IdIsValid("0123456789abcdef0123456789abcdef"));
  EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnoq"));
  EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmno0"));
}


// This test ensures that the mimetype sniffing code stays in sync with the
// actual crx files that we test other parts of the system with.
TEST_F(ExtensionTest, MimeTypeSniffing) {
  base::FilePath path;
  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
  path = path.AppendASCII("extensions").AppendASCII("good.crx");

  std::string data;
  ASSERT_TRUE(file_util::ReadFileToString(path, &data));

  std::string result;
  EXPECT_TRUE(net::SniffMimeType(data.c_str(),
                                 data.size(),
                                 GURL("http://www.example.com/foo.crx"),
                                 std::string(),
                                 &result));
  EXPECT_EQ(std::string(Extension::kMimeType), result);

  data.clear();
  result.clear();
  path = path.DirName().AppendASCII("bad_magic.crx");
  ASSERT_TRUE(file_util::ReadFileToString(path, &data));
  EXPECT_TRUE(net::SniffMimeType(data.c_str(),
                                 data.size(),
                                 GURL("http://www.example.com/foo.crx"),
                                 std::string(),
                                 &result));
  EXPECT_EQ("application/octet-stream", result);
}

TEST_F(ExtensionTest, EffectiveHostPermissions) {
  (new ContentScriptsHandler)->Register();
  scoped_refptr<Extension> extension;
  URLPatternSet hosts;

  extension = LoadManifest("effective_host_permissions", "empty.json");
  EXPECT_EQ(0u, extension->GetEffectiveHostPermissions().patterns().size());
  EXPECT_FALSE(hosts.MatchesURL(GURL("http://www.google.com")));
  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());

  extension = LoadManifest("effective_host_permissions", "one_host.json");
  hosts = extension->GetEffectiveHostPermissions();
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com")));
  EXPECT_FALSE(hosts.MatchesURL(GURL("https://www.google.com")));
  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());

  extension = LoadManifest("effective_host_permissions",
                           "one_host_wildcard.json");
  hosts = extension->GetEffectiveHostPermissions();
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://google.com")));
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://foo.google.com")));
  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());

  extension = LoadManifest("effective_host_permissions", "two_hosts.json");
  hosts = extension->GetEffectiveHostPermissions();
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com")));
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.reddit.com")));
  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());

  extension = LoadManifest("effective_host_permissions",
                           "https_not_considered.json");
  hosts = extension->GetEffectiveHostPermissions();
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://google.com")));
  EXPECT_TRUE(hosts.MatchesURL(GURL("https://google.com")));
  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());

  extension = LoadManifest("effective_host_permissions",
                           "two_content_scripts.json");
  hosts = extension->GetEffectiveHostPermissions();
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://google.com")));
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.reddit.com")));
  EXPECT_TRUE(extension->GetActivePermissions()->HasEffectiveAccessToURL(
      GURL("http://www.reddit.com")));
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://news.ycombinator.com")));
  EXPECT_TRUE(extension->GetActivePermissions()->HasEffectiveAccessToURL(
      GURL("http://news.ycombinator.com")));
  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());

  extension = LoadManifest("effective_host_permissions", "all_hosts.json");
  hosts = extension->GetEffectiveHostPermissions();
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://test/")));
  EXPECT_FALSE(hosts.MatchesURL(GURL("https://test/")));
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com")));
  EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());

  extension = LoadManifest("effective_host_permissions", "all_hosts2.json");
  hosts = extension->GetEffectiveHostPermissions();
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://test/")));
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com")));
  EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());

  extension = LoadManifest("effective_host_permissions", "all_hosts3.json");
  hosts = extension->GetEffectiveHostPermissions();
  EXPECT_FALSE(hosts.MatchesURL(GURL("http://test/")));
  EXPECT_TRUE(hosts.MatchesURL(GURL("https://test/")));
  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com")));
  EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());
}

static bool CheckSocketPermission(scoped_refptr<Extension> extension,
    SocketPermissionRequest::OperationType type,
    const char* host,
    int port) {
  SocketPermission::CheckParam param(type, host, port);
  return extension->CheckAPIPermissionWithParam(
      APIPermission::kSocket, &param);
}

TEST_F(ExtensionTest, SocketPermissions) {
  // Set feature current channel to appropriate value.
  Feature::ScopedCurrentChannel scoped_channel(
      chrome::VersionInfo::CHANNEL_DEV);
  scoped_refptr<Extension> extension;
  std::string error;

  extension = LoadManifest("socket_permissions", "empty.json");
  EXPECT_FALSE(CheckSocketPermission(extension,
      SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));

  extension = LoadManifestUnchecked("socket_permissions",
                                    "socket1.json",
                                    Manifest::INTERNAL, Extension::NO_FLAGS,
                                    &error);
  EXPECT_TRUE(extension == NULL);
  ASSERT_EQ(ErrorUtils::FormatErrorMessage(
        errors::kInvalidPermission, "socket"), error);

  extension = LoadManifest("socket_permissions", "socket2.json");
  EXPECT_TRUE(CheckSocketPermission(extension,
      SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
  EXPECT_FALSE(CheckSocketPermission(
        extension, SocketPermissionRequest::UDP_BIND, "", 80));
  EXPECT_TRUE(CheckSocketPermission(
        extension, SocketPermissionRequest::UDP_BIND, "", 8888));

  EXPECT_FALSE(CheckSocketPermission(
        extension, SocketPermissionRequest::UDP_SEND_TO, "example.com", 1900));
  EXPECT_TRUE(CheckSocketPermission(
        extension,
        SocketPermissionRequest::UDP_SEND_TO,
        "239.255.255.250", 1900));
}

// This tests the API permissions with an empty manifest (one that just
// specifies a name and a version and nothing else).
TEST_F(ExtensionTest, ApiPermissions) {
  const struct {
    const char* permission_name;
    bool expect_success;
  } kTests[] = {
    // Negative test.
    { "non_existing_permission", false },
    // Test default module/package permission.
    { "browserAction",  true },
    { "devtools",       true },
    { "extension",      true },
    { "i18n",           true },
    { "pageAction",     true },
    { "pageActions",    true },
    { "test",           true },
    // Some negative tests.
    { "bookmarks",      false },
    { "cookies",        false },
    { "history",        false },
    // Make sure we find the module name after stripping '.' and '/'.
    { "browserAction/abcd/onClick",  true },
    { "browserAction.abcd.onClick",  true },
    // Test Tabs functions.
    { "tabs.create",      true},
    { "tabs.duplicate",   true},
    { "tabs.onRemoved",   true},
    { "tabs.remove",      true},
    { "tabs.update",      true},
    { "tabs.getSelected", true},
    { "tabs.onUpdated",   true },
    // Test getPermissionWarnings functions. Only one requires permissions.
    { "management.getPermissionWarningsById", false },
    { "management.getPermissionWarningsByManifest", true },
  };

  scoped_refptr<Extension> extension;
  extension = LoadManifest("empty_manifest", "empty.json");

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
    EXPECT_EQ(kTests[i].expect_success,
              extension->HasAPIPermission(kTests[i].permission_name))
                  << "Permission being tested: " << kTests[i].permission_name;
  }
}

TEST_F(ExtensionTest, GetPermissionMessages_ManyApiPermissions) {
  scoped_refptr<Extension> extension;
  extension = LoadManifest("permissions", "many-apis.json");
  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
  ASSERT_EQ(6u, warnings.size());
  EXPECT_EQ("Access your data on api.flickr.com",
            UTF16ToUTF8(warnings[0]));
  EXPECT_EQ("Read and modify your bookmarks", UTF16ToUTF8(warnings[1]));
  EXPECT_EQ("Detect your physical location", UTF16ToUTF8(warnings[2]));
  EXPECT_EQ("Read and modify your browsing history", UTF16ToUTF8(warnings[3]));
  EXPECT_EQ("Access your tabs and browsing activity", UTF16ToUTF8(warnings[4]));
  EXPECT_EQ("Manage your apps, extensions, and themes",
            UTF16ToUTF8(warnings[5]));
}

TEST_F(ExtensionTest, GetPermissionMessages_LocationApiPermission) {
  scoped_refptr<Extension> extension;
  extension = LoadManifest("permissions",
                           "location-api.json",
                           Manifest::COMPONENT,
                           Extension::NO_FLAGS);
  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
  ASSERT_EQ(1u, warnings.size());
  EXPECT_EQ("Detect your physical location", UTF16ToUTF8(warnings[0]));
}

TEST_F(ExtensionTest, GetPermissionMessages_ManyHosts) {
  (new ContentScriptsHandler)->Register();
  scoped_refptr<Extension> extension;
  extension = LoadManifest("permissions", "many-hosts.json");
  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
  ASSERT_EQ(1u, warnings.size());
  EXPECT_EQ("Access your data on encrypted.google.com and www.google.com",
            UTF16ToUTF8(warnings[0]));
}

TEST_F(ExtensionTest, GetPermissionMessages_Plugins) {
  (new PluginsHandler)->Register();
  scoped_refptr<Extension> extension;
  extension = LoadManifest("permissions", "plugins.json");
  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
  // We don't parse the plugins key on Chrome OS, so it should not ask for any
  // permissions.
#if defined(OS_CHROMEOS)
  ASSERT_EQ(0u, warnings.size());
#else
  ASSERT_EQ(1u, warnings.size());
  EXPECT_EQ("Access all data on your computer and the websites you visit",
            UTF16ToUTF8(warnings[0]));
#endif
}

TEST_F(ExtensionTest, WantsFileAccess) {
  (new ContentScriptsHandler)->Register();
  scoped_refptr<Extension> extension;
  GURL file_url("file:///etc/passwd");

  // <all_urls> permission
  extension = LoadManifest("permissions", "permissions_all_urls.json");
  EXPECT_TRUE(extension->wants_file_access());
  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
      file_url, file_url, -1, NULL, NULL));
  extension = LoadManifest(
      "permissions", "permissions_all_urls.json", Extension::ALLOW_FILE_ACCESS);
  EXPECT_TRUE(extension->wants_file_access());
  EXPECT_TRUE(extension->CanExecuteScriptOnPage(
      file_url, file_url, -1, NULL, NULL));

  // file:///* permission
  extension = LoadManifest("permissions", "permissions_file_scheme.json");
  EXPECT_TRUE(extension->wants_file_access());
  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
      file_url, file_url, -1, NULL, NULL));
  extension = LoadManifest("permissions", "permissions_file_scheme.json",
      Extension::ALLOW_FILE_ACCESS);
  EXPECT_TRUE(extension->wants_file_access());
  EXPECT_TRUE(extension->CanExecuteScriptOnPage(
      file_url, file_url, -1, NULL, NULL));

  // http://* permission
  extension = LoadManifest("permissions", "permissions_http_scheme.json");
  EXPECT_FALSE(extension->wants_file_access());
  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
      file_url, file_url, -1, NULL, NULL));
  extension = LoadManifest("permissions", "permissions_http_scheme.json",
      Extension::ALLOW_FILE_ACCESS);
  EXPECT_FALSE(extension->wants_file_access());
  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
      file_url, file_url, -1, NULL, NULL));

  // <all_urls> content script match
  extension = LoadManifest("permissions", "content_script_all_urls.json");
  EXPECT_TRUE(extension->wants_file_access());
  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
      file_url,
      file_url,
      -1,
      &ContentScriptsInfo::GetContentScripts(extension)[0],
      NULL));
  extension = LoadManifest("permissions", "content_script_all_urls.json",
      Extension::ALLOW_FILE_ACCESS);
  EXPECT_TRUE(extension->wants_file_access());
  EXPECT_TRUE(extension->CanExecuteScriptOnPage(
      file_url,
      file_url,
      -1,
      &ContentScriptsInfo::GetContentScripts(extension)[0],
      NULL));

  // file:///* content script match
  extension = LoadManifest("permissions", "content_script_file_scheme.json");
  EXPECT_TRUE(extension->wants_file_access());
  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
      file_url,
      file_url,
      -1,
      &ContentScriptsInfo::GetContentScripts(extension)[0],
      NULL));
  extension = LoadManifest("permissions", "content_script_file_scheme.json",
      Extension::ALLOW_FILE_ACCESS);
  EXPECT_TRUE(extension->wants_file_access());
  EXPECT_TRUE(extension->CanExecuteScriptOnPage(
      file_url,
      file_url,
      -1,
      &ContentScriptsInfo::GetContentScripts(extension)[0],
      NULL));

  // http://* content script match
  extension = LoadManifest("permissions", "content_script_http_scheme.json");
  EXPECT_FALSE(extension->wants_file_access());
  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
      file_url,
      file_url,
      -1,
      &ContentScriptsInfo::GetContentScripts(extension)[0],
      NULL));
  extension = LoadManifest("permissions", "content_script_http_scheme.json",
      Extension::ALLOW_FILE_ACCESS);
  EXPECT_FALSE(extension->wants_file_access());
  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
      file_url,
      file_url,
      -1,
      &ContentScriptsInfo::GetContentScripts(extension)[0],
      NULL));
}

TEST_F(ExtensionTest, ExtraFlags) {
  scoped_refptr<Extension> extension;
  extension = LoadManifest("app", "manifest.json", Extension::FROM_WEBSTORE);
  EXPECT_TRUE(extension->from_webstore());

  extension = LoadManifest("app", "manifest.json", Extension::FROM_BOOKMARK);
  EXPECT_TRUE(extension->from_bookmark());

  extension = LoadManifest("app", "manifest.json", Extension::NO_FLAGS);
  EXPECT_FALSE(extension->from_bookmark());
  EXPECT_FALSE(extension->from_webstore());
}

// Base class for testing the CanExecuteScriptOnPage and CanCaptureVisiblePage
// methods of Extension for extensions with various permissions.
class ExtensionScriptAndCaptureVisibleTest : public testing::Test {
 protected:
  ExtensionScriptAndCaptureVisibleTest()
      : http_url("http://www.google.com"),
        http_url_with_path("http://www.google.com/index.html"),
        https_url("https://www.google.com"),
        file_url("file:///foo/bar"),
        favicon_url("chrome://favicon/http://www.google.com"),
        extension_url("chrome-extension://" +
            id_util::GenerateIdForPath(
                base::FilePath(FILE_PATH_LITERAL("foo")))),
        settings_url("chrome://settings"),
        about_url("about:flags") {
    urls_.insert(http_url);
    urls_.insert(http_url_with_path);
    urls_.insert(https_url);
    urls_.insert(file_url);
    urls_.insert(favicon_url);
    urls_.insert(extension_url);
    urls_.insert(settings_url);
    urls_.insert(about_url);
  }

  bool AllowedScript(const Extension* extension, const GURL& url,
                     const GURL& top_url) {
    return extension->CanExecuteScriptOnPage(url, top_url, -1, NULL, NULL);
  }

  bool BlockedScript(const Extension* extension, const GURL& url,
                     const GURL& top_url) {
    return !extension->CanExecuteScriptOnPage(url, top_url, -1, NULL, NULL);
  }

  bool Allowed(const Extension* extension, const GURL& url) {
    return Allowed(extension, url, -1);
  }

  bool Allowed(const Extension* extension, const GURL& url, int tab_id) {
    return (extension->CanExecuteScriptOnPage(url, url, tab_id, NULL, NULL) &&
            extension->CanCaptureVisiblePage(url, tab_id, NULL));
  }

  bool CaptureOnly(const Extension* extension, const GURL& url) {
    return CaptureOnly(extension, url, -1);
  }

  bool CaptureOnly(const Extension* extension, const GURL& url, int tab_id) {
    return !extension->CanExecuteScriptOnPage(url, url, tab_id, NULL, NULL) &&
            extension->CanCaptureVisiblePage(url, tab_id, NULL);
  }

  bool Blocked(const Extension* extension, const GURL& url) {
    return Blocked(extension, url, -1);
  }

  bool Blocked(const Extension* extension, const GURL& url, int tab_id) {
    return !(extension->CanExecuteScriptOnPage(url, url, tab_id, NULL, NULL) ||
             extension->CanCaptureVisiblePage(url, tab_id, NULL));
  }

  bool AllowedExclusivelyOnTab(
      const Extension* extension,
      const std::set<GURL>& allowed_urls,
      int tab_id) {
    bool result = true;
    for (std::set<GURL>::iterator it = urls_.begin(); it != urls_.end(); ++it) {
      const GURL& url = *it;
      if (allowed_urls.count(url))
        result &= Allowed(extension, url, tab_id);
      else
        result &= Blocked(extension, url, tab_id);
    }
    return result;
  }

  // URLs that are "safe" to provide scripting and capture visible tab access
  // to if the permissions allow it.
  const GURL http_url;
  const GURL http_url_with_path;
  const GURL https_url;
  const GURL file_url;

  // We should allow host permission but not scripting permission for favicon
  // urls.
  const GURL favicon_url;

  // URLs that regular extensions should never get access to.
  const GURL extension_url;
  const GURL settings_url;
  const GURL about_url;

 private:
  // The set of all URLs above.
  std::set<GURL> urls_;
};

TEST_F(ExtensionScriptAndCaptureVisibleTest, Permissions) {
  // Test <all_urls> for regular extensions.
  scoped_refptr<Extension> extension = LoadManifestStrict("script_and_capture",
      "extension_regular_all.json");

  EXPECT_TRUE(Allowed(extension, http_url));
  EXPECT_TRUE(Allowed(extension, https_url));
  EXPECT_TRUE(Blocked(extension, file_url));
  EXPECT_TRUE(Blocked(extension, settings_url));
  EXPECT_TRUE(CaptureOnly(extension, favicon_url));
  EXPECT_TRUE(Blocked(extension, about_url));
  EXPECT_TRUE(Blocked(extension, extension_url));

  // Test access to iframed content.
  GURL within_extension_url = extension->GetResourceURL("page.html");
  EXPECT_TRUE(AllowedScript(extension, http_url, http_url_with_path));
  EXPECT_TRUE(AllowedScript(extension, https_url, http_url_with_path));
  EXPECT_TRUE(AllowedScript(extension, http_url, within_extension_url));
  EXPECT_TRUE(AllowedScript(extension, https_url, within_extension_url));
  EXPECT_TRUE(BlockedScript(extension, http_url, extension_url));
  EXPECT_TRUE(BlockedScript(extension, https_url, extension_url));

  EXPECT_FALSE(extension->HasHostPermission(settings_url));
  EXPECT_FALSE(extension->HasHostPermission(about_url));
  EXPECT_TRUE(extension->HasHostPermission(favicon_url));

  // Test * for scheme, which implies just the http/https schemes.
  extension = LoadManifestStrict("script_and_capture",
      "extension_wildcard.json");
  EXPECT_TRUE(Allowed(extension, http_url));
  EXPECT_TRUE(Allowed(extension, https_url));
  EXPECT_TRUE(Blocked(extension, settings_url));
  EXPECT_TRUE(Blocked(extension, about_url));
  EXPECT_TRUE(Blocked(extension, file_url));
  EXPECT_TRUE(Blocked(extension, favicon_url));
  extension = LoadManifest("script_and_capture",
      "extension_wildcard_settings.json");
  EXPECT_TRUE(Blocked(extension, settings_url));

  // Having chrome://*/ should not work for regular extensions. Note that
  // for favicon access, we require the explicit pattern chrome://favicon/*.
  std::string error;
  extension = LoadManifestUnchecked("script_and_capture",
                                    "extension_wildcard_chrome.json",
                                    Manifest::INTERNAL, Extension::NO_FLAGS,
                                    &error);
  EXPECT_TRUE(extension == NULL);
  EXPECT_EQ(ErrorUtils::FormatErrorMessage(
      errors::kInvalidPermissionScheme, "chrome://*/"), error);

  // Having chrome://favicon/* should not give you chrome://*
  extension = LoadManifestStrict("script_and_capture",
      "extension_chrome_favicon_wildcard.json");
  EXPECT_TRUE(Blocked(extension, settings_url));
  EXPECT_TRUE(CaptureOnly(extension, favicon_url));
  EXPECT_TRUE(Blocked(extension, about_url));
  EXPECT_TRUE(extension->HasHostPermission(favicon_url));

  // Having http://favicon should not give you chrome://favicon
  extension = LoadManifestStrict("script_and_capture",
      "extension_http_favicon.json");
  EXPECT_TRUE(Blocked(extension, settings_url));
  EXPECT_TRUE(Blocked(extension, favicon_url));

  // Component extensions with <all_urls> should get everything.
  extension = LoadManifest("script_and_capture", "extension_component_all.json",
      Manifest::COMPONENT, Extension::NO_FLAGS);
  EXPECT_TRUE(Allowed(extension, http_url));
  EXPECT_TRUE(Allowed(extension, https_url));
  EXPECT_TRUE(Allowed(extension, settings_url));
  EXPECT_TRUE(Allowed(extension, about_url));
  EXPECT_TRUE(Allowed(extension, favicon_url));
  EXPECT_TRUE(extension->HasHostPermission(favicon_url));

  // Component extensions should only get access to what they ask for.
  extension = LoadManifest("script_and_capture",
      "extension_component_google.json", Manifest::COMPONENT,
      Extension::NO_FLAGS);
  EXPECT_TRUE(Allowed(extension, http_url));
  EXPECT_TRUE(Blocked(extension, https_url));
  EXPECT_TRUE(Blocked(extension, file_url));
  EXPECT_TRUE(Blocked(extension, settings_url));
  EXPECT_TRUE(Blocked(extension, favicon_url));
  EXPECT_TRUE(Blocked(extension, about_url));
  EXPECT_TRUE(Blocked(extension, extension_url));
  EXPECT_FALSE(extension->HasHostPermission(settings_url));
}


TEST_F(ExtensionScriptAndCaptureVisibleTest, PermissionsWithChromeURLsEnabled) {
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kExtensionsOnChromeURLs);

  scoped_refptr<Extension> extension;

  // Test <all_urls> for regular extensions.
  extension = LoadManifestStrict("script_and_capture",
      "extension_regular_all.json");
  EXPECT_TRUE(Allowed(extension, http_url));
  EXPECT_TRUE(Allowed(extension, https_url));
  EXPECT_TRUE(Blocked(extension, file_url));
  EXPECT_TRUE(Blocked(extension, settings_url));
  EXPECT_TRUE(Allowed(extension, favicon_url));  // chrome:// requested
  EXPECT_TRUE(Blocked(extension, about_url));
  EXPECT_TRUE(Blocked(extension, extension_url));

  // Test access to iframed content.
  GURL within_extension_url = extension->GetResourceURL("page.html");
  EXPECT_TRUE(AllowedScript(extension, http_url, http_url_with_path));
  EXPECT_TRUE(AllowedScript(extension, https_url, http_url_with_path));
  EXPECT_TRUE(AllowedScript(extension, http_url, within_extension_url));
  EXPECT_TRUE(AllowedScript(extension, https_url, within_extension_url));
  EXPECT_TRUE(BlockedScript(extension, http_url, extension_url));
  EXPECT_TRUE(BlockedScript(extension, https_url, extension_url));

  EXPECT_FALSE(extension->HasHostPermission(settings_url));
  EXPECT_FALSE(extension->HasHostPermission(about_url));
  EXPECT_TRUE(extension->HasHostPermission(favicon_url));

  // Test * for scheme, which implies just the http/https schemes.
  extension = LoadManifestStrict("script_and_capture",
      "extension_wildcard.json");
  EXPECT_TRUE(Allowed(extension, http_url));
  EXPECT_TRUE(Allowed(extension, https_url));
  EXPECT_TRUE(Blocked(extension, settings_url));
  EXPECT_TRUE(Blocked(extension, about_url));
  EXPECT_TRUE(Blocked(extension, file_url));
  EXPECT_TRUE(Blocked(extension, favicon_url));
  extension = LoadManifest("script_and_capture",
      "extension_wildcard_settings.json");
  EXPECT_TRUE(Blocked(extension, settings_url));

  // Having chrome://*/ should work for regular extensions with the flag
  // enabled.
  std::string error;
  extension = LoadManifestUnchecked("script_and_capture",
                                    "extension_wildcard_chrome.json",
                                    Manifest::INTERNAL, Extension::NO_FLAGS,
                                    &error);
  EXPECT_FALSE(extension == NULL);
  EXPECT_TRUE(Blocked(extension, http_url));
  EXPECT_TRUE(Blocked(extension, https_url));
  EXPECT_TRUE(Allowed(extension, settings_url));
  EXPECT_TRUE(Blocked(extension, about_url));
  EXPECT_TRUE(Blocked(extension, file_url));
  EXPECT_TRUE(Allowed(extension, favicon_url));  // chrome:// requested

  // Having chrome://favicon/* should not give you chrome://*
  extension = LoadManifestStrict("script_and_capture",
      "extension_chrome_favicon_wildcard.json");
  EXPECT_TRUE(Blocked(extension, settings_url));
  EXPECT_TRUE(Allowed(extension, favicon_url));  // chrome:// requested
  EXPECT_TRUE(Blocked(extension, about_url));
  EXPECT_TRUE(extension->HasHostPermission(favicon_url));

  // Having http://favicon should not give you chrome://favicon
  extension = LoadManifestStrict("script_and_capture",
      "extension_http_favicon.json");
  EXPECT_TRUE(Blocked(extension, settings_url));
  EXPECT_TRUE(Blocked(extension, favicon_url));

  // Component extensions with <all_urls> should get everything.
  extension = LoadManifest("script_and_capture", "extension_component_all.json",
      Manifest::COMPONENT, Extension::NO_FLAGS);
  EXPECT_TRUE(Allowed(extension, http_url));
  EXPECT_TRUE(Allowed(extension, https_url));
  EXPECT_TRUE(Allowed(extension, settings_url));
  EXPECT_TRUE(Allowed(extension, about_url));
  EXPECT_TRUE(Allowed(extension, favicon_url));
  EXPECT_TRUE(extension->HasHostPermission(favicon_url));

  // Component extensions should only get access to what they ask for.
  extension = LoadManifest("script_and_capture",
      "extension_component_google.json", Manifest::COMPONENT,
      Extension::NO_FLAGS);
  EXPECT_TRUE(Allowed(extension, http_url));
  EXPECT_TRUE(Blocked(extension, https_url));
  EXPECT_TRUE(Blocked(extension, file_url));
  EXPECT_TRUE(Blocked(extension, settings_url));
  EXPECT_TRUE(Blocked(extension, favicon_url));
  EXPECT_TRUE(Blocked(extension, about_url));
  EXPECT_TRUE(Blocked(extension, extension_url));
  EXPECT_FALSE(extension->HasHostPermission(settings_url));
}

TEST_F(ExtensionScriptAndCaptureVisibleTest, TabSpecific) {
  scoped_refptr<Extension> extension =
      LoadManifestStrict("script_and_capture", "tab_specific.json");

  EXPECT_FALSE(extension->GetTabSpecificPermissions(0).get());
  EXPECT_FALSE(extension->GetTabSpecificPermissions(1).get());
  EXPECT_FALSE(extension->GetTabSpecificPermissions(2).get());

  std::set<GURL> no_urls;

  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));

  URLPatternSet allowed_hosts;
  allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL,
                                      http_url.spec()));
  std::set<GURL> allowed_urls;
  allowed_urls.insert(http_url);
  // http_url_with_path() will also be allowed, because Extension should be
  // considering the security origin of the URL not the URL itself, and
  // http_url is in allowed_hosts.
  allowed_urls.insert(http_url_with_path);

  {
    scoped_refptr<PermissionSet> permissions(
        new PermissionSet(APIPermissionSet(), allowed_hosts, URLPatternSet()));
    extension->UpdateTabSpecificPermissions(0, permissions);
    EXPECT_EQ(permissions->explicit_hosts(),
              extension->GetTabSpecificPermissions(0)->explicit_hosts());
  }

  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, allowed_urls, 0));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));

  extension->ClearTabSpecificPermissions(0);
  EXPECT_FALSE(extension->GetTabSpecificPermissions(0).get());

  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));

  std::set<GURL> more_allowed_urls = allowed_urls;
  more_allowed_urls.insert(https_url);
  URLPatternSet more_allowed_hosts = allowed_hosts;
  more_allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL,
                                           https_url.spec()));

  {
    scoped_refptr<PermissionSet> permissions(
        new PermissionSet(APIPermissionSet(), allowed_hosts, URLPatternSet()));
    extension->UpdateTabSpecificPermissions(0, permissions);
    EXPECT_EQ(permissions->explicit_hosts(),
              extension->GetTabSpecificPermissions(0)->explicit_hosts());

    permissions = new PermissionSet(APIPermissionSet(),
                                    more_allowed_hosts,
                                    URLPatternSet());
    extension->UpdateTabSpecificPermissions(1, permissions);
    EXPECT_EQ(permissions->explicit_hosts(),
              extension->GetTabSpecificPermissions(1)->explicit_hosts());
  }

  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, allowed_urls, 0));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, more_allowed_urls, 1));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));

  extension->ClearTabSpecificPermissions(0);
  EXPECT_FALSE(extension->GetTabSpecificPermissions(0).get());

  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, more_allowed_urls, 1));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));

  extension->ClearTabSpecificPermissions(1);
  EXPECT_FALSE(extension->GetTabSpecificPermissions(1).get());

  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1));
  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
}

TEST_F(ExtensionTest, OptionalOnlyPermission) {
  // Set feature current channel to dev because the only permission that must
  // be optional (usbDevices) is only available on dev channel.
  Feature::ScopedCurrentChannel scoped_channel(
      chrome::VersionInfo::CHANNEL_DEV);

  scoped_refptr<Extension> extension;
  std::string error;
  extension = LoadManifestUnchecked("optional_only_permission",
                                    "manifest1.json",
                                    Manifest::INTERNAL, Extension::NO_FLAGS,
                                    &error);
  EXPECT_TRUE(extension == NULL);
  ASSERT_EQ(ErrorUtils::FormatErrorMessage(
        errors::kPermissionMustBeOptional, "usbDevices"), error);

  error.clear();
  extension = LoadManifestUnchecked("optional_only_permission",
                                    "manifest2.json",
                                    Manifest::INTERNAL, Extension::NO_FLAGS,
                                    &error);
  EXPECT_TRUE(extension != NULL);
  EXPECT_TRUE(error.empty());
}

}  // namespace extensions