content_scripts_handler.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/string_number_conversions.h"
11#include "base/string_util.h"
12#include "base/utf_string_conversions.h"
13#include "base/values.h"
14#include "chrome/common/extensions/extension.h"
15#include "chrome/common/extensions/extension_manifest_constants.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/url_pattern.h"
20#include "googleurl/src/gurl.h"
21#include "grit/generated_resources.h"
22#include "ui/base/l10n/l10n_util.h"
23
24namespace extensions {
25
26namespace keys = extension_manifest_keys;
27namespace values = extension_manifest_values;
28namespace errors = extension_manifest_errors;
29
30namespace {
31
32// Helper method that loads either the include_globs or exclude_globs list
33// from an entry in the content_script lists of the manifest.
34bool LoadGlobsHelper(const DictionaryValue* content_script,
35                     int content_script_index,
36                     const char* globs_property_name,
37                     string16* error,
38                     void(UserScript::*add_method)(const std::string& glob),
39                     UserScript* instance) {
40  if (!content_script->HasKey(globs_property_name))
41    return true;  // they are optional
42
43  const ListValue* list = NULL;
44  if (!content_script->GetList(globs_property_name, &list)) {
45    *error = ErrorUtils::FormatErrorMessageUTF16(
46        errors::kInvalidGlobList,
47        base::IntToString(content_script_index),
48        globs_property_name);
49    return false;
50  }
51
52  for (size_t i = 0; i < list->GetSize(); ++i) {
53    std::string glob;
54    if (!list->GetString(i, &glob)) {
55      *error = ErrorUtils::FormatErrorMessageUTF16(
56          errors::kInvalidGlob,
57          base::IntToString(content_script_index),
58          globs_property_name,
59          base::IntToString(i));
60      return false;
61    }
62
63    (instance->*add_method)(glob);
64  }
65
66  return true;
67}
68
69// Helper method that loads a UserScript object from a dictionary in the
70// content_script list of the manifest.
71bool LoadUserScriptFromDictionary(const DictionaryValue* content_script,
72                                  int definition_index,
73                                  Extension* extension,
74                                  string16* error,
75                                  UserScript* result) {
76  // run_at
77  if (content_script->HasKey(keys::kRunAt)) {
78    std::string run_location;
79    if (!content_script->GetString(keys::kRunAt, &run_location)) {
80      *error = ErrorUtils::FormatErrorMessageUTF16(
81          errors::kInvalidRunAt,
82          base::IntToString(definition_index));
83      return false;
84    }
85
86    if (run_location == values::kRunAtDocumentStart) {
87      result->set_run_location(UserScript::DOCUMENT_START);
88    } else if (run_location == values::kRunAtDocumentEnd) {
89      result->set_run_location(UserScript::DOCUMENT_END);
90    } else if (run_location == values::kRunAtDocumentIdle) {
91      result->set_run_location(UserScript::DOCUMENT_IDLE);
92    } else {
93      *error = ErrorUtils::FormatErrorMessageUTF16(
94          errors::kInvalidRunAt,
95          base::IntToString(definition_index));
96      return false;
97    }
98  }
99
100  // all frames
101  if (content_script->HasKey(keys::kAllFrames)) {
102    bool all_frames = false;
103    if (!content_script->GetBoolean(keys::kAllFrames, &all_frames)) {
104      *error = ErrorUtils::FormatErrorMessageUTF16(
105            errors::kInvalidAllFrames, base::IntToString(definition_index));
106      return false;
107    }
108    result->set_match_all_frames(all_frames);
109  }
110
111  // matches (required)
112  const ListValue* matches = NULL;
113  if (!content_script->GetList(keys::kMatches, &matches)) {
114    *error = ErrorUtils::FormatErrorMessageUTF16(
115        errors::kInvalidMatches,
116        base::IntToString(definition_index));
117    return false;
118  }
119
120  if (matches->GetSize() == 0) {
121    *error = ErrorUtils::FormatErrorMessageUTF16(
122        errors::kInvalidMatchCount,
123        base::IntToString(definition_index));
124    return false;
125  }
126  for (size_t j = 0; j < matches->GetSize(); ++j) {
127    std::string match_str;
128    if (!matches->GetString(j, &match_str)) {
129      *error = ErrorUtils::FormatErrorMessageUTF16(
130          errors::kInvalidMatch,
131          base::IntToString(definition_index),
132          base::IntToString(j),
133          errors::kExpectString);
134      return false;
135    }
136
137    URLPattern pattern(UserScript::ValidUserScriptSchemes(
138        extension->CanExecuteScriptEverywhere()));
139
140    URLPattern::ParseResult parse_result = pattern.Parse(match_str);
141    if (parse_result != URLPattern::PARSE_SUCCESS) {
142      *error = ErrorUtils::FormatErrorMessageUTF16(
143          errors::kInvalidMatch,
144          base::IntToString(definition_index),
145          base::IntToString(j),
146          URLPattern::GetParseResultString(parse_result));
147      return false;
148    }
149
150    // TODO(aboxhall): check for webstore
151    if (!extension->CanExecuteScriptEverywhere() &&
152        pattern.scheme() != chrome::kChromeUIScheme) {
153      // Exclude SCHEME_CHROMEUI unless it's been explicitly requested.
154      // If the --extensions-on-chrome-urls flag has not been passed, requesting
155      // a chrome:// url will cause a parse failure above, so there's no need to
156      // check the flag here.
157      pattern.SetValidSchemes(
158          pattern.valid_schemes() & ~URLPattern::SCHEME_CHROMEUI);
159    }
160
161    if (pattern.MatchesScheme(chrome::kFileScheme) &&
162        !extension->CanExecuteScriptEverywhere()) {
163      extension->set_wants_file_access(true);
164      if (!(extension->creation_flags() & Extension::ALLOW_FILE_ACCESS)) {
165        pattern.SetValidSchemes(
166            pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
167      }
168    }
169
170    result->add_url_pattern(pattern);
171  }
172
173  // exclude_matches
174  if (content_script->HasKey(keys::kExcludeMatches)) {  // optional
175    const ListValue* exclude_matches = NULL;
176    if (!content_script->GetList(keys::kExcludeMatches, &exclude_matches)) {
177      *error = ErrorUtils::FormatErrorMessageUTF16(
178          errors::kInvalidExcludeMatches,
179          base::IntToString(definition_index));
180      return false;
181    }
182
183    for (size_t j = 0; j < exclude_matches->GetSize(); ++j) {
184      std::string match_str;
185      if (!exclude_matches->GetString(j, &match_str)) {
186        *error = ErrorUtils::FormatErrorMessageUTF16(
187            errors::kInvalidExcludeMatch,
188            base::IntToString(definition_index),
189            base::IntToString(j),
190            errors::kExpectString);
191        return false;
192      }
193
194      int valid_schemes = UserScript::ValidUserScriptSchemes(
195          extension->CanExecuteScriptEverywhere());
196      URLPattern pattern(valid_schemes);
197
198      URLPattern::ParseResult parse_result = pattern.Parse(match_str);
199      if (parse_result != URLPattern::PARSE_SUCCESS) {
200        *error = ErrorUtils::FormatErrorMessageUTF16(
201            errors::kInvalidExcludeMatch,
202            base::IntToString(definition_index), base::IntToString(j),
203            URLPattern::GetParseResultString(parse_result));
204        return false;
205      }
206
207      result->add_exclude_url_pattern(pattern);
208    }
209  }
210
211  // include/exclude globs (mostly for Greasemonkey compatibility)
212  if (!LoadGlobsHelper(content_script, definition_index, keys::kIncludeGlobs,
213                       error, &UserScript::add_glob, result)) {
214      return false;
215  }
216
217  if (!LoadGlobsHelper(content_script, definition_index, keys::kExcludeGlobs,
218                       error, &UserScript::add_exclude_glob, result)) {
219      return false;
220  }
221
222  // js and css keys
223  const ListValue* js = NULL;
224  if (content_script->HasKey(keys::kJs) &&
225      !content_script->GetList(keys::kJs, &js)) {
226    *error = ErrorUtils::FormatErrorMessageUTF16(
227        errors::kInvalidJsList,
228        base::IntToString(definition_index));
229    return false;
230  }
231
232  const ListValue* css = NULL;
233  if (content_script->HasKey(keys::kCss) &&
234      !content_script->GetList(keys::kCss, &css)) {
235    *error = ErrorUtils::
236        FormatErrorMessageUTF16(errors::kInvalidCssList,
237        base::IntToString(definition_index));
238    return false;
239  }
240
241  // The manifest needs to have at least one js or css user script definition.
242  if (((js ? js->GetSize() : 0) + (css ? css->GetSize() : 0)) == 0) {
243    *error = ErrorUtils::FormatErrorMessageUTF16(
244        errors::kMissingFile,
245        base::IntToString(definition_index));
246    return false;
247  }
248
249  if (js) {
250    for (size_t script_index = 0; script_index < js->GetSize();
251         ++script_index) {
252      const Value* value;
253      std::string relative;
254      if (!js->Get(script_index, &value) || !value->GetAsString(&relative)) {
255        *error = ErrorUtils::FormatErrorMessageUTF16(
256            errors::kInvalidJs,
257            base::IntToString(definition_index),
258            base::IntToString(script_index));
259        return false;
260      }
261      GURL url = extension->GetResourceURL(relative);
262      ExtensionResource resource = extension->GetResource(relative);
263      result->js_scripts().push_back(UserScript::File(
264          resource.extension_root(), resource.relative_path(), url));
265    }
266  }
267
268  if (css) {
269    for (size_t script_index = 0; script_index < css->GetSize();
270         ++script_index) {
271      const Value* value;
272      std::string relative;
273      if (!css->Get(script_index, &value) || !value->GetAsString(&relative)) {
274        *error = ErrorUtils::FormatErrorMessageUTF16(
275            errors::kInvalidCss,
276            base::IntToString(definition_index),
277            base::IntToString(script_index));
278        return false;
279      }
280      GURL url = extension->GetResourceURL(relative);
281      ExtensionResource resource = extension->GetResource(relative);
282      result->css_scripts().push_back(UserScript::File(
283          resource.extension_root(), resource.relative_path(), url));
284    }
285  }
286
287  return true;
288}
289
290// Returns false and sets the error if script file can't be loaded,
291// or if it's not UTF-8 encoded.
292static bool IsScriptValid(const base::FilePath& path,
293                          const base::FilePath& relative_path,
294                          int message_id,
295                          std::string* error) {
296  std::string content;
297  if (!file_util::PathExists(path) ||
298      !file_util::ReadFileToString(path, &content)) {
299    *error = l10n_util::GetStringFUTF8(
300        message_id,
301        relative_path.LossyDisplayName());
302    return false;
303  }
304
305  if (!IsStringUTF8(content)) {
306    *error = l10n_util::GetStringFUTF8(
307        IDS_EXTENSION_BAD_FILE_ENCODING,
308        relative_path.LossyDisplayName());
309    return false;
310  }
311
312  return true;
313}
314
315struct EmptyUserScriptList {
316  UserScriptList user_script_list;
317};
318
319static base::LazyInstance<EmptyUserScriptList> g_empty_script_list =
320    LAZY_INSTANCE_INITIALIZER;
321
322}  // namespace
323
324ContentScriptsInfo::ContentScriptsInfo() {
325}
326
327ContentScriptsInfo::~ContentScriptsInfo() {
328}
329
330// static
331const UserScriptList& ContentScriptsInfo::GetContentScripts(
332    const Extension* extension) {
333  ContentScriptsInfo* info = static_cast<ContentScriptsInfo*>(
334      extension->GetManifestData(keys::kContentScripts));
335  return info ? info->content_scripts
336              : g_empty_script_list.Get().user_script_list;
337}
338
339// static
340bool ContentScriptsInfo::ExtensionHasScriptAtURL(const Extension* extension,
341                                                 const GURL& url) {
342  const UserScriptList& content_scripts = GetContentScripts(extension);
343  for (UserScriptList::const_iterator iter = content_scripts.begin();
344      iter != content_scripts.end(); ++iter) {
345    if (iter->MatchesURL(url))
346      return true;
347  }
348  return false;
349}
350
351ContentScriptsHandler::ContentScriptsHandler() {
352}
353
354ContentScriptsHandler::~ContentScriptsHandler() {
355}
356
357const std::vector<std::string> ContentScriptsHandler::Keys() const {
358  static const char* keys[] = {
359    keys::kContentScripts
360  };
361  return std::vector<std::string>(keys, keys + arraysize(keys));
362}
363
364bool ContentScriptsHandler::Parse(Extension* extension, string16* error) {
365  scoped_ptr<ContentScriptsInfo> content_scripts_info(new ContentScriptsInfo);
366  const ListValue* scripts_list = NULL;
367  if (!extension->manifest()->GetList(keys::kContentScripts, &scripts_list)) {
368    *error = ASCIIToUTF16(errors::kInvalidContentScriptsList);
369    return false;
370  }
371
372  for (size_t i = 0; i < scripts_list->GetSize(); ++i) {
373    const DictionaryValue* script_dict = NULL;
374    if (!scripts_list->GetDictionary(i, &script_dict)) {
375      *error = ErrorUtils::FormatErrorMessageUTF16(
376          errors::kInvalidContentScript,
377          base::IntToString(i));
378      return false;
379    }
380
381    UserScript user_script;
382    if (!LoadUserScriptFromDictionary(script_dict,
383                                      i,
384                                      extension,
385                                      error,
386                                      &user_script)) {
387      return false;  // Failed to parse script context definition.
388    }
389
390    user_script.set_extension_id(extension->id());
391    if (extension->converted_from_user_script()) {
392      user_script.set_emulate_greasemonkey(true);
393      // Greasemonkey matches all frames.
394      user_script.set_match_all_frames(true);
395    }
396    content_scripts_info->content_scripts.push_back(user_script);
397  }
398  extension->SetManifestData(keys::kContentScripts,
399                             content_scripts_info.release());
400  return true;
401}
402
403bool ContentScriptsHandler::Validate(
404    const Extension* extension,
405    std::string* error,
406    std::vector<InstallWarning>* warnings) const {
407  // Validate that claimed script resources actually exist,
408  // and are UTF-8 encoded.
409  ExtensionResource::SymlinkPolicy symlink_policy;
410  if ((extension->creation_flags() &
411       Extension::FOLLOW_SYMLINKS_ANYWHERE) != 0) {
412    symlink_policy = ExtensionResource::FOLLOW_SYMLINKS_ANYWHERE;
413  } else {
414    symlink_policy = ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT;
415  }
416
417  const extensions::UserScriptList& content_scripts =
418      extensions::ContentScriptsInfo::GetContentScripts(extension);
419  for (size_t i = 0; i < content_scripts.size(); ++i) {
420    const extensions::UserScript& script = content_scripts[i];
421
422    for (size_t j = 0; j < script.js_scripts().size(); j++) {
423      const extensions::UserScript::File& js_script = script.js_scripts()[j];
424      const base::FilePath& path = ExtensionResource::GetFilePath(
425          js_script.extension_root(), js_script.relative_path(),
426          symlink_policy);
427      if (!IsScriptValid(path, js_script.relative_path(),
428                         IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED, error))
429        return false;
430    }
431
432    for (size_t j = 0; j < script.css_scripts().size(); j++) {
433      const extensions::UserScript::File& css_script = script.css_scripts()[j];
434      const base::FilePath& path = ExtensionResource::GetFilePath(
435          css_script.extension_root(), css_script.relative_path(),
436          symlink_policy);
437      if (!IsScriptValid(path, css_script.relative_path(),
438                         IDS_EXTENSION_LOAD_CSS_FAILED, error))
439        return false;
440    }
441  }
442
443  return true;
444}
445
446}  // namespace extensions
447