// 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