- add sources.
[platform/framework/web/crosswalk.git] / src / webkit / browser / appcache / appcache_request_handler.cc
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_->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