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(
61 base::IntToString(content_script_index
),
63 base::IntToString(i
));
67 (instance
->*add_method
)(glob
);
73 // Helper method that loads a UserScript object from a dictionary in the
74 // content_script list of the manifest.
75 bool LoadUserScriptFromDictionary(const base::DictionaryValue
* content_script
,
78 base::string16
* error
,
81 if (content_script
->HasKey(keys::kRunAt
)) {
82 std::string run_location
;
83 if (!content_script
->GetString(keys::kRunAt
, &run_location
)) {
84 *error
= ErrorUtils::FormatErrorMessageUTF16(
85 errors::kInvalidRunAt
,
86 base::IntToString(definition_index
));
90 if (run_location
== values::kRunAtDocumentStart
) {
91 result
->set_run_location(UserScript::DOCUMENT_START
);
92 } else if (run_location
== values::kRunAtDocumentEnd
) {
93 result
->set_run_location(UserScript::DOCUMENT_END
);
94 } else if (run_location
== values::kRunAtDocumentIdle
) {
95 result
->set_run_location(UserScript::DOCUMENT_IDLE
);
97 *error
= ErrorUtils::FormatErrorMessageUTF16(
98 errors::kInvalidRunAt
,
99 base::IntToString(definition_index
));
105 if (content_script
->HasKey(keys::kAllFrames
)) {
106 bool all_frames
= false;
107 if (!content_script
->GetBoolean(keys::kAllFrames
, &all_frames
)) {
108 *error
= ErrorUtils::FormatErrorMessageUTF16(
109 errors::kInvalidAllFrames
, base::IntToString(definition_index
));
112 result
->set_match_all_frames(all_frames
);
116 if (content_script
->HasKey(keys::kMatchAboutBlank
)) {
117 bool match_about_blank
= false;
118 if (!content_script
->GetBoolean(keys::kMatchAboutBlank
,
119 &match_about_blank
)) {
120 *error
= ErrorUtils::FormatErrorMessageUTF16(
121 errors::kInvalidMatchAboutBlank
, base::IntToString(definition_index
));
124 result
->set_match_about_blank(match_about_blank
);
127 // matches (required)
128 const base::ListValue
* matches
= NULL
;
129 if (!content_script
->GetList(keys::kMatches
, &matches
)) {
130 *error
= ErrorUtils::FormatErrorMessageUTF16(
131 errors::kInvalidMatches
,
132 base::IntToString(definition_index
));
136 if (matches
->GetSize() == 0) {
137 *error
= ErrorUtils::FormatErrorMessageUTF16(
138 errors::kInvalidMatchCount
,
139 base::IntToString(definition_index
));
142 for (size_t j
= 0; j
< matches
->GetSize(); ++j
) {
143 std::string match_str
;
144 if (!matches
->GetString(j
, &match_str
)) {
145 *error
= ErrorUtils::FormatErrorMessageUTF16(
146 errors::kInvalidMatch
,
147 base::IntToString(definition_index
),
148 base::IntToString(j
),
149 errors::kExpectString
);
153 URLPattern
pattern(UserScript::ValidUserScriptSchemes(
154 PermissionsData::CanExecuteScriptEverywhere(extension
)));
156 URLPattern::ParseResult parse_result
= pattern
.Parse(match_str
);
157 if (parse_result
!= URLPattern::PARSE_SUCCESS
) {
158 *error
= ErrorUtils::FormatErrorMessageUTF16(
159 errors::kInvalidMatch
,
160 base::IntToString(definition_index
),
161 base::IntToString(j
),
162 URLPattern::GetParseResultString(parse_result
));
166 // TODO(aboxhall): check for webstore
167 if (!PermissionsData::CanExecuteScriptEverywhere(extension
) &&
168 pattern
.scheme() != content::kChromeUIScheme
) {
169 // Exclude SCHEME_CHROMEUI unless it's been explicitly requested.
170 // If the --extensions-on-chrome-urls flag has not been passed, requesting
171 // a chrome:// url will cause a parse failure above, so there's no need to
172 // check the flag here.
173 pattern
.SetValidSchemes(
174 pattern
.valid_schemes() & ~URLPattern::SCHEME_CHROMEUI
);
177 if (pattern
.MatchesScheme(url::kFileScheme
) &&
178 !PermissionsData::CanExecuteScriptEverywhere(extension
)) {
179 extension
->set_wants_file_access(true);
180 if (!(extension
->creation_flags() & Extension::ALLOW_FILE_ACCESS
)) {
181 pattern
.SetValidSchemes(
182 pattern
.valid_schemes() & ~URLPattern::SCHEME_FILE
);
186 result
->add_url_pattern(pattern
);
190 if (content_script
->HasKey(keys::kExcludeMatches
)) { // optional
191 const base::ListValue
* exclude_matches
= NULL
;
192 if (!content_script
->GetList(keys::kExcludeMatches
, &exclude_matches
)) {
193 *error
= ErrorUtils::FormatErrorMessageUTF16(
194 errors::kInvalidExcludeMatches
,
195 base::IntToString(definition_index
));
199 for (size_t j
= 0; j
< exclude_matches
->GetSize(); ++j
) {
200 std::string match_str
;
201 if (!exclude_matches
->GetString(j
, &match_str
)) {
202 *error
= ErrorUtils::FormatErrorMessageUTF16(
203 errors::kInvalidExcludeMatch
,
204 base::IntToString(definition_index
),
205 base::IntToString(j
),
206 errors::kExpectString
);
210 int valid_schemes
= UserScript::ValidUserScriptSchemes(
211 PermissionsData::CanExecuteScriptEverywhere(extension
));
212 URLPattern
pattern(valid_schemes
);
214 URLPattern::ParseResult parse_result
= pattern
.Parse(match_str
);
215 if (parse_result
!= URLPattern::PARSE_SUCCESS
) {
216 *error
= ErrorUtils::FormatErrorMessageUTF16(
217 errors::kInvalidExcludeMatch
,
218 base::IntToString(definition_index
), base::IntToString(j
),
219 URLPattern::GetParseResultString(parse_result
));
223 result
->add_exclude_url_pattern(pattern
);
227 // include/exclude globs (mostly for Greasemonkey compatibility)
228 if (!LoadGlobsHelper(content_script
, definition_index
, keys::kIncludeGlobs
,
229 error
, &UserScript::add_glob
, result
)) {
233 if (!LoadGlobsHelper(content_script
, definition_index
, keys::kExcludeGlobs
,
234 error
, &UserScript::add_exclude_glob
, result
)) {
239 const base::ListValue
* js
= NULL
;
240 if (content_script
->HasKey(keys::kJs
) &&
241 !content_script
->GetList(keys::kJs
, &js
)) {
242 *error
= ErrorUtils::FormatErrorMessageUTF16(
243 errors::kInvalidJsList
,
244 base::IntToString(definition_index
));
248 const base::ListValue
* css
= NULL
;
249 if (content_script
->HasKey(keys::kCss
) &&
250 !content_script
->GetList(keys::kCss
, &css
)) {
251 *error
= ErrorUtils::
252 FormatErrorMessageUTF16(errors::kInvalidCssList
,
253 base::IntToString(definition_index
));
257 // The manifest needs to have at least one js or css user script definition.
258 if (((js
? js
->GetSize() : 0) + (css
? css
->GetSize() : 0)) == 0) {
259 *error
= ErrorUtils::FormatErrorMessageUTF16(
260 errors::kMissingFile
,
261 base::IntToString(definition_index
));
266 for (size_t script_index
= 0; script_index
< js
->GetSize();
268 const base::Value
* value
;
269 std::string relative
;
270 if (!js
->Get(script_index
, &value
) || !value
->GetAsString(&relative
)) {
271 *error
= ErrorUtils::FormatErrorMessageUTF16(
273 base::IntToString(definition_index
),
274 base::IntToString(script_index
));
277 GURL url
= extension
->GetResourceURL(relative
);
278 ExtensionResource resource
= extension
->GetResource(relative
);
279 result
->js_scripts().push_back(UserScript::File(
280 resource
.extension_root(), resource
.relative_path(), url
));
285 for (size_t script_index
= 0; script_index
< css
->GetSize();
287 const base::Value
* value
;
288 std::string relative
;
289 if (!css
->Get(script_index
, &value
) || !value
->GetAsString(&relative
)) {
290 *error
= ErrorUtils::FormatErrorMessageUTF16(
292 base::IntToString(definition_index
),
293 base::IntToString(script_index
));
296 GURL url
= extension
->GetResourceURL(relative
);
297 ExtensionResource resource
= extension
->GetResource(relative
);
298 result
->css_scripts().push_back(UserScript::File(
299 resource
.extension_root(), resource
.relative_path(), url
));
306 // Returns false and sets the error if script file can't be loaded,
307 // or if it's not UTF-8 encoded.
308 static bool IsScriptValid(const base::FilePath
& path
,
309 const base::FilePath
& relative_path
,
311 std::string
* error
) {
313 if (!base::PathExists(path
) ||
314 !base::ReadFileToString(path
, &content
)) {
315 *error
= l10n_util::GetStringFUTF8(
317 relative_path
.LossyDisplayName());
321 if (!base::IsStringUTF8(content
)) {
322 *error
= l10n_util::GetStringFUTF8(
323 IDS_EXTENSION_BAD_FILE_ENCODING
,
324 relative_path
.LossyDisplayName());
331 struct EmptyUserScriptList
{
332 UserScriptList user_script_list
;
335 static base::LazyInstance
<EmptyUserScriptList
> g_empty_script_list
=
336 LAZY_INSTANCE_INITIALIZER
;
340 ContentScriptsInfo::ContentScriptsInfo() {
343 ContentScriptsInfo::~ContentScriptsInfo() {
347 const UserScriptList
& ContentScriptsInfo::GetContentScripts(
348 const Extension
* extension
) {
349 ContentScriptsInfo
* info
= static_cast<ContentScriptsInfo
*>(
350 extension
->GetManifestData(keys::kContentScripts
));
351 return info
? info
->content_scripts
352 : g_empty_script_list
.Get().user_script_list
;
356 bool ContentScriptsInfo::ExtensionHasScriptAtURL(const Extension
* extension
,
358 const UserScriptList
& content_scripts
= GetContentScripts(extension
);
359 for (UserScriptList::const_iterator iter
= content_scripts
.begin();
360 iter
!= content_scripts
.end(); ++iter
) {
361 if (iter
->MatchesURL(url
))
368 URLPatternSet
ContentScriptsInfo::GetScriptableHosts(
369 const Extension
* extension
) {
370 const UserScriptList
& content_scripts
= GetContentScripts(extension
);
371 URLPatternSet scriptable_hosts
;
372 for (UserScriptList::const_iterator content_script
=
373 content_scripts
.begin();
374 content_script
!= content_scripts
.end();
376 URLPatternSet::const_iterator pattern
=
377 content_script
->url_patterns().begin();
378 for (; pattern
!= content_script
->url_patterns().end(); ++pattern
)
379 scriptable_hosts
.AddPattern(*pattern
);
381 return scriptable_hosts
;
384 ContentScriptsHandler::ContentScriptsHandler() {
387 ContentScriptsHandler::~ContentScriptsHandler() {
390 const std::vector
<std::string
> ContentScriptsHandler::Keys() const {
391 static const char* const keys
[] = {
392 keys::kContentScripts
394 return std::vector
<std::string
>(keys
, keys
+ arraysize(keys
));
397 bool ContentScriptsHandler::Parse(Extension
* extension
, base::string16
* error
) {
398 scoped_ptr
<ContentScriptsInfo
> content_scripts_info(new ContentScriptsInfo
);
399 const base::ListValue
* scripts_list
= NULL
;
400 if (!extension
->manifest()->GetList(keys::kContentScripts
, &scripts_list
)) {
401 *error
= base::ASCIIToUTF16(errors::kInvalidContentScriptsList
);
405 for (size_t i
= 0; i
< scripts_list
->GetSize(); ++i
) {
406 const base::DictionaryValue
* script_dict
= NULL
;
407 if (!scripts_list
->GetDictionary(i
, &script_dict
)) {
408 *error
= ErrorUtils::FormatErrorMessageUTF16(
409 errors::kInvalidContentScript
,
410 base::IntToString(i
));
414 UserScript user_script
;
415 if (!LoadUserScriptFromDictionary(script_dict
,
420 return false; // Failed to parse script context definition.
423 user_script
.set_host_id(HostID(HostID::EXTENSIONS
, extension
->id()));
424 if (extension
->converted_from_user_script()) {
425 user_script
.set_emulate_greasemonkey(true);
426 // Greasemonkey matches all frames.
427 user_script
.set_match_all_frames(true);
429 user_script
.set_id(UserScript::GenerateUserScriptID());
430 content_scripts_info
->content_scripts
.push_back(user_script
);
432 extension
->SetManifestData(keys::kContentScripts
,
433 content_scripts_info
.release());
434 PermissionsParser::SetScriptableHosts(
435 extension
, ContentScriptsInfo::GetScriptableHosts(extension
));
439 bool ContentScriptsHandler::Validate(
440 const Extension
* extension
,
442 std::vector
<InstallWarning
>* warnings
) const {
443 // Validate that claimed script resources actually exist,
444 // and are UTF-8 encoded.
445 ExtensionResource::SymlinkPolicy symlink_policy
;
446 if ((extension
->creation_flags() &
447 Extension::FOLLOW_SYMLINKS_ANYWHERE
) != 0) {
448 symlink_policy
= ExtensionResource::FOLLOW_SYMLINKS_ANYWHERE
;
450 symlink_policy
= ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT
;
453 const UserScriptList
& content_scripts
=
454 ContentScriptsInfo::GetContentScripts(extension
);
455 for (size_t i
= 0; i
< content_scripts
.size(); ++i
) {
456 const UserScript
& script
= content_scripts
[i
];
458 for (size_t j
= 0; j
< script
.js_scripts().size(); j
++) {
459 const UserScript::File
& js_script
= script
.js_scripts()[j
];
460 const base::FilePath
& path
= ExtensionResource::GetFilePath(
461 js_script
.extension_root(), js_script
.relative_path(),
463 if (!IsScriptValid(path
, js_script
.relative_path(),
464 IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED
, error
))
468 for (size_t j
= 0; j
< script
.css_scripts().size(); j
++) {
469 const UserScript::File
& css_script
= script
.css_scripts()[j
];
470 const base::FilePath
& path
= ExtensionResource::GetFilePath(
471 css_script
.extension_root(), css_script
.relative_path(),
473 if (!IsScriptValid(path
, css_script
.relative_path(),
474 IDS_EXTENSION_LOAD_CSS_FAILED
, error
))
482 } // namespace extensions