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// An implementation of WebURLLoader in terms of ResourceLoaderBridge.
6
7#include "webkit/glue/weburlloader_impl.h"
8
9#include "base/file_path.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/message_loop.h"
12#include "base/process_util.h"
13#include "base/string_util.h"
14#include "base/time.h"
15#include "net/base/data_url.h"
16#include "net/base/load_flags.h"
17#include "net/base/mime_util.h"
18#include "net/base/net_errors.h"
19#include "net/base/net_util.h"
20#include "net/http/http_response_headers.h"
21#include "third_party/WebKit/Source/WebKit/chromium/public/WebHTTPHeaderVisitor.h"
22#include "third_party/WebKit/Source/WebKit/chromium/public/WebHTTPLoadInfo.h"
23#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityPolicy.h"
24#include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h"
25#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h"
26#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoadTiming.h"
27#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderClient.h"
28#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h"
29#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h"
30#include "webkit/glue/ftp_directory_listing_response_delegate.h"
31#include "webkit/glue/multipart_response_delegate.h"
32#include "webkit/glue/resource_loader_bridge.h"
33#include "webkit/glue/site_isolation_metrics.h"
34#include "webkit/glue/webkit_glue.h"
35
36using base::Time;
37using base::TimeDelta;
38using WebKit::WebData;
39using WebKit::WebHTTPBody;
40using WebKit::WebHTTPHeaderVisitor;
41using WebKit::WebHTTPLoadInfo;
42using WebKit::WebSecurityPolicy;
43using WebKit::WebString;
44using WebKit::WebURL;
45using WebKit::WebURLError;
46using WebKit::WebURLLoadTiming;
47using WebKit::WebURLLoader;
48using WebKit::WebURLLoaderClient;
49using WebKit::WebURLRequest;
50using WebKit::WebURLResponse;
51
52namespace webkit_glue {
53
54// Utilities ------------------------------------------------------------------
55
56namespace {
57
58class HeaderFlattener : public WebHTTPHeaderVisitor {
59 public:
60  explicit HeaderFlattener(int load_flags)
61      : load_flags_(load_flags),
62        has_accept_header_(false) {
63  }
64
65  virtual void visitHeader(const WebString& name, const WebString& value) {
66    // TODO(darin): is UTF-8 really correct here?  It is if the strings are
67    // already ASCII (i.e., if they are already escaped properly).
68    const std::string& name_utf8 = name.utf8();
69    const std::string& value_utf8 = value.utf8();
70
71    // Skip over referrer headers found in the header map because we already
72    // pulled it out as a separate parameter.
73    if (LowerCaseEqualsASCII(name_utf8, "referer"))
74      return;
75
76    // Skip over "Cache-Control: max-age=0" header if the corresponding
77    // load flag is already specified. FrameLoader sets both the flag and
78    // the extra header -- the extra header is redundant since our network
79    // implementation will add the necessary headers based on load flags.
80    // See http://code.google.com/p/chromium/issues/detail?id=3434.
81    if ((load_flags_ & net::LOAD_VALIDATE_CACHE) &&
82        LowerCaseEqualsASCII(name_utf8, "cache-control") &&
83        LowerCaseEqualsASCII(value_utf8, "max-age=0"))
84      return;
85
86    if (LowerCaseEqualsASCII(name_utf8, "accept"))
87      has_accept_header_ = true;
88
89    if (!buffer_.empty())
90      buffer_.append("\r\n");
91    buffer_.append(name_utf8 + ": " + value_utf8);
92  }
93
94  const std::string& GetBuffer() {
95    // In some cases, WebKit doesn't add an Accept header, but not having the
96    // header confuses some web servers.  See bug 808613.
97    if (!has_accept_header_) {
98      if (!buffer_.empty())
99        buffer_.append("\r\n");
100      buffer_.append("Accept: */*");
101      has_accept_header_ = true;
102    }
103    return buffer_;
104  }
105
106 private:
107  int load_flags_;
108  std::string buffer_;
109  bool has_accept_header_;
110};
111
112ResourceType::Type FromTargetType(WebURLRequest::TargetType type) {
113  switch (type) {
114    case WebURLRequest::TargetIsMainFrame:
115      return ResourceType::MAIN_FRAME;
116    case WebURLRequest::TargetIsSubframe:
117      return ResourceType::SUB_FRAME;
118    case WebURLRequest::TargetIsSubresource:
119      return ResourceType::SUB_RESOURCE;
120    case WebURLRequest::TargetIsStyleSheet:
121      return ResourceType::STYLESHEET;
122    case WebURLRequest::TargetIsScript:
123      return ResourceType::SCRIPT;
124    case WebURLRequest::TargetIsFontResource:
125      return ResourceType::FONT_RESOURCE;
126    case WebURLRequest::TargetIsImage:
127      return ResourceType::IMAGE;
128    case WebURLRequest::TargetIsObject:
129      return ResourceType::OBJECT;
130    case WebURLRequest::TargetIsMedia:
131      return ResourceType::MEDIA;
132    case WebURLRequest::TargetIsWorker:
133      return ResourceType::WORKER;
134    case WebURLRequest::TargetIsSharedWorker:
135      return ResourceType::SHARED_WORKER;
136    case WebURLRequest::TargetIsPrefetch:
137      return ResourceType::PREFETCH;
138    case WebURLRequest::TargetIsFavicon:
139      return ResourceType::FAVICON;
140    default:
141      NOTREACHED();
142      return ResourceType::SUB_RESOURCE;
143  }
144}
145
146// Extracts the information from a data: url.
147bool GetInfoFromDataURL(const GURL& url,
148                        ResourceResponseInfo* info,
149                        std::string* data,
150                        net::URLRequestStatus* status) {
151  std::string mime_type;
152  std::string charset;
153  if (net::DataURL::Parse(url, &mime_type, &charset, data)) {
154    *status = net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0);
155    info->request_time = Time::Now();
156    info->response_time = Time::Now();
157    info->headers = NULL;
158    info->mime_type.swap(mime_type);
159    info->charset.swap(charset);
160    info->security_info.clear();
161    info->content_length = -1;
162    info->encoded_data_length = 0;
163    info->load_timing.base_time = Time::Now();
164
165    return true;
166  }
167
168  *status = net::URLRequestStatus(net::URLRequestStatus::FAILED,
169                                  net::ERR_INVALID_URL);
170  return false;
171}
172
173typedef ResourceDevToolsInfo::HeadersVector HeadersVector;
174
175void PopulateURLResponse(
176    const GURL& url,
177    const ResourceResponseInfo& info,
178    WebURLResponse* response) {
179  response->setURL(url);
180  response->setResponseTime(info.response_time.ToDoubleT());
181  response->setMIMEType(WebString::fromUTF8(info.mime_type));
182  response->setTextEncodingName(WebString::fromUTF8(info.charset));
183  response->setExpectedContentLength(info.content_length);
184  response->setSecurityInfo(info.security_info);
185  response->setAppCacheID(info.appcache_id);
186  response->setAppCacheManifestURL(info.appcache_manifest_url);
187  response->setWasCached(!info.load_timing.base_time.is_null() &&
188      info.response_time < info.load_timing.base_time);
189  response->setWasFetchedViaSPDY(info.was_fetched_via_spdy);
190  response->setWasNpnNegotiated(info.was_npn_negotiated);
191  response->setWasAlternateProtocolAvailable(
192      info.was_alternate_protocol_available);
193  response->setWasFetchedViaProxy(info.was_fetched_via_proxy);
194  response->setRemoteIPAddress(
195      WebString::fromUTF8(info.socket_address.host()));
196  response->setRemotePort(info.socket_address.port());
197  response->setConnectionID(info.connection_id);
198  response->setConnectionReused(info.connection_reused);
199  response->setDownloadFilePath(FilePathToWebString(info.download_file_path));
200
201  const ResourceLoadTimingInfo& timing_info = info.load_timing;
202  if (!timing_info.base_time.is_null()) {
203    WebURLLoadTiming timing;
204    timing.initialize();
205    timing.setRequestTime(timing_info.base_time.ToDoubleT());
206    timing.setProxyStart(timing_info.proxy_start);
207    timing.setProxyEnd(timing_info.proxy_end);
208    timing.setDNSStart(timing_info.dns_start);
209    timing.setDNSEnd(timing_info.dns_end);
210    timing.setConnectStart(timing_info.connect_start);
211    timing.setConnectEnd(timing_info.connect_end);
212    timing.setSSLStart(timing_info.ssl_start);
213    timing.setSSLEnd(timing_info.ssl_end);
214    timing.setSendStart(timing_info.send_start);
215    timing.setSendEnd(timing_info.send_end);
216    timing.setReceiveHeadersEnd(timing_info.receive_headers_end);
217    response->setLoadTiming(timing);
218  }
219
220  if (info.devtools_info.get()) {
221    WebHTTPLoadInfo load_info;
222
223    load_info.setHTTPStatusCode(info.devtools_info->http_status_code);
224    load_info.setHTTPStatusText(WebString::fromUTF8(
225        info.devtools_info->http_status_text));
226    load_info.setEncodedDataLength(info.encoded_data_length);
227
228    const HeadersVector& request_headers = info.devtools_info->request_headers;
229    for (HeadersVector::const_iterator it = request_headers.begin();
230         it != request_headers.end(); ++it) {
231      load_info.addRequestHeader(WebString::fromUTF8(it->first),
232          WebString::fromUTF8(it->second));
233    }
234    const HeadersVector& response_headers =
235        info.devtools_info->response_headers;
236    for (HeadersVector::const_iterator it = response_headers.begin();
237         it != response_headers.end(); ++it) {
238      load_info.addResponseHeader(WebString::fromUTF8(it->first),
239          WebString::fromUTF8(it->second));
240    }
241    response->setHTTPLoadInfo(load_info);
242  }
243
244  const net::HttpResponseHeaders* headers = info.headers;
245  if (!headers)
246    return;
247
248  response->setHTTPStatusCode(headers->response_code());
249  response->setHTTPStatusText(WebString::fromUTF8(headers->GetStatusText()));
250
251  // TODO(darin): We should leverage HttpResponseHeaders for this, and this
252  // should be using the same code as ResourceDispatcherHost.
253  // TODO(jungshik): Figure out the actual value of the referrer charset and
254  // pass it to GetSuggestedFilename.
255  std::string value;
256  if (headers->EnumerateHeader(NULL, "content-disposition", &value)) {
257    response->setSuggestedFileName(
258        net::GetSuggestedFilename(url, value, "", string16()));
259  }
260
261  Time time_val;
262  if (headers->GetLastModifiedValue(&time_val))
263    response->setLastModifiedDate(time_val.ToDoubleT());
264
265  // Build up the header map.
266  void* iter = NULL;
267  std::string name;
268  while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
269    response->addHTTPHeaderField(WebString::fromUTF8(name),
270                                 WebString::fromUTF8(value));
271  }
272}
273
274}  // namespace
275
276// WebURLLoaderImpl::Context --------------------------------------------------
277
278// This inner class exists since the WebURLLoader may be deleted while inside a
279// call to WebURLLoaderClient.  The bridge requires its Peer to stay alive
280// until it receives OnCompletedRequest.
281class WebURLLoaderImpl::Context : public base::RefCounted<Context>,
282                                  public ResourceLoaderBridge::Peer {
283 public:
284  explicit Context(WebURLLoaderImpl* loader);
285
286  WebURLLoaderClient* client() const { return client_; }
287  void set_client(WebURLLoaderClient* client) { client_ = client; }
288
289  void Cancel();
290  void SetDefersLoading(bool value);
291  void Start(
292      const WebURLRequest& request,
293      ResourceLoaderBridge::SyncLoadResponse* sync_load_response);
294
295  // ResourceLoaderBridge::Peer methods:
296  virtual void OnUploadProgress(uint64 position, uint64 size);
297  virtual bool OnReceivedRedirect(
298      const GURL& new_url,
299      const ResourceResponseInfo& info,
300      bool* has_new_first_party_for_cookies,
301      GURL* new_first_party_for_cookies);
302  virtual void OnReceivedResponse(const ResourceResponseInfo& info);
303  virtual void OnDownloadedData(int len);
304  virtual void OnReceivedData(const char* data,
305                              int data_length,
306                              int encoded_data_length);
307  virtual void OnReceivedCachedMetadata(const char* data, int len);
308  virtual void OnCompletedRequest(const net::URLRequestStatus& status,
309                                  const std::string& security_info,
310                                  const base::Time& completion_time);
311
312 private:
313  friend class base::RefCounted<Context>;
314  ~Context() {}
315
316  // We can optimize the handling of data URLs in most cases.
317  bool CanHandleDataURL(const GURL& url) const;
318  void HandleDataURL();
319
320  WebURLLoaderImpl* loader_;
321  WebURLRequest request_;
322  WebURLLoaderClient* client_;
323  scoped_ptr<ResourceLoaderBridge> bridge_;
324  scoped_ptr<FtpDirectoryListingResponseDelegate> ftp_listing_delegate_;
325  scoped_ptr<MultipartResponseDelegate> multipart_delegate_;
326  scoped_ptr<ResourceLoaderBridge> completed_bridge_;
327
328  // TODO(japhet): Storing this is a temporary hack for site isolation logging.
329  WebURL response_url_;
330};
331
332WebURLLoaderImpl::Context::Context(WebURLLoaderImpl* loader)
333    : loader_(loader),
334      client_(NULL) {
335}
336
337void WebURLLoaderImpl::Context::Cancel() {
338  // The bridge will still send OnCompletedRequest, which will Release() us, so
339  // we don't do that here.
340  if (bridge_.get())
341    bridge_->Cancel();
342
343  // Ensure that we do not notify the multipart delegate anymore as it has
344  // its own pointer to the client.
345  if (multipart_delegate_.get())
346    multipart_delegate_->Cancel();
347
348  // Do not make any further calls to the client.
349  client_ = NULL;
350  loader_ = NULL;
351}
352
353void WebURLLoaderImpl::Context::SetDefersLoading(bool value) {
354  if (bridge_.get())
355    bridge_->SetDefersLoading(value);
356}
357
358void WebURLLoaderImpl::Context::Start(
359    const WebURLRequest& request,
360    ResourceLoaderBridge::SyncLoadResponse* sync_load_response) {
361  DCHECK(!bridge_.get());
362
363  request_ = request;  // Save the request.
364
365  GURL url = request.url();
366  if (url.SchemeIs("data") && CanHandleDataURL(url)) {
367    if (sync_load_response) {
368      // This is a sync load. Do the work now.
369      sync_load_response->url = url;
370      std::string data;
371      GetInfoFromDataURL(sync_load_response->url, sync_load_response,
372                         &sync_load_response->data,
373                         &sync_load_response->status);
374    } else {
375      AddRef();  // Balanced in OnCompletedRequest
376      MessageLoop::current()->PostTask(FROM_HERE,
377          NewRunnableMethod(this, &Context::HandleDataURL));
378    }
379    return;
380  }
381
382  GURL referrer_url(
383      request.httpHeaderField(WebString::fromUTF8("Referer")).utf8());
384  const std::string& method = request.httpMethod().utf8();
385
386  int load_flags = net::LOAD_NORMAL;
387  switch (request.cachePolicy()) {
388    case WebURLRequest::ReloadIgnoringCacheData:
389      // Required by LayoutTests/http/tests/misc/refresh-headers.php
390      load_flags |= net::LOAD_VALIDATE_CACHE;
391      break;
392    case WebURLRequest::ReturnCacheDataElseLoad:
393      load_flags |= net::LOAD_PREFERRING_CACHE;
394      break;
395    case WebURLRequest::ReturnCacheDataDontLoad:
396      load_flags |= net::LOAD_ONLY_FROM_CACHE;
397      break;
398    case WebURLRequest::UseProtocolCachePolicy:
399      break;
400  }
401
402  if (request.reportUploadProgress())
403    load_flags |= net::LOAD_ENABLE_UPLOAD_PROGRESS;
404  if (request.reportLoadTiming())
405    load_flags |= net::LOAD_ENABLE_LOAD_TIMING;
406  if (request.reportRawHeaders())
407    load_flags |= net::LOAD_REPORT_RAW_HEADERS;
408
409  if (!request.allowCookies() || !request.allowStoredCredentials()) {
410    load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES;
411    load_flags |= net::LOAD_DO_NOT_SEND_COOKIES;
412  }
413
414  if (!request.allowStoredCredentials())
415    load_flags |= net::LOAD_DO_NOT_SEND_AUTH_DATA;
416
417  HeaderFlattener flattener(load_flags);
418  request.visitHTTPHeaderFields(&flattener);
419
420  // TODO(abarth): These are wrong!  I need to figure out how to get the right
421  //               strings here.  See: http://crbug.com/8706
422  std::string frame_origin = request.firstPartyForCookies().spec();
423  std::string main_frame_origin = request.firstPartyForCookies().spec();
424
425  // TODO(brettw) this should take parameter encoding into account when
426  // creating the GURLs.
427
428  ResourceLoaderBridge::RequestInfo request_info;
429  request_info.method = method;
430  request_info.url = url;
431  request_info.first_party_for_cookies = request.firstPartyForCookies();
432  request_info.referrer = referrer_url;
433  request_info.frame_origin = frame_origin;
434  request_info.main_frame_origin = main_frame_origin;
435  request_info.headers = flattener.GetBuffer();
436  request_info.load_flags = load_flags;
437  // requestor_pid only needs to be non-zero if the request originates outside
438  // the render process, so we can use requestorProcessID even for requests
439  // from in-process plugins.
440  request_info.requestor_pid = request.requestorProcessID();
441  request_info.request_type = FromTargetType(request.targetType());
442  request_info.appcache_host_id = request.appCacheHostID();
443  request_info.routing_id = request.requestorID();
444  request_info.download_to_file = request.downloadToFile();
445  request_info.has_user_gesture = request.hasUserGesture();
446  bridge_.reset(ResourceLoaderBridge::Create(request_info));
447
448  if (!request.httpBody().isNull()) {
449    // GET and HEAD requests shouldn't have http bodies.
450    DCHECK(method != "GET" && method != "HEAD");
451    const WebHTTPBody& httpBody = request.httpBody();
452    size_t i = 0;
453    WebHTTPBody::Element element;
454    while (httpBody.elementAt(i++, element)) {
455      switch (element.type) {
456        case WebHTTPBody::Element::TypeData:
457          if (!element.data.isEmpty()) {
458            // WebKit sometimes gives up empty data to append. These aren't
459            // necessary so we just optimize those out here.
460            bridge_->AppendDataToUpload(
461                element.data.data(), static_cast<int>(element.data.size()));
462          }
463          break;
464        case WebHTTPBody::Element::TypeFile:
465          if (element.fileLength == -1) {
466            bridge_->AppendFileToUpload(
467                WebStringToFilePath(element.filePath));
468          } else {
469            bridge_->AppendFileRangeToUpload(
470                WebStringToFilePath(element.filePath),
471                static_cast<uint64>(element.fileStart),
472                static_cast<uint64>(element.fileLength),
473                base::Time::FromDoubleT(element.modificationTime));
474          }
475          break;
476        case WebHTTPBody::Element::TypeBlob:
477          bridge_->AppendBlobToUpload(GURL(element.blobURL));
478          break;
479        default:
480          NOTREACHED();
481      }
482    }
483    bridge_->SetUploadIdentifier(request.httpBody().identifier());
484  }
485
486  if (sync_load_response) {
487    bridge_->SyncLoad(sync_load_response);
488    return;
489  }
490
491  if (bridge_->Start(this)) {
492    AddRef();  // Balanced in OnCompletedRequest
493  } else {
494    bridge_.reset();
495  }
496}
497
498void WebURLLoaderImpl::Context::OnUploadProgress(uint64 position, uint64 size) {
499  if (client_)
500    client_->didSendData(loader_, position, size);
501}
502
503bool WebURLLoaderImpl::Context::OnReceivedRedirect(
504    const GURL& new_url,
505    const ResourceResponseInfo& info,
506    bool* has_new_first_party_for_cookies,
507    GURL* new_first_party_for_cookies) {
508  if (!client_)
509    return false;
510
511  WebURLResponse response;
512  response.initialize();
513  PopulateURLResponse(request_.url(), info, &response);
514
515  // TODO(darin): We lack sufficient information to construct the actual
516  // request that resulted from the redirect.
517  WebURLRequest new_request(new_url);
518  new_request.setFirstPartyForCookies(request_.firstPartyForCookies());
519  new_request.setDownloadToFile(request_.downloadToFile());
520
521  WebString referrer_string = WebString::fromUTF8("Referer");
522  WebString referrer = request_.httpHeaderField(referrer_string);
523  if (!WebSecurityPolicy::shouldHideReferrer(new_url, referrer))
524    new_request.setHTTPHeaderField(referrer_string, referrer);
525
526  if (response.httpStatusCode() == 307)
527    new_request.setHTTPMethod(request_.httpMethod());
528
529  client_->willSendRequest(loader_, new_request, response);
530  request_ = new_request;
531  *has_new_first_party_for_cookies = true;
532  *new_first_party_for_cookies = request_.firstPartyForCookies();
533
534  // Only follow the redirect if WebKit left the URL unmodified.
535  if (new_url == GURL(new_request.url()))
536    return true;
537
538  // We assume that WebKit only changes the URL to suppress a redirect, and we
539  // assume that it does so by setting it to be invalid.
540  DCHECK(!new_request.url().isValid());
541  return false;
542}
543
544void WebURLLoaderImpl::Context::OnReceivedResponse(
545    const ResourceResponseInfo& info) {
546  if (!client_)
547    return;
548
549  WebURLResponse response;
550  response.initialize();
551  PopulateURLResponse(request_.url(), info, &response);
552
553  bool show_raw_listing = (GURL(request_.url()).query() == "raw");
554
555  if (info.mime_type == "text/vnd.chromium.ftp-dir") {
556    if (show_raw_listing) {
557      // Set the MIME type to plain text to prevent any active content.
558      response.setMIMEType("text/plain");
559    } else {
560      // We're going to produce a parsed listing in HTML.
561      response.setMIMEType("text/html");
562    }
563  }
564
565  client_->didReceiveResponse(loader_, response);
566
567  // We may have been cancelled after didReceiveResponse, which would leave us
568  // without a client and therefore without much need to do further handling.
569  if (!client_)
570    return;
571
572  DCHECK(!ftp_listing_delegate_.get());
573  DCHECK(!multipart_delegate_.get());
574  if (info.headers && info.mime_type == "multipart/x-mixed-replace") {
575    std::string content_type;
576    info.headers->EnumerateHeader(NULL, "content-type", &content_type);
577
578    std::string boundary = net::GetHeaderParamValue(
579        content_type, "boundary", net::QuoteRule::REMOVE_OUTER_QUOTES);
580    TrimString(boundary, " \"", &boundary);
581
582    // If there's no boundary, just handle the request normally.  In the gecko
583    // code, nsMultiMixedConv::OnStartRequest throws an exception.
584    if (!boundary.empty()) {
585      multipart_delegate_.reset(
586          new MultipartResponseDelegate(client_, loader_, response, boundary));
587    }
588  } else if (info.mime_type == "text/vnd.chromium.ftp-dir" &&
589             !show_raw_listing) {
590    ftp_listing_delegate_.reset(
591        new FtpDirectoryListingResponseDelegate(client_, loader_, response));
592  }
593
594  response_url_ = response.url();
595}
596
597void WebURLLoaderImpl::Context::OnDownloadedData(int len) {
598  if (client_)
599    client_->didDownloadData(loader_, len);
600}
601
602void WebURLLoaderImpl::Context::OnReceivedData(const char* data,
603                                               int data_length,
604                                               int encoded_data_length) {
605  if (!client_)
606    return;
607
608  // Temporary logging, see site_isolation_metrics.h/cc.
609  SiteIsolationMetrics::SniffCrossOriginHTML(response_url_, data, data_length);
610
611  if (ftp_listing_delegate_.get()) {
612    // The FTP listing delegate will make the appropriate calls to
613    // client_->didReceiveData and client_->didReceiveResponse.
614    ftp_listing_delegate_->OnReceivedData(data, data_length);
615  } else if (multipart_delegate_.get()) {
616    // The multipart delegate will make the appropriate calls to
617    // client_->didReceiveData and client_->didReceiveResponse.
618    multipart_delegate_->OnReceivedData(data, data_length, encoded_data_length);
619  } else {
620    client_->didReceiveData(loader_, data, data_length, encoded_data_length);
621  }
622}
623
624void WebURLLoaderImpl::Context::OnReceivedCachedMetadata(
625    const char* data, int len) {
626  if (client_)
627    client_->didReceiveCachedMetadata(loader_, data, len);
628}
629
630void WebURLLoaderImpl::Context::OnCompletedRequest(
631    const net::URLRequestStatus& status,
632    const std::string& security_info,
633    const base::Time& completion_time) {
634  if (ftp_listing_delegate_.get()) {
635    ftp_listing_delegate_->OnCompletedRequest();
636    ftp_listing_delegate_.reset(NULL);
637  } else if (multipart_delegate_.get()) {
638    multipart_delegate_->OnCompletedRequest();
639    multipart_delegate_.reset(NULL);
640  }
641
642  // Prevent any further IPC to the browser now that we're complete, but
643  // don't delete it to keep any downloaded temp files alive.
644  DCHECK(!completed_bridge_.get());
645  completed_bridge_.swap(bridge_);
646
647  if (client_) {
648    if (status.status() != net::URLRequestStatus::SUCCESS) {
649      int error_code;
650      if (status.status() == net::URLRequestStatus::HANDLED_EXTERNALLY) {
651        // By marking this request as aborted we insure that we don't navigate
652        // to an error page.
653        error_code = net::ERR_ABORTED;
654      } else {
655        error_code = status.os_error();
656      }
657      WebURLError error;
658      error.domain = WebString::fromUTF8(net::kErrorDomain);
659      error.reason = error_code;
660      error.unreachableURL = request_.url();
661      client_->didFail(loader_, error);
662    } else {
663      client_->didFinishLoading(loader_, completion_time.ToDoubleT());
664    }
665  }
666
667  // Temporary logging, see site_isolation_metrics.h/cc
668  SiteIsolationMetrics::RemoveCompletedResponse(response_url_);
669
670  // We are done with the bridge now, and so we need to release the reference
671  // to ourselves that we took on behalf of the bridge.  This may cause our
672  // destruction.
673  Release();
674}
675
676bool WebURLLoaderImpl::Context::CanHandleDataURL(const GURL& url) const {
677  DCHECK(url.SchemeIs("data"));
678
679  // Optimize for the case where we can handle a data URL locally.  We must
680  // skip this for data URLs targetted at frames since those could trigger a
681  // download.
682  //
683  // NOTE: We special case MIME types we can render both for performance
684  // reasons as well as to support unit tests, which do not have an underlying
685  // ResourceLoaderBridge implementation.
686
687  if (request_.targetType() != WebURLRequest::TargetIsMainFrame &&
688      request_.targetType() != WebURLRequest::TargetIsSubframe)
689    return true;
690
691  std::string mime_type, unused_charset;
692  if (net::DataURL::Parse(url, &mime_type, &unused_charset, NULL) &&
693      net::IsSupportedMimeType(mime_type))
694    return true;
695
696  return false;
697}
698
699void WebURLLoaderImpl::Context::HandleDataURL() {
700  ResourceResponseInfo info;
701  net::URLRequestStatus status;
702  std::string data;
703
704  if (GetInfoFromDataURL(request_.url(), &info, &data, &status)) {
705    OnReceivedResponse(info);
706    if (!data.empty())
707      OnReceivedData(data.data(), data.size(), 0);
708  }
709
710  OnCompletedRequest(status, info.security_info, base::Time::Now());
711}
712
713// WebURLLoaderImpl -----------------------------------------------------------
714
715WebURLLoaderImpl::WebURLLoaderImpl()
716    : ALLOW_THIS_IN_INITIALIZER_LIST(context_(new Context(this))) {
717}
718
719WebURLLoaderImpl::~WebURLLoaderImpl() {
720  cancel();
721}
722
723void WebURLLoaderImpl::loadSynchronously(const WebURLRequest& request,
724                                         WebURLResponse& response,
725                                         WebURLError& error,
726                                         WebData& data) {
727  ResourceLoaderBridge::SyncLoadResponse sync_load_response;
728  context_->Start(request, &sync_load_response);
729
730  const GURL& final_url = sync_load_response.url;
731
732  // TODO(tc): For file loads, we may want to include a more descriptive
733  // status code or status text.
734  const net::URLRequestStatus::Status& status =
735      sync_load_response.status.status();
736  if (status != net::URLRequestStatus::SUCCESS &&
737      status != net::URLRequestStatus::HANDLED_EXTERNALLY) {
738    response.setURL(final_url);
739    error.domain = WebString::fromUTF8(net::kErrorDomain);
740    error.reason = sync_load_response.status.os_error();
741    error.unreachableURL = final_url;
742    return;
743  }
744
745  PopulateURLResponse(final_url, sync_load_response, &response);
746
747  data.assign(sync_load_response.data.data(),
748              sync_load_response.data.size());
749}
750
751void WebURLLoaderImpl::loadAsynchronously(const WebURLRequest& request,
752                                          WebURLLoaderClient* client) {
753  DCHECK(!context_->client());
754
755  context_->set_client(client);
756  context_->Start(request, NULL);
757}
758
759void WebURLLoaderImpl::cancel() {
760  context_->Cancel();
761}
762
763void WebURLLoaderImpl::setDefersLoading(bool value) {
764  context_->SetDefersLoading(value);
765}
766
767}  // namespace webkit_glue
768