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