| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // An implementation of WebURLLoader in terms of ResourceLoaderBridge. |
| |
| #include "webkit/glue/weburlloader_impl.h" |
| |
| #include "base/file_path.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/message_loop.h" |
| #include "base/process_util.h" |
| #include "base/string_util.h" |
| #include "base/time.h" |
| #include "net/base/data_url.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/mime_util.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/net_util.h" |
| #include "net/http/http_response_headers.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebHTTPHeaderVisitor.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebHTTPLoadInfo.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityPolicy.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoadTiming.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderClient.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h" |
| #include "webkit/glue/ftp_directory_listing_response_delegate.h" |
| #include "webkit/glue/multipart_response_delegate.h" |
| #include "webkit/glue/resource_loader_bridge.h" |
| #include "webkit/glue/site_isolation_metrics.h" |
| #include "webkit/glue/webkit_glue.h" |
| |
| using base::Time; |
| using base::TimeDelta; |
| using WebKit::WebData; |
| using WebKit::WebHTTPBody; |
| using WebKit::WebHTTPHeaderVisitor; |
| using WebKit::WebHTTPLoadInfo; |
| using WebKit::WebSecurityPolicy; |
| using WebKit::WebString; |
| using WebKit::WebURL; |
| using WebKit::WebURLError; |
| using WebKit::WebURLLoadTiming; |
| using WebKit::WebURLLoader; |
| using WebKit::WebURLLoaderClient; |
| using WebKit::WebURLRequest; |
| using WebKit::WebURLResponse; |
| |
| namespace webkit_glue { |
| |
| // Utilities ------------------------------------------------------------------ |
| |
| namespace { |
| |
| class HeaderFlattener : public WebHTTPHeaderVisitor { |
| public: |
| explicit HeaderFlattener(int load_flags) |
| : load_flags_(load_flags), |
| has_accept_header_(false) { |
| } |
| |
| virtual void visitHeader(const WebString& name, const WebString& value) { |
| // TODO(darin): is UTF-8 really correct here? It is if the strings are |
| // already ASCII (i.e., if they are already escaped properly). |
| const std::string& name_utf8 = name.utf8(); |
| const std::string& value_utf8 = value.utf8(); |
| |
| // Skip over referrer headers found in the header map because we already |
| // pulled it out as a separate parameter. |
| if (LowerCaseEqualsASCII(name_utf8, "referer")) |
| return; |
| |
| // Skip over "Cache-Control: max-age=0" header if the corresponding |
| // load flag is already specified. FrameLoader sets both the flag and |
| // the extra header -- the extra header is redundant since our network |
| // implementation will add the necessary headers based on load flags. |
| // See http://code.google.com/p/chromium/issues/detail?id=3434. |
| if ((load_flags_ & net::LOAD_VALIDATE_CACHE) && |
| LowerCaseEqualsASCII(name_utf8, "cache-control") && |
| LowerCaseEqualsASCII(value_utf8, "max-age=0")) |
| return; |
| |
| if (LowerCaseEqualsASCII(name_utf8, "accept")) |
| has_accept_header_ = true; |
| |
| if (!buffer_.empty()) |
| buffer_.append("\r\n"); |
| buffer_.append(name_utf8 + ": " + value_utf8); |
| } |
| |
| const std::string& GetBuffer() { |
| // In some cases, WebKit doesn't add an Accept header, but not having the |
| // header confuses some web servers. See bug 808613. |
| if (!has_accept_header_) { |
| if (!buffer_.empty()) |
| buffer_.append("\r\n"); |
| buffer_.append("Accept: */*"); |
| has_accept_header_ = true; |
| } |
| return buffer_; |
| } |
| |
| private: |
| int load_flags_; |
| std::string buffer_; |
| bool has_accept_header_; |
| }; |
| |
| ResourceType::Type FromTargetType(WebURLRequest::TargetType type) { |
| switch (type) { |
| case WebURLRequest::TargetIsMainFrame: |
| return ResourceType::MAIN_FRAME; |
| case WebURLRequest::TargetIsSubframe: |
| return ResourceType::SUB_FRAME; |
| case WebURLRequest::TargetIsSubresource: |
| return ResourceType::SUB_RESOURCE; |
| case WebURLRequest::TargetIsStyleSheet: |
| return ResourceType::STYLESHEET; |
| case WebURLRequest::TargetIsScript: |
| return ResourceType::SCRIPT; |
| case WebURLRequest::TargetIsFontResource: |
| return ResourceType::FONT_RESOURCE; |
| case WebURLRequest::TargetIsImage: |
| return ResourceType::IMAGE; |
| case WebURLRequest::TargetIsObject: |
| return ResourceType::OBJECT; |
| case WebURLRequest::TargetIsMedia: |
| return ResourceType::MEDIA; |
| case WebURLRequest::TargetIsWorker: |
| return ResourceType::WORKER; |
| case WebURLRequest::TargetIsSharedWorker: |
| return ResourceType::SHARED_WORKER; |
| case WebURLRequest::TargetIsPrefetch: |
| return ResourceType::PREFETCH; |
| case WebURLRequest::TargetIsFavicon: |
| return ResourceType::FAVICON; |
| default: |
| NOTREACHED(); |
| return ResourceType::SUB_RESOURCE; |
| } |
| } |
| |
| // Extracts the information from a data: url. |
| bool GetInfoFromDataURL(const GURL& url, |
| ResourceResponseInfo* info, |
| std::string* data, |
| net::URLRequestStatus* status) { |
| std::string mime_type; |
| std::string charset; |
| if (net::DataURL::Parse(url, &mime_type, &charset, data)) { |
| *status = net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0); |
| info->request_time = Time::Now(); |
| info->response_time = Time::Now(); |
| info->headers = NULL; |
| info->mime_type.swap(mime_type); |
| info->charset.swap(charset); |
| info->security_info.clear(); |
| info->content_length = -1; |
| info->encoded_data_length = 0; |
| info->load_timing.base_time = Time::Now(); |
| |
| return true; |
| } |
| |
| *status = net::URLRequestStatus(net::URLRequestStatus::FAILED, |
| net::ERR_INVALID_URL); |
| return false; |
| } |
| |
| typedef ResourceDevToolsInfo::HeadersVector HeadersVector; |
| |
| void PopulateURLResponse( |
| const GURL& url, |
| const ResourceResponseInfo& info, |
| WebURLResponse* response) { |
| response->setURL(url); |
| response->setResponseTime(info.response_time.ToDoubleT()); |
| response->setMIMEType(WebString::fromUTF8(info.mime_type)); |
| response->setTextEncodingName(WebString::fromUTF8(info.charset)); |
| response->setExpectedContentLength(info.content_length); |
| response->setSecurityInfo(info.security_info); |
| response->setAppCacheID(info.appcache_id); |
| response->setAppCacheManifestURL(info.appcache_manifest_url); |
| response->setWasCached(!info.load_timing.base_time.is_null() && |
| info.response_time < info.load_timing.base_time); |
| response->setWasFetchedViaSPDY(info.was_fetched_via_spdy); |
| response->setWasNpnNegotiated(info.was_npn_negotiated); |
| response->setWasAlternateProtocolAvailable( |
| info.was_alternate_protocol_available); |
| response->setWasFetchedViaProxy(info.was_fetched_via_proxy); |
| response->setRemoteIPAddress( |
| WebString::fromUTF8(info.socket_address.host())); |
| response->setRemotePort(info.socket_address.port()); |
| response->setConnectionID(info.connection_id); |
| response->setConnectionReused(info.connection_reused); |
| response->setDownloadFilePath(FilePathToWebString(info.download_file_path)); |
| |
| const ResourceLoadTimingInfo& timing_info = info.load_timing; |
| if (!timing_info.base_time.is_null()) { |
| WebURLLoadTiming timing; |
| timing.initialize(); |
| timing.setRequestTime(timing_info.base_time.ToDoubleT()); |
| timing.setProxyStart(timing_info.proxy_start); |
| timing.setProxyEnd(timing_info.proxy_end); |
| timing.setDNSStart(timing_info.dns_start); |
| timing.setDNSEnd(timing_info.dns_end); |
| timing.setConnectStart(timing_info.connect_start); |
| timing.setConnectEnd(timing_info.connect_end); |
| timing.setSSLStart(timing_info.ssl_start); |
| timing.setSSLEnd(timing_info.ssl_end); |
| timing.setSendStart(timing_info.send_start); |
| timing.setSendEnd(timing_info.send_end); |
| timing.setReceiveHeadersEnd(timing_info.receive_headers_end); |
| response->setLoadTiming(timing); |
| } |
| |
| if (info.devtools_info.get()) { |
| WebHTTPLoadInfo load_info; |
| |
| load_info.setHTTPStatusCode(info.devtools_info->http_status_code); |
| load_info.setHTTPStatusText(WebString::fromUTF8( |
| info.devtools_info->http_status_text)); |
| load_info.setEncodedDataLength(info.encoded_data_length); |
| |
| const HeadersVector& request_headers = info.devtools_info->request_headers; |
| for (HeadersVector::const_iterator it = request_headers.begin(); |
| it != request_headers.end(); ++it) { |
| load_info.addRequestHeader(WebString::fromUTF8(it->first), |
| WebString::fromUTF8(it->second)); |
| } |
| const HeadersVector& response_headers = |
| info.devtools_info->response_headers; |
| for (HeadersVector::const_iterator it = response_headers.begin(); |
| it != response_headers.end(); ++it) { |
| load_info.addResponseHeader(WebString::fromUTF8(it->first), |
| WebString::fromUTF8(it->second)); |
| } |
| response->setHTTPLoadInfo(load_info); |
| } |
| |
| const net::HttpResponseHeaders* headers = info.headers; |
| if (!headers) |
| return; |
| |
| response->setHTTPStatusCode(headers->response_code()); |
| response->setHTTPStatusText(WebString::fromUTF8(headers->GetStatusText())); |
| |
| // TODO(darin): We should leverage HttpResponseHeaders for this, and this |
| // should be using the same code as ResourceDispatcherHost. |
| // TODO(jungshik): Figure out the actual value of the referrer charset and |
| // pass it to GetSuggestedFilename. |
| std::string value; |
| if (headers->EnumerateHeader(NULL, "content-disposition", &value)) { |
| response->setSuggestedFileName( |
| net::GetSuggestedFilename(url, value, "", string16())); |
| } |
| |
| Time time_val; |
| if (headers->GetLastModifiedValue(&time_val)) |
| response->setLastModifiedDate(time_val.ToDoubleT()); |
| |
| // Build up the header map. |
| void* iter = NULL; |
| std::string name; |
| while (headers->EnumerateHeaderLines(&iter, &name, &value)) { |
| response->addHTTPHeaderField(WebString::fromUTF8(name), |
| WebString::fromUTF8(value)); |
| } |
| } |
| |
| } // namespace |
| |
| // WebURLLoaderImpl::Context -------------------------------------------------- |
| |
| // This inner class exists since the WebURLLoader may be deleted while inside a |
| // call to WebURLLoaderClient. The bridge requires its Peer to stay alive |
| // until it receives OnCompletedRequest. |
| class WebURLLoaderImpl::Context : public base::RefCounted<Context>, |
| public ResourceLoaderBridge::Peer { |
| public: |
| explicit Context(WebURLLoaderImpl* loader); |
| |
| WebURLLoaderClient* client() const { return client_; } |
| void set_client(WebURLLoaderClient* client) { client_ = client; } |
| |
| void Cancel(); |
| void SetDefersLoading(bool value); |
| void Start( |
| const WebURLRequest& request, |
| ResourceLoaderBridge::SyncLoadResponse* sync_load_response); |
| |
| // ResourceLoaderBridge::Peer methods: |
| virtual void OnUploadProgress(uint64 position, uint64 size); |
| virtual bool OnReceivedRedirect( |
| const GURL& new_url, |
| const ResourceResponseInfo& info, |
| bool* has_new_first_party_for_cookies, |
| GURL* new_first_party_for_cookies); |
| virtual void OnReceivedResponse(const ResourceResponseInfo& info); |
| virtual void OnDownloadedData(int len); |
| virtual void OnReceivedData(const char* data, |
| int data_length, |
| int encoded_data_length); |
| virtual void OnReceivedCachedMetadata(const char* data, int len); |
| virtual void OnCompletedRequest(const net::URLRequestStatus& status, |
| const std::string& security_info, |
| const base::Time& completion_time); |
| |
| private: |
| friend class base::RefCounted<Context>; |
| ~Context() {} |
| |
| // We can optimize the handling of data URLs in most cases. |
| bool CanHandleDataURL(const GURL& url) const; |
| void HandleDataURL(); |
| |
| WebURLLoaderImpl* loader_; |
| WebURLRequest request_; |
| WebURLLoaderClient* client_; |
| scoped_ptr<ResourceLoaderBridge> bridge_; |
| scoped_ptr<FtpDirectoryListingResponseDelegate> ftp_listing_delegate_; |
| scoped_ptr<MultipartResponseDelegate> multipart_delegate_; |
| scoped_ptr<ResourceLoaderBridge> completed_bridge_; |
| |
| // TODO(japhet): Storing this is a temporary hack for site isolation logging. |
| WebURL response_url_; |
| }; |
| |
| WebURLLoaderImpl::Context::Context(WebURLLoaderImpl* loader) |
| : loader_(loader), |
| client_(NULL) { |
| } |
| |
| void WebURLLoaderImpl::Context::Cancel() { |
| // The bridge will still send OnCompletedRequest, which will Release() us, so |
| // we don't do that here. |
| if (bridge_.get()) |
| bridge_->Cancel(); |
| |
| // Ensure that we do not notify the multipart delegate anymore as it has |
| // its own pointer to the client. |
| if (multipart_delegate_.get()) |
| multipart_delegate_->Cancel(); |
| |
| // Do not make any further calls to the client. |
| client_ = NULL; |
| loader_ = NULL; |
| } |
| |
| void WebURLLoaderImpl::Context::SetDefersLoading(bool value) { |
| if (bridge_.get()) |
| bridge_->SetDefersLoading(value); |
| } |
| |
| void WebURLLoaderImpl::Context::Start( |
| const WebURLRequest& request, |
| ResourceLoaderBridge::SyncLoadResponse* sync_load_response) { |
| DCHECK(!bridge_.get()); |
| |
| request_ = request; // Save the request. |
| |
| GURL url = request.url(); |
| if (url.SchemeIs("data") && CanHandleDataURL(url)) { |
| if (sync_load_response) { |
| // This is a sync load. Do the work now. |
| sync_load_response->url = url; |
| std::string data; |
| GetInfoFromDataURL(sync_load_response->url, sync_load_response, |
| &sync_load_response->data, |
| &sync_load_response->status); |
| } else { |
| AddRef(); // Balanced in OnCompletedRequest |
| MessageLoop::current()->PostTask(FROM_HERE, |
| NewRunnableMethod(this, &Context::HandleDataURL)); |
| } |
| return; |
| } |
| |
| GURL referrer_url( |
| request.httpHeaderField(WebString::fromUTF8("Referer")).utf8()); |
| const std::string& method = request.httpMethod().utf8(); |
| |
| int load_flags = net::LOAD_NORMAL; |
| switch (request.cachePolicy()) { |
| case WebURLRequest::ReloadIgnoringCacheData: |
| // Required by LayoutTests/http/tests/misc/refresh-headers.php |
| load_flags |= net::LOAD_VALIDATE_CACHE; |
| break; |
| case WebURLRequest::ReturnCacheDataElseLoad: |
| load_flags |= net::LOAD_PREFERRING_CACHE; |
| break; |
| case WebURLRequest::ReturnCacheDataDontLoad: |
| load_flags |= net::LOAD_ONLY_FROM_CACHE; |
| break; |
| case WebURLRequest::UseProtocolCachePolicy: |
| break; |
| } |
| |
| if (request.reportUploadProgress()) |
| load_flags |= net::LOAD_ENABLE_UPLOAD_PROGRESS; |
| if (request.reportLoadTiming()) |
| load_flags |= net::LOAD_ENABLE_LOAD_TIMING; |
| if (request.reportRawHeaders()) |
| load_flags |= net::LOAD_REPORT_RAW_HEADERS; |
| |
| if (!request.allowCookies() || !request.allowStoredCredentials()) { |
| load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; |
| load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; |
| } |
| |
| if (!request.allowStoredCredentials()) |
| load_flags |= net::LOAD_DO_NOT_SEND_AUTH_DATA; |
| |
| HeaderFlattener flattener(load_flags); |
| request.visitHTTPHeaderFields(&flattener); |
| |
| // TODO(abarth): These are wrong! I need to figure out how to get the right |
| // strings here. See: http://crbug.com/8706 |
| std::string frame_origin = request.firstPartyForCookies().spec(); |
| std::string main_frame_origin = request.firstPartyForCookies().spec(); |
| |
| // TODO(brettw) this should take parameter encoding into account when |
| // creating the GURLs. |
| |
| ResourceLoaderBridge::RequestInfo request_info; |
| request_info.method = method; |
| request_info.url = url; |
| request_info.first_party_for_cookies = request.firstPartyForCookies(); |
| request_info.referrer = referrer_url; |
| request_info.frame_origin = frame_origin; |
| request_info.main_frame_origin = main_frame_origin; |
| request_info.headers = flattener.GetBuffer(); |
| request_info.load_flags = load_flags; |
| // requestor_pid only needs to be non-zero if the request originates outside |
| // the render process, so we can use requestorProcessID even for requests |
| // from in-process plugins. |
| request_info.requestor_pid = request.requestorProcessID(); |
| request_info.request_type = FromTargetType(request.targetType()); |
| request_info.appcache_host_id = request.appCacheHostID(); |
| request_info.routing_id = request.requestorID(); |
| request_info.download_to_file = request.downloadToFile(); |
| request_info.has_user_gesture = request.hasUserGesture(); |
| bridge_.reset(ResourceLoaderBridge::Create(request_info)); |
| |
| if (!request.httpBody().isNull()) { |
| // GET and HEAD requests shouldn't have http bodies. |
| DCHECK(method != "GET" && method != "HEAD"); |
| const WebHTTPBody& httpBody = request.httpBody(); |
| size_t i = 0; |
| WebHTTPBody::Element element; |
| while (httpBody.elementAt(i++, element)) { |
| switch (element.type) { |
| case WebHTTPBody::Element::TypeData: |
| if (!element.data.isEmpty()) { |
| // WebKit sometimes gives up empty data to append. These aren't |
| // necessary so we just optimize those out here. |
| bridge_->AppendDataToUpload( |
| element.data.data(), static_cast<int>(element.data.size())); |
| } |
| break; |
| case WebHTTPBody::Element::TypeFile: |
| if (element.fileLength == -1) { |
| bridge_->AppendFileToUpload( |
| WebStringToFilePath(element.filePath)); |
| } else { |
| bridge_->AppendFileRangeToUpload( |
| WebStringToFilePath(element.filePath), |
| static_cast<uint64>(element.fileStart), |
| static_cast<uint64>(element.fileLength), |
| base::Time::FromDoubleT(element.modificationTime)); |
| } |
| break; |
| case WebHTTPBody::Element::TypeBlob: |
| bridge_->AppendBlobToUpload(GURL(element.blobURL)); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| bridge_->SetUploadIdentifier(request.httpBody().identifier()); |
| } |
| |
| if (sync_load_response) { |
| bridge_->SyncLoad(sync_load_response); |
| return; |
| } |
| |
| if (bridge_->Start(this)) { |
| AddRef(); // Balanced in OnCompletedRequest |
| } else { |
| bridge_.reset(); |
| } |
| } |
| |
| void WebURLLoaderImpl::Context::OnUploadProgress(uint64 position, uint64 size) { |
| if (client_) |
| client_->didSendData(loader_, position, size); |
| } |
| |
| bool WebURLLoaderImpl::Context::OnReceivedRedirect( |
| const GURL& new_url, |
| const ResourceResponseInfo& info, |
| bool* has_new_first_party_for_cookies, |
| GURL* new_first_party_for_cookies) { |
| if (!client_) |
| return false; |
| |
| WebURLResponse response; |
| response.initialize(); |
| PopulateURLResponse(request_.url(), info, &response); |
| |
| // TODO(darin): We lack sufficient information to construct the actual |
| // request that resulted from the redirect. |
| WebURLRequest new_request(new_url); |
| new_request.setFirstPartyForCookies(request_.firstPartyForCookies()); |
| new_request.setDownloadToFile(request_.downloadToFile()); |
| |
| WebString referrer_string = WebString::fromUTF8("Referer"); |
| WebString referrer = request_.httpHeaderField(referrer_string); |
| if (!WebSecurityPolicy::shouldHideReferrer(new_url, referrer)) |
| new_request.setHTTPHeaderField(referrer_string, referrer); |
| |
| if (response.httpStatusCode() == 307) |
| new_request.setHTTPMethod(request_.httpMethod()); |
| |
| client_->willSendRequest(loader_, new_request, response); |
| request_ = new_request; |
| *has_new_first_party_for_cookies = true; |
| *new_first_party_for_cookies = request_.firstPartyForCookies(); |
| |
| // Only follow the redirect if WebKit left the URL unmodified. |
| if (new_url == GURL(new_request.url())) |
| return true; |
| |
| // We assume that WebKit only changes the URL to suppress a redirect, and we |
| // assume that it does so by setting it to be invalid. |
| DCHECK(!new_request.url().isValid()); |
| return false; |
| } |
| |
| void WebURLLoaderImpl::Context::OnReceivedResponse( |
| const ResourceResponseInfo& info) { |
| if (!client_) |
| return; |
| |
| WebURLResponse response; |
| response.initialize(); |
| PopulateURLResponse(request_.url(), info, &response); |
| |
| bool show_raw_listing = (GURL(request_.url()).query() == "raw"); |
| |
| if (info.mime_type == "text/vnd.chromium.ftp-dir") { |
| if (show_raw_listing) { |
| // Set the MIME type to plain text to prevent any active content. |
| response.setMIMEType("text/plain"); |
| } else { |
| // We're going to produce a parsed listing in HTML. |
| response.setMIMEType("text/html"); |
| } |
| } |
| |
| client_->didReceiveResponse(loader_, response); |
| |
| // We may have been cancelled after didReceiveResponse, which would leave us |
| // without a client and therefore without much need to do further handling. |
| if (!client_) |
| return; |
| |
| DCHECK(!ftp_listing_delegate_.get()); |
| DCHECK(!multipart_delegate_.get()); |
| if (info.headers && info.mime_type == "multipart/x-mixed-replace") { |
| std::string content_type; |
| info.headers->EnumerateHeader(NULL, "content-type", &content_type); |
| |
| std::string boundary = net::GetHeaderParamValue( |
| content_type, "boundary", net::QuoteRule::REMOVE_OUTER_QUOTES); |
| TrimString(boundary, " \"", &boundary); |
| |
| // If there's no boundary, just handle the request normally. In the gecko |
| // code, nsMultiMixedConv::OnStartRequest throws an exception. |
| if (!boundary.empty()) { |
| multipart_delegate_.reset( |
| new MultipartResponseDelegate(client_, loader_, response, boundary)); |
| } |
| } else if (info.mime_type == "text/vnd.chromium.ftp-dir" && |
| !show_raw_listing) { |
| ftp_listing_delegate_.reset( |
| new FtpDirectoryListingResponseDelegate(client_, loader_, response)); |
| } |
| |
| response_url_ = response.url(); |
| } |
| |
| void WebURLLoaderImpl::Context::OnDownloadedData(int len) { |
| if (client_) |
| client_->didDownloadData(loader_, len); |
| } |
| |
| void WebURLLoaderImpl::Context::OnReceivedData(const char* data, |
| int data_length, |
| int encoded_data_length) { |
| if (!client_) |
| return; |
| |
| // Temporary logging, see site_isolation_metrics.h/cc. |
| SiteIsolationMetrics::SniffCrossOriginHTML(response_url_, data, data_length); |
| |
| if (ftp_listing_delegate_.get()) { |
| // The FTP listing delegate will make the appropriate calls to |
| // client_->didReceiveData and client_->didReceiveResponse. |
| ftp_listing_delegate_->OnReceivedData(data, data_length); |
| } else if (multipart_delegate_.get()) { |
| // The multipart delegate will make the appropriate calls to |
| // client_->didReceiveData and client_->didReceiveResponse. |
| multipart_delegate_->OnReceivedData(data, data_length, encoded_data_length); |
| } else { |
| client_->didReceiveData(loader_, data, data_length, encoded_data_length); |
| } |
| } |
| |
| void WebURLLoaderImpl::Context::OnReceivedCachedMetadata( |
| const char* data, int len) { |
| if (client_) |
| client_->didReceiveCachedMetadata(loader_, data, len); |
| } |
| |
| void WebURLLoaderImpl::Context::OnCompletedRequest( |
| const net::URLRequestStatus& status, |
| const std::string& security_info, |
| const base::Time& completion_time) { |
| if (ftp_listing_delegate_.get()) { |
| ftp_listing_delegate_->OnCompletedRequest(); |
| ftp_listing_delegate_.reset(NULL); |
| } else if (multipart_delegate_.get()) { |
| multipart_delegate_->OnCompletedRequest(); |
| multipart_delegate_.reset(NULL); |
| } |
| |
| // Prevent any further IPC to the browser now that we're complete, but |
| // don't delete it to keep any downloaded temp files alive. |
| DCHECK(!completed_bridge_.get()); |
| completed_bridge_.swap(bridge_); |
| |
| if (client_) { |
| if (status.status() != net::URLRequestStatus::SUCCESS) { |
| int error_code; |
| if (status.status() == net::URLRequestStatus::HANDLED_EXTERNALLY) { |
| // By marking this request as aborted we insure that we don't navigate |
| // to an error page. |
| error_code = net::ERR_ABORTED; |
| } else { |
| error_code = status.os_error(); |
| } |
| WebURLError error; |
| error.domain = WebString::fromUTF8(net::kErrorDomain); |
| error.reason = error_code; |
| error.unreachableURL = request_.url(); |
| client_->didFail(loader_, error); |
| } else { |
| client_->didFinishLoading(loader_, completion_time.ToDoubleT()); |
| } |
| } |
| |
| // Temporary logging, see site_isolation_metrics.h/cc |
| SiteIsolationMetrics::RemoveCompletedResponse(response_url_); |
| |
| // We are done with the bridge now, and so we need to release the reference |
| // to ourselves that we took on behalf of the bridge. This may cause our |
| // destruction. |
| Release(); |
| } |
| |
| bool WebURLLoaderImpl::Context::CanHandleDataURL(const GURL& url) const { |
| DCHECK(url.SchemeIs("data")); |
| |
| // Optimize for the case where we can handle a data URL locally. We must |
| // skip this for data URLs targetted at frames since those could trigger a |
| // download. |
| // |
| // NOTE: We special case MIME types we can render both for performance |
| // reasons as well as to support unit tests, which do not have an underlying |
| // ResourceLoaderBridge implementation. |
| |
| if (request_.targetType() != WebURLRequest::TargetIsMainFrame && |
| request_.targetType() != WebURLRequest::TargetIsSubframe) |
| return true; |
| |
| std::string mime_type, unused_charset; |
| if (net::DataURL::Parse(url, &mime_type, &unused_charset, NULL) && |
| net::IsSupportedMimeType(mime_type)) |
| return true; |
| |
| return false; |
| } |
| |
| void WebURLLoaderImpl::Context::HandleDataURL() { |
| ResourceResponseInfo info; |
| net::URLRequestStatus status; |
| std::string data; |
| |
| if (GetInfoFromDataURL(request_.url(), &info, &data, &status)) { |
| OnReceivedResponse(info); |
| if (!data.empty()) |
| OnReceivedData(data.data(), data.size(), 0); |
| } |
| |
| OnCompletedRequest(status, info.security_info, base::Time::Now()); |
| } |
| |
| // WebURLLoaderImpl ----------------------------------------------------------- |
| |
| WebURLLoaderImpl::WebURLLoaderImpl() |
| : ALLOW_THIS_IN_INITIALIZER_LIST(context_(new Context(this))) { |
| } |
| |
| WebURLLoaderImpl::~WebURLLoaderImpl() { |
| cancel(); |
| } |
| |
| void WebURLLoaderImpl::loadSynchronously(const WebURLRequest& request, |
| WebURLResponse& response, |
| WebURLError& error, |
| WebData& data) { |
| ResourceLoaderBridge::SyncLoadResponse sync_load_response; |
| context_->Start(request, &sync_load_response); |
| |
| const GURL& final_url = sync_load_response.url; |
| |
| // TODO(tc): For file loads, we may want to include a more descriptive |
| // status code or status text. |
| const net::URLRequestStatus::Status& status = |
| sync_load_response.status.status(); |
| if (status != net::URLRequestStatus::SUCCESS && |
| status != net::URLRequestStatus::HANDLED_EXTERNALLY) { |
| response.setURL(final_url); |
| error.domain = WebString::fromUTF8(net::kErrorDomain); |
| error.reason = sync_load_response.status.os_error(); |
| error.unreachableURL = final_url; |
| return; |
| } |
| |
| PopulateURLResponse(final_url, sync_load_response, &response); |
| |
| data.assign(sync_load_response.data.data(), |
| sync_load_response.data.size()); |
| } |
| |
| void WebURLLoaderImpl::loadAsynchronously(const WebURLRequest& request, |
| WebURLLoaderClient* client) { |
| DCHECK(!context_->client()); |
| |
| context_->set_client(client); |
| context_->Start(request, NULL); |
| } |
| |
| void WebURLLoaderImpl::cancel() { |
| context_->Cancel(); |
| } |
| |
| void WebURLLoaderImpl::setDefersLoading(bool value) { |
| context_->SetDefersLoading(value); |
| } |
| |
| } // namespace webkit_glue |