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_write_to_cache_job.h"
6
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"
19
20namespace content {
21
22ServiceWorkerWriteToCacheJob::ServiceWorkerWriteToCacheJob(
23    net::URLRequest* request,
24    net::NetworkDelegate* network_delegate,
25    ResourceType resource_type,
26    base::WeakPtr<ServiceWorkerContextCore> context,
27    ServiceWorkerVersion* version,
28    int extra_load_flags,
29    int64 response_id)
30    : net::URLRequestJob(request, network_delegate),
31      resource_type_(resource_type),
32      context_(context),
33      url_(request->url()),
34      response_id_(response_id),
35      version_(version),
36      has_been_killed_(false),
37      did_notify_started_(false),
38      did_notify_finished_(false),
39      weak_factory_(this) {
40  InitNetRequest(extra_load_flags);
41}
42
43ServiceWorkerWriteToCacheJob::~ServiceWorkerWriteToCacheJob() {
44  DCHECK_EQ(did_notify_started_, did_notify_finished_);
45}
46
47void ServiceWorkerWriteToCacheJob::Start() {
48  TRACE_EVENT_ASYNC_BEGIN1("ServiceWorker",
49                           "ServiceWorkerWriteToCacheJob::ExecutingJob",
50                           this,
51                           "URL", request_->url().spec());
52  if (!context_) {
53    NotifyStartError(net::URLRequestStatus(
54        net::URLRequestStatus::FAILED, net::ERR_FAILED));
55    return;
56  }
57  version_->script_cache_map()->NotifyStartedCaching(
58      url_, response_id_);
59  did_notify_started_ = true;
60  StartNetRequest();
61}
62
63void ServiceWorkerWriteToCacheJob::Kill() {
64  if (has_been_killed_)
65    return;
66  weak_factory_.InvalidateWeakPtrs();
67  has_been_killed_ = true;
68  net_request_.reset();
69  if (did_notify_started_ && !did_notify_finished_) {
70    version_->script_cache_map()->NotifyFinishedCaching(
71        url_,
72        net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_ABORTED));
73    did_notify_finished_ = true;
74  }
75  writer_.reset();
76  context_.reset();
77  net::URLRequestJob::Kill();
78}
79
80net::LoadState ServiceWorkerWriteToCacheJob::GetLoadState() const {
81  if (writer_ && writer_->IsWritePending())
82    return net::LOAD_STATE_WAITING_FOR_APPCACHE;
83  if (net_request_)
84    return net_request_->GetLoadState().state;
85  return net::LOAD_STATE_IDLE;
86}
87
88bool ServiceWorkerWriteToCacheJob::GetCharset(std::string* charset) {
89  if (!http_info())
90    return false;
91  return http_info()->headers->GetCharset(charset);
92}
93
94bool ServiceWorkerWriteToCacheJob::GetMimeType(std::string* mime_type) const {
95  if (!http_info())
96    return false;
97  return http_info()->headers->GetMimeType(mime_type);
98}
99
100void ServiceWorkerWriteToCacheJob::GetResponseInfo(
101    net::HttpResponseInfo* info) {
102  if (!http_info())
103    return;
104  *info = *http_info();
105}
106
107int ServiceWorkerWriteToCacheJob::GetResponseCode() const {
108  if (!http_info())
109    return -1;
110  return http_info()->headers->response_code();
111}
112
113void ServiceWorkerWriteToCacheJob::SetExtraRequestHeaders(
114      const net::HttpRequestHeaders& headers) {
115  std::string value;
116  DCHECK(!headers.GetHeader(net::HttpRequestHeaders::kRange, &value));
117  net_request_->SetExtraRequestHeaders(headers);
118}
119
120bool ServiceWorkerWriteToCacheJob::ReadRawData(
121    net::IOBuffer* buf,
122    int buf_size,
123    int *bytes_read) {
124  net::URLRequestStatus status = ReadNetData(buf, buf_size, bytes_read);
125  SetStatus(status);
126  if (status.is_io_pending())
127    return false;
128
129  // No more data to process, the job is complete.
130  io_buffer_ = NULL;
131  version_->script_cache_map()->NotifyFinishedCaching(
132      url_, net::URLRequestStatus());
133  did_notify_finished_ = true;
134  return status.is_success();
135}
136
137const net::HttpResponseInfo* ServiceWorkerWriteToCacheJob::http_info() const {
138  return http_info_.get();
139}
140
141void ServiceWorkerWriteToCacheJob::InitNetRequest(
142    int extra_load_flags) {
143  DCHECK(request());
144  net_request_ = request()->context()->CreateRequest(
145      request()->url(),
146      request()->priority(),
147      this,
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);
154
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);
159  }
160}
161
162void ServiceWorkerWriteToCacheJob::StartNetRequest() {
163  TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker",
164                               "ServiceWorkerWriteToCacheJob::ExecutingJob",
165                               this,
166                               "NetRequest");
167  net_request_->Start();  // We'll continue in OnResponseStarted.
168}
169
170net::URLRequestStatus ServiceWorkerWriteToCacheJob::ReadNetData(
171    net::IOBuffer* buf,
172    int buf_size,
173    int *bytes_read) {
174  DCHECK_GT(buf_size, 0);
175  DCHECK(bytes_read);
176
177  *bytes_read = 0;
178  io_buffer_ = buf;
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();
185  }
186
187  if (net_bytes_read != 0) {
188    WriteDataToCache(net_bytes_read);
189    DCHECK(GetStatus().is_io_pending());
190    return GetStatus();
191  }
192
193  DCHECK(net_request_->status().is_success());
194  return net_request_->status();
195}
196
197void ServiceWorkerWriteToCacheJob::WriteHeadersToCache() {
198  if (!context_) {
199    AsyncNotifyDoneHelper(net::URLRequestStatus(
200        net::URLRequestStatus::FAILED, net::ERR_FAILED));
201    return;
202  }
203  TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker",
204                               "ServiceWorkerWriteToCacheJob::ExecutingJob",
205                               this,
206                               "WriteHeadersToCache");
207  writer_ = context_->storage()->CreateResponseWriter(response_id_);
208  info_buffer_ = new HttpResponseInfoIOBuffer(
209      new net::HttpResponseInfo(net_request_->response_info()));
210  writer_->WriteInfo(
211      info_buffer_.get(),
212      base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete,
213                 weak_factory_.GetWeakPtr()));
214  SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
215}
216
217void ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete(int result) {
218  SetStatus(net::URLRequestStatus());  // Clear the IO_PENDING status
219  if (result < 0) {
220    ServiceWorkerMetrics::CountWriteResponseResult(
221        ServiceWorkerMetrics::WRITE_HEADERS_ERROR);
222    AsyncNotifyDoneHelper(net::URLRequestStatus(
223        net::URLRequestStatus::FAILED, result));
224    return;
225  }
226  http_info_.reset(info_buffer_->http_info.release());
227  info_buffer_ = NULL;
228  TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker",
229                               "ServiceWorkerWriteToCacheJob::ExecutingJob",
230                               this,
231                               "WriteHeadersToCacheCompleted");
232  NotifyHeadersComplete();
233}
234
235void ServiceWorkerWriteToCacheJob::WriteDataToCache(int amount_to_write) {
236  DCHECK_NE(0, amount_to_write);
237  TRACE_EVENT_ASYNC_STEP_INTO1("ServiceWorker",
238                               "ServiceWorkerWriteToCacheJob::ExecutingJob",
239                               this,
240                               "WriteDataToCache",
241                               "Amount to write", amount_to_write);
242  SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
243  writer_->WriteData(
244      io_buffer_.get(),
245      amount_to_write,
246      base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteDataComplete,
247                 weak_factory_.GetWeakPtr()));
248}
249
250void ServiceWorkerWriteToCacheJob::OnWriteDataComplete(int result) {
251  DCHECK_NE(0, result);
252  io_buffer_ = NULL;
253  if (!context_) {
254    AsyncNotifyDoneHelper(net::URLRequestStatus(
255        net::URLRequestStatus::FAILED, net::ERR_FAILED));
256    return;
257  }
258  if (result < 0) {
259    ServiceWorkerMetrics::CountWriteResponseResult(
260        ServiceWorkerMetrics::WRITE_DATA_ERROR);
261    AsyncNotifyDoneHelper(net::URLRequestStatus(
262        net::URLRequestStatus::FAILED, result));
263    return;
264  }
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",
271                         this);
272}
273
274void 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));
284}
285
286void 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));
295}
296
297void 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));
307}
308
309void ServiceWorkerWriteToCacheJob:: OnSSLCertificateError(
310    net::URLRequest* request,
311    const net::SSLInfo& ssl_info,
312    bool fatal) {
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));
320}
321
322void ServiceWorkerWriteToCacheJob::OnBeforeNetworkStart(
323    net::URLRequest* request,
324    bool* defer) {
325  DCHECK_EQ(net_request_, request);
326  TRACE_EVENT0("ServiceWorker",
327               "ServiceWorkerWriteToCacheJob::OnBeforeNetworkStart");
328  NotifyBeforeNetworkStart(defer);
329}
330
331void ServiceWorkerWriteToCacheJob::OnResponseStarted(
332    net::URLRequest* request) {
333  DCHECK_EQ(net_request_, request);
334  if (!request->status().is_success()) {
335    AsyncNotifyDoneHelper(request->status());
336    return;
337  }
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?
343    return;
344  }
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));
354      return;
355    }
356  }
357  WriteHeadersToCache();
358}
359
360void ServiceWorkerWriteToCacheJob::OnReadCompleted(
361    net::URLRequest* request,
362    int bytes_read) {
363  DCHECK_EQ(net_request_, request);
364  if (!request->status().is_success()) {
365    AsyncNotifyDoneHelper(request->status());
366    return;
367  }
368  if (bytes_read > 0) {
369    WriteDataToCache(bytes_read);
370    return;
371  }
372  TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker",
373                               "ServiceWorkerWriteToCacheJob::ExecutingJob",
374                               this,
375                               "WriteHeadersToCache");
376  // We're done with all.
377  AsyncNotifyDoneHelper(request->status());
378  return;
379}
380
381void 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;
386  SetStatus(status);
387  NotifyDone(status);
388}
389
390}  // namespace content
391