diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/extensions | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/extensions')
83 files changed, 10088 insertions, 0 deletions
diff --git a/chromium/extensions/DEPS b/chromium/extensions/DEPS new file mode 100644 index 00000000000..850ea194bf9 --- /dev/null +++ b/chromium/extensions/DEPS @@ -0,0 +1,13 @@ +include_rules = [ + "+content/public/common", + "+crypto", + "+testing", + "+ui" +] + +# More specific rules for what we are allowed to include. +specific_include_rules = { + ".*test\.cc": [ + "+content/public/test", + ] +} diff --git a/chromium/extensions/OWNERS b/chromium/extensions/OWNERS new file mode 100644 index 00000000000..a6cd87dfa0d --- /dev/null +++ b/chromium/extensions/OWNERS @@ -0,0 +1,11 @@ +# This should match chrome/browser/extensions/OWNERS +asargent@chromium.org +benwells@chromium.org +finnur@chromium.org +jyasskin@chromium.org +kalman@chromium.org +koz@chromium.org +mek@chromium.org +miket@chromium.org +mpcomplete@chromium.org +yoz@chromium.org diff --git a/chromium/extensions/README b/chromium/extensions/README new file mode 100644 index 00000000000..c994549997e --- /dev/null +++ b/chromium/extensions/README @@ -0,0 +1,3 @@ +This will become a reusable extensions module. It implements the core parts of +Chrome's extension system, and can be used with any host of the 'content' +module. diff --git a/chromium/extensions/browser/DEPS b/chromium/extensions/browser/DEPS new file mode 100644 index 00000000000..1c35d9ca694 --- /dev/null +++ b/chromium/extensions/browser/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+content/public/browser", +] diff --git a/chromium/extensions/browser/extension_error.cc b/chromium/extensions/browser/extension_error.cc new file mode 100644 index 00000000000..8b6196c4200 --- /dev/null +++ b/chromium/extensions/browser/extension_error.cc @@ -0,0 +1,172 @@ +// Copyright 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 "extensions/browser/extension_error.h" + +#include "base/json/json_reader.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "extensions/common/constants.h" +#include "url/gurl.h" + +using base::string16; + +namespace extensions { + +namespace { + +const char kLineNumberKey[] = "lineNumber"; +const char kColumnNumberKey[] = "columnNumber"; +const char kURLKey[] = "url"; +const char kFunctionNameKey[] = "functionName"; +const char kExecutionContextURLKey[] = "executionContextURL"; +const char kStackTraceKey[] = "stackTrace"; + +// Try to retrieve an extension ID from a |url|. On success, returns true and +// populates |extension_id| with the ID. On failure, returns false and leaves +// extension_id untouched. +bool GetExtensionIDFromGURL(const GURL& url, std::string* extension_id) { + if (url.SchemeIs(kExtensionScheme)) { + *extension_id = url.host(); + return true; + } + return false; +} + +} // namespace + +ExtensionError::ExtensionError(Type type, + const std::string& extension_id, + bool from_incognito, + const string16& source, + const string16& message) + : type_(type), + extension_id_(extension_id), + from_incognito_(from_incognito), + source_(source), + message_(message) { +} + +ExtensionError::~ExtensionError() { +} + +std::string ExtensionError::PrintForTest() const { + return std::string("Extension Error:") + + "\n OTR: " + std::string(from_incognito_ ? "true" : "false") + + "\n Source: " + base::UTF16ToUTF8(source_) + + "\n Message: " + base::UTF16ToUTF8(message_) + + "\n ID: " + extension_id_; +} + +ManifestParsingError::ManifestParsingError(const std::string& extension_id, + const string16& message) + : ExtensionError(ExtensionError::MANIFEST_PARSING_ERROR, + extension_id, + false, // extensions can't be installed while incognito. + base::FilePath(kManifestFilename).AsUTF16Unsafe(), + message) { +} + +ManifestParsingError::~ManifestParsingError() { +} + +std::string ManifestParsingError::PrintForTest() const { + return ExtensionError::PrintForTest() + + "\n Type: ManifestParsingError"; +} + +JavascriptRuntimeError::StackFrame::StackFrame() : line_number(-1), + column_number(-1) { +} + +JavascriptRuntimeError::StackFrame::StackFrame(size_t frame_line, + size_t frame_column, + const string16& frame_url, + const string16& frame_function) + : line_number(frame_line), + column_number(frame_column), + url(frame_url), + function(frame_function) { +} + +JavascriptRuntimeError::StackFrame::~StackFrame() { +} + +JavascriptRuntimeError::JavascriptRuntimeError(bool from_incognito, + const string16& source, + const string16& message, + logging::LogSeverity level, + const string16& details) + : ExtensionError(ExtensionError::JAVASCRIPT_RUNTIME_ERROR, + std::string(), // We don't know the id yet. + from_incognito, + source, + message), + level_(level) { + ParseDetails(details); + DetermineExtensionID(); +} + +JavascriptRuntimeError::~JavascriptRuntimeError() { +} + +std::string JavascriptRuntimeError::PrintForTest() const { + std::string result = ExtensionError::PrintForTest() + + "\n Type: JavascriptRuntimeError" + "\n Context: " + base::UTF16ToUTF8(execution_context_url_) + + "\n Stack Trace: "; + for (StackTrace::const_iterator iter = stack_trace_.begin(); + iter != stack_trace_.end(); ++iter) { + result += "\n {" + "\n Line: " + base::IntToString(iter->line_number) + + "\n Column: " + base::IntToString(iter->column_number) + + "\n URL: " + base::UTF16ToUTF8(iter->url) + + "\n Function: " + base::UTF16ToUTF8(iter->function) + + "\n }"; + } + return result; +} + +void JavascriptRuntimeError::ParseDetails(const string16& details) { + scoped_ptr<base::Value> value( + base::JSONReader::Read(base::UTF16ToUTF8(details))); + const base::DictionaryValue* details_value; + const base::ListValue* trace_value = NULL; + + // The |details| value should contain an execution context url and a stack + // trace. + if (!value.get() || + !value->GetAsDictionary(&details_value) || + !details_value->GetString(kExecutionContextURLKey, + &execution_context_url_) || + !details_value->GetList(kStackTraceKey, &trace_value)) { + NOTREACHED(); + return; + } + + int line = 0; + int column = 0; + string16 url; + + for (size_t i = 0; i < trace_value->GetSize(); ++i) { + const base::DictionaryValue* frame_value = NULL; + CHECK(trace_value->GetDictionary(i, &frame_value)); + + frame_value->GetInteger(kLineNumberKey, &line); + frame_value->GetInteger(kColumnNumberKey, &column); + frame_value->GetString(kURLKey, &url); + + string16 function; + frame_value->GetString(kFunctionNameKey, &function); // This can be empty. + stack_trace_.push_back(StackFrame(line, column, url, function)); + } +} + +void JavascriptRuntimeError::DetermineExtensionID() { + if (!GetExtensionIDFromGURL(GURL(source_), &extension_id_)) + GetExtensionIDFromGURL(GURL(execution_context_url_), &extension_id_); +} + +} // namespace extensions diff --git a/chromium/extensions/browser/extension_error.h b/chromium/extensions/browser/extension_error.h new file mode 100644 index 00000000000..1cd4a7b69bf --- /dev/null +++ b/chromium/extensions/browser/extension_error.h @@ -0,0 +1,121 @@ +// Copyright 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. + +#ifndef EXTENSIONS_BROWSER_EXTENSION_ERROR_H_ +#define EXTENSIONS_BROWSER_EXTENSION_ERROR_H_ + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/strings/string16.h" + +namespace extensions { + +class ExtensionError { + public: + enum Type { + MANIFEST_PARSING_ERROR, + JAVASCRIPT_RUNTIME_ERROR + }; + + virtual ~ExtensionError(); + + virtual std::string PrintForTest() const; + + Type type() const { return type_; } + const base::string16& source() const { return source_; } + const base::string16& message() const { return message_; } + const std::string& extension_id() const { return extension_id_; } + bool from_incognito() const { return from_incognito_; } + + protected: + ExtensionError(Type type, + const std::string& extension_id, + bool from_incognito, + const base::string16& source, + const base::string16& message); + + // Which type of error this is. + Type type_; + // The ID of the extension which caused the error. + std::string extension_id_; + // Whether or not the error was caused while incognito. + bool from_incognito_; + // The source for the error; this can be a script, web page, or manifest file. + // This is stored as a string (rather than a url) since it can be a Chrome + // script file (e.g., event_bindings.js). + base::string16 source_; + // The error message itself. + base::string16 message_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionError); +}; + +class ManifestParsingError : public ExtensionError { + public: + ManifestParsingError(const std::string& extension_id, + const base::string16& message); + virtual ~ManifestParsingError(); + + virtual std::string PrintForTest() const OVERRIDE; + private: + DISALLOW_COPY_AND_ASSIGN(ManifestParsingError); +}; + +class JavascriptRuntimeError : public ExtensionError { + public: + struct StackFrame { + size_t line_number; + size_t column_number; + // This is stored as a string (rather than a url) since it can be a + // Chrome script file (e.g., event_bindings.js). + base::string16 url; + base::string16 function; // optional + + // STL-Required constructor + StackFrame(); + + StackFrame(size_t frame_line, + size_t frame_column, + const base::string16& frame_url, + const base::string16& frame_function /* can be empty */); + + ~StackFrame(); + }; + typedef std::vector<StackFrame> StackTrace; + + JavascriptRuntimeError(bool from_incognito, + const base::string16& source, + const base::string16& message, + logging::LogSeverity level, + const base::string16& details); + virtual ~JavascriptRuntimeError(); + + virtual std::string PrintForTest() const OVERRIDE; + + logging::LogSeverity level() const { return level_; } + const base::string16& execution_context_url() const { + return execution_context_url_; + } + const StackTrace& stack_trace() const { return stack_trace_; } + private: + // Parse the JSON |details| passed to the error. This includes a stack trace + // and an execution context url. + void ParseDetails(const base::string16& details); + // Try to determine the ID of the extension. This may be obtained through the + // reported source, or through the execution context url. + void DetermineExtensionID(); + + logging::LogSeverity level_; + base::string16 execution_context_url_; + StackTrace stack_trace_; + + DISALLOW_COPY_AND_ASSIGN(JavascriptRuntimeError); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_EXTENSION_ERROR_H_ diff --git a/chromium/extensions/browser/extension_prefs_scope.h b/chromium/extensions/browser/extension_prefs_scope.h new file mode 100644 index 00000000000..3747ee817fb --- /dev/null +++ b/chromium/extensions/browser/extension_prefs_scope.h @@ -0,0 +1,28 @@ +// Copyright 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. + +#ifndef EXTENSIONS_BROWSER_EXTENSION_PREFS_SCOPE_H_ +#define EXTENSIONS_BROWSER_EXTENSION_PREFS_SCOPE_H_ + +#include "base/basictypes.h" + +namespace extensions { + +// Scope for a preference. +enum ExtensionPrefsScope { + // Regular profile and incognito. + kExtensionPrefsScopeRegular, + // Regular profile only. + kExtensionPrefsScopeRegularOnly, + // Incognito profile; preference is persisted to disk and remains active + // after a browser restart. + kExtensionPrefsScopeIncognitoPersistent, + // Incognito profile; preference is kept in memory and deleted when the + // incognito session is terminated. + kExtensionPrefsScopeIncognitoSessionOnly +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_EXTENSION_PREFS_SCOPE_H_ diff --git a/chromium/extensions/browser/file_reader.cc b/chromium/extensions/browser/file_reader.cc new file mode 100644 index 00000000000..f1a560016f4 --- /dev/null +++ b/chromium/extensions/browser/file_reader.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2011 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 "extensions/browser/file_reader.h" + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/message_loop/message_loop.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +FileReader::FileReader(const extensions::ExtensionResource& resource, + const Callback& callback) + : resource_(resource), + callback_(callback), + origin_loop_(base::MessageLoop::current()) {} + +void FileReader::Start() { + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&FileReader::ReadFileOnBackgroundThread, this)); +} + +FileReader::~FileReader() {} + +void FileReader::ReadFileOnBackgroundThread() { + std::string data; + bool success = file_util::ReadFileToString(resource_.GetFilePath(), &data); + origin_loop_->PostTask(FROM_HERE, base::Bind(callback_, success, data)); +} diff --git a/chromium/extensions/browser/file_reader.h b/chromium/extensions/browser/file_reader.h new file mode 100644 index 00000000000..109a97a8b47 --- /dev/null +++ b/chromium/extensions/browser/file_reader.h @@ -0,0 +1,46 @@ +// Copyright (c) 2011 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. + +#ifndef EXTENSIONS_BROWSER_FILE_READER_H_ +#define EXTENSIONS_BROWSER_FILE_READER_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "extensions/common/extension_resource.h" + +namespace base { +class MessageLoop; +} + +// This file defines an interface for reading a file asynchronously on a +// background thread. +// Consider abstracting out a FilePathProvider (ExtensionResource) and moving +// back to chrome/browser/net if other subsystems want to use it. +class FileReader : public base::RefCountedThreadSafe<FileReader> { + public: + // Reports success or failure and the data of the file upon success. + typedef base::Callback<void(bool, const std::string&)> Callback; + + FileReader(const extensions::ExtensionResource& resource, + const Callback& callback); + + // Called to start reading the file on a background thread. Upon completion, + // the callback will be notified of the results. + void Start(); + + private: + friend class base::RefCountedThreadSafe<FileReader>; + + virtual ~FileReader(); + + void ReadFileOnBackgroundThread(); + + extensions::ExtensionResource resource_; + Callback callback_; + base::MessageLoop* origin_loop_; +}; + +#endif // EXTENSIONS_BROWSER_FILE_READER_H_ diff --git a/chromium/extensions/browser/file_reader_unittest.cc b/chromium/extensions/browser/file_reader_unittest.cc new file mode 100644 index 00000000000..17a466ca4c1 --- /dev/null +++ b/chromium/extensions/browser/file_reader_unittest.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2011 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 "base/bind.h" +#include "base/bind_helpers.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "content/public/test/test_browser_thread.h" +#include "extensions/browser/file_reader.h" +#include "extensions/common/extension_paths.h" +#include "extensions/common/extension_resource.h" +#include "extensions/common/id_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; + +namespace extensions { + +class FileReaderTest : public testing::Test { + public: + FileReaderTest() : file_thread_(BrowserThread::FILE) { + file_thread_.Start(); + } + private: + base::MessageLoop message_loop_; + content::TestBrowserThread file_thread_; +}; + +class Receiver { + public: + Receiver() : succeeded_(false) { + } + + FileReader::Callback NewCallback() { + return base::Bind(&Receiver::DidReadFile, base::Unretained(this)); + } + + bool succeeded() const { return succeeded_; } + const std::string& data() const { return data_; } + + private: + void DidReadFile(bool success, const std::string& data) { + succeeded_ = success; + data_ = data; + base::MessageLoop::current()->Quit(); + } + + bool succeeded_; + std::string data_; +}; + +void RunBasicTest(const char* filename) { + base::FilePath path; + PathService::Get(DIR_TEST_DATA, &path); + std::string extension_id = id_util::GenerateId("test"); + ExtensionResource resource( + extension_id, path, base::FilePath().AppendASCII(filename)); + path = path.AppendASCII(filename); + + std::string file_contents; + ASSERT_TRUE(file_util::ReadFileToString(path, &file_contents)); + + Receiver receiver; + + scoped_refptr<FileReader> file_reader( + new FileReader(resource, receiver.NewCallback())); + file_reader->Start(); + + base::MessageLoop::current()->Run(); + + EXPECT_TRUE(receiver.succeeded()); + EXPECT_EQ(file_contents, receiver.data()); +} + +TEST_F(FileReaderTest, SmallFile) { + RunBasicTest("smallfile"); +} + +TEST_F(FileReaderTest, BiggerFile) { + RunBasicTest("bigfile"); +} + +TEST_F(FileReaderTest, NonExistantFile) { + base::FilePath path; + PathService::Get(DIR_TEST_DATA, &path); + std::string extension_id = id_util::GenerateId("test"); + ExtensionResource resource(extension_id, path, base::FilePath( + FILE_PATH_LITERAL("file_that_does_not_exist"))); + path = path.AppendASCII("file_that_does_not_exist"); + + Receiver receiver; + + scoped_refptr<FileReader> file_reader( + new FileReader(resource, receiver.NewCallback())); + file_reader->Start(); + + base::MessageLoop::current()->Run(); + + EXPECT_FALSE(receiver.succeeded()); +} + +} // namespace extensions diff --git a/chromium/extensions/browser/pref_names.cc b/chromium/extensions/browser/pref_names.cc new file mode 100644 index 00000000000..56de3397d9b --- /dev/null +++ b/chromium/extensions/browser/pref_names.cc @@ -0,0 +1,37 @@ +// Copyright 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 "extensions/browser/pref_names.h" + +#include "base/logging.h" + +namespace extensions { +namespace pref_names { + +bool ScopeToPrefName(ExtensionPrefsScope scope, std::string* result) { + switch (scope) { + case kExtensionPrefsScopeRegular: + *result = kPrefPreferences; + return true; + case kExtensionPrefsScopeRegularOnly: + *result = kPrefRegularOnlyPreferences; + return true; + case kExtensionPrefsScopeIncognitoPersistent: + *result = kPrefIncognitoPreferences; + return true; + case kExtensionPrefsScopeIncognitoSessionOnly: + return false; + } + NOTREACHED(); + return false; +} + +const char kPrefPreferences[] = "preferences"; +const char kPrefIncognitoPreferences[] = "incognito_preferences"; +const char kPrefRegularOnlyPreferences[] = "regular_only_preferences"; +const char kPrefContentSettings[] = "content_settings"; +const char kPrefIncognitoContentSettings[] = "incognito_content_settings"; + +} // namespace pref_names +} // namespace extensions diff --git a/chromium/extensions/browser/pref_names.h b/chromium/extensions/browser/pref_names.h new file mode 100644 index 00000000000..f70b525b15c --- /dev/null +++ b/chromium/extensions/browser/pref_names.h @@ -0,0 +1,42 @@ +// Copyright 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. + +#ifndef EXTENSIONS_BROWSER_PREF_NAMES_H_ +#define EXTENSIONS_BROWSER_PREF_NAMES_H_ + +#include <string> + +#include "extensions/browser/extension_prefs_scope.h" + +namespace extensions { + +// Preference keys which are needed by both the ExtensionPrefs and by external +// clients, such as APIs. +namespace pref_names { + +// If the given |scope| is persisted, return true and populate |result| with the +// appropriate pref name. If |scope| is not persisted, return false, and leave +// |result| unchanged. +bool ScopeToPrefName(ExtensionPrefsScope scope, std::string* result); + +// A preference that contains any extension-controlled preferences. +extern const char kPrefPreferences[]; + +// A preference that contains any extension-controlled incognito preferences. +extern const char kPrefIncognitoPreferences[]; + +// A preference that contains any extension-controlled regular-only preferences. +extern const char kPrefRegularOnlyPreferences[]; + +// A preference that contains extension-set content settings. +extern const char kPrefContentSettings[]; + +// A preference that contains extension-set content settings. +extern const char kPrefIncognitoContentSettings[]; + +} // namespace pref_names + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_PREF_NAMES_H_ diff --git a/chromium/extensions/browser/view_type_utils.cc b/chromium/extensions/browser/view_type_utils.cc new file mode 100644 index 00000000000..a28381f2db0 --- /dev/null +++ b/chromium/extensions/browser/view_type_utils.cc @@ -0,0 +1,44 @@ +// 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 "extensions/browser/view_type_utils.h" + +#include "base/lazy_instance.h" +#include "content/public/browser/web_contents.h" + +using content::WebContents; + +namespace extensions { + +namespace { + +const char kViewTypeUserDataKey[] = "ViewTypeUserData"; + +class ViewTypeUserData : public base::SupportsUserData::Data { + public: + explicit ViewTypeUserData(ViewType type) : type_(type) {} + virtual ~ViewTypeUserData() {} + ViewType type() { return type_; } + + private: + ViewType type_; +}; + +} // namespace + +ViewType GetViewType(WebContents* tab) { + if (!tab) + return VIEW_TYPE_INVALID; + + ViewTypeUserData* user_data = static_cast<ViewTypeUserData*>( + tab->GetUserData(&kViewTypeUserDataKey)); + + return user_data ? user_data->type() : VIEW_TYPE_INVALID; +} + +void SetViewType(WebContents* tab, ViewType type) { + tab->SetUserData(&kViewTypeUserDataKey, new ViewTypeUserData(type)); +} + +} // namespace chrome diff --git a/chromium/extensions/browser/view_type_utils.h b/chromium/extensions/browser/view_type_utils.h new file mode 100644 index 00000000000..4667be438f2 --- /dev/null +++ b/chromium/extensions/browser/view_type_utils.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef EXTENSIONS_BROWSER_VIEW_TYPE_UTILS_H_ +#define EXTENSIONS_BROWSER_VIEW_TYPE_UTILS_H_ + +#include "extensions/common/view_type.h" + +namespace content { +class WebContents; +} + +namespace extensions { + +// Get/Set the type of a WebContents. +// GetViewType handles a NULL |tab| for convenience by returning +// VIEW_TYPE_INVALID. +ViewType GetViewType(content::WebContents* tab); +void SetViewType(content::WebContents* tab, ViewType type); + +} // namespace chrome + +#endif // EXTENSIONS_BROWSER_VIEW_TYPE_UTILS_H_ diff --git a/chromium/extensions/common/constants.cc b/chromium/extensions/common/constants.cc new file mode 100644 index 00000000000..b73906121fc --- /dev/null +++ b/chromium/extensions/common/constants.cc @@ -0,0 +1,40 @@ +// 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 "extensions/common/constants.h" + +#include "base/files/file_path.h" + +namespace extensions { + +const char kExtensionScheme[] = "chrome-extension"; + +const base::FilePath::CharType kManifestFilename[] = + FILE_PATH_LITERAL("manifest.json"); +const base::FilePath::CharType kLocaleFolder[] = + FILE_PATH_LITERAL("_locales"); +const base::FilePath::CharType kMessagesFilename[] = + FILE_PATH_LITERAL("messages.json"); +const base::FilePath::CharType kPlatformSpecificFolder[] = + FILE_PATH_LITERAL("_platform_specific"); + +const char kInstallDirectoryName[] = "Extensions"; + +const char kTempExtensionName[] = "CRX_INSTALL"; + +const char kDecodedImagesFilename[] = "DECODED_IMAGES"; + +const char kDecodedMessageCatalogsFilename[] = "DECODED_MESSAGE_CATALOGS"; + +const char kGeneratedBackgroundPageFilename[] = + "_generated_background_page.html"; + +const char kModulesDir[] = "_modules"; + +const base::FilePath::CharType kExtensionFileExtension[] = + FILE_PATH_LITERAL(".crx"); +const base::FilePath::CharType kExtensionKeyFileExtension[] = + FILE_PATH_LITERAL(".pem"); + +} // namespace extensions diff --git a/chromium/extensions/common/constants.h b/chromium/extensions/common/constants.h new file mode 100644 index 00000000000..492ddbc27b4 --- /dev/null +++ b/chromium/extensions/common/constants.h @@ -0,0 +1,57 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_CONSTANTS_H_ +#define EXTENSIONS_COMMON_CONSTANTS_H_ + +#include "base/files/file_path.h" + +namespace extensions { + +// Scheme we serve extension content from. +extern const char kExtensionScheme[]; + + // The name of the manifest inside an extension. +extern const base::FilePath::CharType kManifestFilename[]; + + // The name of locale folder inside an extension. +extern const base::FilePath::CharType kLocaleFolder[]; + + // The name of the messages file inside an extension. +extern const base::FilePath::CharType kMessagesFilename[]; + +// The base directory for subdirectories with platform-specific code. +extern const base::FilePath::CharType kPlatformSpecificFolder[]; + +// The name of the directory inside the profile where extensions are +// installed to. +extern const char kInstallDirectoryName[]; + +// The name of a temporary directory to install an extension into for +// validation before finalizing install. +extern const char kTempExtensionName[]; + +// The file to write our decoded images to, relative to the extension_path. +extern const char kDecodedImagesFilename[]; + +// The file to write our decoded message catalogs to, relative to the +// extension_path. +extern const char kDecodedMessageCatalogsFilename[]; + +// The filename to use for a background page generated from +// background.scripts. +extern const char kGeneratedBackgroundPageFilename[]; + +// Path to imported modules. +extern const char kModulesDir[]; + +// The file extension (.crx) for extensions. +extern const base::FilePath::CharType kExtensionFileExtension[]; + +// The file extension (.pem) for private key files. +extern const base::FilePath::CharType kExtensionKeyFileExtension[]; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_CONSTANTS_H_ diff --git a/chromium/extensions/common/crx_file.cc b/chromium/extensions/common/crx_file.cc new file mode 100644 index 00000000000..73e7f7b73d6 --- /dev/null +++ b/chromium/extensions/common/crx_file.cc @@ -0,0 +1,81 @@ +// 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 "extensions/common/crx_file.h" + +namespace extensions { + +namespace { + +// The current version of the crx format. +static const uint32 kCurrentVersion = 2; + +// The current version of the crx diff format. +static const uint32 kCurrentDiffVersion = 0; + +// The maximum size the crx parser will tolerate for a public key. +static const uint32 kMaxPublicKeySize = 1 << 16; + +// The maximum size the crx parser will tolerate for a signature. +static const uint32 kMaxSignatureSize = 1 << 16; + +} // namespace + +// The magic string embedded in the header. +const char kCrxFileHeaderMagic[] = "Cr24"; +const char kCrxDiffFileHeaderMagic[] = "CrOD"; + +scoped_ptr<CrxFile> CrxFile::Parse(const CrxFile::Header& header, + CrxFile::Error* error) { + if (HeaderIsValid(header, error)) + return scoped_ptr<CrxFile>(new CrxFile(header)); + return scoped_ptr<CrxFile>(); +} + +scoped_ptr<CrxFile> CrxFile::Create(const uint32 key_size, + const uint32 signature_size, + CrxFile::Error* error) { + CrxFile::Header header; + memcpy(&header.magic, kCrxFileHeaderMagic, kCrxFileHeaderMagicSize); + header.version = kCurrentVersion; + header.key_size = key_size; + header.signature_size = signature_size; + if (HeaderIsValid(header, error)) + return scoped_ptr<CrxFile>(new CrxFile(header)); + return scoped_ptr<CrxFile>(); +} + +CrxFile::CrxFile(const Header& header) : header_(header) { +} + +bool CrxFile::HeaderIsDelta(const CrxFile::Header& header) { + return !strncmp(kCrxDiffFileHeaderMagic, header.magic, sizeof(header.magic)); +} + +bool CrxFile::HeaderIsValid(const CrxFile::Header& header, + CrxFile::Error* error) { + bool valid = false; + bool diffCrx = false; + if (!strncmp(kCrxDiffFileHeaderMagic, header.magic, sizeof(header.magic))) + diffCrx = true; + if (strncmp(kCrxFileHeaderMagic, header.magic, sizeof(header.magic)) && + !diffCrx) + *error = kWrongMagic; + else if (header.version != kCurrentVersion + && !(diffCrx && header.version == kCurrentDiffVersion)) + *error = kInvalidVersion; + else if (header.key_size > kMaxPublicKeySize) + *error = kInvalidKeyTooLarge; + else if (header.key_size == 0) + *error = kInvalidKeyTooSmall; + else if (header.signature_size > kMaxSignatureSize) + *error = kInvalidSignatureTooLarge; + else if (header.signature_size == 0) + *error = kInvalidSignatureTooSmall; + else + valid = true; + return valid; +} + +} // namespace extensions diff --git a/chromium/extensions/common/crx_file.h b/chromium/extensions/common/crx_file.h new file mode 100644 index 00000000000..3ac81897925 --- /dev/null +++ b/chromium/extensions/common/crx_file.h @@ -0,0 +1,80 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_CRX_FILE_H_ +#define EXTENSIONS_COMMON_CRX_FILE_H_ + +#include <sys/types.h> +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace extensions { + +// CRX files have a header that includes a magic key, version number, and +// some signature sizing information. Use CrxFile object to validate whether +// the header is valid or not. +class CrxFile { + public: + + // The size of the magic character sequence at the beginning of each crx + // file, in bytes. This should be a multiple of 4. + static const size_t kCrxFileHeaderMagicSize = 4; + + // This header is the first data at the beginning of an extension. Its + // contents are purposely 32-bit aligned so that it can just be slurped into + // a struct without manual parsing. + struct Header { + char magic[kCrxFileHeaderMagicSize]; + uint32 version; + uint32 key_size; // The size of the public key, in bytes. + uint32 signature_size; // The size of the signature, in bytes. + // An ASN.1-encoded PublicKeyInfo structure follows. + // The signature follows. + }; + + enum Error { + kWrongMagic, + kInvalidVersion, + kInvalidKeyTooLarge, + kInvalidKeyTooSmall, + kInvalidSignatureTooLarge, + kInvalidSignatureTooSmall, + }; + + // Construct a new CRX file header object with bytes of a header + // read from a CRX file. If a null scoped_ptr is returned, |error| + // contains an error code with additional information. + static scoped_ptr<CrxFile> Parse(const Header& header, Error* error); + + // Construct a new header for the given key and signature sizes. + // Returns a null scoped_ptr if erroneous values of |key_size| and/or + // |signature_size| are provided. |error| contains an error code with + // additional information. + // Use this constructor and then .header() to obtain the Header + // for writing out to a CRX file. + static scoped_ptr<CrxFile> Create(const uint32 key_size, + const uint32 signature_size, + Error* error); + + // Returns the header structure for writing out to a CRX file. + const Header& header() const { return header_; } + + // Checks a valid |header| to determine whether or not the CRX represents a + // differential CRX. + static bool HeaderIsDelta(const Header& header); + + private: + Header header_; + + // Constructor is private. Clients should use static factory methods above. + explicit CrxFile(const Header& header); + + // Checks the |header| for validity and returns true if the values are valid. + // If false is returned, more detailed error code is returned in |error|. + static bool HeaderIsValid(const Header& header, Error* error); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_CRX_FILE_H_ diff --git a/chromium/extensions/common/draggable_region.cc b/chromium/extensions/common/draggable_region.cc new file mode 100644 index 00000000000..baee6eecf5c --- /dev/null +++ b/chromium/extensions/common/draggable_region.cc @@ -0,0 +1,13 @@ +// 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 "extensions/common/draggable_region.h" + +namespace extensions { + +DraggableRegion::DraggableRegion() + : draggable(false) { +} + +} // namespace extensions diff --git a/chromium/extensions/common/draggable_region.h b/chromium/extensions/common/draggable_region.h new file mode 100644 index 00000000000..8b22aa332c5 --- /dev/null +++ b/chromium/extensions/common/draggable_region.h @@ -0,0 +1,21 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_DRAGGABLE_REGION_H_ +#define EXTENSIONS_COMMON_DRAGGABLE_REGION_H_ + +#include "ui/gfx/rect.h" + +namespace extensions { + +struct DraggableRegion { + bool draggable; + gfx::Rect bounds; + + DraggableRegion(); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_DRAGGABLE_REGION_H_ diff --git a/chromium/extensions/common/error_utils.cc b/chromium/extensions/common/error_utils.cc new file mode 100644 index 00000000000..bd4d32e0d2a --- /dev/null +++ b/chromium/extensions/common/error_utils.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2011 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 "extensions/common/error_utils.h" + +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" + +namespace extensions { + +std::string ErrorUtils::FormatErrorMessage(const std::string& format, + const std::string& s1) { + std::string ret_val = format; + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); + return ret_val; +} + +std::string ErrorUtils::FormatErrorMessage(const std::string& format, + const std::string& s1, + const std::string& s2) { + std::string ret_val = format; + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); + return ret_val; +} + +std::string ErrorUtils::FormatErrorMessage(const std::string& format, + const std::string& s1, + const std::string& s2, + const std::string& s3) { + std::string ret_val = format; + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s3); + return ret_val; +} + +string16 ErrorUtils::FormatErrorMessageUTF16(const std::string& format, + const std::string& s1) { + std::string ret_val = format; + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); + return UTF8ToUTF16(ret_val); +} + +string16 ErrorUtils::FormatErrorMessageUTF16(const std::string& format, + const std::string& s1, + const std::string& s2) { + std::string ret_val = format; + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); + return UTF8ToUTF16(ret_val); +} + +string16 ErrorUtils::FormatErrorMessageUTF16(const std::string& format, + const std::string& s1, + const std::string& s2, + const std::string& s3) { + std::string ret_val = format; + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s3); + return UTF8ToUTF16(ret_val); +} + +} // namespace diff --git a/chromium/extensions/common/error_utils.h b/chromium/extensions/common/error_utils.h new file mode 100644 index 00000000000..c4d012ae2c0 --- /dev/null +++ b/chromium/extensions/common/error_utils.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 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. + +#ifndef EXTENSIONS_COMMON_ERROR_UTILS_H_ +#define EXTENSIONS_COMMON_ERROR_UTILS_H_ + +#include <string> + +#include "base/strings/string16.h" + +namespace extensions { + +class ErrorUtils { + public: + // Creates an error messages from a pattern. + static std::string FormatErrorMessage(const std::string& format, + const std::string& s1); + + static std::string FormatErrorMessage(const std::string& format, + const std::string& s1, + const std::string& s2); + + static std::string FormatErrorMessage(const std::string& format, + const std::string& s1, + const std::string& s2, + const std::string& s3); + + static string16 FormatErrorMessageUTF16(const std::string& format, + const std::string& s1); + + static string16 FormatErrorMessageUTF16(const std::string& format, + const std::string& s1, + const std::string& s2); + + static string16 FormatErrorMessageUTF16(const std::string& format, + const std::string& s1, + const std::string& s2, + const std::string& s3); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_ERROR_UTILS_H_ diff --git a/chromium/extensions/common/event_filter.cc b/chromium/extensions/common/event_filter.cc new file mode 100644 index 00000000000..70915b976d0 --- /dev/null +++ b/chromium/extensions/common/event_filter.cc @@ -0,0 +1,181 @@ +// 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 "extensions/common/event_filter.h" + +#include "extensions/common/matcher/url_matcher_factory.h" +#include "ipc/ipc_message.h" + +namespace extensions { + +EventFilter::EventMatcherEntry::EventMatcherEntry( + scoped_ptr<EventMatcher> event_matcher, + URLMatcher* url_matcher, + const URLMatcherConditionSet::Vector& condition_sets) + : event_matcher_(event_matcher.Pass()), + url_matcher_(url_matcher) { + for (URLMatcherConditionSet::Vector::const_iterator it = + condition_sets.begin(); it != condition_sets.end(); it++) + condition_set_ids_.push_back((*it)->id()); + url_matcher_->AddConditionSets(condition_sets); +} + +EventFilter::EventMatcherEntry::~EventMatcherEntry() { + url_matcher_->RemoveConditionSets(condition_set_ids_); +} + +void EventFilter::EventMatcherEntry::DontRemoveConditionSetsInDestructor() { + condition_set_ids_.clear(); +} + +EventFilter::EventFilter() + : next_id_(0), + next_condition_set_id_(0) { +} + +EventFilter::~EventFilter() { + // Normally when an event matcher entry is removed from event_matchers_ it + // will remove its condition sets from url_matcher_, but as url_matcher_ is + // being destroyed anyway there is no need to do that step here. + for (EventMatcherMultiMap::iterator it = event_matchers_.begin(); + it != event_matchers_.end(); it++) { + for (EventMatcherMap::iterator it2 = it->second.begin(); + it2 != it->second.end(); it2++) { + it2->second->DontRemoveConditionSetsInDestructor(); + } + } +} + +EventFilter::MatcherID +EventFilter::AddEventMatcher(const std::string& event_name, + scoped_ptr<EventMatcher> matcher) { + MatcherID id = next_id_++; + URLMatcherConditionSet::Vector condition_sets; + if (!CreateConditionSets(id, matcher.get(), &condition_sets)) + return -1; + + for (URLMatcherConditionSet::Vector::iterator it = condition_sets.begin(); + it != condition_sets.end(); it++) { + condition_set_id_to_event_matcher_id_.insert( + std::make_pair((*it)->id(), id)); + } + id_to_event_name_[id] = event_name; + event_matchers_[event_name][id] = linked_ptr<EventMatcherEntry>( + new EventMatcherEntry(matcher.Pass(), &url_matcher_, condition_sets)); + return id; +} + +EventMatcher* EventFilter::GetEventMatcher(MatcherID id) { + DCHECK(id_to_event_name_.find(id) != id_to_event_name_.end()); + const std::string& event_name = id_to_event_name_[id]; + return event_matchers_[event_name][id]->event_matcher(); +} + +const std::string& EventFilter::GetEventName(MatcherID id) { + DCHECK(id_to_event_name_.find(id) != id_to_event_name_.end()); + return id_to_event_name_[id]; +} + +bool EventFilter::CreateConditionSets( + MatcherID id, + EventMatcher* matcher, + URLMatcherConditionSet::Vector* condition_sets) { + if (matcher->GetURLFilterCount() == 0) { + // If there are no URL filters then we want to match all events, so create a + // URLFilter from an empty dictionary. + base::DictionaryValue empty_dict; + return AddDictionaryAsConditionSet(&empty_dict, condition_sets); + } + for (int i = 0; i < matcher->GetURLFilterCount(); i++) { + base::DictionaryValue* url_filter; + if (!matcher->GetURLFilter(i, &url_filter)) + return false; + if (!AddDictionaryAsConditionSet(url_filter, condition_sets)) + return false; + } + return true; +} + +bool EventFilter::AddDictionaryAsConditionSet( + base::DictionaryValue* url_filter, + URLMatcherConditionSet::Vector* condition_sets) { + std::string error; + URLMatcherConditionSet::ID condition_set_id = next_condition_set_id_++; + condition_sets->push_back(URLMatcherFactory::CreateFromURLFilterDictionary( + url_matcher_.condition_factory(), + url_filter, + condition_set_id, + &error)); + if (!error.empty()) { + LOG(ERROR) << "CreateFromURLFilterDictionary failed: " << error; + url_matcher_.ClearUnusedConditionSets(); + condition_sets->clear(); + return false; + } + return true; +} + +std::string EventFilter::RemoveEventMatcher(MatcherID id) { + std::map<MatcherID, std::string>::iterator it = id_to_event_name_.find(id); + std::string event_name = it->second; + // EventMatcherEntry's destructor causes the condition set ids to be removed + // from url_matcher_. + event_matchers_[event_name].erase(id); + id_to_event_name_.erase(it); + return event_name; +} + +std::set<EventFilter::MatcherID> EventFilter::MatchEvent( + const std::string& event_name, const EventFilteringInfo& event_info, + int routing_id) { + std::set<MatcherID> matchers; + + EventMatcherMultiMap::iterator it = event_matchers_.find(event_name); + if (it == event_matchers_.end()) + return matchers; + + EventMatcherMap& matcher_map = it->second; + GURL url_to_match_against = event_info.has_url() ? event_info.url() : GURL(); + std::set<URLMatcherConditionSet::ID> matching_condition_set_ids = + url_matcher_.MatchURL(url_to_match_against); + for (std::set<URLMatcherConditionSet::ID>::iterator it = + matching_condition_set_ids.begin(); + it != matching_condition_set_ids.end(); it++) { + std::map<URLMatcherConditionSet::ID, MatcherID>::iterator matcher_id = + condition_set_id_to_event_matcher_id_.find(*it); + if (matcher_id == condition_set_id_to_event_matcher_id_.end()) { + NOTREACHED() << "id not found in condition set map (" << (*it) << ")"; + continue; + } + MatcherID id = matcher_id->second; + EventMatcherMap::iterator matcher_entry = matcher_map.find(id); + if (matcher_entry == matcher_map.end()) { + // Matcher must be for a different event. + continue; + } + const EventMatcher* event_matcher = matcher_entry->second->event_matcher(); + // The context that installed the event listener should be the same context + // as the one where the event listener is called. + if ((routing_id != MSG_ROUTING_NONE) && + (event_matcher->GetRoutingID() != routing_id)) { + continue; + } + if (event_matcher->MatchNonURLCriteria(event_info)) { + CHECK(!event_matcher->HasURLFilters() || event_info.has_url()); + matchers.insert(id); + } + } + + return matchers; +} + +int EventFilter::GetMatcherCountForEvent(const std::string& name) { + EventMatcherMultiMap::const_iterator it = event_matchers_.find(name); + if (it == event_matchers_.end()) + return 0; + + return it->second.size(); +} + +} // namespace extensions diff --git a/chromium/extensions/common/event_filter.h b/chromium/extensions/common/event_filter.h new file mode 100644 index 00000000000..6ef136cb9dc --- /dev/null +++ b/chromium/extensions/common/event_filter.h @@ -0,0 +1,127 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_EVENT_FILTER_H_ +#define EXTENSIONS_COMMON_EVENT_FILTER_H_ + +#include <map> +#include <set> + +#include "base/memory/linked_ptr.h" +#include "extensions/common/event_filtering_info.h" +#include "extensions/common/event_matcher.h" +#include "extensions/common/matcher/url_matcher.h" + +namespace extensions { + +// Matches incoming events against a collection of EventMatchers. Each added +// EventMatcher is given an id which is returned by MatchEvent() when it is +// passed a matching event. +class EventFilter { + public: + typedef int MatcherID; + EventFilter(); + ~EventFilter(); + + // Adds an event matcher that will be used in calls to MatchEvent(). Returns + // the id of the matcher, or -1 if there was an error. + MatcherID AddEventMatcher(const std::string& event_name, + scoped_ptr<EventMatcher> matcher); + + // Retrieve the EventMatcher with the given id. + EventMatcher* GetEventMatcher(MatcherID id); + + // Retrieve the name of the event that the EventMatcher specified by |id| is + // referring to. + const std::string& GetEventName(MatcherID id); + + // Removes an event matcher, returning the name of the event that it was for. + std::string RemoveEventMatcher(MatcherID id); + + // Match an event named |event_name| with filtering info |event_info| against + // our set of event matchers. Returns a set of ids that correspond to the + // event matchers that matched the event. + // TODO(koz): Add a std::string* parameter for retrieving error messages. + std::set<MatcherID> MatchEvent(const std::string& event_name, + const EventFilteringInfo& event_info, + int routing_id); + + int GetMatcherCountForEvent(const std::string& event_name); + + // For testing. + bool IsURLMatcherEmpty() const { + return url_matcher_.IsEmpty(); + } + + private: + class EventMatcherEntry { + public: + // Adds |condition_sets| to |url_matcher| on construction and removes them + // again on destruction. |condition_sets| should be the + // URLMatcherConditionSets that match the URL constraints specified by + // |event_matcher|. + EventMatcherEntry(scoped_ptr<EventMatcher> event_matcher, + URLMatcher* url_matcher, + const URLMatcherConditionSet::Vector& condition_sets); + ~EventMatcherEntry(); + + // Prevents the removal of condition sets when this class is destroyed. We + // call this in EventFilter's destructor so that we don't do the costly + // removal of condition sets when the URLMatcher is going to be destroyed + // and clean them up anyway. + void DontRemoveConditionSetsInDestructor(); + + EventMatcher* event_matcher() { + return event_matcher_.get(); + } + + private: + scoped_ptr<EventMatcher> event_matcher_; + // The id sets in url_matcher_ that this EventMatcher owns. + std::vector<URLMatcherConditionSet::ID> condition_set_ids_; + URLMatcher* url_matcher_; + + DISALLOW_COPY_AND_ASSIGN(EventMatcherEntry); + }; + + // Maps from a matcher id to an event matcher entry. + typedef std::map<MatcherID, linked_ptr<EventMatcherEntry> > EventMatcherMap; + + // Maps from event name to the map of matchers that are registered for it. + typedef std::map<std::string, EventMatcherMap> EventMatcherMultiMap; + + // Adds the list of URL filters in |matcher| to the URL matcher, having + // matches for those URLs map to |id|. + bool CreateConditionSets(MatcherID id, + EventMatcher* matcher, + URLMatcherConditionSet::Vector* condition_sets); + + bool AddDictionaryAsConditionSet( + base::DictionaryValue* url_filter, + URLMatcherConditionSet::Vector* condition_sets); + + URLMatcher url_matcher_; + EventMatcherMultiMap event_matchers_; + + // The next id to assign to an EventMatcher. + MatcherID next_id_; + + // The next id to assign to a condition set passed to URLMatcher. + URLMatcherConditionSet::ID next_condition_set_id_; + + // Maps condition set ids, which URLMatcher operates in, to event matcher + // ids, which the interface to this class operates in. As each EventFilter + // can specify many condition sets this is a many to one relationship. + std::map<URLMatcherConditionSet::ID, MatcherID> + condition_set_id_to_event_matcher_id_; + + // Maps from event matcher ids to the name of the event they match on. + std::map<MatcherID, std::string> id_to_event_name_; + + DISALLOW_COPY_AND_ASSIGN(EventFilter); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_EVENT_FILTER_H_ diff --git a/chromium/extensions/common/event_filter_unittest.cc b/chromium/extensions/common/event_filter_unittest.cc new file mode 100644 index 00000000000..3f7f12b0501 --- /dev/null +++ b/chromium/extensions/common/event_filter_unittest.cc @@ -0,0 +1,253 @@ +// 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 "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "extensions/common/event_filter.h" +#include "extensions/common/event_filtering_info.h" +#include "extensions/common/event_matcher.h" +#include "ipc/ipc_message.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + +class EventFilterUnittest : public testing::Test { + public: + EventFilterUnittest() { + google_event_.SetURL(GURL("http://google.com")); + yahoo_event_.SetURL(GURL("http://yahoo.com")); + random_url_event_.SetURL(GURL("http://www.something-else.com")); + empty_url_event_.SetURL(GURL()); + } + + protected: + scoped_ptr<base::Value> HostSuffixDict(const std::string& host_suffix) { + scoped_ptr<base::DictionaryValue> dict(new DictionaryValue()); + dict->Set("hostSuffix", base::Value::CreateStringValue(host_suffix)); + return scoped_ptr<base::Value>(dict.release()); + } + + scoped_ptr<base::ListValue> ValueAsList(scoped_ptr<base::Value> value) { + scoped_ptr<base::ListValue> result(new base::ListValue()); + result->Append(value.release()); + return result.Pass(); + } + + scoped_ptr<EventMatcher> AllURLs() { + return scoped_ptr<EventMatcher>(new EventMatcher( + scoped_ptr<DictionaryValue>(new DictionaryValue), MSG_ROUTING_NONE)); + } + + scoped_ptr<EventMatcher> HostSuffixMatcher(const std::string& host_suffix) { + return MatcherFromURLFilterList(ValueAsList(HostSuffixDict(host_suffix))); + } + + scoped_ptr<EventMatcher> MatcherFromURLFilterList( + scoped_ptr<ListValue> url_filter_list) { + scoped_ptr<DictionaryValue> filter_dict(new DictionaryValue); + filter_dict->Set("url", url_filter_list.release()); + return scoped_ptr<EventMatcher>( + new EventMatcher(filter_dict.Pass(), MSG_ROUTING_NONE)); + } + + EventFilter event_filter_; + EventFilteringInfo empty_event_; + EventFilteringInfo google_event_; + EventFilteringInfo yahoo_event_; + EventFilteringInfo random_url_event_; + EventFilteringInfo empty_url_event_; +}; + +TEST_F(EventFilterUnittest, NoMatchersMatchIfEmpty) { + std::set<int> matches = event_filter_.MatchEvent("some-event", + empty_event_, + MSG_ROUTING_NONE); + ASSERT_EQ(0u, matches.size()); +} + +TEST_F(EventFilterUnittest, AddingEventMatcherDoesntCrash) { + event_filter_.AddEventMatcher("event1", AllURLs()); +} + +TEST_F(EventFilterUnittest, + DontMatchAgainstMatchersForDifferentEvents) { + event_filter_.AddEventMatcher("event1", AllURLs()); + std::set<int> matches = event_filter_.MatchEvent("event2", + empty_event_, + MSG_ROUTING_NONE); + ASSERT_EQ(0u, matches.size()); +} + +TEST_F(EventFilterUnittest, DoMatchAgainstMatchersForSameEvent) { + int id = event_filter_.AddEventMatcher("event1", AllURLs()); + std::set<int> matches = event_filter_.MatchEvent("event1", + google_event_, MSG_ROUTING_NONE); + ASSERT_EQ(1u, matches.size()); + ASSERT_EQ(1u, matches.count(id)); +} + +TEST_F(EventFilterUnittest, DontMatchUnlessMatcherMatches) { + EventFilteringInfo info; + info.SetURL(GURL("http://www.yahoo.com")); + event_filter_.AddEventMatcher("event1", HostSuffixMatcher("google.com")); + std::set<int> matches = event_filter_.MatchEvent( + "event1", info, MSG_ROUTING_NONE); + ASSERT_TRUE(matches.empty()); +} + +TEST_F(EventFilterUnittest, RemovingAnEventMatcherStopsItMatching) { + int id = event_filter_.AddEventMatcher("event1", AllURLs()); + event_filter_.RemoveEventMatcher(id); + std::set<int> matches = event_filter_.MatchEvent("event1", + empty_event_, + MSG_ROUTING_NONE); + ASSERT_TRUE(matches.empty()); +} + +TEST_F(EventFilterUnittest, MultipleEventMatches) { + int id1 = event_filter_.AddEventMatcher("event1", AllURLs()); + int id2 = event_filter_.AddEventMatcher("event1", AllURLs()); + std::set<int> matches = event_filter_.MatchEvent("event1", + google_event_, MSG_ROUTING_NONE); + ASSERT_EQ(2u, matches.size()); + ASSERT_EQ(1u, matches.count(id1)); + ASSERT_EQ(1u, matches.count(id2)); +} + +TEST_F(EventFilterUnittest, TestURLMatching) { + EventFilteringInfo info; + info.SetURL(GURL("http://www.google.com")); + int id = event_filter_.AddEventMatcher("event1", + HostSuffixMatcher("google.com")); + std::set<int> matches = event_filter_.MatchEvent( + "event1", info, MSG_ROUTING_NONE); + ASSERT_EQ(1u, matches.size()); + ASSERT_EQ(1u, matches.count(id)); +} + +TEST_F(EventFilterUnittest, TestMultipleURLFiltersMatchOnAny) { + scoped_ptr<base::ListValue> filters(new base::ListValue()); + filters->Append(HostSuffixDict("google.com").release()); + filters->Append(HostSuffixDict("yahoo.com").release()); + + scoped_ptr<EventMatcher> matcher(MatcherFromURLFilterList(filters.Pass())); + int id = event_filter_.AddEventMatcher("event1", matcher.Pass()); + + { + std::set<int> matches = event_filter_.MatchEvent("event1", + google_event_, MSG_ROUTING_NONE); + ASSERT_EQ(1u, matches.size()); + ASSERT_EQ(1u, matches.count(id)); + } + { + std::set<int> matches = event_filter_.MatchEvent("event1", + yahoo_event_, MSG_ROUTING_NONE); + ASSERT_EQ(1u, matches.size()); + ASSERT_EQ(1u, matches.count(id)); + } + { + std::set<int> matches = event_filter_.MatchEvent("event1", + random_url_event_, MSG_ROUTING_NONE); + ASSERT_EQ(0u, matches.size()); + } +} + +TEST_F(EventFilterUnittest, TestStillMatchesAfterRemoval) { + int id1 = event_filter_.AddEventMatcher("event1", AllURLs()); + int id2 = event_filter_.AddEventMatcher("event1", AllURLs()); + + event_filter_.RemoveEventMatcher(id1); + { + std::set<int> matches = event_filter_.MatchEvent("event1", + google_event_, MSG_ROUTING_NONE); + ASSERT_EQ(1u, matches.size()); + ASSERT_EQ(1u, matches.count(id2)); + } +} + +TEST_F(EventFilterUnittest, TestMatchesOnlyAgainstPatternsForCorrectEvent) { + int id1 = event_filter_.AddEventMatcher("event1", AllURLs()); + event_filter_.AddEventMatcher("event2", AllURLs()); + + { + std::set<int> matches = event_filter_.MatchEvent("event1", + google_event_, MSG_ROUTING_NONE); + ASSERT_EQ(1u, matches.size()); + ASSERT_EQ(1u, matches.count(id1)); + } +} + +TEST_F(EventFilterUnittest, TestGetMatcherCountForEvent) { + ASSERT_EQ(0, event_filter_.GetMatcherCountForEvent("event1")); + int id1 = event_filter_.AddEventMatcher("event1", AllURLs()); + ASSERT_EQ(1, event_filter_.GetMatcherCountForEvent("event1")); + int id2 = event_filter_.AddEventMatcher("event1", AllURLs()); + ASSERT_EQ(2, event_filter_.GetMatcherCountForEvent("event1")); + event_filter_.RemoveEventMatcher(id1); + ASSERT_EQ(1, event_filter_.GetMatcherCountForEvent("event1")); + event_filter_.RemoveEventMatcher(id2); + ASSERT_EQ(0, event_filter_.GetMatcherCountForEvent("event1")); +} + +TEST_F(EventFilterUnittest, RemoveEventMatcherReturnsEventName) { + int id1 = event_filter_.AddEventMatcher("event1", AllURLs()); + int id2 = event_filter_.AddEventMatcher("event1", AllURLs()); + int id3 = event_filter_.AddEventMatcher("event2", AllURLs()); + + ASSERT_EQ("event1", event_filter_.RemoveEventMatcher(id1)); + ASSERT_EQ("event1", event_filter_.RemoveEventMatcher(id2)); + ASSERT_EQ("event2", event_filter_.RemoveEventMatcher(id3)); +} + +TEST_F(EventFilterUnittest, InvalidURLFilterCantBeAdded) { + scoped_ptr<base::ListValue> filter_list(new base::ListValue()); + filter_list->Append(new base::ListValue()); // Should be a dict. + scoped_ptr<EventMatcher> matcher(MatcherFromURLFilterList( + filter_list.Pass())); + int id1 = event_filter_.AddEventMatcher("event1", matcher.Pass()); + EXPECT_TRUE(event_filter_.IsURLMatcherEmpty()); + ASSERT_EQ(-1, id1); +} + +TEST_F(EventFilterUnittest, EmptyListOfURLFiltersMatchesAllURLs) { + scoped_ptr<base::ListValue> filter_list(new base::ListValue()); + scoped_ptr<EventMatcher> matcher(MatcherFromURLFilterList( + scoped_ptr<ListValue>(new ListValue))); + int id = event_filter_.AddEventMatcher("event1", matcher.Pass()); + std::set<int> matches = event_filter_.MatchEvent("event1", + google_event_, MSG_ROUTING_NONE); + ASSERT_EQ(1u, matches.size()); + ASSERT_EQ(1u, matches.count(id)); +} + +TEST_F(EventFilterUnittest, + InternalURLMatcherShouldBeEmptyWhenThereAreNoEventMatchers) { + ASSERT_TRUE(event_filter_.IsURLMatcherEmpty()); + int id = event_filter_.AddEventMatcher("event1", + HostSuffixMatcher("google.com")); + ASSERT_FALSE(event_filter_.IsURLMatcherEmpty()); + event_filter_.RemoveEventMatcher(id); + ASSERT_TRUE(event_filter_.IsURLMatcherEmpty()); +} + +TEST_F(EventFilterUnittest, EmptyURLsShouldBeMatchedByEmptyURLFilters) { + int id = event_filter_.AddEventMatcher("event1", AllURLs()); + std::set<int> matches = event_filter_.MatchEvent( + "event1", empty_url_event_, MSG_ROUTING_NONE); + ASSERT_EQ(1u, matches.size()); + ASSERT_EQ(1u, matches.count(id)); +} + +TEST_F(EventFilterUnittest, + EmptyURLsShouldBeMatchedByEmptyURLFiltersWithAnEmptyItem) { + scoped_ptr<EventMatcher> matcher(MatcherFromURLFilterList(ValueAsList( + scoped_ptr<Value>(new DictionaryValue())))); + int id = event_filter_.AddEventMatcher("event1", matcher.Pass()); + std::set<int> matches = event_filter_.MatchEvent( + "event1", empty_url_event_, MSG_ROUTING_NONE); + ASSERT_EQ(1u, matches.size()); + ASSERT_EQ(1u, matches.count(id)); +} + +} // namespace extensions diff --git a/chromium/extensions/common/event_filtering_info.cc b/chromium/extensions/common/event_filtering_info.cc new file mode 100644 index 00000000000..29566e891be --- /dev/null +++ b/chromium/extensions/common/event_filtering_info.cc @@ -0,0 +1,48 @@ +// 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 "extensions/common/event_filtering_info.h" + +#include "base/json/json_writer.h" +#include "base/values.h" + +namespace extensions { + +EventFilteringInfo::EventFilteringInfo() + : has_url_(false), + has_instance_id_(false), + instance_id_(0) { +} + +EventFilteringInfo::~EventFilteringInfo() { +} + +void EventFilteringInfo::SetURL(const GURL& url) { + url_ = url; + has_url_ = true; +} + +void EventFilteringInfo::SetInstanceID(int instance_id) { + instance_id_ = instance_id; + has_instance_id_ = true; +} + +scoped_ptr<base::Value> EventFilteringInfo::AsValue() const { + if (IsEmpty()) + return scoped_ptr<base::Value>(base::Value::CreateNullValue()); + + scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue); + if (has_url_) + result->SetString("url", url_.spec()); + + if (has_instance_id_) + result->SetInteger("instanceId", instance_id_); + return result.PassAs<base::Value>(); +} + +bool EventFilteringInfo::IsEmpty() const { + return !has_url_; +} + +} // namespace extensions diff --git a/chromium/extensions/common/event_filtering_info.h b/chromium/extensions/common/event_filtering_info.h new file mode 100644 index 00000000000..86309ab2b93 --- /dev/null +++ b/chromium/extensions/common/event_filtering_info.h @@ -0,0 +1,53 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_EVENT_FILTERING_INFO_H_ +#define EXTENSIONS_COMMON_EVENT_FILTERING_INFO_H_ + +#include "base/memory/scoped_ptr.h" +#include "url/gurl.h" + +namespace base { +class Value; +} + +namespace extensions { + +// Extra information about an event that is used in event filtering. +// +// This is the information that is matched against criteria specified in JS +// extension event listeners. Eg: +// +// chrome.someApi.onSomeEvent.addListener(cb, +// {url: [{hostSuffix: 'google.com'}], +// tabId: 1}); +class EventFilteringInfo { + public: + EventFilteringInfo(); + ~EventFilteringInfo(); + void SetURL(const GURL& url); + void SetInstanceID(int instance_id); + + bool has_url() const { return has_url_; } + const GURL& url() const { return url_; } + + bool has_instance_id() const { return has_instance_id_; } + int instance_id() const { return instance_id_; } + + scoped_ptr<base::Value> AsValue() const; + bool IsEmpty() const; + + private: + bool has_url_; + GURL url_; + + bool has_instance_id_; + int instance_id_; + + // Allow implicit copy and assignment. +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_EVENT_FILTERING_INFO_H_ diff --git a/chromium/extensions/common/event_matcher.cc b/chromium/extensions/common/event_matcher.cc new file mode 100644 index 00000000000..ad76c89f4db --- /dev/null +++ b/chromium/extensions/common/event_matcher.cc @@ -0,0 +1,61 @@ +// 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 "extensions/common/event_matcher.h" + +#include "extensions/common/event_filtering_info.h" + +namespace { +const char kUrlFiltersKey[] = "url"; +} + +namespace extensions { + +EventMatcher::EventMatcher(scoped_ptr<base::DictionaryValue> filter, + int routing_id) + : filter_(filter.Pass()), + routing_id_(routing_id) { +} + +EventMatcher::~EventMatcher() { +} + +bool EventMatcher::MatchNonURLCriteria( + const EventFilteringInfo& event_info) const { + if (!event_info.has_instance_id()) + return true; + + return event_info.instance_id() == GetInstanceID(); +} + +int EventMatcher::GetURLFilterCount() const { + base::ListValue* url_filters = NULL; + if (filter_->GetList(kUrlFiltersKey, &url_filters)) + return url_filters->GetSize(); + return 0; +} + +bool EventMatcher::GetURLFilter(int i, base::DictionaryValue** url_filter_out) { + base::ListValue* url_filters = NULL; + if (filter_->GetList(kUrlFiltersKey, &url_filters)) { + return url_filters->GetDictionary(i, url_filter_out); + } + return false; +} + +int EventMatcher::HasURLFilters() const { + return GetURLFilterCount() != 0; +} + +int EventMatcher::GetInstanceID() const { + int instance_id = 0; + filter_->GetInteger("instanceId", &instance_id); + return instance_id; +} + +int EventMatcher::GetRoutingID() const { + return routing_id_; +} + +} // namespace extensions diff --git a/chromium/extensions/common/event_matcher.h b/chromium/extensions/common/event_matcher.h new file mode 100644 index 00000000000..7843fced0c3 --- /dev/null +++ b/chromium/extensions/common/event_matcher.h @@ -0,0 +1,57 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_EVENT_MATCHER_H_ +#define EXTENSIONS_COMMON_EVENT_MATCHER_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/values.h" + +namespace extensions { + +class EventFilteringInfo; + +// Matches EventFilteringInfos against a set of criteria. This is intended to +// be used by EventFilter which performs efficient URL matching across +// potentially many EventMatchers itself. This is why this class only exposes +// MatchNonURLCriteria() - URL matching is handled by EventFilter. +class EventMatcher { + public: + EventMatcher(scoped_ptr<base::DictionaryValue> filter, + int routing_id); + ~EventMatcher(); + + // Returns true if |event_info| satisfies this matcher's criteria, not taking + // into consideration any URL criteria. + bool MatchNonURLCriteria(const EventFilteringInfo& event_info) const; + + int GetURLFilterCount() const; + bool GetURLFilter(int i, base::DictionaryValue** url_filter_out); + + int HasURLFilters() const; + + int GetInstanceID() const; + + int GetRoutingID() const; + + base::DictionaryValue* value() const { + return filter_.get(); + } + + private: + // Contains a dictionary that corresponds to a single event filter, eg: + // + // {url: [{hostSuffix: 'google.com'}]} + // + // The valid filter keys are event-specific. + scoped_ptr<base::DictionaryValue> filter_; + + int routing_id_; + + DISALLOW_COPY_AND_ASSIGN(EventMatcher); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_EVENT_MATCHER_H_ diff --git a/chromium/extensions/common/extension_paths.cc b/chromium/extensions/common/extension_paths.cc new file mode 100644 index 00000000000..1e0c461ac0a --- /dev/null +++ b/chromium/extensions/common/extension_paths.cc @@ -0,0 +1,41 @@ +// 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 "extensions/common/extension_paths.h" + +#include "base/file_util.h" +#include "base/path_service.h" + +namespace extensions { + +bool PathProvider(int key, base::FilePath* result) { + switch (key) { + case DIR_TEST_DATA: { + base::FilePath cur; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("extensions")); + cur = cur.Append(FILE_PATH_LITERAL("test")); + cur = cur.Append(FILE_PATH_LITERAL("data")); + if (!base::PathExists(cur)) // we don't want to create this + return false; + + *result = cur; + return true; + break; + } + default: + return false; + } + + return false; +} + +// This cannot be done as a static initializer sadly since Visual Studio will +// eliminate this object file if there is no direct entry point into it. +void RegisterPathProvider() { + PathService::RegisterProvider(PathProvider, PATH_START, PATH_END); +} + +} // namespace extensions diff --git a/chromium/extensions/common/extension_paths.h b/chromium/extensions/common/extension_paths.h new file mode 100644 index 00000000000..bbeff6acefd --- /dev/null +++ b/chromium/extensions/common/extension_paths.h @@ -0,0 +1,27 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_EXTENSION_PATHS_H_ +#define EXTENSIONS_COMMON_EXTENSION_PATHS_H_ + +// This file declares path keys for extensions. These can be used with +// the PathService to access various special directories and files. + +namespace extensions { + +enum { + PATH_START = 6000, + + // Valid only in development environment + DIR_TEST_DATA, + + PATH_END +}; + +// Call once to register the provider for the path keys defined above. +void RegisterPathProvider(); + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_EXTENSION_PATHS_H_ diff --git a/chromium/extensions/common/extension_resource.cc b/chromium/extensions/common/extension_resource.cc new file mode 100644 index 00000000000..e6605440132 --- /dev/null +++ b/chromium/extensions/common/extension_resource.cc @@ -0,0 +1,128 @@ +// 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 "extensions/common/extension_resource.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" + +namespace extensions { + +ExtensionResource::ExtensionResource() : follow_symlinks_anywhere_(false) { +} + +ExtensionResource::ExtensionResource(const std::string& extension_id, + const base::FilePath& extension_root, + const base::FilePath& relative_path) + : extension_id_(extension_id), + extension_root_(extension_root), + relative_path_(relative_path), + follow_symlinks_anywhere_(false) { +} + +ExtensionResource::~ExtensionResource() {} + +void ExtensionResource::set_follow_symlinks_anywhere() { + follow_symlinks_anywhere_ = true; +} + +const base::FilePath& ExtensionResource::GetFilePath() const { + if (extension_root_.empty() || relative_path_.empty()) { + DCHECK(full_resource_path_.empty()); + return full_resource_path_; + } + + // We've already checked, just return last value. + if (!full_resource_path_.empty()) + return full_resource_path_; + + full_resource_path_ = GetFilePath( + extension_root_, relative_path_, + follow_symlinks_anywhere_ ? + FOLLOW_SYMLINKS_ANYWHERE : SYMLINKS_MUST_RESOLVE_WITHIN_ROOT); + return full_resource_path_; +} + +// static +base::FilePath ExtensionResource::GetFilePath( + const base::FilePath& extension_root, + const base::FilePath& relative_path, + SymlinkPolicy symlink_policy) { + // We need to resolve the parent references in the extension_root + // path on its own because IsParent doesn't like parent references. + base::FilePath clean_extension_root( + base::MakeAbsoluteFilePath(extension_root)); + if (clean_extension_root.empty()) + return base::FilePath(); + + base::FilePath full_path = clean_extension_root.Append(relative_path); + + // If we are allowing the file to be a symlink outside of the root, then the + // path before resolving the symlink must still be within it. + if (symlink_policy == FOLLOW_SYMLINKS_ANYWHERE) { + std::vector<base::FilePath::StringType> components; + relative_path.GetComponents(&components); + int depth = 0; + + for (std::vector<base::FilePath::StringType>::const_iterator + i = components.begin(); i != components.end(); i++) { + if (*i == base::FilePath::kParentDirectory) { + depth--; + } else if (*i != base::FilePath::kCurrentDirectory) { + depth++; + } + if (depth < 0) { + return base::FilePath(); + } + } + } + + // We must resolve the absolute path of the combined path when + // the relative path contains references to a parent folder (i.e., '..'). + // We also check if the path exists because the posix version of + // MakeAbsoluteFilePath will fail if the path doesn't exist, and we want the + // same behavior on Windows... So until the posix and Windows version of + // MakeAbsoluteFilePath are unified, we need an extra call to PathExists, + // unfortunately. + // TODO(mad): Fix this once MakeAbsoluteFilePath is unified. + full_path = base::MakeAbsoluteFilePath(full_path); + if (base::PathExists(full_path) && + (symlink_policy == FOLLOW_SYMLINKS_ANYWHERE || + clean_extension_root.IsParent(full_path))) { + return full_path; + } + + return base::FilePath(); +} + +// Unit-testing helpers. +base::FilePath::StringType ExtensionResource::NormalizeSeperators( + const base::FilePath::StringType& path) const { +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + base::FilePath::StringType win_path = path; + for (size_t i = 0; i < win_path.length(); i++) { + if (base::FilePath::IsSeparator(win_path[i])) + win_path[i] = base::FilePath::kSeparators[0]; + } + return win_path; +#else + return path; +#endif // FILE_PATH_USES_WIN_SEPARATORS +} + +bool ExtensionResource::ComparePathWithDefault( + const base::FilePath& path) const { + // Make sure we have a cached value to test against... + if (full_resource_path_.empty()) + GetFilePath(); + if (NormalizeSeperators(path.value()) == + NormalizeSeperators(full_resource_path_.value())) { + return true; + } else { + return false; + } +} + +} // namespace extensions diff --git a/chromium/extensions/common/extension_resource.h b/chromium/extensions/common/extension_resource.h new file mode 100644 index 00000000000..806889e8d31 --- /dev/null +++ b/chromium/extensions/common/extension_resource.h @@ -0,0 +1,89 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_EXTENSION_RESOURCE_H_ +#define EXTENSIONS_COMMON_EXTENSION_RESOURCE_H_ + +#include <string> + +#include "base/files/file_path.h" + +namespace extensions { + +// Represents a resource inside an extension. For example, an image, or a +// JavaScript file. This is more complicated than just a simple FilePath +// because extension resources can come from multiple physical file locations +// depending on locale. +class ExtensionResource { + public: + // SymlinkPolicy decides whether we'll allow resources to be a symlink to + // anywhere, or whether they must end up within the extension root. + enum SymlinkPolicy { + SYMLINKS_MUST_RESOLVE_WITHIN_ROOT, + FOLLOW_SYMLINKS_ANYWHERE, + }; + + ExtensionResource(); + + ExtensionResource(const std::string& extension_id, + const base::FilePath& extension_root, + const base::FilePath& relative_path); + + ~ExtensionResource(); + + // set_follow_symlinks_anywhere allows the resource to be a symlink to + // anywhere in the filesystem. By default, resources have to be within + // |extension_root| after resolving symlinks. + void set_follow_symlinks_anywhere(); + + // Returns actual path to the resource (default or locale specific). In the + // browser process, this will DCHECK if not called on the file thread. To + // easily load extension images on the UI thread, see ImageLoader. + const base::FilePath& GetFilePath() const; + + // Gets the physical file path for the extension resource, taking into account + // localization. In the browser process, this will DCHECK if not called on the + // file thread. To easily load extension images on the UI thread, see + // ImageLoader. + // + // The relative path must not resolve to a location outside of + // |extension_root|. Iff |file_can_symlink_outside_root| is true, then the + // file can be a symlink that links outside of |extension_root|. + static base::FilePath GetFilePath(const base::FilePath& extension_root, + const base::FilePath& relative_path, + SymlinkPolicy symlink_policy); + + // Getters + const std::string& extension_id() const { return extension_id_; } + const base::FilePath& extension_root() const { return extension_root_; } + const base::FilePath& relative_path() const { return relative_path_; } + + bool empty() const { return extension_root().empty(); } + + // Unit test helpers. + base::FilePath::StringType NormalizeSeperators( + const base::FilePath::StringType& path) const; + bool ComparePathWithDefault(const base::FilePath& path) const; + + private: + // The id of the extension that this resource is associated with. + std::string extension_id_; + + // Extension root. + base::FilePath extension_root_; + + // Relative path to resource. + base::FilePath relative_path_; + + // If |follow_symlinks_anywhere_| is true then the resource itself must be + // within |extension_root|, but it can be a symlink to a file that is not. + bool follow_symlinks_anywhere_; + + // Full path to extension resource. Starts empty. + mutable base::FilePath full_resource_path_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_EXTENSION_RESOURCE_H_ diff --git a/chromium/extensions/common/extension_resource_unittest.cc b/chromium/extensions/common/extension_resource_unittest.cc new file mode 100644 index 00000000000..8ec44bb33da --- /dev/null +++ b/chromium/extensions/common/extension_resource_unittest.cc @@ -0,0 +1,164 @@ +// 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 <algorithm> + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension_paths.h" +#include "extensions/common/extension_resource.h" +#include "extensions/common/id_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/l10n/l10n_util.h" + +namespace extensions { + +TEST(ExtensionResourceTest, CreateEmptyResource) { + ExtensionResource resource; + + EXPECT_TRUE(resource.extension_root().empty()); + EXPECT_TRUE(resource.relative_path().empty()); + EXPECT_TRUE(resource.GetFilePath().empty()); +} + +const base::FilePath::StringType ToLower( + const base::FilePath::StringType& in_str) { + base::FilePath::StringType str(in_str); + std::transform(str.begin(), str.end(), str.begin(), tolower); + return str; +} + +TEST(ExtensionResourceTest, CreateWithMissingResourceOnDisk) { + base::FilePath root_path; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &root_path)); + base::FilePath relative_path; + relative_path = relative_path.AppendASCII("cira.js"); + std::string extension_id = id_util::GenerateId("test"); + ExtensionResource resource(extension_id, root_path, relative_path); + + // The path doesn't exist on disk, we will be returned an empty path. + EXPECT_EQ(root_path.value(), resource.extension_root().value()); + EXPECT_EQ(relative_path.value(), resource.relative_path().value()); + EXPECT_TRUE(resource.GetFilePath().empty()); +} + +TEST(ExtensionResourceTest, ResourcesOutsideOfPath) { + base::ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + + base::FilePath inner_dir = temp.path().AppendASCII("directory"); + ASSERT_TRUE(file_util::CreateDirectory(inner_dir)); + base::FilePath sub_dir = inner_dir.AppendASCII("subdir"); + ASSERT_TRUE(file_util::CreateDirectory(sub_dir)); + base::FilePath inner_file = inner_dir.AppendASCII("inner"); + base::FilePath outer_file = temp.path().AppendASCII("outer"); + ASSERT_TRUE(file_util::WriteFile(outer_file, "X", 1)); + ASSERT_TRUE(file_util::WriteFile(inner_file, "X", 1)); + std::string extension_id = id_util::GenerateId("test"); + +#if defined(OS_POSIX) + base::FilePath symlink_file = inner_dir.AppendASCII("symlink"); + file_util::CreateSymbolicLink( + base::FilePath().AppendASCII("..").AppendASCII("outer"), + symlink_file); +#endif + + // A non-packing extension should be able to access the file within the + // directory. + ExtensionResource r1(extension_id, inner_dir, + base::FilePath().AppendASCII("inner")); + EXPECT_FALSE(r1.GetFilePath().empty()); + + // ... but not a relative path that walks out of |inner_dir|. + ExtensionResource r2(extension_id, inner_dir, + base::FilePath().AppendASCII("..").AppendASCII("outer")); + EXPECT_TRUE(r2.GetFilePath().empty()); + + // A packing extension should also be able to access the file within the + // directory. + ExtensionResource r3(extension_id, inner_dir, + base::FilePath().AppendASCII("inner")); + r3.set_follow_symlinks_anywhere(); + EXPECT_FALSE(r3.GetFilePath().empty()); + + // ... but, again, not a relative path that walks out of |inner_dir|. + ExtensionResource r4(extension_id, inner_dir, + base::FilePath().AppendASCII("..").AppendASCII("outer")); + r4.set_follow_symlinks_anywhere(); + EXPECT_TRUE(r4.GetFilePath().empty()); + + // ... and not even when clever current-directory syntax is present. Note + // that the path for this test case can't start with the current directory + // component due to quirks in FilePath::Append(), and the path must exist. + ExtensionResource r4a( + extension_id, inner_dir, + base::FilePath().AppendASCII("subdir").AppendASCII(".").AppendASCII(".."). + AppendASCII("..").AppendASCII("outer")); + r4a.set_follow_symlinks_anywhere(); + EXPECT_TRUE(r4a.GetFilePath().empty()); + +#if defined(OS_POSIX) + // The non-packing extension should also not be able to access a resource that + // symlinks out of the directory. + ExtensionResource r5(extension_id, inner_dir, + base::FilePath().AppendASCII("symlink")); + EXPECT_TRUE(r5.GetFilePath().empty()); + + // ... but a packing extension can. + ExtensionResource r6(extension_id, inner_dir, + base::FilePath().AppendASCII("symlink")); + r6.set_follow_symlinks_anywhere(); + EXPECT_FALSE(r6.GetFilePath().empty()); +#endif +} + +TEST(ExtensionResourceTest, CreateWithAllResourcesOnDisk) { + base::ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + + // Create resource in the extension root. + const char* filename = "res.ico"; + base::FilePath root_resource = temp.path().AppendASCII(filename); + std::string data = "some foo"; + ASSERT_TRUE(file_util::WriteFile(root_resource, data.c_str(), data.length())); + + // Create l10n resources (for current locale and its parents). + base::FilePath l10n_path = + temp.path().Append(kLocaleFolder); + ASSERT_TRUE(file_util::CreateDirectory(l10n_path)); + + std::vector<std::string> locales; + l10n_util::GetParentLocales(l10n_util::GetApplicationLocale(std::string()), + &locales); + ASSERT_FALSE(locales.empty()); + for (size_t i = 0; i < locales.size(); i++) { + base::FilePath make_path; + make_path = l10n_path.AppendASCII(locales[i]); + ASSERT_TRUE(file_util::CreateDirectory(make_path)); + ASSERT_TRUE(file_util::WriteFile(make_path.AppendASCII(filename), + data.c_str(), data.length())); + } + + base::FilePath path; + std::string extension_id = id_util::GenerateId("test"); + ExtensionResource resource(extension_id, temp.path(), + base::FilePath().AppendASCII(filename)); + base::FilePath resolved_path = resource.GetFilePath(); + + base::FilePath expected_path; + // Expect default path only, since fallback logic is disabled. + // See http://crbug.com/27359. + expected_path = base::MakeAbsoluteFilePath(root_resource); + ASSERT_FALSE(expected_path.empty()); + + EXPECT_EQ(ToLower(expected_path.value()), ToLower(resolved_path.value())); + EXPECT_EQ(ToLower(temp.path().value()), + ToLower(resource.extension_root().value())); + EXPECT_EQ(ToLower(base::FilePath().AppendASCII(filename).value()), + ToLower(resource.relative_path().value())); +} + +} // namespace extensions diff --git a/chromium/extensions/common/extensions_client.cc b/chromium/extensions/common/extensions_client.cc new file mode 100644 index 00000000000..e7f936dfbdf --- /dev/null +++ b/chromium/extensions/common/extensions_client.cc @@ -0,0 +1,32 @@ +// Copyright 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 "base/basictypes.h" +#include "extensions/common/extensions_client.h" + +namespace extensions { + +namespace { + +ExtensionsClient* g_client = NULL; + +void Initialize(ExtensionsClient* client) { + client->RegisterManifestHandlers(); +} + +} // namespace + +ExtensionsClient* ExtensionsClient::Get() { + return g_client; +} + +void ExtensionsClient::Set(ExtensionsClient* client) { + // This can happen in unit tests, where the utility thread runs in-process. + if (g_client) + return; + g_client = client; + Initialize(g_client); +} + +} // namespace extensions diff --git a/chromium/extensions/common/extensions_client.h b/chromium/extensions/common/extensions_client.h new file mode 100644 index 00000000000..83550a9a1aa --- /dev/null +++ b/chromium/extensions/common/extensions_client.h @@ -0,0 +1,38 @@ +// Copyright 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. + +#ifndef EXTENSIONS_COMMON_EXTENSIONS_CLIENT_H_ +#define EXTENSIONS_COMMON_EXTENSIONS_CLIENT_H_ + +#include <string> + +namespace extensions { + +class FeatureProvider; +class PermissionsProvider; + +// Sets up global state for the extensions system. Should be Set() once in each +// process. This should be implemented by the client of the extensions system. +class ExtensionsClient { + public: + // Returns a PermissionsProvider to initialize the permissions system. + virtual const PermissionsProvider& GetPermissionsProvider() const = 0; + + // Gets a feature provider for a specific feature type. + virtual FeatureProvider* GetFeatureProviderByName(const std::string& name) + const = 0; + + // Called at startup. Registers the handlers for parsing manifests. + virtual void RegisterManifestHandlers() const = 0; + + // Return the extensions client. + static ExtensionsClient* Get(); + + // Initialize the extensions system with this extensions client. + static void Set(ExtensionsClient* client); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_EXTENSIONS_CLIENT_H_ diff --git a/chromium/extensions/common/features/feature_provider.cc b/chromium/extensions/common/features/feature_provider.cc new file mode 100644 index 00000000000..8e15d4b5366 --- /dev/null +++ b/chromium/extensions/common/features/feature_provider.cc @@ -0,0 +1,16 @@ +// Copyright 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 "extensions/common/features/feature_provider.h" + +#include "base/basictypes.h" +#include "extensions/common/extensions_client.h" + +namespace extensions { + +FeatureProvider* FeatureProvider::GetByName(const std::string& name) { + return ExtensionsClient::Get()->GetFeatureProviderByName(name); +} + +} // namespace extensions diff --git a/chromium/extensions/common/features/feature_provider.h b/chromium/extensions/common/features/feature_provider.h new file mode 100644 index 00000000000..c20e5d1c3b8 --- /dev/null +++ b/chromium/extensions/common/features/feature_provider.h @@ -0,0 +1,36 @@ +// Copyright 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. + +#ifndef EXTENSIONS_COMMON_FEATURES_FEATURE_PROVIDER_H_ +#define EXTENSIONS_COMMON_FEATURES_FEATURE_PROVIDER_H_ + +#include <string> +#include <vector> + +namespace extensions { + +class Feature; + +// Implemented by classes that can vend features. +class FeatureProvider { + public: + FeatureProvider() {} + virtual ~FeatureProvider() {} + + // Returns the feature with the specified name. + virtual Feature* GetFeature(const std::string& name) = 0; + + // Returns the parent feature of |feature|, or NULL if there isn't one. + virtual Feature* GetParent(Feature* feature) = 0; + + // Returns all features described by this instance. + virtual const std::vector<std::string>& GetAllFeatureNames() = 0; + + // Gets a feature provider for a specific feature type, like "permission". + static FeatureProvider* GetByName(const std::string& name); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_FEATURES_FEATURE_PROVIDER_H_ diff --git a/chromium/extensions/common/id_util.cc b/chromium/extensions/common/id_util.cc new file mode 100644 index 00000000000..3b8628ee04c --- /dev/null +++ b/chromium/extensions/common/id_util.cc @@ -0,0 +1,73 @@ +// 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 "extensions/common/id_util.h" + +#include "base/files/file_path.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "crypto/sha2.h" + +namespace { + +// Converts a normal hexadecimal string into the alphabet used by extensions. +// We use the characters 'a'-'p' instead of '0'-'f' to avoid ever having a +// completely numeric host, since some software interprets that as an IP +// address. +static void ConvertHexadecimalToIDAlphabet(std::string* id) { + for (size_t i = 0; i < id->size(); ++i) { + int val; + if (base::HexStringToInt(base::StringPiece(id->begin() + i, + id->begin() + i + 1), + &val)) { + (*id)[i] = val + 'a'; + } else { + (*id)[i] = 'a'; + } + } +} + +} // namespace + +namespace extensions { +namespace id_util { + +// First 16 bytes of SHA256 hashed public key. +const size_t kIdSize = 16; + +std::string GenerateId(const std::string& input) { + uint8 hash[kIdSize]; + crypto::SHA256HashString(input, hash, sizeof(hash)); + std::string output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash))); + ConvertHexadecimalToIDAlphabet(&output); + + return output; +} + +std::string GenerateIdForPath(const base::FilePath& path) { + base::FilePath new_path = MaybeNormalizePath(path); + std::string path_bytes = + std::string(reinterpret_cast<const char*>(new_path.value().data()), + new_path.value().size() * sizeof(base::FilePath::CharType)); + return GenerateId(path_bytes); +} + +base::FilePath MaybeNormalizePath(const base::FilePath& path) { +#if defined(OS_WIN) + // Normalize any drive letter to upper-case. We do this for consistency with + // net_utils::FilePathToFileURL(), which does the same thing, to make string + // comparisons simpler. + base::FilePath::StringType path_str = path.value(); + if (path_str.size() >= 2 && path_str[0] >= L'a' && path_str[0] <= L'z' && + path_str[1] == ':') + path_str[0] += ('A' - 'a'); + + return base::FilePath(path_str); +#else + return path; +#endif +} + +} // namespace id_util +} // namespace extensions diff --git a/chromium/extensions/common/id_util.h b/chromium/extensions/common/id_util.h new file mode 100644 index 00000000000..74e690dbf60 --- /dev/null +++ b/chromium/extensions/common/id_util.h @@ -0,0 +1,35 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_ID_UTIL_H_ +#define EXTENSIONS_COMMON_ID_UTIL_H_ + +#include <string> + +namespace base { +class FilePath; +} + +namespace extensions { +namespace id_util { + +// The number of bytes in a legal id. +extern const size_t kIdSize; + +// Generates an extension ID from arbitrary input. The same input string will +// always generate the same output ID. +std::string GenerateId(const std::string& input); + +// Generate an ID for an extension in the given path. +// Used while developing extensions, before they have a key. +std::string GenerateIdForPath(const base::FilePath& path); + +// Normalize the path for use by the extension. On Windows, this will make +// sure the drive letter is uppercase. +base::FilePath MaybeNormalizePath(const base::FilePath& path); + +} // namespace id_util +} // namespace extensions + +#endif // EXTENSIONS_COMMON_ID_UTIL_H_ diff --git a/chromium/extensions/common/id_util_unittest.cc b/chromium/extensions/common/id_util_unittest.cc new file mode 100644 index 00000000000..f43d206f1d9 --- /dev/null +++ b/chromium/extensions/common/id_util_unittest.cc @@ -0,0 +1,44 @@ +// 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 "base/basictypes.h" +#include "extensions/common/id_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { +namespace id_util { + +TEST(IDUtilTest, GenerateID) { + const uint8 public_key_info[] = { + 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, + 0x89, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x7f, 0x2b, 0x20, 0xdc, 0x7c, 0x9b, + 0x0c, 0xdc, 0x51, 0x61, 0x99, 0x0d, 0x36, 0x0f, 0xd4, 0x66, 0x88, 0x08, + 0x55, 0x84, 0xd5, 0x3a, 0xbf, 0x2b, 0xa4, 0x64, 0x85, 0x7b, 0x0c, 0x04, + 0x13, 0x3f, 0x8d, 0xf4, 0xbc, 0x38, 0x0d, 0x49, 0xfe, 0x6b, 0xc4, 0x5a, + 0xb0, 0x40, 0x53, 0x3a, 0xd7, 0x66, 0x09, 0x0f, 0x9e, 0x36, 0x74, 0x30, + 0xda, 0x8a, 0x31, 0x4f, 0x1f, 0x14, 0x50, 0xd7, 0xc7, 0x20, 0x94, 0x17, + 0xde, 0x4e, 0xb9, 0x57, 0x5e, 0x7e, 0x0a, 0xe5, 0xb2, 0x65, 0x7a, 0x89, + 0x4e, 0xb6, 0x47, 0xff, 0x1c, 0xbd, 0xb7, 0x38, 0x13, 0xaf, 0x47, 0x85, + 0x84, 0x32, 0x33, 0xf3, 0x17, 0x49, 0xbf, 0xe9, 0x96, 0xd0, 0xd6, 0x14, + 0x6f, 0x13, 0x8d, 0xc5, 0xfc, 0x2c, 0x72, 0xba, 0xac, 0xea, 0x7e, 0x18, + 0x53, 0x56, 0xa6, 0x83, 0xa2, 0xce, 0x93, 0x93, 0xe7, 0x1f, 0x0f, 0xe6, + 0x0f, 0x02, 0x03, 0x01, 0x00, 0x01 + }; + std::string extension_id = GenerateId( + std::string(reinterpret_cast<const char*>(&public_key_info[0]), + arraysize(public_key_info))); + EXPECT_EQ("melddjfinppjdikinhbgehiennejpfhp", extension_id); + + EXPECT_EQ("jpignaibiiemhngfjkcpokkamffknabf", GenerateId("test")); + + EXPECT_EQ("ncocknphbhhlhkikpnnlmbcnbgdempcd", GenerateId("_")); + + EXPECT_EQ("jimneklojkjdibfkgiiophfhjhbdgcfi", + GenerateId( + "this_string_is_longer_than_a_single_sha256_hash_digest")); +} + +} // namespace id_util +} // namespace extensions diff --git a/chromium/extensions/common/install_warning.cc b/chromium/extensions/common/install_warning.cc new file mode 100644 index 00000000000..c632a71ae08 --- /dev/null +++ b/chromium/extensions/common/install_warning.cc @@ -0,0 +1,24 @@ +// 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 "extensions/common/install_warning.h" + +namespace extensions { + +void PrintTo(const InstallWarning& warning, ::std::ostream* os) { + *os << "InstallWarning("; + switch (warning.format) { + case InstallWarning::FORMAT_TEXT: + *os << "FORMAT_TEXT, \""; + break; + case InstallWarning::FORMAT_HTML: + *os << "FORMAT_HTML, \""; + break; + } + // This is just for test error messages, so no need to escape '"' + // characters inside the message. + *os << warning.message << "\")"; +} + +} // namespace extensions diff --git a/chromium/extensions/common/install_warning.h b/chromium/extensions/common/install_warning.h new file mode 100644 index 00000000000..fb3fccb29b6 --- /dev/null +++ b/chromium/extensions/common/install_warning.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_INSTALL_WARNING_H_ +#define EXTENSIONS_COMMON_INSTALL_WARNING_H_ + +#include <ostream> +#include <string> + +namespace extensions { + +struct InstallWarning { + enum Format { + // IMPORTANT: Do not build HTML strings from user or developer-supplied + // input. + FORMAT_TEXT, + FORMAT_HTML, + }; + static InstallWarning Text(const std::string& message) { + return InstallWarning(FORMAT_TEXT, message); + } + InstallWarning(Format format, const std::string& message) + : format(format), message(message) { + } + bool operator==(const InstallWarning& other) const { + return format == other.format && message == other.message; + } + Format format; + std::string message; +}; + +// Let gtest print InstallWarnings. +void PrintTo(const InstallWarning&, ::std::ostream* os); + +} // namespace + +#endif // EXTENSIONS_COMMON_INSTALL_WARNING_H_ diff --git a/chromium/extensions/common/manifest_constants.cc b/chromium/extensions/common/manifest_constants.cc new file mode 100644 index 00000000000..fcdf6e99fc8 --- /dev/null +++ b/chromium/extensions/common/manifest_constants.cc @@ -0,0 +1,151 @@ +// Copyright 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 "extensions/common/manifest_constants.h" + +namespace extensions { + +namespace manifest_keys { + +const char kAllFrames[] = "all_frames"; +const char kAltKey[] = "altKey"; +const char kApp[] = "app"; +const char kAudio[] = "audio"; +const char kBackgroundAllowJsAccess[] = "background.allow_js_access"; +const char kBackgroundPage[] = "background.page"; +const char kBackgroundPageLegacy[] = "background_page"; +const char kBackgroundPersistent[] = "background.persistent"; +const char kBackgroundScripts[] = "background.scripts"; +const char kBrowserAction[] = "browser_action"; +const char kChromeURLOverrides[] = "chrome_url_overrides"; +const char kCommands[] = "commands"; +const char kContentPack[] = "content_pack"; +const char kContentPackSites[] = "sites"; +const char kContentScripts[] = "content_scripts"; +const char kContentSecurityPolicy[] = "content_security_policy"; +const char kConvertedFromUserScript[] = "converted_from_user_script"; +const char kCss[] = "css"; +const char kCtrlKey[] = "ctrlKey"; +const char kCurrentLocale[] = "current_locale"; +const char kDefaultLocale[] = "default_locale"; +const char kDescription[] = "description"; +const char kDevToolsPage[] = "devtools_page"; +const char kDisplayInLauncher[] = "display_in_launcher"; +const char kDisplayInNewTabPage[] = "display_in_new_tab_page"; +const char kEventName[] = "event_name"; +const char kExcludeGlobs[] = "exclude_globs"; +const char kExcludeMatches[] = "exclude_matches"; +const char kExport[] = "export"; +const char kExternallyConnectable[] = "externally_connectable"; +const char kFileAccessList[] = "file_access"; +const char kFileFilters[] = "file_filters"; +const char kFileBrowserHandlers[] = "file_browser_handlers"; +const char kMediaGalleriesHandlers[] = "media_galleries_handlers"; +const char kFileHandlers[] = "file_handlers"; +const char kFileHandlerExtensions[] = "extensions"; +const char kFileHandlerTitle[] = "title"; +const char kFileHandlerTypes[] = "types"; +const char kHomepageURL[] = "homepage_url"; +const char kIcons[] = "icons"; +const char kId[] = "id"; +const char kImport[] = "import"; +const char kIncognito[] = "incognito"; +const char kIncludeGlobs[] = "include_globs"; +const char kInputComponents[] = "input_components"; +const char kIsolation[] = "app.isolation"; +const char kJs[] = "js"; +const char kKey[] = "key"; +const char kKeycode[] = "keyCode"; +const char kKioskEnabled[] = "kiosk_enabled"; +const char kLanguage[] = "language"; +const char kLaunch[] = "app.launch"; +const char kLaunchContainer[] = "app.launch.container"; +const char kLaunchHeight[] = "app.launch.height"; +const char kLaunchLocalPath[] = "app.launch.local_path"; +const char kLaunchWebURL[] = "app.launch.web_url"; +const char kLaunchWidth[] = "app.launch.width"; +const char kLayouts[] = "layouts"; +const char kManifestVersion[] = "manifest_version"; +const char kMatches[] = "matches"; +const char kMinimumChromeVersion[] = "minimum_chrome_version"; +const char kMinimumVersion[] = "minimum_version"; +const char kMIMETypes[] = "mime_types"; +const char kMimeTypesHandler[] = "mime_types_handler"; +const char kName[] = "name"; +const char kNaClModules[] = "nacl_modules"; +const char kNaClModulesMIMEType[] = "mime_type"; +const char kNaClModulesPath[] = "path"; +const char kOAuth2[] = "oauth2"; +const char kOAuth2AutoApprove[] = "oauth2.auto_approve"; +const char kOAuth2ClientId[] = "oauth2.client_id"; +const char kOAuth2Scopes[] = "oauth2.scopes"; +const char kOfflineEnabled[] = "offline_enabled"; +const char kOmnibox[] = "omnibox"; +const char kOmniboxKeyword[] = "omnibox.keyword"; +const char kOptionalPermissions[] = "optional_permissions"; +const char kOptionsPage[] = "options_page"; +const char kPageAction[] = "page_action"; +const char kPageActionDefaultIcon[] = "default_icon"; +const char kPageActionDefaultPopup[] = "default_popup"; +const char kPageActionDefaultTitle[] = "default_title"; +const char kPageActionIcons[] = "icons"; +const char kPageActionId[] = "id"; +const char kPageActionPopup[] = "popup"; +const char kPageActionPopupPath[] = "path"; +const char kPageActions[] = "page_actions"; +const char kPermissions[] = "permissions"; +const char kPlatformAppBackground[] = "app.background"; +const char kPlatformAppBackgroundPage[] = "app.background.page"; +const char kPlatformAppBackgroundScripts[] = "app.background.scripts"; +const char kPlatformAppContentSecurityPolicy[] = "app.content_security_policy"; +const char kPlugins[] = "plugins"; +const char kPluginsPath[] = "path"; +const char kPluginsPublic[] = "public"; +const char kPublicKey[] = "key"; +const char kResources[] = "resources"; +const char kRequirements[] = "requirements"; +const char kRunAt[] = "run_at"; +const char kSandboxedPages[] = "sandbox.pages"; +const char kSandboxedPagesCSP[] = "sandbox.content_security_policy"; +const char kScriptBadge[] = "script_badge"; +const char kShiftKey[] = "shiftKey"; +const char kShortcutKey[] = "shortcutKey"; +const char kSignature[] = "signature"; +const char kSpellcheck[] = "spellcheck"; +const char kSpellcheckDictionaryFormat[] = "dictionary_format"; +const char kSpellcheckDictionaryLanguage[] = "dictionary_language"; +const char kSpellcheckDictionaryLocale[] = "dictionary_locale"; +const char kSpellcheckDictionaryPath[] = "dictionary_path"; +const char kStorageManagedSchema[] = "storage.managed_schema"; +const char kSuggestedKey[] = "suggested_key"; +const char kSystemIndicator[] = "system_indicator"; +const char kSystemInfoDisplay[] = "systemInfo.display"; +const char kTheme[] = "theme"; +const char kThemeColors[] = "colors"; +const char kThemeDisplayProperties[] = "properties"; +const char kThemeImages[] = "images"; +const char kThemeTints[] = "tints"; +const char kTtsEngine[] = "tts_engine"; +const char kTtsGenderFemale[] = "female"; +const char kTtsGenderMale[] = "male"; +const char kTtsVoices[] = "voices"; +const char kTtsVoicesEventTypeEnd[] = "end"; +const char kTtsVoicesEventTypeError[] = "error"; +const char kTtsVoicesEventTypeMarker[] = "marker"; +const char kTtsVoicesEventTypeSentence[] = "sentence"; +const char kTtsVoicesEventTypeStart[] = "start"; +const char kTtsVoicesEventTypeWord[] = "word"; +const char kTtsVoicesEventTypes[] = "event_types"; +const char kTtsVoicesGender[] = "gender"; +const char kTtsVoicesLang[] = "lang"; +const char kTtsVoicesVoiceName[] = "voice_name"; +const char kType[] = "type"; +const char kUpdateURL[] = "update_url"; +const char kVersion[] = "version"; +const char kWebAccessibleResources[] = "web_accessible_resources"; +const char kWebURLs[] = "app.urls"; + +} // namespace manifest_keys + +} // namespace extensions diff --git a/chromium/extensions/common/manifest_constants.h b/chromium/extensions/common/manifest_constants.h new file mode 100644 index 00000000000..ce31f7c6881 --- /dev/null +++ b/chromium/extensions/common/manifest_constants.h @@ -0,0 +1,157 @@ +// Copyright 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. + +#ifndef EXTENSIONS_COMMON_MANIFEST_CONSTANTS_H_ +#define EXTENSIONS_COMMON_MANIFEST_CONSTANTS_H_ + +// Keys used in JSON representation of extensions. +namespace extensions { +namespace manifest_keys { + extern const char kAllFrames[]; + extern const char kAltKey[]; + extern const char kApp[]; + extern const char kBackgroundAllowJsAccess[]; + extern const char kBackgroundPage[]; + extern const char kBackgroundPageLegacy[]; + extern const char kBackgroundPersistent[]; + extern const char kBackgroundScripts[]; + extern const char kBrowserAction[]; + extern const char kBrowseURLs[]; + extern const char kChromeURLOverrides[]; + extern const char kCommands[]; + extern const char kContentPack[]; + extern const char kContentPackSites[]; + extern const char kContentScripts[]; + extern const char kContentSecurityPolicy[]; + extern const char kConvertedFromUserScript[]; + extern const char kCss[]; + extern const char kCtrlKey[]; + extern const char kCurrentLocale[]; + extern const char kDefaultLocale[]; + extern const char kDescription[]; + extern const char kDevToolsPage[]; + extern const char kDisplayInLauncher[]; + extern const char kDisplayInNewTabPage[]; + extern const char kEventName[]; + extern const char kExcludeGlobs[]; + extern const char kExcludeMatches[]; + extern const char kExport[]; + extern const char kExternallyConnectable[]; + extern const char kFileAccessList[]; + extern const char kFileHandlers[]; + extern const char kFileHandlerExtensions[]; + extern const char kFileHandlerTitle[]; + extern const char kFileHandlerTypes[]; + extern const char kFileFilters[]; + extern const char kFileBrowserHandlers[]; + extern const char kMediaGalleriesHandlers[]; + extern const char kHomepageURL[]; + extern const char kIcons[]; + extern const char kId[]; + extern const char kImport[]; + extern const char kIncognito[]; + extern const char kIncludeGlobs[]; + extern const char kInputComponents[]; + extern const char kIntentDisposition[]; + extern const char kIntentHref[]; + extern const char kIntentPath[]; + extern const char kIntents[]; + extern const char kIntentTitle[]; + extern const char kIntentType[]; + extern const char kIsolation[]; + extern const char kJs[]; + extern const char kKey[]; + extern const char kKeycode[]; + extern const char kKioskEnabled[]; + extern const char kLanguage[]; + extern const char kLaunch[]; + extern const char kLaunchContainer[]; + extern const char kLaunchHeight[]; + extern const char kLaunchLocalPath[]; + extern const char kLaunchWebURL[]; + extern const char kLaunchWidth[]; + extern const char kLayouts[]; + extern const char kManifestVersion[]; + extern const char kMatches[]; + extern const char kMIMETypes[]; + extern const char kMimeTypesHandler[]; + extern const char kMinimumChromeVersion[]; + extern const char kMinimumVersion[]; + extern const char kNaClModules[]; + extern const char kNaClModulesMIMEType[]; + extern const char kNaClModulesPath[]; + extern const char kName[]; + extern const char kOAuth2[]; + extern const char kOAuth2AutoApprove[]; + extern const char kOAuth2ClientId[]; + extern const char kOAuth2Scopes[]; + extern const char kOfflineEnabled[]; + extern const char kOmnibox[]; + extern const char kOmniboxKeyword[]; + extern const char kOptionalPermissions[]; + extern const char kOptionsPage[]; + extern const char kPageAction[]; + extern const char kPageActionDefaultIcon[]; + extern const char kPageActionDefaultPopup[]; + extern const char kPageActionDefaultTitle[]; + extern const char kPageActionIcons[]; + extern const char kPageActionId[]; + extern const char kPageActionPopup[]; + extern const char kPageActionPopupPath[]; + extern const char kPageActions[]; + extern const char kPermissions[]; + extern const char kPlatformAppBackground[]; + extern const char kPlatformAppBackgroundPage[]; + extern const char kPlatformAppBackgroundScripts[]; + extern const char kPlatformAppContentSecurityPolicy[]; + extern const char kPlugins[]; + extern const char kPluginsPath[]; + extern const char kPluginsPublic[]; + extern const char kPublicKey[]; + extern const char kResources[]; + extern const char kRequirements[]; + extern const char kRunAt[]; + extern const char kSandboxedPages[]; + extern const char kSandboxedPagesCSP[]; + extern const char kScriptBadge[]; + extern const char kShiftKey[]; + extern const char kShortcutKey[]; + extern const char kSignature[]; + extern const char kSpellcheck[]; + extern const char kSpellcheckDictionaryFormat[]; + extern const char kSpellcheckDictionaryLanguage[]; + extern const char kSpellcheckDictionaryLocale[]; + extern const char kSpellcheckDictionaryPath[]; + extern const char kStorageManagedSchema[]; + extern const char kSuggestedKey[]; + extern const char kSystemIndicator[]; + extern const char kTheme[]; + extern const char kThemeColors[]; + extern const char kThemeDisplayProperties[]; + extern const char kThemeImages[]; + extern const char kThemeTints[]; + extern const char kTtsEngine[]; + extern const char kTtsGenderFemale[]; + extern const char kTtsGenderMale[]; + extern const char kTtsVoices[]; + extern const char kTtsVoicesEventTypeEnd[]; + extern const char kTtsVoicesEventTypeError[]; + extern const char kTtsVoicesEventTypeMarker[]; + extern const char kTtsVoicesEventTypeSentence[]; + extern const char kTtsVoicesEventTypeStart[]; + extern const char kTtsVoicesEventTypeWord[]; + extern const char kTtsVoicesEventTypes[]; + extern const char kTtsVoicesGender[]; + extern const char kTtsVoicesLang[]; + extern const char kTtsVoicesVoiceName[]; + extern const char kType[]; + extern const char kUpdateURL[]; + extern const char kVersion[]; + extern const char kWebAccessibleResources[]; + extern const char kWebURLs[]; +} // namespace manifest_keys + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_MANIFEST_CONSTANTS_H_ diff --git a/chromium/extensions/common/matcher/DEPS b/chromium/extensions/common/matcher/DEPS new file mode 100644 index 00000000000..972b087be35 --- /dev/null +++ b/chromium/extensions/common/matcher/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+third_party/re2" +] diff --git a/chromium/extensions/common/matcher/OWNERS b/chromium/extensions/common/matcher/OWNERS new file mode 100644 index 00000000000..d5f125f04e3 --- /dev/null +++ b/chromium/extensions/common/matcher/OWNERS @@ -0,0 +1 @@ +battre@chromium.org diff --git a/chromium/extensions/common/matcher/regex_set_matcher.cc b/chromium/extensions/common/matcher/regex_set_matcher.cc new file mode 100644 index 00000000000..24d99efa3ff --- /dev/null +++ b/chromium/extensions/common/matcher/regex_set_matcher.cc @@ -0,0 +1,113 @@ +// 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 "extensions/common/matcher/regex_set_matcher.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "extensions/common/matcher/substring_set_matcher.h" +#include "third_party/re2/re2/filtered_re2.h" +#include "third_party/re2/re2/re2.h" + +namespace extensions { + +RegexSetMatcher::RegexSetMatcher() {} + +RegexSetMatcher::~RegexSetMatcher() { + DeleteSubstringPatterns(); +} + +void RegexSetMatcher::AddPatterns( + const std::vector<const StringPattern*>& regex_list) { + if (regex_list.empty()) + return; + for (size_t i = 0; i < regex_list.size(); ++i) { + regexes_[regex_list[i]->id()] = regex_list[i]; + } + + RebuildMatcher(); +} + +void RegexSetMatcher::ClearPatterns() { + regexes_.clear(); + RebuildMatcher(); +} + +bool RegexSetMatcher::Match(const std::string& text, + std::set<StringPattern::ID>* matches) const { + size_t old_number_of_matches = matches->size(); + if (regexes_.empty()) + return false; + if (!filtered_re2_.get()) { + LOG(ERROR) << "RegexSetMatcher was not initialized"; + return false; + } + + // FilteredRE2 expects lowercase for prefiltering, but we still + // match case-sensitively. + std::vector<RE2ID> atoms(FindSubstringMatches( + StringToLowerASCII(text))); + + std::vector<RE2ID> re2_ids; + filtered_re2_->AllMatches(text, atoms, &re2_ids); + + for (size_t i = 0; i < re2_ids.size(); ++i) { + StringPattern::ID id = re2_id_map_[re2_ids[i]]; + matches->insert(id); + } + return old_number_of_matches != matches->size(); +} + +bool RegexSetMatcher::IsEmpty() const { + return regexes_.empty(); +} + +std::vector<RegexSetMatcher::RE2ID> RegexSetMatcher::FindSubstringMatches( + const std::string& text) const { + std::set<int> atoms_set; + substring_matcher_->Match(text, &atoms_set); + return std::vector<RE2ID>(atoms_set.begin(), atoms_set.end()); +} + +void RegexSetMatcher::RebuildMatcher() { + re2_id_map_.clear(); + filtered_re2_.reset(new re2::FilteredRE2()); + if (regexes_.empty()) + return; + + for (RegexMap::iterator it = regexes_.begin(); it != regexes_.end(); ++it) { + RE2ID re2_id; + RE2::ErrorCode error = filtered_re2_->Add( + it->second->pattern(), RE2::DefaultOptions, &re2_id); + if (error == RE2::NoError) { + DCHECK_EQ(static_cast<RE2ID>(re2_id_map_.size()), re2_id); + re2_id_map_.push_back(it->first); + } else { + // Unparseable regexes should have been rejected already in + // URLMatcherFactory::CreateURLMatchesCondition. + LOG(ERROR) << "Could not parse regex (id=" << it->first << ", " + << it->second->pattern() << ")"; + } + } + + std::vector<std::string> strings_to_match; + filtered_re2_->Compile(&strings_to_match); + + substring_matcher_.reset(new SubstringSetMatcher); + DeleteSubstringPatterns(); + // Build SubstringSetMatcher from |strings_to_match|. + // SubstringSetMatcher doesn't own its strings. + for (size_t i = 0; i < strings_to_match.size(); ++i) { + substring_patterns_.push_back( + new StringPattern(strings_to_match[i], i)); + } + substring_matcher_->RegisterPatterns(substring_patterns_); +} + +void RegexSetMatcher::DeleteSubstringPatterns() { + STLDeleteElements(&substring_patterns_); +} + +} // namespace extensions diff --git a/chromium/extensions/common/matcher/regex_set_matcher.h b/chromium/extensions/common/matcher/regex_set_matcher.h new file mode 100644 index 00000000000..906ab9ca3ee --- /dev/null +++ b/chromium/extensions/common/matcher/regex_set_matcher.h @@ -0,0 +1,82 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_MATCHER_REGEX_SET_MATCHER_H_ +#define EXTENSIONS_COMMON_MATCHER_REGEX_SET_MATCHER_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "extensions/common/matcher/string_pattern.h" +#include "extensions/common/matcher/substring_set_matcher.h" + +namespace re2 { +class FilteredRE2; +} + +namespace extensions { + +// Efficiently matches URLs against a collection of regular expressions, +// using FilteredRE2 to reduce the number of regexes that must be matched +// by pre-filtering with substring matching. See: +// http://swtch.com/~rsc/regexp/regexp3.html#analysis +class RegexSetMatcher { + public: + RegexSetMatcher(); + virtual ~RegexSetMatcher(); + + // Adds the regex patterns in |regex_list| to the matcher. Also rebuilds + // the FilteredRE2 matcher; thus, for efficiency, prefer adding multiple + // patterns at once. + // Ownership of the patterns remains with the caller. + void AddPatterns(const std::vector<const StringPattern*>& regex_list); + + // Removes all regex patterns. + void ClearPatterns(); + + // Appends the IDs of regular expressions in our set that match the |text| + // to |matches|. + bool Match(const std::string& text, + std::set<StringPattern::ID>* matches) const; + + bool IsEmpty() const; + + private: + typedef int RE2ID; + typedef std::map<StringPattern::ID, const StringPattern*> RegexMap; + typedef std::vector<StringPattern::ID> RE2IDMap; + + // Use Aho-Corasick SubstringSetMatcher to find which literal patterns + // match the |text|. + std::vector<RE2ID> FindSubstringMatches(const std::string& text) const; + + // Rebuild FilteredRE2 from scratch. Needs to be called whenever + // our set of regexes changes. + // TODO(yoz): investigate if it could be done incrementally; + // apparently not supported by FilteredRE2. + void RebuildMatcher(); + + // Clean up StringPatterns in |substring_patterns_|. + void DeleteSubstringPatterns(); + + // Mapping of regex StringPattern::IDs to regexes. + RegexMap regexes_; + // Mapping of RE2IDs from FilteredRE2 (which are assigned in order) + // to regex StringPattern::IDs. + RE2IDMap re2_id_map_; + + scoped_ptr<re2::FilteredRE2> filtered_re2_; + scoped_ptr<SubstringSetMatcher> substring_matcher_; + + // The substring patterns from FilteredRE2, which are used in + // |substring_matcher_| but whose lifetime is managed here. + std::vector<const StringPattern*> substring_patterns_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_MATCHER_REGEX_SET_MATCHER_H_ diff --git a/chromium/extensions/common/matcher/regex_set_matcher_unittest.cc b/chromium/extensions/common/matcher/regex_set_matcher_unittest.cc new file mode 100644 index 00000000000..49d312a1401 --- /dev/null +++ b/chromium/extensions/common/matcher/regex_set_matcher_unittest.cc @@ -0,0 +1,61 @@ +// 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 "extensions/common/matcher/regex_set_matcher.h" + +#include <set> + +#include "base/stl_util.h" +#include "url/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" + +using extensions::StringPattern; +using extensions::RegexSetMatcher; + +TEST(RegexSetMatcherTest, MatchRegexes) { + StringPattern pattern_1("ab.*c", 42); + StringPattern pattern_2("f*f", 17); + StringPattern pattern_3("c(ar|ra)b|brac", 239); + std::vector<const StringPattern*> regexes; + regexes.push_back(&pattern_1); + regexes.push_back(&pattern_2); + regexes.push_back(&pattern_3); + RegexSetMatcher matcher; + matcher.AddPatterns(regexes); + + std::set<StringPattern::ID> result1; + matcher.Match("http://abracadabra.com", &result1); + EXPECT_EQ(2U, result1.size()); + EXPECT_TRUE(ContainsKey(result1, 42)); + EXPECT_TRUE(ContainsKey(result1, 239)); + + std::set<StringPattern::ID> result2; + matcher.Match("https://abfffffffffffffffffffffffffffffff.fi/cf", &result2); + EXPECT_EQ(2U, result2.size()); + EXPECT_TRUE(ContainsKey(result2, 17)); + EXPECT_TRUE(ContainsKey(result2, 42)); + + std::set<StringPattern::ID> result3; + matcher.Match("http://nothing.com/", &result3); + EXPECT_EQ(0U, result3.size()); +} + +TEST(RegexSetMatcherTest, CaseSensitivity) { + StringPattern pattern_1("AAA", 51); + StringPattern pattern_2("aaA", 57); + std::vector<const StringPattern*> regexes; + regexes.push_back(&pattern_1); + regexes.push_back(&pattern_2); + RegexSetMatcher matcher; + matcher.AddPatterns(regexes); + + std::set<StringPattern::ID> result1; + matcher.Match("http://aaa.net/", &result1); + EXPECT_EQ(0U, result1.size()); + + std::set<StringPattern::ID> result2; + matcher.Match("http://aaa.net/quaaACK", &result2); + EXPECT_EQ(1U, result2.size()); + EXPECT_TRUE(ContainsKey(result2, 57)); +} diff --git a/chromium/extensions/common/matcher/string_pattern.cc b/chromium/extensions/common/matcher/string_pattern.cc new file mode 100644 index 00000000000..4e5c350c45a --- /dev/null +++ b/chromium/extensions/common/matcher/string_pattern.cc @@ -0,0 +1,20 @@ +// 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 "extensions/common/matcher/string_pattern.h" + +namespace extensions { + +StringPattern::StringPattern(const std::string& pattern, + StringPattern::ID id) + : pattern_(pattern), id_(id) {} + +StringPattern::~StringPattern() {} + +bool StringPattern::operator<(const StringPattern& rhs) const { + if (id_ != rhs.id_) return id_ < rhs.id_; + return pattern_ < rhs.pattern_; +} + +} // namespace extensions diff --git a/chromium/extensions/common/matcher/string_pattern.h b/chromium/extensions/common/matcher/string_pattern.h new file mode 100644 index 00000000000..1781b032833 --- /dev/null +++ b/chromium/extensions/common/matcher/string_pattern.h @@ -0,0 +1,42 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_MATCHER_STRING_PATTERN_H_ +#define EXTENSIONS_COMMON_MATCHER_STRING_PATTERN_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" + +namespace extensions { + +// An individual pattern of a substring or regex matcher. A pattern consists of +// a string (interpreted as individual bytes, no character encoding) and an +// identifier. +// IDs are returned to the caller of SubstringSetMatcher::Match() or +// RegexMatcher::MatchURL() to help the caller to figure out what +// patterns matched a string. All patterns registered to a matcher +// need to contain unique IDs. +class StringPattern { + public: + typedef int ID; + + StringPattern(const std::string& pattern, ID id); + ~StringPattern(); + const std::string& pattern() const { return pattern_; } + ID id() const { return id_; } + + bool operator<(const StringPattern& rhs) const; + + private: + std::string pattern_; + ID id_; + + DISALLOW_COPY_AND_ASSIGN(StringPattern); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_MATCHER_STRING_PATTERN_H_ diff --git a/chromium/extensions/common/matcher/string_pattern_unittest.cc b/chromium/extensions/common/matcher/string_pattern_unittest.cc new file mode 100644 index 00000000000..6e7e4bfbcfe --- /dev/null +++ b/chromium/extensions/common/matcher/string_pattern_unittest.cc @@ -0,0 +1,23 @@ +// 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 "extensions/common/matcher/string_pattern.h" + +#include <string> + +#include "testing/gtest/include/gtest/gtest.h" + +using extensions::StringPattern; + +TEST(StringPatternTest, StringPattern) { + StringPattern r1("Test", 2); + EXPECT_EQ("Test", r1.pattern()); + EXPECT_EQ(2, r1.id()); + + EXPECT_FALSE(r1 < r1); + StringPattern r2("Test", 3); + EXPECT_TRUE(r1 < r2); + StringPattern r3("ZZZZ", 2); + EXPECT_TRUE(r1 < r3); +} diff --git a/chromium/extensions/common/matcher/substring_set_matcher.cc b/chromium/extensions/common/matcher/substring_set_matcher.cc new file mode 100644 index 00000000000..91ac718b04c --- /dev/null +++ b/chromium/extensions/common/matcher/substring_set_matcher.cc @@ -0,0 +1,272 @@ +// 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 "extensions/common/matcher/substring_set_matcher.h" + +#include <algorithm> +#include <queue> + +#include "base/logging.h" +#include "base/stl_util.h" + +namespace extensions { + +namespace { + +// Compare StringPattern instances based on their string patterns. +bool ComparePatterns(const StringPattern* a, const StringPattern* b) { + return a->pattern() < b->pattern(); +} + +// Given the set of patterns, compute how many nodes will the corresponding +// Aho-Corasick tree have. Note that |patterns| need to be sorted. +uint32 TreeSize(const std::vector<const StringPattern*>& patterns) { + uint32 result = 1u; // 1 for the root node. + if (patterns.empty()) + return result; + + std::vector<const StringPattern*>::const_iterator last = patterns.begin(); + std::vector<const StringPattern*>::const_iterator current = last + 1; + // For the first pattern, each letter is a label of an edge to a new node. + result += (*last)->pattern().size(); + + // For the subsequent patterns, only count the edges which were not counted + // yet. For this it suffices to test against the previous pattern, because the + // patterns are sorted. + for (; current != patterns.end(); ++last, ++current) { + const std::string& last_pattern = (*last)->pattern(); + const std::string& current_pattern = (*current)->pattern(); + const uint32 prefix_bound = + std::min(last_pattern.size(), current_pattern.size()); + + uint32 common_prefix = 0; + while (common_prefix < prefix_bound && + last_pattern[common_prefix] == current_pattern[common_prefix]) + ++common_prefix; + result += current_pattern.size() - common_prefix; + } + return result; +} + +} // namespace + +// +// SubstringSetMatcher +// + +SubstringSetMatcher::SubstringSetMatcher() { + RebuildAhoCorasickTree(SubstringPatternVector()); +} + +SubstringSetMatcher::~SubstringSetMatcher() {} + +void SubstringSetMatcher::RegisterPatterns( + const std::vector<const StringPattern*>& patterns) { + RegisterAndUnregisterPatterns(patterns, + std::vector<const StringPattern*>()); +} + +void SubstringSetMatcher::UnregisterPatterns( + const std::vector<const StringPattern*>& patterns) { + RegisterAndUnregisterPatterns(std::vector<const StringPattern*>(), + patterns); +} + +void SubstringSetMatcher::RegisterAndUnregisterPatterns( + const std::vector<const StringPattern*>& to_register, + const std::vector<const StringPattern*>& to_unregister) { + // Register patterns. + for (std::vector<const StringPattern*>::const_iterator i = + to_register.begin(); i != to_register.end(); ++i) { + DCHECK(patterns_.find((*i)->id()) == patterns_.end()); + patterns_[(*i)->id()] = *i; + } + + // Unregister patterns + for (std::vector<const StringPattern*>::const_iterator i = + to_unregister.begin(); i != to_unregister.end(); ++i) { + patterns_.erase((*i)->id()); + } + + // Now we compute the total number of tree nodes needed. + SubstringPatternVector sorted_patterns; + sorted_patterns.resize(patterns_.size()); + + size_t next = 0; + for (SubstringPatternMap::const_iterator i = patterns_.begin(); + i != patterns_.end(); + ++i, ++next) { + sorted_patterns[next] = i->second; + } + + std::sort(sorted_patterns.begin(), sorted_patterns.end(), ComparePatterns); + tree_.reserve(TreeSize(sorted_patterns)); + + RebuildAhoCorasickTree(sorted_patterns); +} + +bool SubstringSetMatcher::Match(const std::string& text, + std::set<StringPattern::ID>* matches) const { + const size_t old_number_of_matches = matches->size(); + + // Handle patterns matching the empty string. + matches->insert(tree_[0].matches().begin(), tree_[0].matches().end()); + + uint32 current_node = 0; + for (std::string::const_iterator i = text.begin(); i != text.end(); ++i) { + uint32 edge_from_current = tree_[current_node].GetEdge(*i); + while (edge_from_current == AhoCorasickNode::kNoSuchEdge && + current_node != 0) { + current_node = tree_[current_node].failure(); + edge_from_current = tree_[current_node].GetEdge(*i); + } + if (edge_from_current != AhoCorasickNode::kNoSuchEdge) { + current_node = edge_from_current; + matches->insert(tree_[current_node].matches().begin(), + tree_[current_node].matches().end()); + } else { + DCHECK_EQ(0u, current_node); + } + } + + return old_number_of_matches != matches->size(); +} + +bool SubstringSetMatcher::IsEmpty() const { + // An empty tree consists of only the root node. + return patterns_.empty() && tree_.size() == 1u; +} + +void SubstringSetMatcher::RebuildAhoCorasickTree( + const SubstringPatternVector& sorted_patterns) { + tree_.clear(); + + // Initialize root note of tree. + AhoCorasickNode root; + root.set_failure(0); + tree_.push_back(root); + + // Insert all patterns. + for (SubstringPatternVector::const_iterator i = sorted_patterns.begin(); + i != sorted_patterns.end(); + ++i) { + InsertPatternIntoAhoCorasickTree(*i); + } + + CreateFailureEdges(); +} + +void SubstringSetMatcher::InsertPatternIntoAhoCorasickTree( + const StringPattern* pattern) { + const std::string& text = pattern->pattern(); + const std::string::const_iterator text_end = text.end(); + + // Iterators on the tree and the text. + uint32 current_node = 0; + std::string::const_iterator i = text.begin(); + + // Follow existing paths for as long as possible. + while (i != text_end) { + uint32 edge_from_current = tree_[current_node].GetEdge(*i); + if (edge_from_current == AhoCorasickNode::kNoSuchEdge) + break; + current_node = edge_from_current; + ++i; + } + + // Create new nodes if necessary. + while (i != text_end) { + tree_.push_back(AhoCorasickNode()); + tree_[current_node].SetEdge(*i, tree_.size() - 1); + current_node = tree_.size() - 1; + ++i; + } + + // Register match. + tree_[current_node].AddMatch(pattern->id()); +} + +void SubstringSetMatcher::CreateFailureEdges() { + typedef AhoCorasickNode::Edges Edges; + + std::queue<uint32> queue; + + AhoCorasickNode& root = tree_[0]; + root.set_failure(0); + const Edges& root_edges = root.edges(); + for (Edges::const_iterator e = root_edges.begin(); e != root_edges.end(); + ++e) { + const uint32& leads_to = e->second; + tree_[leads_to].set_failure(0); + queue.push(leads_to); + } + + while (!queue.empty()) { + AhoCorasickNode& current_node = tree_[queue.front()]; + queue.pop(); + for (Edges::const_iterator e = current_node.edges().begin(); + e != current_node.edges().end(); ++e) { + const char& edge_label = e->first; + const uint32& leads_to = e->second; + queue.push(leads_to); + + uint32 failure = current_node.failure(); + uint32 edge_from_failure = tree_[failure].GetEdge(edge_label); + while (edge_from_failure == AhoCorasickNode::kNoSuchEdge && + failure != 0) { + failure = tree_[failure].failure(); + edge_from_failure = tree_[failure].GetEdge(edge_label); + } + + const uint32 follow_in_case_of_failure = + edge_from_failure != AhoCorasickNode::kNoSuchEdge + ? edge_from_failure + : 0; + tree_[leads_to].set_failure(follow_in_case_of_failure); + tree_[leads_to].AddMatches(tree_[follow_in_case_of_failure].matches()); + } + } +} + +const uint32 SubstringSetMatcher::AhoCorasickNode::kNoSuchEdge = ~0; + +SubstringSetMatcher::AhoCorasickNode::AhoCorasickNode() + : failure_(kNoSuchEdge) {} + +SubstringSetMatcher::AhoCorasickNode::~AhoCorasickNode() {} + +SubstringSetMatcher::AhoCorasickNode::AhoCorasickNode( + const SubstringSetMatcher::AhoCorasickNode& other) + : edges_(other.edges_), + failure_(other.failure_), + matches_(other.matches_) {} + +SubstringSetMatcher::AhoCorasickNode& +SubstringSetMatcher::AhoCorasickNode::operator=( + const SubstringSetMatcher::AhoCorasickNode& other) { + edges_ = other.edges_; + failure_ = other.failure_; + matches_ = other.matches_; + return *this; +} + +uint32 SubstringSetMatcher::AhoCorasickNode::GetEdge(char c) const { + Edges::const_iterator i = edges_.find(c); + return i == edges_.end() ? kNoSuchEdge : i->second; +} + +void SubstringSetMatcher::AhoCorasickNode::SetEdge(char c, uint32 node) { + edges_[c] = node; +} + +void SubstringSetMatcher::AhoCorasickNode::AddMatch(StringPattern::ID id) { + matches_.insert(id); +} + +void SubstringSetMatcher::AhoCorasickNode::AddMatches( + const SubstringSetMatcher::AhoCorasickNode::Matches& matches) { + matches_.insert(matches.begin(), matches.end()); +} + +} // namespace extensions diff --git a/chromium/extensions/common/matcher/substring_set_matcher.h b/chromium/extensions/common/matcher/substring_set_matcher.h new file mode 100644 index 00000000000..610efc0fc22 --- /dev/null +++ b/chromium/extensions/common/matcher/substring_set_matcher.h @@ -0,0 +1,140 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_MATCHER_SUBSTRING_SET_MATCHER_H_ +#define EXTENSIONS_COMMON_MATCHER_SUBSTRING_SET_MATCHER_H_ + +#include <limits> +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "extensions/common/matcher/string_pattern.h" + +namespace extensions { + +// Class that store a set of string patterns and can find for a string S, +// which string patterns occur in S. +class SubstringSetMatcher { + public: + SubstringSetMatcher(); + ~SubstringSetMatcher(); + + // Registers all |patterns|. The ownership remains with the caller. + // The same pattern cannot be registered twice and each pattern needs to have + // a unique ID. + // Ownership of the patterns remains with the caller. + void RegisterPatterns(const std::vector<const StringPattern*>& patterns); + + // Unregisters the passed |patterns|. + void UnregisterPatterns(const std::vector<const StringPattern*>& patterns); + + // Analogous to RegisterPatterns and UnregisterPatterns but executes both + // operations in one step, which is cheaper in the execution. + void RegisterAndUnregisterPatterns( + const std::vector<const StringPattern*>& to_register, + const std::vector<const StringPattern*>& to_unregister); + + // Matches |text| against all registered StringPatterns. Stores the IDs + // of matching patterns in |matches|. |matches| is not cleared before adding + // to it. + bool Match(const std::string& text, + std::set<StringPattern::ID>* matches) const; + + // Returns true if this object retains no allocated data. Only for debugging. + bool IsEmpty() const; + + private: + // A node of an Aho Corasick Tree. This is implemented according to + // http://www.cs.uku.fi/~kilpelai/BSA05/lectures/slides04.pdf + // + // The algorithm is based on the idea of building a trie of all registered + // patterns. Each node of the tree is annotated with a set of pattern + // IDs that are used to report matches. + // + // The root of the trie represents an empty match. If we were looking whether + // any registered pattern matches a text at the beginning of the text (i.e. + // whether any pattern is a prefix of the text), we could just follow + // nodes in the trie according to the matching characters in the text. + // E.g., if text == "foobar", we would follow the trie from the root node + // to its child labeled 'f', from there to child 'o', etc. In this process we + // would report all pattern IDs associated with the trie nodes as matches. + // + // As we are not looking for all prefix matches but all substring matches, + // this algorithm would need to compare text.substr(0), text.substr(1), ... + // against the trie, which is in O(|text|^2). + // + // The Aho Corasick algorithm improves this runtime by using failure edges. + // In case we have found a partial match of length k in the text + // (text[i, ..., i + k - 1]) in the trie starting at the root and ending at + // a node at depth k, but cannot find a match in the trie for character + // text[i + k] at depth k + 1, we follow a failure edge. This edge + // corresponds to the longest proper suffix of text[i, ..., i + k - 1] that + // is a prefix of any registered pattern. + // + // If your brain thinks "Forget it, let's go shopping.", don't worry. + // Take a nap and read an introductory text on the Aho Corasick algorithm. + // It will make sense. Eventually. + class AhoCorasickNode { + public: + // Key: label of the edge, value: node index in |tree_| of parent class. + typedef std::map<char, uint32> Edges; + typedef std::set<StringPattern::ID> Matches; + + static const uint32 kNoSuchEdge; // Represents an invalid node index. + + AhoCorasickNode(); + ~AhoCorasickNode(); + AhoCorasickNode(const AhoCorasickNode& other); + AhoCorasickNode& operator=(const AhoCorasickNode& other); + + uint32 GetEdge(char c) const; + void SetEdge(char c, uint32 node); + const Edges& edges() const { return edges_; } + + uint32 failure() const { return failure_; } + void set_failure(uint32 failure) { failure_ = failure; } + + void AddMatch(StringPattern::ID id); + void AddMatches(const Matches& matches); + const Matches& matches() const { return matches_; } + + private: + // Outgoing edges of current node. + Edges edges_; + + // Node index that failure edge leads to. + uint32 failure_; + + // Identifiers of matches. + Matches matches_; + }; + + typedef std::map<StringPattern::ID, const StringPattern*> SubstringPatternMap; + typedef std::vector<const StringPattern*> SubstringPatternVector; + + // |sorted_patterns| is a copy of |patterns_| sorted by the pattern string. + void RebuildAhoCorasickTree(const SubstringPatternVector& sorted_patterns); + + // Inserts a path for |pattern->pattern()| into the tree and adds + // |pattern->id()| to the set of matches. Ownership of |pattern| remains with + // the caller. + void InsertPatternIntoAhoCorasickTree(const StringPattern* pattern); + void CreateFailureEdges(); + + // Set of all registered StringPatterns. Used to regenerate the + // Aho-Corasick tree in case patterns are registered or unregistered. + SubstringPatternMap patterns_; + + // The nodes of a Aho-Corasick tree. + std::vector<AhoCorasickNode> tree_; + + DISALLOW_COPY_AND_ASSIGN(SubstringSetMatcher); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_MATCHER_SUBSTRING_SET_MATCHER_H_ diff --git a/chromium/extensions/common/matcher/substring_set_matcher_unittest.cc b/chromium/extensions/common/matcher/substring_set_matcher_unittest.cc new file mode 100644 index 00000000000..fde65bf2dca --- /dev/null +++ b/chromium/extensions/common/matcher/substring_set_matcher_unittest.cc @@ -0,0 +1,167 @@ +// 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 "extensions/common/matcher/substring_set_matcher.h" + +#include <set> +#include <string> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" + +using extensions::StringPattern; +using extensions::SubstringSetMatcher; + +namespace { +void TestOnePattern(const std::string& test_string, + const std::string& pattern, + bool is_match) { + std::string test = + "TestOnePattern(" + test_string + ", " + pattern + ", " + + (is_match ? "1" : "0") + ")"; + std::vector<const StringPattern*> patterns; + StringPattern substring_pattern(pattern, 1); + patterns.push_back(&substring_pattern); + SubstringSetMatcher matcher; + matcher.RegisterPatterns(patterns); + std::set<int> matches; + matcher.Match(test_string, &matches); + + size_t expected_matches = (is_match ? 1 : 0); + EXPECT_EQ(expected_matches, matches.size()) << test; + EXPECT_EQ(is_match, matches.find(1) != matches.end()) << test; +} + +void TestTwoPatterns(const std::string& test_string, + const std::string& pattern_1, + const std::string& pattern_2, + bool is_match_1, + bool is_match_2) { + std::string test = + "TestTwoPatterns(" + test_string + ", " + pattern_1 + ", " + pattern_2 + + ", " + (is_match_1 ? "1" : "0") + ", " + (is_match_2 ? "1" : "0") + ")"; + StringPattern substring_pattern_1(pattern_1, 1); + StringPattern substring_pattern_2(pattern_2, 2); + // In order to make sure that the order in which patterns are registered + // does not make any difference we try both permutations. + for (int permutation = 0; permutation < 2; ++permutation) { + std::vector<const StringPattern*> patterns; + if (permutation == 0) { + patterns.push_back(&substring_pattern_1); + patterns.push_back(&substring_pattern_2); + } else { + patterns.push_back(&substring_pattern_2); + patterns.push_back(&substring_pattern_1); + } + SubstringSetMatcher matcher; + matcher.RegisterPatterns(patterns); + std::set<int> matches; + matcher.Match(test_string, &matches); + + size_t expected_matches = (is_match_1 ? 1 : 0) + (is_match_2 ? 1 : 0); + EXPECT_EQ(expected_matches, matches.size()) << test; + EXPECT_EQ(is_match_1, matches.find(1) != matches.end()) << test; + EXPECT_EQ(is_match_2, matches.find(2) != matches.end()) << test; + } +} +} + +TEST(SubstringSetMatcherTest, TestMatcher) { + // Test overlapping patterns + // String abcde + // Pattern 1 bc + // Pattern 2 cd + TestTwoPatterns("abcde", "bc", "cd", true, true); + + // Test subpatterns - part 1 + // String abcde + // Pattern 1 bc + // Pattern 2 b + TestTwoPatterns("abcde", "bc", "b", true, true); + + // Test subpatterns - part 2 + // String abcde + // Pattern 1 bc + // Pattern 2 c + TestTwoPatterns("abcde", "bc", "c", true, true); + + // Test identical matches + // String abcde + // Pattern 1 abcde + TestOnePattern("abcde", "abcde", true); + + // Test multiple matches + // String aaaaa + // Pattern 1 a + TestOnePattern("abcde", "a", true); + + // Test matches at beginning and end + // String abcde + // Pattern 1 ab + // Pattern 2 de + TestTwoPatterns("abcde", "ab", "de", true, true); + + // Test duplicate patterns with different IDs + // String abcde + // Pattern 1 bc + // Pattern 2 bc + TestTwoPatterns("abcde", "bc", "bc", true, true); + + // Test non-match + // String abcde + // Pattern 1 fg + TestOnePattern("abcde", "fg", false); + + // Test empty pattern and too long pattern + // String abcde + // Pattern 1 + // Pattern 2 abcdef + TestTwoPatterns("abcde", std::string(), "abcdef", true, false); +} + +TEST(SubstringSetMatcherTest, RegisterAndRemove) { + SubstringSetMatcher matcher; + + StringPattern pattern_1("a", 1); + StringPattern pattern_2("b", 2); + StringPattern pattern_3("c", 3); + + std::vector<const StringPattern*> patterns; + patterns.push_back(&pattern_1); + matcher.RegisterPatterns(patterns); + + patterns.clear(); + patterns.push_back(&pattern_2); + patterns.push_back(&pattern_3); + matcher.RegisterPatterns(patterns); + + std::set<int> matches; + matcher.Match("abd", &matches); + EXPECT_EQ(2u, matches.size()); + EXPECT_TRUE(matches.end() != matches.find(1)); + EXPECT_TRUE(matches.end() != matches.find(2)); + + patterns.clear(); + patterns.push_back(&pattern_2); + matcher.UnregisterPatterns(patterns); + + matches.clear(); + matcher.Match("abd", &matches); + EXPECT_EQ(1u, matches.size()); + EXPECT_TRUE(matches.end() != matches.find(1)); + EXPECT_TRUE(matches.end() == matches.find(2)); + + patterns.clear(); + patterns.push_back(&pattern_1); + patterns.push_back(&pattern_3); + matcher.UnregisterPatterns(patterns); + EXPECT_TRUE(matcher.IsEmpty()); +} + +TEST(SubstringSetMatcherTest, TestEmptyMatcher) { + SubstringSetMatcher matcher; + std::set<int> matches; + matcher.Match("abd", &matches); + EXPECT_TRUE(matches.empty()); +} diff --git a/chromium/extensions/common/matcher/url_matcher.cc b/chromium/extensions/common/matcher/url_matcher.cc new file mode 100644 index 00000000000..be3057a26b5 --- /dev/null +++ b/chromium/extensions/common/matcher/url_matcher.cc @@ -0,0 +1,885 @@ +// 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 "extensions/common/matcher/url_matcher.h" + +#include <algorithm> +#include <iterator> + +#include "base/logging.h" +#include "content/public/common/url_constants.h" +#include "url/gurl.h" +#include "url/url_canon.h" + +namespace extensions { + +// This set of classes implement a mapping of URL Component Patterns, such as +// host_prefix, host_suffix, host_equals, ..., etc., to StringPatterns +// for use in substring comparisons. +// +// The idea of this mapping is to reduce the problem of comparing many +// URL Component Patterns against one URL to the problem of searching many +// substrings in one string: +// +// ---------------------- ----------------- +// | URL Query operator | ----translate----> | StringPattern | +// ---------------------- ----------------- +// ^ +// | +// compare +// | +// v +// ---------------------- ----------------- +// | URL to compare | | | +// | to all URL Query | ----translate----> | String | +// | operators | | | +// ---------------------- ----------------- +// +// The reason for this problem reduction is that there are efficient algorithms +// for searching many substrings in one string (see Aho-Corasick algorithm). +// +// Additionally, some of the same pieces are reused to implement regular +// expression comparisons. The FilteredRE2 implementation for matching many +// regular expressions against one string uses prefiltering, in which a set +// of substrings (derived from the regexes) are first searched for, to reduce +// the number of regular expressions to test; the prefiltering step also +// uses Aho-Corasick. +// +// Case 1: {host,path,query}_{prefix,suffix,equals} searches. +// ========================================================== +// +// For searches in this class, we normalize URLs as follows: +// +// Step 1: +// Remove scheme, port and segment from URL: +// -> http://www.example.com:8080/index.html?search=foo#first_match becomes +// www.example.com/index.html?search=foo +// +// We remove the scheme and port number because they can be checked later +// in a secondary filter step. We remove the segment (the #... part) because +// this is not guaranteed to be ASCII-7 encoded. +// +// Step 2: +// Translate URL to String and add the following position markers: +// - BU = Beginning of URL +// - ED = End of Domain +// - EP = End of Path +// - EU = End of URL +// Furthermore, the hostname is canonicalized to start with a ".". +// +// Position markers are represented as characters >127, which are therefore +// guaranteed not to be part of the ASCII-7 encoded URL character set. +// +// -> www.example.com/index.html?search=foo becomes +// BU .www.example.com ED /index.html EP ?search=foo EU +// +// -> www.example.com/index.html becomes +// BU .www.example.com ED /index.html EP EU +// +// Step 3: +// Translate URL Component Patterns as follows: +// +// host_prefix(prefix) = BU add_missing_dot_prefix(prefix) +// -> host_prefix("www.example") = BU .www.example +// +// host_suffix(suffix) = suffix ED +// -> host_suffix("example.com") = example.com ED +// -> host_suffix(".example.com") = .example.com ED +// +// host_equals(domain) = BU add_missing_dot_prefix(domain) ED +// -> host_equals("www.example.com") = BU .www.example.com ED +// +// Similarly for path query parameters ({path, query}_{prefix, suffix, equals}). +// +// With this, we can search the StringPatterns in the normalized URL. +// +// +// Case 2: url_{prefix,suffix,equals,contains} searches. +// ===================================================== +// +// Step 1: as above, except that +// - the scheme is not removed +// - the port is not removed if it is specified and does not match the default +// port for the given scheme. +// +// Step 2: +// Translate URL to String and add the following position markers: +// - BU = Beginning of URL +// - EU = End of URL +// +// -> http://www.example.com:8080/index.html?search=foo#first_match becomes +// BU http://www.example.com:8080/index.html?search=foo EU +// -> http://www.example.com:80/index.html?search=foo#first_match becomes +// BU http://www.example.com/index.html?search=foo EU +// +// url_prefix(prefix) = BU prefix +// -> url_prefix("http://www.example") = BU http://www.example +// +// url_contains(substring) = substring +// -> url_contains("index") = index +// +// +// Case 3: {host,path,query}_contains searches. +// ============================================ +// +// These kinds of searches are not supported directly but can be derived +// by a combination of a url_contains() query followed by an explicit test: +// +// host_contains(str) = url_contains(str) followed by test whether str occurs +// in host component of original URL. +// -> host_contains("example.co") = example.co +// followed by gurl.host().find("example.co"); +// +// [similarly for path_contains and query_contains]. +// +// +// Regular expression matching (url_matches searches) +// ================================================== +// +// This class also supports matching regular expressions (RE2 syntax) +// against full URLs, which are transformed as in case 2. + +namespace { + +bool IsRegexCriterion(URLMatcherCondition::Criterion criterion) { + return criterion == URLMatcherCondition::URL_MATCHES; +} + +bool IsOriginAndPathRegexCriterion(URLMatcherCondition::Criterion criterion) { + return criterion == URLMatcherCondition::ORIGIN_AND_PATH_MATCHES; +} + +} // namespace + +// +// URLMatcherCondition +// + +URLMatcherCondition::URLMatcherCondition() + : criterion_(HOST_PREFIX), + string_pattern_(NULL) {} + +URLMatcherCondition::~URLMatcherCondition() {} + +URLMatcherCondition::URLMatcherCondition( + Criterion criterion, + const StringPattern* string_pattern) + : criterion_(criterion), + string_pattern_(string_pattern) {} + +URLMatcherCondition::URLMatcherCondition(const URLMatcherCondition& rhs) + : criterion_(rhs.criterion_), + string_pattern_(rhs.string_pattern_) {} + +URLMatcherCondition& URLMatcherCondition::operator=( + const URLMatcherCondition& rhs) { + criterion_ = rhs.criterion_; + string_pattern_ = rhs.string_pattern_; + return *this; +} + +bool URLMatcherCondition::operator<(const URLMatcherCondition& rhs) const { + if (criterion_ < rhs.criterion_) return true; + if (criterion_ > rhs.criterion_) return false; + if (string_pattern_ != NULL && rhs.string_pattern_ != NULL) + return *string_pattern_ < *rhs.string_pattern_; + if (string_pattern_ == NULL && rhs.string_pattern_ != NULL) return true; + // Either string_pattern_ != NULL && rhs.string_pattern_ == NULL, + // or both are NULL. + return false; +} + +bool URLMatcherCondition::IsFullURLCondition() const { + // For these criteria the SubstringMatcher needs to be executed on the + // GURL that is canonicalized with + // URLMatcherConditionFactory::CanonicalizeURLForFullSearches. + switch (criterion_) { + case HOST_CONTAINS: + case PATH_CONTAINS: + case QUERY_CONTAINS: + case URL_PREFIX: + case URL_SUFFIX: + case URL_CONTAINS: + case URL_EQUALS: + return true; + default: + break; + } + return false; +} + +bool URLMatcherCondition::IsRegexCondition() const { + return IsRegexCriterion(criterion_); +} + +bool URLMatcherCondition::IsOriginAndPathRegexCondition() const { + return IsOriginAndPathRegexCriterion(criterion_); +} + +bool URLMatcherCondition::IsMatch( + const std::set<StringPattern::ID>& matching_patterns, + const GURL& url) const { + DCHECK(string_pattern_); + if (!ContainsKey(matching_patterns, string_pattern_->id())) + return false; + // The criteria HOST_CONTAINS, PATH_CONTAINS, QUERY_CONTAINS are based on + // a substring match on the raw URL. In case of a match, we need to verify + // that the match was found in the correct component of the URL. + switch (criterion_) { + case HOST_CONTAINS: + return url.host().find(string_pattern_->pattern()) != + std::string::npos; + case PATH_CONTAINS: + return url.path().find(string_pattern_->pattern()) != + std::string::npos; + case QUERY_CONTAINS: + return url.query().find(string_pattern_->pattern()) != + std::string::npos; + default: + break; + } + return true; +} + +// +// URLMatcherConditionFactory +// + +namespace { +// These are symbols that are not contained in 7-bit ASCII used in GURLs. +const char kBeginningOfURL[] = {static_cast<char>(-1), 0}; +const char kEndOfDomain[] = {static_cast<char>(-2), 0}; +const char kEndOfPath[] = {static_cast<char>(-3), 0}; +const char kEndOfURL[] = {static_cast<char>(-4), 0}; +} // namespace + +URLMatcherConditionFactory::URLMatcherConditionFactory() : id_counter_(0) {} + +URLMatcherConditionFactory::~URLMatcherConditionFactory() { + STLDeleteElements(&substring_pattern_singletons_); + STLDeleteElements(®ex_pattern_singletons_); + STLDeleteElements(&origin_and_path_regex_pattern_singletons_); +} + +std::string URLMatcherConditionFactory::CanonicalizeURLForComponentSearches( + const GURL& url) const { + return kBeginningOfURL + CanonicalizeHostname(url.host()) + kEndOfDomain + + url.path() + kEndOfPath + + (url.has_query() ? "?" + url.query() : std::string()) + kEndOfURL; +} + +URLMatcherCondition URLMatcherConditionFactory::CreateHostPrefixCondition( + const std::string& prefix) { + return CreateCondition(URLMatcherCondition::HOST_PREFIX, + kBeginningOfURL + CanonicalizeHostname(prefix)); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateHostSuffixCondition( + const std::string& suffix) { + return CreateCondition(URLMatcherCondition::HOST_SUFFIX, + suffix + kEndOfDomain); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateHostContainsCondition( + const std::string& str) { + return CreateCondition(URLMatcherCondition::HOST_CONTAINS, str); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateHostEqualsCondition( + const std::string& str) { + return CreateCondition(URLMatcherCondition::HOST_EQUALS, + kBeginningOfURL + CanonicalizeHostname(str) + kEndOfDomain); +} + +URLMatcherCondition URLMatcherConditionFactory::CreatePathPrefixCondition( + const std::string& prefix) { + return CreateCondition(URLMatcherCondition::PATH_PREFIX, + kEndOfDomain + prefix); +} + +URLMatcherCondition URLMatcherConditionFactory::CreatePathSuffixCondition( + const std::string& suffix) { + return CreateCondition(URLMatcherCondition::PATH_SUFFIX, suffix + kEndOfPath); +} + +URLMatcherCondition URLMatcherConditionFactory::CreatePathContainsCondition( + const std::string& str) { + return CreateCondition(URLMatcherCondition::PATH_CONTAINS, str); +} + +URLMatcherCondition URLMatcherConditionFactory::CreatePathEqualsCondition( + const std::string& str) { + return CreateCondition(URLMatcherCondition::PATH_EQUALS, + kEndOfDomain + str + kEndOfPath); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateQueryPrefixCondition( + const std::string& prefix) { + std::string pattern; + if (!prefix.empty() && prefix[0] == '?') + pattern = kEndOfPath + prefix; + else + pattern = kEndOfPath + ('?' + prefix); + + return CreateCondition(URLMatcherCondition::QUERY_PREFIX, pattern); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateQuerySuffixCondition( + const std::string& suffix) { + if (!suffix.empty() && suffix[0] == '?') { + return CreateQueryEqualsCondition(suffix); + } else { + return CreateCondition(URLMatcherCondition::QUERY_SUFFIX, + suffix + kEndOfURL); + } +} + +URLMatcherCondition URLMatcherConditionFactory::CreateQueryContainsCondition( + const std::string& str) { + if (!str.empty() && str[0] == '?') + return CreateQueryPrefixCondition(str); + else + return CreateCondition(URLMatcherCondition::QUERY_CONTAINS, str); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateQueryEqualsCondition( + const std::string& str) { + std::string pattern; + if (!str.empty() && str[0] == '?') + pattern = kEndOfPath + str + kEndOfURL; + else + pattern = kEndOfPath + ('?' + str) + kEndOfURL; + + return CreateCondition(URLMatcherCondition::QUERY_EQUALS, pattern); +} + +URLMatcherCondition + URLMatcherConditionFactory::CreateHostSuffixPathPrefixCondition( + const std::string& host_suffix, + const std::string& path_prefix) { + return CreateCondition(URLMatcherCondition::HOST_SUFFIX_PATH_PREFIX, + host_suffix + kEndOfDomain + path_prefix); +} + +URLMatcherCondition +URLMatcherConditionFactory::CreateHostEqualsPathPrefixCondition( + const std::string& host, + const std::string& path_prefix) { + return CreateCondition(URLMatcherCondition::HOST_EQUALS_PATH_PREFIX, + kBeginningOfURL + CanonicalizeHostname(host) + kEndOfDomain + + path_prefix); +} + +std::string URLMatcherConditionFactory::CanonicalizeURLForFullSearches( + const GURL& url) const { + GURL::Replacements replacements; + replacements.ClearPassword(); + replacements.ClearUsername(); + replacements.ClearRef(); + // Clear port if it is implicit from scheme. + if (url.has_port()) { + const std::string& port = url.scheme(); + if (url_canon::DefaultPortForScheme(port.c_str(), port.size()) == + url.EffectiveIntPort()) { + replacements.ClearPort(); + } + } + return kBeginningOfURL + url.ReplaceComponents(replacements).spec() + + kEndOfURL; +} + +static std::string CanonicalizeURLForRegexSearchesHelper( + const GURL& url, + bool clear_query) { + GURL::Replacements replacements; + replacements.ClearPassword(); + replacements.ClearUsername(); + replacements.ClearRef(); + if (clear_query) + replacements.ClearQuery(); + // Clear port if it is implicit from scheme. + if (url.has_port()) { + const std::string& port = url.scheme(); + if (url_canon::DefaultPortForScheme(port.c_str(), port.size()) == + url.EffectiveIntPort()) { + replacements.ClearPort(); + } + } + return url.ReplaceComponents(replacements).spec(); +} + +std::string URLMatcherConditionFactory::CanonicalizeURLForRegexSearches( + const GURL& url) const { + return CanonicalizeURLForRegexSearchesHelper(url, false); +} + +std::string +URLMatcherConditionFactory::CanonicalizeURLForOriginAndPathRegexSearches( + const GURL& url) const { + return CanonicalizeURLForRegexSearchesHelper(url, true); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateURLPrefixCondition( + const std::string& prefix) { + return CreateCondition(URLMatcherCondition::URL_PREFIX, + kBeginningOfURL + prefix); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateURLSuffixCondition( + const std::string& suffix) { + return CreateCondition(URLMatcherCondition::URL_SUFFIX, suffix + kEndOfURL); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateURLContainsCondition( + const std::string& str) { + return CreateCondition(URLMatcherCondition::URL_CONTAINS, str); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateURLEqualsCondition( + const std::string& str) { + return CreateCondition(URLMatcherCondition::URL_EQUALS, + kBeginningOfURL + str + kEndOfURL); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateURLMatchesCondition( + const std::string& regex) { + return CreateCondition(URLMatcherCondition::URL_MATCHES, regex); +} + +URLMatcherCondition +URLMatcherConditionFactory::CreateOriginAndPathMatchesCondition( + const std::string& regex) { + return CreateCondition(URLMatcherCondition::ORIGIN_AND_PATH_MATCHES, regex); +} + +void URLMatcherConditionFactory::ForgetUnusedPatterns( + const std::set<StringPattern::ID>& used_patterns) { + PatternSingletons::iterator i = substring_pattern_singletons_.begin(); + while (i != substring_pattern_singletons_.end()) { + if (ContainsKey(used_patterns, (*i)->id())) { + ++i; + } else { + delete *i; + substring_pattern_singletons_.erase(i++); + } + } + i = regex_pattern_singletons_.begin(); + while (i != regex_pattern_singletons_.end()) { + if (ContainsKey(used_patterns, (*i)->id())) { + ++i; + } else { + delete *i; + regex_pattern_singletons_.erase(i++); + } + } + i = origin_and_path_regex_pattern_singletons_.begin(); + while (i != origin_and_path_regex_pattern_singletons_.end()) { + if (ContainsKey(used_patterns, (*i)->id())) { + ++i; + } else { + delete *i; + origin_and_path_regex_pattern_singletons_.erase(i++); + } + } +} + +bool URLMatcherConditionFactory::IsEmpty() const { + return substring_pattern_singletons_.empty() && + regex_pattern_singletons_.empty() && + origin_and_path_regex_pattern_singletons_.empty(); +} + +URLMatcherCondition URLMatcherConditionFactory::CreateCondition( + URLMatcherCondition::Criterion criterion, + const std::string& pattern) { + StringPattern search_pattern(pattern, 0); + PatternSingletons* pattern_singletons = NULL; + if (IsRegexCriterion(criterion)) + pattern_singletons = ®ex_pattern_singletons_; + else if (IsOriginAndPathRegexCriterion(criterion)) + pattern_singletons = &origin_and_path_regex_pattern_singletons_; + else + pattern_singletons = &substring_pattern_singletons_; + + PatternSingletons::const_iterator iter = + pattern_singletons->find(&search_pattern); + + if (iter != pattern_singletons->end()) { + return URLMatcherCondition(criterion, *iter); + } else { + StringPattern* new_pattern = + new StringPattern(pattern, id_counter_++); + pattern_singletons->insert(new_pattern); + return URLMatcherCondition(criterion, new_pattern); + } +} + +std::string URLMatcherConditionFactory::CanonicalizeHostname( + const std::string& hostname) const { + if (!hostname.empty() && hostname[0] == '.') + return hostname; + else + return "." + hostname; +} + +bool URLMatcherConditionFactory::StringPatternPointerCompare::operator()( + StringPattern* lhs, + StringPattern* rhs) const { + if (lhs == NULL && rhs != NULL) return true; + if (lhs != NULL && rhs != NULL) + return lhs->pattern() < rhs->pattern(); + // Either both are NULL or only rhs is NULL. + return false; +} + +// +// URLMatcherSchemeFilter +// + +URLMatcherSchemeFilter::URLMatcherSchemeFilter(const std::string& filter) + : filters_(1) { + filters_.push_back(filter); +} + +URLMatcherSchemeFilter::URLMatcherSchemeFilter( + const std::vector<std::string>& filters) + : filters_(filters) {} + +URLMatcherSchemeFilter::~URLMatcherSchemeFilter() {} + +bool URLMatcherSchemeFilter::IsMatch(const GURL& url) const { + return std::find(filters_.begin(), filters_.end(), url.scheme()) != + filters_.end(); +} + +// +// URLMatcherPortFilter +// + +URLMatcherPortFilter::URLMatcherPortFilter( + const std::vector<URLMatcherPortFilter::Range>& ranges) + : ranges_(ranges) {} + +URLMatcherPortFilter::~URLMatcherPortFilter() {} + +bool URLMatcherPortFilter::IsMatch(const GURL& url) const { + int port = url.EffectiveIntPort(); + for (std::vector<Range>::const_iterator i = ranges_.begin(); + i != ranges_.end(); ++i) { + if (i->first <= port && port <= i->second) + return true; + } + return false; +} + +// static +URLMatcherPortFilter::Range URLMatcherPortFilter::CreateRange(int from, + int to) { + return Range(from, to); +} + +// static +URLMatcherPortFilter::Range URLMatcherPortFilter::CreateRange(int port) { + return Range(port, port); +} + +// +// URLMatcherConditionSet +// + +URLMatcherConditionSet::~URLMatcherConditionSet() {} + +URLMatcherConditionSet::URLMatcherConditionSet( + ID id, + const Conditions& conditions) + : id_(id), + conditions_(conditions) {} + +URLMatcherConditionSet::URLMatcherConditionSet( + ID id, + const Conditions& conditions, + scoped_ptr<URLMatcherSchemeFilter> scheme_filter, + scoped_ptr<URLMatcherPortFilter> port_filter) + : id_(id), + conditions_(conditions), + scheme_filter_(scheme_filter.Pass()), + port_filter_(port_filter.Pass()) {} + +bool URLMatcherConditionSet::IsMatch( + const std::set<StringPattern::ID>& matching_patterns, + const GURL& url) const { + for (Conditions::const_iterator i = conditions_.begin(); + i != conditions_.end(); ++i) { + if (!i->IsMatch(matching_patterns, url)) + return false; + } + if (scheme_filter_.get() && !scheme_filter_->IsMatch(url)) + return false; + if (port_filter_.get() && !port_filter_->IsMatch(url)) + return false; + return true; +} + +// +// URLMatcher +// + +URLMatcher::URLMatcher() {} + +URLMatcher::~URLMatcher() {} + +void URLMatcher::AddConditionSets( + const URLMatcherConditionSet::Vector& condition_sets) { + for (URLMatcherConditionSet::Vector::const_iterator i = + condition_sets.begin(); i != condition_sets.end(); ++i) { + DCHECK(url_matcher_condition_sets_.find((*i)->id()) == + url_matcher_condition_sets_.end()); + url_matcher_condition_sets_[(*i)->id()] = *i; + } + UpdateInternalDatastructures(); +} + +void URLMatcher::RemoveConditionSets( + const std::vector<URLMatcherConditionSet::ID>& condition_set_ids) { + for (std::vector<URLMatcherConditionSet::ID>::const_iterator i = + condition_set_ids.begin(); i != condition_set_ids.end(); ++i) { + DCHECK(url_matcher_condition_sets_.find(*i) != + url_matcher_condition_sets_.end()); + url_matcher_condition_sets_.erase(*i); + } + UpdateInternalDatastructures(); +} + +void URLMatcher::ClearUnusedConditionSets() { + UpdateConditionFactory(); +} + +std::set<URLMatcherConditionSet::ID> URLMatcher::MatchURL( + const GURL& url) const { + // Find all IDs of StringPatterns that match |url|. + // See URLMatcherConditionFactory for the canonicalization of URLs and the + // distinction between full url searches and url component searches. + std::set<StringPattern::ID> matches; + if (!full_url_matcher_.IsEmpty()) { + full_url_matcher_.Match( + condition_factory_.CanonicalizeURLForFullSearches(url), &matches); + } + if (!url_component_matcher_.IsEmpty()) { + url_component_matcher_.Match( + condition_factory_.CanonicalizeURLForComponentSearches(url), &matches); + } + if (!regex_set_matcher_.IsEmpty()) { + regex_set_matcher_.Match( + condition_factory_.CanonicalizeURLForRegexSearches(url), &matches); + } + if (!origin_and_path_regex_set_matcher_.IsEmpty()) { + origin_and_path_regex_set_matcher_.Match( + condition_factory_.CanonicalizeURLForOriginAndPathRegexSearches(url), + &matches); + } + + // Calculate all URLMatcherConditionSets for which all URLMatcherConditions + // were fulfilled. + std::set<URLMatcherConditionSet::ID> result; + for (std::set<StringPattern::ID>::const_iterator i = matches.begin(); + i != matches.end(); ++i) { + // For each URLMatcherConditionSet there is exactly one condition + // registered in substring_match_triggers_. This means that the following + // logic tests each URLMatcherConditionSet exactly once if it can be + // completely fulfilled. + StringPatternTriggers::const_iterator triggered_condition_sets_iter = + substring_match_triggers_.find(*i); + if (triggered_condition_sets_iter == substring_match_triggers_.end()) + continue; // Not all substring matches are triggers for a condition set. + const std::set<URLMatcherConditionSet::ID>& condition_sets = + triggered_condition_sets_iter->second; + for (std::set<URLMatcherConditionSet::ID>::const_iterator j = + condition_sets.begin(); j != condition_sets.end(); ++j) { + URLMatcherConditionSets::const_iterator condition_set_iter = + url_matcher_condition_sets_.find(*j); + DCHECK(condition_set_iter != url_matcher_condition_sets_.end()); + if (condition_set_iter->second->IsMatch(matches, url)) + result.insert(*j); + } + } + + return result; +} + +bool URLMatcher::IsEmpty() const { + return condition_factory_.IsEmpty() && + url_matcher_condition_sets_.empty() && + substring_match_triggers_.empty() && + full_url_matcher_.IsEmpty() && + url_component_matcher_.IsEmpty() && + regex_set_matcher_.IsEmpty() && + origin_and_path_regex_set_matcher_.IsEmpty() && + registered_full_url_patterns_.empty() && + registered_url_component_patterns_.empty(); +} + +void URLMatcher::UpdateSubstringSetMatcher(bool full_url_conditions) { + // The purpose of |full_url_conditions| is just that we need to execute + // the same logic once for Full URL searches and once for URL Component + // searches (see URLMatcherConditionFactory). + + // Determine which patterns need to be registered when this function + // terminates. + std::set<const StringPattern*> new_patterns; + for (URLMatcherConditionSets::const_iterator condition_set_iter = + url_matcher_condition_sets_.begin(); + condition_set_iter != url_matcher_condition_sets_.end(); + ++condition_set_iter) { + const URLMatcherConditionSet::Conditions& conditions = + condition_set_iter->second->conditions(); + for (URLMatcherConditionSet::Conditions::const_iterator condition_iter = + conditions.begin(); condition_iter != conditions.end(); + ++condition_iter) { + // If we are called to process Full URL searches, ignore others, and + // vice versa. (Regex conditions are updated in UpdateRegexSetMatcher.) + if (!condition_iter->IsRegexCondition() && + !condition_iter->IsOriginAndPathRegexCondition() && + full_url_conditions == condition_iter->IsFullURLCondition()) + new_patterns.insert(condition_iter->string_pattern()); + } + } + + // This is the set of patterns that were registered before this function + // is called. + std::set<const StringPattern*>& registered_patterns = + full_url_conditions ? registered_full_url_patterns_ + : registered_url_component_patterns_; + + // Add all patterns that are in new_patterns but not in registered_patterns. + std::vector<const StringPattern*> patterns_to_register; + std::set_difference( + new_patterns.begin(), new_patterns.end(), + registered_patterns.begin(), registered_patterns.end(), + std::back_inserter(patterns_to_register)); + + // Remove all patterns that are in registered_patterns but not in + // new_patterns. + std::vector<const StringPattern*> patterns_to_unregister; + std::set_difference( + registered_patterns.begin(), registered_patterns.end(), + new_patterns.begin(), new_patterns.end(), + std::back_inserter(patterns_to_unregister)); + + // Update the SubstringSetMatcher. + SubstringSetMatcher& url_matcher = + full_url_conditions ? full_url_matcher_ : url_component_matcher_; + url_matcher.RegisterAndUnregisterPatterns(patterns_to_register, + patterns_to_unregister); + + // Update the set of registered_patterns for the next time this function + // is being called. + registered_patterns.swap(new_patterns); +} + +void URLMatcher::UpdateRegexSetMatcher() { + std::vector<const StringPattern*> new_patterns; + std::vector<const StringPattern*> new_origin_and_path_patterns; + + for (URLMatcherConditionSets::const_iterator condition_set_iter = + url_matcher_condition_sets_.begin(); + condition_set_iter != url_matcher_condition_sets_.end(); + ++condition_set_iter) { + const URLMatcherConditionSet::Conditions& conditions = + condition_set_iter->second->conditions(); + for (URLMatcherConditionSet::Conditions::const_iterator condition_iter = + conditions.begin(); condition_iter != conditions.end(); + ++condition_iter) { + if (condition_iter->IsRegexCondition()) { + new_patterns.push_back(condition_iter->string_pattern()); + } else if (condition_iter->IsOriginAndPathRegexCondition()) { + new_origin_and_path_patterns.push_back( + condition_iter->string_pattern()); + } + } + } + + // Start over from scratch. We can't really do better than this, since the + // FilteredRE2 backend doesn't support incremental updates. + regex_set_matcher_.ClearPatterns(); + regex_set_matcher_.AddPatterns(new_patterns); + origin_and_path_regex_set_matcher_.ClearPatterns(); + origin_and_path_regex_set_matcher_.AddPatterns(new_origin_and_path_patterns); +} + +void URLMatcher::UpdateTriggers() { + // Count substring pattern frequencies. + std::map<StringPattern::ID, size_t> substring_pattern_frequencies; + for (URLMatcherConditionSets::const_iterator condition_set_iter = + url_matcher_condition_sets_.begin(); + condition_set_iter != url_matcher_condition_sets_.end(); + ++condition_set_iter) { + const URLMatcherConditionSet::Conditions& conditions = + condition_set_iter->second->conditions(); + for (URLMatcherConditionSet::Conditions::const_iterator condition_iter = + conditions.begin(); condition_iter != conditions.end(); + ++condition_iter) { + const StringPattern* pattern = condition_iter->string_pattern(); + substring_pattern_frequencies[pattern->id()]++; + } + } + + // Update trigger conditions: Determine for each URLMatcherConditionSet which + // URLMatcherCondition contains a StringPattern that occurs least + // frequently in this URLMatcher. We assume that this condition is very + // specific and occurs rarely in URLs. If a match occurs for this + // URLMatcherCondition, we want to test all other URLMatcherCondition in the + // respective URLMatcherConditionSet as well to see whether the entire + // URLMatcherConditionSet is considered matching. + substring_match_triggers_.clear(); + for (URLMatcherConditionSets::const_iterator condition_set_iter = + url_matcher_condition_sets_.begin(); + condition_set_iter != url_matcher_condition_sets_.end(); + ++condition_set_iter) { + const URLMatcherConditionSet::Conditions& conditions = + condition_set_iter->second->conditions(); + if (conditions.empty()) + continue; + URLMatcherConditionSet::Conditions::const_iterator condition_iter = + conditions.begin(); + StringPattern::ID trigger = condition_iter->string_pattern()->id(); + // We skip the first element in the following loop. + ++condition_iter; + for (; condition_iter != conditions.end(); ++condition_iter) { + StringPattern::ID current_id = + condition_iter->string_pattern()->id(); + if (substring_pattern_frequencies[trigger] > + substring_pattern_frequencies[current_id]) { + trigger = current_id; + } + } + substring_match_triggers_[trigger].insert(condition_set_iter->second->id()); + } +} + +void URLMatcher::UpdateConditionFactory() { + std::set<StringPattern::ID> used_patterns; + for (URLMatcherConditionSets::const_iterator condition_set_iter = + url_matcher_condition_sets_.begin(); + condition_set_iter != url_matcher_condition_sets_.end(); + ++condition_set_iter) { + const URLMatcherConditionSet::Conditions& conditions = + condition_set_iter->second->conditions(); + for (URLMatcherConditionSet::Conditions::const_iterator condition_iter = + conditions.begin(); condition_iter != conditions.end(); + ++condition_iter) { + used_patterns.insert(condition_iter->string_pattern()->id()); + } + } + condition_factory_.ForgetUnusedPatterns(used_patterns); +} + +void URLMatcher::UpdateInternalDatastructures() { + UpdateSubstringSetMatcher(false); + UpdateSubstringSetMatcher(true); + UpdateRegexSetMatcher(); + UpdateTriggers(); + UpdateConditionFactory(); +} + +} // namespace extensions diff --git a/chromium/extensions/common/matcher/url_matcher.h b/chromium/extensions/common/matcher/url_matcher.h new file mode 100644 index 00000000000..d93a6067013 --- /dev/null +++ b/chromium/extensions/common/matcher/url_matcher.h @@ -0,0 +1,355 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_MATCHER_URL_MATCHER_H_ +#define EXTENSIONS_COMMON_MATCHER_URL_MATCHER_H_ + +#include <set> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "extensions/common/matcher/regex_set_matcher.h" +#include "extensions/common/matcher/substring_set_matcher.h" + +class GURL; + +namespace base { +class DictionaryValue; +} + +namespace extensions { + +// This class represents a single URL matching condition, e.g. a match on the +// host suffix or the containment of a string in the query component of a GURL. +// +// The difference from a simple StringPattern is that this also supports +// checking whether the {Host, Path, Query} of a URL contains a string. The +// reduction of URL matching conditions to StringPatterns conducted by +// URLMatcherConditionFactory is not capable of expressing that alone. +// +// Also supported is matching regular expressions against the URL (URL_MATCHES). +class URLMatcherCondition { + public: + enum Criterion { + HOST_PREFIX, + HOST_SUFFIX, + HOST_CONTAINS, + HOST_EQUALS, + PATH_PREFIX, + PATH_SUFFIX, + PATH_CONTAINS, + PATH_EQUALS, + QUERY_PREFIX, + QUERY_SUFFIX, + QUERY_CONTAINS, + QUERY_EQUALS, + HOST_SUFFIX_PATH_PREFIX, + HOST_EQUALS_PATH_PREFIX, + URL_PREFIX, + URL_SUFFIX, + URL_CONTAINS, + URL_EQUALS, + URL_MATCHES, + ORIGIN_AND_PATH_MATCHES, // Matches the URL minus its query string. + }; + + URLMatcherCondition(); + ~URLMatcherCondition(); + URLMatcherCondition(Criterion criterion, + const StringPattern* substring_pattern); + URLMatcherCondition(const URLMatcherCondition& rhs); + URLMatcherCondition& operator=(const URLMatcherCondition& rhs); + bool operator<(const URLMatcherCondition& rhs) const; + + Criterion criterion() const { return criterion_; } + const StringPattern* string_pattern() const { + return string_pattern_; + } + + // Returns whether this URLMatcherCondition needs to be executed on a + // full URL rather than the individual components (see + // URLMatcherConditionFactory). + bool IsFullURLCondition() const; + + // Returns whether this URLMatcherCondition is a regular expression to be + // handled by a regex matcher instead of a substring matcher. + bool IsRegexCondition() const; + + // Returns whether this URLMatcherCondition is a regular expression that shall + // be evaluated on the URL without the query parameter. + bool IsOriginAndPathRegexCondition() const; + + // Returns whether this condition is fulfilled according to + // |matching_patterns| and |url|. + bool IsMatch(const std::set<StringPattern::ID>& matching_patterns, + const GURL& url) const; + + private: + // |criterion_| and |string_pattern_| describe together what property a URL + // needs to fulfill to be considered a match. + Criterion criterion_; + + // This is the StringPattern that is used in a SubstringSetMatcher. + const StringPattern* string_pattern_; +}; + +// Class to map the problem of finding {host, path, query} {prefixes, suffixes, +// containments, and equality} in GURLs to the substring matching problem. +// +// Say, you want to check whether the path of a URL starts with "/index.html". +// This class preprocesses a URL like "www.google.com/index.html" into something +// like "www.google.com|/index.html". After preprocessing, you can search for +// "|/index.html" in the string and see that this candidate URL actually has +// a path that starts with "/index.html". On the contrary, +// "www.google.com/images/index.html" would be normalized to +// "www.google.com|/images/index.html". It is easy to see that it contains +// "/index.html" but the path of the URL does not start with "/index.html". +// +// This preprocessing is important if you want to match a URL against many +// patterns because it reduces the matching to a "discover all substrings +// of a dictionary in a text" problem, which can be solved very efficiently +// by the Aho-Corasick algorithm. +// +// IMPORTANT: The URLMatcherConditionFactory owns the StringPattern +// referenced by created URLMatcherConditions. Therefore, it must outlive +// all created URLMatcherCondition and the SubstringSetMatcher. +class URLMatcherConditionFactory { + public: + URLMatcherConditionFactory(); + ~URLMatcherConditionFactory(); + + // Canonicalizes a URL for "Create{Host,Path,Query}*Condition" searches. + std::string CanonicalizeURLForComponentSearches(const GURL& url) const; + + // Factory methods for various condition types. + // + // Note that these methods fill the pattern_singletons_. If you create + // conditions and don't register them to a URLMatcher, they will continue to + // consume memory. You need to call ForgetUnusedPatterns() or + // URLMatcher::ClearUnusedConditionSets() in this case. + URLMatcherCondition CreateHostPrefixCondition(const std::string& prefix); + URLMatcherCondition CreateHostSuffixCondition(const std::string& suffix); + URLMatcherCondition CreateHostContainsCondition(const std::string& str); + URLMatcherCondition CreateHostEqualsCondition(const std::string& str); + + URLMatcherCondition CreatePathPrefixCondition(const std::string& prefix); + URLMatcherCondition CreatePathSuffixCondition(const std::string& suffix); + URLMatcherCondition CreatePathContainsCondition(const std::string& str); + URLMatcherCondition CreatePathEqualsCondition(const std::string& str); + + URLMatcherCondition CreateQueryPrefixCondition(const std::string& prefix); + URLMatcherCondition CreateQuerySuffixCondition(const std::string& suffix); + URLMatcherCondition CreateQueryContainsCondition(const std::string& str); + URLMatcherCondition CreateQueryEqualsCondition(const std::string& str); + + // This covers the common case, where you don't care whether a domain + // "foobar.com" is expressed as "foobar.com" or "www.foobar.com", and it + // should be followed by a given |path_prefix|. + URLMatcherCondition CreateHostSuffixPathPrefixCondition( + const std::string& host_suffix, + const std::string& path_prefix); + URLMatcherCondition CreateHostEqualsPathPrefixCondition( + const std::string& host, + const std::string& path_prefix); + + // Canonicalizes a URL for "CreateURL*Condition" searches. + std::string CanonicalizeURLForFullSearches(const GURL& url) const; + + // Canonicalizes a URL for "CreateURLMatchesCondition" searches. + std::string CanonicalizeURLForRegexSearches(const GURL& url) const; + // Canonicalizes a URL for "CreateOriginAndPathMatchesCondition" searches. + std::string CanonicalizeURLForOriginAndPathRegexSearches( + const GURL& url) const; + + URLMatcherCondition CreateURLPrefixCondition(const std::string& prefix); + URLMatcherCondition CreateURLSuffixCondition(const std::string& suffix); + URLMatcherCondition CreateURLContainsCondition(const std::string& str); + URLMatcherCondition CreateURLEqualsCondition(const std::string& str); + + URLMatcherCondition CreateURLMatchesCondition(const std::string& regex); + URLMatcherCondition CreateOriginAndPathMatchesCondition( + const std::string& regex); + + // Removes all patterns from |pattern_singletons_| that are not listed in + // |used_patterns|. These patterns are not referenced any more and get + // freed. + void ForgetUnusedPatterns( + const std::set<StringPattern::ID>& used_patterns); + + // Returns true if this object retains no allocated data. Only for debugging. + bool IsEmpty() const; + + private: + // Creates a URLMatcherCondition according to the parameters passed. + // The URLMatcherCondition will refer to a StringPattern that is + // owned by |pattern_singletons_|. + URLMatcherCondition CreateCondition(URLMatcherCondition::Criterion criterion, + const std::string& pattern); + + // Prepends a "." to the hostname if it does not start with one. + std::string CanonicalizeHostname(const std::string& hostname) const; + + // Counter that ensures that all created StringPatterns have unique IDs. + // Note that substring patterns and regex patterns will use different IDs. + int id_counter_; + + // This comparison considers only the pattern() value of the + // StringPatterns. + struct StringPatternPointerCompare { + bool operator()(StringPattern* lhs, StringPattern* rhs) const; + }; + // Set to ensure that we generate only one StringPattern for each content + // of StringPattern::pattern(). + typedef std::set<StringPattern*, StringPatternPointerCompare> + PatternSingletons; + PatternSingletons substring_pattern_singletons_; + PatternSingletons regex_pattern_singletons_; + PatternSingletons origin_and_path_regex_pattern_singletons_; + + DISALLOW_COPY_AND_ASSIGN(URLMatcherConditionFactory); +}; + +// This class represents a filter for the URL scheme to be hooked up into a +// URLMatcherConditionSet. +class URLMatcherSchemeFilter { + public: + explicit URLMatcherSchemeFilter(const std::string& filter); + explicit URLMatcherSchemeFilter(const std::vector<std::string>& filters); + ~URLMatcherSchemeFilter(); + bool IsMatch(const GURL& url) const; + + private: + std::vector<std::string> filters_; + + DISALLOW_COPY_AND_ASSIGN(URLMatcherSchemeFilter); +}; + +// This class represents a filter for port numbers to be hooked up into a +// URLMatcherConditionSet. +class URLMatcherPortFilter { + public: + // Boundaries of a port range (both ends are included). + typedef std::pair<int, int> Range; + explicit URLMatcherPortFilter(const std::vector<Range>& ranges); + ~URLMatcherPortFilter(); + bool IsMatch(const GURL& url) const; + + // Creates a port range [from, to]; both ends are included. + static Range CreateRange(int from, int to); + // Creates a port range containing a single port. + static Range CreateRange(int port); + + private: + std::vector<Range> ranges_; + + DISALLOW_COPY_AND_ASSIGN(URLMatcherPortFilter); +}; + +// This class represents a set of conditions that all need to match on a +// given URL in order to be considered a match. +class URLMatcherConditionSet : public base::RefCounted<URLMatcherConditionSet> { + public: + typedef int ID; + typedef std::set<URLMatcherCondition> Conditions; + typedef std::vector<scoped_refptr<URLMatcherConditionSet> > Vector; + + // Matches if all conditions in |conditions| are fulfilled. + URLMatcherConditionSet(ID id, const Conditions& conditions); + + // Matches if all conditions in |conditions|, |scheme_filter| and + // |port_filter| are fulfilled. |scheme_filter| and |port_filter| may be NULL, + // in which case, no restrictions are imposed on the scheme/port of a URL. + URLMatcherConditionSet(ID id, const Conditions& conditions, + scoped_ptr<URLMatcherSchemeFilter> scheme_filter, + scoped_ptr<URLMatcherPortFilter> port_filter); + + ID id() const { return id_; } + const Conditions& conditions() const { return conditions_; } + + bool IsMatch(const std::set<StringPattern::ID>& matching_patterns, + const GURL& url) const; + + private: + friend class base::RefCounted<URLMatcherConditionSet>; + ~URLMatcherConditionSet(); + ID id_; + Conditions conditions_; + scoped_ptr<URLMatcherSchemeFilter> scheme_filter_; + scoped_ptr<URLMatcherPortFilter> port_filter_; + + DISALLOW_COPY_AND_ASSIGN(URLMatcherConditionSet); +}; + +// This class allows matching one URL against a large set of +// URLMatcherConditionSets at the same time. +class URLMatcher { + public: + URLMatcher(); + ~URLMatcher(); + + // Adds new URLMatcherConditionSet to this URL Matcher. Each condition set + // must have a unique ID. + // This is an expensive operation as it triggers pre-calculations on the + // currently registered condition sets. Do not call this operation many + // times with a single condition set in each call. + void AddConditionSets(const URLMatcherConditionSet::Vector& condition_sets); + + // Removes the listed condition sets. All |condition_set_ids| must be + // currently registered. This function should be called with large batches + // of |condition_set_ids| at a time to improve performance. + void RemoveConditionSets( + const std::vector<URLMatcherConditionSet::ID>& condition_set_ids); + + // Removes all unused condition sets from the ConditionFactory. + void ClearUnusedConditionSets(); + + // Returns the IDs of all URLMatcherConditionSet that match to this |url|. + std::set<URLMatcherConditionSet::ID> MatchURL(const GURL& url) const; + + // Returns the URLMatcherConditionFactory that must be used to create + // URLMatcherConditionSets for this URLMatcher. + URLMatcherConditionFactory* condition_factory() { + return &condition_factory_; + } + + // Returns true if this object retains no allocated data. Only for debugging. + bool IsEmpty() const; + + private: + void UpdateSubstringSetMatcher(bool full_url_conditions); + void UpdateRegexSetMatcher(); + void UpdateTriggers(); + void UpdateConditionFactory(); + void UpdateInternalDatastructures(); + + URLMatcherConditionFactory condition_factory_; + + // Maps the ID of a URLMatcherConditionSet to the respective + // URLMatcherConditionSet. + typedef std::map<URLMatcherConditionSet::ID, + scoped_refptr<URLMatcherConditionSet> > + URLMatcherConditionSets; + URLMatcherConditionSets url_matcher_condition_sets_; + + // Maps a StringPattern ID to the URLMatcherConditions that need to + // be triggered in case of a StringPattern match. + typedef std::map<StringPattern::ID, std::set<URLMatcherConditionSet::ID> > + StringPatternTriggers; + StringPatternTriggers substring_match_triggers_; + + SubstringSetMatcher full_url_matcher_; + SubstringSetMatcher url_component_matcher_; + RegexSetMatcher regex_set_matcher_; + RegexSetMatcher origin_and_path_regex_set_matcher_; + std::set<const StringPattern*> registered_full_url_patterns_; + std::set<const StringPattern*> registered_url_component_patterns_; + + DISALLOW_COPY_AND_ASSIGN(URLMatcher); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_MATCHER_URL_MATCHER_H_ diff --git a/chromium/extensions/common/matcher/url_matcher_constants.cc b/chromium/extensions/common/matcher/url_matcher_constants.cc new file mode 100644 index 00000000000..5b23dea09ae --- /dev/null +++ b/chromium/extensions/common/matcher/url_matcher_constants.cc @@ -0,0 +1,34 @@ +// 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 "extensions/common/matcher/url_matcher_constants.h" + +namespace extensions { +namespace url_matcher_constants { + +// Keys of dictionaries for URL constraints +const char kPortsKey[] = "ports"; +const char kSchemesKey[] = "schemes"; +const char kHostContainsKey[] = "hostContains"; +const char kHostEqualsKey[] = "hostEquals"; +const char kHostPrefixKey[] = "hostPrefix"; +const char kHostSuffixKey[] = "hostSuffix"; +const char kHostSuffixPathPrefixKey[] = "hostSuffixPathPrefix"; +const char kOriginAndPathMatchesKey[] = "originAndPathMatches"; +const char kPathContainsKey[] = "pathContains"; +const char kPathEqualsKey[] = "pathEquals"; +const char kPathPrefixKey[] = "pathPrefix"; +const char kPathSuffixKey[] = "pathSuffix"; +const char kQueryContainsKey[] = "queryContains"; +const char kQueryEqualsKey[] = "queryEquals"; +const char kQueryPrefixKey[] = "queryPrefix"; +const char kQuerySuffixKey[] = "querySuffix"; +const char kURLContainsKey[] = "urlContains"; +const char kURLEqualsKey[] = "urlEquals"; +const char kURLMatchesKey[] = "urlMatches"; +const char kURLPrefixKey[] = "urlPrefix"; +const char kURLSuffixKey[] = "urlSuffix"; + +} // namespace url_matcher_constants +} // namespace extensions diff --git a/chromium/extensions/common/matcher/url_matcher_constants.h b/chromium/extensions/common/matcher/url_matcher_constants.h new file mode 100644 index 00000000000..0618a521577 --- /dev/null +++ b/chromium/extensions/common/matcher/url_matcher_constants.h @@ -0,0 +1,39 @@ +// 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. + +// Constants used for the URLMatcher component of the Declarative API. + +#ifndef EXTENSIONS_COMMON_MATCHER_URL_MATCHER_CONSTANTS_H_ +#define EXTENSIONS_COMMON_MATCHER_URL_MATCHER_CONSTANTS_H_ + +namespace extensions { +namespace url_matcher_constants { + +// Keys of dictionaries for URL constraints +extern const char kPortsKey[]; +extern const char kSchemesKey[]; +extern const char kHostContainsKey[]; +extern const char kHostEqualsKey[]; +extern const char kHostPrefixKey[]; +extern const char kHostSuffixKey[]; +extern const char kHostSuffixPathPrefixKey[]; +extern const char kOriginAndPathMatchesKey[]; +extern const char kPathContainsKey[]; +extern const char kPathEqualsKey[]; +extern const char kPathPrefixKey[]; +extern const char kPathSuffixKey[]; +extern const char kQueryContainsKey[]; +extern const char kQueryEqualsKey[]; +extern const char kQueryPrefixKey[]; +extern const char kQuerySuffixKey[]; +extern const char kURLContainsKey[]; +extern const char kURLEqualsKey[]; +extern const char kURLMatchesKey[]; +extern const char kURLPrefixKey[]; +extern const char kURLSuffixKey[]; + +} // namespace url_matcher_constants +} // namespace extensions + +#endif // EXTENSIONS_COMMON_MATCHER_URL_MATCHER_CONSTANTS_H_ diff --git a/chromium/extensions/common/matcher/url_matcher_factory.cc b/chromium/extensions/common/matcher/url_matcher_factory.cc new file mode 100644 index 00000000000..6eec2e6ab12 --- /dev/null +++ b/chromium/extensions/common/matcher/url_matcher_factory.cc @@ -0,0 +1,277 @@ +// 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 "extensions/common/matcher/url_matcher_factory.h" + +#include <algorithm> +#include <cctype> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/matcher/url_matcher_constants.h" +#include "extensions/common/matcher/url_matcher_helpers.h" +#include "third_party/re2/re2/re2.h" + +namespace helpers = extensions::url_matcher_helpers; +namespace keys = extensions::url_matcher_constants; + +namespace { +// Error messages: +const char kInvalidPortRanges[] = "Invalid port ranges in UrlFilter."; +const char kVectorOfStringsExpected[] = + "UrlFilter attribute '*' expected a vector of strings as parameter."; +const char kUnknownURLFilterAttribute[] = + "Unknown attribute '*' in UrlFilter."; +const char kAttributeExpectedString[] = + "UrlFilter attribute '*' expected a string value."; +const char kUnparseableRegexString[] = + "Could not parse regular expression '*': *"; +const char kLowerCaseExpected[] = "* values need to be in lower case."; + +// Registry for all factory methods of extensions::URLMatcherConditionFactory +// that allows translating string literals from the extension API into +// the corresponding factory method to be called. +class URLMatcherConditionFactoryMethods { + public: + URLMatcherConditionFactoryMethods() { + typedef extensions::URLMatcherConditionFactory F; + factory_methods_[keys::kHostContainsKey] = &F::CreateHostContainsCondition; + factory_methods_[keys::kHostEqualsKey] = &F::CreateHostEqualsCondition; + factory_methods_[keys::kHostPrefixKey] = &F::CreateHostPrefixCondition; + factory_methods_[keys::kHostSuffixKey] = &F::CreateHostSuffixCondition; + factory_methods_[keys::kOriginAndPathMatchesKey] = + &F::CreateOriginAndPathMatchesCondition; + factory_methods_[keys::kPathContainsKey] = &F::CreatePathContainsCondition; + factory_methods_[keys::kPathEqualsKey] = &F::CreatePathEqualsCondition; + factory_methods_[keys::kPathPrefixKey] = &F::CreatePathPrefixCondition; + factory_methods_[keys::kPathSuffixKey] = &F::CreatePathSuffixCondition; + factory_methods_[keys::kQueryContainsKey] = + &F::CreateQueryContainsCondition; + factory_methods_[keys::kQueryEqualsKey] = &F::CreateQueryEqualsCondition; + factory_methods_[keys::kQueryPrefixKey] = &F::CreateQueryPrefixCondition; + factory_methods_[keys::kQuerySuffixKey] = &F::CreateQuerySuffixCondition; + factory_methods_[keys::kURLContainsKey] = &F::CreateURLContainsCondition; + factory_methods_[keys::kURLEqualsKey] = &F::CreateURLEqualsCondition; + factory_methods_[keys::kURLPrefixKey] = &F::CreateURLPrefixCondition; + factory_methods_[keys::kURLSuffixKey] = &F::CreateURLSuffixCondition; + factory_methods_[keys::kURLMatchesKey] = &F::CreateURLMatchesCondition; + } + + // Returns whether a factory method for the specified |pattern_type| (e.g. + // "host_suffix") is known. + bool Contains(const std::string& pattern_type) const { + return factory_methods_.find(pattern_type) != factory_methods_.end(); + } + + // Creates a URLMatcherCondition instance from |url_matcher_condition_factory| + // of the given |pattern_type| (e.g. "host_suffix") for the given + // |pattern_value| (e.g. "example.com"). + // The |pattern_type| needs to be known to this class (see Contains()) or + // a CHECK is triggered. + extensions::URLMatcherCondition Call( + extensions::URLMatcherConditionFactory* url_matcher_condition_factory, + const std::string& pattern_type, + const std::string& pattern_value) const { + FactoryMethods::const_iterator i = factory_methods_.find(pattern_type); + CHECK(i != factory_methods_.end()); + const FactoryMethod& method = i->second; + return (url_matcher_condition_factory->*method)(pattern_value); + } + + private: + typedef extensions::URLMatcherCondition + (extensions::URLMatcherConditionFactory::* FactoryMethod) + (const std::string& prefix); + typedef std::map<std::string, FactoryMethod> FactoryMethods; + + FactoryMethods factory_methods_; + + DISALLOW_COPY_AND_ASSIGN(URLMatcherConditionFactoryMethods); +}; + +static base::LazyInstance<URLMatcherConditionFactoryMethods> + g_url_matcher_condition_factory_methods = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace extensions { + +// static +scoped_refptr<URLMatcherConditionSet> +URLMatcherFactory::CreateFromURLFilterDictionary( + URLMatcherConditionFactory* url_matcher_condition_factory, + const base::DictionaryValue* url_filter_dict, + URLMatcherConditionSet::ID id, + std::string* error) { + scoped_ptr<URLMatcherSchemeFilter> url_matcher_schema_filter; + scoped_ptr<URLMatcherPortFilter> url_matcher_port_filter; + URLMatcherConditionSet::Conditions url_matcher_conditions; + + for (base::DictionaryValue::Iterator iter(*url_filter_dict); + !iter.IsAtEnd(); iter.Advance()) { + const std::string& condition_attribute_name = iter.key(); + const Value& condition_attribute_value = iter.value(); + if (IsURLMatcherConditionAttribute(condition_attribute_name)) { + // Handle {host, path, ...}{Prefix, Suffix, Contains, Equals}. + URLMatcherCondition url_matcher_condition = + CreateURLMatcherCondition( + url_matcher_condition_factory, + condition_attribute_name, + &condition_attribute_value, + error); + if (!error->empty()) + return scoped_refptr<URLMatcherConditionSet>(NULL); + url_matcher_conditions.insert(url_matcher_condition); + } else if (condition_attribute_name == keys::kSchemesKey) { + // Handle scheme. + url_matcher_schema_filter = CreateURLMatcherScheme( + &condition_attribute_value, error); + if (!error->empty()) + return scoped_refptr<URLMatcherConditionSet>(NULL); + } else if (condition_attribute_name == keys::kPortsKey) { + // Handle ports. + url_matcher_port_filter = CreateURLMatcherPorts( + &condition_attribute_value, error); + if (!error->empty()) + return scoped_refptr<URLMatcherConditionSet>(NULL); + } else { + // Handle unknown attributes. + *error = ErrorUtils::FormatErrorMessage( + kUnknownURLFilterAttribute, + condition_attribute_name); + return scoped_refptr<URLMatcherConditionSet>(NULL); + } + } + + // As the URL is the preliminary matching criterion that triggers the tests + // for the remaining condition attributes, we insert an empty URL match if + // no other url match conditions were specified. Such an empty URL is always + // matched. + if (url_matcher_conditions.empty()) { + url_matcher_conditions.insert( + url_matcher_condition_factory->CreateHostPrefixCondition( + std::string())); + } + + scoped_refptr<URLMatcherConditionSet> url_matcher_condition_set( + new URLMatcherConditionSet(id, url_matcher_conditions, + url_matcher_schema_filter.Pass(), url_matcher_port_filter.Pass())); + return url_matcher_condition_set; +} + +// static +bool URLMatcherFactory::IsURLMatcherConditionAttribute( + const std::string& condition_attribute_name) { + return g_url_matcher_condition_factory_methods.Get().Contains( + condition_attribute_name); +} + +namespace { + +// Returns true if some alphabetic characters in this string are upper case. +bool ContainsUpperCase(const std::string& str) { + return std::find_if(str.begin(), str.end(), ::isupper) != str.end(); +} + +} // namespace + +// static +URLMatcherCondition URLMatcherFactory::CreateURLMatcherCondition( + URLMatcherConditionFactory* url_matcher_condition_factory, + const std::string& condition_attribute_name, + const base::Value* value, + std::string* error) { + std::string str_value; + if (!value->GetAsString(&str_value)) { + *error = ErrorUtils::FormatErrorMessage(kAttributeExpectedString, + condition_attribute_name); + return URLMatcherCondition(); + } + if (condition_attribute_name == keys::kHostContainsKey || + condition_attribute_name == keys::kHostPrefixKey || + condition_attribute_name == keys::kHostSuffixKey || + condition_attribute_name == keys::kHostEqualsKey) { + if (ContainsUpperCase(str_value)) { + *error = ErrorUtils::FormatErrorMessage(kLowerCaseExpected, + "Host"); + return URLMatcherCondition(); + } + } + + // Test regular expressions for validity. + if (condition_attribute_name == keys::kURLMatchesKey || + condition_attribute_name == keys::kOriginAndPathMatchesKey) { + re2::RE2 regex(str_value); + if (!regex.ok()) { + *error = ErrorUtils::FormatErrorMessage(kUnparseableRegexString, + str_value, regex.error()); + return URLMatcherCondition(); + } + } + return g_url_matcher_condition_factory_methods.Get().Call( + url_matcher_condition_factory, condition_attribute_name, str_value); +} + +// static +scoped_ptr<URLMatcherSchemeFilter> URLMatcherFactory::CreateURLMatcherScheme( + const base::Value* value, + std::string* error) { + std::vector<std::string> schemas; + if (!helpers::GetAsStringVector(value, &schemas)) { + *error = ErrorUtils::FormatErrorMessage(kVectorOfStringsExpected, + keys::kSchemesKey); + return scoped_ptr<URLMatcherSchemeFilter>(); + } + for (std::vector<std::string>::const_iterator it = schemas.begin(); + it != schemas.end(); ++it) { + if (ContainsUpperCase(*it)) { + *error = ErrorUtils::FormatErrorMessage(kLowerCaseExpected, + "Scheme"); + return scoped_ptr<URLMatcherSchemeFilter>(); + } + } + return scoped_ptr<URLMatcherSchemeFilter>( + new URLMatcherSchemeFilter(schemas)); +} + +// static +scoped_ptr<URLMatcherPortFilter> URLMatcherFactory::CreateURLMatcherPorts( + const base::Value* value, + std::string* error) { + std::vector<URLMatcherPortFilter::Range> ranges; + const base::ListValue* value_list = NULL; + if (!value->GetAsList(&value_list)) { + *error = kInvalidPortRanges; + return scoped_ptr<URLMatcherPortFilter>(); + } + + for (ListValue::const_iterator i = value_list->begin(); + i != value_list->end(); ++i) { + Value* entry = *i; + int port = 0; + base::ListValue* range = NULL; + if (entry->GetAsInteger(&port)) { + ranges.push_back(URLMatcherPortFilter::CreateRange(port)); + } else if (entry->GetAsList(&range)) { + int from = 0, to = 0; + if (range->GetSize() != 2u || + !range->GetInteger(0, &from) || + !range->GetInteger(1, &to)) { + *error = kInvalidPortRanges; + return scoped_ptr<URLMatcherPortFilter>(); + } + ranges.push_back(URLMatcherPortFilter::CreateRange(from, to)); + } else { + *error = kInvalidPortRanges; + return scoped_ptr<URLMatcherPortFilter>(); + } + } + + return scoped_ptr<URLMatcherPortFilter>(new URLMatcherPortFilter(ranges)); +} + +} // namespace extensions diff --git a/chromium/extensions/common/matcher/url_matcher_factory.h b/chromium/extensions/common/matcher/url_matcher_factory.h new file mode 100644 index 00000000000..7c5be6832d8 --- /dev/null +++ b/chromium/extensions/common/matcher/url_matcher_factory.h @@ -0,0 +1,62 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_MATCHER_URL_MATCHER_FACTORY_H_ +#define EXTENSIONS_COMMON_MATCHER_URL_MATCHER_FACTORY_H_ + +#include <string> + +#include "base/basictypes.h" +#include "extensions/common/matcher/url_matcher.h" + +namespace base { +class DictionaryValue; +class Value; +} + +namespace extensions { + +class URLMatcherFactory { + public: + // Creates a URLMatcherConditionSet from a UrlFilter dictionary as defined in + // the declarative API. |url_fetcher_dict| contains the dictionary passed + // by the extension, |id| is the identifier assigned to the created + // URLMatcherConditionSet. In case of an error, |error| is set to contain + // an error message. + // + // Note: In case this function fails or if you don't register the + // URLMatcherConditionSet to the URLMatcher, you need to call + // URLMatcher::ClearUnusedConditionSets() on the URLMatcher that owns this + // URLMatcherFactory. Otherwise you leak memory. + static scoped_refptr<URLMatcherConditionSet> CreateFromURLFilterDictionary( + URLMatcherConditionFactory* url_matcher_condition_factory, + const base::DictionaryValue* url_filter_dict, + URLMatcherConditionSet::ID id, + std::string* error); + + private: + // Returns whether a condition attribute with name |condition_attribute_name| + // needs to be handled by the URLMatcher. + static bool IsURLMatcherConditionAttribute( + const std::string& condition_attribute_name); + + // Factory method of for URLMatcherConditions. + static URLMatcherCondition CreateURLMatcherCondition( + URLMatcherConditionFactory* url_matcher_condition_factory, + const std::string& condition_attribute_name, + const base::Value* value, + std::string* error); + + static scoped_ptr<URLMatcherSchemeFilter> CreateURLMatcherScheme( + const base::Value* value, std::string* error); + + static scoped_ptr<URLMatcherPortFilter> CreateURLMatcherPorts( + const base::Value* value, std::string* error); + + DISALLOW_IMPLICIT_CONSTRUCTORS(URLMatcherFactory); +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_MATCHER_URL_MATCHER_FACTORY_H_ diff --git a/chromium/extensions/common/matcher/url_matcher_factory_unittest.cc b/chromium/extensions/common/matcher/url_matcher_factory_unittest.cc new file mode 100644 index 00000000000..733e1b8a941 --- /dev/null +++ b/chromium/extensions/common/matcher/url_matcher_factory_unittest.cc @@ -0,0 +1,339 @@ +// 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 "extensions/common/matcher/url_matcher_factory.h" + +#include "base/basictypes.h" +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "extensions/common/matcher/url_matcher_constants.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace extensions { + +namespace keys = url_matcher_constants; + +TEST(URLMatcherFactoryTest, CreateFromURLFilterDictionary) { + URLMatcher matcher; + + std::string error; + scoped_refptr<URLMatcherConditionSet> result; + + // Invalid key: {"invalid": "foobar"} + DictionaryValue invalid_condition; + invalid_condition.SetString("invalid", "foobar"); + + // Invalid value type: {"hostSuffix": []} + DictionaryValue invalid_condition2; + invalid_condition2.Set(keys::kHostSuffixKey, new ListValue); + + // Invalid regex value: {"urlMatches": "*"} + DictionaryValue invalid_condition3; + invalid_condition3.SetString(keys::kURLMatchesKey, "*"); + + // Invalid regex value: {"originAndPathMatches": "*"} + DictionaryValue invalid_condition4; + invalid_condition4.SetString(keys::kOriginAndPathMatchesKey, "*"); + + // Valid values: + // { + // "port_range": [80, [1000, 1010]], + // "schemes": ["http"], + // "hostSuffix": "example.com" + // "hostPrefix": "www" + // } + + // Port range: Allow 80;1000-1010. + ListValue* port_range = new ListValue(); + port_range->Append(Value::CreateIntegerValue(1000)); + port_range->Append(Value::CreateIntegerValue(1010)); + ListValue* port_ranges = new ListValue(); + port_ranges->Append(Value::CreateIntegerValue(80)); + port_ranges->Append(port_range); + + ListValue* scheme_list = new ListValue(); + scheme_list->Append(Value::CreateStringValue("http")); + + DictionaryValue valid_condition; + valid_condition.SetString(keys::kHostSuffixKey, "example.com"); + valid_condition.SetString(keys::kHostPrefixKey, "www"); + valid_condition.Set(keys::kPortsKey, port_ranges); + valid_condition.Set(keys::kSchemesKey, scheme_list); + + // Test wrong condition name passed. + error.clear(); + result = URLMatcherFactory::CreateFromURLFilterDictionary( + matcher.condition_factory(), &invalid_condition, 1, &error); + EXPECT_FALSE(error.empty()); + EXPECT_FALSE(result.get()); + + // Test wrong datatype in hostSuffix. + error.clear(); + result = URLMatcherFactory::CreateFromURLFilterDictionary( + matcher.condition_factory(), &invalid_condition2, 2, &error); + EXPECT_FALSE(error.empty()); + EXPECT_FALSE(result.get()); + + // Test invalid regex in urlMatches. + error.clear(); + result = URLMatcherFactory::CreateFromURLFilterDictionary( + matcher.condition_factory(), &invalid_condition3, 3, &error); + EXPECT_FALSE(error.empty()); + EXPECT_FALSE(result.get()); + + error.clear(); + result = URLMatcherFactory::CreateFromURLFilterDictionary( + matcher.condition_factory(), &invalid_condition4, 4, &error); + EXPECT_FALSE(error.empty()); + EXPECT_FALSE(result.get()); + + // Test success. + error.clear(); + result = URLMatcherFactory::CreateFromURLFilterDictionary( + matcher.condition_factory(), &valid_condition, 100, &error); + EXPECT_EQ("", error); + ASSERT_TRUE(result.get()); + + URLMatcherConditionSet::Vector conditions; + conditions.push_back(result); + matcher.AddConditionSets(conditions); + + EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com")).size()); + EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com:80")).size()); + EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com:1000")).size()); + // Wrong scheme. + EXPECT_EQ(0u, matcher.MatchURL(GURL("https://www.example.com:80")).size()); + // Wrong port. + EXPECT_EQ(0u, matcher.MatchURL(GURL("http://www.example.com:81")).size()); + // Unfulfilled host prefix. + EXPECT_EQ(0u, matcher.MatchURL(GURL("http://mail.example.com:81")).size()); +} + +// Using upper case letters for scheme and host values is currently an error. +// See more context at http://crbug.com/160702#c6 . +TEST(URLMatcherFactoryTest, UpperCase) { + URLMatcher matcher; + std::string error; + scoped_refptr<URLMatcherConditionSet> result; + + // {"hostContains": "exaMple"} + DictionaryValue invalid_condition1; + invalid_condition1.SetString(keys::kHostContainsKey, "exaMple"); + + // {"hostSuffix": ".Com"} + DictionaryValue invalid_condition2; + invalid_condition2.SetString(keys::kHostSuffixKey, ".Com"); + + // {"hostPrefix": "WWw."} + DictionaryValue invalid_condition3; + invalid_condition3.SetString(keys::kHostPrefixKey, "WWw."); + + // {"hostEquals": "WWW.example.Com"} + DictionaryValue invalid_condition4; + invalid_condition4.SetString(keys::kHostEqualsKey, "WWW.example.Com"); + + // {"scheme": ["HTTP"]} + ListValue* scheme_list = new ListValue(); + scheme_list->Append(Value::CreateStringValue("HTTP")); + DictionaryValue invalid_condition5; + invalid_condition5.Set(keys::kSchemesKey, scheme_list); + + const DictionaryValue* invalid_conditions[] = { + &invalid_condition1, + &invalid_condition2, + &invalid_condition3, + &invalid_condition4, + &invalid_condition5 + }; + + for (size_t i = 0; i < arraysize(invalid_conditions); ++i) { + error.clear(); + result = URLMatcherFactory::CreateFromURLFilterDictionary( + matcher.condition_factory(), invalid_conditions[i], 1, &error); + EXPECT_FALSE(error.empty()) << "in iteration " << i; + EXPECT_FALSE(result.get()) << "in iteration " << i; + } +} + +// This class wraps a case sensitivity test for a single UrlFilter condition. +class UrlConditionCaseTest { + public: + // The condition is identified by the key |condition_key|. If that key is + // associated with string values, then |use_list_of_strings| should be false, + // if the key is associated with list-of-string values, then + // |use_list_of_strings| should be true. In |url| is the URL to test against. + UrlConditionCaseTest(const char* condition_key, + bool use_list_of_strings, + const std::string& expected_value, + const std::string& incorrect_case_value, + bool case_sensitive, + bool lower_case_enforced, + const GURL& url) + : condition_key_(condition_key), + use_list_of_strings_(use_list_of_strings), + expected_value_(expected_value), + incorrect_case_value_(incorrect_case_value), + expected_result_for_wrong_case_(ExpectedResult(case_sensitive, + lower_case_enforced)), + url_(url) {} + + ~UrlConditionCaseTest() {} + + // Match the condition against |url_|. Checks via EXPECT_* macros that + // |expected_value_| matches always, and that |incorrect_case_value_| matches + // iff |case_sensitive_| is false. + void Test() const; + + private: + enum ResultType { OK, NOT_FULFILLED, CREATE_FAILURE }; + + // What is the expected result of |CheckCondition| if a wrong-case |value| + // containing upper case letters is supplied. + static ResultType ExpectedResult(bool case_sensitive, + bool lower_case_enforced) { + if (lower_case_enforced) + return CREATE_FAILURE; + if (case_sensitive) + return NOT_FULFILLED; + return OK; + } + + // Test the condition |condition_key_| = |value| against |url_|. + // Check, via EXPECT_* macros, that either the condition cannot be constructed + // at all, or that the condition is not fulfilled, or that it is fulfilled, + // depending on the value of |expected_result|. + void CheckCondition(const std::string& value, + ResultType expected_result) const; + + const char* condition_key_; + const bool use_list_of_strings_; + const std::string& expected_value_; + const std::string& incorrect_case_value_; + const ResultType expected_result_for_wrong_case_; + const GURL& url_; + + // Allow implicit copy and assign, because a public copy constructor is + // needed, but never used (!), for the definition of arrays of this class. +}; + +void UrlConditionCaseTest::Test() const { + CheckCondition(expected_value_, OK); + CheckCondition(incorrect_case_value_, expected_result_for_wrong_case_); +} + +void UrlConditionCaseTest::CheckCondition( + const std::string& value, + UrlConditionCaseTest::ResultType expected_result) const { + DictionaryValue condition; + if (use_list_of_strings_) { + ListValue* list = new ListValue(); + list->Append(Value::CreateStringValue(value)); + condition.SetWithoutPathExpansion(condition_key_, list); + } else { + condition.SetStringWithoutPathExpansion(condition_key_, value); + } + + URLMatcher matcher; + std::string error; + scoped_refptr<URLMatcherConditionSet> result; + + result = URLMatcherFactory::CreateFromURLFilterDictionary( + matcher.condition_factory(), &condition, 1, &error); + if (expected_result == CREATE_FAILURE) { + EXPECT_FALSE(error.empty()); + EXPECT_FALSE(result.get()); + return; + } + EXPECT_EQ("", error); + ASSERT_TRUE(result.get()); + + URLMatcherConditionSet::Vector conditions; + conditions.push_back(result); + matcher.AddConditionSets(conditions); + EXPECT_EQ((expected_result == OK ? 1u : 0u), matcher.MatchURL(url_).size()) + << "while matching condition " << condition_key_ << " with value " + << value << " against url " << url_; +} + +// This tests that the UrlFilter handles case sensitivity on various parts of +// URLs correctly. +TEST(URLMatcherFactoryTest, CaseSensitivity) { + const std::string kScheme("https"); + const std::string kSchemeUpper("HTTPS"); + const std::string kHost("www.example.com"); + const std::string kHostUpper("WWW.EXAMPLE.COM"); + const std::string kPath("/path"); + const std::string kPathUpper("/PATH"); + const std::string kQuery("?option=value&A=B"); + const std::string kQueryUpper("?OPTION=VALUE&A=B"); + const std::string kUrl(kScheme + "://" + kHost + ":1234" + kPath + kQuery); + const std::string kUrlUpper( + kSchemeUpper + "://" + kHostUpper + ":1234" + kPathUpper + kQueryUpper); + const GURL url(kUrl); + // Note: according to RFC 3986, and RFC 1034, schema and host, respectively + // should be case insensitive. See crbug.com/160702#6 for why we still + // require them to be case sensitive in UrlFilter, and enforce lower case. + const bool kIsSchemeLowerCaseEnforced = true; + const bool kIsHostLowerCaseEnforced = true; + const bool kIsPathLowerCaseEnforced = false; + const bool kIsQueryLowerCaseEnforced = false; + const bool kIsUrlLowerCaseEnforced = false; + const bool kIsSchemeCaseSensitive = true; + const bool kIsHostCaseSensitive = true; + const bool kIsPathCaseSensitive = true; + const bool kIsQueryCaseSensitive = true; + const bool kIsUrlCaseSensitive = kIsSchemeCaseSensitive || + kIsHostCaseSensitive || + kIsPathCaseSensitive || + kIsQueryCaseSensitive; + + const UrlConditionCaseTest case_tests[] = { + UrlConditionCaseTest(keys::kSchemesKey, true, kScheme, kSchemeUpper, + kIsSchemeCaseSensitive, kIsSchemeLowerCaseEnforced, + url), + UrlConditionCaseTest(keys::kHostContainsKey, false, kHost, kHostUpper, + kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kHostEqualsKey, false, kHost, kHostUpper, + kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kHostPrefixKey, false, kHost, kHostUpper, + kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kHostSuffixKey, false, kHost, kHostUpper, + kIsHostCaseSensitive, kIsHostLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kPathContainsKey, false, kPath, kPathUpper, + kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kPathEqualsKey, false, kPath, kPathUpper, + kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kPathPrefixKey, false, kPath, kPathUpper, + kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kPathSuffixKey, false, kPath, kPathUpper, + kIsPathCaseSensitive, kIsPathLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kQueryContainsKey, false, kQuery, kQueryUpper, + kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kQueryEqualsKey, false, kQuery, kQueryUpper, + kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kQueryPrefixKey, false, kQuery, kQueryUpper, + kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kQuerySuffixKey, false, kQuery, kQueryUpper, + kIsQueryCaseSensitive, kIsQueryLowerCaseEnforced, url), + // Excluding kURLMatchesKey because case sensitivity can be specified in the + // RE2 expression. + UrlConditionCaseTest(keys::kURLContainsKey, false, kUrl, kUrlUpper, + kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kURLEqualsKey, false, kUrl, kUrlUpper, + kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kURLPrefixKey, false, kUrl, kUrlUpper, + kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url), + UrlConditionCaseTest(keys::kURLSuffixKey, false, kUrl, kUrlUpper, + kIsUrlCaseSensitive, kIsUrlLowerCaseEnforced, url), + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(case_tests); ++i) { + SCOPED_TRACE(base::StringPrintf("Iteration: %" PRIuS, i)); + case_tests[i].Test(); + } +} + +} // namespace extensions diff --git a/chromium/extensions/common/matcher/url_matcher_helpers.cc b/chromium/extensions/common/matcher/url_matcher_helpers.cc new file mode 100644 index 00000000000..e4832cc18ac --- /dev/null +++ b/chromium/extensions/common/matcher/url_matcher_helpers.cc @@ -0,0 +1,31 @@ +// 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 "extensions/common/matcher/url_matcher_helpers.h" + +#include "base/values.h" + +namespace extensions { +namespace url_matcher_helpers { + +// Converts a ValueList |value| of strings into a vector. Returns true if +// successful. +bool GetAsStringVector(const base::Value* value, + std::vector<std::string>* out) { + const ListValue* value_as_list = 0; + if (!value->GetAsList(&value_as_list)) + return false; + + size_t number_types = value_as_list->GetSize(); + for (size_t i = 0; i < number_types; ++i) { + std::string item; + if (!value_as_list->GetString(i, &item)) + return false; + out->push_back(item); + } + return true; +} + +} // namespace url_matcher_helpers +} // namespace extensions diff --git a/chromium/extensions/common/matcher/url_matcher_helpers.h b/chromium/extensions/common/matcher/url_matcher_helpers.h new file mode 100644 index 00000000000..57bd2997cee --- /dev/null +++ b/chromium/extensions/common/matcher/url_matcher_helpers.h @@ -0,0 +1,27 @@ +// 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. + +// Helper functions used for URLMatcher and Declarative APIs. + +#ifndef EXTENSIONS_COMMON_MATCHER_URL_MATCHER_HELPERS_H_ +#define EXTENSIONS_COMMON_MATCHER_URL_MATCHER_HELPERS_H_ + +#include <string> +#include <vector> + +namespace base { +class Value; +} + +namespace extensions { +namespace url_matcher_helpers { + +// Converts a ValueList |value| of strings into a vector. Returns true if +// successful. +bool GetAsStringVector(const base::Value* value, std::vector<std::string>* out); + +} // namespace declarative_helpers +} // namespace extensions + +#endif // EXTENSIONS_COMMON_MATCHER_URL_MATCHER_HELPERS_H_ diff --git a/chromium/extensions/common/matcher/url_matcher_unittest.cc b/chromium/extensions/common/matcher/url_matcher_unittest.cc new file mode 100644 index 00000000000..29b7c890dfd --- /dev/null +++ b/chromium/extensions/common/matcher/url_matcher_unittest.cc @@ -0,0 +1,682 @@ +// 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 "extensions/common/matcher/url_matcher.h" + +#include "base/strings/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace extensions { + +// +// URLMatcherCondition +// + +TEST(URLMatcherConditionTest, Constructors) { + StringPattern pattern("example.com", 1); + URLMatcherCondition m1(URLMatcherCondition::HOST_SUFFIX, &pattern); + EXPECT_EQ(URLMatcherCondition::HOST_SUFFIX, m1.criterion()); + EXPECT_EQ(&pattern, m1.string_pattern()); + + URLMatcherCondition m2; + m2 = m1; + EXPECT_EQ(URLMatcherCondition::HOST_SUFFIX, m2.criterion()); + EXPECT_EQ(&pattern, m2.string_pattern()); + + URLMatcherCondition m3(m1); + EXPECT_EQ(URLMatcherCondition::HOST_SUFFIX, m3.criterion()); + EXPECT_EQ(&pattern, m3.string_pattern()); +} + +TEST(URLMatcherSchemeFilter, TestMatching) { + URLMatcherSchemeFilter filter1("https"); + std::vector<std::string> filter2_content; + filter2_content.push_back("http"); + filter2_content.push_back("https"); + URLMatcherSchemeFilter filter2(filter2_content); + + GURL matching_url("https://www.foobar.com"); + GURL non_matching_url("http://www.foobar.com"); + EXPECT_TRUE(filter1.IsMatch(matching_url)); + EXPECT_FALSE(filter1.IsMatch(non_matching_url)); + EXPECT_TRUE(filter2.IsMatch(matching_url)); + EXPECT_TRUE(filter2.IsMatch(non_matching_url)); +} + +TEST(URLMatcherPortFilter, TestMatching) { + std::vector<URLMatcherPortFilter::Range> ranges; + ranges.push_back(URLMatcherPortFilter::CreateRange(80, 90)); + ranges.push_back(URLMatcherPortFilter::CreateRange(8080)); + URLMatcherPortFilter filter(ranges); + EXPECT_TRUE(filter.IsMatch(GURL("http://www.example.com"))); + EXPECT_TRUE(filter.IsMatch(GURL("http://www.example.com:80"))); + EXPECT_TRUE(filter.IsMatch(GURL("http://www.example.com:81"))); + EXPECT_TRUE(filter.IsMatch(GURL("http://www.example.com:90"))); + EXPECT_TRUE(filter.IsMatch(GURL("http://www.example.com:8080"))); + EXPECT_FALSE(filter.IsMatch(GURL("http://www.example.com:79"))); + EXPECT_FALSE(filter.IsMatch(GURL("http://www.example.com:91"))); + EXPECT_FALSE(filter.IsMatch(GURL("https://www.example.com"))); +} + +TEST(URLMatcherConditionTest, IsFullURLCondition) { + StringPattern pattern("example.com", 1); + EXPECT_FALSE(URLMatcherCondition(URLMatcherCondition::HOST_SUFFIX, + &pattern).IsFullURLCondition()); + + EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::HOST_CONTAINS, + &pattern).IsFullURLCondition()); + EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::PATH_CONTAINS, + &pattern).IsFullURLCondition()); + EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::QUERY_CONTAINS, + &pattern).IsFullURLCondition()); + + EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::URL_PREFIX, + &pattern).IsFullURLCondition()); + EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::URL_SUFFIX, + &pattern).IsFullURLCondition()); + EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::URL_CONTAINS, + &pattern).IsFullURLCondition()); + EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::URL_EQUALS, + &pattern).IsFullURLCondition()); +} + +TEST(URLMatcherConditionTest, IsMatch) { + GURL url1("http://www.example.com/www.foobar.com/index.html"); + GURL url2("http://www.foobar.com/example.com/index.html"); + + StringPattern pattern("example.com", 1); + URLMatcherCondition m1(URLMatcherCondition::HOST_SUFFIX, &pattern); + + std::set<StringPattern::ID> matching_patterns; + + // matches = {0} --> matcher did not indicate that m1 was a match. + matching_patterns.insert(0); + EXPECT_FALSE(m1.IsMatch(matching_patterns, url1)); + + // matches = {0, 1} --> matcher did indicate that m1 was a match. + matching_patterns.insert(1); + EXPECT_TRUE(m1.IsMatch(matching_patterns, url1)); + + // For m2 we use a HOST_CONTAINS test, which requires a post-validation + // whether the match reported by the SubstringSetMatcher occurs really + // in the correct url component. + URLMatcherCondition m2(URLMatcherCondition::HOST_CONTAINS, &pattern); + EXPECT_TRUE(m2.IsMatch(matching_patterns, url1)); + EXPECT_FALSE(m2.IsMatch(matching_patterns, url2)); +} + +TEST(URLMatcherConditionTest, Comparison) { + StringPattern p1("foobar.com", 1); + StringPattern p2("foobar.com", 2); + // The first component of each test is expected to be < than the second. + URLMatcherCondition test_smaller[][2] = { + {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p1), + URLMatcherCondition(URLMatcherCondition::HOST_SUFFIX, &p1)}, + {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p1), + URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p2)}, + {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, NULL), + URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p2)}, + {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p1), + URLMatcherCondition(URLMatcherCondition::HOST_SUFFIX, NULL)}, + }; + for (size_t i = 0; i < arraysize(test_smaller); ++i) { + EXPECT_TRUE(test_smaller[i][0] < test_smaller[i][1]) + << "Test " << i << " of test_smaller failed"; + EXPECT_FALSE(test_smaller[i][1] < test_smaller[i][0]) + << "Test " << i << " of test_smaller failed"; + } + URLMatcherCondition test_equal[][2] = { + {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p1), + URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p1)}, + {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, NULL), + URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, NULL)}, + }; + for (size_t i = 0; i < arraysize(test_equal); ++i) { + EXPECT_FALSE(test_equal[i][0] < test_equal[i][1]) + << "Test " << i << " of test_equal failed"; + EXPECT_FALSE(test_equal[i][1] < test_equal[i][0]) + << "Test " << i << " of test_equal failed"; + } +} + +// +// URLMatcherConditionFactory +// + +namespace { + +bool Matches(const URLMatcherCondition& condition, std::string text) { + return text.find(condition.string_pattern()->pattern()) != + std::string::npos; +} + +} // namespace + +TEST(URLMatcherConditionFactoryTest, GURLCharacterSet) { + // GURL guarantees that neither domain, nor path, nor query may contain + // non ASCII-7 characters. We test this here, because a change to this + // guarantee breaks this implementation horribly. + GURL url("http://www.föö.com/föö?föö#föö"); + EXPECT_TRUE(IsStringASCII(url.host())); + EXPECT_TRUE(IsStringASCII(url.path())); + EXPECT_TRUE(IsStringASCII(url.query())); + EXPECT_FALSE(IsStringASCII(url.ref())); +} + +TEST(URLMatcherConditionFactoryTest, Criteria) { + URLMatcherConditionFactory factory; + EXPECT_EQ(URLMatcherCondition::HOST_PREFIX, + factory.CreateHostPrefixCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::HOST_SUFFIX, + factory.CreateHostSuffixCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::HOST_CONTAINS, + factory.CreateHostContainsCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::HOST_EQUALS, + factory.CreateHostEqualsCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::PATH_PREFIX, + factory.CreatePathPrefixCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::PATH_SUFFIX, + factory.CreatePathSuffixCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::PATH_CONTAINS, + factory.CreatePathContainsCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::PATH_EQUALS, + factory.CreatePathEqualsCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::QUERY_PREFIX, + factory.CreateQueryPrefixCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::QUERY_SUFFIX, + factory.CreateQuerySuffixCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::QUERY_CONTAINS, + factory.CreateQueryContainsCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::QUERY_EQUALS, + factory.CreateQueryEqualsCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::HOST_SUFFIX_PATH_PREFIX, + factory.CreateHostSuffixPathPrefixCondition("foo", + "bar").criterion()); + EXPECT_EQ(URLMatcherCondition::HOST_EQUALS_PATH_PREFIX, + factory.CreateHostEqualsPathPrefixCondition("foo", + "bar").criterion()); + EXPECT_EQ(URLMatcherCondition::URL_PREFIX, + factory.CreateURLPrefixCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::URL_SUFFIX, + factory.CreateURLSuffixCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::URL_CONTAINS, + factory.CreateURLContainsCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::URL_EQUALS, + factory.CreateURLEqualsCondition("foo").criterion()); + EXPECT_EQ(URLMatcherCondition::URL_MATCHES, + factory.CreateURLMatchesCondition("foo").criterion()); +} + +TEST(URLMatcherConditionFactoryTest, TestSingletonProperty) { + URLMatcherConditionFactory factory; + URLMatcherCondition c1 = factory.CreateHostEqualsCondition("www.google.com"); + URLMatcherCondition c2 = factory.CreateHostEqualsCondition("www.google.com"); + EXPECT_EQ(c1.criterion(), c2.criterion()); + EXPECT_EQ(c1.string_pattern(), c2.string_pattern()); + URLMatcherCondition c3 = factory.CreateHostEqualsCondition("www.google.de"); + EXPECT_EQ(c2.criterion(), c3.criterion()); + EXPECT_NE(c2.string_pattern(), c3.string_pattern()); + EXPECT_NE(c2.string_pattern()->id(), c3.string_pattern()->id()); + EXPECT_NE(c2.string_pattern()->pattern(), + c3.string_pattern()->pattern()); + URLMatcherCondition c4 = factory.CreateURLMatchesCondition("www.google.com"); + URLMatcherCondition c5 = factory.CreateURLContainsCondition("www.google.com"); + // Regex patterns and substring patterns do not share IDs. + EXPECT_EQ(c5.string_pattern()->pattern(), c4.string_pattern()->pattern()); + EXPECT_NE(c5.string_pattern(), c4.string_pattern()); + EXPECT_NE(c5.string_pattern()->id(), c4.string_pattern()->id()); + + // Check that all StringPattern singletons are freed if we call + // ForgetUnusedPatterns. + StringPattern::ID old_id_1 = c1.string_pattern()->id(); + StringPattern::ID old_id_4 = c4.string_pattern()->id(); + factory.ForgetUnusedPatterns(std::set<StringPattern::ID>()); + EXPECT_TRUE(factory.IsEmpty()); + URLMatcherCondition c6 = factory.CreateHostEqualsCondition("www.google.com"); + EXPECT_NE(old_id_1, c6.string_pattern()->id()); + URLMatcherCondition c7 = factory.CreateURLMatchesCondition("www.google.com"); + EXPECT_NE(old_id_4, c7.string_pattern()->id()); +} + +TEST(URLMatcherConditionFactoryTest, TestComponentSearches) { + GURL gurl("https://www.google.com:1234/webhp?sourceid=chrome-instant&ie=UTF-8" + "&ion=1#hl=en&output=search&sclient=psy-ab&q=chrome%20is%20awesome"); + URLMatcherConditionFactory factory; + std::string url = factory.CanonicalizeURLForComponentSearches(gurl); + + // Test host component. + EXPECT_TRUE(Matches(factory.CreateHostPrefixCondition(std::string()), url)); + EXPECT_TRUE(Matches(factory.CreateHostPrefixCondition("www.goog"), url)); + EXPECT_TRUE( + Matches(factory.CreateHostPrefixCondition("www.google.com"), url)); + EXPECT_TRUE( + Matches(factory.CreateHostPrefixCondition(".www.google.com"), url)); + EXPECT_FALSE(Matches(factory.CreateHostPrefixCondition("google.com"), url)); + EXPECT_FALSE( + Matches(factory.CreateHostPrefixCondition("www.google.com/"), url)); + EXPECT_FALSE(Matches(factory.CreateHostPrefixCondition("webhp"), url)); + + EXPECT_TRUE(Matches(factory.CreateHostSuffixCondition(std::string()), url)); + EXPECT_TRUE(Matches(factory.CreateHostSuffixCondition("com"), url)); + EXPECT_TRUE(Matches(factory.CreateHostSuffixCondition(".com"), url)); + EXPECT_TRUE( + Matches(factory.CreateHostSuffixCondition("www.google.com"), url)); + EXPECT_TRUE( + Matches(factory.CreateHostSuffixCondition(".www.google.com"), url)); + EXPECT_FALSE(Matches(factory.CreateHostSuffixCondition("www"), url)); + EXPECT_FALSE( + Matches(factory.CreateHostSuffixCondition("www.google.com/"), url)); + EXPECT_FALSE(Matches(factory.CreateHostSuffixCondition("webhp"), url)); + + EXPECT_FALSE(Matches(factory.CreateHostEqualsCondition(std::string()), url)); + EXPECT_FALSE(Matches(factory.CreateHostEqualsCondition("www"), url)); + EXPECT_TRUE( + Matches(factory.CreateHostEqualsCondition("www.google.com"), url)); + EXPECT_FALSE( + Matches(factory.CreateHostEqualsCondition("www.google.com/"), url)); + + + // Test path component. + EXPECT_TRUE(Matches(factory.CreatePathPrefixCondition(std::string()), url)); + EXPECT_TRUE(Matches(factory.CreatePathPrefixCondition("/web"), url)); + EXPECT_TRUE(Matches(factory.CreatePathPrefixCondition("/webhp"), url)); + EXPECT_FALSE(Matches(factory.CreatePathPrefixCondition("webhp"), url)); + EXPECT_FALSE(Matches(factory.CreatePathPrefixCondition("/webhp?"), url)); + EXPECT_FALSE(Matches(factory.CreatePathPrefixCondition("?sourceid"), url)); + + EXPECT_TRUE(Matches(factory.CreatePathSuffixCondition(std::string()), url)); + EXPECT_TRUE(Matches(factory.CreatePathSuffixCondition("webhp"), url)); + EXPECT_TRUE(Matches(factory.CreatePathSuffixCondition("/webhp"), url)); + EXPECT_FALSE(Matches(factory.CreatePathSuffixCondition("/web"), url)); + EXPECT_FALSE(Matches(factory.CreatePathSuffixCondition("/webhp?"), url)); + + EXPECT_TRUE(Matches(factory.CreatePathEqualsCondition("/webhp"), url)); + EXPECT_FALSE(Matches(factory.CreatePathEqualsCondition("webhp"), url)); + EXPECT_FALSE(Matches(factory.CreatePathEqualsCondition("/webhp?"), url)); + EXPECT_FALSE( + Matches(factory.CreatePathEqualsCondition("www.google.com"), url)); + + + // Test query component. + EXPECT_TRUE(Matches(factory.CreateQueryPrefixCondition(std::string()), url)); + EXPECT_TRUE(Matches(factory.CreateQueryPrefixCondition("sourceid"), url)); + // The '?' at the beginning is just ignored. + EXPECT_TRUE(Matches(factory.CreateQueryPrefixCondition("?sourceid"), url)); + + EXPECT_TRUE(Matches(factory.CreateQuerySuffixCondition(std::string()), url)); + EXPECT_TRUE(Matches(factory.CreateQuerySuffixCondition("ion=1"), url)); + EXPECT_FALSE(Matches(factory.CreateQuerySuffixCondition("www"), url)); + // "Suffix" condition + pattern starting with '?' = "equals" condition. + EXPECT_FALSE(Matches(factory.CreateQuerySuffixCondition( + "?sourceid=chrome-instant&ie=UTF-8&ion="), url)); + EXPECT_TRUE(Matches(factory.CreateQuerySuffixCondition( + "?sourceid=chrome-instant&ie=UTF-8&ion=1"), url)); + + EXPECT_FALSE(Matches(factory.CreateQueryEqualsCondition( + "?sourceid=chrome-instant&ie=UTF-8&ion="), url)); + EXPECT_FALSE(Matches(factory.CreateQueryEqualsCondition( + "sourceid=chrome-instant&ie=UTF-8&ion="), url)); + EXPECT_TRUE(Matches(factory.CreateQueryEqualsCondition( + "sourceid=chrome-instant&ie=UTF-8&ion=1"), url)); + // The '?' at the beginning is just ignored. + EXPECT_TRUE(Matches(factory.CreateQueryEqualsCondition( + "?sourceid=chrome-instant&ie=UTF-8&ion=1"), url)); + EXPECT_FALSE( + Matches(factory.CreateQueryEqualsCondition("www.google.com"), url)); + + + // Test adjacent components + EXPECT_TRUE(Matches(factory.CreateHostSuffixPathPrefixCondition( + "google.com", "/webhp"), url)); + EXPECT_TRUE(Matches( + factory.CreateHostSuffixPathPrefixCondition(std::string(), "/webhp"), + url)); + EXPECT_TRUE(Matches( + factory.CreateHostSuffixPathPrefixCondition("google.com", std::string()), + url)); + EXPECT_FALSE(Matches( + factory.CreateHostSuffixPathPrefixCondition("www", std::string()), url)); + + EXPECT_TRUE(Matches(factory.CreateHostEqualsPathPrefixCondition( + "www.google.com", "/webhp"), url)); + EXPECT_FALSE(Matches( + factory.CreateHostEqualsPathPrefixCondition(std::string(), "/webhp"), + url)); + EXPECT_TRUE(Matches(factory.CreateHostEqualsPathPrefixCondition( + "www.google.com", std::string()), + url)); + EXPECT_FALSE(Matches( + factory.CreateHostEqualsPathPrefixCondition("google.com", std::string()), + url)); +} + +TEST(URLMatcherConditionFactoryTest, TestFullSearches) { + // The Port 443 is stripped because it is the default port for https. + GURL gurl("https://www.google.com:443/webhp?sourceid=chrome-instant&ie=UTF-8" + "&ion=1#hl=en&output=search&sclient=psy-ab&q=chrome%20is%20awesome"); + URLMatcherConditionFactory factory; + std::string url = factory.CanonicalizeURLForFullSearches(gurl); + + EXPECT_TRUE(Matches(factory.CreateURLPrefixCondition(std::string()), url)); + EXPECT_TRUE( + Matches(factory.CreateURLPrefixCondition("https://www.goog"), url)); + EXPECT_TRUE(Matches(factory.CreateURLPrefixCondition( + "https://www.google.com"), url)); + EXPECT_TRUE(Matches(factory.CreateURLPrefixCondition( + "https://www.google.com/webhp?"), url)); + EXPECT_FALSE(Matches(factory.CreateURLPrefixCondition( + "http://www.google.com"), url)); + EXPECT_FALSE(Matches(factory.CreateURLPrefixCondition("webhp"), url)); + + EXPECT_TRUE(Matches(factory.CreateURLSuffixCondition(std::string()), url)); + EXPECT_TRUE(Matches(factory.CreateURLSuffixCondition("ion=1"), url)); + EXPECT_FALSE(Matches(factory.CreateURLSuffixCondition("www"), url)); + + EXPECT_TRUE(Matches(factory.CreateURLContainsCondition(std::string()), url)); + EXPECT_TRUE(Matches(factory.CreateURLContainsCondition("www.goog"), url)); + EXPECT_TRUE(Matches(factory.CreateURLContainsCondition("webhp"), url)); + EXPECT_TRUE(Matches(factory.CreateURLContainsCondition("?"), url)); + EXPECT_TRUE(Matches(factory.CreateURLContainsCondition("sourceid"), url)); + EXPECT_TRUE(Matches(factory.CreateURLContainsCondition("ion=1"), url)); + EXPECT_FALSE(Matches(factory.CreateURLContainsCondition(".www.goog"), url)); + EXPECT_FALSE(Matches(factory.CreateURLContainsCondition("foobar"), url)); + EXPECT_FALSE(Matches(factory.CreateURLContainsCondition("search"), url)); + EXPECT_FALSE(Matches(factory.CreateURLContainsCondition(":443"), url)); + + EXPECT_TRUE(Matches(factory.CreateURLEqualsCondition( + "https://www.google.com/webhp?sourceid=chrome-instant&ie=UTF-8&ion=1"), + url)); + EXPECT_FALSE( + Matches(factory.CreateURLEqualsCondition("https://www.google.com"), url)); + + // Same as above but this time with a non-standard port. + gurl = GURL("https://www.google.com:1234/webhp?sourceid=chrome-instant&" + "ie=UTF-8&ion=1#hl=en&output=search&sclient=psy-ab&q=chrome%20is%20" + "awesome"); + url = factory.CanonicalizeURLForFullSearches(gurl); + EXPECT_TRUE(Matches(factory.CreateURLPrefixCondition( + "https://www.google.com:1234/webhp?"), url)); + EXPECT_TRUE(Matches(factory.CreateURLContainsCondition(":1234"), url)); +} + +// +// URLMatcherConditionSet +// + +TEST(URLMatcherConditionSetTest, Constructor) { + URLMatcherConditionFactory factory; + URLMatcherCondition m1 = factory.CreateHostSuffixCondition("example.com"); + URLMatcherCondition m2 = factory.CreatePathContainsCondition("foo"); + + std::set<URLMatcherCondition> conditions; + conditions.insert(m1); + conditions.insert(m2); + + scoped_refptr<URLMatcherConditionSet> condition_set( + new URLMatcherConditionSet(1, conditions)); + EXPECT_EQ(1, condition_set->id()); + EXPECT_EQ(2u, condition_set->conditions().size()); +} + +TEST(URLMatcherConditionSetTest, Matching) { + GURL url1("http://www.example.com/foo?bar=1"); + GURL url2("http://foo.example.com/index.html"); + GURL url3("http://www.example.com:80/foo?bar=1"); + GURL url4("http://www.example.com:8080/foo?bar=1"); + + URLMatcherConditionFactory factory; + URLMatcherCondition m1 = factory.CreateHostSuffixCondition("example.com"); + URLMatcherCondition m2 = factory.CreatePathContainsCondition("foo"); + + std::set<URLMatcherCondition> conditions; + conditions.insert(m1); + conditions.insert(m2); + + scoped_refptr<URLMatcherConditionSet> condition_set( + new URLMatcherConditionSet(1, conditions)); + EXPECT_EQ(1, condition_set->id()); + EXPECT_EQ(2u, condition_set->conditions().size()); + + std::set<StringPattern::ID> matching_patterns; + matching_patterns.insert(m1.string_pattern()->id()); + EXPECT_FALSE(condition_set->IsMatch(matching_patterns, url1)); + + matching_patterns.insert(m2.string_pattern()->id()); + EXPECT_TRUE(condition_set->IsMatch(matching_patterns, url1)); + EXPECT_FALSE(condition_set->IsMatch(matching_patterns, url2)); + + // Test scheme filters. + scoped_refptr<URLMatcherConditionSet> condition_set2( + new URLMatcherConditionSet(1, + conditions, + scoped_ptr<URLMatcherSchemeFilter>( + new URLMatcherSchemeFilter("https")), + scoped_ptr<URLMatcherPortFilter>())); + EXPECT_FALSE(condition_set2->IsMatch(matching_patterns, url1)); + scoped_refptr<URLMatcherConditionSet> condition_set3( + new URLMatcherConditionSet(1, + conditions, + scoped_ptr<URLMatcherSchemeFilter>( + new URLMatcherSchemeFilter("http")), + scoped_ptr<URLMatcherPortFilter>())); + EXPECT_TRUE(condition_set3->IsMatch(matching_patterns, url1)); + + // Test port filters. + std::vector<URLMatcherPortFilter::Range> ranges; + ranges.push_back(URLMatcherPortFilter::CreateRange(80)); + scoped_ptr<URLMatcherPortFilter> filter(new URLMatcherPortFilter(ranges)); + scoped_refptr<URLMatcherConditionSet> condition_set4( + new URLMatcherConditionSet( + 1, conditions, scoped_ptr<URLMatcherSchemeFilter>(), filter.Pass())); + EXPECT_TRUE(condition_set4->IsMatch(matching_patterns, url1)); + EXPECT_TRUE(condition_set4->IsMatch(matching_patterns, url3)); + EXPECT_FALSE(condition_set4->IsMatch(matching_patterns, url4)); + + // Test regex patterns. + matching_patterns.clear(); + URLMatcherCondition r1 = factory.CreateURLMatchesCondition("/fo?oo"); + std::set<URLMatcherCondition> regex_conditions; + regex_conditions.insert(r1); + scoped_refptr<URLMatcherConditionSet> condition_set5( + new URLMatcherConditionSet(1, regex_conditions)); + EXPECT_FALSE(condition_set5->IsMatch(matching_patterns, url1)); + matching_patterns.insert(r1.string_pattern()->id()); + EXPECT_TRUE(condition_set5->IsMatch(matching_patterns, url1)); + + regex_conditions.insert(m1); + scoped_refptr<URLMatcherConditionSet> condition_set6( + new URLMatcherConditionSet(1, regex_conditions)); + EXPECT_FALSE(condition_set6->IsMatch(matching_patterns, url1)); + matching_patterns.insert(m1.string_pattern()->id()); + EXPECT_TRUE(condition_set6->IsMatch(matching_patterns, url1)); + + matching_patterns.clear(); + regex_conditions.clear(); + URLMatcherCondition r2 = factory.CreateOriginAndPathMatchesCondition("b[a]r"); + regex_conditions.insert(r2); + scoped_refptr<URLMatcherConditionSet> condition_set7( + new URLMatcherConditionSet(1, regex_conditions)); + EXPECT_FALSE(condition_set7->IsMatch(matching_patterns, url1)); + matching_patterns.insert(r2.string_pattern()->id()); + EXPECT_TRUE(condition_set7->IsMatch(matching_patterns, url1)); +} + + +// +// URLMatcher +// + +TEST(URLMatcherTest, FullTest) { + GURL url1("http://www.example.com/foo?bar=1"); + GURL url2("http://foo.example.com/index.html"); + + URLMatcher matcher; + URLMatcherConditionFactory* factory = matcher.condition_factory(); + + // First insert. + URLMatcherConditionSet::Conditions conditions1; + conditions1.insert(factory->CreateHostSuffixCondition("example.com")); + conditions1.insert(factory->CreatePathContainsCondition("foo")); + + const int kConditionSetId1 = 1; + URLMatcherConditionSet::Vector insert1; + insert1.push_back(make_scoped_refptr( + new URLMatcherConditionSet(kConditionSetId1, conditions1))); + matcher.AddConditionSets(insert1); + EXPECT_EQ(1u, matcher.MatchURL(url1).size()); + EXPECT_EQ(0u, matcher.MatchURL(url2).size()); + + // Second insert. + URLMatcherConditionSet::Conditions conditions2; + conditions2.insert(factory->CreateHostSuffixCondition("example.com")); + + const int kConditionSetId2 = 2; + URLMatcherConditionSet::Vector insert2; + insert2.push_back(make_scoped_refptr( + new URLMatcherConditionSet(kConditionSetId2, conditions2))); + matcher.AddConditionSets(insert2); + EXPECT_EQ(2u, matcher.MatchURL(url1).size()); + EXPECT_EQ(1u, matcher.MatchURL(url2).size()); + + // This should be the cached singleton. + int patternId1 = factory->CreateHostSuffixCondition( + "example.com").string_pattern()->id(); + + // Third insert. + URLMatcherConditionSet::Conditions conditions3; + conditions3.insert(factory->CreateHostSuffixCondition("example.com")); + conditions3.insert(factory->CreateURLMatchesCondition("x.*[0-9]")); + + const int kConditionSetId3 = 3; + URLMatcherConditionSet::Vector insert3; + insert3.push_back(make_scoped_refptr( + new URLMatcherConditionSet(kConditionSetId3, conditions3))); + matcher.AddConditionSets(insert3); + EXPECT_EQ(3u, matcher.MatchURL(url1).size()); + EXPECT_EQ(1u, matcher.MatchURL(url2).size()); + + // Removal of third insert. + std::vector<URLMatcherConditionSet::ID> remove3; + remove3.push_back(kConditionSetId3); + matcher.RemoveConditionSets(remove3); + EXPECT_EQ(2u, matcher.MatchURL(url1).size()); + EXPECT_EQ(1u, matcher.MatchURL(url2).size()); + + // Removal of second insert. + std::vector<URLMatcherConditionSet::ID> remove2; + remove2.push_back(kConditionSetId2); + matcher.RemoveConditionSets(remove2); + EXPECT_EQ(1u, matcher.MatchURL(url1).size()); + EXPECT_EQ(0u, matcher.MatchURL(url2).size()); + + // Removal of first insert. + std::vector<URLMatcherConditionSet::ID> remove1; + remove1.push_back(kConditionSetId1); + matcher.RemoveConditionSets(remove1); + EXPECT_EQ(0u, matcher.MatchURL(url1).size()); + EXPECT_EQ(0u, matcher.MatchURL(url2).size()); + + EXPECT_TRUE(matcher.IsEmpty()); + + // The cached singleton in matcher.condition_factory_ should be destroyed to + // free memory. + int patternId2 = factory->CreateHostSuffixCondition( + "example.com").string_pattern()->id(); + // If patternId1 and patternId2 are different that indicates that + // matcher.condition_factory_ does not leak memory by holding onto + // unused patterns. + EXPECT_NE(patternId1, patternId2); +} + +TEST(URLMatcherTest, TestComponentsImplyContains) { + // Due to a different implementation of component (prefix, suffix and equals) + // and *Contains conditions we need to check that when a pattern matches a + // given part of a URL as equal, prefix or suffix, it also matches it in the + // "contains" test. + GURL url("https://www.google.com:1234/webhp?test=val&a=b"); + + URLMatcher matcher; + URLMatcherConditionFactory* factory = matcher.condition_factory(); + + URLMatcherConditionSet::Conditions conditions; + + // First insert all the matching equals => contains pairs. + conditions.insert(factory->CreateHostEqualsCondition("www.google.com")); + conditions.insert(factory->CreateHostContainsCondition("www.google.com")); + + conditions.insert(factory->CreateHostPrefixCondition("www.")); + conditions.insert(factory->CreateHostContainsCondition("www.")); + + conditions.insert(factory->CreateHostSuffixCondition("com")); + conditions.insert(factory->CreateHostContainsCondition("com")); + + conditions.insert(factory->CreatePathEqualsCondition("/webhp")); + conditions.insert(factory->CreatePathContainsCondition("/webhp")); + + conditions.insert(factory->CreatePathPrefixCondition("/we")); + conditions.insert(factory->CreatePathContainsCondition("/we")); + + conditions.insert(factory->CreatePathSuffixCondition("hp")); + conditions.insert(factory->CreatePathContainsCondition("hp")); + + conditions.insert(factory->CreateQueryEqualsCondition("test=val&a=b")); + conditions.insert(factory->CreateQueryContainsCondition("test=val&a=b")); + + conditions.insert(factory->CreateQueryPrefixCondition("test=v")); + conditions.insert(factory->CreateQueryContainsCondition("test=v")); + + conditions.insert(factory->CreateQuerySuffixCondition("l&a=b")); + conditions.insert(factory->CreateQueryContainsCondition("l&a=b")); + + // The '?' for equality is just ignored. + conditions.insert(factory->CreateQueryEqualsCondition("?test=val&a=b")); + // Due to '?' the condition created here is a prefix-testing condition. + conditions.insert(factory->CreateQueryContainsCondition("?test=val&a=b")); + + const int kConditionSetId = 1; + URLMatcherConditionSet::Vector insert; + insert.push_back(make_scoped_refptr( + new URLMatcherConditionSet(kConditionSetId, conditions))); + matcher.AddConditionSets(insert); + EXPECT_EQ(1u, matcher.MatchURL(url).size()); +} + +// Check that matches in everything but the query are found. +TEST(URLMatcherTest, TestOriginAndPathRegExPositive) { + GURL url("https://www.google.com:1234/webhp?test=val&a=b"); + + URLMatcher matcher; + URLMatcherConditionFactory* factory = matcher.condition_factory(); + + URLMatcherConditionSet::Conditions conditions; + + conditions.insert(factory->CreateOriginAndPathMatchesCondition("w..hp")); + const int kConditionSetId = 1; + URLMatcherConditionSet::Vector insert; + insert.push_back(make_scoped_refptr( + new URLMatcherConditionSet(kConditionSetId, conditions))); + matcher.AddConditionSets(insert); + EXPECT_EQ(1u, matcher.MatchURL(url).size()); +} + +// Check that matches in the query are ignored. +TEST(URLMatcherTest, TestOriginAndPathRegExNegative) { + GURL url("https://www.google.com:1234/webhp?test=val&a=b"); + + URLMatcher matcher; + URLMatcherConditionFactory* factory = matcher.condition_factory(); + + URLMatcherConditionSet::Conditions conditions; + + conditions.insert(factory->CreateOriginAndPathMatchesCondition("val")); + const int kConditionSetId = 1; + URLMatcherConditionSet::Vector insert; + insert.push_back(make_scoped_refptr( + new URLMatcherConditionSet(kConditionSetId, conditions))); + matcher.AddConditionSets(insert); + EXPECT_EQ(0u, matcher.MatchURL(url).size()); +} + +} // namespace extensions diff --git a/chromium/extensions/common/one_shot_event.cc b/chromium/extensions/common/one_shot_event.cc new file mode 100644 index 00000000000..318acad5c6b --- /dev/null +++ b/chromium/extensions/common/one_shot_event.cc @@ -0,0 +1,70 @@ +// Copyright 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 "extensions/common/one_shot_event.h" + +#include "base/callback.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/task_runner.h" + +using base::TaskRunner; + +namespace extensions { + +struct OneShotEvent::TaskInfo { + TaskInfo() {} + TaskInfo(const tracked_objects::Location& from_here, + const scoped_refptr<TaskRunner>& runner, + const base::Closure& task) + : from_here(from_here), runner(runner), task(task) { + CHECK(runner.get()); // Detect mistakes with a decent stack frame. + } + tracked_objects::Location from_here; + scoped_refptr<TaskRunner> runner; + base::Closure task; +}; + +OneShotEvent::OneShotEvent() : signaled_(false) { + // It's acceptable to construct the OneShotEvent on one thread, but + // immediately move it to another thread. + thread_checker_.DetachFromThread(); +} +OneShotEvent::~OneShotEvent() {} + +void OneShotEvent::Post(const tracked_objects::Location& from_here, + const base::Closure& task) const { + Post(from_here, task, base::MessageLoopProxy::current()); +} + +void OneShotEvent::Post(const tracked_objects::Location& from_here, + const base::Closure& task, + const scoped_refptr<TaskRunner>& runner) const { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (is_signaled()) { + runner->PostTask(from_here, task); + } else { + tasks_.push_back(TaskInfo(from_here, runner, task)); + } +} + +void OneShotEvent::Signal() { + DCHECK(thread_checker_.CalledOnValidThread()); + + CHECK(!signaled_) << "Only call Signal once."; + + signaled_ = true; + // After this point, a call to Post() from one of the queued tasks + // could proceed immediately, but the fact that this object is + // single-threaded prevents that from being relevant. + + // We could randomize tasks_ in debug mode in order to check that + // the order doesn't matter... + for (size_t i = 0; i < tasks_.size(); ++i) { + tasks_[i].runner->PostTask(tasks_[i].from_here, tasks_[i].task); + } +} + +} // namespace extensions diff --git a/chromium/extensions/common/one_shot_event.h b/chromium/extensions/common/one_shot_event.h new file mode 100644 index 00000000000..17ab5c88f73 --- /dev/null +++ b/chromium/extensions/common/one_shot_event.h @@ -0,0 +1,98 @@ +// Copyright 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. + +#ifndef EXTENSIONS_COMMON_ONE_SHOT_EVENT_H_ +#define EXTENSIONS_COMMON_ONE_SHOT_EVENT_H_ + +#include <vector> + +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" + +namespace base { +class TaskRunner; +} + +namespace tracked_objects { +class Location; +} + +namespace extensions { + +// This class represents an event that's expected to happen once. It +// allows clients to guarantee that code is run after the OneShotEvent +// is signaled. If the OneShotEvent is destroyed before it's +// signaled, the delayed closures are destroyed without being run. +// +// This class is similar to a WaitableEvent combined with several +// WaitableEventWatchers, but using it is simpler. +// +// This class is not thread-safe, and must be used from a single thread. +class OneShotEvent { + public: + OneShotEvent(); + ~OneShotEvent(); + + // True if Signal has been called. This function is mostly for + // migrating old code; usually calling Post() unconditionally will + // result in more readable code. + bool is_signaled() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return signaled_; + } + + // Causes is_signaled() to return true and all queued tasks to be + // run in an arbitrary order. This method must only be called once. + void Signal(); + + // Scheduled |task| to be called on |runner| after is_signaled() + // becomes true. Inside |task|, if this OneShotEvent is still + // alive, CHECK(is_signaled()) will never fail (which implies that + // OneShotEvent::Reset() doesn't exist). + // + // If |*this| is destroyed before being released, none of these + // tasks will be executed. + // + // Omitting the |runner| argument indicates that |task| should run + // on MessageLoopProxy::current(). + // + // Tasks may be run in an arbitrary order, not just FIFO. Tasks + // will never be called on the current thread before this function + // returns. Beware that there's no simple way to wait for all tasks + // on a OneShotEvent to complete, so it's almost never safe to use + // base::Unretained() when creating one. + // + // Const because Post() doesn't modify the logical state of this + // object (which is just the is_signaled() bit). + void Post(const tracked_objects::Location& from_here, + const base::Closure& task) const; + void Post(const tracked_objects::Location& from_here, + const base::Closure& task, + const scoped_refptr<base::TaskRunner>& runner) const; + + private: + struct TaskInfo; + + base::ThreadChecker thread_checker_; + + bool signaled_; + + // The task list is mutable because it's not part of the logical + // state of the object. This lets us return const references to the + // OneShotEvent to clients that just want to run tasks through it + // without worrying that they'll signal the event. + // + // Optimization note: We could reduce the size of this class to a + // single pointer by storing |signaled_| in the low bit of a + // pointer, and storing the size and capacity of the array (if any) + // on the far end of the pointer. + mutable std::vector<TaskInfo> tasks_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_ONE_SHOT_EVENT_H_ diff --git a/chromium/extensions/common/one_shot_event_unittest.cc b/chromium/extensions/common/one_shot_event_unittest.cc new file mode 100644 index 00000000000..4009a0106a2 --- /dev/null +++ b/chromium/extensions/common/one_shot_event_unittest.cc @@ -0,0 +1,111 @@ +// Copyright 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 "extensions/common/one_shot_event.h" + +#include "base/bind.h" +#include "base/run_loop.h" +#include "base/test/test_simple_task_runner.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + +namespace { + +void Increment(int* i) { ++*i; } + +TEST(OneShotEventTest, RecordsSignal) { + OneShotEvent event; + EXPECT_FALSE(event.is_signaled()); + event.Signal(); + EXPECT_TRUE(event.is_signaled()); +} + +TEST(OneShotEventTest, CallsQueue) { + OneShotEvent event; + scoped_refptr<base::TestSimpleTaskRunner> runner( + new base::TestSimpleTaskRunner); + int i = 0; + event.Post(FROM_HERE, base::Bind(&Increment, &i), runner); + event.Post(FROM_HERE, base::Bind(&Increment, &i), runner); + EXPECT_EQ(0U, runner->GetPendingTasks().size()); + event.Signal(); + ASSERT_EQ(2U, runner->GetPendingTasks().size()); + EXPECT_NE(runner->GetPendingTasks()[0].location.line_number(), + runner->GetPendingTasks()[1].location.line_number()) + << "Make sure FROM_HERE is propagated."; + EXPECT_EQ(0, i); + runner->RunPendingTasks(); + EXPECT_EQ(2, i); +} + +TEST(OneShotEventTest, CallsAfterSignalDontRunInline) { + OneShotEvent event; + scoped_refptr<base::TestSimpleTaskRunner> runner( + new base::TestSimpleTaskRunner); + int i = 0; + + event.Signal(); + event.Post(FROM_HERE, base::Bind(&Increment, &i), runner); + EXPECT_EQ(1U, runner->GetPendingTasks().size()); + EXPECT_EQ(0, i); + runner->RunPendingTasks(); + EXPECT_EQ(1, i); +} + +TEST(OneShotEventTest, PostDefaultsToCurrentMessageLoop) { + OneShotEvent event; + scoped_refptr<base::TestSimpleTaskRunner> runner( + new base::TestSimpleTaskRunner); + base::MessageLoop loop; + int runner_i = 0; + int loop_i = 0; + + event.Post(FROM_HERE, base::Bind(&Increment, &runner_i), runner); + event.Post(FROM_HERE, base::Bind(&Increment, &loop_i)); + event.Signal(); + EXPECT_EQ(1U, runner->GetPendingTasks().size()); + EXPECT_EQ(0, runner_i); + runner->RunPendingTasks(); + EXPECT_EQ(1, runner_i); + EXPECT_EQ(0, loop_i); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, loop_i); +} + +void CheckSignaledAndPostIncrement( + OneShotEvent* event, + const scoped_refptr<base::TaskRunner>& runner, + int* i) { + EXPECT_TRUE(event->is_signaled()); + event->Post(FROM_HERE, base::Bind(&Increment, i), runner); +} + +TEST(OneShotEventTest, IsSignaledAndPostsFromCallbackWork) { + OneShotEvent event; + scoped_refptr<base::TestSimpleTaskRunner> runner( + new base::TestSimpleTaskRunner); + int i = 0; + + event.Post(FROM_HERE, + base::Bind(&CheckSignaledAndPostIncrement, &event, runner, &i), + runner); + EXPECT_EQ(0, i); + event.Signal(); + + // CheckSignaledAndPostIncrement is queued on |runner|. + EXPECT_EQ(1U, runner->GetPendingTasks().size()); + EXPECT_EQ(0, i); + runner->RunPendingTasks(); + // Increment is queued on |runner|. + EXPECT_EQ(1U, runner->GetPendingTasks().size()); + EXPECT_EQ(0, i); + runner->RunPendingTasks(); + // Increment has run. + EXPECT_EQ(0U, runner->GetPendingTasks().size()); + EXPECT_EQ(1, i); +} + +} // namespace +} // namespace extensions diff --git a/chromium/extensions/common/permissions/permissions_provider.h b/chromium/extensions/common/permissions/permissions_provider.h new file mode 100644 index 00000000000..5a27a860419 --- /dev/null +++ b/chromium/extensions/common/permissions/permissions_provider.h @@ -0,0 +1,37 @@ +// Copyright 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. + +#ifndef EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_PROVIDER_H_ +#define EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_PROVIDER_H_ + +#include <vector> + +namespace extensions { + +class APIPermissionInfo; + +// The PermissionsProvider creates the APIPermissions instances. It is only +// needed at startup time. +class PermissionsProvider { + public: + // An alias for a given permission |name|. + struct AliasInfo { + const char* name; + const char* alias; + + AliasInfo(const char* name, const char* alias) + : name(name), alias(alias) { + } + }; + // Returns all the known permissions. The caller, PermissionsInfo, + // takes ownership of the APIPermissionInfos. + virtual std::vector<APIPermissionInfo*> GetAllPermissions() const = 0; + + // Returns all the known permission aliases. + virtual std::vector<AliasInfo> GetAllAliases() const = 0; +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_PROVIDER_H_ diff --git a/chromium/extensions/common/switches.cc b/chromium/extensions/common/switches.cc new file mode 100644 index 00000000000..228a557fa58 --- /dev/null +++ b/chromium/extensions/common/switches.cc @@ -0,0 +1,36 @@ +// Copyright 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 "extensions/common/switches.h" + +namespace extensions { + +namespace switches { + +// Allows the browser to load extensions that lack a modern manifest when that +// would otherwise be forbidden. +const char kAllowLegacyExtensionManifests[] = + "allow-legacy-extension-manifests"; + +// Allows injecting extensions and user scripts on the extensions gallery +// site. Normally prevented for security reasons, but can be useful for +// automation testing of the gallery. +const char kAllowScriptingGallery[] = "allow-scripting-gallery"; + +// Enables extension APIs that are in development. +const char kEnableExperimentalExtensionApis[] = + "enable-experimental-extension-apis"; + +// Enables extensions running scripts on chrome:// URLs. +// Extensions still need to explicitly request access to chrome:// URLs in the +// manifest. +const char kExtensionsOnChromeURLs[] = "extensions-on-chrome-urls"; + +// Makes component extensions appear in chrome://settings/extensions. +const char kShowComponentExtensionOptions[] = + "show-component-extension-options"; + +} // namespace switches + +} // namespace extensions diff --git a/chromium/extensions/common/switches.h b/chromium/extensions/common/switches.h new file mode 100644 index 00000000000..7fceb54e9d6 --- /dev/null +++ b/chromium/extensions/common/switches.h @@ -0,0 +1,24 @@ +// Copyright 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. + +#ifndef EXTENSIONS_COMMON_SWITCHES_H_ +#define EXTENSIONS_COMMON_SWITCHES_H_ + +// All switches in alphabetical order. The switches should be documented +// alongside the definition of their values in the .cc file. +namespace extensions { + +namespace switches { + +extern const char kAllowLegacyExtensionManifests[]; +extern const char kAllowScriptingGallery[]; +extern const char kEnableExperimentalExtensionApis[]; +extern const char kExtensionsOnChromeURLs[]; +extern const char kShowComponentExtensionOptions[]; + +} // namespace switches + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_SWITCHES_H_ diff --git a/chromium/extensions/common/url_pattern.cc b/chromium/extensions/common/url_pattern.cc new file mode 100644 index 00000000000..5400567bde5 --- /dev/null +++ b/chromium/extensions/common/url_pattern.cc @@ -0,0 +1,540 @@ +// 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 "extensions/common/url_pattern.h" + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "content/public/common/url_constants.h" +#include "extensions/common/constants.h" +#include "url/gurl.h" +#include "url/url_util.h" + +const char URLPattern::kAllUrlsPattern[] = "<all_urls>"; + +namespace { + +// TODO(aa): What about more obscure schemes like data: and javascript: ? +// Note: keep this array in sync with kValidSchemeMasks. +const char* kValidSchemes[] = { + chrome::kHttpScheme, + chrome::kHttpsScheme, + chrome::kFileScheme, + chrome::kFtpScheme, + chrome::kChromeUIScheme, + extensions::kExtensionScheme, + chrome::kFileSystemScheme, +}; + +const int kValidSchemeMasks[] = { + URLPattern::SCHEME_HTTP, + URLPattern::SCHEME_HTTPS, + URLPattern::SCHEME_FILE, + URLPattern::SCHEME_FTP, + URLPattern::SCHEME_CHROMEUI, + URLPattern::SCHEME_EXTENSION, + URLPattern::SCHEME_FILESYSTEM, +}; + +COMPILE_ASSERT(arraysize(kValidSchemes) == arraysize(kValidSchemeMasks), + must_keep_these_arrays_in_sync); + +const char kParseSuccess[] = "Success."; +const char kParseErrorMissingSchemeSeparator[] = "Missing scheme separator."; +const char kParseErrorInvalidScheme[] = "Invalid scheme."; +const char kParseErrorWrongSchemeType[] = "Wrong scheme type."; +const char kParseErrorEmptyHost[] = "Host can not be empty."; +const char kParseErrorInvalidHostWildcard[] = "Invalid host wildcard."; +const char kParseErrorEmptyPath[] = "Empty path."; +const char kParseErrorInvalidPort[] = "Invalid port."; + +// Message explaining each URLPattern::ParseResult. +const char* const kParseResultMessages[] = { + kParseSuccess, + kParseErrorMissingSchemeSeparator, + kParseErrorInvalidScheme, + kParseErrorWrongSchemeType, + kParseErrorEmptyHost, + kParseErrorInvalidHostWildcard, + kParseErrorEmptyPath, + kParseErrorInvalidPort, +}; + +COMPILE_ASSERT(URLPattern::NUM_PARSE_RESULTS == arraysize(kParseResultMessages), + must_add_message_for_each_parse_result); + +const char kPathSeparator[] = "/"; + +bool IsStandardScheme(const std::string& scheme) { + // "*" gets the same treatment as a standard scheme. + if (scheme == "*") + return true; + + return url_util::IsStandard(scheme.c_str(), + url_parse::Component(0, static_cast<int>(scheme.length()))); +} + +bool IsValidPortForScheme(const std::string& scheme, const std::string& port) { + if (port == "*") + return true; + + // Only accept non-wildcard ports if the scheme uses ports. + if (url_canon::DefaultPortForScheme(scheme.c_str(), scheme.length()) == + url_parse::PORT_UNSPECIFIED) { + return false; + } + + int parsed_port = url_parse::PORT_UNSPECIFIED; + if (!base::StringToInt(port, &parsed_port)) + return false; + return (parsed_port >= 0) && (parsed_port < 65536); +} + +// Returns |path| with the trailing wildcard stripped if one existed. +// +// The functions that rely on this (OverlapsWith and Contains) are only +// called for the patterns inside URLPatternSet. In those cases, we know that +// the path will have only a single wildcard at the end. This makes figuring +// out overlap much easier. It seems like there is probably a computer-sciency +// way to solve the general case, but we don't need that yet. +std::string StripTrailingWildcard(const std::string& path) { + size_t wildcard_index = path.find('*'); + size_t path_last = path.size() - 1; + DCHECK(wildcard_index == std::string::npos || wildcard_index == path_last); + return wildcard_index == path_last ? path.substr(0, path_last) : path; +} + +} // namespace + +URLPattern::URLPattern() + : valid_schemes_(SCHEME_NONE), + match_all_urls_(false), + match_subdomains_(false), + port_("*") {} + +URLPattern::URLPattern(int valid_schemes) + : valid_schemes_(valid_schemes), + match_all_urls_(false), + match_subdomains_(false), + port_("*") {} + +URLPattern::URLPattern(int valid_schemes, const std::string& pattern) + // Strict error checking is used, because this constructor is only + // appropriate when we know |pattern| is valid. + : valid_schemes_(valid_schemes), + match_all_urls_(false), + match_subdomains_(false), + port_("*") { + if (PARSE_SUCCESS != Parse(pattern)) + NOTREACHED() << "URLPattern is invalid: " << pattern; +} + +URLPattern::~URLPattern() { +} + +bool URLPattern::operator<(const URLPattern& other) const { + return GetAsString() < other.GetAsString(); +} + +bool URLPattern::operator==(const URLPattern& other) const { + return GetAsString() == other.GetAsString(); +} + +URLPattern::ParseResult URLPattern::Parse(const std::string& pattern) { + spec_.clear(); + SetMatchAllURLs(false); + SetMatchSubdomains(false); + SetPort("*"); + + // Special case pattern to match every valid URL. + if (pattern == kAllUrlsPattern) { + SetMatchAllURLs(true); + return PARSE_SUCCESS; + } + + // Parse out the scheme. + size_t scheme_end_pos = pattern.find(content::kStandardSchemeSeparator); + bool has_standard_scheme_separator = true; + + // Some urls also use ':' alone as the scheme separator. + if (scheme_end_pos == std::string::npos) { + scheme_end_pos = pattern.find(':'); + has_standard_scheme_separator = false; + } + + if (scheme_end_pos == std::string::npos) + return PARSE_ERROR_MISSING_SCHEME_SEPARATOR; + + if (!SetScheme(pattern.substr(0, scheme_end_pos))) + return PARSE_ERROR_INVALID_SCHEME; + + bool standard_scheme = IsStandardScheme(scheme_); + if (standard_scheme != has_standard_scheme_separator) + return PARSE_ERROR_WRONG_SCHEME_SEPARATOR; + + // Advance past the scheme separator. + scheme_end_pos += + (standard_scheme ? strlen(content::kStandardSchemeSeparator) : 1); + if (scheme_end_pos >= pattern.size()) + return PARSE_ERROR_EMPTY_HOST; + + // Parse out the host and path. + size_t host_start_pos = scheme_end_pos; + size_t path_start_pos = 0; + + if (!standard_scheme) { + path_start_pos = host_start_pos; + } else if (scheme_ == chrome::kFileScheme) { + size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos); + if (host_end_pos == std::string::npos) { + // Allow hostname omission. + // e.g. file://* is interpreted as file:///*, + // file://foo* is interpreted as file:///foo*. + path_start_pos = host_start_pos - 1; + } else { + // Ignore hostname if scheme is file://. + // e.g. file://localhost/foo is equal to file:///foo. + path_start_pos = host_end_pos; + } + } else { + size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos); + + // Host is required. + if (host_start_pos == host_end_pos) + return PARSE_ERROR_EMPTY_HOST; + + if (host_end_pos == std::string::npos) + return PARSE_ERROR_EMPTY_PATH; + + host_ = pattern.substr(host_start_pos, host_end_pos - host_start_pos); + + // The first component can optionally be '*' to match all subdomains. + std::vector<std::string> host_components; + base::SplitString(host_, '.', &host_components); + if (host_components[0] == "*") { + match_subdomains_ = true; + host_components.erase(host_components.begin(), + host_components.begin() + 1); + } + host_ = JoinString(host_components, '.'); + + path_start_pos = host_end_pos; + } + + SetPath(pattern.substr(path_start_pos)); + + size_t port_pos = host_.find(':'); + if (port_pos != std::string::npos) { + if (!SetPort(host_.substr(port_pos + 1))) + return PARSE_ERROR_INVALID_PORT; + host_ = host_.substr(0, port_pos); + } + + // No other '*' can occur in the host, though. This isn't necessary, but is + // done as a convenience to developers who might otherwise be confused and + // think '*' works as a glob in the host. + if (host_.find('*') != std::string::npos) + return PARSE_ERROR_INVALID_HOST_WILDCARD; + + return PARSE_SUCCESS; +} + +void URLPattern::SetValidSchemes(int valid_schemes) { + spec_.clear(); + valid_schemes_ = valid_schemes; +} + +void URLPattern::SetHost(const std::string& host) { + spec_.clear(); + host_ = host; +} + +void URLPattern::SetMatchAllURLs(bool val) { + spec_.clear(); + match_all_urls_ = val; + + if (val) { + match_subdomains_ = true; + scheme_ = "*"; + host_.clear(); + SetPath("/*"); + } +} + +void URLPattern::SetMatchSubdomains(bool val) { + spec_.clear(); + match_subdomains_ = val; +} + +bool URLPattern::SetScheme(const std::string& scheme) { + spec_.clear(); + scheme_ = scheme; + if (scheme_ == "*") { + valid_schemes_ &= (SCHEME_HTTP | SCHEME_HTTPS); + } else if (!IsValidScheme(scheme_)) { + return false; + } + return true; +} + +bool URLPattern::IsValidScheme(const std::string& scheme) const { + if (valid_schemes_ == SCHEME_ALL) + return true; + + for (size_t i = 0; i < arraysize(kValidSchemes); ++i) { + if (scheme == kValidSchemes[i] && (valid_schemes_ & kValidSchemeMasks[i])) + return true; + } + + return false; +} + +void URLPattern::SetPath(const std::string& path) { + spec_.clear(); + path_ = path; + path_escaped_ = path_; + ReplaceSubstringsAfterOffset(&path_escaped_, 0, "\\", "\\\\"); + ReplaceSubstringsAfterOffset(&path_escaped_, 0, "?", "\\?"); +} + +bool URLPattern::SetPort(const std::string& port) { + spec_.clear(); + if (IsValidPortForScheme(scheme_, port)) { + port_ = port; + return true; + } + return false; +} + +bool URLPattern::MatchesURL(const GURL& test) const { + const GURL* test_url = &test; + bool has_inner_url = test.inner_url() != NULL; + + if (has_inner_url) { + if (!test.SchemeIsFileSystem()) + return false; // The only nested URLs we handle are filesystem URLs. + test_url = test.inner_url(); + } + + if (!MatchesScheme(test_url->scheme())) + return false; + + if (match_all_urls_) + return true; + + std::string path_for_request = test.PathForRequest(); + if (has_inner_url) + path_for_request = test_url->path() + path_for_request; + + return MatchesSecurityOriginHelper(*test_url) && + MatchesPath(path_for_request); +} + +bool URLPattern::MatchesSecurityOrigin(const GURL& test) const { + const GURL* test_url = &test; + bool has_inner_url = test.inner_url() != NULL; + + if (has_inner_url) { + if (!test.SchemeIsFileSystem()) + return false; // The only nested URLs we handle are filesystem URLs. + test_url = test.inner_url(); + } + + if (!MatchesScheme(test_url->scheme())) + return false; + + if (match_all_urls_) + return true; + + return MatchesSecurityOriginHelper(*test_url); +} + +bool URLPattern::MatchesScheme(const std::string& test) const { + if (!IsValidScheme(test)) + return false; + + return scheme_ == "*" || test == scheme_; +} + +bool URLPattern::MatchesHost(const std::string& host) const { + std::string test(chrome::kHttpScheme); + test += content::kStandardSchemeSeparator; + test += host; + test += "/"; + return MatchesHost(GURL(test)); +} + +bool URLPattern::MatchesHost(const GURL& test) const { + // If the hosts are exactly equal, we have a match. + if (test.host() == host_) + return true; + + // If we're matching subdomains, and we have no host in the match pattern, + // that means that we're matching all hosts, which means we have a match no + // matter what the test host is. + if (match_subdomains_ && host_.empty()) + return true; + + // Otherwise, we can only match if our match pattern matches subdomains. + if (!match_subdomains_) + return false; + + // We don't do subdomain matching against IP addresses, so we can give up now + // if the test host is an IP address. + if (test.HostIsIPAddress()) + return false; + + // Check if the test host is a subdomain of our host. + if (test.host().length() <= (host_.length() + 1)) + return false; + + if (test.host().compare(test.host().length() - host_.length(), + host_.length(), host_) != 0) + return false; + + return test.host()[test.host().length() - host_.length() - 1] == '.'; +} + +bool URLPattern::MatchesPath(const std::string& test) const { + // Make the behaviour of OverlapsWith consistent with MatchesURL, which is + // need to match hosted apps on e.g. 'google.com' also run on 'google.com/'. + if (test + "/*" == path_escaped_) + return true; + + return MatchPattern(test, path_escaped_); +} + +const std::string& URLPattern::GetAsString() const { + if (!spec_.empty()) + return spec_; + + if (match_all_urls_) { + spec_ = kAllUrlsPattern; + return spec_; + } + + bool standard_scheme = IsStandardScheme(scheme_); + + std::string spec = scheme_ + + (standard_scheme ? content::kStandardSchemeSeparator : ":"); + + if (scheme_ != chrome::kFileScheme && standard_scheme) { + if (match_subdomains_) { + spec += "*"; + if (!host_.empty()) + spec += "."; + } + + if (!host_.empty()) + spec += host_; + + if (port_ != "*") { + spec += ":"; + spec += port_; + } + } + + if (!path_.empty()) + spec += path_; + + spec_ = spec; + return spec_; +} + +bool URLPattern::OverlapsWith(const URLPattern& other) const { + if (match_all_urls() || other.match_all_urls()) + return true; + return (MatchesAnyScheme(other.GetExplicitSchemes()) || + other.MatchesAnyScheme(GetExplicitSchemes())) + && (MatchesHost(other.host()) || other.MatchesHost(host())) + && (MatchesPortPattern(other.port()) || other.MatchesPortPattern(port())) + && (MatchesPath(StripTrailingWildcard(other.path())) || + other.MatchesPath(StripTrailingWildcard(path()))); +} + +bool URLPattern::Contains(const URLPattern& other) const { + if (match_all_urls()) + return true; + return MatchesAllSchemes(other.GetExplicitSchemes()) + && MatchesHost(other.host()) + && MatchesPortPattern(other.port()) + && MatchesPath(StripTrailingWildcard(other.path())); +} + +bool URLPattern::MatchesAnyScheme( + const std::vector<std::string>& schemes) const { + for (std::vector<std::string>::const_iterator i = schemes.begin(); + i != schemes.end(); ++i) { + if (MatchesScheme(*i)) + return true; + } + + return false; +} + +bool URLPattern::MatchesAllSchemes( + const std::vector<std::string>& schemes) const { + for (std::vector<std::string>::const_iterator i = schemes.begin(); + i != schemes.end(); ++i) { + if (!MatchesScheme(*i)) + return false; + } + + return true; +} + +bool URLPattern::MatchesSecurityOriginHelper(const GURL& test) const { + // Ignore hostname if scheme is file://. + if (scheme_ != chrome::kFileScheme && !MatchesHost(test)) + return false; + + if (!MatchesPortPattern(base::IntToString(test.EffectiveIntPort()))) + return false; + + return true; +} + +bool URLPattern::MatchesPortPattern(const std::string& port) const { + return port_ == "*" || port_ == port; +} + +std::vector<std::string> URLPattern::GetExplicitSchemes() const { + std::vector<std::string> result; + + if (scheme_ != "*" && !match_all_urls_ && IsValidScheme(scheme_)) { + result.push_back(scheme_); + return result; + } + + for (size_t i = 0; i < arraysize(kValidSchemes); ++i) { + if (MatchesScheme(kValidSchemes[i])) { + result.push_back(kValidSchemes[i]); + } + } + + return result; +} + +std::vector<URLPattern> URLPattern::ConvertToExplicitSchemes() const { + std::vector<std::string> explicit_schemes = GetExplicitSchemes(); + std::vector<URLPattern> result; + + for (std::vector<std::string>::const_iterator i = explicit_schemes.begin(); + i != explicit_schemes.end(); ++i) { + URLPattern temp = *this; + temp.SetScheme(*i); + temp.SetMatchAllURLs(false); + result.push_back(temp); + } + + return result; +} + +// static +const char* URLPattern::GetParseResultString( + URLPattern::ParseResult parse_result) { + return kParseResultMessages[parse_result]; +} diff --git a/chromium/extensions/common/url_pattern.h b/chromium/extensions/common/url_pattern.h new file mode 100644 index 00000000000..7d55b8da1fd --- /dev/null +++ b/chromium/extensions/common/url_pattern.h @@ -0,0 +1,245 @@ +// 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. +#ifndef EXTENSIONS_COMMON_URL_PATTERN_H_ +#define EXTENSIONS_COMMON_URL_PATTERN_H_ + +#include <functional> +#include <string> +#include <vector> + +class GURL; + +// A pattern that can be used to match URLs. A URLPattern is a very restricted +// subset of URL syntax: +// +// <url-pattern> := <scheme>://<host><port><path> | '<all_urls>' +// <scheme> := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome' | +// 'chrome-extension' | 'filesystem' +// <host> := '*' | '*.' <anychar except '/' and '*'>+ +// <port> := [':' ('*' | <port number between 0 and 65535>)] +// <path> := '/' <any chars> +// +// * Host is not used when the scheme is 'file'. +// * The path can have embedded '*' characters which act as glob wildcards. +// * '<all_urls>' is a special pattern that matches any URL that contains a +// valid scheme (as specified by valid_schemes_). +// * The '*' scheme pattern excludes file URLs. +// +// Examples of valid patterns: +// - http://*/* +// - http://*/foo* +// - https://*.google.com/foo*bar +// - file://monkey* +// - http://127.0.0.1/* +// +// Examples of invalid patterns: +// - http://* -- path not specified +// - http://*foo/bar -- * not allowed as substring of host component +// - http://foo.*.bar/baz -- * must be first component +// - http:/bar -- scheme separator not found +// - foo://* -- invalid scheme +// - chrome:// -- we don't support chrome internal URLs +class URLPattern { + public: + // A collection of scheme bitmasks for use with valid_schemes. + enum SchemeMasks { + SCHEME_NONE = 0, + SCHEME_HTTP = 1 << 0, + SCHEME_HTTPS = 1 << 1, + SCHEME_FILE = 1 << 2, + SCHEME_FTP = 1 << 3, + SCHEME_CHROMEUI = 1 << 4, + SCHEME_EXTENSION = 1 << 5, + SCHEME_FILESYSTEM = 1 << 6, + + // IMPORTANT! + // SCHEME_ALL will match every scheme, including chrome://, chrome- + // extension://, about:, etc. Because this has lots of security + // implications, third-party extensions should usually not be able to get + // access to URL patterns initialized this way. If there is a reason + // for violating this general rule, document why this it safe. + SCHEME_ALL = -1, + }; + + // Error codes returned from Parse(). + enum ParseResult { + PARSE_SUCCESS = 0, + PARSE_ERROR_MISSING_SCHEME_SEPARATOR, + PARSE_ERROR_INVALID_SCHEME, + PARSE_ERROR_WRONG_SCHEME_SEPARATOR, + PARSE_ERROR_EMPTY_HOST, + PARSE_ERROR_INVALID_HOST_WILDCARD, + PARSE_ERROR_EMPTY_PATH, + PARSE_ERROR_INVALID_PORT, + NUM_PARSE_RESULTS + }; + + // The <all_urls> string pattern. + static const char kAllUrlsPattern[]; + + explicit URLPattern(int valid_schemes); + + // Convenience to construct a URLPattern from a string. If the string is not + // known ahead of time, use Parse() instead, which returns success or failure. + URLPattern(int valid_schemes, const std::string& pattern); + + URLPattern(); + ~URLPattern(); + + bool operator<(const URLPattern& other) const; + bool operator==(const URLPattern& other) const; + + // Initializes this instance by parsing the provided string. Returns + // URLPattern::PARSE_SUCCESS on success, or an error code otherwise. On + // failure, this instance will have some intermediate values and is in an + // invalid state. + ParseResult Parse(const std::string& pattern_str); + + // Gets the bitmask of valid schemes. + int valid_schemes() const { return valid_schemes_; } + void SetValidSchemes(int valid_schemes); + + // Gets the host the pattern matches. This can be an empty string if the + // pattern matches all hosts (the input was <scheme>://*/<whatever>). + const std::string& host() const { return host_; } + void SetHost(const std::string& host); + + // Gets whether to match subdomains of host(). + bool match_subdomains() const { return match_subdomains_; } + void SetMatchSubdomains(bool val); + + // Gets the path the pattern matches with the leading slash. This can have + // embedded asterisks which are interpreted using glob rules. + const std::string& path() const { return path_; } + void SetPath(const std::string& path); + + // Returns true if this pattern matches all urls. + bool match_all_urls() const { return match_all_urls_; } + void SetMatchAllURLs(bool val); + + // Sets the scheme for pattern matches. This can be a single '*' if the + // pattern matches all valid schemes (as defined by the valid_schemes_ + // property). Returns false on failure (if the scheme is not valid). + bool SetScheme(const std::string& scheme); + // Note: You should use MatchesScheme() instead of this getter unless you + // absolutely need the exact scheme. This is exposed for testing. + const std::string& scheme() const { return scheme_; } + + // Returns true if the specified scheme can be used in this URL pattern, and + // false otherwise. Uses valid_schemes_ to determine validity. + bool IsValidScheme(const std::string& scheme) const; + + // Returns true if this instance matches the specified URL. + bool MatchesURL(const GURL& test) const; + + // Returns true if this instance matches the specified security origin. + bool MatchesSecurityOrigin(const GURL& test) const; + + // Returns true if |test| matches our scheme. + // Note that if test is "filesystem", this may fail whereas MatchesURL + // may succeed. MatchesURL is smart enough to look at the inner_url instead + // of the outer "filesystem:" part. + bool MatchesScheme(const std::string& test) const; + + // Returns true if |test| matches our host. + bool MatchesHost(const std::string& test) const; + bool MatchesHost(const GURL& test) const; + + // Returns true if |test| matches our path. + bool MatchesPath(const std::string& test) const; + + // Sets the port. Returns false if the port is invalid. + bool SetPort(const std::string& port); + const std::string& port() const { return port_; } + + // Returns a string representing this instance. + const std::string& GetAsString() const; + + // Determines whether there is a URL that would match this instance and + // another instance. This method is symmetrical: Calling + // other.OverlapsWith(this) would result in the same answer. + bool OverlapsWith(const URLPattern& other) const; + + // Returns true if this pattern matches all possible URLs that |other| can + // match. For example, http://*.google.com encompasses http://www.google.com. + bool Contains(const URLPattern& other) const; + + // Converts this URLPattern into an equivalent set of URLPatterns that don't + // use a wildcard in the scheme component. If this URLPattern doesn't use a + // wildcard scheme, then the returned set will contain one element that is + // equivalent to this instance. + std::vector<URLPattern> ConvertToExplicitSchemes() const; + + static bool EffectiveHostCompare(const URLPattern& a, const URLPattern& b) { + if (a.match_all_urls_ && b.match_all_urls_) + return false; + return a.host_.compare(b.host_) < 0; + }; + + // Used for origin comparisons in a std::set. + class EffectiveHostCompareFunctor { + public: + bool operator()(const URLPattern& a, const URLPattern& b) const { + return EffectiveHostCompare(a, b); + }; + }; + + // Get an error string for a ParseResult. + static const char* GetParseResultString(URLPattern::ParseResult parse_result); + + // Checks whether the bit is set for the given scheme in the given scheme mask + static bool IsSchemeBitSet(const std::string& scheme, const int mask); + + private: + // Returns true if any of the |schemes| items matches our scheme. + bool MatchesAnyScheme(const std::vector<std::string>& schemes) const; + + // Returns true if all of the |schemes| items matches our scheme. + bool MatchesAllSchemes(const std::vector<std::string>& schemes) const; + + bool MatchesSecurityOriginHelper(const GURL& test) const; + + // Returns true if our port matches the |port| pattern (it may be "*"). + bool MatchesPortPattern(const std::string& port) const; + + // If the URLPattern contains a wildcard scheme, returns a list of + // equivalent literal schemes, otherwise returns the current scheme. + std::vector<std::string> GetExplicitSchemes() const; + + // A bitmask containing the schemes which are considered valid for this + // pattern. Parse() uses this to decide whether a pattern contains a valid + // scheme. + int valid_schemes_; + + // True if this is a special-case "<all_urls>" pattern. + bool match_all_urls_; + + // The scheme for the pattern. + std::string scheme_; + + // The host without any leading "*" components. + std::string host_; + + // Whether we should match subdomains of the host. This is true if the first + // component of the pattern's host was "*". + bool match_subdomains_; + + // The port. + std::string port_; + + // The path to match. This is everything after the host of the URL, or + // everything after the scheme in the case of file:// URLs. + std::string path_; + + // The path with "?" and "\" characters escaped for use with the + // MatchPattern() function. + std::string path_escaped_; + + // A string representing this URLPattern. + mutable std::string spec_; +}; + +typedef std::vector<URLPattern> URLPatternList; + +#endif // EXTENSIONS_COMMON_URL_PATTERN_H_ diff --git a/chromium/extensions/common/url_pattern_set.cc b/chromium/extensions/common/url_pattern_set.cc new file mode 100644 index 00000000000..87a752c8683 --- /dev/null +++ b/chromium/extensions/common/url_pattern_set.cc @@ -0,0 +1,233 @@ +// 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 "extensions/common/url_pattern_set.h" + +#include <algorithm> +#include <iterator> + +#include "base/logging.h" +#include "base/memory/linked_ptr.h" +#include "base/values.h" +#include "content/public/common/url_constants.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/url_pattern.h" +#include "url/gurl.h" + +namespace extensions { + +namespace { + +const char kInvalidURLPatternError[] = "Invalid url pattern '*'"; + +} // namespace + +// static +void URLPatternSet::CreateDifference(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out) { + out->ClearPatterns(); + std::set_difference(set1.patterns_.begin(), set1.patterns_.end(), + set2.patterns_.begin(), set2.patterns_.end(), + std::inserter<std::set<URLPattern> >( + out->patterns_, out->patterns_.begin())); +} + +// static +void URLPatternSet::CreateIntersection(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out) { + out->ClearPatterns(); + std::set_intersection(set1.patterns_.begin(), set1.patterns_.end(), + set2.patterns_.begin(), set2.patterns_.end(), + std::inserter<std::set<URLPattern> >( + out->patterns_, out->patterns_.begin())); +} + +// static +void URLPatternSet::CreateUnion(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out) { + out->ClearPatterns(); + std::set_union(set1.patterns_.begin(), set1.patterns_.end(), + set2.patterns_.begin(), set2.patterns_.end(), + std::inserter<std::set<URLPattern> >( + out->patterns_, out->patterns_.begin())); +} + +// static +void URLPatternSet::CreateUnion(const std::vector<URLPatternSet>& sets, + URLPatternSet* out) { + out->ClearPatterns(); + if (sets.empty()) + return; + + // N-way union algorithm is basic O(nlog(n)) merge algorithm. + // + // Do the first merge step into a working set so that we don't mutate any of + // the input. + std::vector<URLPatternSet> working; + for (size_t i = 0; i < sets.size(); i += 2) { + if (i + 1 < sets.size()) { + URLPatternSet u; + URLPatternSet::CreateUnion(sets[i], sets[i + 1], &u); + working.push_back(u); + } else { + working.push_back(sets[i]); + } + } + + for (size_t skip = 1; skip < working.size(); skip *= 2) { + for (size_t i = 0; i < (working.size() - skip); i += skip) { + URLPatternSet u; + URLPatternSet::CreateUnion(working[i], working[i + skip], &u); + working[i].patterns_.swap(u.patterns_); + } + } + + out->patterns_.swap(working[0].patterns_); +} + +URLPatternSet::URLPatternSet() {} + +URLPatternSet::URLPatternSet(const URLPatternSet& rhs) + : patterns_(rhs.patterns_) {} + +URLPatternSet::URLPatternSet(const std::set<URLPattern>& patterns) + : patterns_(patterns) {} + +URLPatternSet::~URLPatternSet() {} + +URLPatternSet& URLPatternSet::operator=(const URLPatternSet& rhs) { + patterns_ = rhs.patterns_; + return *this; +} + +bool URLPatternSet::operator==(const URLPatternSet& other) const { + return patterns_ == other.patterns_; +} + +bool URLPatternSet::is_empty() const { + return patterns_.empty(); +} + +size_t URLPatternSet::size() const { + return patterns_.size(); +} + +bool URLPatternSet::AddPattern(const URLPattern& pattern) { + return patterns_.insert(pattern).second; +} + +void URLPatternSet::AddPatterns(const URLPatternSet& set) { + patterns_.insert(set.patterns().begin(), + set.patterns().end()); +} + +void URLPatternSet::ClearPatterns() { + patterns_.clear(); +} + +bool URLPatternSet::Contains(const URLPatternSet& other) const { + for (URLPatternSet::const_iterator it = other.begin(); + it != other.end(); ++it) { + if (!ContainsPattern(*it)) + return false; + } + + return true; +} + +bool URLPatternSet::ContainsPattern(const URLPattern& pattern) const { + for (URLPatternSet::const_iterator it = begin(); + it != end(); ++it) { + if (it->Contains(pattern)) + return true; + } + return false; +} + +bool URLPatternSet::MatchesURL(const GURL& url) const { + for (URLPatternSet::const_iterator pattern = patterns_.begin(); + pattern != patterns_.end(); ++pattern) { + if (pattern->MatchesURL(url)) + return true; + } + + return false; +} + +bool URLPatternSet::MatchesSecurityOrigin(const GURL& origin) const { + for (URLPatternSet::const_iterator pattern = patterns_.begin(); + pattern != patterns_.end(); ++pattern) { + if (pattern->MatchesSecurityOrigin(origin)) + return true; + } + + return false; +} + +bool URLPatternSet::OverlapsWith(const URLPatternSet& other) const { + // Two extension extents overlap if there is any one URL that would match at + // least one pattern in each of the extents. + for (URLPatternSet::const_iterator i = patterns_.begin(); + i != patterns_.end(); ++i) { + for (URLPatternSet::const_iterator j = other.patterns().begin(); + j != other.patterns().end(); ++j) { + if (i->OverlapsWith(*j)) + return true; + } + } + + return false; +} + +scoped_ptr<base::ListValue> URLPatternSet::ToValue() const { + scoped_ptr<ListValue> value(new ListValue); + for (URLPatternSet::const_iterator i = patterns_.begin(); + i != patterns_.end(); ++i) + value->AppendIfNotPresent(Value::CreateStringValue(i->GetAsString())); + return value.Pass(); +} + +bool URLPatternSet::Populate(const std::vector<std::string>& patterns, + int valid_schemes, + bool allow_file_access, + std::string* error) { + ClearPatterns(); + for (size_t i = 0; i < patterns.size(); ++i) { + URLPattern pattern(valid_schemes); + if (pattern.Parse(patterns[i]) != URLPattern::PARSE_SUCCESS) { + if (error) { + *error = ErrorUtils::FormatErrorMessage(kInvalidURLPatternError, + patterns[i]); + } else { + LOG(ERROR) << "Invalid url pattern: " << patterns[i]; + } + return false; + } + if (!allow_file_access && pattern.MatchesScheme(chrome::kFileScheme)) { + pattern.SetValidSchemes( + pattern.valid_schemes() & ~URLPattern::SCHEME_FILE); + } + AddPattern(pattern); + } + return true; +} + +bool URLPatternSet::Populate(const base::ListValue& value, + int valid_schemes, + bool allow_file_access, + std::string* error) { + std::vector<std::string> patterns; + for (size_t i = 0; i < value.GetSize(); ++i) { + std::string item; + if (!value.GetString(i, &item)) + return false; + patterns.push_back(item); + } + return Populate(patterns, valid_schemes, allow_file_access, error); +} + +} // namespace extensions diff --git a/chromium/extensions/common/url_pattern_set.h b/chromium/extensions/common/url_pattern_set.h new file mode 100644 index 00000000000..da6ad132db1 --- /dev/null +++ b/chromium/extensions/common/url_pattern_set.h @@ -0,0 +1,105 @@ +// 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. + +#ifndef EXTENSIONS_COMMMON_URL_PATTERN_SET_H_ +#define EXTENSIONS_COMMMON_URL_PATTERN_SET_H_ + +#include <set> + +#include "base/memory/scoped_ptr.h" +#include "extensions/common/url_pattern.h" + +class GURL; + +namespace base { +class ListValue; +class Value; +} + +namespace extensions { + +// Represents the set of URLs an extension uses for web content. +class URLPatternSet { + public: + typedef std::set<URLPattern>::const_iterator const_iterator; + typedef std::set<URLPattern>::iterator iterator; + + // Clears |out| and populates the set with |set1| - |set2|. + static void CreateDifference(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out); + + // Clears |out| and populates the set with the intersection of |set1| + // and |set2|. + static void CreateIntersection(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out); + + // Clears |out| and populates the set with the union of |set1| and |set2|. + static void CreateUnion(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out); + + // Clears |out| and populates it with the union of all sets in |sets|. + static void CreateUnion(const std::vector<URLPatternSet>& sets, + URLPatternSet* out); + + URLPatternSet(); + URLPatternSet(const URLPatternSet& rhs); + explicit URLPatternSet(const std::set<URLPattern>& patterns); + ~URLPatternSet(); + + URLPatternSet& operator=(const URLPatternSet& rhs); + bool operator==(const URLPatternSet& rhs) const; + + bool is_empty() const; + size_t size() const; + const std::set<URLPattern>& patterns() const { return patterns_; } + const_iterator begin() const { return patterns_.begin(); } + const_iterator end() const { return patterns_.end(); } + + // Adds a pattern to the set. Returns true if a new pattern was inserted, + // false if the pattern was already in the set. + bool AddPattern(const URLPattern& pattern); + + // Adds all patterns from |set| into this. + void AddPatterns(const URLPatternSet& set); + + void ClearPatterns(); + + // Returns true if every URL that matches |set| is matched by this. In other + // words, if every pattern in |set| is encompassed by a pattern in this. + bool Contains(const URLPatternSet& set) const; + + // Returns true if any pattern in this set encompasses |pattern|. + bool ContainsPattern(const URLPattern& pattern) const; + + // Test if the extent contains a URL. + bool MatchesURL(const GURL& url) const; + + bool MatchesSecurityOrigin(const GURL& origin) const; + + // Returns true if there is a single URL that would be in two extents. + bool OverlapsWith(const URLPatternSet& other) const; + + // Converts to and from Value for serialization to preferences. + scoped_ptr<base::ListValue> ToValue() const; + bool Populate(const base::ListValue& value, + int valid_schemes, + bool allow_file_access, + std::string* error); + + bool Populate(const std::vector<std::string>& patterns, + int valid_schemes, + bool allow_file_access, + std::string* error); + + private: + // The list of URL patterns that comprise the extent. + std::set<URLPattern> patterns_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMMON_URL_PATTERN_SET_H_ diff --git a/chromium/extensions/common/url_pattern_set_unittest.cc b/chromium/extensions/common/url_pattern_set_unittest.cc new file mode 100644 index 00000000000..89b5435b820 --- /dev/null +++ b/chromium/extensions/common/url_pattern_set_unittest.cc @@ -0,0 +1,396 @@ +// 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 "extensions/common/url_pattern_set.h" + +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace extensions { + +namespace { + +void AddPattern(URLPatternSet* set, const std::string& pattern) { + int schemes = URLPattern::SCHEME_ALL; + set->AddPattern(URLPattern(schemes, pattern)); +} + +URLPatternSet Patterns(const std::string& pattern) { + URLPatternSet set; + AddPattern(&set, pattern); + return set; +} + +URLPatternSet Patterns(const std::string& pattern1, + const std::string& pattern2) { + URLPatternSet set; + AddPattern(&set, pattern1); + AddPattern(&set, pattern2); + return set; +} + +} + +TEST(URLPatternSetTest, Empty) { + URLPatternSet set; + EXPECT_FALSE(set.MatchesURL(GURL("http://www.foo.com/bar"))); + EXPECT_FALSE(set.MatchesURL(GURL())); + EXPECT_FALSE(set.MatchesURL(GURL("invalid"))); +} + +TEST(URLPatternSetTest, One) { + URLPatternSet set; + AddPattern(&set, "http://www.google.com/*"); + + EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/"))); + EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/monkey"))); + EXPECT_FALSE(set.MatchesURL(GURL("https://www.google.com/"))); + EXPECT_FALSE(set.MatchesURL(GURL("https://www.microsoft.com/"))); +} + +TEST(URLPatternSetTest, Two) { + URLPatternSet set; + AddPattern(&set, "http://www.google.com/*"); + AddPattern(&set, "http://www.yahoo.com/*"); + + EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/monkey"))); + EXPECT_TRUE(set.MatchesURL(GURL("http://www.yahoo.com/monkey"))); + EXPECT_FALSE(set.MatchesURL(GURL("https://www.apple.com/monkey"))); +} + +TEST(URLPatternSetTest, OverlapsWith) { + URLPatternSet set1; + AddPattern(&set1, "http://www.google.com/f*"); + AddPattern(&set1, "http://www.yahoo.com/b*"); + + URLPatternSet set2; + AddPattern(&set2, "http://www.reddit.com/f*"); + AddPattern(&set2, "http://www.yahoo.com/z*"); + + URLPatternSet set3; + AddPattern(&set3, "http://www.google.com/q/*"); + AddPattern(&set3, "http://www.yahoo.com/b/*"); + + EXPECT_FALSE(set1.OverlapsWith(set2)); + EXPECT_FALSE(set2.OverlapsWith(set1)); + + EXPECT_TRUE(set1.OverlapsWith(set3)); + EXPECT_TRUE(set3.OverlapsWith(set1)); +} + +TEST(URLPatternSetTest, CreateDifference) { + URLPatternSet expected; + URLPatternSet set1; + URLPatternSet set2; + AddPattern(&set1, "http://www.google.com/f*"); + AddPattern(&set1, "http://www.yahoo.com/b*"); + + // Subtract an empty set. + URLPatternSet result; + URLPatternSet::CreateDifference(set1, set2, &result); + EXPECT_EQ(set1, result); + + // Subtract a real set. + AddPattern(&set2, "http://www.reddit.com/f*"); + AddPattern(&set2, "http://www.yahoo.com/z*"); + AddPattern(&set2, "http://www.google.com/f*"); + + AddPattern(&expected, "http://www.yahoo.com/b*"); + + result.ClearPatterns(); + URLPatternSet::CreateDifference(set1, set2, &result); + EXPECT_EQ(expected, result); + EXPECT_FALSE(result.is_empty()); + EXPECT_TRUE(set1.Contains(result)); + EXPECT_FALSE(result.Contains(set2)); + EXPECT_FALSE(set2.Contains(result)); + + URLPatternSet intersection; + URLPatternSet::CreateIntersection(result, set2, &intersection); + EXPECT_TRUE(intersection.is_empty()); +} + +TEST(URLPatternSetTest, CreateIntersection) { + URLPatternSet empty_set; + URLPatternSet expected; + URLPatternSet set1; + AddPattern(&set1, "http://www.google.com/f*"); + AddPattern(&set1, "http://www.yahoo.com/b*"); + + // Intersection with an empty set. + URLPatternSet result; + URLPatternSet::CreateIntersection(set1, empty_set, &result); + EXPECT_EQ(expected, result); + EXPECT_TRUE(result.is_empty()); + EXPECT_TRUE(empty_set.Contains(result)); + EXPECT_TRUE(result.Contains(empty_set)); + EXPECT_TRUE(set1.Contains(result)); + + // Intersection with a real set. + URLPatternSet set2; + AddPattern(&set2, "http://www.reddit.com/f*"); + AddPattern(&set2, "http://www.yahoo.com/z*"); + AddPattern(&set2, "http://www.google.com/f*"); + + AddPattern(&expected, "http://www.google.com/f*"); + + result.ClearPatterns(); + URLPatternSet::CreateIntersection(set1, set2, &result); + EXPECT_EQ(expected, result); + EXPECT_FALSE(result.is_empty()); + EXPECT_TRUE(set1.Contains(result)); + EXPECT_TRUE(set2.Contains(result)); +} + +TEST(URLPatternSetTest, CreateUnion) { + URLPatternSet empty_set; + + URLPatternSet set1; + AddPattern(&set1, "http://www.google.com/f*"); + AddPattern(&set1, "http://www.yahoo.com/b*"); + + URLPatternSet expected; + AddPattern(&expected, "http://www.google.com/f*"); + AddPattern(&expected, "http://www.yahoo.com/b*"); + + // Union with an empty set. + URLPatternSet result; + URLPatternSet::CreateUnion(set1, empty_set, &result); + EXPECT_EQ(expected, result); + + // Union with a real set. + URLPatternSet set2; + AddPattern(&set2, "http://www.reddit.com/f*"); + AddPattern(&set2, "http://www.yahoo.com/z*"); + AddPattern(&set2, "http://www.google.com/f*"); + + AddPattern(&expected, "http://www.reddit.com/f*"); + AddPattern(&expected, "http://www.yahoo.com/z*"); + + result.ClearPatterns(); + URLPatternSet::CreateUnion(set1, set2, &result); + EXPECT_EQ(expected, result); +} + +TEST(URLPatternSetTest, Contains) { + URLPatternSet set1; + URLPatternSet set2; + URLPatternSet empty_set; + + AddPattern(&set1, "http://www.google.com/*"); + AddPattern(&set1, "http://www.yahoo.com/*"); + + AddPattern(&set2, "http://www.reddit.com/*"); + + EXPECT_FALSE(set1.Contains(set2)); + EXPECT_TRUE(set1.Contains(empty_set)); + EXPECT_FALSE(empty_set.Contains(set1)); + + AddPattern(&set2, "http://www.yahoo.com/*"); + + EXPECT_FALSE(set1.Contains(set2)); + EXPECT_FALSE(set2.Contains(set1)); + + AddPattern(&set2, "http://www.google.com/*"); + + EXPECT_FALSE(set1.Contains(set2)); + EXPECT_TRUE(set2.Contains(set1)); + + // Note that this checks if individual patterns contain other patterns, not + // just equality. For example: + AddPattern(&set1, "http://*.reddit.com/*"); + EXPECT_TRUE(set1.Contains(set2)); + EXPECT_FALSE(set2.Contains(set1)); +} + +TEST(URLPatternSetTest, Duplicates) { + URLPatternSet set1; + URLPatternSet set2; + + AddPattern(&set1, "http://www.google.com/*"); + AddPattern(&set2, "http://www.google.com/*"); + + AddPattern(&set1, "http://www.google.com/*"); + + // The sets should still be equal after adding a duplicate. + EXPECT_EQ(set2, set1); +} + +TEST(URLPatternSetTest, ToValueAndPopulate) { + URLPatternSet set1; + URLPatternSet set2; + + std::vector<std::string> patterns; + patterns.push_back("http://www.google.com/*"); + patterns.push_back("http://www.yahoo.com/*"); + + for (size_t i = 0; i < patterns.size(); ++i) + AddPattern(&set1, patterns[i]); + + std::string error; + bool allow_file_access = false; + scoped_ptr<base::ListValue> value(set1.ToValue()); + set2.Populate(*value, URLPattern::SCHEME_ALL, allow_file_access, &error); + EXPECT_EQ(set1, set2); + + set2.ClearPatterns(); + set2.Populate(patterns, URLPattern::SCHEME_ALL, allow_file_access, &error); + EXPECT_EQ(set1, set2); +} + +TEST(URLPatternSetTest, NwayUnion) { + std::string google_a = "http://www.google.com/a*"; + std::string google_b = "http://www.google.com/b*"; + std::string google_c = "http://www.google.com/c*"; + std::string yahoo_a = "http://www.yahoo.com/a*"; + std::string yahoo_b = "http://www.yahoo.com/b*"; + std::string yahoo_c = "http://www.yahoo.com/c*"; + std::string reddit_a = "http://www.reddit.com/a*"; + std::string reddit_b = "http://www.reddit.com/b*"; + std::string reddit_c = "http://www.reddit.com/c*"; + + // Empty list. + { + std::vector<URLPatternSet> empty; + + URLPatternSet result; + URLPatternSet::CreateUnion(empty, &result); + + URLPatternSet expected; + EXPECT_EQ(expected, result); + } + + // Singleton list. + { + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected = Patterns(google_a); + EXPECT_EQ(expected, result); + } + + // List with 2 elements. + { + + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a, google_b)); + test.push_back(Patterns(google_b, google_c)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected; + AddPattern(&expected, google_a); + AddPattern(&expected, google_b); + AddPattern(&expected, google_c); + EXPECT_EQ(expected, result); + } + + // List with 3 elements. + { + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a, google_b)); + test.push_back(Patterns(google_b, google_c)); + test.push_back(Patterns(yahoo_a, yahoo_b)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected; + AddPattern(&expected, google_a); + AddPattern(&expected, google_b); + AddPattern(&expected, google_c); + AddPattern(&expected, yahoo_a); + AddPattern(&expected, yahoo_b); + EXPECT_EQ(expected, result); + } + + // List with 7 elements. + { + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a)); + test.push_back(Patterns(google_b)); + test.push_back(Patterns(google_c)); + test.push_back(Patterns(yahoo_a)); + test.push_back(Patterns(yahoo_b)); + test.push_back(Patterns(yahoo_c)); + test.push_back(Patterns(reddit_a)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected; + AddPattern(&expected, google_a); + AddPattern(&expected, google_b); + AddPattern(&expected, google_c); + AddPattern(&expected, yahoo_a); + AddPattern(&expected, yahoo_b); + AddPattern(&expected, yahoo_c); + AddPattern(&expected, reddit_a); + EXPECT_EQ(expected, result); + } + + // List with 8 elements. + { + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a)); + test.push_back(Patterns(google_b)); + test.push_back(Patterns(google_c)); + test.push_back(Patterns(yahoo_a)); + test.push_back(Patterns(yahoo_b)); + test.push_back(Patterns(yahoo_c)); + test.push_back(Patterns(reddit_a)); + test.push_back(Patterns(reddit_b)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected; + AddPattern(&expected, google_a); + AddPattern(&expected, google_b); + AddPattern(&expected, google_c); + AddPattern(&expected, yahoo_a); + AddPattern(&expected, yahoo_b); + AddPattern(&expected, yahoo_c); + AddPattern(&expected, reddit_a); + AddPattern(&expected, reddit_b); + EXPECT_EQ(expected, result); + } + + // List with 9 elements. + { + + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a)); + test.push_back(Patterns(google_b)); + test.push_back(Patterns(google_c)); + test.push_back(Patterns(yahoo_a)); + test.push_back(Patterns(yahoo_b)); + test.push_back(Patterns(yahoo_c)); + test.push_back(Patterns(reddit_a)); + test.push_back(Patterns(reddit_b)); + test.push_back(Patterns(reddit_c)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected; + AddPattern(&expected, google_a); + AddPattern(&expected, google_b); + AddPattern(&expected, google_c); + AddPattern(&expected, yahoo_a); + AddPattern(&expected, yahoo_b); + AddPattern(&expected, yahoo_c); + AddPattern(&expected, reddit_a); + AddPattern(&expected, reddit_b); + AddPattern(&expected, reddit_c); + EXPECT_EQ(expected, result); + } +} + +} // namespace extensions diff --git a/chromium/extensions/common/url_pattern_unittest.cc b/chromium/extensions/common/url_pattern_unittest.cc new file mode 100644 index 00000000000..d1cc8c2103c --- /dev/null +++ b/chromium/extensions/common/url_pattern_unittest.cc @@ -0,0 +1,802 @@ +// 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 "base/memory/scoped_ptr.h" +#include "extensions/common/url_pattern.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace { + +// See url_pattern.h for examples of valid and invalid patterns. + +static const int kAllSchemes = + URLPattern::SCHEME_HTTP | + URLPattern::SCHEME_HTTPS | + URLPattern::SCHEME_FILE | + URLPattern::SCHEME_FTP | + URLPattern::SCHEME_CHROMEUI | + URLPattern::SCHEME_EXTENSION | + URLPattern::SCHEME_FILESYSTEM; + +TEST(ExtensionURLPatternTest, ParseInvalid) { + const struct { + const char* pattern; + URLPattern::ParseResult expected_result; + } kInvalidPatterns[] = { + { "http", URLPattern::PARSE_ERROR_MISSING_SCHEME_SEPARATOR }, + { "http:", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR }, + { "http:/", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR }, + { "about://", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR }, + { "http://", URLPattern::PARSE_ERROR_EMPTY_HOST }, + { "http:///", URLPattern::PARSE_ERROR_EMPTY_HOST }, + { "http://*foo/bar", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD }, + { "http://foo.*.bar/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD }, + { "http://fo.*.ba:123/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD }, + { "http:/bar", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR }, + { "http://bar", URLPattern::PARSE_ERROR_EMPTY_PATH }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kInvalidPatterns); ++i) { + URLPattern pattern(URLPattern::SCHEME_ALL); + EXPECT_EQ(kInvalidPatterns[i].expected_result, + pattern.Parse(kInvalidPatterns[i].pattern)) + << kInvalidPatterns[i].pattern; + } +}; + +TEST(ExtensionURLPatternTest, Ports) { + const struct { + const char* pattern; + URLPattern::ParseResult expected_result; + const char* expected_port; + } kTestPatterns[] = { + { "http://foo:1234/", URLPattern::PARSE_SUCCESS, "1234" }, + { "http://foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234" }, + { "http://*.foo:1234/", URLPattern::PARSE_SUCCESS, "1234" }, + { "http://*.foo:1234/bar", URLPattern::PARSE_SUCCESS,"1234" }, + { "http://:1234/", URLPattern::PARSE_SUCCESS, "1234" }, + { "http://foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" }, + { "http://foo:*/", URLPattern::PARSE_SUCCESS, "*" }, + { "http://*.foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" }, + { "http://foo:com/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" }, + { "http://foo:123456/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" }, + { "http://foo:80:80/monkey", URLPattern::PARSE_ERROR_INVALID_PORT, "*" }, + { "file://foo:1234/bar", URLPattern::PARSE_SUCCESS, "*" }, + { "chrome://foo:1234/bar", URLPattern::PARSE_ERROR_INVALID_PORT, "*" }, + + // Port-like strings in the path should not trigger a warning. + { "http://*/:1234", URLPattern::PARSE_SUCCESS, "*" }, + { "http://*.foo/bar:1234", URLPattern::PARSE_SUCCESS, "*" }, + { "http://foo/bar:1234/path", URLPattern::PARSE_SUCCESS,"*" }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestPatterns); ++i) { + URLPattern pattern(URLPattern::SCHEME_ALL); + EXPECT_EQ(kTestPatterns[i].expected_result, + pattern.Parse(kTestPatterns[i].pattern)) + << "Got unexpected result for URL pattern: " + << kTestPatterns[i].pattern; + EXPECT_EQ(kTestPatterns[i].expected_port, pattern.port()) + << "Got unexpected port for URL pattern: " << kTestPatterns[i].pattern; + } +}; + +// all pages for a given scheme +TEST(ExtensionURLPatternTest, Match1) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*/*")); + EXPECT_EQ("http", pattern.scheme()); + EXPECT_EQ("", pattern.host()); + EXPECT_TRUE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/*", pattern.path()); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://yahoo.com"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com/foo"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("https://google.com"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://74.125.127.100/search"))); +} + +// all domains +TEST(ExtensionURLPatternTest, Match2) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("https://*/foo*")); + EXPECT_EQ("https", pattern.scheme()); + EXPECT_EQ("", pattern.host()); + EXPECT_TRUE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/foo*", pattern.path()); + EXPECT_TRUE(pattern.MatchesURL(GURL("https://www.google.com/foo"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("https://www.google.com/foobar"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("http://www.google.com/foo"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("https://www.google.com/"))); + EXPECT_TRUE(pattern.MatchesURL( + GURL("filesystem:https://www.google.com/foobar/"))); +} + +// subdomains +TEST(URLPatternTest, Match3) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, + pattern.Parse("http://*.google.com/foo*bar")); + EXPECT_EQ("http", pattern.scheme()); + EXPECT_EQ("google.com", pattern.host()); + EXPECT_TRUE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/foo*bar", pattern.path()); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com/foobar"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.google.com/foo?bar"))); + EXPECT_TRUE(pattern.MatchesURL( + GURL("http://monkey.images.google.com/foooobar"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("http://yahoo.com/foobar"))); + EXPECT_TRUE(pattern.MatchesURL( + GURL("filesystem:http://google.com/foo/bar"))); + EXPECT_FALSE(pattern.MatchesURL( + GURL("filesystem:http://google.com/temporary/foobar"))); +} + +// glob escaping +TEST(ExtensionURLPatternTest, Match5) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file:///foo?bar\\*baz")); + EXPECT_EQ("file", pattern.scheme()); + EXPECT_EQ("", pattern.host()); + EXPECT_FALSE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/foo?bar\\*baz", pattern.path()); + EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo?bar\\hellobaz"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("file:///fooXbar\\hellobaz"))); +} + +// ip addresses +TEST(ExtensionURLPatternTest, Match6) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://127.0.0.1/*")); + EXPECT_EQ("http", pattern.scheme()); + EXPECT_EQ("127.0.0.1", pattern.host()); + EXPECT_FALSE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/*", pattern.path()); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1"))); +} + +// subdomain matching with ip addresses +TEST(ExtensionURLPatternTest, Match7) { + URLPattern pattern(kAllSchemes); + // allowed, but useless + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.0.0.1/*")); + EXPECT_EQ("http", pattern.scheme()); + EXPECT_EQ("0.0.1", pattern.host()); + EXPECT_TRUE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/*", pattern.path()); + // Subdomain matching is never done if the argument has an IP address host. + EXPECT_FALSE(pattern.MatchesURL(GURL("http://127.0.0.1"))); +}; + +// unicode +TEST(ExtensionURLPatternTest, Match8) { + URLPattern pattern(kAllSchemes); + // The below is the ASCII encoding of the following URL: + // http://*.\xe1\x80\xbf/a\xc2\x81\xe1* + EXPECT_EQ(URLPattern::PARSE_SUCCESS, + pattern.Parse("http://*.xn--gkd/a%C2%81%E1*")); + EXPECT_EQ("http", pattern.scheme()); + EXPECT_EQ("xn--gkd", pattern.host()); + EXPECT_TRUE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/a%C2%81%E1*", pattern.path()); + EXPECT_TRUE(pattern.MatchesURL( + GURL("http://abc.\xe1\x80\xbf/a\xc2\x81\xe1xyz"))); + EXPECT_TRUE(pattern.MatchesURL( + GURL("http://\xe1\x80\xbf/a\xc2\x81\xe1\xe1"))); +}; + +// chrome:// +TEST(ExtensionURLPatternTest, Match9) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("chrome://favicon/*")); + EXPECT_EQ("chrome", pattern.scheme()); + EXPECT_EQ("favicon", pattern.host()); + EXPECT_FALSE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/*", pattern.path()); + EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/https://google.com"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("chrome://history"))); +}; + +// *:// +TEST(ExtensionURLPatternTest, Match10) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("*://*/*")); + EXPECT_TRUE(pattern.MatchesScheme("http")); + EXPECT_TRUE(pattern.MatchesScheme("https")); + EXPECT_FALSE(pattern.MatchesScheme("chrome")); + EXPECT_FALSE(pattern.MatchesScheme("file")); + EXPECT_FALSE(pattern.MatchesScheme("ftp")); + EXPECT_TRUE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/*", pattern.path()); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("file:///foo/bar"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("file://localhost/foo/bar"))); +}; + +// <all_urls> +TEST(ExtensionURLPatternTest, Match11) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("<all_urls>")); + EXPECT_TRUE(pattern.MatchesScheme("chrome")); + EXPECT_TRUE(pattern.MatchesScheme("http")); + EXPECT_TRUE(pattern.MatchesScheme("https")); + EXPECT_TRUE(pattern.MatchesScheme("file")); + EXPECT_TRUE(pattern.MatchesScheme("filesystem")); + EXPECT_TRUE(pattern.MatchesScheme("chrome-extension")); + EXPECT_TRUE(pattern.match_subdomains()); + EXPECT_TRUE(pattern.match_all_urls()); + EXPECT_EQ("/*", pattern.path()); + EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo/bar"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo/bar"))); + + // Make sure the properties are the same when creating an <all_urls> pattern + // via SetMatchAllURLs and by parsing <all_urls>. + URLPattern pattern2(kAllSchemes); + pattern2.SetMatchAllURLs(true); + + EXPECT_EQ(pattern.valid_schemes(), pattern2.valid_schemes()); + EXPECT_EQ(pattern.match_subdomains(), pattern2.match_subdomains()); + EXPECT_EQ(pattern.path(), pattern2.path()); + EXPECT_EQ(pattern.match_all_urls(), pattern2.match_all_urls()); + EXPECT_EQ(pattern.scheme(), pattern2.scheme()); + EXPECT_EQ(pattern.port(), pattern2.port()); + EXPECT_EQ(pattern.GetAsString(), pattern2.GetAsString()); +}; + +// SCHEME_ALL matches all schemes. +TEST(ExtensionURLPatternTest, Match12) { + URLPattern pattern(URLPattern::SCHEME_ALL); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("<all_urls>")); + EXPECT_TRUE(pattern.MatchesScheme("chrome")); + EXPECT_TRUE(pattern.MatchesScheme("http")); + EXPECT_TRUE(pattern.MatchesScheme("https")); + EXPECT_TRUE(pattern.MatchesScheme("file")); + EXPECT_TRUE(pattern.MatchesScheme("filesystem")); + EXPECT_TRUE(pattern.MatchesScheme("javascript")); + EXPECT_TRUE(pattern.MatchesScheme("data")); + EXPECT_TRUE(pattern.MatchesScheme("about")); + EXPECT_TRUE(pattern.MatchesScheme("chrome-extension")); + EXPECT_TRUE(pattern.match_subdomains()); + EXPECT_TRUE(pattern.match_all_urls()); + EXPECT_EQ("/*", pattern.path()); + EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo/bar"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo/bar"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://newtab"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("about:blank"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("about:version"))); + EXPECT_TRUE(pattern.MatchesURL( + GURL("data:text/html;charset=utf-8,<html>asdf</html>"))); +}; + +static const struct MatchPatterns { + const char* pattern; + const char* matches; +} kMatch13UrlPatternTestCases[] = { + {"about:*", "about:blank"}, + {"about:blank", "about:blank"}, + {"about:*", "about:version"}, + {"chrome-extension://*/*", "chrome-extension://FTW"}, + {"data:*", "data:monkey"}, + {"javascript:*", "javascript:atemyhomework"}, +}; + +// SCHEME_ALL and specific schemes. +TEST(ExtensionURLPatternTest, Match13) { + for (size_t i = 0; i < arraysize(kMatch13UrlPatternTestCases); ++i) { + URLPattern pattern(URLPattern::SCHEME_ALL); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, + pattern.Parse(kMatch13UrlPatternTestCases[i].pattern)) + << " while parsing " << kMatch13UrlPatternTestCases[i].pattern; + EXPECT_TRUE(pattern.MatchesURL( + GURL(kMatch13UrlPatternTestCases[i].matches))) + << " while matching " << kMatch13UrlPatternTestCases[i].matches; + } + + // Negative test. + URLPattern pattern(URLPattern::SCHEME_ALL); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("data:*")); + EXPECT_FALSE(pattern.MatchesURL(GURL("about:blank"))); +}; + +// file scheme with empty hostname +TEST(ExtensionURLPatternTest, Match14) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file:///foo*")); + EXPECT_EQ("file", pattern.scheme()); + EXPECT_EQ("", pattern.host()); + EXPECT_FALSE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/foo*", pattern.path()); + EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foobar"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo"))); +} + +// file scheme without hostname part +TEST(ExtensionURLPatternTest, Match15) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file://foo*")); + EXPECT_EQ("file", pattern.scheme()); + EXPECT_EQ("", pattern.host()); + EXPECT_FALSE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/foo*", pattern.path()); + EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foobar"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo"))); +} + +// file scheme with hostname +TEST(ExtensionURLPatternTest, Match16) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file://localhost/foo*")); + EXPECT_EQ("file", pattern.scheme()); + // Since hostname is ignored for file://. + EXPECT_EQ("", pattern.host()); + EXPECT_FALSE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/foo*", pattern.path()); + EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foobar"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo"))); +} + +// Specific port +TEST(ExtensionURLPatternTest, Match17) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, + pattern.Parse("http://www.example.com:80/foo")); + EXPECT_EQ("http", pattern.scheme()); + EXPECT_EQ("www.example.com", pattern.host()); + EXPECT_FALSE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/foo", pattern.path()); + EXPECT_EQ("80", pattern.port()); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com:80/foo"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com/foo"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("http://www.example.com:8080/foo"))); + EXPECT_FALSE(pattern.MatchesURL( + GURL("filesystem:http://www.example.com:8080/foo/"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("filesystem:http://www.example.com/f/foo"))); +} + +// Explicit port wildcard +TEST(ExtensionURLPatternTest, Match18) { + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, + pattern.Parse("http://www.example.com:*/foo")); + EXPECT_EQ("http", pattern.scheme()); + EXPECT_EQ("www.example.com", pattern.host()); + EXPECT_FALSE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/foo", pattern.path()); + EXPECT_EQ("*", pattern.port()); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com:80/foo"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com/foo"))); + EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com:8080/foo"))); + EXPECT_FALSE(pattern.MatchesURL( + GURL("filesystem:http://www.example.com:8080/foo/"))); +} + +// chrome-extension:// +TEST(ExtensionURLPatternTest, Match19) { + URLPattern pattern(URLPattern::SCHEME_EXTENSION); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, + pattern.Parse("chrome-extension://ftw/*")); + EXPECT_EQ("chrome-extension", pattern.scheme()); + EXPECT_EQ("ftw", pattern.host()); + EXPECT_FALSE(pattern.match_subdomains()); + EXPECT_FALSE(pattern.match_all_urls()); + EXPECT_EQ("/*", pattern.path()); + EXPECT_TRUE(pattern.MatchesURL(GURL("chrome-extension://ftw"))); + EXPECT_TRUE(pattern.MatchesURL( + GURL("chrome-extension://ftw/http://google.com"))); + EXPECT_TRUE(pattern.MatchesURL( + GURL("chrome-extension://ftw/https://google.com"))); + EXPECT_FALSE(pattern.MatchesURL(GURL("chrome-extension://foobar"))); + EXPECT_TRUE(pattern.MatchesURL( + GURL("filesystem:chrome-extension://ftw/t/file.txt"))); +}; + +static const struct GetAsStringPatterns { + const char* pattern; +} kGetAsStringTestCases[] = { + { "http://www/" }, + { "http://*/*" }, + { "chrome://*/*" }, + { "chrome://newtab/" }, + { "about:*" }, + { "about:blank" }, + { "chrome-extension://*/*" }, + { "chrome-extension://FTW/" }, + { "data:*" }, + { "data:monkey" }, + { "javascript:*" }, + { "javascript:atemyhomework" }, + { "http://www.example.com:8080/foo" }, +}; + +TEST(ExtensionURLPatternTest, GetAsString) { + for (size_t i = 0; i < arraysize(kGetAsStringTestCases); ++i) { + URLPattern pattern(URLPattern::SCHEME_ALL); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, + pattern.Parse(kGetAsStringTestCases[i].pattern)) + << "Error parsing " << kGetAsStringTestCases[i].pattern; + EXPECT_EQ(kGetAsStringTestCases[i].pattern, + pattern.GetAsString()); + } +} + +testing::AssertionResult Overlaps(const URLPattern& pattern1, + const URLPattern& pattern2) { + if (!pattern1.OverlapsWith(pattern2)) { + return testing::AssertionFailure() + << pattern1.GetAsString() << " does not overlap " << + pattern2.GetAsString(); + } + if (!pattern2.OverlapsWith(pattern1)) { + return testing::AssertionFailure() + << pattern2.GetAsString() << " does not overlap " << + pattern1.GetAsString(); + } + return testing::AssertionSuccess() + << pattern1.GetAsString() << " overlaps with " << pattern2.GetAsString(); +} + +TEST(ExtensionURLPatternTest, Overlaps) { + URLPattern pattern1(kAllSchemes, "http://www.google.com/foo/*"); + URLPattern pattern2(kAllSchemes, "https://www.google.com/foo/*"); + URLPattern pattern3(kAllSchemes, "http://*.google.com/foo/*"); + URLPattern pattern4(kAllSchemes, "http://*.yahooo.com/foo/*"); + URLPattern pattern5(kAllSchemes, "http://www.yahooo.com/bar/*"); + URLPattern pattern6(kAllSchemes, + "http://www.yahooo.com/bar/baz/*"); + URLPattern pattern7(kAllSchemes, "file:///*"); + URLPattern pattern8(kAllSchemes, "*://*/*"); + URLPattern pattern9(URLPattern::SCHEME_HTTPS, "*://*/*"); + URLPattern pattern10(kAllSchemes, "<all_urls>"); + + EXPECT_TRUE(Overlaps(pattern1, pattern1)); + EXPECT_FALSE(Overlaps(pattern1, pattern2)); + EXPECT_TRUE(Overlaps(pattern1, pattern3)); + EXPECT_FALSE(Overlaps(pattern1, pattern4)); + EXPECT_FALSE(Overlaps(pattern3, pattern4)); + EXPECT_FALSE(Overlaps(pattern4, pattern5)); + EXPECT_TRUE(Overlaps(pattern5, pattern6)); + + // Test that scheme restrictions work. + EXPECT_TRUE(Overlaps(pattern1, pattern8)); + EXPECT_FALSE(Overlaps(pattern1, pattern9)); + EXPECT_TRUE(Overlaps(pattern1, pattern10)); + + // Test that '<all_urls>' includes file URLs, while scheme '*' does not. + EXPECT_FALSE(Overlaps(pattern7, pattern8)); + EXPECT_TRUE(Overlaps(pattern7, pattern10)); + + // Test that wildcard schemes are handled correctly, especially when compared + // to each-other. + URLPattern pattern11(kAllSchemes, "http://example.com/*"); + URLPattern pattern12(kAllSchemes, "*://example.com/*"); + URLPattern pattern13(kAllSchemes, "*://example.com/foo/*"); + URLPattern pattern14(kAllSchemes, "*://google.com/*"); + EXPECT_TRUE(Overlaps(pattern8, pattern12)); + EXPECT_TRUE(Overlaps(pattern9, pattern12)); + EXPECT_TRUE(Overlaps(pattern10, pattern12)); + EXPECT_TRUE(Overlaps(pattern11, pattern12)); + EXPECT_TRUE(Overlaps(pattern12, pattern13)); + EXPECT_TRUE(Overlaps(pattern11, pattern13)); + EXPECT_FALSE(Overlaps(pattern14, pattern12)); + EXPECT_FALSE(Overlaps(pattern14, pattern13)); +} + +TEST(ExtensionURLPatternTest, ConvertToExplicitSchemes) { + URLPatternList all_urls(URLPattern( + kAllSchemes, + "<all_urls>").ConvertToExplicitSchemes()); + + URLPatternList all_schemes(URLPattern( + kAllSchemes, + "*://google.com/foo").ConvertToExplicitSchemes()); + + URLPatternList monkey(URLPattern( + URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS | + URLPattern::SCHEME_FTP, + "http://google.com/monkey").ConvertToExplicitSchemes()); + + ASSERT_EQ(7u, all_urls.size()); + ASSERT_EQ(2u, all_schemes.size()); + ASSERT_EQ(1u, monkey.size()); + + EXPECT_EQ("http://*/*", all_urls[0].GetAsString()); + EXPECT_EQ("https://*/*", all_urls[1].GetAsString()); + EXPECT_EQ("file:///*", all_urls[2].GetAsString()); + EXPECT_EQ("ftp://*/*", all_urls[3].GetAsString()); + EXPECT_EQ("chrome://*/*", all_urls[4].GetAsString()); + + EXPECT_EQ("http://google.com/foo", all_schemes[0].GetAsString()); + EXPECT_EQ("https://google.com/foo", all_schemes[1].GetAsString()); + + EXPECT_EQ("http://google.com/monkey", monkey[0].GetAsString()); +} + +TEST(ExtensionURLPatternTest, IgnorePorts) { + std::string pattern_str = "http://www.example.com:8080/foo"; + GURL url("http://www.example.com:1234/foo"); + + URLPattern pattern(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse(pattern_str)); + + EXPECT_EQ(pattern_str, pattern.GetAsString()); + EXPECT_FALSE(pattern.MatchesURL(url)); +} + +TEST(ExtensionURLPatternTest, IgnoreMissingBackslashes) { + std::string pattern_str1 = "http://www.example.com/example"; + std::string pattern_str2 = "http://www.example.com/example/*"; + GURL url1("http://www.example.com/example"); + GURL url2("http://www.example.com/example/"); + + URLPattern pattern1(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern1.Parse(pattern_str1)); + URLPattern pattern2(kAllSchemes); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern2.Parse(pattern_str2)); + + // Same patterns should match same urls. + EXPECT_TRUE(pattern1.MatchesURL(url1)); + EXPECT_TRUE(pattern2.MatchesURL(url2)); + // The not terminated path should match the terminated pattern. + EXPECT_TRUE(pattern2.MatchesURL(url1)); + // The terminated path however should not match the unterminated pattern. + EXPECT_FALSE(pattern1.MatchesURL(url2)); +} + +TEST(ExtensionURLPatternTest, Equals) { + const struct { + const char* pattern1; + const char* pattern2; + bool expected_equal; + } kEqualsTestCases[] = { + // schemes + { "http://en.google.com/blah/*/foo", + "https://en.google.com/blah/*/foo", + false + }, + { "https://en.google.com/blah/*/foo", + "https://en.google.com/blah/*/foo", + true + }, + { "https://en.google.com/blah/*/foo", + "ftp://en.google.com/blah/*/foo", + false + }, + + // subdomains + { "https://en.google.com/blah/*/foo", + "https://fr.google.com/blah/*/foo", + false + }, + { "https://www.google.com/blah/*/foo", + "https://*.google.com/blah/*/foo", + false + }, + { "https://*.google.com/blah/*/foo", + "https://*.google.com/blah/*/foo", + true + }, + + // domains + { "http://en.example.com/blah/*/foo", + "http://en.google.com/blah/*/foo", + false + }, + + // ports + { "http://en.google.com:8000/blah/*/foo", + "http://en.google.com/blah/*/foo", + false + }, + { "http://fr.google.com:8000/blah/*/foo", + "http://fr.google.com:8000/blah/*/foo", + true + }, + { "http://en.google.com:8000/blah/*/foo", + "http://en.google.com:8080/blah/*/foo", + false + }, + + // paths + { "http://en.google.com/blah/*/foo", + "http://en.google.com/blah/*", + false + }, + { "http://en.google.com/*", + "http://en.google.com/", + false + }, + { "http://en.google.com/*", + "http://en.google.com/*", + true + }, + + // all_urls + { "<all_urls>", + "<all_urls>", + true + }, + { "<all_urls>", + "http://*/*", + false + } + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kEqualsTestCases); ++i) { + std::string message = kEqualsTestCases[i].pattern1; + message += " "; + message += kEqualsTestCases[i].pattern2; + + URLPattern pattern1(URLPattern::SCHEME_ALL); + URLPattern pattern2(URLPattern::SCHEME_ALL); + + pattern1.Parse(kEqualsTestCases[i].pattern1); + pattern2.Parse(kEqualsTestCases[i].pattern2); + EXPECT_EQ(kEqualsTestCases[i].expected_equal, pattern1 == pattern2) + << message; + } +} + +TEST(ExtensionURLPatternTest, CanReusePatternWithParse) { + URLPattern pattern1(URLPattern::SCHEME_ALL); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern1.Parse("http://aa.com/*")); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern1.Parse("http://bb.com/*")); + + EXPECT_TRUE(pattern1.MatchesURL(GURL("http://bb.com/path"))); + EXPECT_FALSE(pattern1.MatchesURL(GURL("http://aa.com/path"))); + + URLPattern pattern2(URLPattern::SCHEME_ALL, URLPattern::kAllUrlsPattern); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern2.Parse("http://aa.com/*")); + + EXPECT_FALSE(pattern2.MatchesURL(GURL("http://bb.com/path"))); + EXPECT_TRUE(pattern2.MatchesURL(GURL("http://aa.com/path"))); + EXPECT_FALSE(pattern2.MatchesURL(GURL("http://sub.aa.com/path"))); + + URLPattern pattern3(URLPattern::SCHEME_ALL, "http://aa.com/*"); + EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern3.Parse("http://aa.com:88/*")); + EXPECT_FALSE(pattern3.MatchesURL(GURL("http://aa.com/path"))); + EXPECT_TRUE(pattern3.MatchesURL(GURL("http://aa.com:88/path"))); +} + +// Returns success if neither |a| nor |b| encompasses the other. +testing::AssertionResult NeitherContains(const URLPattern& a, + const URLPattern& b) { + if (a.Contains(b)) + return testing::AssertionFailure() << a.GetAsString() << " encompasses " << + b.GetAsString(); + if (b.Contains(a)) + return testing::AssertionFailure() << b.GetAsString() << " encompasses " << + a.GetAsString(); + return testing::AssertionSuccess() << + "Neither " << a.GetAsString() << " nor " << b.GetAsString() << + " encompass the other"; +} + +// Returns success if |a| encompasses |b| but not the other way around. +testing::AssertionResult StrictlyContains(const URLPattern& a, + const URLPattern& b) { + if (!a.Contains(b)) + return testing::AssertionFailure() << a.GetAsString() << + " does not encompass " << + b.GetAsString(); + if (b.Contains(a)) + return testing::AssertionFailure() << b.GetAsString() << " encompasses " << + a.GetAsString(); + return testing::AssertionSuccess() << a.GetAsString() << + " strictly encompasses " << + b.GetAsString(); +} + +TEST(ExtensionURLPatternTest, Subset) { + URLPattern pattern1(kAllSchemes, "http://www.google.com/foo/*"); + URLPattern pattern2(kAllSchemes, "https://www.google.com/foo/*"); + URLPattern pattern3(kAllSchemes, "http://*.google.com/foo/*"); + URLPattern pattern4(kAllSchemes, "http://*.yahooo.com/foo/*"); + URLPattern pattern5(kAllSchemes, "http://www.yahooo.com/bar/*"); + URLPattern pattern6(kAllSchemes, "http://www.yahooo.com/bar/baz/*"); + URLPattern pattern7(kAllSchemes, "file:///*"); + URLPattern pattern8(kAllSchemes, "*://*/*"); + URLPattern pattern9(URLPattern::SCHEME_HTTPS, "*://*/*"); + URLPattern pattern10(kAllSchemes, "<all_urls>"); + URLPattern pattern11(kAllSchemes, "http://example.com/*"); + URLPattern pattern12(kAllSchemes, "*://example.com/*"); + URLPattern pattern13(kAllSchemes, "*://example.com/foo/*"); + + // All patterns should encompass themselves. + EXPECT_TRUE(pattern1.Contains(pattern1)); + EXPECT_TRUE(pattern2.Contains(pattern2)); + EXPECT_TRUE(pattern3.Contains(pattern3)); + EXPECT_TRUE(pattern4.Contains(pattern4)); + EXPECT_TRUE(pattern5.Contains(pattern5)); + EXPECT_TRUE(pattern6.Contains(pattern6)); + EXPECT_TRUE(pattern7.Contains(pattern7)); + EXPECT_TRUE(pattern8.Contains(pattern8)); + EXPECT_TRUE(pattern9.Contains(pattern9)); + EXPECT_TRUE(pattern10.Contains(pattern10)); + EXPECT_TRUE(pattern11.Contains(pattern11)); + EXPECT_TRUE(pattern12.Contains(pattern12)); + EXPECT_TRUE(pattern13.Contains(pattern13)); + + // pattern1's relationship to the other patterns. + EXPECT_TRUE(NeitherContains(pattern1, pattern2)); + EXPECT_TRUE(StrictlyContains(pattern3, pattern1)); + EXPECT_TRUE(NeitherContains(pattern1, pattern4)); + EXPECT_TRUE(NeitherContains(pattern1, pattern5)); + EXPECT_TRUE(NeitherContains(pattern1, pattern6)); + EXPECT_TRUE(NeitherContains(pattern1, pattern7)); + EXPECT_TRUE(StrictlyContains(pattern8, pattern1)); + EXPECT_TRUE(NeitherContains(pattern1, pattern9)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern1)); + EXPECT_TRUE(NeitherContains(pattern1, pattern11)); + EXPECT_TRUE(NeitherContains(pattern1, pattern12)); + EXPECT_TRUE(NeitherContains(pattern1, pattern13)); + + // pattern2's relationship to the other patterns. + EXPECT_TRUE(NeitherContains(pattern2, pattern3)); + EXPECT_TRUE(NeitherContains(pattern2, pattern4)); + EXPECT_TRUE(NeitherContains(pattern2, pattern5)); + EXPECT_TRUE(NeitherContains(pattern2, pattern6)); + EXPECT_TRUE(NeitherContains(pattern2, pattern7)); + EXPECT_TRUE(StrictlyContains(pattern8, pattern2)); + EXPECT_TRUE(StrictlyContains(pattern9, pattern2)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern2)); + EXPECT_TRUE(NeitherContains(pattern2, pattern11)); + EXPECT_TRUE(NeitherContains(pattern2, pattern12)); + EXPECT_TRUE(NeitherContains(pattern2, pattern13)); + + // Specifically test file:// URLs. + EXPECT_TRUE(NeitherContains(pattern7, pattern8)); + EXPECT_TRUE(NeitherContains(pattern7, pattern9)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern7)); + + // <all_urls> encompasses everything. + EXPECT_TRUE(StrictlyContains(pattern10, pattern1)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern2)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern3)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern4)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern5)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern6)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern7)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern8)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern9)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern11)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern12)); + EXPECT_TRUE(StrictlyContains(pattern10, pattern13)); + + // More... + EXPECT_TRUE(StrictlyContains(pattern12, pattern11)); + EXPECT_TRUE(NeitherContains(pattern11, pattern13)); + EXPECT_TRUE(StrictlyContains(pattern12, pattern13)); +} + +} // namespace diff --git a/chromium/extensions/common/user_script.cc b/chromium/extensions/common/user_script.cc new file mode 100644 index 00000000000..84c1236c604 --- /dev/null +++ b/chromium/extensions/common/user_script.cc @@ -0,0 +1,236 @@ +// Copyright 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 "extensions/common/user_script.h" + +#include "base/command_line.h" +#include "base/pickle.h" +#include "base/strings/string_util.h" +#include "extensions/common/switches.h" + +namespace { + +bool UrlMatchesGlobs(const std::vector<std::string>* globs, + const GURL& url) { + for (std::vector<std::string>::const_iterator glob = globs->begin(); + glob != globs->end(); ++glob) { + if (MatchPattern(url.spec(), *glob)) + return true; + } + + return false; +} + +} // namespace + +namespace extensions { + +// The bitmask for valid user script injectable schemes used by URLPattern. +enum { + kValidUserScriptSchemes = URLPattern::SCHEME_CHROMEUI | + URLPattern::SCHEME_HTTP | + URLPattern::SCHEME_HTTPS | + URLPattern::SCHEME_FILE | + URLPattern::SCHEME_FTP +}; + +// static +const char UserScript::kFileExtension[] = ".user.js"; + +bool UserScript::IsURLUserScript(const GURL& url, + const std::string& mime_type) { + return EndsWith(url.ExtractFileName(), kFileExtension, false) && + mime_type != "text/html"; +} + +// static +int UserScript::ValidUserScriptSchemes(bool canExecuteScriptEverywhere) { + if (canExecuteScriptEverywhere) + return URLPattern::SCHEME_ALL; + int valid_schemes = kValidUserScriptSchemes; + if (!CommandLine::ForCurrentProcess()->HasSwitch( + switches::kExtensionsOnChromeURLs)) { + valid_schemes &= ~URLPattern::SCHEME_CHROMEUI; + } + return valid_schemes; +} + +UserScript::File::File(const base::FilePath& extension_root, + const base::FilePath& relative_path, + const GURL& url) + : extension_root_(extension_root), + relative_path_(relative_path), + url_(url) { +} + +UserScript::File::File() {} + +UserScript::File::~File() {} + +UserScript::UserScript() + : run_location_(DOCUMENT_IDLE), emulate_greasemonkey_(false), + match_all_frames_(false), incognito_enabled_(false) { +} + +UserScript::~UserScript() { +} + +void UserScript::add_url_pattern(const URLPattern& pattern) { + url_set_.AddPattern(pattern); +} + +void UserScript::add_exclude_url_pattern(const URLPattern& pattern) { + exclude_url_set_.AddPattern(pattern); +} + +bool UserScript::MatchesURL(const GURL& url) const { + if (!url_set_.is_empty()) { + if (!url_set_.MatchesURL(url)) + return false; + } + + if (!exclude_url_set_.is_empty()) { + if (exclude_url_set_.MatchesURL(url)) + return false; + } + + if (!globs_.empty()) { + if (!UrlMatchesGlobs(&globs_, url)) + return false; + } + + if (!exclude_globs_.empty()) { + if (UrlMatchesGlobs(&exclude_globs_, url)) + return false; + } + + return true; +} + +void UserScript::File::Pickle(::Pickle* pickle) const { + pickle->WriteString(url_.spec()); + // Do not write path. It's not needed in the renderer. + // Do not write content. It will be serialized by other means. +} + +void UserScript::File::Unpickle(const ::Pickle& pickle, PickleIterator* iter) { + // Read the url from the pickle. + std::string url; + CHECK(pickle.ReadString(iter, &url)); + set_url(GURL(url)); +} + +void UserScript::Pickle(::Pickle* pickle) const { + // Write the simple types to the pickle. + pickle->WriteInt(run_location()); + pickle->WriteString(extension_id()); + pickle->WriteBool(emulate_greasemonkey()); + pickle->WriteBool(match_all_frames()); + pickle->WriteBool(is_incognito_enabled()); + + PickleGlobs(pickle, globs_); + PickleGlobs(pickle, exclude_globs_); + PickleURLPatternSet(pickle, url_set_); + PickleURLPatternSet(pickle, exclude_url_set_); + PickleScripts(pickle, js_scripts_); + PickleScripts(pickle, css_scripts_); +} + +void UserScript::PickleGlobs(::Pickle* pickle, + const std::vector<std::string>& globs) const { + pickle->WriteUInt64(globs.size()); + for (std::vector<std::string>::const_iterator glob = globs.begin(); + glob != globs.end(); ++glob) { + pickle->WriteString(*glob); + } +} + +void UserScript::PickleURLPatternSet(::Pickle* pickle, + const URLPatternSet& pattern_list) const { + pickle->WriteUInt64(pattern_list.patterns().size()); + for (URLPatternSet::const_iterator pattern = pattern_list.begin(); + pattern != pattern_list.end(); ++pattern) { + pickle->WriteInt(pattern->valid_schemes()); + pickle->WriteString(pattern->GetAsString()); + } +} + +void UserScript::PickleScripts(::Pickle* pickle, + const FileList& scripts) const { + pickle->WriteUInt64(scripts.size()); + for (FileList::const_iterator file = scripts.begin(); + file != scripts.end(); ++file) { + file->Pickle(pickle); + } +} + +void UserScript::Unpickle(const ::Pickle& pickle, PickleIterator* iter) { + // Read the run location. + int run_location = 0; + CHECK(pickle.ReadInt(iter, &run_location)); + CHECK(run_location >= 0 && run_location < RUN_LOCATION_LAST); + run_location_ = static_cast<RunLocation>(run_location); + + CHECK(pickle.ReadString(iter, &extension_id_)); + CHECK(pickle.ReadBool(iter, &emulate_greasemonkey_)); + CHECK(pickle.ReadBool(iter, &match_all_frames_)); + CHECK(pickle.ReadBool(iter, &incognito_enabled_)); + + UnpickleGlobs(pickle, iter, &globs_); + UnpickleGlobs(pickle, iter, &exclude_globs_); + UnpickleURLPatternSet(pickle, iter, &url_set_); + UnpickleURLPatternSet(pickle, iter, &exclude_url_set_); + UnpickleScripts(pickle, iter, &js_scripts_); + UnpickleScripts(pickle, iter, &css_scripts_); +} + +void UserScript::UnpickleGlobs(const ::Pickle& pickle, PickleIterator* iter, + std::vector<std::string>* globs) { + uint64 num_globs = 0; + CHECK(pickle.ReadUInt64(iter, &num_globs)); + globs->clear(); + for (uint64 i = 0; i < num_globs; ++i) { + std::string glob; + CHECK(pickle.ReadString(iter, &glob)); + globs->push_back(glob); + } +} + +void UserScript::UnpickleURLPatternSet(const ::Pickle& pickle, + PickleIterator* iter, + URLPatternSet* pattern_list) { + uint64 num_patterns = 0; + CHECK(pickle.ReadUInt64(iter, &num_patterns)); + + pattern_list->ClearPatterns(); + for (uint64 i = 0; i < num_patterns; ++i) { + int valid_schemes; + CHECK(pickle.ReadInt(iter, &valid_schemes)); + + std::string pattern_str; + CHECK(pickle.ReadString(iter, &pattern_str)); + + URLPattern pattern(kValidUserScriptSchemes); + URLPattern::ParseResult result = pattern.Parse(pattern_str); + CHECK(URLPattern::PARSE_SUCCESS == result) << + URLPattern::GetParseResultString(result) << " " << pattern_str.c_str(); + + pattern.SetValidSchemes(valid_schemes); + pattern_list->AddPattern(pattern); + } +} + +void UserScript::UnpickleScripts(const ::Pickle& pickle, PickleIterator* iter, + FileList* scripts) { + uint64 num_files = 0; + CHECK(pickle.ReadUInt64(iter, &num_files)); + scripts->clear(); + for (uint64 i = 0; i < num_files; ++i) { + File file; + file.Unpickle(pickle, iter); + scripts->push_back(file); + } +} + +} // namespace extensions diff --git a/chromium/extensions/common/user_script.h b/chromium/extensions/common/user_script.h new file mode 100644 index 00000000000..76b64d459a7 --- /dev/null +++ b/chromium/extensions/common/user_script.h @@ -0,0 +1,260 @@ +// Copyright 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. + +#ifndef EXTENSIONS_COMMON_USER_SCRIPT_H_ +#define EXTENSIONS_COMMON_USER_SCRIPT_H_ + +#include <string> +#include <vector> + +#include "base/files/file_path.h" +#include "base/strings/string_piece.h" +#include "extensions/common/url_pattern.h" +#include "extensions/common/url_pattern_set.h" +#include "url/gurl.h" + +class Pickle; +class PickleIterator; + +namespace extensions { + +// Represents a user script, either a standalone one, or one that is part of an +// extension. +class UserScript { + public: + // The file extension for standalone user scripts. + static const char kFileExtension[]; + + // Check if a URL should be treated as a user script and converted to an + // extension. + static bool IsURLUserScript(const GURL& url, const std::string& mime_type); + + // Get the valid user script schemes for the current process. If + // canExecuteScriptEverywhere is true, this will return ALL_SCHEMES. + static int ValidUserScriptSchemes(bool canExecuteScriptEverywhere = false); + + // Locations that user scripts can be run inside the document. + enum RunLocation { + UNDEFINED, + DOCUMENT_START, // After the documentElement is created, but before + // anything else happens. + DOCUMENT_END, // After the entire document is parsed. Same as + // DOMContentLoaded. + DOCUMENT_IDLE, // Sometime after DOMContentLoaded, as soon as the document + // is "idle". Currently this uses the simple heuristic of: + // min(DOM_CONTENT_LOADED + TIMEOUT, ONLOAD), but no + // particular injection point is guaranteed. + RUN_LOCATION_LAST // Leave this as the last item. + }; + + // Holds actual script file info. + class File { + public: + File(const base::FilePath& extension_root, + const base::FilePath& relative_path, + const GURL& url); + File(); + ~File(); + + const base::FilePath& extension_root() const { return extension_root_; } + const base::FilePath& relative_path() const { return relative_path_; } + + const GURL& url() const { return url_; } + void set_url(const GURL& url) { url_ = url; } + + // If external_content_ is set returns it as content otherwise it returns + // content_ + const base::StringPiece GetContent() const { + if (external_content_.data()) + return external_content_; + else + return content_; + } + void set_external_content(const base::StringPiece& content) { + external_content_ = content; + } + void set_content(const base::StringPiece& content) { + content_.assign(content.begin(), content.end()); + } + + // Serialization support. The content and FilePath members will not be + // serialized! + void Pickle(::Pickle* pickle) const; + void Unpickle(const ::Pickle& pickle, PickleIterator* iter); + + private: + // Where the script file lives on the disk. We keep the path split so that + // it can be localized at will. + base::FilePath extension_root_; + base::FilePath relative_path_; + + // The url to this scipt file. + GURL url_; + + // The script content. It can be set to either loaded_content_ or + // externally allocated string. + base::StringPiece external_content_; + + // Set when the content is loaded by LoadContent + std::string content_; + }; + + typedef std::vector<File> FileList; + + // Constructor. Default the run location to document end, which is like + // Greasemonkey and probably more useful for typical scripts. + UserScript(); + ~UserScript(); + + const std::string& name_space() const { return name_space_; } + void set_name_space(const std::string& name_space) { + name_space_ = name_space; + } + + const std::string& name() const { return name_; } + void set_name(const std::string& name) { name_ = name; } + + const std::string& version() const { return version_; } + void set_version(const std::string& version) { + version_ = version; + } + + const std::string& description() const { return description_; } + void set_description(const std::string& description) { + description_ = description; + } + + // The place in the document to run the script. + RunLocation run_location() const { return run_location_; } + void set_run_location(RunLocation location) { run_location_ = location; } + + // Whether to emulate greasemonkey when running this script. + bool emulate_greasemonkey() const { return emulate_greasemonkey_; } + void set_emulate_greasemonkey(bool val) { emulate_greasemonkey_ = val; } + + // Whether to match all frames, or only the top one. + bool match_all_frames() const { return match_all_frames_; } + void set_match_all_frames(bool val) { match_all_frames_ = val; } + + // The globs, if any, that determine which pages this script runs against. + // These are only used with "standalone" Greasemonkey-like user scripts. + const std::vector<std::string>& globs() const { return globs_; } + void add_glob(const std::string& glob) { globs_.push_back(glob); } + void clear_globs() { globs_.clear(); } + const std::vector<std::string>& exclude_globs() const { + return exclude_globs_; + } + void add_exclude_glob(const std::string& glob) { + exclude_globs_.push_back(glob); + } + void clear_exclude_globs() { exclude_globs_.clear(); } + + // The URLPatterns, if any, that determine which pages this script runs + // against. + const URLPatternSet& url_patterns() const { return url_set_; } + void add_url_pattern(const URLPattern& pattern); + const URLPatternSet& exclude_url_patterns() const { + return exclude_url_set_; + } + void add_exclude_url_pattern(const URLPattern& pattern); + + // List of js scripts for this user script + FileList& js_scripts() { return js_scripts_; } + const FileList& js_scripts() const { return js_scripts_; } + + // List of css scripts for this user script + FileList& css_scripts() { return css_scripts_; } + const FileList& css_scripts() const { return css_scripts_; } + + const std::string& extension_id() const { return extension_id_; } + void set_extension_id(const std::string& id) { extension_id_ = id; } + + bool is_incognito_enabled() const { return incognito_enabled_; } + void set_incognito_enabled(bool enabled) { incognito_enabled_ = enabled; } + + bool is_standalone() const { return extension_id_.empty(); } + + // Returns true if the script should be applied to the specified URL, false + // otherwise. + bool MatchesURL(const GURL& url) const; + + // Serialize the UserScript into a pickle. The content of the scripts and + // paths to UserScript::Files will not be serialized! + void Pickle(::Pickle* pickle) const; + + // Deserialize the script from a pickle. Note that this always succeeds + // because presumably we were the one that pickled it, and we did it + // correctly. + void Unpickle(const ::Pickle& pickle, PickleIterator* iter); + + private: + // Pickle helper functions used to pickle the individual types of components. + void PickleGlobs(::Pickle* pickle, + const std::vector<std::string>& globs) const; + void PickleURLPatternSet(::Pickle* pickle, + const URLPatternSet& pattern_list) const; + void PickleScripts(::Pickle* pickle, const FileList& scripts) const; + + // Unpickle helper functions used to unpickle individual types of components. + void UnpickleGlobs(const ::Pickle& pickle, PickleIterator* iter, + std::vector<std::string>* globs); + void UnpickleURLPatternSet(const ::Pickle& pickle, PickleIterator* iter, + URLPatternSet* pattern_list); + void UnpickleScripts(const ::Pickle& pickle, PickleIterator* iter, + FileList* scripts); + + // The location to run the script inside the document. + RunLocation run_location_; + + // The namespace of the script. This is used by Greasemonkey in the same way + // as XML namespaces. Only used when parsing Greasemonkey-style scripts. + std::string name_space_; + + // The script's name. Only used when parsing Greasemonkey-style scripts. + std::string name_; + + // A longer description. Only used when parsing Greasemonkey-style scripts. + std::string description_; + + // A version number of the script. Only used when parsing Greasemonkey-style + // scripts. + std::string version_; + + // Greasemonkey-style globs that determine pages to inject the script into. + // These are only used with standalone scripts. + std::vector<std::string> globs_; + std::vector<std::string> exclude_globs_; + + // URLPatterns that determine pages to inject the script into. These are + // only used with scripts that are part of extensions. + URLPatternSet url_set_; + URLPatternSet exclude_url_set_; + + // List of js scripts defined in content_scripts + FileList js_scripts_; + + // List of css scripts defined in content_scripts + FileList css_scripts_; + + // The ID of the extension this script is a part of, if any. Can be empty if + // the script is a "standlone" user script. + std::string extension_id_; + + // Whether we should try to emulate Greasemonkey's APIs when running this + // script. + bool emulate_greasemonkey_; + + // Whether the user script should run in all frames, or only just the top one. + // Defaults to false. + bool match_all_frames_; + + // True if the script should be injected into an incognito tab. + bool incognito_enabled_; +}; + +typedef std::vector<UserScript> UserScriptList; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_USER_SCRIPT_H_ diff --git a/chromium/extensions/common/user_script_unittest.cc b/chromium/extensions/common/user_script_unittest.cc new file mode 100644 index 00000000000..ffec4f0e1db --- /dev/null +++ b/chromium/extensions/common/user_script_unittest.cc @@ -0,0 +1,220 @@ +// Copyright 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 "base/files/file_path.h" +#include "base/pickle.h" +#include "extensions/common/user_script.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace extensions { + +static const int kAllSchemes = + URLPattern::SCHEME_HTTP | + URLPattern::SCHEME_HTTPS | + URLPattern::SCHEME_FILE | + URLPattern::SCHEME_FTP | + URLPattern::SCHEME_CHROMEUI; + +TEST(ExtensionUserScriptTest, Glob_HostString) { + UserScript script; + script.add_glob("*mail.google.com*"); + script.add_glob("*mail.yahoo.com*"); + script.add_glob("*mail.msn.com*"); + EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com"))); + EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com/foo"))); + EXPECT_TRUE(script.MatchesURL(GURL("https://mail.google.com/foo"))); + EXPECT_TRUE(script.MatchesURL(GURL("ftp://mail.google.com/foo"))); + EXPECT_TRUE(script.MatchesURL(GURL("http://woo.mail.google.com/foo"))); + EXPECT_TRUE(script.MatchesURL(GURL("http://mail.yahoo.com/bar"))); + EXPECT_TRUE(script.MatchesURL(GURL("http://mail.msn.com/baz"))); + EXPECT_FALSE(script.MatchesURL(GURL("http://www.hotmail.com"))); + + script.add_exclude_glob("*foo*"); + EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com"))); + EXPECT_FALSE(script.MatchesURL(GURL("http://mail.google.com/foo"))); +} + +TEST(ExtensionUserScriptTest, Glob_TrailingSlash) { + UserScript script; + script.add_glob("*mail.google.com/"); + // GURL normalizes the URL to have a trailing "/" + EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com"))); + EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com/"))); + EXPECT_FALSE(script.MatchesURL(GURL("http://mail.google.com/foo"))); +} + +TEST(ExtensionUserScriptTest, Glob_TrailingSlashStar) { + UserScript script; + script.add_glob("http://mail.google.com/*"); + // GURL normalizes the URL to have a trailing "/" + EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com"))); + EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com/foo"))); + EXPECT_FALSE(script.MatchesURL(GURL("https://mail.google.com/foo"))); +} + +TEST(ExtensionUserScriptTest, Glob_Star) { + UserScript script; + script.add_glob("*"); + EXPECT_TRUE(script.MatchesURL(GURL("http://foo.com/bar"))); + EXPECT_TRUE(script.MatchesURL(GURL("http://hot.com/dog"))); + EXPECT_TRUE(script.MatchesURL(GURL("https://hot.com/dog"))); + EXPECT_TRUE(script.MatchesURL(GURL("file:///foo/bar"))); + EXPECT_TRUE(script.MatchesURL(GURL("file://localhost/foo/bar"))); +} + +TEST(ExtensionUserScriptTest, Glob_StringAnywhere) { + UserScript script; + script.add_glob("*foo*"); + EXPECT_TRUE(script.MatchesURL(GURL("http://foo.com/bar"))); + EXPECT_TRUE(script.MatchesURL(GURL("http://baz.org/foo/bar"))); + EXPECT_FALSE(script.MatchesURL(GURL("http://baz.org"))); +} + +TEST(ExtensionUserScriptTest, UrlPattern) { + URLPattern pattern(kAllSchemes); + ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*/foo*")); + + UserScript script; + script.add_url_pattern(pattern); + EXPECT_TRUE(script.MatchesURL(GURL("http://monkey.com/foobar"))); + EXPECT_FALSE(script.MatchesURL(GURL("http://monkey.com/hotdog"))); + + // NOTE: URLPattern is tested more extensively in url_pattern_unittest.cc. +} + +TEST(ExtensionUserScriptTest, ExcludeUrlPattern) { + UserScript script; + + URLPattern pattern(kAllSchemes); + ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.nytimes.com/*")); + script.add_url_pattern(pattern); + + URLPattern exclude(kAllSchemes); + ASSERT_EQ(URLPattern::PARSE_SUCCESS, exclude.Parse("*://*/*business*")); + script.add_exclude_url_pattern(exclude); + + EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com/health"))); + EXPECT_FALSE(script.MatchesURL(GURL("http://www.nytimes.com/business"))); + EXPECT_TRUE(script.MatchesURL(GURL("http://business.nytimes.com"))); +} + +TEST(ExtensionUserScriptTest, UrlPatternAndIncludeGlobs) { + UserScript script; + + URLPattern pattern(kAllSchemes); + ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.nytimes.com/*")); + script.add_url_pattern(pattern); + + script.add_glob("*nytimes.com/???s/*"); + + EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com/arts/1.html"))); + EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com/jobs/1.html"))); + EXPECT_FALSE(script.MatchesURL(GURL("http://www.nytimes.com/sports/1.html"))); +} + +TEST(ExtensionUserScriptTest, UrlPatternAndExcludeGlobs) { + UserScript script; + + URLPattern pattern(kAllSchemes); + ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.nytimes.com/*")); + script.add_url_pattern(pattern); + + script.add_exclude_glob("*science*"); + + EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com"))); + EXPECT_FALSE(script.MatchesURL(GURL("http://science.nytimes.com"))); + EXPECT_FALSE(script.MatchesURL(GURL("http://www.nytimes.com/science"))); +} + +TEST(ExtensionUserScriptTest, UrlPatternGlobInteraction) { + // If there are both, match intersection(union(globs), union(urlpatterns)). + UserScript script; + + URLPattern pattern(kAllSchemes); + ASSERT_EQ(URLPattern::PARSE_SUCCESS,pattern.Parse("http://www.google.com/*")); + script.add_url_pattern(pattern); + + script.add_glob("*bar*"); + + // No match, because it doesn't match the glob. + EXPECT_FALSE(script.MatchesURL(GURL("http://www.google.com/foo"))); + + script.add_exclude_glob("*baz*"); + + // No match, because it matches the exclude glob. + EXPECT_FALSE(script.MatchesURL(GURL("http://www.google.com/baz"))); + + // Match, because it matches the glob, doesn't match the exclude glob. + EXPECT_TRUE(script.MatchesURL(GURL("http://www.google.com/bar"))); + + // Try with just a single exclude glob. + script.clear_globs(); + EXPECT_TRUE(script.MatchesURL(GURL("http://www.google.com/foo"))); + + // Try with no globs or exclude globs. + script.clear_exclude_globs(); + EXPECT_TRUE(script.MatchesURL(GURL("http://www.google.com/foo"))); +} + +TEST(ExtensionUserScriptTest, Pickle) { + URLPattern pattern1(kAllSchemes); + URLPattern pattern2(kAllSchemes); + URLPattern exclude1(kAllSchemes); + URLPattern exclude2(kAllSchemes); + ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern1.Parse("http://*/foo*")); + ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern2.Parse("http://bar/baz*")); + ASSERT_EQ(URLPattern::PARSE_SUCCESS, exclude1.Parse("*://*/*bar")); + ASSERT_EQ(URLPattern::PARSE_SUCCESS, exclude2.Parse("https://*/*")); + + UserScript script1; + script1.js_scripts().push_back(UserScript::File( + base::FilePath(FILE_PATH_LITERAL("c:\\foo\\")), + base::FilePath(FILE_PATH_LITERAL("foo.user.js")), + GURL("chrome-extension://abc/foo.user.js"))); + script1.css_scripts().push_back(UserScript::File( + base::FilePath(FILE_PATH_LITERAL("c:\\foo\\")), + base::FilePath(FILE_PATH_LITERAL("foo.user.css")), + GURL("chrome-extension://abc/foo.user.css"))); + script1.css_scripts().push_back(UserScript::File( + base::FilePath(FILE_PATH_LITERAL("c:\\foo\\")), + base::FilePath(FILE_PATH_LITERAL("foo2.user.css")), + GURL("chrome-extension://abc/foo2.user.css"))); + script1.set_run_location(UserScript::DOCUMENT_START); + + script1.add_url_pattern(pattern1); + script1.add_url_pattern(pattern2); + script1.add_exclude_url_pattern(exclude1); + script1.add_exclude_url_pattern(exclude2); + + Pickle pickle; + script1.Pickle(&pickle); + + PickleIterator iter(pickle); + UserScript script2; + script2.Unpickle(pickle, &iter); + + EXPECT_EQ(1U, script2.js_scripts().size()); + EXPECT_EQ(script1.js_scripts()[0].url(), script2.js_scripts()[0].url()); + + EXPECT_EQ(2U, script2.css_scripts().size()); + for (size_t i = 0; i < script2.js_scripts().size(); ++i) { + EXPECT_EQ(script1.css_scripts()[i].url(), script2.css_scripts()[i].url()); + } + + ASSERT_EQ(script1.globs().size(), script2.globs().size()); + for (size_t i = 0; i < script1.globs().size(); ++i) { + EXPECT_EQ(script1.globs()[i], script2.globs()[i]); + } + + ASSERT_EQ(script1.url_patterns(), script2.url_patterns()); + ASSERT_EQ(script1.exclude_url_patterns(), script2.exclude_url_patterns()); +} + +TEST(ExtensionUserScriptTest, Defaults) { + UserScript script; + ASSERT_EQ(UserScript::DOCUMENT_IDLE, script.run_location()); +} + +} // namespace extensions diff --git a/chromium/extensions/common/view_type.cc b/chromium/extensions/common/view_type.cc new file mode 100644 index 00000000000..679532a5acd --- /dev/null +++ b/chromium/extensions/common/view_type.cc @@ -0,0 +1,19 @@ +// 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 "extensions/common/view_type.h" + +namespace extensions { + +const char kViewTypeTabContents[] = "TAB"; +const char kViewTypeBackgroundPage[] = "BACKGROUND"; +const char kViewTypePopup[] = "POPUP"; +const char kViewTypePanel[] = "PANEL"; +const char kViewTypeInfobar[] = "INFOBAR"; +const char kViewTypeNotification[] = "NOTIFICATION"; +const char kViewTypeExtensionDialog[] = "EXTENSION_DIALOG"; +const char kViewTypeAppShell[] = "SHELL"; +const char kViewTypeAll[] = "ALL"; + +} // namespace extensions diff --git a/chromium/extensions/common/view_type.h b/chromium/extensions/common/view_type.h new file mode 100644 index 00000000000..6030e6ca92c --- /dev/null +++ b/chromium/extensions/common/view_type.h @@ -0,0 +1,45 @@ +// 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. + +#ifndef EXTENSIONS_COMMON_VIEW_TYPE_H_ +#define EXTENSIONS_COMMON_VIEW_TYPE_H_ + +namespace extensions { + +// Icky RTTI used by a few systems to distinguish the host type of a given +// WebContents. +// +// TODO(aa): Remove this and teach those systems to keep track of their own +// data. +enum ViewType { + VIEW_TYPE_INVALID, + VIEW_TYPE_APP_SHELL, + VIEW_TYPE_BACKGROUND_CONTENTS, + VIEW_TYPE_EXTENSION_BACKGROUND_PAGE, + VIEW_TYPE_EXTENSION_DIALOG, + VIEW_TYPE_EXTENSION_INFOBAR, + VIEW_TYPE_EXTENSION_POPUP, + // TODO(jam): remove this once http://crbug.com/137297 is fixed and HTML5 + // notifications don't use WebContents. + VIEW_TYPE_NOTIFICATION, + VIEW_TYPE_PANEL, + VIEW_TYPE_TAB_CONTENTS, + VIEW_TYPE_VIRTUAL_KEYBOARD, +}; + +// Constant strings corresponding to the Type enumeration values. Used +// when converting JS arguments. +extern const char kViewTypeAll[]; +extern const char kViewTypeAppShell[]; +extern const char kViewTypeBackgroundPage[]; +extern const char kViewTypeExtensionDialog[]; +extern const char kViewTypeInfobar[]; +extern const char kViewTypeNotification[]; +extern const char kViewTypePanel[]; +extern const char kViewTypePopup[]; +extern const char kViewTypeTabContents[]; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_VIEW_TYPE_H_ |