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