- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / manifest_handlers / content_scripts_handler.cc
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 "chrome/common/extensions/extension.h"
15 #include "chrome/common/extensions/permissions/permissions_data.h"
16 #include "content/public/common/url_constants.h"
17 #include "extensions/common/error_utils.h"
18 #include "extensions/common/extension_resource.h"
19 #include "extensions/common/manifest_constants.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"
24 #include "url/gurl.h"
25
26 namespace extensions {
27
28 namespace keys = extensions::manifest_keys;
29 namespace values = manifest_values;
30 namespace errors = manifest_errors;
31
32 namespace {
33
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                      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
44
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),
50         globs_property_name);
51     return false;
52   }
53
54   for (size_t i = 0; i < list->GetSize(); ++i) {
55     std::string glob;
56     if (!list->GetString(i, &glob)) {
57       *error = ErrorUtils::FormatErrorMessageUTF16(
58           errors::kInvalidGlob,
59           base::IntToString(content_script_index),
60           globs_property_name,
61           base::IntToString(i));
62       return false;
63     }
64
65     (instance->*add_method)(glob);
66   }
67
68   return true;
69 }
70
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,
74                                   int definition_index,
75                                   Extension* extension,
76                                   string16* error,
77                                   UserScript* result) {
78   // run_at
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));
85       return false;
86     }
87
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);
94     } else {
95       *error = ErrorUtils::FormatErrorMessageUTF16(
96           errors::kInvalidRunAt,
97           base::IntToString(definition_index));
98       return false;
99     }
100   }
101
102   // all frames
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));
108       return false;
109     }
110     result->set_match_all_frames(all_frames);
111   }
112
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));
119     return false;
120   }
121
122   if (matches->GetSize() == 0) {
123     *error = ErrorUtils::FormatErrorMessageUTF16(
124         errors::kInvalidMatchCount,
125         base::IntToString(definition_index));
126     return false;
127   }
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);
136       return false;
137     }
138
139     URLPattern pattern(UserScript::ValidUserScriptSchemes(
140         PermissionsData::CanExecuteScriptEverywhere(extension)));
141
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));
149       return false;
150     }
151
152     // TODO(aboxhall): check for webstore
153     if (!PermissionsData::CanExecuteScriptEverywhere(extension) &&
154         pattern.scheme() != chrome::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);
161     }
162
163     if (pattern.MatchesScheme(chrome::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);
169       }
170     }
171
172     result->add_url_pattern(pattern);
173   }
174
175   // exclude_matches
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));
182       return false;
183     }
184
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);
193         return false;
194       }
195
196       int valid_schemes = UserScript::ValidUserScriptSchemes(
197           PermissionsData::CanExecuteScriptEverywhere(extension));
198       URLPattern pattern(valid_schemes);
199
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));
206         return false;
207       }
208
209       result->add_exclude_url_pattern(pattern);
210     }
211   }
212
213   // include/exclude globs (mostly for Greasemonkey compatibility)
214   if (!LoadGlobsHelper(content_script, definition_index, keys::kIncludeGlobs,
215                        error, &UserScript::add_glob, result)) {
216       return false;
217   }
218
219   if (!LoadGlobsHelper(content_script, definition_index, keys::kExcludeGlobs,
220                        error, &UserScript::add_exclude_glob, result)) {
221       return false;
222   }
223
224   // js and css keys
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));
231     return false;
232   }
233
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));
240     return false;
241   }
242
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));
248     return false;
249   }
250
251   if (js) {
252     for (size_t script_index = 0; script_index < js->GetSize();
253          ++script_index) {
254       const Value* value;
255       std::string relative;
256       if (!js->Get(script_index, &value) || !value->GetAsString(&relative)) {
257         *error = ErrorUtils::FormatErrorMessageUTF16(
258             errors::kInvalidJs,
259             base::IntToString(definition_index),
260             base::IntToString(script_index));
261         return false;
262       }
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));
267     }
268   }
269
270   if (css) {
271     for (size_t script_index = 0; script_index < css->GetSize();
272          ++script_index) {
273       const Value* value;
274       std::string relative;
275       if (!css->Get(script_index, &value) || !value->GetAsString(&relative)) {
276         *error = ErrorUtils::FormatErrorMessageUTF16(
277             errors::kInvalidCss,
278             base::IntToString(definition_index),
279             base::IntToString(script_index));
280         return false;
281       }
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));
286     }
287   }
288
289   return true;
290 }
291
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,
296                           int message_id,
297                           std::string* error) {
298   std::string content;
299   if (!base::PathExists(path) ||
300       !base::ReadFileToString(path, &content)) {
301     *error = l10n_util::GetStringFUTF8(
302         message_id,
303         relative_path.LossyDisplayName());
304     return false;
305   }
306
307   if (!IsStringUTF8(content)) {
308     *error = l10n_util::GetStringFUTF8(
309         IDS_EXTENSION_BAD_FILE_ENCODING,
310         relative_path.LossyDisplayName());
311     return false;
312   }
313
314   return true;
315 }
316
317 struct EmptyUserScriptList {
318   UserScriptList user_script_list;
319 };
320
321 static base::LazyInstance<EmptyUserScriptList> g_empty_script_list =
322     LAZY_INSTANCE_INITIALIZER;
323
324 }  // namespace
325
326 ContentScriptsInfo::ContentScriptsInfo() {
327 }
328
329 ContentScriptsInfo::~ContentScriptsInfo() {
330 }
331
332 // static
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;
339 }
340
341 // static
342 bool ContentScriptsInfo::ExtensionHasScriptAtURL(const Extension* extension,
343                                                  const GURL& url) {
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))
348       return true;
349   }
350   return false;
351 }
352
353 // static
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();
361        ++content_script) {
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);
366   }
367   return scriptable_hosts;
368 }
369
370 ContentScriptsHandler::ContentScriptsHandler() {
371 }
372
373 ContentScriptsHandler::~ContentScriptsHandler() {
374 }
375
376 const std::vector<std::string> ContentScriptsHandler::Keys() const {
377   static const char* keys[] = {
378     keys::kContentScripts
379   };
380   return std::vector<std::string>(keys, keys + arraysize(keys));
381 }
382
383 bool ContentScriptsHandler::Parse(Extension* extension, 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 = ASCIIToUTF16(errors::kInvalidContentScriptsList);
388     return false;
389   }
390
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));
397       return false;
398     }
399
400     UserScript user_script;
401     if (!LoadUserScriptFromDictionary(script_dict,
402                                       i,
403                                       extension,
404                                       error,
405                                       &user_script)) {
406       return false;  // Failed to parse script context definition.
407     }
408
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);
414     }
415     content_scripts_info->content_scripts.push_back(user_script);
416   }
417   extension->SetManifestData(keys::kContentScripts,
418                              content_scripts_info.release());
419   PermissionsData::SetInitialScriptableHosts(
420       extension,
421       ContentScriptsInfo::GetScriptableHosts(extension));
422   return true;
423 }
424
425 bool ContentScriptsHandler::Validate(
426     const Extension* extension,
427     std::string* error,
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;
435   } else {
436     symlink_policy = ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT;
437   }
438
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];
443
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(),
448           symlink_policy);
449       if (!IsScriptValid(path, js_script.relative_path(),
450                          IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED, error))
451         return false;
452     }
453
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(),
458           symlink_policy);
459       if (!IsScriptValid(path, css_script.relative_path(),
460                          IDS_EXTENSION_LOAD_CSS_FAILED, error))
461         return false;
462     }
463   }
464
465   return true;
466 }
467
468 }  // namespace extensions