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