1// Copyright 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/chrome_extensions_client.h"
6
7#include "base/command_line.h"
8#include "base/strings/string_util.h"
9#include "chrome/common/chrome_switches.h"
10#include "chrome/common/chrome_version_info.h"
11#include "chrome/common/extensions/chrome_manifest_handlers.h"
12#include "chrome/common/extensions/extension_constants.h"
13#include "chrome/common/extensions/features/chrome_channel_feature_filter.h"
14#include "chrome/common/extensions/features/feature_channel.h"
15#include "chrome/common/url_constants.h"
16#include "chrome/grit/chromium_strings.h"
17#include "chrome/grit/common_resources.h"
18#include "chrome/grit/generated_resources.h"
19#include "content/public/common/url_constants.h"
20#include "extensions/common/common_manifest_handlers.h"
21#include "extensions/common/extension.h"
22#include "extensions/common/extension_api.h"
23#include "extensions/common/extension_urls.h"
24#include "extensions/common/features/api_feature.h"
25#include "extensions/common/features/base_feature_provider.h"
26#include "extensions/common/features/feature_provider.h"
27#include "extensions/common/features/json_feature_provider_source.h"
28#include "extensions/common/features/manifest_feature.h"
29#include "extensions/common/features/permission_feature.h"
30#include "extensions/common/features/simple_feature.h"
31#include "extensions/common/manifest_constants.h"
32#include "extensions/common/manifest_handler.h"
33#include "extensions/common/permissions/api_permission_set.h"
34#include "extensions/common/permissions/permission_message.h"
35#include "extensions/common/permissions/permissions_info.h"
36#include "extensions/common/url_pattern.h"
37#include "extensions/common/url_pattern_set.h"
38#include "extensions/grit/extensions_resources.h"
39#include "ui/base/l10n/l10n_util.h"
40#include "url/gurl.h"
41
42// TODO(thestig): Remove these #defines. This file should not be built when
43// extensions are disabled.
44#if defined(ENABLE_EXTENSIONS)
45#include "chrome/common/extensions/api/generated_schemas.h"
46#include "chrome/grit/extensions_api_resources.h"
47#include "extensions/common/api/generated_schemas.h"
48#endif
49
50namespace extensions {
51
52namespace {
53
54// TODO(battre): Delete the HTTP URL once the blacklist is downloaded via HTTPS.
55const char kExtensionBlocklistUrlPrefix[] =
56    "http://www.gstatic.com/chrome/extensions/blacklist";
57const char kExtensionBlocklistHttpsUrlPrefix[] =
58    "https://www.gstatic.com/chrome/extensions/blacklist";
59
60const char kThumbsWhiteListedExtension[] = "khopmbdjffemhegeeobelklnbglcdgfh";
61
62template <class FeatureClass>
63SimpleFeature* CreateFeature() {
64  SimpleFeature* feature = new FeatureClass;
65  feature->AddFilter(
66      scoped_ptr<SimpleFeatureFilter>(new ChromeChannelFeatureFilter(feature)));
67  return feature;
68}
69
70}  // namespace
71
72static base::LazyInstance<ChromeExtensionsClient> g_client =
73    LAZY_INSTANCE_INITIALIZER;
74
75ChromeExtensionsClient::ChromeExtensionsClient()
76    : chrome_api_permissions_(ChromeAPIPermissions()),
77      extensions_api_permissions_(ExtensionsAPIPermissions()) {
78}
79
80ChromeExtensionsClient::~ChromeExtensionsClient() {
81}
82
83void ChromeExtensionsClient::Initialize() {
84  // Registration could already be finalized in unit tests, where the utility
85  // thread runs in-process.
86  if (!ManifestHandler::IsRegistrationFinalized()) {
87    RegisterCommonManifestHandlers();
88#if defined(ENABLE_EXTENSIONS)
89    RegisterChromeManifestHandlers();
90#endif
91    ManifestHandler::FinalizeRegistration();
92  }
93
94  // Set up permissions.
95  PermissionsInfo::GetInstance()->AddProvider(chrome_api_permissions_);
96  PermissionsInfo::GetInstance()->AddProvider(extensions_api_permissions_);
97
98  // Set up the scripting whitelist.
99  // Whitelist ChromeVox, an accessibility extension from Google that needs
100  // the ability to script webui pages. This is temporary and is not
101  // meant to be a general solution.
102  // TODO(dmazzoni): remove this once we have an extension API that
103  // allows any extension to request read-only access to webui pages.
104  scripting_whitelist_.push_back(extension_misc::kChromeVoxExtensionId);
105
106  // Whitelist "Discover DevTools Companion" extension from Google that
107  // needs the ability to script DevTools pages. Companion will assist
108  // online courses and will be needed while the online educational programs
109  // are in place.
110  scripting_whitelist_.push_back("angkfkebojeancgemegoedelbnjgcgme");
111}
112
113const PermissionMessageProvider&
114ChromeExtensionsClient::GetPermissionMessageProvider() const {
115  return permission_message_provider_;
116}
117
118const std::string ChromeExtensionsClient::GetProductName() {
119  return l10n_util::GetStringUTF8(IDS_PRODUCT_NAME);
120}
121
122scoped_ptr<FeatureProvider> ChromeExtensionsClient::CreateFeatureProvider(
123    const std::string& name) const {
124  scoped_ptr<FeatureProvider> provider;
125  scoped_ptr<JSONFeatureProviderSource> source(
126      CreateFeatureProviderSource(name));
127  if (name == "api") {
128    provider.reset(new BaseFeatureProvider(source->dictionary(),
129                                           CreateFeature<APIFeature>));
130  } else if (name == "manifest") {
131    provider.reset(new BaseFeatureProvider(source->dictionary(),
132                                           CreateFeature<ManifestFeature>));
133  } else if (name == "permission") {
134    provider.reset(new BaseFeatureProvider(source->dictionary(),
135                                           CreateFeature<PermissionFeature>));
136  } else {
137    NOTREACHED();
138  }
139  return provider.Pass();
140}
141
142scoped_ptr<JSONFeatureProviderSource>
143ChromeExtensionsClient::CreateFeatureProviderSource(
144    const std::string& name) const {
145  scoped_ptr<JSONFeatureProviderSource> source(
146      new JSONFeatureProviderSource(name));
147  if (name == "api") {
148    source->LoadJSON(IDR_EXTENSION_API_FEATURES);
149    source->LoadJSON(IDR_CHROME_EXTENSION_API_FEATURES);
150  } else if (name == "manifest") {
151    source->LoadJSON(IDR_EXTENSION_MANIFEST_FEATURES);
152    source->LoadJSON(IDR_CHROME_EXTENSION_MANIFEST_FEATURES);
153  } else if (name == "permission") {
154    source->LoadJSON(IDR_EXTENSION_PERMISSION_FEATURES);
155    source->LoadJSON(IDR_CHROME_EXTENSION_PERMISSION_FEATURES);
156  } else {
157    NOTREACHED();
158    source.reset();
159  }
160  return source.Pass();
161}
162
163void ChromeExtensionsClient::FilterHostPermissions(
164    const URLPatternSet& hosts,
165    URLPatternSet* new_hosts,
166    std::set<PermissionMessage>* messages) const {
167  for (URLPatternSet::const_iterator i = hosts.begin();
168       i != hosts.end(); ++i) {
169    // Filters out every URL pattern that matches chrome:// scheme.
170    if (i->scheme() == content::kChromeUIScheme) {
171      // chrome://favicon is the only URL for chrome:// scheme that we
172      // want to support. We want to deprecate the "chrome" scheme.
173      // We should not add any additional "host" here.
174      if (GURL(chrome::kChromeUIFaviconURL).host() != i->host())
175        continue;
176      messages->insert(PermissionMessage(
177          PermissionMessage::kFavicon,
178          l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FAVICON)));
179    } else {
180      new_hosts->AddPattern(*i);
181    }
182  }
183}
184
185void ChromeExtensionsClient::SetScriptingWhitelist(
186    const ExtensionsClient::ScriptingWhitelist& whitelist) {
187  scripting_whitelist_ = whitelist;
188}
189
190const ExtensionsClient::ScriptingWhitelist&
191ChromeExtensionsClient::GetScriptingWhitelist() const {
192  return scripting_whitelist_;
193}
194
195URLPatternSet ChromeExtensionsClient::GetPermittedChromeSchemeHosts(
196      const Extension* extension,
197      const APIPermissionSet& api_permissions) const {
198  URLPatternSet hosts;
199  // Regular extensions are only allowed access to chrome://favicon.
200  hosts.AddPattern(URLPattern(URLPattern::SCHEME_CHROMEUI,
201                              chrome::kChromeUIFaviconURL));
202
203  // Experimental extensions are also allowed chrome://thumb.
204  //
205  // TODO: A public API should be created for retrieving thumbnails.
206  // See http://crbug.com/222856. A temporary hack is implemented here to
207  // make chrome://thumbs available to NTP Russia extension as
208  // non-experimental.
209  if ((api_permissions.find(APIPermission::kExperimental) !=
210       api_permissions.end()) ||
211      (extension->id() == kThumbsWhiteListedExtension &&
212       extension->from_webstore())) {
213    hosts.AddPattern(URLPattern(URLPattern::SCHEME_CHROMEUI,
214                                chrome::kChromeUIThumbnailURL));
215  }
216  return hosts;
217}
218
219bool ChromeExtensionsClient::IsScriptableURL(
220    const GURL& url, std::string* error) const {
221  // The gallery is special-cased as a restricted URL for scripting to prevent
222  // access to special JS bindings we expose to the gallery (and avoid things
223  // like extensions removing the "report abuse" link).
224  // TODO(erikkay): This seems like the wrong test.  Shouldn't we we testing
225  // against the store app extent?
226  GURL store_url(extension_urls::GetWebstoreLaunchURL());
227  if (url.host() == store_url.host()) {
228    if (error)
229      *error = manifest_errors::kCannotScriptGallery;
230    return false;
231  }
232  return true;
233}
234
235bool ChromeExtensionsClient::IsAPISchemaGenerated(
236    const std::string& name) const {
237#if defined(ENABLE_EXTENSIONS)
238  // Test from most common to least common.
239  return api::GeneratedSchemas::IsGenerated(name) ||
240         core_api::GeneratedSchemas::IsGenerated(name);
241#else
242  return false;
243#endif
244}
245
246base::StringPiece ChromeExtensionsClient::GetAPISchema(
247    const std::string& name) const {
248#if defined(ENABLE_EXTENSIONS)
249  // Test from most common to least common.
250  if (api::GeneratedSchemas::IsGenerated(name))
251    return api::GeneratedSchemas::Get(name);
252
253  return core_api::GeneratedSchemas::Get(name);
254#else
255  return base::StringPiece();
256#endif
257}
258
259void ChromeExtensionsClient::RegisterAPISchemaResources(
260    ExtensionAPI* api) const {
261#if defined(ENABLE_EXTENSIONS)
262  api->RegisterSchemaResource("accessibilityPrivate",
263                              IDR_EXTENSION_API_JSON_ACCESSIBILITYPRIVATE);
264  api->RegisterSchemaResource("app", IDR_EXTENSION_API_JSON_APP);
265  api->RegisterSchemaResource("browserAction",
266                              IDR_EXTENSION_API_JSON_BROWSERACTION);
267  api->RegisterSchemaResource("commands", IDR_EXTENSION_API_JSON_COMMANDS);
268  api->RegisterSchemaResource("declarativeContent",
269                              IDR_EXTENSION_API_JSON_DECLARATIVE_CONTENT);
270  api->RegisterSchemaResource("declarativeWebRequest",
271                              IDR_EXTENSION_API_JSON_DECLARATIVE_WEBREQUEST);
272  api->RegisterSchemaResource("fileBrowserHandler",
273                              IDR_EXTENSION_API_JSON_FILEBROWSERHANDLER);
274  api->RegisterSchemaResource("inputMethodPrivate",
275                              IDR_EXTENSION_API_JSON_INPUTMETHODPRIVATE);
276  api->RegisterSchemaResource("pageAction", IDR_EXTENSION_API_JSON_PAGEACTION);
277  api->RegisterSchemaResource("privacy", IDR_EXTENSION_API_JSON_PRIVACY);
278  api->RegisterSchemaResource("processes", IDR_EXTENSION_API_JSON_PROCESSES);
279  api->RegisterSchemaResource("proxy", IDR_EXTENSION_API_JSON_PROXY);
280  api->RegisterSchemaResource("scriptBadge",
281                              IDR_EXTENSION_API_JSON_SCRIPTBADGE);
282  api->RegisterSchemaResource("ttsEngine", IDR_EXTENSION_API_JSON_TTSENGINE);
283  api->RegisterSchemaResource("tts", IDR_EXTENSION_API_JSON_TTS);
284  api->RegisterSchemaResource("types", IDR_EXTENSION_API_JSON_TYPES);
285  api->RegisterSchemaResource("types.private",
286                              IDR_EXTENSION_API_JSON_TYPES_PRIVATE);
287  api->RegisterSchemaResource("webstore", IDR_EXTENSION_API_JSON_WEBSTORE);
288  api->RegisterSchemaResource("webViewRequest",
289                              IDR_EXTENSION_API_JSON_WEB_VIEW_REQUEST);
290#endif  // defined(ENABLE_EXTENSIONS)
291}
292
293bool ChromeExtensionsClient::ShouldSuppressFatalErrors() const {
294  // Suppress fatal errors only on beta and stable channels.
295  return GetCurrentChannel() > chrome::VersionInfo::CHANNEL_DEV;
296}
297
298std::string ChromeExtensionsClient::GetWebstoreBaseURL() const {
299  std::string gallery_prefix = extension_urls::kChromeWebstoreBaseURL;
300  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAppsGalleryURL))
301    gallery_prefix = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
302        switches::kAppsGalleryURL);
303  if (EndsWith(gallery_prefix, "/", true))
304    gallery_prefix = gallery_prefix.substr(0, gallery_prefix.length() - 1);
305  return gallery_prefix;
306}
307
308std::string ChromeExtensionsClient::GetWebstoreUpdateURL() const {
309  CommandLine* cmdline = CommandLine::ForCurrentProcess();
310  if (cmdline->HasSwitch(switches::kAppsGalleryUpdateURL))
311    return cmdline->GetSwitchValueASCII(switches::kAppsGalleryUpdateURL);
312  else
313    return extension_urls::GetDefaultWebstoreUpdateUrl().spec();
314}
315
316bool ChromeExtensionsClient::IsBlacklistUpdateURL(const GURL& url) const {
317  // The extension blacklist URL is returned from the update service and
318  // therefore not determined by Chromium. If the location of the blacklist file
319  // ever changes, we need to update this function. A DCHECK in the
320  // ExtensionUpdater ensures that we notice a change. This is the full URL
321  // of a blacklist:
322  // http://www.gstatic.com/chrome/extensions/blacklist/l_0_0_0_7.txt
323  return StartsWithASCII(url.spec(), kExtensionBlocklistUrlPrefix, true) ||
324         StartsWithASCII(url.spec(), kExtensionBlocklistHttpsUrlPrefix, true);
325}
326
327// static
328ChromeExtensionsClient* ChromeExtensionsClient::GetInstance() {
329  return g_client.Pointer();
330}
331
332}  // namespace extensions
333