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