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/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/permissions/permissions_data.h"
20 #include "extensions/common/url_pattern.h"
21 #include "extensions/common/url_pattern_set.h"
22 #include "grit/generated_resources.h"
23 #include "ui/base/l10n/l10n_util.h"
26 namespace extensions
{
28 namespace keys
= extensions::manifest_keys
;
29 namespace values
= manifest_values
;
30 namespace errors
= manifest_errors
;
34 // Helper method that loads either the include_globs or exclude_globs list
35 // from an entry in the content_script lists of the manifest.
36 bool LoadGlobsHelper(const base::DictionaryValue
* content_script
,
37 int content_script_index
,
38 const char* globs_property_name
,
39 base::string16
* error
,
40 void(UserScript::*add_method
)(const std::string
& glob
),
41 UserScript
* instance
) {
42 if (!content_script
->HasKey(globs_property_name
))
43 return true; // they are optional
45 const base::ListValue
* list
= NULL
;
46 if (!content_script
->GetList(globs_property_name
, &list
)) {
47 *error
= ErrorUtils::FormatErrorMessageUTF16(
48 errors::kInvalidGlobList
,
49 base::IntToString(content_script_index
),
54 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
56 if (!list
->GetString(i
, &glob
)) {
57 *error
= ErrorUtils::FormatErrorMessageUTF16(
59 base::IntToString(content_script_index
),
61 base::IntToString(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
);
113 // matches (required)
114 const base::ListValue
* matches
= NULL
;
115 if (!content_script
->GetList(keys::kMatches
, &matches
)) {
116 *error
= ErrorUtils::FormatErrorMessageUTF16(
117 errors::kInvalidMatches
,
118 base::IntToString(definition_index
));
122 if (matches
->GetSize() == 0) {
123 *error
= ErrorUtils::FormatErrorMessageUTF16(
124 errors::kInvalidMatchCount
,
125 base::IntToString(definition_index
));
128 for (size_t j
= 0; j
< matches
->GetSize(); ++j
) {
129 std::string match_str
;
130 if (!matches
->GetString(j
, &match_str
)) {
131 *error
= ErrorUtils::FormatErrorMessageUTF16(
132 errors::kInvalidMatch
,
133 base::IntToString(definition_index
),
134 base::IntToString(j
),
135 errors::kExpectString
);
139 URLPattern
pattern(UserScript::ValidUserScriptSchemes(
140 PermissionsData::CanExecuteScriptEverywhere(extension
)));
142 URLPattern::ParseResult parse_result
= pattern
.Parse(match_str
);
143 if (parse_result
!= URLPattern::PARSE_SUCCESS
) {
144 *error
= ErrorUtils::FormatErrorMessageUTF16(
145 errors::kInvalidMatch
,
146 base::IntToString(definition_index
),
147 base::IntToString(j
),
148 URLPattern::GetParseResultString(parse_result
));
152 // TODO(aboxhall): check for webstore
153 if (!PermissionsData::CanExecuteScriptEverywhere(extension
) &&
154 pattern
.scheme() != content::kChromeUIScheme
) {
155 // Exclude SCHEME_CHROMEUI unless it's been explicitly requested.
156 // If the --extensions-on-chrome-urls flag has not been passed, requesting
157 // a chrome:// url will cause a parse failure above, so there's no need to
158 // check the flag here.
159 pattern
.SetValidSchemes(
160 pattern
.valid_schemes() & ~URLPattern::SCHEME_CHROMEUI
);
163 if (pattern
.MatchesScheme(content::kFileScheme
) &&
164 !PermissionsData::CanExecuteScriptEverywhere(extension
)) {
165 extension
->set_wants_file_access(true);
166 if (!(extension
->creation_flags() & Extension::ALLOW_FILE_ACCESS
)) {
167 pattern
.SetValidSchemes(
168 pattern
.valid_schemes() & ~URLPattern::SCHEME_FILE
);
172 result
->add_url_pattern(pattern
);
176 if (content_script
->HasKey(keys::kExcludeMatches
)) { // optional
177 const base::ListValue
* exclude_matches
= NULL
;
178 if (!content_script
->GetList(keys::kExcludeMatches
, &exclude_matches
)) {
179 *error
= ErrorUtils::FormatErrorMessageUTF16(
180 errors::kInvalidExcludeMatches
,
181 base::IntToString(definition_index
));
185 for (size_t j
= 0; j
< exclude_matches
->GetSize(); ++j
) {
186 std::string match_str
;
187 if (!exclude_matches
->GetString(j
, &match_str
)) {
188 *error
= ErrorUtils::FormatErrorMessageUTF16(
189 errors::kInvalidExcludeMatch
,
190 base::IntToString(definition_index
),
191 base::IntToString(j
),
192 errors::kExpectString
);
196 int valid_schemes
= UserScript::ValidUserScriptSchemes(
197 PermissionsData::CanExecuteScriptEverywhere(extension
));
198 URLPattern
pattern(valid_schemes
);
200 URLPattern::ParseResult parse_result
= pattern
.Parse(match_str
);
201 if (parse_result
!= URLPattern::PARSE_SUCCESS
) {
202 *error
= ErrorUtils::FormatErrorMessageUTF16(
203 errors::kInvalidExcludeMatch
,
204 base::IntToString(definition_index
), base::IntToString(j
),
205 URLPattern::GetParseResultString(parse_result
));
209 result
->add_exclude_url_pattern(pattern
);
213 // include/exclude globs (mostly for Greasemonkey compatibility)
214 if (!LoadGlobsHelper(content_script
, definition_index
, keys::kIncludeGlobs
,
215 error
, &UserScript::add_glob
, result
)) {
219 if (!LoadGlobsHelper(content_script
, definition_index
, keys::kExcludeGlobs
,
220 error
, &UserScript::add_exclude_glob
, result
)) {
225 const base::ListValue
* js
= NULL
;
226 if (content_script
->HasKey(keys::kJs
) &&
227 !content_script
->GetList(keys::kJs
, &js
)) {
228 *error
= ErrorUtils::FormatErrorMessageUTF16(
229 errors::kInvalidJsList
,
230 base::IntToString(definition_index
));
234 const base::ListValue
* css
= NULL
;
235 if (content_script
->HasKey(keys::kCss
) &&
236 !content_script
->GetList(keys::kCss
, &css
)) {
237 *error
= ErrorUtils::
238 FormatErrorMessageUTF16(errors::kInvalidCssList
,
239 base::IntToString(definition_index
));
243 // The manifest needs to have at least one js or css user script definition.
244 if (((js
? js
->GetSize() : 0) + (css
? css
->GetSize() : 0)) == 0) {
245 *error
= ErrorUtils::FormatErrorMessageUTF16(
246 errors::kMissingFile
,
247 base::IntToString(definition_index
));
252 for (size_t script_index
= 0; script_index
< js
->GetSize();
254 const base::Value
* value
;
255 std::string relative
;
256 if (!js
->Get(script_index
, &value
) || !value
->GetAsString(&relative
)) {
257 *error
= ErrorUtils::FormatErrorMessageUTF16(
259 base::IntToString(definition_index
),
260 base::IntToString(script_index
));
263 GURL url
= extension
->GetResourceURL(relative
);
264 ExtensionResource resource
= extension
->GetResource(relative
);
265 result
->js_scripts().push_back(UserScript::File(
266 resource
.extension_root(), resource
.relative_path(), url
));
271 for (size_t script_index
= 0; script_index
< css
->GetSize();
273 const base::Value
* value
;
274 std::string relative
;
275 if (!css
->Get(script_index
, &value
) || !value
->GetAsString(&relative
)) {
276 *error
= ErrorUtils::FormatErrorMessageUTF16(
278 base::IntToString(definition_index
),
279 base::IntToString(script_index
));
282 GURL url
= extension
->GetResourceURL(relative
);
283 ExtensionResource resource
= extension
->GetResource(relative
);
284 result
->css_scripts().push_back(UserScript::File(
285 resource
.extension_root(), resource
.relative_path(), url
));
292 // Returns false and sets the error if script file can't be loaded,
293 // or if it's not UTF-8 encoded.
294 static bool IsScriptValid(const base::FilePath
& path
,
295 const base::FilePath
& relative_path
,
297 std::string
* error
) {
299 if (!base::PathExists(path
) ||
300 !base::ReadFileToString(path
, &content
)) {
301 *error
= l10n_util::GetStringFUTF8(
303 relative_path
.LossyDisplayName());
307 if (!IsStringUTF8(content
)) {
308 *error
= l10n_util::GetStringFUTF8(
309 IDS_EXTENSION_BAD_FILE_ENCODING
,
310 relative_path
.LossyDisplayName());
317 struct EmptyUserScriptList
{
318 UserScriptList user_script_list
;
321 static base::LazyInstance
<EmptyUserScriptList
> g_empty_script_list
=
322 LAZY_INSTANCE_INITIALIZER
;
326 ContentScriptsInfo::ContentScriptsInfo() {
329 ContentScriptsInfo::~ContentScriptsInfo() {
333 const UserScriptList
& ContentScriptsInfo::GetContentScripts(
334 const Extension
* extension
) {
335 ContentScriptsInfo
* info
= static_cast<ContentScriptsInfo
*>(
336 extension
->GetManifestData(keys::kContentScripts
));
337 return info
? info
->content_scripts
338 : g_empty_script_list
.Get().user_script_list
;
342 bool ContentScriptsInfo::ExtensionHasScriptAtURL(const Extension
* extension
,
344 const UserScriptList
& content_scripts
= GetContentScripts(extension
);
345 for (UserScriptList::const_iterator iter
= content_scripts
.begin();
346 iter
!= content_scripts
.end(); ++iter
) {
347 if (iter
->MatchesURL(url
))
354 URLPatternSet
ContentScriptsInfo::GetScriptableHosts(
355 const Extension
* extension
) {
356 const UserScriptList
& content_scripts
= GetContentScripts(extension
);
357 URLPatternSet scriptable_hosts
;
358 for (UserScriptList::const_iterator content_script
=
359 content_scripts
.begin();
360 content_script
!= content_scripts
.end();
362 URLPatternSet::const_iterator pattern
=
363 content_script
->url_patterns().begin();
364 for (; pattern
!= content_script
->url_patterns().end(); ++pattern
)
365 scriptable_hosts
.AddPattern(*pattern
);
367 return scriptable_hosts
;
370 ContentScriptsHandler::ContentScriptsHandler() {
373 ContentScriptsHandler::~ContentScriptsHandler() {
376 const std::vector
<std::string
> ContentScriptsHandler::Keys() const {
377 static const char* keys
[] = {
378 keys::kContentScripts
380 return std::vector
<std::string
>(keys
, keys
+ arraysize(keys
));
383 bool ContentScriptsHandler::Parse(Extension
* extension
, base::string16
* error
) {
384 scoped_ptr
<ContentScriptsInfo
> content_scripts_info(new ContentScriptsInfo
);
385 const base::ListValue
* scripts_list
= NULL
;
386 if (!extension
->manifest()->GetList(keys::kContentScripts
, &scripts_list
)) {
387 *error
= base::ASCIIToUTF16(errors::kInvalidContentScriptsList
);
391 for (size_t i
= 0; i
< scripts_list
->GetSize(); ++i
) {
392 const base::DictionaryValue
* script_dict
= NULL
;
393 if (!scripts_list
->GetDictionary(i
, &script_dict
)) {
394 *error
= ErrorUtils::FormatErrorMessageUTF16(
395 errors::kInvalidContentScript
,
396 base::IntToString(i
));
400 UserScript user_script
;
401 if (!LoadUserScriptFromDictionary(script_dict
,
406 return false; // Failed to parse script context definition.
409 user_script
.set_extension_id(extension
->id());
410 if (extension
->converted_from_user_script()) {
411 user_script
.set_emulate_greasemonkey(true);
412 // Greasemonkey matches all frames.
413 user_script
.set_match_all_frames(true);
415 content_scripts_info
->content_scripts
.push_back(user_script
);
417 extension
->SetManifestData(keys::kContentScripts
,
418 content_scripts_info
.release());
419 PermissionsData::SetInitialScriptableHosts(
421 ContentScriptsInfo::GetScriptableHosts(extension
));
425 bool ContentScriptsHandler::Validate(
426 const Extension
* extension
,
428 std::vector
<InstallWarning
>* warnings
) const {
429 // Validate that claimed script resources actually exist,
430 // and are UTF-8 encoded.
431 ExtensionResource::SymlinkPolicy symlink_policy
;
432 if ((extension
->creation_flags() &
433 Extension::FOLLOW_SYMLINKS_ANYWHERE
) != 0) {
434 symlink_policy
= ExtensionResource::FOLLOW_SYMLINKS_ANYWHERE
;
436 symlink_policy
= ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT
;
439 const UserScriptList
& content_scripts
=
440 ContentScriptsInfo::GetContentScripts(extension
);
441 for (size_t i
= 0; i
< content_scripts
.size(); ++i
) {
442 const UserScript
& script
= content_scripts
[i
];
444 for (size_t j
= 0; j
< script
.js_scripts().size(); j
++) {
445 const UserScript::File
& js_script
= script
.js_scripts()[j
];
446 const base::FilePath
& path
= ExtensionResource::GetFilePath(
447 js_script
.extension_root(), js_script
.relative_path(),
449 if (!IsScriptValid(path
, js_script
.relative_path(),
450 IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED
, error
))
454 for (size_t j
= 0; j
< script
.css_scripts().size(); j
++) {
455 const UserScript::File
& css_script
= script
.css_scripts()[j
];
456 const base::FilePath
& path
= ExtensionResource::GetFilePath(
457 css_script
.extension_root(), css_script
.relative_path(),
459 if (!IsScriptValid(path
, css_script
.relative_path(),
460 IDS_EXTENSION_LOAD_CSS_FAILED
, error
))
468 } // namespace extensions