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
14namespace content {
15
16AppCacheRequestHandler::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
30AppCacheRequestHandler::~AppCacheRequestHandler() {
31  if (host_) {
32    storage()->CancelDelegateCallbacks(this);
33    host_->RemoveObserver(this);
34  }
35}
36
37AppCacheStorage* AppCacheRequestHandler::storage() const {
38  DCHECK(host_);
39  return host_->storage();
40}
41
42AppCacheURLRequestJob* 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
90AppCacheURLRequestJob* 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
128AppCacheURLRequestJob* 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
173void 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
181void 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
189void 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
198void 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
210void 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
224void AppCacheRequestHandler::DeliverErrorResponse() {
225  DCHECK(job_.get() && job_->is_waiting());
226  job_->DeliverErrorResponse();
227}
228
229void AppCacheRequestHandler::DeliverNetworkResponse() {
230  DCHECK(job_.get() && job_->is_waiting());
231  job_->DeliverNetworkResponse();
232}
233
234// Main-resource handling ----------------------------------------------
235
236void 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
255void 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
315void 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
339void 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
388void 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