| OLD | NEW |
| 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/extensions/extension.h" | 5 #include "chrome/browser/extensions/extension.h" |
| 6 | 6 |
| 7 #include "base/logging.h" | 7 #include "base/logging.h" |
| 8 #include "base/string_util.h" | 8 #include "base/string_util.h" |
| 9 #include "net/base/net_util.h" | |
| 10 | 9 |
| 11 const char Extension::kManifestFilename[] = "manifest"; | 10 const char Extension::kManifestFilename[] = "manifest"; |
| 12 | 11 |
| 13 const wchar_t* Extension::kDescriptionKey = L"description"; | |
| 14 const wchar_t* Extension::kFilesKey = L"files"; | |
| 15 const wchar_t* Extension::kFormatVersionKey = L"format_version"; | 12 const wchar_t* Extension::kFormatVersionKey = L"format_version"; |
| 16 const wchar_t* Extension::kIdKey = L"id"; | 13 const wchar_t* Extension::kIdKey = L"id"; |
| 17 const wchar_t* Extension::kMatchesKey = L"matches"; | |
| 18 const wchar_t* Extension::kNameKey = L"name"; | 14 const wchar_t* Extension::kNameKey = L"name"; |
| 19 const wchar_t* Extension::kUserScriptsKey = L"user_scripts"; | 15 const wchar_t* Extension::kDescriptionKey = L"description"; |
| 16 const wchar_t* Extension::kContentScriptsKey = L"content_scripts"; |
| 20 const wchar_t* Extension::kVersionKey = L"version"; | 17 const wchar_t* Extension::kVersionKey = L"version"; |
| 21 | 18 |
| 22 // Extension-related error messages. Some of these are simple patterns, where a | |
| 23 // '*' is replaced at runtime with a specific value. This is used instead of | |
| 24 // printf because we want to unit test them and scanf is hard to make | |
| 25 // cross-platform. | |
| 26 const char* Extension::kInvalidDescriptionError = | |
| 27 "Invalid value for 'description'."; | |
| 28 const char* Extension::kInvalidFileCountError = | |
| 29 "Invalid value for 'user_scripts[*].files. Only one file is currently " | |
| 30 "supported per-user script."; | |
| 31 const char* Extension::kInvalidFileError = | |
| 32 "Invalid value for 'user_scripts[*].files[*]'."; | |
| 33 const char* Extension::kInvalidFilesError = | |
| 34 "Required value 'user_scripts[*].files is missing or invalid."; | |
| 35 const char* Extension::kInvalidFormatVersionError = | |
| 36 "Required value 'format_version' is missing or invalid."; | |
| 37 const char* Extension::kInvalidIdError = | |
| 38 "Required value 'id' is missing or invalid."; | |
| 39 const char* Extension::kInvalidManifestError = | 19 const char* Extension::kInvalidManifestError = |
| 40 "Manifest is missing or invalid."; | 20 "Manifest is missing or invalid."; |
| 41 const char* Extension::kInvalidMatchCountError = | 21 const char* Extension::kInvalidFormatVersionError = |
| 42 "Invalid value for 'user_scripts[*].matches. There must be at least one " | 22 "Required key 'format_version' is missing or invalid."; |
| 43 "match specified."; | 23 const char* Extension::kInvalidIdError = |
| 44 const char* Extension::kInvalidMatchError = | 24 "Required key 'id' is missing or invalid."; |
| 45 "Invalid value for 'user_scripts[*].matches[*]'."; | |
| 46 const char* Extension::kInvalidMatchesError = | |
| 47 "Required value 'user_scripts[*].matches' is missing or invalid."; | |
| 48 const char* Extension::kInvalidNameError = | 25 const char* Extension::kInvalidNameError = |
| 49 "Required value 'name' is missing or invalid."; | 26 "Required key 'name' is missing or has invalid type."; |
| 50 const char* Extension::kInvalidUserScriptError = | 27 const char* Extension::kInvalidDescriptionError = |
| 51 "Invalid value for 'user_scripts[*]'."; | 28 "Invalid type for 'description' key."; |
| 52 const char* Extension::kInvalidUserScriptsListError = | 29 const char* Extension::kInvalidContentScriptsListError = |
| 53 "Invalid value for 'user_scripts'."; | 30 "Invalid type for 'content_scripts' key."; |
| 31 const char* Extension::kInvalidContentScriptError = |
| 32 "Invalid type for content_scripts at index "; |
| 54 const char* Extension::kInvalidVersionError = | 33 const char* Extension::kInvalidVersionError = |
| 55 "Required value 'version' is missing or invalid."; | 34 "Required key 'version' is missing or invalid."; |
| 56 | |
| 57 // Defined in extension_protocols.h. | |
| 58 extern const char kExtensionURLScheme[]; | |
| 59 | |
| 60 // static | |
| 61 GURL Extension::GetResourceURL(const GURL& extension_url, | |
| 62 const std::string& relative_path) { | |
| 63 DCHECK(extension_url.SchemeIs(kExtensionURLScheme)); | |
| 64 DCHECK(extension_url.path() == "/"); | |
| 65 | |
| 66 GURL ret_val = GURL(extension_url.spec() + relative_path); | |
| 67 DCHECK(StartsWithASCII(ret_val.spec(), extension_url.spec(), false)); | |
| 68 | |
| 69 return ret_val; | |
| 70 } | |
| 71 | |
| 72 // static | |
| 73 FilePath Extension::GetResourcePath(const FilePath& extension_path, | |
| 74 const std::string& relative_path) { | |
| 75 // Build up a file:// URL and convert that back to a FilePath. This avoids | |
| 76 // URL encoding and path separator issues. | |
| 77 | |
| 78 // Convert the extension's root to a file:// URL. | |
| 79 GURL extension_url = net::FilePathToFileURL(extension_path); | |
| 80 if (!extension_url.is_valid()) | |
| 81 return FilePath(); | |
| 82 | |
| 83 // Append the requested path. | |
| 84 GURL::Replacements replacements; | |
| 85 std::string new_path(extension_url.path()); | |
| 86 new_path += "/"; | |
| 87 new_path += relative_path; | |
| 88 replacements.SetPathStr(new_path); | |
| 89 GURL file_url = extension_url.ReplaceComponents(replacements); | |
| 90 if (!file_url.is_valid()) | |
| 91 return FilePath(); | |
| 92 | |
| 93 // Convert the result back to a FilePath. | |
| 94 FilePath ret_val; | |
| 95 if (!net::FileURLToFilePath(file_url, &ret_val)) | |
| 96 return FilePath(); | |
| 97 | |
| 98 // Double-check that the path we ended up with is actually inside the | |
| 99 // extension root. We can do this with a simple prefix match because: | |
| 100 // a) We control the prefix on both sides, and they should match. | |
| 101 // b) GURL normalizes things like "../" and "//" before it gets to us. | |
| 102 if (ret_val.value().find(extension_path.value() + | |
| 103 FilePath::kSeparators[0]) != 0) | |
| 104 return FilePath(); | |
| 105 | |
| 106 return ret_val; | |
| 107 } | |
| 108 | |
| 109 // Creates an error messages from a pattern. | |
| 110 static std::string FormatErrorMessage(const std::string& format, | |
| 111 const std::string s1) { | |
| 112 std::string ret_val = format; | |
| 113 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); | |
| 114 return ret_val; | |
| 115 } | |
| 116 | |
| 117 static std::string FormatErrorMessage(const std::string& format, | |
| 118 const std::string s1, | |
| 119 const std::string s2) { | |
| 120 std::string ret_val = format; | |
| 121 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); | |
| 122 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); | |
| 123 return ret_val; | |
| 124 } | |
| 125 | |
| 126 Extension::Extension(const FilePath& path) { | |
| 127 DCHECK(path.IsAbsolute()); | |
| 128 | |
| 129 #if defined(OS_WIN) | |
| 130 // Normalize any drive letter to upper-case. We do this for consistency with | |
| 131 // net_utils::FilePathToFileURL(), which does the same thing, to make string | |
| 132 // comparisons simpler. | |
| 133 std::wstring path_str = path.value(); | |
| 134 if (path_str.size() >= 2 && path_str[0] >= L'a' && path_str[0] <= L'z' && | |
| 135 path_str[1] == ':') | |
| 136 path_str[0] += ('A' - 'a'); | |
| 137 | |
| 138 path_ = FilePath(path_str); | |
| 139 #else | |
| 140 path_ = path; | |
| 141 #endif | |
| 142 } | |
| 143 | 35 |
| 144 bool Extension::InitFromValue(const DictionaryValue& source, | 36 bool Extension::InitFromValue(const DictionaryValue& source, |
| 145 std::string* error) { | 37 std::string* error) { |
| 146 // Check format version. | 38 // Check format version. |
| 147 int format_version = 0; | 39 int format_version = 0; |
| 148 if (!source.GetInteger(kFormatVersionKey, &format_version) || | 40 if (!source.GetInteger(kFormatVersionKey, &format_version) || |
| 149 format_version != kExpectedFormatVersion) { | 41 format_version != kExpectedFormatVersion) { |
| 150 *error = kInvalidFormatVersionError; | 42 *error = kInvalidFormatVersionError; |
| 151 return false; | 43 return false; |
| 152 } | 44 } |
| 153 | 45 |
| 154 // Initialize id. | 46 // Initialize id. |
| 155 if (!source.GetString(kIdKey, &id_)) { | 47 if (!source.GetString(kIdKey, &id_)) { |
| 156 *error = kInvalidIdError; | 48 *error = kInvalidIdError; |
| 157 return false; | 49 return false; |
| 158 } | 50 } |
| 159 | 51 |
| 160 // Initialize URL. | |
| 161 extension_url_ = GURL(std::string(kExtensionURLScheme) + "://" + id_ + "/"); | |
| 162 | |
| 163 // Initialize version. | 52 // Initialize version. |
| 164 if (!source.GetString(kVersionKey, &version_)) { | 53 if (!source.GetString(kVersionKey, &version_)) { |
| 165 *error = kInvalidVersionError; | 54 *error = kInvalidVersionError; |
| 166 return false; | 55 return false; |
| 167 } | 56 } |
| 168 | 57 |
| 169 // Initialize name. | 58 // Initialize name. |
| 170 if (!source.GetString(kNameKey, &name_)) { | 59 if (!source.GetString(kNameKey, &name_)) { |
| 171 *error = kInvalidNameError; | 60 *error = kInvalidNameError; |
| 172 return false; | 61 return false; |
| 173 } | 62 } |
| 174 | 63 |
| 175 // Initialize description (optional). | 64 // Initialize description (optional). |
| 176 if (source.HasKey(kDescriptionKey)) { | 65 Value* value = NULL; |
| 177 if (!source.GetString(kDescriptionKey, &description_)) { | 66 if (source.Get(kDescriptionKey, &value)) { |
| 67 if (!value->GetAsString(&description_)) { |
| 178 *error = kInvalidDescriptionError; | 68 *error = kInvalidDescriptionError; |
| 179 return false; | 69 return false; |
| 180 } | 70 } |
| 181 } | 71 } |
| 182 | 72 |
| 183 // Initialize user scripts (optional). | 73 // Initialize content scripts (optional). |
| 184 if (source.HasKey(kUserScriptsKey)) { | 74 if (source.Get(kContentScriptsKey, &value)) { |
| 185 ListValue* list_value; | 75 ListValue* list_value = NULL; |
| 186 if (!source.GetList(kUserScriptsKey, &list_value)) { | 76 if (value->GetType() != Value::TYPE_LIST) { |
| 187 *error = kInvalidUserScriptsListError; | 77 *error = kInvalidContentScriptsListError; |
| 188 return false; | 78 return false; |
| 79 } else { |
| 80 list_value = static_cast<ListValue*>(value); |
| 189 } | 81 } |
| 190 | 82 |
| 191 for (size_t i = 0; i < list_value->GetSize(); ++i) { | 83 for (size_t i = 0; i < list_value->GetSize(); ++i) { |
| 192 DictionaryValue* user_script; | 84 std::string content_script; |
| 193 if (!list_value->GetDictionary(i, &user_script)) { | 85 if (!list_value->Get(i, &value) || !value->GetAsString(&content_script)) { |
| 194 *error = FormatErrorMessage(kInvalidUserScriptError, IntToString(i)); | 86 *error = kInvalidContentScriptError; |
| 87 *error += IntToString(i); |
| 195 return false; | 88 return false; |
| 196 } | 89 } |
| 197 | 90 |
| 198 ListValue* matches; | 91 content_scripts_.push_back(content_script); |
| 199 ListValue* files; | |
| 200 | |
| 201 if (!user_script->GetList(kMatchesKey, &matches)) { | |
| 202 *error = FormatErrorMessage(kInvalidMatchesError, IntToString(i)); | |
| 203 return false; | |
| 204 } | |
| 205 | |
| 206 if (!user_script->GetList(kFilesKey, &files)) { | |
| 207 *error = FormatErrorMessage(kInvalidFilesError, IntToString(i)); | |
| 208 return false; | |
| 209 } | |
| 210 | |
| 211 if (matches->GetSize() == 0) { | |
| 212 *error = FormatErrorMessage(kInvalidMatchCountError, IntToString(i)); | |
| 213 return false; | |
| 214 } | |
| 215 | |
| 216 // NOTE: Only one file is supported right now. | |
| 217 if (files->GetSize() != 1) { | |
| 218 *error = FormatErrorMessage(kInvalidFileCountError, IntToString(i)); | |
| 219 return false; | |
| 220 } | |
| 221 | |
| 222 UserScriptInfo script_info; | |
| 223 for (size_t j = 0; j < matches->GetSize(); ++j) { | |
| 224 std::string match; | |
| 225 if (!matches->GetString(j, &match)) { | |
| 226 *error = FormatErrorMessage(kInvalidMatchError, IntToString(i), | |
| 227 IntToString(j)); | |
| 228 return false; | |
| 229 } | |
| 230 | |
| 231 script_info.matches.push_back(match); | |
| 232 } | |
| 233 | |
| 234 // TODO(aa): Support multiple files. | |
| 235 std::string file; | |
| 236 if (!files->GetString(0, &file)) { | |
| 237 *error = FormatErrorMessage(kInvalidFileError, IntToString(i), | |
| 238 IntToString(0)); | |
| 239 return false; | |
| 240 } | |
| 241 script_info.path = Extension::GetResourcePath(path(), file); | |
| 242 script_info.url = Extension::GetResourceURL(url(), file); | |
| 243 | |
| 244 user_scripts_.push_back(script_info); | |
| 245 } | 92 } |
| 246 } | 93 } |
| 247 | 94 |
| 248 return true; | 95 return true; |
| 249 } | 96 } |
| 97 |
| 98 void Extension::CopyToValue(DictionaryValue* destination) { |
| 99 // Set format version |
| 100 destination->SetInteger(kFormatVersionKey, |
| 101 kExpectedFormatVersion); |
| 102 |
| 103 // Copy id. |
| 104 destination->SetString(kIdKey, id_); |
| 105 |
| 106 // Copy version. |
| 107 destination->SetString(kVersionKey, version_); |
| 108 |
| 109 // Copy name. |
| 110 destination->SetString(kNameKey, name_); |
| 111 |
| 112 // Copy description (optional). |
| 113 if (description_.size() > 0) |
| 114 destination->SetString(kDescriptionKey, description_); |
| 115 |
| 116 // Copy content scripts (optional). |
| 117 if (content_scripts_.size() > 0) { |
| 118 ListValue* list_value = new ListValue(); |
| 119 destination->Set(kContentScriptsKey, list_value); |
| 120 |
| 121 for (size_t i = 0; i < content_scripts_.size(); ++i) { |
| 122 list_value->Set(i, Value::CreateStringValue(content_scripts_[i])); |
| 123 } |
| 124 } |
| 125 } |
| OLD | NEW |