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