Home | History | Annotate | Download | only in browser
      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 
     61 using content::BrowserThread;
     62 using content::ResourceRequestInfo;
     63 using content::ResourceType;
     64 using extensions::Extension;
     65 using extensions::SharedModuleInfo;
     66 
     67 namespace extensions {
     68 namespace {
     69 
     70 class 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 
    116 base::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 
    125 base::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 
    134 void 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 
    163 class 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 
    296 bool 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.
    319 bool 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.
    367 bool 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 
    380 class 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.
    400 net::URLRequestJob*
    401 ExtensionProtocolHandler::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 
    539 net::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 
    577 net::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