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/host_id.h"
20 #include "extensions/common/manifest_constants.h"
21 #include "extensions/common/manifest_handlers/permissions_parser.h"
22 #include "extensions/common/permissions/permissions_data.h"
23 #include "extensions/common/url_pattern.h"
24 #include "extensions/common/url_pattern_set.h"
25 #include "ui/base/l10n/l10n_util.h"
28 namespace extensions
{
30 namespace keys
= extensions::manifest_keys
;
31 namespace values
= manifest_values
;
32 namespace errors
= manifest_errors
;
36 // Helper method that loads either the include_globs or exclude_globs list
37 // from an entry in the content_script lists of the manifest.
38 bool LoadGlobsHelper(const base::DictionaryValue
* content_script
,
39 int content_script_index
,
40 const char* globs_property_name
,
41 base::string16
* error
,
42 void(UserScript::*add_method
)(const std::string
& glob
),
43 UserScript
* instance
) {
44 if (!content_script
->HasKey(globs_property_name
))
45 return true; // they are optional
47 const base::ListValue
* list
= NULL
;
48 if (!content_script
->GetList(globs_property_name
, &list
)) {
49 *error
= ErrorUtils::FormatErrorMessageUTF16(
50 errors::kInvalidGlobList
,
51 base::IntToString(content_script_index
),
56 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
58 if (!list
->GetString(i
, &glob
)) {
59 *error
= ErrorUtils::FormatErrorMessageUTF16(
60 errors::kInvalidGlob
, base::IntToString(content_script_index
),
61 globs_property_name
, base::SizeTToString(i
));
65 (instance
->*add_method
)(glob
);
71 // Helper method that loads a UserScript object from a dictionary in the
72 // content_script list of the manifest.
73 bool LoadUserScriptFromDictionary(const base::DictionaryValue
* content_script
,
76 base::string16
* error
,
79 if (content_script
->HasKey(keys::kRunAt
)) {
80 std::string run_location
;
81 if (!content_script
->GetString(keys::kRunAt
, &run_location
)) {
82 *error
= ErrorUtils::FormatErrorMessageUTF16(
83 errors::kInvalidRunAt
,
84 base::IntToString(definition_index
));
88 if (run_location
== values::kRunAtDocumentStart
) {
89 result
->set_run_location(UserScript::DOCUMENT_START
);
90 } else if (run_location
== values::kRunAtDocumentEnd
) {
91 result
->set_run_location(UserScript::DOCUMENT_END
);
92 } else if (run_location
== values::kRunAtDocumentIdle
) {
93 result
->set_run_location(UserScript::DOCUMENT_IDLE
);
95 *error
= ErrorUtils::FormatErrorMessageUTF16(
96 errors::kInvalidRunAt
,
97 base::IntToString(definition_index
));
103 if (content_script
->HasKey(keys::kAllFrames
)) {
104 bool all_frames
= false;
105 if (!content_script
->GetBoolean(keys::kAllFrames
, &all_frames
)) {
106 *error
= ErrorUtils::FormatErrorMessageUTF16(
107 errors::kInvalidAllFrames
, base::IntToString(definition_index
));
110 result
->set_match_all_frames(all_frames
);
114 if (content_script
->HasKey(keys::kMatchAboutBlank
)) {
115 bool match_about_blank
= false;
116 if (!content_script
->GetBoolean(keys::kMatchAboutBlank
,
117 &match_about_blank
)) {
118 *error
= ErrorUtils::FormatErrorMessageUTF16(
119 errors::kInvalidMatchAboutBlank
, base::IntToString(definition_index
));
122 result
->set_match_about_blank(match_about_blank
);
125 // matches (required)
126 const base::ListValue
* matches
= NULL
;
127 if (!content_script
->GetList(keys::kMatches
, &matches
)) {
128 *error
= ErrorUtils::FormatErrorMessageUTF16(
129 errors::kInvalidMatches
,
130 base::IntToString(definition_index
));
134 if (matches
->GetSize() == 0) {
135 *error
= ErrorUtils::FormatErrorMessageUTF16(
136 errors::kInvalidMatchCount
,
137 base::IntToString(definition_index
));
140 for (size_t j
= 0; j
< matches
->GetSize(); ++j
) {
141 std::string match_str
;
142 if (!matches
->GetString(j
, &match_str
)) {
143 *error
= ErrorUtils::FormatErrorMessageUTF16(
144 errors::kInvalidMatch
, base::IntToString(definition_index
),
145 base::SizeTToString(j
), errors::kExpectString
);
149 URLPattern
pattern(UserScript::ValidUserScriptSchemes(
150 PermissionsData::CanExecuteScriptEverywhere(extension
)));
152 URLPattern::ParseResult parse_result
= pattern
.Parse(match_str
);
153 if (parse_result
!= URLPattern::PARSE_SUCCESS
) {
154 *error
= ErrorUtils::FormatErrorMessageUTF16(
155 errors::kInvalidMatch
, base::IntToString(definition_index
),
156 base::SizeTToString(j
),
157 URLPattern::GetParseResultString(parse_result
));
161 // TODO(aboxhall): check for webstore
162 if (!PermissionsData::CanExecuteScriptEverywhere(extension
) &&
163 pattern
.scheme() != content::kChromeUIScheme
) {
164 // Exclude SCHEME_CHROMEUI unless it's been explicitly requested.
165 // If the --extensions-on-chrome-urls flag has not been passed, requesting
166 // a chrome:// url will cause a parse failure above, so there's no need to
167 // check the flag here.
168 pattern
.SetValidSchemes(
169 pattern
.valid_schemes() & ~URLPattern::SCHEME_CHROMEUI
);
172 if (pattern
.MatchesScheme(url::kFileScheme
) &&
173 !PermissionsData::CanExecuteScriptEverywhere(extension
)) {
174 extension
->set_wants_file_access(true);
175 if (!(extension
->creation_flags() & Extension::ALLOW_FILE_ACCESS
)) {
176 pattern
.SetValidSchemes(
177 pattern
.valid_schemes() & ~URLPattern::SCHEME_FILE
);
181 result
->add_url_pattern(pattern
);
185 if (content_script
->HasKey(keys::kExcludeMatches
)) { // optional
186 const base::ListValue
* exclude_matches
= NULL
;
187 if (!content_script
->GetList(keys::kExcludeMatches
, &exclude_matches
)) {
188 *error
= ErrorUtils::FormatErrorMessageUTF16(
189 errors::kInvalidExcludeMatches
,
190 base::IntToString(definition_index
));
194 for (size_t j
= 0; j
< exclude_matches
->GetSize(); ++j
) {
195 std::string match_str
;
196 if (!exclude_matches
->GetString(j
, &match_str
)) {
197 *error
= ErrorUtils::FormatErrorMessageUTF16(
198 errors::kInvalidExcludeMatch
, base::IntToString(definition_index
),
199 base::SizeTToString(j
), errors::kExpectString
);
203 int valid_schemes
= UserScript::ValidUserScriptSchemes(
204 PermissionsData::CanExecuteScriptEverywhere(extension
));
205 URLPattern
pattern(valid_schemes
);
207 URLPattern::ParseResult parse_result
= pattern
.Parse(match_str
);
208 if (parse_result
!= URLPattern::PARSE_SUCCESS
) {
209 *error
= ErrorUtils::FormatErrorMessageUTF16(
210 errors::kInvalidExcludeMatch
, base::IntToString(definition_index
),
211 base::SizeTToString(j
),
212 URLPattern::GetParseResultString(parse_result
));
216 result
->add_exclude_url_pattern(pattern
);
220 // include/exclude globs (mostly for Greasemonkey compatibility)
221 if (!LoadGlobsHelper(content_script
, definition_index
, keys::kIncludeGlobs
,
222 error
, &UserScript::add_glob
, result
)) {
226 if (!LoadGlobsHelper(content_script
, definition_index
, keys::kExcludeGlobs
,
227 error
, &UserScript::add_exclude_glob
, result
)) {
232 const base::ListValue
* js
= NULL
;
233 if (content_script
->HasKey(keys::kJs
) &&
234 !content_script
->GetList(keys::kJs
, &js
)) {
235 *error
= ErrorUtils::FormatErrorMessageUTF16(
236 errors::kInvalidJsList
,
237 base::IntToString(definition_index
));
241 const base::ListValue
* css
= NULL
;
242 if (content_script
->HasKey(keys::kCss
) &&
243 !content_script
->GetList(keys::kCss
, &css
)) {
244 *error
= ErrorUtils::
245 FormatErrorMessageUTF16(errors::kInvalidCssList
,
246 base::IntToString(definition_index
));
250 // The manifest needs to have at least one js or css user script definition.
251 if (((js
? js
->GetSize() : 0) + (css
? css
->GetSize() : 0)) == 0) {
252 *error
= ErrorUtils::FormatErrorMessageUTF16(
253 errors::kMissingFile
,
254 base::IntToString(definition_index
));
259 for (size_t script_index
= 0; script_index
< js
->GetSize();
261 const base::Value
* value
;
262 std::string relative
;
263 if (!js
->Get(script_index
, &value
) || !value
->GetAsString(&relative
)) {
264 *error
= ErrorUtils::FormatErrorMessageUTF16(
265 errors::kInvalidJs
, base::IntToString(definition_index
),
266 base::SizeTToString(script_index
));
269 GURL url
= extension
->GetResourceURL(relative
);
270 ExtensionResource resource
= extension
->GetResource(relative
);
271 result
->js_scripts().push_back(UserScript::File(
272 resource
.extension_root(), resource
.relative_path(), url
));
277 for (size_t script_index
= 0; script_index
< css
->GetSize();
279 const base::Value
* value
;
280 std::string relative
;
281 if (!css
->Get(script_index
, &value
) || !value
->GetAsString(&relative
)) {
282 *error
= ErrorUtils::FormatErrorMessageUTF16(
283 errors::kInvalidCss
, base::IntToString(definition_index
),
284 base::SizeTToString(script_index
));
287 GURL url
= extension
->GetResourceURL(relative
);
288 ExtensionResource resource
= extension
->GetResource(relative
);
289 result
->css_scripts().push_back(UserScript::File(
290 resource
.extension_root(), resource
.relative_path(), url
));
297 // Returns false and sets the error if script file can't be loaded,
298 // or if it's not UTF-8 encoded.
299 static bool IsScriptValid(const base::FilePath
& path
,
300 const base::FilePath
& relative_path
,
302 std::string
* error
) {
304 if (!base::PathExists(path
) ||
305 !base::ReadFileToString(path
, &content
)) {
306 *error
= l10n_util::GetStringFUTF8(
308 relative_path
.LossyDisplayName());
312 if (!base::IsStringUTF8(content
)) {
313 *error
= l10n_util::GetStringFUTF8(
314 IDS_EXTENSION_BAD_FILE_ENCODING
,
315 relative_path
.LossyDisplayName());
322 struct EmptyUserScriptList
{
323 UserScriptList user_script_list
;
326 static base::LazyInstance
<EmptyUserScriptList
> g_empty_script_list
=
327 LAZY_INSTANCE_INITIALIZER
;
331 ContentScriptsInfo::ContentScriptsInfo() {
334 ContentScriptsInfo::~ContentScriptsInfo() {
338 const UserScriptList
& ContentScriptsInfo::GetContentScripts(
339 const Extension
* extension
) {
340 ContentScriptsInfo
* info
= static_cast<ContentScriptsInfo
*>(
341 extension
->GetManifestData(keys::kContentScripts
));
342 return info
? info
->content_scripts
343 : g_empty_script_list
.Get().user_script_list
;
347 bool ContentScriptsInfo::ExtensionHasScriptAtURL(const Extension
* extension
,
349 const UserScriptList
& content_scripts
= GetContentScripts(extension
);
350 for (UserScriptList::const_iterator iter
= content_scripts
.begin();
351 iter
!= content_scripts
.end(); ++iter
) {
352 if (iter
->MatchesURL(url
))
359 URLPatternSet
ContentScriptsInfo::GetScriptableHosts(
360 const Extension
* extension
) {
361 const UserScriptList
& content_scripts
= GetContentScripts(extension
);
362 URLPatternSet scriptable_hosts
;
363 for (UserScriptList::const_iterator content_script
=
364 content_scripts
.begin();
365 content_script
!= content_scripts
.end();
367 URLPatternSet::const_iterator pattern
=
368 content_script
->url_patterns().begin();
369 for (; pattern
!= content_script
->url_patterns().end(); ++pattern
)
370 scriptable_hosts
.AddPattern(*pattern
);
372 return scriptable_hosts
;
375 ContentScriptsHandler::ContentScriptsHandler() {
378 ContentScriptsHandler::~ContentScriptsHandler() {
381 const std::vector
<std::string
> ContentScriptsHandler::Keys() const {
382 static const char* const keys
[] = {
383 keys::kContentScripts
385 return std::vector
<std::string
>(keys
, keys
+ arraysize(keys
));
388 bool ContentScriptsHandler::Parse(Extension
* extension
, base::string16
* error
) {
389 scoped_ptr
<ContentScriptsInfo
> content_scripts_info(new ContentScriptsInfo
);
390 const base::ListValue
* scripts_list
= NULL
;
391 if (!extension
->manifest()->GetList(keys::kContentScripts
, &scripts_list
)) {
392 *error
= base::ASCIIToUTF16(errors::kInvalidContentScriptsList
);
396 for (size_t i
= 0; i
< scripts_list
->GetSize(); ++i
) {
397 const base::DictionaryValue
* script_dict
= NULL
;
398 if (!scripts_list
->GetDictionary(i
, &script_dict
)) {
399 *error
= ErrorUtils::FormatErrorMessageUTF16(
400 errors::kInvalidContentScript
, base::SizeTToString(i
));
404 UserScript user_script
;
405 if (!LoadUserScriptFromDictionary(script_dict
,
410 return false; // Failed to parse script context definition.
413 user_script
.set_host_id(HostID(HostID::EXTENSIONS
, extension
->id()));
414 if (extension
->converted_from_user_script()) {
415 user_script
.set_emulate_greasemonkey(true);
416 // Greasemonkey matches all frames.
417 user_script
.set_match_all_frames(true);
419 user_script
.set_id(UserScript::GenerateUserScriptID());
420 content_scripts_info
->content_scripts
.push_back(user_script
);
422 extension
->SetManifestData(keys::kContentScripts
,
423 content_scripts_info
.release());
424 PermissionsParser::SetScriptableHosts(
425 extension
, ContentScriptsInfo::GetScriptableHosts(extension
));
429 bool ContentScriptsHandler::Validate(
430 const Extension
* extension
,
432 std::vector
<InstallWarning
>* warnings
) const {
433 // Validate that claimed script resources actually exist,
434 // and are UTF-8 encoded.
435 ExtensionResource::SymlinkPolicy symlink_policy
;
436 if ((extension
->creation_flags() &
437 Extension::FOLLOW_SYMLINKS_ANYWHERE
) != 0) {
438 symlink_policy
= ExtensionResource::FOLLOW_SYMLINKS_ANYWHERE
;
440 symlink_policy
= ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT
;
443 const UserScriptList
& content_scripts
=
444 ContentScriptsInfo::GetContentScripts(extension
);
445 for (size_t i
= 0; i
< content_scripts
.size(); ++i
) {
446 const UserScript
& script
= content_scripts
[i
];
448 for (size_t j
= 0; j
< script
.js_scripts().size(); j
++) {
449 const UserScript::File
& js_script
= script
.js_scripts()[j
];
450 const base::FilePath
& path
= ExtensionResource::GetFilePath(
451 js_script
.extension_root(), js_script
.relative_path(),
453 if (!IsScriptValid(path
, js_script
.relative_path(),
454 IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED
, error
))
458 for (size_t j
= 0; j
< script
.css_scripts().size(); j
++) {
459 const UserScript::File
& css_script
= script
.css_scripts()[j
];
460 const base::FilePath
& path
= ExtensionResource::GetFilePath(
461 css_script
.extension_root(), css_script
.relative_path(),
463 if (!IsScriptValid(path
, css_script
.relative_path(),
464 IDS_EXTENSION_LOAD_CSS_FAILED
, error
))
472 } // namespace extensions