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