1// Copyright (c) 2011 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_path.h" 12#include "base/file_util.h" 13#include "base/memory/scoped_temp_dir.h" 14#include "base/path_service.h" 15#include "base/string_util.h" 16#include "crypto/sha2.h" 17#include "chrome/browser/extensions/user_script_master.h" 18#include "chrome/common/chrome_paths.h" 19#include "chrome/common/extensions/extension.h" 20#include "chrome/common/extensions/extension_constants.h" 21#include "chrome/common/extensions/extension_file_util.h" 22#include "chrome/common/extensions/user_script.h" 23#include "content/common/json_value_serializer.h" 24#include "googleurl/src/gurl.h" 25 26namespace keys = extension_manifest_keys; 27 28scoped_refptr<Extension> ConvertUserScriptToExtension( 29 const FilePath& user_script_path, const GURL& original_url, 30 std::string* error) { 31 std::string content; 32 if (!file_util::ReadFileToString(user_script_path, &content)) { 33 *error = "Could not read source file."; 34 return NULL; 35 } 36 37 if (!IsStringUTF8(content)) { 38 *error = "User script must be UTF8 encoded."; 39 return NULL; 40 } 41 42 UserScript script; 43 if (!UserScriptMaster::ScriptReloader::ParseMetadataHeader(content, 44 &script)) { 45 *error = "Invalid script header."; 46 return NULL; 47 } 48 49 FilePath user_data_temp_dir = extension_file_util::GetUserDataTempDir(); 50 if (user_data_temp_dir.empty()) { 51 *error = "Could not get path to profile temporary directory."; 52 return NULL; 53 } 54 55 ScopedTempDir temp_dir; 56 if (!temp_dir.CreateUniqueTempDirUnderPath(user_data_temp_dir)) { 57 *error = "Could not create temporary directory."; 58 return NULL; 59 } 60 61 // Create the manifest 62 scoped_ptr<DictionaryValue> root(new DictionaryValue); 63 std::string script_name; 64 if (!script.name().empty() && !script.name_space().empty()) 65 script_name = script.name_space() + "/" + script.name(); 66 else 67 script_name = original_url.spec(); 68 69 // Create the public key. 70 // User scripts are not signed, but the public key for an extension doubles as 71 // its unique identity, and we need one of those. A user script's unique 72 // identity is its namespace+name, so we hash that to create a public key. 73 // There will be no corresponding private key, which means user scripts cannot 74 // be auto-updated, or claimed in the gallery. 75 char raw[crypto::SHA256_LENGTH] = {0}; 76 std::string key; 77 crypto::SHA256HashString(script_name, raw, crypto::SHA256_LENGTH); 78 base::Base64Encode(std::string(raw, crypto::SHA256_LENGTH), &key); 79 80 // The script may not have a name field, but we need one for an extension. If 81 // it is missing, use the filename of the original URL. 82 if (!script.name().empty()) 83 root->SetString(keys::kName, script.name()); 84 else 85 root->SetString(keys::kName, original_url.ExtractFileName()); 86 87 // Not all scripts have a version, but we need one. Default to 1.0 if it is 88 // missing. 89 if (!script.version().empty()) 90 root->SetString(keys::kVersion, script.version()); 91 else 92 root->SetString(keys::kVersion, "1.0"); 93 94 root->SetString(keys::kDescription, script.description()); 95 root->SetString(keys::kPublicKey, key); 96 root->SetBoolean(keys::kConvertedFromUserScript, true); 97 98 ListValue* js_files = new ListValue(); 99 js_files->Append(Value::CreateStringValue("script.js")); 100 101 // If the script provides its own match patterns, we use those. Otherwise, we 102 // generate some using the include globs. 103 ListValue* matches = new ListValue(); 104 if (!script.url_patterns().empty()) { 105 for (size_t i = 0; i < script.url_patterns().size(); ++i) { 106 matches->Append(Value::CreateStringValue( 107 script.url_patterns()[i].GetAsString())); 108 } 109 } else { 110 // TODO(aa): Derive tighter matches where possible. 111 matches->Append(Value::CreateStringValue("http://*/*")); 112 matches->Append(Value::CreateStringValue("https://*/*")); 113 } 114 115 ListValue* includes = new ListValue(); 116 for (size_t i = 0; i < script.globs().size(); ++i) 117 includes->Append(Value::CreateStringValue(script.globs().at(i))); 118 119 ListValue* excludes = new ListValue(); 120 for (size_t i = 0; i < script.exclude_globs().size(); ++i) 121 excludes->Append(Value::CreateStringValue(script.exclude_globs().at(i))); 122 123 DictionaryValue* content_script = new DictionaryValue(); 124 content_script->Set(keys::kMatches, matches); 125 content_script->Set(keys::kIncludeGlobs, includes); 126 content_script->Set(keys::kExcludeGlobs, excludes); 127 content_script->Set(keys::kJs, js_files); 128 129 ListValue* content_scripts = new ListValue(); 130 content_scripts->Append(content_script); 131 132 root->Set(keys::kContentScripts, content_scripts); 133 134 FilePath manifest_path = temp_dir.path().Append( 135 Extension::kManifestFilename); 136 JSONFileValueSerializer serializer(manifest_path); 137 if (!serializer.Serialize(*root)) { 138 *error = "Could not write JSON."; 139 return NULL; 140 } 141 142 // Write the script file. 143 if (!file_util::CopyFile(user_script_path, 144 temp_dir.path().AppendASCII("script.js"))) { 145 *error = "Could not copy script file."; 146 return NULL; 147 } 148 149 scoped_refptr<Extension> extension = Extension::Create( 150 temp_dir.path(), 151 Extension::INTERNAL, 152 *root, 153 Extension::NO_FLAGS, 154 error); 155 if (!extension) { 156 NOTREACHED() << "Could not init extension " << *error; 157 return NULL; 158 } 159 160 temp_dir.Take(); // The caller takes ownership of the directory. 161 return extension; 162} 163