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.
5 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
7 #include "base/files/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 "chrome/grit/generated_resources.h"
15 #include "content/public/common/url_constants.h"
16 #include "extensions/common/error_utils.h"
17 #include "extensions/common/extension.h"
18 #include "extensions/common/extension_resource.h"
19 #include "extensions/common/manifest_constants.h"
20 #include "extensions/common/manifest_handlers/permissions_parser.h"
21 #include "extensions/common/permissions/permissions_data.h"
22 #include "extensions/common/url_pattern.h"
23 #include "extensions/common/url_pattern_set.h"
24 #include "ui/base/l10n/l10n_util.h"
27 namespace extensions
{
29 namespace keys
= extensions::manifest_keys
;
30 namespace values
= manifest_values
;
31 namespace errors
= manifest_errors
;
35 // Helper method that loads either the include_globs or exclude_globs list
36 // from an entry in the content_script lists of the manifest.
37 bool LoadGlobsHelper(const base::DictionaryValue
* content_script
,
38 int content_script_index
,
39 const char* globs_property_name
,
40 base::string16
* error
,
41 void(UserScript::*add_method
)(const std::string
& glob
),
42 UserScript
* instance
) {
43 if (!content_script
->HasKey(globs_property_name
))
44 return true; // they are optional
46 const base::ListValue
* list
= NULL
;
47 if (!content_script
->GetList(globs_property_name
, &list
)) {
48 *error
= ErrorUtils::FormatErrorMessageUTF16(
49 errors::kInvalidGlobList
,
50 base::IntToString(content_script_index
),
55 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
57 if (!list
->GetString(i
, &glob
)) {
58 *error
= ErrorUtils::FormatErrorMessageUTF16(
60 base::IntToString(content_script_index
),
62 base::IntToString(i
));
66 (instance
->*add_method
)(glob
);
72 // Helper method that loads a UserScript object from a dictionary in the
73 // content_script list of the manifest.
74 bool LoadUserScriptFromDictionary(const base::DictionaryValue
* content_script
,
77 base::string16
* error
,
80 if (content_script
->HasKey(keys::kRunAt
)) {
81 std::string run_location
;
82 if (!content_script
->GetString(keys::kRunAt
, &run_location
)) {
83 *error
= ErrorUtils::FormatErrorMessageUTF16(
84 errors::kInvalidRunAt
,
85 base::IntToString(definition_index
));
89 if (run_location
== values::kRunAtDocumentStart
) {
90 result
->set_run_location(UserScript::DOCUMENT_START
);
91 } else if (run_location
== values::kRunAtDocumentEnd
) {
92 result
->set_run_location(UserScript::DOCUMENT_END
);
93 } else if (run_location
== values::kRunAtDocumentIdle
) {
94 result
->set_run_location(UserScript::DOCUMENT_IDLE
);
96 *error
= ErrorUtils::FormatErrorMessageUTF16(
97 errors::kInvalidRunAt
,
98 base::IntToString(definition_index
));
104 if (content_script
->HasKey(keys::kAllFrames
)) {
105 bool all_frames
= false;
106 if (!content_script
->GetBoolean(keys::kAllFrames
, &all_frames
)) {
107 *error
= ErrorUtils::FormatErrorMessageUTF16(
108 errors::kInvalidAllFrames
, base::IntToString(definition_index
));
111 result
->set_match_all_frames(all_frames
);
115 if (content_script
->HasKey(keys::kMatchAboutBlank
)) {
116 bool match_about_blank
= false;
117 if (!content_script
->GetBoolean(keys::kMatchAboutBlank
,
118 &match_about_blank
)) {
119 *error
= ErrorUtils::FormatErrorMessageUTF16(
120 errors::kInvalidMatchAboutBlank
, base::IntToString(definition_index
));
123 result
->set_match_about_blank(match_about_blank
);
126 // matches (required)
127 const base::ListValue
* matches
= NULL
;
128 if (!content_script
->GetList(keys::kMatches
, &matches
)) {
129 *error
= ErrorUtils::FormatErrorMessageUTF16(
130 errors::kInvalidMatches
,
131 base::IntToString(definition_index
));
135 if (matches
->GetSize() == 0) {
136 *error
= ErrorUtils::FormatErrorMessageUTF16(
137 errors::kInvalidMatchCount
,
138 base::IntToString(definition_index
));
141 for (size_t j
= 0; j
< matches
->GetSize(); ++j
) {
142 std::string match_str
;
143 if (!matches
->GetString(j
, &match_str
)) {
144 *error
= ErrorUtils::FormatErrorMessageUTF16(
145 errors::kInvalidMatch
,
146 base::IntToString(definition_index
),
147 base::IntToString(j
),
148 errors::kExpectString
);
152 URLPattern
pattern(UserScript::ValidUserScriptSchemes(
153 PermissionsData::CanExecuteScriptEverywhere(extension
)));
155 URLPattern::ParseResult parse_result
= pattern
.Parse(match_str
);
156 if (parse_result
!= URLPattern::PARSE_SUCCESS
) {
157 *error
= ErrorUtils::FormatErrorMessageUTF16(
158 errors::kInvalidMatch
,
159 base::IntToString(definition_index
),
160 base::IntToString(j
),
161 URLPattern::GetParseResultString(parse_result
));
165 // TODO(aboxhall): check for webstore
166 if (!PermissionsData::CanExecuteScriptEverywhere(extension
) &&
167 pattern
.scheme() != content::kChromeUIScheme
) {
168 // Exclude SCHEME_CHROMEUI unless it's been explicitly requested.
169 // If the --extensions-on-chrome-urls flag has not been passed, requesting
170 // a chrome:// url will cause a parse failure above, so there's no need to
171 // check the flag here.
172 pattern
.SetValidSchemes(
173 pattern
.valid_schemes() & ~URLPattern::SCHEME_CHROMEUI
);
176 if (pattern
.MatchesScheme(url::kFileScheme
) &&
177 !PermissionsData::CanExecuteScriptEverywhere(extension
)) {
178 extension
->set_wants_file_access(true);
179 if (!(extension
->creation_flags() & Extension::ALLOW_FILE_ACCESS
)) {
180 pattern
.SetValidSchemes(
181 pattern
.valid_schemes() & ~URLPattern::SCHEME_FILE
);
185 result
->add_url_pattern(pattern
);
189 if (content_script
->HasKey(keys::kExcludeMatches
)) { // optional
190 const base::ListValue
* exclude_matches
= NULL
;
191 if (!content_script
->GetList(keys::kExcludeMatches
, &exclude_matches
)) {
192 *error
= ErrorUtils::FormatErrorMessageUTF16(
193 errors::kInvalidExcludeMatches
,
194 base::IntToString(definition_index
));
198 for (size_t j
= 0; j
< exclude_matches
->GetSize(); ++j
) {
199 std::string match_str
;
200 if (!exclude_matches
->GetString(j
, &match_str
)) {
201 *error
= ErrorUtils::FormatErrorMessageUTF16(
202 errors::kInvalidExcludeMatch
,
203 base::IntToString(definition_index
),
204 base::IntToString(j
),
205 errors::kExpectString
);
209 int valid_schemes
= UserScript::ValidUserScriptSchemes(
210 PermissionsData::CanExecuteScriptEverywhere(extension
));
211 URLPattern
pattern(valid_schemes
);
213 URLPattern::ParseResult parse_result
= pattern
.Parse(match_str
);
214 if (parse_result
!= URLPattern::PARSE_SUCCESS
) {
215 *error
= ErrorUtils::FormatErrorMessageUTF16(
216 errors::kInvalidExcludeMatch
,
217 base::IntToString(definition_index
), base::IntToString(j
),
218 URLPattern::GetParseResultString(parse_result
));
222 result
->add_exclude_url_pattern(pattern
);
226 // include/exclude globs (mostly for Greasemonkey compatibility)
227 if (!LoadGlobsHelper(content_script
, definition_index
, keys::kIncludeGlobs
,
228 error
, &UserScript::add_glob
, result
)) {
232 if (!LoadGlobsHelper(content_script
, definition_index
, keys::kExcludeGlobs
,
233 error
, &UserScript::add_exclude_glob
, result
)) {
238 const base::ListValue
* js
= NULL
;
239 if (content_script
->HasKey(keys::kJs
) &&
240 !content_script
->GetList(keys::kJs
, &js
)) {
241 *error
= ErrorUtils::FormatErrorMessageUTF16(
242 errors::kInvalidJsList
,
243 base::IntToString(definition_index
));
247 const base::ListValue
* css
= NULL
;
248 if (content_script
->HasKey(keys::kCss
) &&
249 !content_script
->GetList(keys::kCss
, &css
)) {
250 *error
= ErrorUtils::
251 FormatErrorMessageUTF16(errors::kInvalidCssList
,
252 base::IntToString(definition_index
));
256 // The manifest needs to have at least one js or css user script definition.
257 if (((js
? js
->GetSize() : 0) + (css
? css
->GetSize() : 0)) == 0) {
258 *error
= ErrorUtils::FormatErrorMessageUTF16(
259 errors::kMissingFile
,
260 base::IntToString(definition_index
));
265 for (size_t script_index
= 0; script_index
< js
->GetSize();
267 const base::Value
* value
;
268 std::string relative
;
269 if (!js
->Get(script_index
, &value
) || !value
->GetAsString(&relative
)) {
270 *error
= ErrorUtils::FormatErrorMessageUTF16(
272 base::IntToString(definition_index
),
273 base::IntToString(script_index
));
276 GURL url
= extension
->GetResourceURL(relative
);
277 ExtensionResource resource
= extension
->GetResource(relative
);
278 result
->js_scripts().push_back(UserScript::File(
279 resource
.extension_root(), resource
.relative_path(), url
));
284 for (size_t script_index
= 0; script_index
< css
->GetSize();
286 const base::Value
* value
;
287 std::string relative
;
288 if (!css
->Get(script_index
, &value
) || !value
->GetAsString(&relative
)) {
289 *error
= ErrorUtils::FormatErrorMessageUTF16(
291 base::IntToString(definition_index
),
292 base::IntToString(script_index
));
295 GURL url
= extension
->GetResourceURL(relative
);
296 ExtensionResource resource
= extension
->GetResource(relative
);
297 result
->css_scripts().push_back(UserScript::File(
298 resource
.extension_root(), resource
.relative_path(), url
));
305 // Returns false and sets the error if script file can't be loaded,
306 // or if it's not UTF-8 encoded.
307 static bool IsScriptValid(const base::FilePath
& path
,
308 const base::FilePath
& relative_path
,
310 std::string
* error
) {
312 if (!base::PathExists(path
) ||
313 !base::ReadFileToString(path
, &content
)) {
314 *error
= l10n_util::GetStringFUTF8(
316 relative_path
.LossyDisplayName());
320 if (!base::IsStringUTF8(content
)) {
321 *error
= l10n_util::GetStringFUTF8(
322 IDS_EXTENSION_BAD_FILE_ENCODING
,
323 relative_path
.LossyDisplayName());
330 struct EmptyUserScriptList
{
331 UserScriptList user_script_list
;
334 static base::LazyInstance
<EmptyUserScriptList
> g_empty_script_list
=
335 LAZY_INSTANCE_INITIALIZER
;
339 ContentScriptsInfo::ContentScriptsInfo() {
342 ContentScriptsInfo::~ContentScriptsInfo() {
346 const UserScriptList
& ContentScriptsInfo::GetContentScripts(
347 const Extension
* extension
) {
348 ContentScriptsInfo
* info
= static_cast<ContentScriptsInfo
*>(
349 extension
->GetManifestData(keys::kContentScripts
));
350 return info
? info
->content_scripts
351 : g_empty_script_list
.Get().user_script_list
;
355 bool ContentScriptsInfo::ExtensionHasScriptAtURL(const Extension
* extension
,
357 const UserScriptList
& content_scripts
= GetContentScripts(extension
);
358 for (UserScriptList::const_iterator iter
= content_scripts
.begin();
359 iter
!= content_scripts
.end(); ++iter
) {
360 if (iter
->MatchesURL(url
))
367 URLPatternSet
ContentScriptsInfo::GetScriptableHosts(
368 const Extension
* extension
) {
369 const UserScriptList
& content_scripts
= GetContentScripts(extension
);
370 URLPatternSet scriptable_hosts
;
371 for (UserScriptList::const_iterator content_script
=
372 content_scripts
.begin();
373 content_script
!= content_scripts
.end();
375 URLPatternSet::const_iterator pattern
=
376 content_script
->url_patterns().begin();
377 for (; pattern
!= content_script
->url_patterns().end(); ++pattern
)
378 scriptable_hosts
.AddPattern(*pattern
);
380 return scriptable_hosts
;
383 ContentScriptsHandler::ContentScriptsHandler() {
386 ContentScriptsHandler::~ContentScriptsHandler() {
389 const std::vector
<std::string
> ContentScriptsHandler::Keys() const {
390 static const char* const keys
[] = {
391 keys::kContentScripts
393 return std::vector
<std::string
>(keys
, keys
+ arraysize(keys
));
396 bool ContentScriptsHandler::Parse(Extension
* extension
, base::string16
* error
) {
397 scoped_ptr
<ContentScriptsInfo
> content_scripts_info(new ContentScriptsInfo
);
398 const base::ListValue
* scripts_list
= NULL
;
399 if (!extension
->manifest()->GetList(keys::kContentScripts
, &scripts_list
)) {
400 *error
= base::ASCIIToUTF16(errors::kInvalidContentScriptsList
);
404 for (size_t i
= 0; i
< scripts_list
->GetSize(); ++i
) {
405 const base::DictionaryValue
* script_dict
= NULL
;
406 if (!scripts_list
->GetDictionary(i
, &script_dict
)) {
407 *error
= ErrorUtils::FormatErrorMessageUTF16(
408 errors::kInvalidContentScript
,
409 base::IntToString(i
));
413 UserScript user_script
;
414 if (!LoadUserScriptFromDictionary(script_dict
,
419 return false; // Failed to parse script context definition.
422 user_script
.set_extension_id(extension
->id());
423 if (extension
->converted_from_user_script()) {
424 user_script
.set_emulate_greasemonkey(true);
425 // Greasemonkey matches all frames.
426 user_script
.set_match_all_frames(true);
428 user_script
.set_id(UserScript::GenerateUserScriptID());
429 content_scripts_info
->content_scripts
.push_back(user_script
);
431 extension
->SetManifestData(keys::kContentScripts
,
432 content_scripts_info
.release());
433 PermissionsParser::SetScriptableHosts(
434 extension
, ContentScriptsInfo::GetScriptableHosts(extension
));
438 bool ContentScriptsHandler::Validate(
439 const Extension
* extension
,
441 std::vector
<InstallWarning
>* warnings
) const {
442 // Validate that claimed script resources actually exist,
443 // and are UTF-8 encoded.
444 ExtensionResource::SymlinkPolicy symlink_policy
;
445 if ((extension
->creation_flags() &
446 Extension::FOLLOW_SYMLINKS_ANYWHERE
) != 0) {
447 symlink_policy
= ExtensionResource::FOLLOW_SYMLINKS_ANYWHERE
;
449 symlink_policy
= ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT
;
452 const UserScriptList
& content_scripts
=
453 ContentScriptsInfo::GetContentScripts(extension
);
454 for (size_t i
= 0; i
< content_scripts
.size(); ++i
) {
455 const UserScript
& script
= content_scripts
[i
];
457 for (size_t j
= 0; j
< script
.js_scripts().size(); j
++) {
458 const UserScript::File
& js_script
= script
.js_scripts()[j
];
459 const base::FilePath
& path
= ExtensionResource::GetFilePath(
460 js_script
.extension_root(), js_script
.relative_path(),
462 if (!IsScriptValid(path
, js_script
.relative_path(),
463 IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED
, error
))
467 for (size_t j
= 0; j
< script
.css_scripts().size(); j
++) {
468 const UserScript::File
& css_script
= script
.css_scripts()[j
];
469 const base::FilePath
& path
= ExtensionResource::GetFilePath(
470 css_script
.extension_root(), css_script
.relative_path(),
472 if (!IsScriptValid(path
, css_script
.relative_path(),
473 IDS_EXTENSION_LOAD_CSS_FAILED
, error
))
481 } // namespace extensions