extension_protocols.cc revision 010d83a9304c5a91596085d917d248abff47903a
1// Copyright 2014 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 "extensions/browser/extension_protocols.h"
6
7#include <algorithm>
8#include <string>
9#include <vector>
10
11#include "base/base64.h"
12#include "base/compiler_specific.h"
13#include "base/file_util.h"
14#include "base/files/file_path.h"
15#include "base/format_macros.h"
16#include "base/logging.h"
17#include "base/memory/weak_ptr.h"
18#include "base/message_loop/message_loop.h"
19#include "base/metrics/field_trial.h"
20#include "base/metrics/histogram.h"
21#include "base/metrics/sparse_histogram.h"
22#include "base/path_service.h"
23#include "base/sha1.h"
24#include "base/strings/string_number_conversions.h"
25#include "base/strings/string_util.h"
26#include "base/strings/stringprintf.h"
27#include "base/strings/utf_string_conversions.h"
28#include "base/threading/sequenced_worker_pool.h"
29#include "base/threading/thread_restrictions.h"
30#include "base/timer/elapsed_timer.h"
31#include "build/build_config.h"
32#include "content/public/browser/browser_thread.h"
33#include "content/public/browser/resource_request_info.h"
34#include "crypto/secure_hash.h"
35#include "crypto/sha2.h"
36#include "extensions/browser/content_verifier.h"
37#include "extensions/browser/content_verify_job.h"
38#include "extensions/browser/extensions_browser_client.h"
39#include "extensions/browser/info_map.h"
40#include "extensions/common/constants.h"
41#include "extensions/common/extension.h"
42#include "extensions/common/extension_resource.h"
43#include "extensions/common/file_util.h"
44#include "extensions/common/manifest_handlers/background_info.h"
45#include "extensions/common/manifest_handlers/csp_info.h"
46#include "extensions/common/manifest_handlers/icons_handler.h"
47#include "extensions/common/manifest_handlers/incognito_info.h"
48#include "extensions/common/manifest_handlers/shared_module_info.h"
49#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
50#include "net/base/io_buffer.h"
51#include "net/base/net_errors.h"
52#include "net/http/http_request_headers.h"
53#include "net/http/http_response_headers.h"
54#include "net/http/http_response_info.h"
55#include "net/url_request/url_request_error_job.h"
56#include "net/url_request/url_request_file_job.h"
57#include "net/url_request/url_request_simple_job.h"
58#include "url/url_util.h"
59
60using content::BrowserThread;
61using content::ResourceRequestInfo;
62using extensions::Extension;
63using extensions::SharedModuleInfo;
64
65namespace extensions {
66namespace {
67
68class GeneratedBackgroundPageJob : public net::URLRequestSimpleJob {
69 public:
70  GeneratedBackgroundPageJob(net::URLRequest* request,
71                             net::NetworkDelegate* network_delegate,
72                             const scoped_refptr<const Extension> extension,
73                             const std::string& content_security_policy)
74      : net::URLRequestSimpleJob(request, network_delegate),
75        extension_(extension) {
76    const bool send_cors_headers = false;
77    // Leave cache headers out of generated background page jobs.
78    response_info_.headers = BuildHttpHeaders(content_security_policy,
79                                              send_cors_headers,
80                                              base::Time());
81  }
82
83  // Overridden from URLRequestSimpleJob:
84  virtual int GetData(std::string* mime_type,
85                      std::string* charset,
86                      std::string* data,
87                      const net::CompletionCallback& callback) const OVERRIDE {
88    *mime_type = "text/html";
89    *charset = "utf-8";
90
91    *data = "<!DOCTYPE html>\n<body>\n";
92    const std::vector<std::string>& background_scripts =
93        extensions::BackgroundInfo::GetBackgroundScripts(extension_.get());
94    for (size_t i = 0; i < background_scripts.size(); ++i) {
95      *data += "<script src=\"";
96      *data += background_scripts[i];
97      *data += "\"></script>\n";
98    }
99
100    return net::OK;
101  }
102
103  virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE {
104    *info = response_info_;
105  }
106
107 private:
108  virtual ~GeneratedBackgroundPageJob() {}
109
110  scoped_refptr<const Extension> extension_;
111  net::HttpResponseInfo response_info_;
112};
113
114base::Time GetFileLastModifiedTime(const base::FilePath& filename) {
115  if (base::PathExists(filename)) {
116    base::File::Info info;
117    if (base::GetFileInfo(filename, &info))
118      return info.last_modified;
119  }
120  return base::Time();
121}
122
123base::Time GetFileCreationTime(const base::FilePath& filename) {
124  if (base::PathExists(filename)) {
125    base::File::Info info;
126    if (base::GetFileInfo(filename, &info))
127      return info.creation_time;
128  }
129  return base::Time();
130}
131
132void ReadResourceFilePathAndLastModifiedTime(
133    const extensions::ExtensionResource& resource,
134    const base::FilePath& directory,
135    base::FilePath* file_path,
136    base::Time* last_modified_time) {
137  *file_path = resource.GetFilePath();
138  *last_modified_time = GetFileLastModifiedTime(*file_path);
139  // While we're here, log the delta between extension directory
140  // creation time and the resource's last modification time.
141  base::ElapsedTimer query_timer;
142  base::Time dir_creation_time = GetFileCreationTime(directory);
143  UMA_HISTOGRAM_TIMES("Extensions.ResourceDirectoryTimestampQueryLatency",
144                      query_timer.Elapsed());
145  int64 delta_seconds = (*last_modified_time - dir_creation_time).InSeconds();
146  if (delta_seconds >= 0) {
147    UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedDelta",
148                                delta_seconds,
149                                0,
150                                base::TimeDelta::FromDays(30).InSeconds(),
151                                50);
152  } else {
153    UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedNegativeDelta",
154                                -delta_seconds,
155                                1,
156                                base::TimeDelta::FromDays(30).InSeconds(),
157                                50);
158  }
159}
160
161class URLRequestExtensionJob : public net::URLRequestFileJob {
162 public:
163  URLRequestExtensionJob(net::URLRequest* request,
164                         net::NetworkDelegate* network_delegate,
165                         const std::string& extension_id,
166                         const base::FilePath& directory_path,
167                         const base::FilePath& relative_path,
168                         const std::string& content_security_policy,
169                         bool send_cors_header,
170                         bool follow_symlinks_anywhere,
171                         ContentVerifyJob* verify_job)
172      : net::URLRequestFileJob(
173            request,
174            network_delegate,
175            base::FilePath(),
176            BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior(
177                base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
178        verify_job_(verify_job),
179        seek_position_(0),
180        bytes_read_(0),
181        directory_path_(directory_path),
182        // TODO(tc): Move all of these files into resources.pak so we don't
183        // break when updating on Linux.
184        resource_(extension_id, directory_path, relative_path),
185        content_security_policy_(content_security_policy),
186        send_cors_header_(send_cors_header),
187        weak_factory_(this) {
188    if (follow_symlinks_anywhere) {
189      resource_.set_follow_symlinks_anywhere();
190    }
191  }
192
193  virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE {
194    *info = response_info_;
195  }
196
197  virtual void Start() OVERRIDE {
198    base::FilePath* read_file_path = new base::FilePath;
199    base::Time* last_modified_time = new base::Time();
200    bool posted = BrowserThread::PostBlockingPoolTaskAndReply(
201        FROM_HERE,
202        base::Bind(&ReadResourceFilePathAndLastModifiedTime,
203                   resource_,
204                   directory_path_,
205                   base::Unretained(read_file_path),
206                   base::Unretained(last_modified_time)),
207        base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead,
208                   weak_factory_.GetWeakPtr(),
209                   base::Owned(read_file_path),
210                   base::Owned(last_modified_time)));
211    DCHECK(posted);
212  }
213
214  virtual void SetExtraRequestHeaders(
215      const net::HttpRequestHeaders& headers) OVERRIDE {
216    // TODO(asargent) - we'll need to add proper support for range headers.
217    // crbug.com/369895.
218    std::string range_header;
219    if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
220      if (verify_job_)
221        verify_job_ = NULL;
222    }
223    URLRequestFileJob::SetExtraRequestHeaders(headers);
224  }
225
226  virtual void OnSeekComplete(int64 result) OVERRIDE {
227    DCHECK_EQ(seek_position_, 0);
228    seek_position_ = result;
229    // TODO(asargent) - we'll need to add proper support for range headers.
230    // crbug.com/369895.
231    if (result > 0 && verify_job_)
232      verify_job_ = NULL;
233  }
234
235  virtual void OnReadComplete(net::IOBuffer* buffer, int result) OVERRIDE {
236    if (result >= 0)
237      UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.OnReadCompleteResult", result);
238    else
239      UMA_HISTOGRAM_SPARSE_SLOWLY("ExtensionUrlRequest.OnReadCompleteError",
240                                  -result);
241    if (result > 0) {
242      bytes_read_ += result;
243      if (verify_job_) {
244        verify_job_->BytesRead(result, buffer->data());
245        if (!remaining_bytes())
246          verify_job_->DoneReading();
247      }
248    }
249  }
250
251 private:
252  virtual ~URLRequestExtensionJob() {
253    UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.TotalKbRead", bytes_read_ / 1024);
254    UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.SeekPosition", seek_position_);
255  }
256
257  void OnFilePathAndLastModifiedTimeRead(base::FilePath* read_file_path,
258                                         base::Time* last_modified_time) {
259    file_path_ = *read_file_path;
260    response_info_.headers = BuildHttpHeaders(
261        content_security_policy_,
262        send_cors_header_,
263        *last_modified_time);
264    URLRequestFileJob::Start();
265  }
266
267  scoped_refptr<ContentVerifyJob> verify_job_;
268
269  // The position we seeked to in the file.
270  int64 seek_position_;
271
272  // The number of bytes of content we read from the file.
273  int bytes_read_;
274
275  net::HttpResponseInfo response_info_;
276  base::FilePath directory_path_;
277  extensions::ExtensionResource resource_;
278  std::string content_security_policy_;
279  bool send_cors_header_;
280  base::WeakPtrFactory<URLRequestExtensionJob> weak_factory_;
281};
282
283bool ExtensionCanLoadInIncognito(const ResourceRequestInfo* info,
284                                 const std::string& extension_id,
285                                 extensions::InfoMap* extension_info_map) {
286  if (!extension_info_map->IsIncognitoEnabled(extension_id))
287    return false;
288
289  // Only allow incognito toplevel navigations to extension resources in
290  // split mode. In spanning mode, the extension must run in a single process,
291  // and an incognito tab prevents that.
292  if (info->GetResourceType() == ResourceType::MAIN_FRAME) {
293    const Extension* extension =
294        extension_info_map->extensions().GetByID(extension_id);
295    return extension && extensions::IncognitoInfo::IsSplitMode(extension);
296  }
297
298  return true;
299}
300
301// Returns true if an chrome-extension:// resource should be allowed to load.
302// Pass true for |is_incognito| only for incognito profiles and not Chrome OS
303// guest mode profiles.
304// TODO(aa): This should be moved into ExtensionResourceRequestPolicy, but we
305// first need to find a way to get CanLoadInIncognito state into the renderers.
306bool AllowExtensionResourceLoad(net::URLRequest* request,
307                                bool is_incognito,
308                                const Extension* extension,
309                                extensions::InfoMap* extension_info_map) {
310  const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
311
312  // We have seen crashes where info is NULL: crbug.com/52374.
313  if (!info) {
314    LOG(ERROR) << "Allowing load of " << request->url().spec()
315               << "from unknown origin. Could not find user data for "
316               << "request.";
317    return true;
318  }
319
320  if (is_incognito && !ExtensionCanLoadInIncognito(
321                          info, request->url().host(), extension_info_map)) {
322    return false;
323  }
324
325  // The following checks are meant to replicate similar set of checks in the
326  // renderer process, performed by ResourceRequestPolicy::CanRequestResource.
327  // These are not exactly equivalent, because we don't have the same bits of
328  // information. The two checks need to be kept in sync as much as possible, as
329  // an exploited renderer can bypass the checks in ResourceRequestPolicy.
330
331  // Check if the extension for which this request is made is indeed loaded in
332  // the process sending the request. If not, we need to explicitly check if
333  // the resource is explicitly accessible or fits in a set of exception cases.
334  // Note: This allows a case where two extensions execute in the same renderer
335  // process to request each other's resources. We can't do a more precise
336  // check, since the renderer can lie about which extension has made the
337  // request.
338  if (extension_info_map->process_map().Contains(
339      request->url().host(), info->GetChildID())) {
340    return true;
341  }
342
343  // Allow the extension module embedder to grant permission for loads.
344  if (ExtensionsBrowserClient::Get()->AllowCrossRendererResourceLoad(
345          request, is_incognito, extension, extension_info_map)) {
346    return true;
347  }
348
349  // No special exceptions for cross-process loading. Block the load.
350  return false;
351}
352
353// Returns true if the given URL references an icon in the given extension.
354bool URLIsForExtensionIcon(const GURL& url, const Extension* extension) {
355  DCHECK(url.SchemeIs(extensions::kExtensionScheme));
356
357  if (!extension)
358    return false;
359
360  std::string path = url.path();
361  DCHECK_EQ(url.host(), extension->id());
362  DCHECK(path.length() > 0 && path[0] == '/');
363  path = path.substr(1);
364  return extensions::IconsInfo::GetIcons(extension).ContainsPath(path);
365}
366
367class ExtensionProtocolHandler
368    : public net::URLRequestJobFactory::ProtocolHandler {
369 public:
370  ExtensionProtocolHandler(bool is_incognito,
371                           extensions::InfoMap* extension_info_map)
372      : is_incognito_(is_incognito), extension_info_map_(extension_info_map) {}
373
374  virtual ~ExtensionProtocolHandler() {}
375
376  virtual net::URLRequestJob* MaybeCreateJob(
377      net::URLRequest* request,
378      net::NetworkDelegate* network_delegate) const OVERRIDE;
379
380 private:
381  const bool is_incognito_;
382  extensions::InfoMap* const extension_info_map_;
383  DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler);
384};
385
386// Creates URLRequestJobs for extension:// URLs.
387net::URLRequestJob*
388ExtensionProtocolHandler::MaybeCreateJob(
389    net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
390  // chrome-extension://extension-id/resource/path.js
391  std::string extension_id = request->url().host();
392  const Extension* extension =
393      extension_info_map_->extensions().GetByID(extension_id);
394
395  // TODO(mpcomplete): better error code.
396  if (!AllowExtensionResourceLoad(
397          request, is_incognito_, extension, extension_info_map_)) {
398    return new net::URLRequestErrorJob(
399        request, network_delegate, net::ERR_ADDRESS_UNREACHABLE);
400  }
401
402  // If this is a disabled extension only allow the icon to load.
403  base::FilePath directory_path;
404  if (extension)
405    directory_path = extension->path();
406  if (directory_path.value().empty()) {
407    const Extension* disabled_extension =
408        extension_info_map_->disabled_extensions().GetByID(extension_id);
409    if (URLIsForExtensionIcon(request->url(), disabled_extension))
410      directory_path = disabled_extension->path();
411    if (directory_path.value().empty()) {
412      LOG(WARNING) << "Failed to GetPathForExtension: " << extension_id;
413      return NULL;
414    }
415  }
416
417  // Set up content security policy.
418  std::string content_security_policy;
419  bool send_cors_header = false;
420  bool follow_symlinks_anywhere = false;
421
422  if (extension) {
423    std::string resource_path = request->url().path();
424
425    // Use default CSP for <webview>.
426    if (!ExtensionsBrowserClient::Get()->IsWebViewRequest(request)) {
427      content_security_policy =
428          extensions::CSPInfo::GetResourceContentSecurityPolicy(extension,
429                                                                resource_path);
430    }
431
432    if ((extension->manifest_version() >= 2 ||
433         extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources(
434             extension)) &&
435        extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible(
436            extension, resource_path)) {
437      send_cors_header = true;
438    }
439
440    follow_symlinks_anywhere =
441        (extension->creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE)
442        != 0;
443  }
444
445  // Create a job for a generated background page.
446  std::string path = request->url().path();
447  if (path.size() > 1 &&
448      path.substr(1) == extensions::kGeneratedBackgroundPageFilename) {
449    return new GeneratedBackgroundPageJob(
450        request, network_delegate, extension, content_security_policy);
451  }
452
453  // Component extension resources may be part of the embedder's resource files,
454  // for example component_extension_resources.pak in Chrome.
455  net::URLRequestJob* resource_bundle_job =
456      extensions::ExtensionsBrowserClient::Get()
457          ->MaybeCreateResourceBundleRequestJob(request,
458                                                network_delegate,
459                                                directory_path,
460                                                content_security_policy,
461                                                send_cors_header);
462  if (resource_bundle_job)
463    return resource_bundle_job;
464
465  base::FilePath relative_path =
466      extensions::file_util::ExtensionURLToRelativeFilePath(request->url());
467
468  // Handle shared resources (extension A loading resources out of extension B).
469  if (SharedModuleInfo::IsImportedPath(path)) {
470    std::string new_extension_id;
471    std::string new_relative_path;
472    SharedModuleInfo::ParseImportedPath(path, &new_extension_id,
473                                        &new_relative_path);
474    const Extension* new_extension =
475        extension_info_map_->extensions().GetByID(new_extension_id);
476
477    bool first_party_in_import = false;
478    // NB: This first_party_for_cookies call is not for security, it is only
479    // used so an exported extension can limit the visible surface to the
480    // extension that imports it, more or less constituting its API.
481    const std::string& first_party_path =
482        request->first_party_for_cookies().path();
483    if (SharedModuleInfo::IsImportedPath(first_party_path)) {
484      std::string first_party_id;
485      std::string dummy;
486      SharedModuleInfo::ParseImportedPath(first_party_path, &first_party_id,
487                                          &dummy);
488      if (first_party_id == new_extension_id) {
489        first_party_in_import = true;
490      }
491    }
492
493    if (SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) &&
494        new_extension &&
495        (first_party_in_import ||
496         SharedModuleInfo::IsExportAllowed(new_extension, new_relative_path))) {
497      directory_path = new_extension->path();
498      extension_id = new_extension_id;
499      relative_path = base::FilePath::FromUTF8Unsafe(new_relative_path);
500    } else {
501      return NULL;
502    }
503  }
504  ContentVerifyJob* verify_job = NULL;
505  ContentVerifier* verifier = extension_info_map_->content_verifier();
506  if (verifier) {
507    verify_job =
508        verifier->CreateJobFor(extension_id, directory_path, relative_path);
509    if (verify_job)
510      verify_job->Start();
511  }
512
513  return new URLRequestExtensionJob(request,
514                                    network_delegate,
515                                    extension_id,
516                                    directory_path,
517                                    relative_path,
518                                    content_security_policy,
519                                    send_cors_header,
520                                    follow_symlinks_anywhere,
521                                    verify_job);
522}
523
524}  // namespace
525
526net::HttpResponseHeaders* BuildHttpHeaders(
527    const std::string& content_security_policy,
528    bool send_cors_header,
529    const base::Time& last_modified_time) {
530  std::string raw_headers;
531  raw_headers.append("HTTP/1.1 200 OK");
532  if (!content_security_policy.empty()) {
533    raw_headers.append(1, '\0');
534    raw_headers.append("Content-Security-Policy: ");
535    raw_headers.append(content_security_policy);
536  }
537
538  if (send_cors_header) {
539    raw_headers.append(1, '\0');
540    raw_headers.append("Access-Control-Allow-Origin: *");
541  }
542
543  if (!last_modified_time.is_null()) {
544    // Hash the time and make an etag to avoid exposing the exact
545    // user installation time of the extension.
546    std::string hash =
547        base::StringPrintf("%" PRId64, last_modified_time.ToInternalValue());
548    hash = base::SHA1HashString(hash);
549    std::string etag;
550    base::Base64Encode(hash, &etag);
551    raw_headers.append(1, '\0');
552    raw_headers.append("ETag: \"");
553    raw_headers.append(etag);
554    raw_headers.append("\"");
555    // Also force revalidation.
556    raw_headers.append(1, '\0');
557    raw_headers.append("cache-control: no-cache");
558  }
559
560  raw_headers.append(2, '\0');
561  return new net::HttpResponseHeaders(raw_headers);
562}
563
564net::URLRequestJobFactory::ProtocolHandler* CreateExtensionProtocolHandler(
565    bool is_incognito,
566    extensions::InfoMap* extension_info_map) {
567  return new ExtensionProtocolHandler(is_incognito, extension_info_map);
568}
569
570}  // namespace extensions
571