Home | History | Annotate | Download | only in appcache
      1 // Copyright (c) 2011 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 "content/browser/appcache/appcache_request_handler.h"
      6 
      7 #include "content/browser/appcache/appcache.h"
      8 #include "content/browser/appcache/appcache_backend_impl.h"
      9 #include "content/browser/appcache/appcache_policy.h"
     10 #include "content/browser/appcache/appcache_url_request_job.h"
     11 #include "net/url_request/url_request.h"
     12 #include "net/url_request/url_request_job.h"
     13 
     14 namespace content {
     15 
     16 AppCacheRequestHandler::AppCacheRequestHandler(AppCacheHost* host,
     17                                                ResourceType resource_type)
     18     : host_(host),
     19       resource_type_(resource_type),
     20       is_waiting_for_cache_selection_(false),
     21       found_group_id_(0),
     22       found_cache_id_(0),
     23       found_network_namespace_(false),
     24       cache_entry_not_found_(false),
     25       maybe_load_resource_executed_(false) {
     26   DCHECK(host_);
     27   host_->AddObserver(this);
     28 }
     29 
     30 AppCacheRequestHandler::~AppCacheRequestHandler() {
     31   if (host_) {
     32     storage()->CancelDelegateCallbacks(this);
     33     host_->RemoveObserver(this);
     34   }
     35 }
     36 
     37 AppCacheStorage* AppCacheRequestHandler::storage() const {
     38   DCHECK(host_);
     39   return host_->storage();
     40 }
     41 
     42 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadResource(
     43     net::URLRequest* request, net::NetworkDelegate* network_delegate) {
     44   maybe_load_resource_executed_ = true;
     45   if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) ||
     46       cache_entry_not_found_)
     47     return NULL;
     48 
     49   // This method can get called multiple times over the life
     50   // of a request. The case we detect here is having scheduled
     51   // delivery of a "network response" using a job setup on an
     52   // earlier call thru this method. To send the request thru
     53   // to the network involves restarting the request altogether,
     54   // which will call thru to our interception layer again.
     55   // This time thru, we return NULL so the request hits the wire.
     56   if (job_.get()) {
     57     DCHECK(job_->is_delivering_network_response() ||
     58            job_->cache_entry_not_found());
     59     if (job_->cache_entry_not_found())
     60       cache_entry_not_found_ = true;
     61     job_ = NULL;
     62     storage()->CancelDelegateCallbacks(this);
     63     return NULL;
     64   }
     65 
     66   // Clear out our 'found' fields since we're starting a request for a
     67   // new resource, any values in those fields are no longer valid.
     68   found_entry_ = AppCacheEntry();
     69   found_fallback_entry_ = AppCacheEntry();
     70   found_cache_id_ = kAppCacheNoCacheId;
     71   found_manifest_url_ = GURL();
     72   found_network_namespace_ = false;
     73 
     74   if (is_main_resource())
     75     MaybeLoadMainResource(request, network_delegate);
     76   else
     77     MaybeLoadSubResource(request, network_delegate);
     78 
     79   // If its been setup to deliver a network response, we can just delete
     80   // it now and return NULL instead to achieve that since it couldn't
     81   // have been started yet.
     82   if (job_.get() && job_->is_delivering_network_response()) {
     83     DCHECK(!job_->has_been_started());
     84     job_ = NULL;
     85   }
     86 
     87   return job_.get();
     88 }
     89 
     90 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForRedirect(
     91     net::URLRequest* request,
     92     net::NetworkDelegate* network_delegate,
     93     const GURL& location) {
     94   if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) ||
     95       cache_entry_not_found_)
     96     return NULL;
     97   if (is_main_resource())
     98     return NULL;
     99   // TODO(vabr) This is a temporary fix (see crbug/141114). We should get rid of
    100   // it once a more general solution to crbug/121325 is in place.
    101   if (!maybe_load_resource_executed_)
    102     return NULL;
    103   if (request->url().GetOrigin() == location.GetOrigin())
    104     return NULL;
    105 
    106   DCHECK(!job_.get());  // our jobs never generate redirects
    107 
    108   if (found_fallback_entry_.has_response_id()) {
    109     // 6.9.6, step 4: If this results in a redirect to another origin,
    110     // get the resource of the fallback entry.
    111     job_ = new AppCacheURLRequestJob(request, network_delegate,
    112                                      storage(), host_, is_main_resource());
    113     DeliverAppCachedResponse(
    114         found_fallback_entry_, found_cache_id_, found_group_id_,
    115         found_manifest_url_,  true, found_namespace_entry_url_);
    116   } else if (!found_network_namespace_) {
    117     // 6.9.6, step 6: Fail the resource load.
    118     job_ = new AppCacheURLRequestJob(request, network_delegate,
    119                                      storage(), host_, is_main_resource());
    120     DeliverErrorResponse();
    121   } else {
    122     // 6.9.6 step 3 and 5: Fetch the resource normally.
    123   }
    124 
    125   return job_.get();
    126 }
    127 
    128 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForResponse(
    129     net::URLRequest* request, net::NetworkDelegate* network_delegate) {
    130   if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) ||
    131       cache_entry_not_found_)
    132     return NULL;
    133   if (!found_fallback_entry_.has_response_id())
    134     return NULL;
    135 
    136   if (request->status().status() == net::URLRequestStatus::CANCELED) {
    137     // 6.9.6, step 4: But not if the user canceled the download.
    138     return NULL;
    139   }
    140 
    141   // We don't fallback for responses that we delivered.
    142   if (job_.get()) {
    143     DCHECK(!job_->is_delivering_network_response());
    144     return NULL;
    145   }
    146 
    147   if (request->status().is_success()) {
    148     int code_major = request->GetResponseCode() / 100;
    149     if (code_major !=4 && code_major != 5)
    150       return NULL;
    151 
    152     // Servers can override the fallback behavior with a response header.
    153     const std::string kFallbackOverrideHeader(
    154         "x-chromium-appcache-fallback-override");
    155     const std::string kFallbackOverrideValue(
    156         "disallow-fallback");
    157     std::string header_value;
    158     request->GetResponseHeaderByName(kFallbackOverrideHeader, &header_value);
    159     if (header_value == kFallbackOverrideValue)
    160       return NULL;
    161   }
    162 
    163   // 6.9.6, step 4: If this results in a 4xx or 5xx status code
    164   // or there were network errors, get the resource of the fallback entry.
    165   job_ = new AppCacheURLRequestJob(request, network_delegate,
    166                                    storage(), host_, is_main_resource());
    167   DeliverAppCachedResponse(
    168       found_fallback_entry_, found_cache_id_, found_group_id_,
    169       found_manifest_url_, true, found_namespace_entry_url_);
    170   return job_.get();
    171 }
    172 
    173 void AppCacheRequestHandler::GetExtraResponseInfo(
    174     int64* cache_id, GURL* manifest_url) {
    175   if (job_.get() && job_->is_delivering_appcache_response()) {
    176     *cache_id = job_->cache_id();
    177     *manifest_url = job_->manifest_url();
    178   }
    179 }
    180 
    181 void AppCacheRequestHandler::PrepareForCrossSiteTransfer(int old_process_id) {
    182   if (!host_)
    183     return;
    184   AppCacheBackendImpl* backend = host_->service()->GetBackend(old_process_id);
    185   host_for_cross_site_transfer_ = backend->TransferHostOut(host_->host_id());
    186   DCHECK_EQ(host_, host_for_cross_site_transfer_.get());
    187 }
    188 
    189 void AppCacheRequestHandler::CompleteCrossSiteTransfer(
    190     int new_process_id, int new_host_id) {
    191   if (!host_for_cross_site_transfer_.get())
    192     return;
    193   DCHECK_EQ(host_, host_for_cross_site_transfer_.get());
    194   AppCacheBackendImpl* backend = host_->service()->GetBackend(new_process_id);
    195   backend->TransferHostIn(new_host_id, host_for_cross_site_transfer_.Pass());
    196 }
    197 
    198 void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost* host) {
    199   storage()->CancelDelegateCallbacks(this);
    200   host_ = NULL;  // no need to RemoveObserver, the host is being deleted
    201 
    202   // Since the host is being deleted, we don't have to complete any job
    203   // that is current running. It's destined for the bit bucket anyway.
    204   if (job_.get()) {
    205     job_->Kill();
    206     job_ = NULL;
    207   }
    208 }
    209 
    210 void AppCacheRequestHandler::DeliverAppCachedResponse(
    211     const AppCacheEntry& entry, int64 cache_id, int64 group_id,
    212     const GURL& manifest_url,  bool is_fallback,
    213     const GURL& namespace_entry_url) {
    214   DCHECK(host_ && job_.get() && job_->is_waiting());
    215   DCHECK(entry.has_response_id());
    216 
    217   if (IsResourceTypeFrame(resource_type_) && !namespace_entry_url.is_empty())
    218     host_->NotifyMainResourceIsNamespaceEntry(namespace_entry_url);
    219 
    220   job_->DeliverAppCachedResponse(manifest_url, group_id, cache_id,
    221                                  entry, is_fallback);
    222 }
    223 
    224 void AppCacheRequestHandler::DeliverErrorResponse() {
    225   DCHECK(job_.get() && job_->is_waiting());
    226   job_->DeliverErrorResponse();
    227 }
    228 
    229 void AppCacheRequestHandler::DeliverNetworkResponse() {
    230   DCHECK(job_.get() && job_->is_waiting());
    231   job_->DeliverNetworkResponse();
    232 }
    233 
    234 // Main-resource handling ----------------------------------------------
    235 
    236 void AppCacheRequestHandler::MaybeLoadMainResource(
    237     net::URLRequest* request, net::NetworkDelegate* network_delegate) {
    238   DCHECK(!job_.get());
    239   DCHECK(host_);
    240 
    241   const AppCacheHost* spawning_host =
    242       (resource_type_ == RESOURCE_TYPE_SHARED_WORKER) ?
    243       host_ : host_->GetSpawningHost();
    244   GURL preferred_manifest_url = spawning_host ?
    245       spawning_host->preferred_manifest_url() : GURL();
    246 
    247   // We may have to wait for our storage query to complete, but
    248   // this query can also complete syncrhonously.
    249   job_ = new AppCacheURLRequestJob(request, network_delegate,
    250                                    storage(), host_, is_main_resource());
    251   storage()->FindResponseForMainRequest(
    252       request->url(), preferred_manifest_url, this);
    253 }
    254 
    255 void AppCacheRequestHandler::OnMainResponseFound(
    256     const GURL& url, const AppCacheEntry& entry,
    257     const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry,
    258     int64 cache_id, int64 group_id, const GURL& manifest_url) {
    259   DCHECK(job_.get());
    260   DCHECK(host_);
    261   DCHECK(is_main_resource());
    262   DCHECK(!entry.IsForeign());
    263   DCHECK(!fallback_entry.IsForeign());
    264   DCHECK(!(entry.has_response_id() && fallback_entry.has_response_id()));
    265 
    266   if (!job_.get())
    267     return;
    268 
    269   AppCachePolicy* policy = host_->service()->appcache_policy();
    270   bool was_blocked_by_policy = !manifest_url.is_empty() && policy &&
    271       !policy->CanLoadAppCache(manifest_url, host_->first_party_url());
    272 
    273   if (was_blocked_by_policy) {
    274     if (IsResourceTypeFrame(resource_type_)) {
    275       host_->NotifyMainResourceBlocked(manifest_url);
    276     } else {
    277       DCHECK_EQ(resource_type_, RESOURCE_TYPE_SHARED_WORKER);
    278       host_->frontend()->OnContentBlocked(host_->host_id(), manifest_url);
    279     }
    280     DeliverNetworkResponse();
    281     return;
    282   }
    283 
    284   if (IsResourceTypeFrame(resource_type_) && cache_id != kAppCacheNoCacheId) {
    285     // AppCacheHost loads and holds a reference to the main resource cache
    286     // for two reasons, firstly to preload the cache into the working set
    287     // in advance of subresource loads happening, secondly to prevent the
    288     // AppCache from falling out of the working set on frame navigations.
    289     host_->LoadMainResourceCache(cache_id);
    290     host_->set_preferred_manifest_url(manifest_url);
    291   }
    292 
    293   // 6.11.1 Navigating across documents, steps 10 and 14.
    294 
    295   found_entry_ = entry;
    296   found_namespace_entry_url_ = namespace_entry_url;
    297   found_fallback_entry_ = fallback_entry;
    298   found_cache_id_ = cache_id;
    299   found_group_id_ = group_id;
    300   found_manifest_url_ = manifest_url;
    301   found_network_namespace_ = false;  // not applicable to main requests
    302 
    303   if (found_entry_.has_response_id()) {
    304     DCHECK(!found_fallback_entry_.has_response_id());
    305     DeliverAppCachedResponse(
    306         found_entry_, found_cache_id_, found_group_id_, found_manifest_url_,
    307         false, found_namespace_entry_url_);
    308   } else {
    309     DeliverNetworkResponse();
    310   }
    311 }
    312 
    313 // Sub-resource handling ----------------------------------------------
    314 
    315 void AppCacheRequestHandler::MaybeLoadSubResource(
    316     net::URLRequest* request, net::NetworkDelegate* network_delegate) {
    317   DCHECK(!job_.get());
    318 
    319   if (host_->is_selection_pending()) {
    320     // We have to wait until cache selection is complete and the
    321     // selected cache is loaded.
    322     is_waiting_for_cache_selection_ = true;
    323     job_ = new AppCacheURLRequestJob(request, network_delegate,
    324                                      storage(), host_, is_main_resource());
    325     return;
    326   }
    327 
    328   if (!host_->associated_cache() ||
    329       !host_->associated_cache()->is_complete() ||
    330       host_->associated_cache()->owning_group()->is_being_deleted()) {
    331     return;
    332   }
    333 
    334   job_ = new AppCacheURLRequestJob(request, network_delegate,
    335                                    storage(), host_, is_main_resource());
    336   ContinueMaybeLoadSubResource();
    337 }
    338 
    339 void AppCacheRequestHandler::ContinueMaybeLoadSubResource() {
    340   // 6.9.6 Changes to the networking model
    341   // If the resource is not to be fetched using the HTTP GET mechanism or
    342   // equivalent ... then fetch the resource normally.
    343   DCHECK(job_.get());
    344   DCHECK(host_->associated_cache() && host_->associated_cache()->is_complete());
    345 
    346   const GURL& url = job_->request()->url();
    347   AppCache* cache = host_->associated_cache();
    348   storage()->FindResponseForSubRequest(
    349       host_->associated_cache(), url,
    350       &found_entry_, &found_fallback_entry_, &found_network_namespace_);
    351 
    352   if (found_entry_.has_response_id()) {
    353     // Step 2: If there's an entry, get it instead.
    354     DCHECK(!found_network_namespace_ &&
    355            !found_fallback_entry_.has_response_id());
    356     found_cache_id_ = cache->cache_id();
    357     found_group_id_ = cache->owning_group()->group_id();
    358     found_manifest_url_ = cache->owning_group()->manifest_url();
    359     DeliverAppCachedResponse(
    360         found_entry_, found_cache_id_, found_group_id_, found_manifest_url_,
    361         false, GURL());
    362     return;
    363   }
    364 
    365   if (found_fallback_entry_.has_response_id()) {
    366     // Step 4: Fetch the resource normally, if this results
    367     // in certain conditions, then use the fallback.
    368     DCHECK(!found_network_namespace_ &&
    369            !found_entry_.has_response_id());
    370     found_cache_id_ = cache->cache_id();
    371     found_manifest_url_ = cache->owning_group()->manifest_url();
    372     DeliverNetworkResponse();
    373     return;
    374   }
    375 
    376   if (found_network_namespace_) {
    377     // Step 3 and 5: Fetch the resource normally.
    378     DCHECK(!found_entry_.has_response_id() &&
    379            !found_fallback_entry_.has_response_id());
    380     DeliverNetworkResponse();
    381     return;
    382   }
    383 
    384   // Step 6: Fail the resource load.
    385   DeliverErrorResponse();
    386 }
    387 
    388 void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost* host) {
    389   DCHECK(host == host_);
    390   if (is_main_resource())
    391     return;
    392   if (!is_waiting_for_cache_selection_)
    393     return;
    394 
    395   is_waiting_for_cache_selection_ = false;
    396 
    397   if (!host_->associated_cache() ||
    398       !host_->associated_cache()->is_complete()) {
    399     DeliverNetworkResponse();
    400     return;
    401   }
    402 
    403   ContinueMaybeLoadSubResource();
    404 }
    405 
    406 }  // namespace content
    407