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