| // 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 "chrome/browser/extensions/convert_user_script.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/json/json_file_value_serializer.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "crypto/sha2.h" |
| #include "extensions/browser/extension_user_script_loader.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/file_util.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "extensions/common/user_script.h" |
| #include "url/gurl.h" |
| |
| namespace extensions { |
| |
| scoped_refptr<Extension> ConvertUserScriptToExtension( |
| const base::FilePath& user_script_path, const GURL& original_url, |
| const base::FilePath& extensions_dir, base::string16* error) { |
| std::string content; |
| if (!base::ReadFileToString(user_script_path, &content)) { |
| *error = base::ASCIIToUTF16("Could not read source file."); |
| return NULL; |
| } |
| |
| if (!base::IsStringUTF8(content)) { |
| *error = base::ASCIIToUTF16("User script must be UTF8 encoded."); |
| return NULL; |
| } |
| |
| UserScript script; |
| if (!UserScriptLoader::ParseMetadataHeader(content, &script)) { |
| *error = base::ASCIIToUTF16("Invalid script header."); |
| return NULL; |
| } |
| |
| base::FilePath install_temp_dir = |
| file_util::GetInstallTempDir(extensions_dir); |
| if (install_temp_dir.empty()) { |
| *error = base::ASCIIToUTF16( |
| "Could not get path to profile temporary directory."); |
| return NULL; |
| } |
| |
| base::ScopedTempDir temp_dir; |
| if (!temp_dir.CreateUniqueTempDirUnderPath(install_temp_dir)) { |
| *error = base::ASCIIToUTF16("Could not create temporary directory."); |
| return NULL; |
| } |
| |
| // Create the manifest |
| std::unique_ptr<base::DictionaryValue> root(new base::DictionaryValue); |
| std::string script_name; |
| if (!script.name().empty() && !script.name_space().empty()) |
| script_name = script.name_space() + "/" + script.name(); |
| else |
| script_name = original_url.spec(); |
| |
| // Create the public key. |
| // User scripts are not signed, but the public key for an extension doubles as |
| // its unique identity, and we need one of those. A user script's unique |
| // identity is its namespace+name, so we hash that to create a public key. |
| // There will be no corresponding private key, which means user scripts cannot |
| // be auto-updated, or claimed in the gallery. |
| char raw[crypto::kSHA256Length] = {0}; |
| std::string key; |
| crypto::SHA256HashString(script_name, raw, crypto::kSHA256Length); |
| base::Base64Encode(base::StringPiece(raw, crypto::kSHA256Length), &key); |
| |
| // The script may not have a name field, but we need one for an extension. If |
| // it is missing, use the filename of the original URL. |
| if (!script.name().empty()) |
| root->SetString(manifest_keys::kName, script.name()); |
| else |
| root->SetString(manifest_keys::kName, original_url.ExtractFileName()); |
| |
| // Not all scripts have a version, but we need one. Default to 1.0 if it is |
| // missing. |
| if (!script.version().empty()) |
| root->SetString(manifest_keys::kVersion, script.version()); |
| else |
| root->SetString(manifest_keys::kVersion, "1.0"); |
| |
| root->SetString(manifest_keys::kDescription, script.description()); |
| root->SetString(manifest_keys::kPublicKey, key); |
| root->SetBoolean(manifest_keys::kConvertedFromUserScript, true); |
| |
| auto js_files = std::make_unique<base::ListValue>(); |
| js_files->AppendString("script.js"); |
| |
| // If the script provides its own match patterns, we use those. Otherwise, we |
| // generate some using the include globs. |
| auto matches = std::make_unique<base::ListValue>(); |
| if (!script.url_patterns().is_empty()) { |
| for (auto i = script.url_patterns().begin(); |
| i != script.url_patterns().end(); ++i) { |
| matches->AppendString(i->GetAsString()); |
| } |
| } else { |
| // TODO(aa): Derive tighter matches where possible. |
| matches->AppendString("http://*/*"); |
| matches->AppendString("https://*/*"); |
| } |
| |
| // Read the exclude matches, if any are present. |
| auto exclude_matches = std::make_unique<base::ListValue>(); |
| if (!script.exclude_url_patterns().is_empty()) { |
| for (auto i = script.exclude_url_patterns().begin(); |
| i != script.exclude_url_patterns().end(); ++i) { |
| exclude_matches->AppendString(i->GetAsString()); |
| } |
| } |
| |
| auto includes = std::make_unique<base::ListValue>(); |
| for (size_t i = 0; i < script.globs().size(); ++i) |
| includes->AppendString(script.globs().at(i)); |
| |
| auto excludes = std::make_unique<base::ListValue>(); |
| for (size_t i = 0; i < script.exclude_globs().size(); ++i) |
| excludes->AppendString(script.exclude_globs().at(i)); |
| |
| auto content_script = std::make_unique<base::DictionaryValue>(); |
| content_script->Set(manifest_keys::kMatches, std::move(matches)); |
| content_script->Set(manifest_keys::kExcludeMatches, |
| std::move(exclude_matches)); |
| content_script->Set(manifest_keys::kIncludeGlobs, std::move(includes)); |
| content_script->Set(manifest_keys::kExcludeGlobs, std::move(excludes)); |
| content_script->Set(manifest_keys::kJs, std::move(js_files)); |
| |
| if (script.run_location() == UserScript::DOCUMENT_START) |
| content_script->SetString(manifest_keys::kRunAt, |
| manifest_values::kRunAtDocumentStart); |
| else if (script.run_location() == UserScript::DOCUMENT_END) |
| content_script->SetString(manifest_keys::kRunAt, |
| manifest_values::kRunAtDocumentEnd); |
| else if (script.run_location() == UserScript::DOCUMENT_IDLE) |
| // This is the default, but store it just in case we change that. |
| content_script->SetString(manifest_keys::kRunAt, |
| manifest_values::kRunAtDocumentIdle); |
| |
| auto content_scripts = std::make_unique<base::ListValue>(); |
| content_scripts->Append(std::move(content_script)); |
| |
| root->Set(manifest_keys::kContentScripts, std::move(content_scripts)); |
| |
| base::FilePath manifest_path = temp_dir.GetPath().Append(kManifestFilename); |
| JSONFileValueSerializer serializer(manifest_path); |
| if (!serializer.Serialize(*root)) { |
| *error = base::ASCIIToUTF16("Could not write JSON."); |
| return NULL; |
| } |
| |
| // Write the script file. |
| if (!base::CopyFile(user_script_path, |
| temp_dir.GetPath().AppendASCII("script.js"))) { |
| *error = base::ASCIIToUTF16("Could not copy script file."); |
| return NULL; |
| } |
| |
| // TODO(rdevlin.cronin): Continue removing std::string errors and replacing |
| // with base::string16 |
| std::string utf8_error; |
| scoped_refptr<Extension> extension = |
| Extension::Create(temp_dir.GetPath(), Manifest::INTERNAL, *root, |
| Extension::NO_FLAGS, &utf8_error); |
| *error = base::UTF8ToUTF16(utf8_error); |
| if (!extension.get()) { |
| NOTREACHED() << "Could not init extension " << *error; |
| return NULL; |
| } |
| |
| temp_dir.Take(); // The caller takes ownership of the directory. |
| return extension; |
| } |
| |
| } // namespace extensions |