1// Copyright (c) 2012 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/browser/extensions/extension_protocols.h"
6
7#include <algorithm>
8
9#include "base/base64.h"
10#include "base/compiler_specific.h"
11#include "base/file_util.h"
12#include "base/files/file_path.h"
13#include "base/format_macros.h"
14#include "base/logging.h"
15#include "base/memory/weak_ptr.h"
16#include "base/message_loop/message_loop.h"
17#include "base/metrics/histogram.h"
18#include "base/path_service.h"
19#include "base/sha1.h"
20#include "base/strings/string_number_conversions.h"
21#include "base/strings/string_util.h"
22#include "base/strings/stringprintf.h"
23#include "base/strings/utf_string_conversions.h"
24#include "base/threading/sequenced_worker_pool.h"
25#include "base/threading/thread_restrictions.h"
26#include "base/timer/elapsed_timer.h"
27#include "build/build_config.h"
28#include "chrome/browser/extensions/extension_renderer_state.h"
29#include "chrome/browser/extensions/image_loader.h"
30#include "chrome/common/chrome_paths.h"
31#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
32#include "chrome/common/extensions/manifest_url_handler.h"
33#include "chrome/common/extensions/web_accessible_resources_handler.h"
34#include "chrome/common/extensions/webview_handler.h"
35#include "chrome/common/url_constants.h"
36#include "content/public/browser/browser_thread.h"
37#include "content/public/browser/resource_request_info.h"
38#include "extensions/browser/info_map.h"
39#include "extensions/common/constants.h"
40#include "extensions/common/extension.h"
41#include "extensions/common/extension_resource.h"
42#include "extensions/common/file_util.h"
43#include "extensions/common/manifest_handlers/background_info.h"
44#include "extensions/common/manifest_handlers/csp_info.h"
45#include "extensions/common/manifest_handlers/incognito_info.h"
46#include "extensions/common/manifest_handlers/shared_module_info.h"
47#include "grit/component_extension_resources_map.h"
48#include "net/base/mime_util.h"
49#include "net/base/net_errors.h"
50#include "net/http/http_request_headers.h"
51#include "net/http/http_response_headers.h"
52#include "net/http/http_response_info.h"
53#include "net/url_request/url_request_error_job.h"
54#include "net/url_request/url_request_file_job.h"
55#include "net/url_request/url_request_simple_job.h"
56#include "ui/base/resource/resource_bundle.h"
57#include "url/url_util.h"
58
59using content::BrowserThread;
60using content::ResourceRequestInfo;
61using extensions::Extension;
62using extensions::SharedModuleInfo;
63
64namespace {
65
66net::HttpResponseHeaders* BuildHttpHeaders(
67    const std::string& content_security_policy, bool send_cors_header,
68    const base::Time& last_modified_time) {
69  std::string raw_headers;
70  raw_headers.append("HTTP/1.1 200 OK");
71  if (!content_security_policy.empty()) {
72    raw_headers.append(1, '\0');
73    raw_headers.append("Content-Security-Policy: ");
74    raw_headers.append(content_security_policy);
75  }
76
77  if (send_cors_header) {
78    raw_headers.append(1, '\0');
79    raw_headers.append("Access-Control-Allow-Origin: *");
80  }
81
82  if (!last_modified_time.is_null()) {
83    // Hash the time and make an etag to avoid exposing the exact
84    // user installation time of the extension.
85    std::string hash = base::StringPrintf("%" PRId64,
86                                          last_modified_time.ToInternalValue());
87    hash = base::SHA1HashString(hash);
88    std::string etag;
89    base::Base64Encode(hash, &etag);
90    raw_headers.append(1, '\0');
91    raw_headers.append("ETag: \"");
92    raw_headers.append(etag);
93    raw_headers.append("\"");
94    // Also force revalidation.
95    raw_headers.append(1, '\0');
96    raw_headers.append("cache-control: no-cache");
97  }
98
99  raw_headers.append(2, '\0');
100  return new net::HttpResponseHeaders(raw_headers);
101}
102
103class URLRequestResourceBundleJob : public net::URLRequestSimpleJob {
104 public:
105  URLRequestResourceBundleJob(net::URLRequest* request,
106                              net::NetworkDelegate* network_delegate,
107                              const base::FilePath& filename,
108                              int resource_id,
109                              const std::string& content_security_policy,
110                              bool send_cors_header)
111      : net::URLRequestSimpleJob(request, network_delegate),
112        filename_(filename),
113        resource_id_(resource_id),
114        weak_factory_(this) {
115     // Leave cache headers out of resource bundle requests.
116    response_info_.headers = BuildHttpHeaders(content_security_policy,
117                                              send_cors_header,
118                                              base::Time());
119  }
120
121  // Overridden from URLRequestSimpleJob:
122  virtual int GetData(std::string* mime_type,
123                      std::string* charset,
124                      std::string* data,
125                      const net::CompletionCallback& callback) const OVERRIDE {
126    const ResourceBundle& rb = ResourceBundle::GetSharedInstance();
127    *data = rb.GetRawDataResource(resource_id_).as_string();
128
129    // Add the Content-Length header now that we know the resource length.
130    response_info_.headers->AddHeader(base::StringPrintf(
131        "%s: %s",  net::HttpRequestHeaders::kContentLength,
132        base::UintToString(data->size()).c_str()));
133
134    std::string* read_mime_type = new std::string;
135    bool posted = base::PostTaskAndReplyWithResult(
136        BrowserThread::GetBlockingPool(),
137        FROM_HERE,
138        base::Bind(&net::GetMimeTypeFromFile, filename_,
139                   base::Unretained(read_mime_type)),
140        base::Bind(&URLRequestResourceBundleJob::OnMimeTypeRead,
141                   weak_factory_.GetWeakPtr(),
142                   mime_type, charset, data,
143                   base::Owned(read_mime_type),
144                   callback));
145    DCHECK(posted);
146
147    return net::ERR_IO_PENDING;
148  }
149
150  virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE {
151    *info = response_info_;
152  }
153
154 private:
155  virtual ~URLRequestResourceBundleJob() { }
156
157  void OnMimeTypeRead(std::string* out_mime_type,
158                      std::string* charset,
159                      std::string* data,
160                      std::string* read_mime_type,
161                      const net::CompletionCallback& callback,
162                      bool read_result) {
163    *out_mime_type = *read_mime_type;
164    if (StartsWithASCII(*read_mime_type, "text/", false)) {
165      // All of our HTML files should be UTF-8 and for other resource types
166      // (like images), charset doesn't matter.
167      DCHECK(IsStringUTF8(*data));
168      *charset = "utf-8";
169    }
170    int result = read_result ? net::OK : net::ERR_INVALID_URL;
171    callback.Run(result);
172  }
173
174  // We need the filename of the resource to determine the mime type.
175  base::FilePath filename_;
176
177  // The resource bundle id to load.
178  int resource_id_;
179
180  net::HttpResponseInfo response_info_;
181
182  mutable base::WeakPtrFactory<URLRequestResourceBundleJob> weak_factory_;
183};
184
185class GeneratedBackgroundPageJob : public net::URLRequestSimpleJob {
186 public:
187  GeneratedBackgroundPageJob(net::URLRequest* request,
188                             net::NetworkDelegate* network_delegate,
189                             const scoped_refptr<const Extension> extension,
190                             const std::string& content_security_policy)
191      : net::URLRequestSimpleJob(request, network_delegate),
192        extension_(extension) {
193    const bool send_cors_headers = false;
194    // Leave cache headers out of generated background page jobs.
195    response_info_.headers = BuildHttpHeaders(content_security_policy,
196                                              send_cors_headers,
197                                              base::Time());
198  }
199
200  // Overridden from URLRequestSimpleJob:
201  virtual int GetData(std::string* mime_type,
202                      std::string* charset,
203                      std::string* data,
204                      const net::CompletionCallback& callback) const OVERRIDE {
205    *mime_type = "text/html";
206    *charset = "utf-8";
207
208    *data = "<!DOCTYPE html>\n<body>\n";
209    const std::vector<std::string>& background_scripts =
210        extensions::BackgroundInfo::GetBackgroundScripts(extension_.get());
211    for (size_t i = 0; i < background_scripts.size(); ++i) {
212      *data += "<script src=\"";
213      *data += background_scripts[i];
214      *data += "\"></script>\n";
215    }
216
217    return net::OK;
218  }
219
220  virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE {
221    *info = response_info_;
222  }
223
224 private:
225  virtual ~GeneratedBackgroundPageJob() {}
226
227  scoped_refptr<const Extension> extension_;
228  net::HttpResponseInfo response_info_;
229};
230
231base::Time GetFileLastModifiedTime(const base::FilePath& filename) {
232  if (base::PathExists(filename)) {
233    base::PlatformFileInfo info;
234    if (base::GetFileInfo(filename, &info))
235      return info.last_modified;
236  }
237  return base::Time();
238}
239
240base::Time GetFileCreationTime(const base::FilePath& filename) {
241  if (base::PathExists(filename)) {
242    base::PlatformFileInfo info;
243    if (base::GetFileInfo(filename, &info))
244      return info.creation_time;
245  }
246  return base::Time();
247}
248
249void ReadResourceFilePathAndLastModifiedTime(
250    const extensions::ExtensionResource& resource,
251    const base::FilePath& directory,
252    base::FilePath* file_path,
253    base::Time* last_modified_time) {
254  *file_path = resource.GetFilePath();
255  *last_modified_time = GetFileLastModifiedTime(*file_path);
256  // While we're here, log the delta between extension directory
257  // creation time and the resource's last modification time.
258  base::ElapsedTimer query_timer;
259  base::Time dir_creation_time = GetFileCreationTime(directory);
260  UMA_HISTOGRAM_TIMES("Extensions.ResourceDirectoryTimestampQueryLatency",
261                      query_timer.Elapsed());
262  int64 delta_seconds = (*last_modified_time - dir_creation_time).InSeconds();
263  if (delta_seconds >= 0) {
264    UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedDelta",
265                                delta_seconds,
266                                0,
267                                base::TimeDelta::FromDays(30).InSeconds(),
268                                50);
269  } else {
270    UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedNegativeDelta",
271                                -delta_seconds,
272                                1,
273                                base::TimeDelta::FromDays(30).InSeconds(),
274                                50);
275  }
276}
277
278class URLRequestExtensionJob : public net::URLRequestFileJob {
279 public:
280  URLRequestExtensionJob(net::URLRequest* request,
281                         net::NetworkDelegate* network_delegate,
282                         const std::string& extension_id,
283                         const base::FilePath& directory_path,
284                         const base::FilePath& relative_path,
285                         const std::string& content_security_policy,
286                         bool send_cors_header)
287    : net::URLRequestFileJob(
288          request, network_delegate, base::FilePath(),
289          BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior(
290                  base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
291      directory_path_(directory_path),
292      // TODO(tc): Move all of these files into resources.pak so we don't break
293      // when updating on Linux.
294      resource_(extension_id, directory_path, relative_path),
295      content_security_policy_(content_security_policy),
296      send_cors_header_(send_cors_header),
297      weak_factory_(this) {
298  }
299
300  virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE {
301    *info = response_info_;
302  }
303
304  virtual void Start() OVERRIDE {
305    base::FilePath* read_file_path = new base::FilePath;
306    base::Time* last_modified_time = new base::Time();
307    bool posted = BrowserThread::PostBlockingPoolTaskAndReply(
308        FROM_HERE,
309        base::Bind(&ReadResourceFilePathAndLastModifiedTime, resource_,
310                   directory_path_,
311                   base::Unretained(read_file_path),
312                   base::Unretained(last_modified_time)),
313        base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead,
314                   weak_factory_.GetWeakPtr(),
315                   base::Owned(read_file_path),
316                   base::Owned(last_modified_time)));
317    DCHECK(posted);
318  }
319
320 private:
321  virtual ~URLRequestExtensionJob() {}
322
323  void OnFilePathAndLastModifiedTimeRead(base::FilePath* read_file_path,
324                                         base::Time* last_modified_time) {
325    file_path_ = *read_file_path;
326    response_info_.headers = BuildHttpHeaders(
327        content_security_policy_,
328        send_cors_header_,
329        *last_modified_time);
330    URLRequestFileJob::Start();
331  }
332
333  net::HttpResponseInfo response_info_;
334  base::FilePath directory_path_;
335  extensions::ExtensionResource resource_;
336  std::string content_security_policy_;
337  bool send_cors_header_;
338  base::WeakPtrFactory<URLRequestExtensionJob> weak_factory_;
339};
340
341bool ExtensionCanLoadInIncognito(const ResourceRequestInfo* info,
342                                 const std::string& extension_id,
343                                 extensions::InfoMap* extension_info_map) {
344  if (!extension_info_map->IsIncognitoEnabled(extension_id))
345    return false;
346
347  // Only allow incognito toplevel navigations to extension resources in
348  // split mode. In spanning mode, the extension must run in a single process,
349  // and an incognito tab prevents that.
350  if (info->GetResourceType() == ResourceType::MAIN_FRAME) {
351    const Extension* extension =
352        extension_info_map->extensions().GetByID(extension_id);
353    return extension && extensions::IncognitoInfo::IsSplitMode(extension);
354  }
355
356  return true;
357}
358
359// Returns true if an chrome-extension:// resource should be allowed to load.
360// TODO(aa): This should be moved into ExtensionResourceRequestPolicy, but we
361// first need to find a way to get CanLoadInIncognito state into the renderers.
362bool AllowExtensionResourceLoad(net::URLRequest* request,
363                                bool is_incognito,
364                                const Extension* extension,
365                                extensions::InfoMap* extension_info_map) {
366  const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
367
368  // We have seen crashes where info is NULL: crbug.com/52374.
369  if (!info) {
370    LOG(ERROR) << "Allowing load of " << request->url().spec()
371               << "from unknown origin. Could not find user data for "
372               << "request.";
373    return true;
374  }
375
376  if (is_incognito && !ExtensionCanLoadInIncognito(info, request->url().host(),
377                                                   extension_info_map)) {
378    return false;
379  }
380
381  // The following checks are meant to replicate similar set of checks in the
382  // renderer process, performed by ResourceRequestPolicy::CanRequestResource.
383  // These are not exactly equivalent, because we don't have the same bits of
384  // information. The two checks need to be kept in sync as much as possible, as
385  // an exploited renderer can bypass the checks in ResourceRequestPolicy.
386
387  // Check if the extension for which this request is made is indeed loaded in
388  // the process sending the request. If not, we need to explicitly check if
389  // the resource is explicitly accessible or fits in a set of exception cases.
390  // Note: This allows a case where two extensions execute in the same renderer
391  // process to request each other's resources. We can't do a more precise
392  // check, since the renderer can lie about which extension has made the
393  // request.
394  if (extension_info_map->process_map().Contains(
395      request->url().host(), info->GetChildID())) {
396    return true;
397  }
398
399  // Extensions with webview: allow loading certain resources by guest renderers
400  // with privileged partition IDs as specified in the manifest file.
401  ExtensionRendererState* renderer_state =
402      ExtensionRendererState::GetInstance();
403  ExtensionRendererState::WebViewInfo webview_info;
404  bool is_guest = renderer_state->GetWebViewInfo(info->GetChildID(),
405                                                 info->GetRouteID(),
406                                                 &webview_info);
407  std::string resource_path = request->url().path();
408  if (is_guest && webview_info.allow_chrome_extension_urls &&
409      extensions::WebviewInfo::IsResourceWebviewAccessible(
410            extension, webview_info.partition_id, resource_path)) {
411    return true;
412  }
413
414  // If the request is for navigations outside of webviews, then it should be
415  // allowed. The navigation logic in CrossSiteResourceHandler will properly
416  // transfer the navigation to a privileged process before it commits.
417  if (ResourceType::IsFrame(info->GetResourceType()) && !is_guest)
418    return true;
419
420  if (!content::PageTransitionIsWebTriggerable(info->GetPageTransition()))
421    return false;
422
423  // The following checks require that we have an actual extension object. If we
424  // don't have it, allow the request handling to continue with the rest of the
425  // checks.
426  if (!extension)
427    return true;
428
429  // Disallow loading of packaged resources for hosted apps. We don't allow
430  // hybrid hosted/packaged apps. The one exception is access to icons, since
431  // some extensions want to be able to do things like create their own
432  // launchers.
433  std::string resource_root_relative_path =
434      request->url().path().empty() ? std::string()
435                                    : request->url().path().substr(1);
436  if (extension->is_hosted_app() &&
437      !extensions::IconsInfo::GetIcons(extension)
438          .ContainsPath(resource_root_relative_path)) {
439    LOG(ERROR) << "Denying load of " << request->url().spec() << " from "
440               << "hosted app.";
441    return false;
442  }
443
444  // Extensions with web_accessible_resources: allow loading by regular
445  // renderers. Since not all subresources are required to be listed in a v2
446  // manifest, we must allow all loads if there are any web accessible
447  // resources. See http://crbug.com/179127.
448  if (extension->manifest_version() < 2 ||
449      extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources(
450      extension)) {
451    return true;
452  }
453
454  // If there aren't any explicitly marked web accessible resources, the
455  // load should be allowed only if it is by DevTools. A close approximation is
456  // checking if the extension contains a DevTools page.
457  if (extensions::ManifestURL::GetDevToolsPage(extension).is_empty())
458    return false;
459
460  return true;
461}
462
463// Returns true if the given URL references an icon in the given extension.
464bool URLIsForExtensionIcon(const GURL& url, const Extension* extension) {
465  DCHECK(url.SchemeIs(extensions::kExtensionScheme));
466
467  if (!extension)
468    return false;
469
470  std::string path = url.path();
471  DCHECK_EQ(url.host(), extension->id());
472  DCHECK(path.length() > 0 && path[0] == '/');
473  path = path.substr(1);
474  return extensions::IconsInfo::GetIcons(extension).ContainsPath(path);
475}
476
477class ExtensionProtocolHandler
478    : public net::URLRequestJobFactory::ProtocolHandler {
479 public:
480  ExtensionProtocolHandler(bool is_incognito,
481                           extensions::InfoMap* extension_info_map)
482      : is_incognito_(is_incognito), extension_info_map_(extension_info_map) {}
483
484  virtual ~ExtensionProtocolHandler() {}
485
486  virtual net::URLRequestJob* MaybeCreateJob(
487      net::URLRequest* request,
488      net::NetworkDelegate* network_delegate) const OVERRIDE;
489
490 private:
491  const bool is_incognito_;
492  extensions::InfoMap* const extension_info_map_;
493  DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler);
494};
495
496// Creates URLRequestJobs for extension:// URLs.
497net::URLRequestJob*
498ExtensionProtocolHandler::MaybeCreateJob(
499    net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
500  // chrome-extension://extension-id/resource/path.js
501  std::string extension_id = request->url().host();
502  const Extension* extension =
503      extension_info_map_->extensions().GetByID(extension_id);
504
505  // TODO(mpcomplete): better error code.
506  if (!AllowExtensionResourceLoad(
507           request, is_incognito_, extension, extension_info_map_)) {
508    return new net::URLRequestErrorJob(
509        request, network_delegate, net::ERR_ADDRESS_UNREACHABLE);
510  }
511
512  base::FilePath directory_path;
513  if (extension)
514    directory_path = extension->path();
515  if (directory_path.value().empty()) {
516    const Extension* disabled_extension =
517        extension_info_map_->disabled_extensions().GetByID(extension_id);
518    if (URLIsForExtensionIcon(request->url(), disabled_extension))
519      directory_path = disabled_extension->path();
520    if (directory_path.value().empty()) {
521      LOG(WARNING) << "Failed to GetPathForExtension: " << extension_id;
522      return NULL;
523    }
524  }
525
526  std::string content_security_policy;
527  bool send_cors_header = false;
528  if (extension) {
529    std::string resource_path = request->url().path();
530    content_security_policy =
531        extensions::CSPInfo::GetResourceContentSecurityPolicy(extension,
532                                                              resource_path);
533    if ((extension->manifest_version() >= 2 ||
534         extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources(
535             extension)) &&
536        extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible(
537            extension, resource_path))
538      send_cors_header = true;
539  }
540
541  std::string path = request->url().path();
542  if (path.size() > 1 &&
543      path.substr(1) == extensions::kGeneratedBackgroundPageFilename) {
544    return new GeneratedBackgroundPageJob(
545        request, network_delegate, extension, content_security_policy);
546  }
547
548  base::FilePath resources_path;
549  base::FilePath relative_path;
550  // Try to load extension resources from chrome resource file if
551  // directory_path is a descendant of resources_path. resources_path
552  // corresponds to src/chrome/browser/resources in source tree.
553  if (PathService::Get(chrome::DIR_RESOURCES, &resources_path) &&
554      // Since component extension resources are included in
555      // component_extension_resources.pak file in resources_path, calculate
556      // extension relative path against resources_path.
557      resources_path.AppendRelativePath(directory_path, &relative_path)) {
558    base::FilePath request_path =
559        extensions::file_util::ExtensionURLToRelativeFilePath(request->url());
560    int resource_id;
561    if (extensions::ImageLoader::IsComponentExtensionResource(
562        directory_path, request_path, &resource_id)) {
563      relative_path = relative_path.Append(request_path);
564      relative_path = relative_path.NormalizePathSeparators();
565      return new URLRequestResourceBundleJob(
566          request,
567          network_delegate,
568          relative_path,
569          resource_id,
570          content_security_policy,
571          send_cors_header);
572    }
573  }
574
575  relative_path =
576      extensions::file_util::ExtensionURLToRelativeFilePath(request->url());
577
578  if (SharedModuleInfo::IsImportedPath(path)) {
579    std::string new_extension_id;
580    std::string new_relative_path;
581    SharedModuleInfo::ParseImportedPath(path, &new_extension_id,
582                                        &new_relative_path);
583    const Extension* new_extension =
584        extension_info_map_->extensions().GetByID(new_extension_id);
585
586    bool first_party_in_import = false;
587    // NB: This first_party_for_cookies call is not for security, it is only
588    // used so an exported extension can limit the visible surface to the
589    // extension that imports it, more or less constituting its API.
590    const std::string& first_party_path =
591        request->first_party_for_cookies().path();
592    if (SharedModuleInfo::IsImportedPath(first_party_path)) {
593      std::string first_party_id;
594      std::string dummy;
595      SharedModuleInfo::ParseImportedPath(first_party_path, &first_party_id,
596                                          &dummy);
597      if (first_party_id == new_extension_id) {
598        first_party_in_import = true;
599      }
600    }
601
602    if (SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) &&
603        new_extension &&
604        (first_party_in_import ||
605         SharedModuleInfo::IsExportAllowed(new_extension, new_relative_path))) {
606      directory_path = new_extension->path();
607      extension_id = new_extension_id;
608      relative_path = base::FilePath::FromUTF8Unsafe(new_relative_path);
609    } else {
610      return NULL;
611    }
612  }
613
614  return new URLRequestExtensionJob(request,
615                                    network_delegate,
616                                    extension_id,
617                                    directory_path,
618                                    relative_path,
619                                    content_security_policy,
620                                    send_cors_header);
621}
622
623}  // namespace
624
625net::URLRequestJobFactory::ProtocolHandler* CreateExtensionProtocolHandler(
626    bool is_incognito,
627    extensions::InfoMap* extension_info_map) {
628  return new ExtensionProtocolHandler(is_incognito, extension_info_map);
629}
630