1 // Copyright (c) 2013 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/common/extensions/manifest_handlers/content_scripts_handler.h"
6
7 #include "base/file_util.h"
8 #include "base/lazy_instance.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/values.h"
14 #include "content/public/common/url_constants.h"
15 #include "extensions/common/error_utils.h"
16 #include "extensions/common/extension.h"
17 #include "extensions/common/extension_resource.h"
18 #include "extensions/common/manifest_constants.h"
19 #include "extensions/common/manifest_handlers/permissions_parser.h"
20 #include "extensions/common/permissions/permissions_data.h"
21 #include "extensions/common/url_pattern.h"
22 #include "extensions/common/url_pattern_set.h"
23 #include "grit/generated_resources.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "url/gurl.h"
26
27 namespace extensions {
28
29 namespace keys = extensions::manifest_keys;
30 namespace values = manifest_values;
31 namespace errors = manifest_errors;
32
33 namespace {
34
35 // The globally-unique id for a user script.
36 int64 g_next_user_script_id = 0;
37
38 // Helper method that loads either the include_globs or exclude_globs list
39 // from an entry in the content_script lists of the manifest.
LoadGlobsHelper(const base::DictionaryValue * content_script,int content_script_index,const char * globs_property_name,base::string16 * error,void (UserScript::* add_method)(const std::string & glob),UserScript * instance)40 bool LoadGlobsHelper(const base::DictionaryValue* content_script,
41 int content_script_index,
42 const char* globs_property_name,
43 base::string16* error,
44 void(UserScript::*add_method)(const std::string& glob),
45 UserScript* instance) {
46 if (!content_script->HasKey(globs_property_name))
47 return true; // they are optional
48
49 const base::ListValue* list = NULL;
50 if (!content_script->GetList(globs_property_name, &list)) {
51 *error = ErrorUtils::FormatErrorMessageUTF16(
52 errors::kInvalidGlobList,
53 base::IntToString(content_script_index),
54 globs_property_name);
55 return false;
56 }
57
58 for (size_t i = 0; i < list->GetSize(); ++i) {
59 std::string glob;
60 if (!list->GetString(i, &glob)) {
61 *error = ErrorUtils::FormatErrorMessageUTF16(
62 errors::kInvalidGlob,
63 base::IntToString(content_script_index),
64 globs_property_name,
65 base::IntToString(i));
66 return false;
67 }
68
69 (instance->*add_method)(glob);
70 }
71
72 return true;
73 }
74
75 // Helper method that loads a UserScript object from a dictionary in the
76 // content_script list of the manifest.
LoadUserScriptFromDictionary(const base::DictionaryValue * content_script,int definition_index,Extension * extension,base::string16 * error,UserScript * result)77 bool LoadUserScriptFromDictionary(const base::DictionaryValue* content_script,
78 int definition_index,
79 Extension* extension,
80 base::string16* error,
81 UserScript* result) {
82 // run_at
83 if (content_script->HasKey(keys::kRunAt)) {
84 std::string run_location;
85 if (!content_script->GetString(keys::kRunAt, &run_location)) {
86 *error = ErrorUtils::FormatErrorMessageUTF16(
87 errors::kInvalidRunAt,
88 base::IntToString(definition_index));
89 return false;
90 }
91
92 if (run_location == values::kRunAtDocumentStart) {
93 result->set_run_location(UserScript::DOCUMENT_START);
94 } else if (run_location == values::kRunAtDocumentEnd) {
95 result->set_run_location(UserScript::DOCUMENT_END);
96 } else if (run_location == values::kRunAtDocumentIdle) {
97 result->set_run_location(UserScript::DOCUMENT_IDLE);
98 } else {
99 *error = ErrorUtils::FormatErrorMessageUTF16(
100 errors::kInvalidRunAt,
101 base::IntToString(definition_index));
102 return false;
103 }
104 }
105
106 // all frames
107 if (content_script->HasKey(keys::kAllFrames)) {
108 bool all_frames = false;
109 if (!content_script->GetBoolean(keys::kAllFrames, &all_frames)) {
110 *error = ErrorUtils::FormatErrorMessageUTF16(
111 errors::kInvalidAllFrames, base::IntToString(definition_index));
112 return false;
113 }
114 result->set_match_all_frames(all_frames);
115 }
116
117 // match about blank
118 if (content_script->HasKey(keys::kMatchAboutBlank)) {
119 bool match_about_blank = false;
120 if (!content_script->GetBoolean(keys::kMatchAboutBlank,
121 &match_about_blank)) {
122 *error = ErrorUtils::FormatErrorMessageUTF16(
123 errors::kInvalidMatchAboutBlank, base::IntToString(definition_index));
124 return false;
125 }
126 result->set_match_about_blank(match_about_blank);
127 }
128
129 // matches (required)
130 const base::ListValue* matches = NULL;
131 if (!content_script->GetList(keys::kMatches, &matches)) {
132 *error = ErrorUtils::FormatErrorMessageUTF16(
133 errors::kInvalidMatches,
134 base::IntToString(definition_index));
135 return false;
136 }
137
138 if (matches->GetSize() == 0) {
139 *error = ErrorUtils::FormatErrorMessageUTF16(
140 errors::kInvalidMatchCount,
141 base::IntToString(definition_index));
142 return false;
143 }
144 for (size_t j = 0; j < matches->GetSize(); ++j) {
145 std::string match_str;
146 if (!matches->GetString(j, &match_str)) {
147 *error = ErrorUtils::FormatErrorMessageUTF16(
148 errors::kInvalidMatch,
149 base::IntToString(definition_index),
150 base::IntToString(j),
151 errors::kExpectString);
152 return false;
153 }
154
155 URLPattern pattern(UserScript::ValidUserScriptSchemes(
156 PermissionsData::CanExecuteScriptEverywhere(extension)));
157
158 URLPattern::ParseResult parse_result = pattern.Parse(match_str);
159 if (parse_result != URLPattern::PARSE_SUCCESS) {
160 *error = ErrorUtils::FormatErrorMessageUTF16(
161 errors::kInvalidMatch,
162 base::IntToString(definition_index),
163 base::IntToString(j),
164 URLPattern::GetParseResultString(parse_result));
165 return false;
166 }
167
168 // TODO(aboxhall): check for webstore
169 if (!PermissionsData::CanExecuteScriptEverywhere(extension) &&
170 pattern.scheme() != content::kChromeUIScheme) {
171 // Exclude SCHEME_CHROMEUI unless it's been explicitly requested.
172 // If the --extensions-on-chrome-urls flag has not been passed, requesting
173 // a chrome:// url will cause a parse failure above, so there's no need to
174 // check the flag here.
175 pattern.SetValidSchemes(
176 pattern.valid_schemes() & ~URLPattern::SCHEME_CHROMEUI);
177 }
178
179 if (pattern.MatchesScheme(url::kFileScheme) &&
180 !PermissionsData::CanExecuteScriptEverywhere(extension)) {
181 extension->set_wants_file_access(true);
182 if (!(extension->creation_flags() & Extension::ALLOW_FILE_ACCESS)) {
183 pattern.SetValidSchemes(
184 pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
185 }
186 }
187
188 result->add_url_pattern(pattern);
189 }
190
191 // exclude_matches
192 if (content_script->HasKey(keys::kExcludeMatches)) { // optional
193 const base::ListValue* exclude_matches = NULL;
194 if (!content_script->GetList(keys::kExcludeMatches, &exclude_matches)) {
195 *error = ErrorUtils::FormatErrorMessageUTF16(
196 errors::kInvalidExcludeMatches,
197 base::IntToString(definition_index));
198 return false;
199 }
200
201 for (size_t j = 0; j < exclude_matches->GetSize(); ++j) {
202 std::string match_str;
203 if (!exclude_matches->GetString(j, &match_str)) {
204 *error = ErrorUtils::FormatErrorMessageUTF16(
205 errors::kInvalidExcludeMatch,
206 base::IntToString(definition_index),
207 base::IntToString(j),
208 errors::kExpectString);
209 return false;
210 }
211
212 int valid_schemes = UserScript::ValidUserScriptSchemes(
213 PermissionsData::CanExecuteScriptEverywhere(extension));
214 URLPattern pattern(valid_schemes);
215
216 URLPattern::ParseResult parse_result = pattern.Parse(match_str);
217 if (parse_result != URLPattern::PARSE_SUCCESS) {
218 *error = ErrorUtils::FormatErrorMessageUTF16(
219 errors::kInvalidExcludeMatch,
220 base::IntToString(definition_index), base::IntToString(j),
221 URLPattern::GetParseResultString(parse_result));
222 return false;
223 }
224
225 result->add_exclude_url_pattern(pattern);
226 }
227 }
228
229 // include/exclude globs (mostly for Greasemonkey compatibility)
230 if (!LoadGlobsHelper(content_script, definition_index, keys::kIncludeGlobs,
231 error, &UserScript::add_glob, result)) {
232 return false;
233 }
234
235 if (!LoadGlobsHelper(content_script, definition_index, keys::kExcludeGlobs,
236 error, &UserScript::add_exclude_glob, result)) {
237 return false;
238 }
239
240 // js and css keys
241 const base::ListValue* js = NULL;
242 if (content_script->HasKey(keys::kJs) &&
243 !content_script->GetList(keys::kJs, &js)) {
244 *error = ErrorUtils::FormatErrorMessageUTF16(
245 errors::kInvalidJsList,
246 base::IntToString(definition_index));
247 return false;
248 }
249
250 const base::ListValue* css = NULL;
251 if (content_script->HasKey(keys::kCss) &&
252 !content_script->GetList(keys::kCss, &css)) {
253 *error = ErrorUtils::
254 FormatErrorMessageUTF16(errors::kInvalidCssList,
255 base::IntToString(definition_index));
256 return false;
257 }
258
259 // The manifest needs to have at least one js or css user script definition.
260 if (((js ? js->GetSize() : 0) + (css ? css->GetSize() : 0)) == 0) {
261 *error = ErrorUtils::FormatErrorMessageUTF16(
262 errors::kMissingFile,
263 base::IntToString(definition_index));
264 return false;
265 }
266
267 if (js) {
268 for (size_t script_index = 0; script_index < js->GetSize();
269 ++script_index) {
270 const base::Value* value;
271 std::string relative;
272 if (!js->Get(script_index, &value) || !value->GetAsString(&relative)) {
273 *error = ErrorUtils::FormatErrorMessageUTF16(
274 errors::kInvalidJs,
275 base::IntToString(definition_index),
276 base::IntToString(script_index));
277 return false;
278 }
279 GURL url = extension->GetResourceURL(relative);
280 ExtensionResource resource = extension->GetResource(relative);
281 result->js_scripts().push_back(UserScript::File(
282 resource.extension_root(), resource.relative_path(), url));
283 }
284 }
285
286 if (css) {
287 for (size_t script_index = 0; script_index < css->GetSize();
288 ++script_index) {
289 const base::Value* value;
290 std::string relative;
291 if (!css->Get(script_index, &value) || !value->GetAsString(&relative)) {
292 *error = ErrorUtils::FormatErrorMessageUTF16(
293 errors::kInvalidCss,
294 base::IntToString(definition_index),
295 base::IntToString(script_index));
296 return false;
297 }
298 GURL url = extension->GetResourceURL(relative);
299 ExtensionResource resource = extension->GetResource(relative);
300 result->css_scripts().push_back(UserScript::File(
301 resource.extension_root(), resource.relative_path(), url));
302 }
303 }
304
305 return true;
306 }
307
308 // Returns false and sets the error if script file can't be loaded,
309 // or if it's not UTF-8 encoded.
IsScriptValid(const base::FilePath & path,const base::FilePath & relative_path,int message_id,std::string * error)310 static bool IsScriptValid(const base::FilePath& path,
311 const base::FilePath& relative_path,
312 int message_id,
313 std::string* error) {
314 std::string content;
315 if (!base::PathExists(path) ||
316 !base::ReadFileToString(path, &content)) {
317 *error = l10n_util::GetStringFUTF8(
318 message_id,
319 relative_path.LossyDisplayName());
320 return false;
321 }
322
323 if (!base::IsStringUTF8(content)) {
324 *error = l10n_util::GetStringFUTF8(
325 IDS_EXTENSION_BAD_FILE_ENCODING,
326 relative_path.LossyDisplayName());
327 return false;
328 }
329
330 return true;
331 }
332
333 struct EmptyUserScriptList {
334 UserScriptList user_script_list;
335 };
336
337 static base::LazyInstance<EmptyUserScriptList> g_empty_script_list =
338 LAZY_INSTANCE_INITIALIZER;
339
340 } // namespace
341
ContentScriptsInfo()342 ContentScriptsInfo::ContentScriptsInfo() {
343 }
344
~ContentScriptsInfo()345 ContentScriptsInfo::~ContentScriptsInfo() {
346 }
347
348 // static
GetContentScripts(const Extension * extension)349 const UserScriptList& ContentScriptsInfo::GetContentScripts(
350 const Extension* extension) {
351 ContentScriptsInfo* info = static_cast<ContentScriptsInfo*>(
352 extension->GetManifestData(keys::kContentScripts));
353 return info ? info->content_scripts
354 : g_empty_script_list.Get().user_script_list;
355 }
356
357 // static
ExtensionHasScriptAtURL(const Extension * extension,const GURL & url)358 bool ContentScriptsInfo::ExtensionHasScriptAtURL(const Extension* extension,
359 const GURL& url) {
360 const UserScriptList& content_scripts = GetContentScripts(extension);
361 for (UserScriptList::const_iterator iter = content_scripts.begin();
362 iter != content_scripts.end(); ++iter) {
363 if (iter->MatchesURL(url))
364 return true;
365 }
366 return false;
367 }
368
369 // static
GetScriptableHosts(const Extension * extension)370 URLPatternSet ContentScriptsInfo::GetScriptableHosts(
371 const Extension* extension) {
372 const UserScriptList& content_scripts = GetContentScripts(extension);
373 URLPatternSet scriptable_hosts;
374 for (UserScriptList::const_iterator content_script =
375 content_scripts.begin();
376 content_script != content_scripts.end();
377 ++content_script) {
378 URLPatternSet::const_iterator pattern =
379 content_script->url_patterns().begin();
380 for (; pattern != content_script->url_patterns().end(); ++pattern)
381 scriptable_hosts.AddPattern(*pattern);
382 }
383 return scriptable_hosts;
384 }
385
ContentScriptsHandler()386 ContentScriptsHandler::ContentScriptsHandler() {
387 }
388
~ContentScriptsHandler()389 ContentScriptsHandler::~ContentScriptsHandler() {
390 }
391
Keys() const392 const std::vector<std::string> ContentScriptsHandler::Keys() const {
393 static const char* keys[] = {
394 keys::kContentScripts
395 };
396 return std::vector<std::string>(keys, keys + arraysize(keys));
397 }
398
Parse(Extension * extension,base::string16 * error)399 bool ContentScriptsHandler::Parse(Extension* extension, base::string16* error) {
400 scoped_ptr<ContentScriptsInfo> content_scripts_info(new ContentScriptsInfo);
401 const base::ListValue* scripts_list = NULL;
402 if (!extension->manifest()->GetList(keys::kContentScripts, &scripts_list)) {
403 *error = base::ASCIIToUTF16(errors::kInvalidContentScriptsList);
404 return false;
405 }
406
407 for (size_t i = 0; i < scripts_list->GetSize(); ++i) {
408 const base::DictionaryValue* script_dict = NULL;
409 if (!scripts_list->GetDictionary(i, &script_dict)) {
410 *error = ErrorUtils::FormatErrorMessageUTF16(
411 errors::kInvalidContentScript,
412 base::IntToString(i));
413 return false;
414 }
415
416 UserScript user_script;
417 if (!LoadUserScriptFromDictionary(script_dict,
418 i,
419 extension,
420 error,
421 &user_script)) {
422 return false; // Failed to parse script context definition.
423 }
424
425 user_script.set_extension_id(extension->id());
426 if (extension->converted_from_user_script()) {
427 user_script.set_emulate_greasemonkey(true);
428 // Greasemonkey matches all frames.
429 user_script.set_match_all_frames(true);
430 }
431 user_script.set_id(g_next_user_script_id++);
432 content_scripts_info->content_scripts.push_back(user_script);
433 }
434 extension->SetManifestData(keys::kContentScripts,
435 content_scripts_info.release());
436 PermissionsParser::SetScriptableHosts(
437 extension, ContentScriptsInfo::GetScriptableHosts(extension));
438 return true;
439 }
440
Validate(const Extension * extension,std::string * error,std::vector<InstallWarning> * warnings) const441 bool ContentScriptsHandler::Validate(
442 const Extension* extension,
443 std::string* error,
444 std::vector<InstallWarning>* warnings) const {
445 // Validate that claimed script resources actually exist,
446 // and are UTF-8 encoded.
447 ExtensionResource::SymlinkPolicy symlink_policy;
448 if ((extension->creation_flags() &
449 Extension::FOLLOW_SYMLINKS_ANYWHERE) != 0) {
450 symlink_policy = ExtensionResource::FOLLOW_SYMLINKS_ANYWHERE;
451 } else {
452 symlink_policy = ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT;
453 }
454
455 const UserScriptList& content_scripts =
456 ContentScriptsInfo::GetContentScripts(extension);
457 for (size_t i = 0; i < content_scripts.size(); ++i) {
458 const UserScript& script = content_scripts[i];
459
460 for (size_t j = 0; j < script.js_scripts().size(); j++) {
461 const UserScript::File& js_script = script.js_scripts()[j];
462 const base::FilePath& path = ExtensionResource::GetFilePath(
463 js_script.extension_root(), js_script.relative_path(),
464 symlink_policy);
465 if (!IsScriptValid(path, js_script.relative_path(),
466 IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED, error))
467 return false;
468 }
469
470 for (size_t j = 0; j < script.css_scripts().size(); j++) {
471 const UserScript::File& css_script = script.css_scripts()[j];
472 const base::FilePath& path = ExtensionResource::GetFilePath(
473 css_script.extension_root(), css_script.relative_path(),
474 symlink_policy);
475 if (!IsScriptValid(path, css_script.relative_path(),
476 IDS_EXTENSION_LOAD_CSS_FAILED, error))
477 return false;
478 }
479 }
480
481 return true;
482 }
483
484 } // namespace extensions
485