content_scripts_handler.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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 "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"
24#include "url/gurl.h"
25
26namespace extensions {
27
28namespace keys = extensions::manifest_keys;
29namespace values = manifest_values;
30namespace errors = manifest_errors;
31
32namespace {
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.
36bool 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
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.
73bool LoadUserScriptFromDictionary(const base::DictionaryValue* content_script,
74                                  int definition_index,
75                                  Extension* extension,
76                                  base::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.
294static 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
317struct EmptyUserScriptList {
318  UserScriptList user_script_list;
319};
320
321static base::LazyInstance<EmptyUserScriptList> g_empty_script_list =
322    LAZY_INSTANCE_INITIALIZER;
323
324}  // namespace
325
326ContentScriptsInfo::ContentScriptsInfo() {
327}
328
329ContentScriptsInfo::~ContentScriptsInfo() {
330}
331
332// static
333const 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
342bool 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
354URLPatternSet 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
370ContentScriptsHandler::ContentScriptsHandler() {
371}
372
373ContentScriptsHandler::~ContentScriptsHandler() {
374}
375
376const 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
383bool 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 = 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
425bool 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
469