diff options
Diffstat (limited to 'chrome/browser/automation/url_request_automation_job.cc')
-rw-r--r-- | chrome/browser/automation/url_request_automation_job.cc | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/chrome/browser/automation/url_request_automation_job.cc b/chrome/browser/automation/url_request_automation_job.cc new file mode 100644 index 0000000..6cdfdbf --- /dev/null +++ b/chrome/browser/automation/url_request_automation_job.cc @@ -0,0 +1,460 @@ +// Copyright (c) 2010 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/message_loop.h" +#include "base/time.h" +#include "chrome/browser/automation/automation_resource_message_filter.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "chrome/test/automation/automation_messages.h" +#include "net/base/cookie_monster.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request_context.h" + +using base::Time; +using base::TimeDelta; + +// The list of filtered headers that are removed from requests sent via +// StartAsync(). These must be lower case. +static const char* const kFilteredHeaderStrings[] = { + "accept", + "cache-control", + "connection", + "cookie", + "expect", + "max-forwards", + "proxy-authorization", + "te", + "upgrade", + "via" +}; + +int URLRequestAutomationJob::instance_count_ = 0; +bool URLRequestAutomationJob::is_protocol_factory_registered_ = false; + +URLRequest::ProtocolFactory* URLRequestAutomationJob::old_http_factory_ + = NULL; +URLRequest::ProtocolFactory* URLRequestAutomationJob::old_https_factory_ + = NULL; + +URLRequestAutomationJob::URLRequestAutomationJob(URLRequest* request, int tab, + int request_id, AutomationResourceMessageFilter* filter, bool is_pending) + : URLRequestJob(request), + tab_(tab), + message_filter_(filter), + pending_buf_size_(0), + redirect_status_(0), + request_id_(request_id), + is_pending_(is_pending) { + DLOG(INFO) << "URLRequestAutomationJob create. Count: " << ++instance_count_; + DCHECK(message_filter_ != NULL); + + if (message_filter_) { + id_ = message_filter_->NewAutomationRequestId(); + DCHECK_NE(id_, 0); + } +} + +URLRequestAutomationJob::~URLRequestAutomationJob() { + DLOG(INFO) << "URLRequestAutomationJob delete. Count: " << --instance_count_; + Cleanup(); +} + +bool URLRequestAutomationJob::EnsureProtocolFactoryRegistered() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + + if (!is_protocol_factory_registered_) { + old_http_factory_ = + URLRequest::RegisterProtocolFactory("http", + &URLRequestAutomationJob::Factory); + old_https_factory_ = + URLRequest::RegisterProtocolFactory("https", + &URLRequestAutomationJob::Factory); + is_protocol_factory_registered_ = true; + } + + return true; +} + +URLRequestJob* URLRequestAutomationJob::Factory(URLRequest* request, + 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) { + ResourceDispatcherHostRequestInfo* request_info = + ResourceDispatcherHost::InfoForRequest(request); + if (request_info) { + int child_id = request_info->child_id(); + int route_id = request_info->route_id(); + + if (request_info->process_type() == ChildProcessInfo::PLUGIN_PROCESS) { + child_id = request_info->host_renderer_id(); + route_id = request_info->host_render_view_id(); + } + + AutomationResourceMessageFilter::AutomationDetails details; + if (AutomationResourceMessageFilter::LookupRegisteredRenderView( + child_id, route_id, &details)) { + URLRequestAutomationJob* job = new URLRequestAutomationJob(request, + details.tab_handle, request_info->request_id(), details.filter, + details.is_pending_render_view); + return job; + } + } + + if (scheme_is_http && old_http_factory_) + return old_http_factory_(request, scheme); + else if (scheme_is_https && old_https_factory_) + return old_https_factory_(request, scheme); + } + return NULL; +} + +// 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. + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + this, &URLRequestAutomationJob::StartAsync)); + } 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(0, tab_, id_, + URLRequestStatus(URLRequestStatus::CANCELED, net::ERR_ABORTED))); + } + } + DisconnectFromMessageFilter(); + URLRequestJob::Kill(); +} + +bool URLRequestAutomationJob::ReadRawData( + net::IOBuffer* buf, int buf_size, int* bytes_read) { + DLOG(INFO) << "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_) { + message_filter_->Send(new AutomationMsg_RequestRead(0, tab_, id_, + buf_size)); + SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); + } else { + ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, + NewRunnableMethod(this, + &URLRequestAutomationJob::NotifyJobCompletionTask)); + } + return false; +} + +bool URLRequestAutomationJob::GetMimeType(std::string* mime_type) const { + if (!mime_type_.empty()) { + *mime_type = mime_type_; + } else if (headers_) { + headers_->GetMimeType(mime_type); + } + + return (!mime_type->empty()); +} + +bool URLRequestAutomationJob::GetCharset(std::string* charset) { + if (headers_) + return headers_->GetCharset(charset); + return false; +} + +void URLRequestAutomationJob::GetResponseInfo(net::HttpResponseInfo* info) { + if (headers_) + 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 = 0; + } +} + +int URLRequestAutomationJob::GetResponseCode() const { + if (headers_) + 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; +} + +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: { + void* iter = NULL; + int tab = 0; + if (message.ReadInt(&iter, &tab) && + 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; + } + + IPC_BEGIN_MESSAGE_MAP(URLRequestAutomationJob, message) + IPC_MESSAGE_HANDLER(AutomationMsg_RequestStarted, OnRequestStarted) + IPC_MESSAGE_HANDLER(AutomationMsg_RequestData, OnDataAvailable) + IPC_MESSAGE_HANDLER(AutomationMsg_RequestEnd, OnRequestEnd) + IPC_END_MESSAGE_MAP() +} + +void URLRequestAutomationJob::OnRequestStarted(int tab, int id, + const IPC::AutomationURLResponse& response) { + DLOG(INFO) << "URLRequestAutomationJob: " << + request_->url().spec() << " - response started."; + set_expected_content_size(response.content_length); + mime_type_ = response.mime_type; + + 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())); + } + NotifyHeadersComplete(); +} + +void URLRequestAutomationJob::OnDataAvailable( + int tab, int id, const std::string& bytes) { + DLOG(INFO) << "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(URLRequestStatus()); + + if (pending_buf_ && 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 tab, int id, const URLRequestStatus& status) { +#ifndef NDEBUG + std::string url; + if (request_) + url = request_->url().spec(); + DLOG(INFO) << "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() == URLRequestStatus::FAILED && + // net::IsCertificateError(status.os_error()) && request_->delegate()) { + // request_->delegate()->OnSSLCertificateError(request_, status.os_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() || pending_buf_) { + NotifyDone(status); + } else { + // Wait for the http stack to issue a Read request where we will notify + // that the job has completed. + request_status_ = status; + return; + } + } + + // Reset any pending reads. + if (pending_buf_) { + pending_buf_ = NULL; + pending_buf_size_ = 0; + NotifyReadComplete(0); + } +} + +void URLRequestAutomationJob::Cleanup() { + headers_ = NULL; + mime_type_.erase(); + + id_ = 0; + tab_ = 0; + + DCHECK(message_filter_ == NULL); + DisconnectFromMessageFilter(); + + pending_buf_ = NULL; + pending_buf_size_ = 0; +} + +void URLRequestAutomationJob::StartAsync() { + DLOG(INFO) << "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(URLRequestStatus(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]); + + if (request_->context()) { + // Only add default Accept-Language and Accept-Charset if the request + // didn't have them specified. + if (!new_request_headers.HasHeader( + net::HttpRequestHeaders::kAcceptLanguage) && + !request_->context()->accept_language().empty()) { + new_request_headers.SetHeader(net::HttpRequestHeaders::kAcceptLanguage, + request_->context()->accept_language()); + } + if (!new_request_headers.HasHeader( + net::HttpRequestHeaders::kAcceptCharset) && + !request_->context()->accept_charset().empty()) { + new_request_headers.SetHeader(net::HttpRequestHeaders::kAcceptCharset, + request_->context()->accept_charset()); + } + } + + // Ensure that we do not send username and password fields in the referrer. + GURL referrer(request_->GetSanitizedReferrer()); + + // 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()) { + DLOG(INFO) << + "Suppressing referrer header since going from secure to non-secure"; + referrer = GURL(); + } + + // Ask automation to start this request. + IPC::AutomationURLRequest automation_request = { + request_->url().spec(), + request_->method(), + referrer.spec(), + new_request_headers.ToString(), + request_->get_upload() + }; + + DCHECK(message_filter_); + message_filter_->Send(new AutomationMsg_RequestStart(0, tab_, id_, + automation_request)); +} + +void URLRequestAutomationJob::DisconnectFromMessageFilter() { + if (message_filter_) { + 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_) { + pending_buf_ = NULL; + pending_buf_size_ = 0; + NotifyReadComplete(0); + } +} |