blob: fba5f8560ae58cc10d2ecf5c9a32251fdbeb76b7 [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/automation/url_request_automation_job.h"
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "chrome/browser/automation/automation_resource_message_filter.h"
#include "chrome/common/automation_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/resource_request_info.h"
#include "net/base/host_port_pair.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_data_stream.h"
#include "net/base/upload_file_element_reader.h"
#include "net/cookies/cookie_monster.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/url_request/http_user_agent_settings.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
using base::Time;
using base::TimeDelta;
using content::BrowserThread;
using content::ResourceRequestInfo;
namespace {
// The list of filtered headers that are removed from requests sent via
// StartAsync(). These must be lower case.
const char* const kFilteredHeaderStrings[] = {
"connection",
"cookie",
"expect",
"max-forwards",
"proxy-authorization",
"referer",
"te",
"upgrade",
"via"
};
// Creates UploadData from UploadDataStream.
net::UploadData* CreateUploadData(
const net::UploadDataStream* upload_data_stream) {
net::UploadData* upload_data = new net::UploadData();
const ScopedVector<net::UploadElementReader>& element_readers =
upload_data_stream->element_readers();
for (size_t i = 0; i < element_readers.size(); ++i) {
const net::UploadElementReader* reader = element_readers[i];
if (reader->AsBytesReader()) {
const net::UploadBytesElementReader* bytes_reader =
reader->AsBytesReader();
upload_data->AppendBytes(bytes_reader->bytes(), bytes_reader->length());
} else if (reader->AsFileReader()) {
const net::UploadFileElementReader* file_reader =
reader->AsFileReader();
upload_data->AppendFileRange(file_reader->path(),
file_reader->range_offset(),
file_reader->range_length(),
file_reader->expected_modification_time());
} else {
NOTIMPLEMENTED();
}
}
upload_data->set_identifier(upload_data_stream->identifier());
upload_data->set_is_chunked(upload_data_stream->is_chunked());
upload_data->set_last_chunk_appended(
upload_data_stream->last_chunk_appended());
return upload_data;
}
} // namespace
int URLRequestAutomationJob::instance_count_ = 0;
bool URLRequestAutomationJob::is_protocol_factory_registered_ = false;
net::URLRequest::ProtocolFactory* URLRequestAutomationJob::old_http_factory_
= NULL;
net::URLRequest::ProtocolFactory* URLRequestAutomationJob::old_https_factory_
= NULL;
URLRequestAutomationJob::URLRequestAutomationJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate,
const net::HttpUserAgentSettings* http_user_agent_settings,
int tab,
int request_id,
AutomationResourceMessageFilter* filter,
bool is_pending)
: net::URLRequestJob(request, network_delegate),
http_user_agent_settings_(http_user_agent_settings),
id_(0),
tab_(tab),
message_filter_(filter),
pending_buf_size_(0),
redirect_status_(0),
request_id_(request_id),
is_pending_(is_pending),
upload_size_(0),
weak_factory_(this) {
DVLOG(1) << "URLRequestAutomationJob create. Count: " << ++instance_count_;
DCHECK(message_filter_.get() != NULL);
if (message_filter_.get()) {
id_ = message_filter_->NewAutomationRequestId();
DCHECK_NE(id_, 0);
}
}
URLRequestAutomationJob::~URLRequestAutomationJob() {
DVLOG(1) << "URLRequestAutomationJob delete. Count: " << --instance_count_;
Cleanup();
}
void URLRequestAutomationJob::EnsureProtocolFactoryRegistered() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!is_protocol_factory_registered_) {
old_http_factory_ =
net::URLRequest::Deprecated::RegisterProtocolFactory(
"http", &URLRequestAutomationJob::Factory);
old_https_factory_ =
net::URLRequest::Deprecated::RegisterProtocolFactory(
"https", &URLRequestAutomationJob::Factory);
is_protocol_factory_registered_ = true;
}
}
net::URLRequestJob* URLRequestAutomationJob::Factory(
net::URLRequest* request,
net::NetworkDelegate* network_delegate,
const std::string& scheme) {
bool scheme_is_http = request->url().SchemeIs("http");
bool scheme_is_https = request->url().SchemeIs("https");
// Returning null here just means that the built-in handler will be used.
if (scheme_is_http || scheme_is_https) {
const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
if (info) {
int child_id = info->GetChildID();
int route_id = info->GetRouteID();
AutomationResourceMessageFilter::AutomationDetails details;
if (AutomationResourceMessageFilter::LookupRegisteredRenderView(
child_id, route_id, &details)) {
URLRequestAutomationJob* job = new URLRequestAutomationJob(
request,
network_delegate,
request->context()->http_user_agent_settings(),
details.tab_handle,
info->GetRequestID(),
details.filter.get(),
details.is_pending_render_view);
return job;
}
}
if (scheme_is_http && old_http_factory_)
return old_http_factory_(request, network_delegate, scheme);
else if (scheme_is_https && old_https_factory_)
return old_https_factory_(request, network_delegate, scheme);
}
return NULL;
}
// net::URLRequestJob Implementation.
void URLRequestAutomationJob::Start() {
if (!is_pending()) {
// Start reading asynchronously so that all error reporting and data
// callbacks happen as they would for network requests.
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&URLRequestAutomationJob::StartAsync,
weak_factory_.GetWeakPtr()));
} else {
// If this is a pending job, then register it immediately with the message
// filter so it can be serviced later when we receive a request from the
// external host to connect to the corresponding external tab.
message_filter_->RegisterRequest(this);
}
}
void URLRequestAutomationJob::Kill() {
if (message_filter_.get()) {
if (!is_pending()) {
message_filter_->Send(new AutomationMsg_RequestEnd(tab_, id_,
net::URLRequestStatus(net::URLRequestStatus::CANCELED,
net::ERR_ABORTED)));
}
}
DisconnectFromMessageFilter();
receive_headers_end_ = base::TimeTicks();
net::URLRequestJob::Kill();
}
bool URLRequestAutomationJob::ReadRawData(
net::IOBuffer* buf, int buf_size, int* bytes_read) {
DVLOG(1) << "URLRequestAutomationJob: " << request_->url().spec()
<< " - read pending: " << buf_size;
// We should not receive a read request for a pending job.
DCHECK(!is_pending());
pending_buf_ = buf;
pending_buf_size_ = buf_size;
if (message_filter_.get()) {
message_filter_->Send(new AutomationMsg_RequestRead(tab_, id_, buf_size));
SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
} else {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&URLRequestAutomationJob::NotifyJobCompletionTask,
weak_factory_.GetWeakPtr()));
}
return false;
}
bool URLRequestAutomationJob::GetMimeType(std::string* mime_type) const {
if (!mime_type_.empty()) {
*mime_type = mime_type_;
} else if (headers_.get()) {
headers_->GetMimeType(mime_type);
}
return (!mime_type->empty());
}
bool URLRequestAutomationJob::GetCharset(std::string* charset) {
if (headers_.get())
return headers_->GetCharset(charset);
return false;
}
void URLRequestAutomationJob::GetResponseInfo(net::HttpResponseInfo* info) {
if (headers_.get())
info->headers = headers_;
if (request_->url().SchemeIsSecure()) {
// Make up a fake certificate for this response since we don't have
// access to the real SSL info.
const char* kCertIssuer = "Chrome Internal";
const int kLifetimeDays = 100;
info->ssl_info.cert =
new net::X509Certificate(request_->url().GetWithEmptyPath().spec(),
kCertIssuer,
Time::Now(),
Time::Now() +
TimeDelta::FromDays(kLifetimeDays));
info->ssl_info.cert_status = 0;
info->ssl_info.security_bits = -1;
}
}
void URLRequestAutomationJob::GetLoadTimingInfo(
net::LoadTimingInfo* load_timing_info) const {
if (!receive_headers_end_.is_null()) {
load_timing_info->send_start = request_start_;
// The send ended some time ago, but that information is not available on
// this side of the automation channel. Consider the send to have ended at
// the same time we received the response headers.
load_timing_info->send_end = receive_headers_end_;
load_timing_info->receive_headers_end = receive_headers_end_;
}
}
int URLRequestAutomationJob::GetResponseCode() const {
if (headers_.get())
return headers_->response_code();
static const int kDefaultResponseCode = 200;
return kDefaultResponseCode;
}
bool URLRequestAutomationJob::IsRedirectResponse(
GURL* location, int* http_status_code) {
if (!net::HttpResponseHeaders::IsRedirectResponseCode(redirect_status_))
return false;
*http_status_code = redirect_status_;
*location = GURL(redirect_url_);
return true;
}
net::UploadProgress URLRequestAutomationJob::GetUploadProgress() const {
uint64 progress = 0;
if (request_ && request_->status().is_success()) {
// We don't support incremental progress notifications in ChromeFrame. When
// we receive a response for the POST request from Chromeframe, it means
// that the upload is fully complete.
progress = upload_size_;
}
return net::UploadProgress(progress, upload_size_);
}
net::HostPortPair URLRequestAutomationJob::GetSocketAddress() const {
return socket_address_;
}
bool URLRequestAutomationJob::MayFilterMessage(const IPC::Message& message,
int* request_id) {
switch (message.type()) {
case AutomationMsg_RequestStarted::ID:
case AutomationMsg_RequestData::ID:
case AutomationMsg_RequestEnd::ID: {
PickleIterator iter(message);
if (message.ReadInt(&iter, request_id))
return true;
break;
}
}
return false;
}
void URLRequestAutomationJob::OnMessage(const IPC::Message& message) {
if (!request_) {
NOTREACHED() << __FUNCTION__
<< ": Unexpected request received for job:"
<< id();
return;
}
bool deserialize_success = false;
IPC_BEGIN_MESSAGE_MAP_EX(URLRequestAutomationJob,
message,
deserialize_success)
IPC_MESSAGE_HANDLER(AutomationMsg_RequestStarted, OnRequestStarted)
IPC_MESSAGE_HANDLER(AutomationMsg_RequestData, OnDataAvailable)
IPC_MESSAGE_HANDLER(AutomationMsg_RequestEnd, OnRequestEnd)
IPC_END_MESSAGE_MAP_EX()
if (!deserialize_success) {
LOG(ERROR) << "Failed to deserialize IPC message.";
}
}
void URLRequestAutomationJob::OnRequestStarted(
int id, const AutomationURLResponse& response) {
DVLOG(1) << "URLRequestAutomationJob: " << request_->url().spec()
<< " - response started.";
set_expected_content_size(response.content_length);
mime_type_ = response.mime_type;
receive_headers_end_ = base::TimeTicks::Now();
redirect_url_ = response.redirect_url;
redirect_status_ = response.redirect_status;
DCHECK(redirect_status_ == 0 || redirect_status_ == 200 ||
(redirect_status_ >= 300 && redirect_status_ < 400));
if (!response.headers.empty()) {
headers_ = new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(response.headers.data(),
response.headers.size()));
}
socket_address_ = response.socket_address;
upload_size_ = response.upload_size;
NotifyHeadersComplete();
}
void URLRequestAutomationJob::OnDataAvailable(
int id, const std::string& bytes) {
DVLOG(1) << "URLRequestAutomationJob: " << request_->url().spec()
<< " - data available, Size: " << bytes.size();
DCHECK(!bytes.empty());
// The request completed, and we have all the data.
// Clear any IO pending status.
SetStatus(net::URLRequestStatus());
if (pending_buf_.get() && pending_buf_->data()) {
DCHECK_GE(pending_buf_size_, bytes.size());
const int bytes_to_copy = std::min(bytes.size(), pending_buf_size_);
memcpy(pending_buf_->data(), &bytes[0], bytes_to_copy);
pending_buf_ = NULL;
pending_buf_size_ = 0;
NotifyReadComplete(bytes_to_copy);
} else {
NOTREACHED() << "Received unexpected data of length:" << bytes.size();
}
}
void URLRequestAutomationJob::OnRequestEnd(
int id, const net::URLRequestStatus& status) {
#ifndef NDEBUG
std::string url;
if (request_)
url = request_->url().spec();
DVLOG(1) << "URLRequestAutomationJob: " << url << " - request end. Status: "
<< status.status();
#endif
// TODO(tommi): When we hit certificate errors, notify the delegate via
// OnSSLCertificateError(). Right now we don't have the certificate
// so we don't. We could possibly call OnSSLCertificateError with a NULL
// certificate, but I'm not sure if all implementations expect it.
// if (status.status() == net::URLRequestStatus::FAILED &&
// net::IsCertificateError(status.error()) && request_->delegate()) {
// request_->delegate()->OnSSLCertificateError(request_, status.error());
// }
DisconnectFromMessageFilter();
// NotifyDone may have been called on the job if the original request was
// redirected.
if (!is_done()) {
// We can complete the job if we have a valid response or a pending read.
// An end request can be received in the following cases
// 1. We failed to connect to the server, in which case we did not receive
// a valid response.
// 2. In response to a read request.
if (!has_response_started()) {
NotifyStartError(status);
} else if (pending_buf_.get()) {
pending_buf_ = NULL;
pending_buf_size_ = 0;
NotifyDone(status);
NotifyReadComplete(0);
} else {
// Wait for the http stack to issue a Read request where we will notify
// that the job has completed.
request_status_ = status;
}
}
// Note
// The job could have been destroyed above. Please don't attempt to access
// member variables here.
}
void URLRequestAutomationJob::Cleanup() {
headers_ = NULL;
mime_type_.erase();
id_ = 0;
tab_ = 0;
DCHECK(!message_filter_.get());
DisconnectFromMessageFilter();
pending_buf_ = NULL;
pending_buf_size_ = 0;
}
void URLRequestAutomationJob::StartAsync() {
DVLOG(1) << "URLRequestAutomationJob: start request: "
<< (request_ ? request_->url().spec() : "NULL request");
// If the job is cancelled before we got a chance to start it
// we have nothing much to do here.
if (is_done())
return;
// We should not receive a Start request for a pending job.
DCHECK(!is_pending());
if (!request_) {
NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
net::ERR_FAILED));
return;
}
// Register this request with automation message filter.
message_filter_->RegisterRequest(this);
// Strip unwanted headers.
net::HttpRequestHeaders new_request_headers;
new_request_headers.MergeFrom(request_->extra_request_headers());
for (size_t i = 0; i < arraysize(kFilteredHeaderStrings); ++i)
new_request_headers.RemoveHeader(kFilteredHeaderStrings[i]);
// Only add default Accept-Language if the request didn't have it specified.
if (!new_request_headers.HasHeader(
net::HttpRequestHeaders::kAcceptLanguage) &&
http_user_agent_settings_) {
std::string accept_language =
http_user_agent_settings_->GetAcceptLanguage();
if (!accept_language.empty()) {
new_request_headers.SetHeader(net::HttpRequestHeaders::kAcceptLanguage,
accept_language);
}
}
// URLRequest::SetReferrer() ensures that we do not send username and
// password fields in the referrer.
GURL referrer(request_->referrer());
// The referrer header must be suppressed if the preceding URL was
// a secure one and the new one is not.
if (referrer.SchemeIsSecure() && !request_->url().SchemeIsSecure()) {
DVLOG(1) << "Suppressing referrer header since going from secure to "
"non-secure";
referrer = GURL();
}
// Get the resource type (main_frame/script/image/stylesheet etc.
const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_);
ResourceType::Type resource_type = ResourceType::MAIN_FRAME;
if (info) {
resource_type = info->GetResourceType();
}
// Construct UploadData from UploadDataStream.
scoped_refptr<net::UploadData> upload_data;
if (request_->get_upload())
upload_data = CreateUploadData(request_->get_upload());
request_start_ = base::TimeTicks::Now();
// Ask automation to start this request.
AutomationURLRequest automation_request;
automation_request.url = request_->url().spec();
automation_request.method = request_->method();
automation_request.referrer = referrer.spec();
automation_request.extra_request_headers = new_request_headers.ToString();
automation_request.upload_data = upload_data;
automation_request.resource_type = resource_type;
automation_request.load_flags = request_->load_flags();
DCHECK(message_filter_.get());
message_filter_->Send(
new AutomationMsg_RequestStart(tab_, id_, automation_request));
}
void URLRequestAutomationJob::DisconnectFromMessageFilter() {
if (message_filter_.get()) {
message_filter_->UnRegisterRequest(this);
message_filter_ = NULL;
}
}
void URLRequestAutomationJob::StartPendingJob(
int new_tab_handle,
AutomationResourceMessageFilter* new_filter) {
DCHECK(new_filter != NULL);
tab_ = new_tab_handle;
message_filter_ = new_filter;
is_pending_ = false;
Start();
}
void URLRequestAutomationJob::NotifyJobCompletionTask() {
if (!is_done()) {
NotifyDone(request_status_);
}
// Reset any pending reads.
if (pending_buf_.get()) {
pending_buf_ = NULL;
pending_buf_size_ = 0;
NotifyReadComplete(0);
}
}