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