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/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 
     60 using content::BrowserThread;
     61 using content::ResourceRequestInfo;
     62 using extensions::Extension;
     63 using extensions::SharedModuleInfo;
     64 
     65 namespace extensions {
     66 namespace {
     67 
     68 class 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 
    114 base::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 
    123 base::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 
    132 void 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 
    161 class 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 
    283 bool 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.
    306 bool 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.
    354 bool 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 
    367 class 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.
    387 net::URLRequestJob*
    388 ExtensionProtocolHandler::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 
    526 net::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 
    564 net::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