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.
5 #include "content/browser/service_worker/service_worker_write_to_cache_job.h"
7 #include "base/debug/trace_event.h"
8 #include "content/browser/service_worker/service_worker_context_core.h"
9 #include "content/browser/service_worker/service_worker_disk_cache.h"
10 #include "content/browser/service_worker/service_worker_metrics.h"
11 #include "net/base/io_buffer.h"
12 #include "net/base/net_errors.h"
13 #include "net/http/http_request_headers.h"
14 #include "net/http/http_response_headers.h"
15 #include "net/http/http_util.h"
16 #include "net/url_request/url_request.h"
17 #include "net/url_request/url_request_context.h"
18 #include "net/url_request/url_request_status.h"
22 ServiceWorkerWriteToCacheJob::ServiceWorkerWriteToCacheJob(
23 net::URLRequest* request,
24 net::NetworkDelegate* network_delegate,
25 ResourceType resource_type,
26 base::WeakPtr<ServiceWorkerContextCore> context,
27 ServiceWorkerVersion* version,
30 : net::URLRequestJob(request, network_delegate),
31 resource_type_(resource_type),
34 response_id_(response_id),
36 has_been_killed_(false),
37 did_notify_started_(false),
38 did_notify_finished_(false),
40 InitNetRequest(extra_load_flags);
43 ServiceWorkerWriteToCacheJob::~ServiceWorkerWriteToCacheJob() {
44 DCHECK_EQ(did_notify_started_, did_notify_finished_);
47 void ServiceWorkerWriteToCacheJob::Start() {
48 TRACE_EVENT_ASYNC_BEGIN1("ServiceWorker",
49 "ServiceWorkerWriteToCacheJob::ExecutingJob",
51 "URL", request_->url().spec());
53 NotifyStartError(net::URLRequestStatus(
54 net::URLRequestStatus::FAILED, net::ERR_FAILED));
57 version_->script_cache_map()->NotifyStartedCaching(
59 did_notify_started_ = true;
63 void ServiceWorkerWriteToCacheJob::Kill() {
66 weak_factory_.InvalidateWeakPtrs();
67 has_been_killed_ = true;
69 if (did_notify_started_ && !did_notify_finished_) {
70 version_->script_cache_map()->NotifyFinishedCaching(
72 net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_ABORTED));
73 did_notify_finished_ = true;
77 net::URLRequestJob::Kill();
80 net::LoadState ServiceWorkerWriteToCacheJob::GetLoadState() const {
81 if (writer_ && writer_->IsWritePending())
82 return net::LOAD_STATE_WAITING_FOR_APPCACHE;
84 return net_request_->GetLoadState().state;
85 return net::LOAD_STATE_IDLE;
88 bool ServiceWorkerWriteToCacheJob::GetCharset(std::string* charset) {
91 return http_info()->headers->GetCharset(charset);
94 bool ServiceWorkerWriteToCacheJob::GetMimeType(std::string* mime_type) const {
97 return http_info()->headers->GetMimeType(mime_type);
100 void ServiceWorkerWriteToCacheJob::GetResponseInfo(
101 net::HttpResponseInfo* info) {
104 *info = *http_info();
107 int ServiceWorkerWriteToCacheJob::GetResponseCode() const {
110 return http_info()->headers->response_code();
113 void ServiceWorkerWriteToCacheJob::SetExtraRequestHeaders(
114 const net::HttpRequestHeaders& headers) {
116 DCHECK(!headers.GetHeader(net::HttpRequestHeaders::kRange, &value));
117 net_request_->SetExtraRequestHeaders(headers);
120 bool ServiceWorkerWriteToCacheJob::ReadRawData(
124 net::URLRequestStatus status = ReadNetData(buf, buf_size, bytes_read);
126 if (status.is_io_pending())
129 // No more data to process, the job is complete.
131 version_->script_cache_map()->NotifyFinishedCaching(
132 url_, net::URLRequestStatus());
133 did_notify_finished_ = true;
134 return status.is_success();
137 const net::HttpResponseInfo* ServiceWorkerWriteToCacheJob::http_info() const {
138 return http_info_.get();
141 void ServiceWorkerWriteToCacheJob::InitNetRequest(
142 int extra_load_flags) {
144 net_request_ = request()->context()->CreateRequest(
146 request()->priority(),
148 this->GetCookieStore());
149 net_request_->set_first_party_for_cookies(
150 request()->first_party_for_cookies());
151 net_request_->SetReferrer(request()->referrer());
152 if (extra_load_flags)
153 net_request_->SetLoadFlags(net_request_->load_flags() | extra_load_flags);
155 if (resource_type_ == RESOURCE_TYPE_SERVICE_WORKER) {
156 // This will get copied into net_request_ when URLRequest::StartJob calls
157 // ServiceWorkerWriteToCacheJob::SetExtraRequestHeaders.
158 request()->SetExtraRequestHeaderByName("Service-Worker", "script", true);
162 void ServiceWorkerWriteToCacheJob::StartNetRequest() {
163 TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker",
164 "ServiceWorkerWriteToCacheJob::ExecutingJob",
167 net_request_->Start(); // We'll continue in OnResponseStarted.
170 net::URLRequestStatus ServiceWorkerWriteToCacheJob::ReadNetData(
174 DCHECK_GT(buf_size, 0);
179 int net_bytes_read = 0;
180 if (!net_request_->Read(buf, buf_size, &net_bytes_read)) {
181 if (net_request_->status().is_io_pending())
182 return net_request_->status();
183 DCHECK(!net_request_->status().is_success());
184 return net_request_->status();
187 if (net_bytes_read != 0) {
188 WriteDataToCache(net_bytes_read);
189 DCHECK(GetStatus().is_io_pending());
193 DCHECK(net_request_->status().is_success());
194 return net_request_->status();
197 void ServiceWorkerWriteToCacheJob::WriteHeadersToCache() {
199 AsyncNotifyDoneHelper(net::URLRequestStatus(
200 net::URLRequestStatus::FAILED, net::ERR_FAILED));
203 TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker",
204 "ServiceWorkerWriteToCacheJob::ExecutingJob",
206 "WriteHeadersToCache");
207 writer_ = context_->storage()->CreateResponseWriter(response_id_);
208 info_buffer_ = new HttpResponseInfoIOBuffer(
209 new net::HttpResponseInfo(net_request_->response_info()));
212 base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete,
213 weak_factory_.GetWeakPtr()));
214 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
217 void ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete(int result) {
218 SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
220 ServiceWorkerMetrics::CountWriteResponseResult(
221 ServiceWorkerMetrics::WRITE_HEADERS_ERROR);
222 AsyncNotifyDoneHelper(net::URLRequestStatus(
223 net::URLRequestStatus::FAILED, result));
226 http_info_.reset(info_buffer_->http_info.release());
228 TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker",
229 "ServiceWorkerWriteToCacheJob::ExecutingJob",
231 "WriteHeadersToCacheCompleted");
232 NotifyHeadersComplete();
235 void ServiceWorkerWriteToCacheJob::WriteDataToCache(int amount_to_write) {
236 DCHECK_NE(0, amount_to_write);
237 TRACE_EVENT_ASYNC_STEP_INTO1("ServiceWorker",
238 "ServiceWorkerWriteToCacheJob::ExecutingJob",
241 "Amount to write", amount_to_write);
242 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
246 base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteDataComplete,
247 weak_factory_.GetWeakPtr()));
250 void ServiceWorkerWriteToCacheJob::OnWriteDataComplete(int result) {
251 DCHECK_NE(0, result);
254 AsyncNotifyDoneHelper(net::URLRequestStatus(
255 net::URLRequestStatus::FAILED, net::ERR_FAILED));
259 ServiceWorkerMetrics::CountWriteResponseResult(
260 ServiceWorkerMetrics::WRITE_DATA_ERROR);
261 AsyncNotifyDoneHelper(net::URLRequestStatus(
262 net::URLRequestStatus::FAILED, result));
265 ServiceWorkerMetrics::CountWriteResponseResult(
266 ServiceWorkerMetrics::WRITE_OK);
267 SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
268 NotifyReadComplete(result);
269 TRACE_EVENT_ASYNC_END0("ServiceWorker",
270 "ServiceWorkerWriteToCacheJob::ExecutingJob",
274 void ServiceWorkerWriteToCacheJob::OnReceivedRedirect(
275 net::URLRequest* request,
276 const net::RedirectInfo& redirect_info,
277 bool* defer_redirect) {
278 DCHECK_EQ(net_request_, request);
279 TRACE_EVENT0("ServiceWorker",
280 "ServiceWorkerWriteToCacheJob::OnReceivedRedirect");
281 // Script resources can't redirect.
282 AsyncNotifyDoneHelper(net::URLRequestStatus(
283 net::URLRequestStatus::FAILED, net::ERR_UNSAFE_REDIRECT));
286 void ServiceWorkerWriteToCacheJob::OnAuthRequired(
287 net::URLRequest* request,
288 net::AuthChallengeInfo* auth_info) {
289 DCHECK_EQ(net_request_, request);
290 TRACE_EVENT0("ServiceWorker",
291 "ServiceWorkerWriteToCacheJob::OnAuthRequired");
292 // TODO(michaeln): Pass this thru to our jobs client.
293 AsyncNotifyDoneHelper(net::URLRequestStatus(
294 net::URLRequestStatus::FAILED, net::ERR_FAILED));
297 void ServiceWorkerWriteToCacheJob::OnCertificateRequested(
298 net::URLRequest* request,
299 net::SSLCertRequestInfo* cert_request_info) {
300 DCHECK_EQ(net_request_, request);
301 TRACE_EVENT0("ServiceWorker",
302 "ServiceWorkerWriteToCacheJob::OnCertificateRequested");
303 // TODO(michaeln): Pass this thru to our jobs client.
304 // see NotifyCertificateRequested.
305 AsyncNotifyDoneHelper(net::URLRequestStatus(
306 net::URLRequestStatus::FAILED, net::ERR_FAILED));
309 void ServiceWorkerWriteToCacheJob:: OnSSLCertificateError(
310 net::URLRequest* request,
311 const net::SSLInfo& ssl_info,
313 DCHECK_EQ(net_request_, request);
314 TRACE_EVENT0("ServiceWorker",
315 "ServiceWorkerWriteToCacheJob::OnSSLCertificateError");
316 // TODO(michaeln): Pass this thru to our jobs client,
317 // see NotifySSLCertificateError.
318 AsyncNotifyDoneHelper(net::URLRequestStatus(
319 net::URLRequestStatus::FAILED, net::ERR_FAILED));
322 void ServiceWorkerWriteToCacheJob::OnBeforeNetworkStart(
323 net::URLRequest* request,
325 DCHECK_EQ(net_request_, request);
326 TRACE_EVENT0("ServiceWorker",
327 "ServiceWorkerWriteToCacheJob::OnBeforeNetworkStart");
328 NotifyBeforeNetworkStart(defer);
331 void ServiceWorkerWriteToCacheJob::OnResponseStarted(
332 net::URLRequest* request) {
333 DCHECK_EQ(net_request_, request);
334 if (!request->status().is_success()) {
335 AsyncNotifyDoneHelper(request->status());
338 if (request->GetResponseCode() / 100 != 2) {
339 AsyncNotifyDoneHelper(net::URLRequestStatus(
340 net::URLRequestStatus::FAILED, net::ERR_FAILED));
341 // TODO(michaeln): Instead of error'ing immediately, send the net
342 // response to our consumer, just don't cache it?
345 // To prevent most user-uploaded content from being used as a serviceworker.
346 if (version_->script_url() == url_) {
347 std::string mime_type;
348 request->GetMimeType(&mime_type);
349 if (mime_type != "application/x-javascript" &&
350 mime_type != "text/javascript" &&
351 mime_type != "application/javascript") {
352 AsyncNotifyDoneHelper(net::URLRequestStatus(
353 net::URLRequestStatus::FAILED, net::ERR_INSECURE_RESPONSE));
357 WriteHeadersToCache();
360 void ServiceWorkerWriteToCacheJob::OnReadCompleted(
361 net::URLRequest* request,
363 DCHECK_EQ(net_request_, request);
364 if (!request->status().is_success()) {
365 AsyncNotifyDoneHelper(request->status());
368 if (bytes_read > 0) {
369 WriteDataToCache(bytes_read);
372 TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker",
373 "ServiceWorkerWriteToCacheJob::ExecutingJob",
375 "WriteHeadersToCache");
376 // We're done with all.
377 AsyncNotifyDoneHelper(request->status());
381 void ServiceWorkerWriteToCacheJob::AsyncNotifyDoneHelper(
382 const net::URLRequestStatus& status) {
383 DCHECK(!status.is_io_pending());
384 version_->script_cache_map()->NotifyFinishedCaching(url_, status);
385 did_notify_finished_ = true;
390 } // namespace content