1 // Copyright 2014 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/service_worker/service_worker_url_request_job.h"
6
7 #include <map>
8 #include <string>
9 #include <vector>
10
11 #include "base/bind.h"
12 #include "base/guid.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/time/time.h"
15 #include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
16 #include "content/browser/service_worker/service_worker_provider_host.h"
17 #include "content/common/resource_request_body.h"
18 #include "content/common/service_worker/service_worker_types.h"
19 #include "content/public/browser/blob_handle.h"
20 #include "content/public/browser/resource_request_info.h"
21 #include "net/base/net_errors.h"
22 #include "net/http/http_request_headers.h"
23 #include "net/http/http_response_headers.h"
24 #include "net/http/http_response_info.h"
25 #include "net/http/http_util.h"
26 #include "storage/browser/blob/blob_data_handle.h"
27 #include "storage/browser/blob/blob_storage_context.h"
28 #include "storage/browser/blob/blob_url_request_job_factory.h"
29 #include "ui/base/page_transition_types.h"
30
31 namespace content {
32
ServiceWorkerURLRequestJob(net::URLRequest * request,net::NetworkDelegate * network_delegate,base::WeakPtr<ServiceWorkerProviderHost> provider_host,base::WeakPtr<storage::BlobStorageContext> blob_storage_context,scoped_refptr<ResourceRequestBody> body)33 ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob(
34 net::URLRequest* request,
35 net::NetworkDelegate* network_delegate,
36 base::WeakPtr<ServiceWorkerProviderHost> provider_host,
37 base::WeakPtr<storage::BlobStorageContext> blob_storage_context,
38 scoped_refptr<ResourceRequestBody> body)
39 : net::URLRequestJob(request, network_delegate),
40 provider_host_(provider_host),
41 response_type_(NOT_DETERMINED),
42 is_started_(false),
43 blob_storage_context_(blob_storage_context),
44 body_(body),
45 weak_factory_(this) {
46 }
47
FallbackToNetwork()48 void ServiceWorkerURLRequestJob::FallbackToNetwork() {
49 DCHECK_EQ(NOT_DETERMINED, response_type_);
50 response_type_ = FALLBACK_TO_NETWORK;
51 MaybeStartRequest();
52 }
53
ForwardToServiceWorker()54 void ServiceWorkerURLRequestJob::ForwardToServiceWorker() {
55 DCHECK_EQ(NOT_DETERMINED, response_type_);
56 response_type_ = FORWARD_TO_SERVICE_WORKER;
57 MaybeStartRequest();
58 }
59
Start()60 void ServiceWorkerURLRequestJob::Start() {
61 is_started_ = true;
62 MaybeStartRequest();
63 }
64
Kill()65 void ServiceWorkerURLRequestJob::Kill() {
66 net::URLRequestJob::Kill();
67 fetch_dispatcher_.reset();
68 blob_request_.reset();
69 weak_factory_.InvalidateWeakPtrs();
70 }
71
GetLoadState() const72 net::LoadState ServiceWorkerURLRequestJob::GetLoadState() const {
73 // TODO(kinuko): refine this for better debug.
74 return net::URLRequestJob::GetLoadState();
75 }
76
GetCharset(std::string * charset)77 bool ServiceWorkerURLRequestJob::GetCharset(std::string* charset) {
78 if (!http_info())
79 return false;
80 return http_info()->headers->GetCharset(charset);
81 }
82
GetMimeType(std::string * mime_type) const83 bool ServiceWorkerURLRequestJob::GetMimeType(std::string* mime_type) const {
84 if (!http_info())
85 return false;
86 return http_info()->headers->GetMimeType(mime_type);
87 }
88
GetResponseInfo(net::HttpResponseInfo * info)89 void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
90 if (!http_info())
91 return;
92 *info = *http_info();
93 info->response_time = response_time_;
94 }
95
GetLoadTimingInfo(net::LoadTimingInfo * load_timing_info) const96 void ServiceWorkerURLRequestJob::GetLoadTimingInfo(
97 net::LoadTimingInfo* load_timing_info) const {
98 *load_timing_info = load_timing_info_;
99 }
100
GetResponseCode() const101 int ServiceWorkerURLRequestJob::GetResponseCode() const {
102 if (!http_info())
103 return -1;
104 return http_info()->headers->response_code();
105 }
106
SetExtraRequestHeaders(const net::HttpRequestHeaders & headers)107 void ServiceWorkerURLRequestJob::SetExtraRequestHeaders(
108 const net::HttpRequestHeaders& headers) {
109 std::string range_header;
110 std::vector<net::HttpByteRange> ranges;
111 if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) ||
112 !net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
113 return;
114 }
115
116 // We don't support multiple range requests in one single URL request.
117 if (ranges.size() == 1U)
118 byte_range_ = ranges[0];
119 }
120
ReadRawData(net::IOBuffer * buf,int buf_size,int * bytes_read)121 bool ServiceWorkerURLRequestJob::ReadRawData(
122 net::IOBuffer* buf, int buf_size, int *bytes_read) {
123 if (!blob_request_) {
124 *bytes_read = 0;
125 return true;
126 }
127
128 blob_request_->Read(buf, buf_size, bytes_read);
129 net::URLRequestStatus status = blob_request_->status();
130 SetStatus(status);
131 if (status.is_io_pending())
132 return false;
133 return status.is_success();
134 }
135
OnReceivedRedirect(net::URLRequest * request,const net::RedirectInfo & redirect_info,bool * defer_redirect)136 void ServiceWorkerURLRequestJob::OnReceivedRedirect(
137 net::URLRequest* request,
138 const net::RedirectInfo& redirect_info,
139 bool* defer_redirect) {
140 NOTREACHED();
141 }
142
OnAuthRequired(net::URLRequest * request,net::AuthChallengeInfo * auth_info)143 void ServiceWorkerURLRequestJob::OnAuthRequired(
144 net::URLRequest* request,
145 net::AuthChallengeInfo* auth_info) {
146 NOTREACHED();
147 }
148
OnCertificateRequested(net::URLRequest * request,net::SSLCertRequestInfo * cert_request_info)149 void ServiceWorkerURLRequestJob::OnCertificateRequested(
150 net::URLRequest* request,
151 net::SSLCertRequestInfo* cert_request_info) {
152 NOTREACHED();
153 }
154
OnSSLCertificateError(net::URLRequest * request,const net::SSLInfo & ssl_info,bool fatal)155 void ServiceWorkerURLRequestJob::OnSSLCertificateError(
156 net::URLRequest* request,
157 const net::SSLInfo& ssl_info,
158 bool fatal) {
159 NOTREACHED();
160 }
161
OnBeforeNetworkStart(net::URLRequest * request,bool * defer)162 void ServiceWorkerURLRequestJob::OnBeforeNetworkStart(net::URLRequest* request,
163 bool* defer) {
164 NOTREACHED();
165 }
166
OnResponseStarted(net::URLRequest * request)167 void ServiceWorkerURLRequestJob::OnResponseStarted(net::URLRequest* request) {
168 // TODO(falken): Add Content-Length, Content-Type if they were not provided in
169 // the ServiceWorkerResponse.
170 response_time_ = base::Time::Now();
171 CommitResponseHeader();
172 }
173
OnReadCompleted(net::URLRequest * request,int bytes_read)174 void ServiceWorkerURLRequestJob::OnReadCompleted(net::URLRequest* request,
175 int bytes_read) {
176 SetStatus(request->status());
177 if (!request->status().is_success()) {
178 NotifyDone(request->status());
179 return;
180 }
181 NotifyReadComplete(bytes_read);
182 if (bytes_read == 0)
183 NotifyDone(request->status());
184 }
185
http_info() const186 const net::HttpResponseInfo* ServiceWorkerURLRequestJob::http_info() const {
187 if (!http_response_info_)
188 return NULL;
189 if (range_response_info_)
190 return range_response_info_.get();
191 return http_response_info_.get();
192 }
193
GetExtraResponseInfo(bool * was_fetched_via_service_worker,GURL * original_url_via_service_worker,base::TimeTicks * fetch_start_time,base::TimeTicks * fetch_ready_time,base::TimeTicks * fetch_end_time) const194 void ServiceWorkerURLRequestJob::GetExtraResponseInfo(
195 bool* was_fetched_via_service_worker,
196 GURL* original_url_via_service_worker,
197 base::TimeTicks* fetch_start_time,
198 base::TimeTicks* fetch_ready_time,
199 base::TimeTicks* fetch_end_time) const {
200 if (response_type_ != FORWARD_TO_SERVICE_WORKER) {
201 *was_fetched_via_service_worker = false;
202 *original_url_via_service_worker = GURL();
203 return;
204 }
205 *was_fetched_via_service_worker = true;
206 *original_url_via_service_worker = response_url_;
207 *fetch_start_time = fetch_start_time_;
208 *fetch_ready_time = fetch_ready_time_;
209 *fetch_end_time = fetch_end_time_;
210 }
211
212
~ServiceWorkerURLRequestJob()213 ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() {
214 }
215
MaybeStartRequest()216 void ServiceWorkerURLRequestJob::MaybeStartRequest() {
217 if (is_started_ && response_type_ != NOT_DETERMINED) {
218 // Start asynchronously.
219 base::MessageLoop::current()->PostTask(
220 FROM_HERE,
221 base::Bind(&ServiceWorkerURLRequestJob::StartRequest,
222 weak_factory_.GetWeakPtr()));
223 }
224 }
225
StartRequest()226 void ServiceWorkerURLRequestJob::StartRequest() {
227 switch (response_type_) {
228 case NOT_DETERMINED:
229 NOTREACHED();
230 return;
231
232 case FALLBACK_TO_NETWORK:
233 // Restart the request to create a new job. Our request handler will
234 // return NULL, and the default job (which will hit network) should be
235 // created.
236 NotifyRestartRequired();
237 return;
238
239 case FORWARD_TO_SERVICE_WORKER:
240 DCHECK(provider_host_ && provider_host_->active_version());
241 DCHECK(!fetch_dispatcher_);
242 // Send a fetch event to the ServiceWorker associated to the
243 // provider_host.
244 fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher(
245 CreateFetchRequest(),
246 provider_host_->active_version(),
247 base::Bind(&ServiceWorkerURLRequestJob::DidPrepareFetchEvent,
248 weak_factory_.GetWeakPtr()),
249 base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent,
250 weak_factory_.GetWeakPtr())));
251 fetch_start_time_ = base::TimeTicks::Now();
252 load_timing_info_.send_start = fetch_start_time_;
253 fetch_dispatcher_->Run();
254 return;
255 }
256
257 NOTREACHED();
258 }
259
260 scoped_ptr<ServiceWorkerFetchRequest>
CreateFetchRequest()261 ServiceWorkerURLRequestJob::CreateFetchRequest() {
262 std::string blob_uuid;
263 uint64 blob_size = 0;
264 CreateRequestBodyBlob(&blob_uuid, &blob_size);
265 scoped_ptr<ServiceWorkerFetchRequest> request(
266 new ServiceWorkerFetchRequest());
267
268 request->url = request_->url();
269 request->method = request_->method();
270 const net::HttpRequestHeaders& headers = request_->extra_request_headers();
271 for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();)
272 request->headers[it.name()] = it.value();
273 request->blob_uuid = blob_uuid;
274 request->blob_size = blob_size;
275 request->referrer = GURL(request_->referrer());
276 const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_);
277 if (info) {
278 request->is_reload = ui::PageTransitionCoreTypeIs(
279 info->GetPageTransition(), ui::PAGE_TRANSITION_RELOAD);
280 }
281 return request.Pass();
282 }
283
CreateRequestBodyBlob(std::string * blob_uuid,uint64 * blob_size)284 bool ServiceWorkerURLRequestJob::CreateRequestBodyBlob(std::string* blob_uuid,
285 uint64* blob_size) {
286 if (!body_.get() || !blob_storage_context_)
287 return false;
288
289 std::vector<const ResourceRequestBody::Element*> resolved_elements;
290 for (size_t i = 0; i < body_->elements()->size(); ++i) {
291 const ResourceRequestBody::Element& element = (*body_->elements())[i];
292 if (element.type() != ResourceRequestBody::Element::TYPE_BLOB) {
293 resolved_elements.push_back(&element);
294 continue;
295 }
296 scoped_ptr<storage::BlobDataHandle> handle =
297 blob_storage_context_->GetBlobDataFromUUID(element.blob_uuid());
298 if (handle->data()->items().empty())
299 continue;
300 for (size_t i = 0; i < handle->data()->items().size(); ++i) {
301 const storage::BlobData::Item& item = handle->data()->items().at(i);
302 DCHECK_NE(storage::BlobData::Item::TYPE_BLOB, item.type());
303 resolved_elements.push_back(&item);
304 }
305 }
306
307 const std::string uuid(base::GenerateGUID());
308 uint64 total_size = 0;
309 scoped_refptr<storage::BlobData> blob_data = new storage::BlobData(uuid);
310 for (size_t i = 0; i < resolved_elements.size(); ++i) {
311 const ResourceRequestBody::Element& element = *resolved_elements[i];
312 if (total_size != kuint64max && element.length() != kuint64max)
313 total_size += element.length();
314 else
315 total_size = kuint64max;
316 switch (element.type()) {
317 case ResourceRequestBody::Element::TYPE_BYTES:
318 blob_data->AppendData(element.bytes(), element.length());
319 break;
320 case ResourceRequestBody::Element::TYPE_FILE:
321 blob_data->AppendFile(element.path(),
322 element.offset(),
323 element.length(),
324 element.expected_modification_time());
325 break;
326 case ResourceRequestBody::Element::TYPE_BLOB:
327 // Blob elements should be resolved beforehand.
328 NOTREACHED();
329 break;
330 case ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM:
331 blob_data->AppendFileSystemFile(element.filesystem_url(),
332 element.offset(),
333 element.length(),
334 element.expected_modification_time());
335 break;
336 default:
337 NOTIMPLEMENTED();
338 }
339 }
340
341 request_body_blob_data_handle_ =
342 blob_storage_context_->AddFinishedBlob(blob_data.get());
343 *blob_uuid = uuid;
344 *blob_size = total_size;
345 return true;
346 }
347
DidPrepareFetchEvent()348 void ServiceWorkerURLRequestJob::DidPrepareFetchEvent() {
349 fetch_ready_time_ = base::TimeTicks::Now();
350 }
351
DidDispatchFetchEvent(ServiceWorkerStatusCode status,ServiceWorkerFetchEventResult fetch_result,const ServiceWorkerResponse & response)352 void ServiceWorkerURLRequestJob::DidDispatchFetchEvent(
353 ServiceWorkerStatusCode status,
354 ServiceWorkerFetchEventResult fetch_result,
355 const ServiceWorkerResponse& response) {
356 fetch_dispatcher_.reset();
357
358 // Check if we're not orphaned.
359 if (!request())
360 return;
361
362 if (status != SERVICE_WORKER_OK) {
363 // Dispatching event has been failed, falling back to the network.
364 // (Tentative behavior described on github)
365 // TODO(kinuko): consider returning error if we've come here because
366 // unexpected worker termination etc (so that we could fix bugs).
367 // TODO(kinuko): Would be nice to log the error case.
368 response_type_ = FALLBACK_TO_NETWORK;
369 NotifyRestartRequired();
370 return;
371 }
372
373 if (fetch_result == SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK) {
374 // Change the response type and restart the request to fallback to
375 // the network.
376 response_type_ = FALLBACK_TO_NETWORK;
377 NotifyRestartRequired();
378 return;
379 }
380
381 // We should have a response now.
382 DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, fetch_result);
383
384 // Treat a response whose status is 0 as a Network Error.
385 if (response.status_code == 0) {
386 NotifyDone(
387 net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED));
388 return;
389 }
390
391 fetch_end_time_ = base::TimeTicks::Now();
392 load_timing_info_.send_end = fetch_end_time_;
393
394 // Set up a request for reading the blob.
395 if (!response.blob_uuid.empty() && blob_storage_context_) {
396 scoped_ptr<storage::BlobDataHandle> blob_data_handle =
397 blob_storage_context_->GetBlobDataFromUUID(response.blob_uuid);
398 if (!blob_data_handle) {
399 // The renderer gave us a bad blob UUID.
400 DeliverErrorResponse();
401 return;
402 }
403 blob_request_ = storage::BlobProtocolHandler::CreateBlobRequest(
404 blob_data_handle.Pass(), request()->context(), this);
405 blob_request_->Start();
406 }
407
408 response_url_ = response.url;
409 CreateResponseHeader(
410 response.status_code, response.status_text, response.headers);
411 load_timing_info_.receive_headers_end = base::TimeTicks::Now();
412 if (!blob_request_)
413 CommitResponseHeader();
414 }
415
CreateResponseHeader(int status_code,const std::string & status_text,const ServiceWorkerHeaderMap & headers)416 void ServiceWorkerURLRequestJob::CreateResponseHeader(
417 int status_code,
418 const std::string& status_text,
419 const ServiceWorkerHeaderMap& headers) {
420 // TODO(kinuko): If the response has an identifier to on-disk cache entry,
421 // pull response header from the disk.
422 std::string status_line(
423 base::StringPrintf("HTTP/1.1 %d %s", status_code, status_text.c_str()));
424 status_line.push_back('\0');
425 http_response_headers_ = new net::HttpResponseHeaders(status_line);
426 for (ServiceWorkerHeaderMap::const_iterator it = headers.begin();
427 it != headers.end();
428 ++it) {
429 std::string header;
430 header.reserve(it->first.size() + 2 + it->second.size());
431 header.append(it->first);
432 header.append(": ");
433 header.append(it->second);
434 http_response_headers_->AddHeader(header);
435 }
436 }
437
CommitResponseHeader()438 void ServiceWorkerURLRequestJob::CommitResponseHeader() {
439 http_response_info_.reset(new net::HttpResponseInfo());
440 http_response_info_->headers.swap(http_response_headers_);
441 NotifyHeadersComplete();
442 }
443
DeliverErrorResponse()444 void ServiceWorkerURLRequestJob::DeliverErrorResponse() {
445 // TODO(falken): Print an error to the console of the ServiceWorker and of
446 // the requesting page.
447 CreateResponseHeader(
448 500, "Service Worker Response Error", ServiceWorkerHeaderMap());
449 CommitResponseHeader();
450 }
451
452 } // namespace content
453