1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/extensions/convert_user_script.h" 6 7#include <string> 8#include <vector> 9 10#include "base/base64.h" 11#include "base/file_util.h" 12#include "base/files/file_path.h" 13#include "base/files/scoped_temp_dir.h" 14#include "base/json/json_file_value_serializer.h" 15#include "base/path_service.h" 16#include "base/strings/string_util.h" 17#include "base/strings/utf_string_conversions.h" 18#include "chrome/browser/extensions/user_script_master.h" 19#include "chrome/common/chrome_paths.h" 20#include "chrome/common/extensions/extension.h" 21#include "chrome/common/extensions/extension_file_util.h" 22#include "chrome/common/extensions/extension_manifest_constants.h" 23#include "crypto/sha2.h" 24#include "extensions/common/constants.h" 25#include "extensions/common/user_script.h" 26#include "url/gurl.h" 27 28namespace keys = extension_manifest_keys; 29namespace values = extension_manifest_values; 30 31namespace extensions { 32 33scoped_refptr<Extension> ConvertUserScriptToExtension( 34 const base::FilePath& user_script_path, const GURL& original_url, 35 const base::FilePath& extensions_dir, string16* error) { 36 std::string content; 37 if (!file_util::ReadFileToString(user_script_path, &content)) { 38 *error = ASCIIToUTF16("Could not read source file."); 39 return NULL; 40 } 41 42 if (!IsStringUTF8(content)) { 43 *error = ASCIIToUTF16("User script must be UTF8 encoded."); 44 return NULL; 45 } 46 47 UserScript script; 48 if (!UserScriptMaster::ScriptReloader::ParseMetadataHeader(content, 49 &script)) { 50 *error = ASCIIToUTF16("Invalid script header."); 51 return NULL; 52 } 53 54 base::FilePath install_temp_dir = 55 extension_file_util::GetInstallTempDir(extensions_dir); 56 if (install_temp_dir.empty()) { 57 *error = ASCIIToUTF16("Could not get path to profile temporary directory."); 58 return NULL; 59 } 60 61 base::ScopedTempDir temp_dir; 62 if (!temp_dir.CreateUniqueTempDirUnderPath(install_temp_dir)) { 63 *error = ASCIIToUTF16("Could not create temporary directory."); 64 return NULL; 65 } 66 67 // Create the manifest 68 scoped_ptr<DictionaryValue> root(new DictionaryValue); 69 std::string script_name; 70 if (!script.name().empty() && !script.name_space().empty()) 71 script_name = script.name_space() + "/" + script.name(); 72 else 73 script_name = original_url.spec(); 74 75 // Create the public key. 76 // User scripts are not signed, but the public key for an extension doubles as 77 // its unique identity, and we need one of those. A user script's unique 78 // identity is its namespace+name, so we hash that to create a public key. 79 // There will be no corresponding private key, which means user scripts cannot 80 // be auto-updated, or claimed in the gallery. 81 char raw[crypto::kSHA256Length] = {0}; 82 std::string key; 83 crypto::SHA256HashString(script_name, raw, crypto::kSHA256Length); 84 base::Base64Encode(std::string(raw, crypto::kSHA256Length), &key); 85 86 // The script may not have a name field, but we need one for an extension. If 87 // it is missing, use the filename of the original URL. 88 if (!script.name().empty()) 89 root->SetString(keys::kName, script.name()); 90 else 91 root->SetString(keys::kName, original_url.ExtractFileName()); 92 93 // Not all scripts have a version, but we need one. Default to 1.0 if it is 94 // missing. 95 if (!script.version().empty()) 96 root->SetString(keys::kVersion, script.version()); 97 else 98 root->SetString(keys::kVersion, "1.0"); 99 100 root->SetString(keys::kDescription, script.description()); 101 root->SetString(keys::kPublicKey, key); 102 root->SetBoolean(keys::kConvertedFromUserScript, true); 103 104 base::ListValue* js_files = new base::ListValue(); 105 js_files->Append(Value::CreateStringValue("script.js")); 106 107 // If the script provides its own match patterns, we use those. Otherwise, we 108 // generate some using the include globs. 109 base::ListValue* matches = new base::ListValue(); 110 if (!script.url_patterns().is_empty()) { 111 for (URLPatternSet::const_iterator i = script.url_patterns().begin(); 112 i != script.url_patterns().end(); ++i) { 113 matches->Append(Value::CreateStringValue(i->GetAsString())); 114 } 115 } else { 116 // TODO(aa): Derive tighter matches where possible. 117 matches->Append(Value::CreateStringValue("http://*/*")); 118 matches->Append(Value::CreateStringValue("https://*/*")); 119 } 120 121 // Read the exclude matches, if any are present. 122 base::ListValue* exclude_matches = new base::ListValue(); 123 if (!script.exclude_url_patterns().is_empty()) { 124 for (URLPatternSet::const_iterator i = 125 script.exclude_url_patterns().begin(); 126 i != script.exclude_url_patterns().end(); ++i) { 127 exclude_matches->Append(Value::CreateStringValue(i->GetAsString())); 128 } 129 } 130 131 base::ListValue* includes = new base::ListValue(); 132 for (size_t i = 0; i < script.globs().size(); ++i) 133 includes->Append(Value::CreateStringValue(script.globs().at(i))); 134 135 base::ListValue* excludes = new base::ListValue(); 136 for (size_t i = 0; i < script.exclude_globs().size(); ++i) 137 excludes->Append(Value::CreateStringValue(script.exclude_globs().at(i))); 138 139 DictionaryValue* content_script = new DictionaryValue(); 140 content_script->Set(keys::kMatches, matches); 141 content_script->Set(keys::kExcludeMatches, exclude_matches); 142 content_script->Set(keys::kIncludeGlobs, includes); 143 content_script->Set(keys::kExcludeGlobs, excludes); 144 content_script->Set(keys::kJs, js_files); 145 146 if (script.run_location() == UserScript::DOCUMENT_START) 147 content_script->SetString(keys::kRunAt, values::kRunAtDocumentStart); 148 else if (script.run_location() == UserScript::DOCUMENT_END) 149 content_script->SetString(keys::kRunAt, values::kRunAtDocumentEnd); 150 else if (script.run_location() == UserScript::DOCUMENT_IDLE) 151 // This is the default, but store it just in case we change that. 152 content_script->SetString(keys::kRunAt, values::kRunAtDocumentIdle); 153 154 base::ListValue* content_scripts = new base::ListValue(); 155 content_scripts->Append(content_script); 156 157 root->Set(keys::kContentScripts, content_scripts); 158 159 base::FilePath manifest_path = temp_dir.path().Append(kManifestFilename); 160 JSONFileValueSerializer serializer(manifest_path); 161 if (!serializer.Serialize(*root)) { 162 *error = ASCIIToUTF16("Could not write JSON."); 163 return NULL; 164 } 165 166 // Write the script file. 167 if (!base::CopyFile(user_script_path, 168 temp_dir.path().AppendASCII("script.js"))) { 169 *error = ASCIIToUTF16("Could not copy script file."); 170 return NULL; 171 } 172 173 // TODO(rdevlin.cronin): Continue removing std::string errors and replacing 174 // with string16 175 std::string utf8_error; 176 scoped_refptr<Extension> extension = Extension::Create( 177 temp_dir.path(), 178 Manifest::INTERNAL, 179 *root, 180 Extension::NO_FLAGS, 181 &utf8_error); 182 *error = UTF8ToUTF16(utf8_error); 183 if (!extension.get()) { 184 NOTREACHED() << "Could not init extension " << *error; 185 return NULL; 186 } 187 188 temp_dir.Take(); // The caller takes ownership of the directory. 189 return extension; 190} 191 192} // namespace extensions 193