// Copyright (c) 2013 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. #include "content/child/npapi/plugin_url_fetcher.h" #include "base/memory/scoped_ptr.h" #include "content/child/child_thread.h" #include "content/child/multipart_response_delegate.h" #include "content/child/npapi/plugin_host.h" #include "content/child/npapi/plugin_instance.h" #include "content/child/npapi/plugin_stream_url.h" #include "content/child/npapi/webplugin.h" #include "content/child/npapi/webplugin_resource_client.h" #include "content/child/plugin_messages.h" #include "content/child/request_extra_data.h" #include "content/child/request_info.h" #include "content/child/resource_dispatcher.h" #include "content/child/resource_loader_bridge.h" #include "content/child/web_url_loader_impl.h" #include "content/common/resource_request_body.h" #include "content/common/service_worker/service_worker_types.h" #include "content/public/common/resource_response_info.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" #include "net/url_request/redirect_info.h" #include "third_party/WebKit/public/platform/WebURLLoaderClient.h" #include "third_party/WebKit/public/platform/WebURLResponse.h" namespace content { namespace { // This class handles individual multipart responses. It is instantiated when // we receive HTTP status code 206 in the HTTP response. This indicates // that the response could have multiple parts each separated by a boundary // specified in the response header. // TODO(jam): this is similar to MultiPartResponseClient in webplugin_impl.cc, // we should remove that other class once we switch to loading from the plugin // process by default. class MultiPartResponseClient : public blink::WebURLLoaderClient { public: explicit MultiPartResponseClient(PluginStreamUrl* plugin_stream) : byte_range_lower_bound_(0), plugin_stream_(plugin_stream) {} // blink::WebURLLoaderClient implementation: virtual void didReceiveResponse( blink::WebURLLoader* loader, const blink::WebURLResponse& response) OVERRIDE { int64 byte_range_upper_bound, instance_size; if (!MultipartResponseDelegate::ReadContentRanges(response, &byte_range_lower_bound_, &byte_range_upper_bound, &instance_size)) { NOTREACHED(); } } virtual void didReceiveData(blink::WebURLLoader* loader, const char* data, int data_length, int encoded_data_length) OVERRIDE { // TODO(ananta) // We should defer further loads on multipart resources on the same lines // as regular resources requested by plugins to prevent reentrancy. int64 data_offset = byte_range_lower_bound_; byte_range_lower_bound_ += data_length; plugin_stream_->DidReceiveData(data, data_length, data_offset); // DANGER: this instance may be deleted at this point. } private: // The lower bound of the byte range. int64 byte_range_lower_bound_; // The handler for the data. PluginStreamUrl* plugin_stream_; }; } // namespace PluginURLFetcher::PluginURLFetcher(PluginStreamUrl* plugin_stream, const GURL& url, const GURL& first_party_for_cookies, const std::string& method, const char* buf, unsigned int len, const GURL& referrer, const std::string& range, bool notify_redirects, bool is_plugin_src_load, int origin_pid, int render_frame_id, int render_view_id, unsigned long resource_id, bool copy_stream_data) : plugin_stream_(plugin_stream), url_(url), first_party_for_cookies_(first_party_for_cookies), referrer_(referrer), notify_redirects_(notify_redirects), is_plugin_src_load_(is_plugin_src_load), origin_pid_(origin_pid), render_frame_id_(render_frame_id), render_view_id_(render_view_id), resource_id_(resource_id), copy_stream_data_(copy_stream_data), data_offset_(0), pending_failure_notification_(false) { RequestInfo request_info; request_info.method = method; request_info.url = url; request_info.first_party_for_cookies = first_party_for_cookies; request_info.referrer = referrer; request_info.load_flags = net::LOAD_NORMAL; request_info.requestor_pid = origin_pid; request_info.request_type = RESOURCE_TYPE_OBJECT; request_info.routing_id = render_view_id; RequestExtraData extra_data; extra_data.set_render_frame_id(render_frame_id); extra_data.set_is_main_frame(false); request_info.extra_data = &extra_data; std::vector<char> body; if (method == "POST") { bool content_type_found = false; std::vector<std::string> names; std::vector<std::string> values; PluginHost::SetPostData(buf, len, &names, &values, &body); for (size_t i = 0; i < names.size(); ++i) { if (!request_info.headers.empty()) request_info.headers += "\r\n"; request_info.headers += names[i] + ": " + values[i]; if (LowerCaseEqualsASCII(names[i], "content-type")) content_type_found = true; } if (!content_type_found) { if (!request_info.headers.empty()) request_info.headers += "\r\n"; request_info.headers += "Content-Type: application/x-www-form-urlencoded"; } } else { if (!range.empty()) request_info.headers = std::string("Range: ") + range; } bridge_.reset(ChildThread::current()->resource_dispatcher()->CreateBridge( request_info)); if (!body.empty()) { scoped_refptr<ResourceRequestBody> request_body = new ResourceRequestBody; request_body->AppendBytes(&body[0], body.size()); bridge_->SetRequestBody(request_body.get()); } bridge_->Start(this); // TODO(jam): range requests } PluginURLFetcher::~PluginURLFetcher() { } void PluginURLFetcher::Cancel() { bridge_->Cancel(); // Due to races and nested event loops, PluginURLFetcher may still receive // events from the bridge before being destroyed. Do not forward additional // events back to the plugin, via either |plugin_stream_| or // |multipart_delegate_| which has its own pointer via // MultiPartResponseClient. if (multipart_delegate_) multipart_delegate_->Cancel(); plugin_stream_ = NULL; } void PluginURLFetcher::URLRedirectResponse(bool allow) { if (!plugin_stream_) return; if (allow) { bridge_->SetDefersLoading(false); } else { bridge_->Cancel(); plugin_stream_->DidFail(resource_id_); // That will delete |this|. } } void PluginURLFetcher::OnUploadProgress(uint64 position, uint64 size) { } bool PluginURLFetcher::OnReceivedRedirect( const net::RedirectInfo& redirect_info, const ResourceResponseInfo& info) { if (!plugin_stream_) return false; // TODO(jam): THIS LOGIC IS COPIED FROM WebPluginImpl::willSendRequest until // kDirectNPAPIRequests is the default and we can remove the old path there. // Currently this check is just to catch an https -> http redirect when // loading the main plugin src URL. Longer term, we could investigate // firing mixed diplay or scripting issues for subresource loads // initiated by plug-ins. if (is_plugin_src_load_ && !plugin_stream_->instance()->webplugin()->CheckIfRunInsecureContent( redirect_info.new_url)) { plugin_stream_->DidFail(resource_id_); // That will delete |this|. return false; } GURL old_url = url_; url_ = redirect_info.new_url; first_party_for_cookies_ = redirect_info.new_first_party_for_cookies; // If the plugin does not participate in url redirect notifications then just // block cross origin 307 POST redirects. if (!notify_redirects_) { if (redirect_info.status_code == 307 && redirect_info.new_method == "POST" && old_url.GetOrigin() != url_.GetOrigin()) { plugin_stream_->DidFail(resource_id_); // That will delete |this|. return false; } } else { // Pause the request while we ask the plugin what to do about the redirect. bridge_->SetDefersLoading(true); plugin_stream_->WillSendRequest(url_, redirect_info.status_code); } return true; } void PluginURLFetcher::OnReceivedResponse(const ResourceResponseInfo& info) { if (!plugin_stream_) return; // TODO(jam): THIS LOGIC IS COPIED FROM WebPluginImpl::didReceiveResponse // GetAllHeaders, and GetResponseInfo until kDirectNPAPIRequests is the // default and we can remove the old path there. bool request_is_seekable = true; DCHECK(!multipart_delegate_.get()); if (plugin_stream_->seekable()) { int response_code = info.headers->response_code(); if (response_code == 206) { blink::WebURLResponse response; response.initialize(); WebURLLoaderImpl::PopulateURLResponse(url_, info, &response); std::string multipart_boundary; if (MultipartResponseDelegate::ReadMultipartBoundary( response, &multipart_boundary)) { plugin_stream_->instance()->webplugin()->DidStartLoading(); MultiPartResponseClient* multi_part_response_client = new MultiPartResponseClient(plugin_stream_); multipart_delegate_.reset(new MultipartResponseDelegate( multi_part_response_client, NULL, response, multipart_boundary)); // Multiple ranges requested, data will be delivered by // MultipartResponseDelegate. data_offset_ = 0; return; } int64 upper_bound = 0, instance_size = 0; // Single range requested - go through original processing for // non-multipart requests, but update data offset. MultipartResponseDelegate::ReadContentRanges( response, &data_offset_, &upper_bound, &instance_size); } else if (response_code == 200) { // TODO: should we handle this case? We used to but it's not clear that we // still need to. This was bug 5403, fixed in r7139. } } // If the length comes in as -1, then it indicates that it was not // read off the HTTP headers. We replicate Safari webkit behavior here, // which is to set it to 0. int expected_length = std::max(static_cast<int>(info.content_length), 0); base::Time temp; uint32 last_modified = 0; std::string headers; if (info.headers.get()) { // NULL for data: urls. if (info.headers->GetLastModifiedValue(&temp)) last_modified = static_cast<uint32>(temp.ToDoubleT()); // TODO(darin): Shouldn't we also report HTTP version numbers? int response_code = info.headers->response_code(); headers = base::StringPrintf("HTTP %d ", response_code); headers += info.headers->GetStatusText(); headers += "\n"; void* iter = NULL; std::string name, value; while (info.headers->EnumerateHeaderLines(&iter, &name, &value)) { // TODO(darin): Should we really exclude headers with an empty value? if (!name.empty() && !value.empty()) headers += name + ": " + value + "\n"; } // Bug http://b/issue?id=925559. The flash plugin would not handle the HTTP // error codes in the stream header and as a result, was unaware of the fate // of the HTTP requests issued via NPN_GetURLNotify. Webkit and FF destroy // the stream and invoke the NPP_DestroyStream function on the plugin if the // HTTPrequest fails. if ((url_.SchemeIs(url::kHttpScheme) || url_.SchemeIs(url::kHttpsScheme)) && (response_code < 100 || response_code >= 400)) { pending_failure_notification_ = true; } } plugin_stream_->DidReceiveResponse(info.mime_type, headers, expected_length, last_modified, request_is_seekable); } void PluginURLFetcher::OnDownloadedData(int len, int encoded_data_length) { } void PluginURLFetcher::OnReceivedData(const char* data, int data_length, int encoded_data_length) { if (!plugin_stream_) return; if (multipart_delegate_) { multipart_delegate_->OnReceivedData(data, data_length, encoded_data_length); } else { int64 offset = data_offset_; data_offset_ += data_length; if (copy_stream_data_) { // QuickTime writes to this memory, and since we got it from // ResourceDispatcher it's not mapped for write access in this process. // http://crbug.com/308466. scoped_ptr<char[]> data_copy(new char[data_length]); memcpy(data_copy.get(), data, data_length); plugin_stream_->DidReceiveData(data_copy.get(), data_length, offset); } else { plugin_stream_->DidReceiveData(data, data_length, offset); } // DANGER: this instance may be deleted at this point. } } void PluginURLFetcher::OnCompletedRequest( int error_code, bool was_ignored_by_handler, bool stale_copy_in_cache, const std::string& security_info, const base::TimeTicks& completion_time, int64 total_transfer_size) { if (!plugin_stream_) return; if (multipart_delegate_) { multipart_delegate_->OnCompletedRequest(); multipart_delegate_.reset(); } if (error_code == net::OK) { plugin_stream_->DidFinishLoading(resource_id_); } else { plugin_stream_->DidFail(resource_id_); } } } // namespace content