url_request_automation_job.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/automation/url_request_automation_job.h" 6 7#include "base/message_loop.h" 8#include "base/time.h" 9#include "chrome/browser/automation/automation_resource_message_filter.h" 10#include "chrome/browser/browser_thread.h" 11#include "chrome/browser/renderer_host/render_view_host.h" 12#include "chrome/browser/renderer_host/resource_dispatcher_host.h" 13#include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h" 14#include "chrome/test/automation/automation_messages.h" 15#include "net/base/cookie_monster.h" 16#include "net/base/io_buffer.h" 17#include "net/base/net_errors.h" 18#include "net/http/http_response_headers.h" 19#include "net/http/http_request_headers.h" 20#include "net/http/http_util.h" 21#include "net/url_request/url_request_context.h" 22 23using base::Time; 24using base::TimeDelta; 25 26// The list of filtered headers that are removed from requests sent via 27// StartAsync(). These must be lower case. 28static const char* const kFilteredHeaderStrings[] = { 29 "connection", 30 "cookie", 31 "expect", 32 "max-forwards", 33 "proxy-authorization", 34 "te", 35 "upgrade", 36 "via" 37}; 38 39int URLRequestAutomationJob::instance_count_ = 0; 40bool URLRequestAutomationJob::is_protocol_factory_registered_ = false; 41 42URLRequest::ProtocolFactory* URLRequestAutomationJob::old_http_factory_ 43 = NULL; 44URLRequest::ProtocolFactory* URLRequestAutomationJob::old_https_factory_ 45 = NULL; 46 47URLRequestAutomationJob::URLRequestAutomationJob(URLRequest* request, int tab, 48 int request_id, AutomationResourceMessageFilter* filter, bool is_pending) 49 : URLRequestJob(request), 50 id_(0), 51 tab_(tab), 52 message_filter_(filter), 53 pending_buf_size_(0), 54 redirect_status_(0), 55 request_id_(request_id), 56 is_pending_(is_pending) { 57 DLOG(INFO) << "URLRequestAutomationJob create. Count: " << ++instance_count_; 58 DCHECK(message_filter_ != NULL); 59 60 if (message_filter_) { 61 id_ = message_filter_->NewAutomationRequestId(); 62 DCHECK_NE(id_, 0); 63 } 64} 65 66URLRequestAutomationJob::~URLRequestAutomationJob() { 67 DLOG(INFO) << "URLRequestAutomationJob delete. Count: " << --instance_count_; 68 Cleanup(); 69} 70 71bool URLRequestAutomationJob::EnsureProtocolFactoryRegistered() { 72 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 73 74 if (!is_protocol_factory_registered_) { 75 old_http_factory_ = 76 URLRequest::RegisterProtocolFactory("http", 77 &URLRequestAutomationJob::Factory); 78 old_https_factory_ = 79 URLRequest::RegisterProtocolFactory("https", 80 &URLRequestAutomationJob::Factory); 81 is_protocol_factory_registered_ = true; 82 } 83 84 return true; 85} 86 87URLRequestJob* URLRequestAutomationJob::Factory(URLRequest* request, 88 const std::string& scheme) { 89 bool scheme_is_http = request->url().SchemeIs("http"); 90 bool scheme_is_https = request->url().SchemeIs("https"); 91 92 // Returning null here just means that the built-in handler will be used. 93 if (scheme_is_http || scheme_is_https) { 94 ResourceDispatcherHostRequestInfo* request_info = 95 ResourceDispatcherHost::InfoForRequest(request); 96 if (request_info) { 97 int child_id = request_info->child_id(); 98 int route_id = request_info->route_id(); 99 100 if (request_info->process_type() == ChildProcessInfo::PLUGIN_PROCESS) { 101 child_id = request_info->host_renderer_id(); 102 route_id = request_info->host_render_view_id(); 103 } 104 105 AutomationResourceMessageFilter::AutomationDetails details; 106 if (AutomationResourceMessageFilter::LookupRegisteredRenderView( 107 child_id, route_id, &details)) { 108 URLRequestAutomationJob* job = new URLRequestAutomationJob(request, 109 details.tab_handle, request_info->request_id(), details.filter, 110 details.is_pending_render_view); 111 return job; 112 } 113 } 114 115 if (scheme_is_http && old_http_factory_) 116 return old_http_factory_(request, scheme); 117 else if (scheme_is_https && old_https_factory_) 118 return old_https_factory_(request, scheme); 119 } 120 return NULL; 121} 122 123// URLRequestJob Implementation. 124void URLRequestAutomationJob::Start() { 125 if (!is_pending()) { 126 // Start reading asynchronously so that all error reporting and data 127 // callbacks happen as they would for network requests. 128 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( 129 this, &URLRequestAutomationJob::StartAsync)); 130 } else { 131 // If this is a pending job, then register it immediately with the message 132 // filter so it can be serviced later when we receive a request from the 133 // external host to connect to the corresponding external tab. 134 message_filter_->RegisterRequest(this); 135 } 136} 137 138void URLRequestAutomationJob::Kill() { 139 if (message_filter_.get()) { 140 if (!is_pending()) { 141 message_filter_->Send(new AutomationMsg_RequestEnd(0, tab_, id_, 142 URLRequestStatus(URLRequestStatus::CANCELED, net::ERR_ABORTED))); 143 } 144 } 145 DisconnectFromMessageFilter(); 146 URLRequestJob::Kill(); 147} 148 149bool URLRequestAutomationJob::ReadRawData( 150 net::IOBuffer* buf, int buf_size, int* bytes_read) { 151 DLOG(INFO) << "URLRequestAutomationJob: " << 152 request_->url().spec() << " - read pending: " << buf_size; 153 154 // We should not receive a read request for a pending job. 155 DCHECK(!is_pending()); 156 157 pending_buf_ = buf; 158 pending_buf_size_ = buf_size; 159 160 if (message_filter_) { 161 message_filter_->Send(new AutomationMsg_RequestRead(0, tab_, id_, 162 buf_size)); 163 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); 164 } else { 165 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 166 NewRunnableMethod(this, 167 &URLRequestAutomationJob::NotifyJobCompletionTask)); 168 } 169 return false; 170} 171 172bool URLRequestAutomationJob::GetMimeType(std::string* mime_type) const { 173 if (!mime_type_.empty()) { 174 *mime_type = mime_type_; 175 } else if (headers_) { 176 headers_->GetMimeType(mime_type); 177 } 178 179 return (!mime_type->empty()); 180} 181 182bool URLRequestAutomationJob::GetCharset(std::string* charset) { 183 if (headers_) 184 return headers_->GetCharset(charset); 185 return false; 186} 187 188void URLRequestAutomationJob::GetResponseInfo(net::HttpResponseInfo* info) { 189 if (headers_) 190 info->headers = headers_; 191 if (request_->url().SchemeIsSecure()) { 192 // Make up a fake certificate for this response since we don't have 193 // access to the real SSL info. 194 const char* kCertIssuer = "Chrome Internal"; 195 const int kLifetimeDays = 100; 196 197 info->ssl_info.cert = 198 new net::X509Certificate(request_->url().GetWithEmptyPath().spec(), 199 kCertIssuer, 200 Time::Now(), 201 Time::Now() + 202 TimeDelta::FromDays(kLifetimeDays)); 203 info->ssl_info.cert_status = 0; 204 info->ssl_info.security_bits = -1; 205 } 206} 207 208int URLRequestAutomationJob::GetResponseCode() const { 209 if (headers_) 210 return headers_->response_code(); 211 212 static const int kDefaultResponseCode = 200; 213 return kDefaultResponseCode; 214} 215 216bool URLRequestAutomationJob::IsRedirectResponse( 217 GURL* location, int* http_status_code) { 218 if (!net::HttpResponseHeaders::IsRedirectResponseCode(redirect_status_)) 219 return false; 220 221 *http_status_code = redirect_status_; 222 *location = GURL(redirect_url_); 223 return true; 224} 225 226bool URLRequestAutomationJob::MayFilterMessage(const IPC::Message& message, 227 int* request_id) { 228 switch (message.type()) { 229 case AutomationMsg_RequestStarted::ID: 230 case AutomationMsg_RequestData::ID: 231 case AutomationMsg_RequestEnd::ID: { 232 void* iter = NULL; 233 int tab = 0; 234 if (message.ReadInt(&iter, &tab) && 235 message.ReadInt(&iter, request_id)) { 236 return true; 237 } 238 break; 239 } 240 } 241 242 return false; 243} 244 245void URLRequestAutomationJob::OnMessage(const IPC::Message& message) { 246 if (!request_) { 247 NOTREACHED() << __FUNCTION__ 248 << ": Unexpected request received for job:" 249 << id(); 250 return; 251 } 252 253 IPC_BEGIN_MESSAGE_MAP(URLRequestAutomationJob, message) 254 IPC_MESSAGE_HANDLER(AutomationMsg_RequestStarted, OnRequestStarted) 255 IPC_MESSAGE_HANDLER(AutomationMsg_RequestData, OnDataAvailable) 256 IPC_MESSAGE_HANDLER(AutomationMsg_RequestEnd, OnRequestEnd) 257 IPC_END_MESSAGE_MAP() 258} 259 260void URLRequestAutomationJob::OnRequestStarted(int tab, int id, 261 const IPC::AutomationURLResponse& response) { 262 DLOG(INFO) << "URLRequestAutomationJob: " << 263 request_->url().spec() << " - response started."; 264 set_expected_content_size(response.content_length); 265 mime_type_ = response.mime_type; 266 267 redirect_url_ = response.redirect_url; 268 redirect_status_ = response.redirect_status; 269 DCHECK(redirect_status_ == 0 || redirect_status_ == 200 || 270 (redirect_status_ >= 300 && redirect_status_ < 400)); 271 272 if (!response.headers.empty()) { 273 headers_ = new net::HttpResponseHeaders( 274 net::HttpUtil::AssembleRawHeaders(response.headers.data(), 275 response.headers.size())); 276 } 277 NotifyHeadersComplete(); 278} 279 280void URLRequestAutomationJob::OnDataAvailable( 281 int tab, int id, const std::string& bytes) { 282 DLOG(INFO) << "URLRequestAutomationJob: " << 283 request_->url().spec() << " - data available, Size: " << bytes.size(); 284 DCHECK(!bytes.empty()); 285 286 // The request completed, and we have all the data. 287 // Clear any IO pending status. 288 SetStatus(URLRequestStatus()); 289 290 if (pending_buf_ && pending_buf_->data()) { 291 DCHECK_GE(pending_buf_size_, bytes.size()); 292 const int bytes_to_copy = std::min(bytes.size(), pending_buf_size_); 293 memcpy(pending_buf_->data(), &bytes[0], bytes_to_copy); 294 295 pending_buf_ = NULL; 296 pending_buf_size_ = 0; 297 298 NotifyReadComplete(bytes_to_copy); 299 } else { 300 NOTREACHED() << "Received unexpected data of length:" << bytes.size(); 301 } 302} 303 304void URLRequestAutomationJob::OnRequestEnd( 305 int tab, int id, const URLRequestStatus& status) { 306#ifndef NDEBUG 307 std::string url; 308 if (request_) 309 url = request_->url().spec(); 310 DLOG(INFO) << "URLRequestAutomationJob: " 311 << url << " - request end. Status: " << status.status(); 312#endif 313 314 // TODO(tommi): When we hit certificate errors, notify the delegate via 315 // OnSSLCertificateError(). Right now we don't have the certificate 316 // so we don't. We could possibly call OnSSLCertificateError with a NULL 317 // certificate, but I'm not sure if all implementations expect it. 318 // if (status.status() == URLRequestStatus::FAILED && 319 // net::IsCertificateError(status.os_error()) && request_->delegate()) { 320 // request_->delegate()->OnSSLCertificateError(request_, status.os_error()); 321 // } 322 323 DisconnectFromMessageFilter(); 324 // NotifyDone may have been called on the job if the original request was 325 // redirected. 326 if (!is_done()) { 327 // We can complete the job if we have a valid response or a pending read. 328 // An end request can be received in the following cases 329 // 1. We failed to connect to the server, in which case we did not receive 330 // a valid response. 331 // 2. In response to a read request. 332 if (!has_response_started() || pending_buf_) { 333 NotifyDone(status); 334 } else { 335 // Wait for the http stack to issue a Read request where we will notify 336 // that the job has completed. 337 request_status_ = status; 338 return; 339 } 340 } 341 342 // Reset any pending reads. 343 if (pending_buf_) { 344 pending_buf_ = NULL; 345 pending_buf_size_ = 0; 346 NotifyReadComplete(0); 347 } 348} 349 350void URLRequestAutomationJob::Cleanup() { 351 headers_ = NULL; 352 mime_type_.erase(); 353 354 id_ = 0; 355 tab_ = 0; 356 357 DCHECK(message_filter_ == NULL); 358 DisconnectFromMessageFilter(); 359 360 pending_buf_ = NULL; 361 pending_buf_size_ = 0; 362} 363 364void URLRequestAutomationJob::StartAsync() { 365 DLOG(INFO) << "URLRequestAutomationJob: start request: " << 366 (request_ ? request_->url().spec() : "NULL request"); 367 368 // If the job is cancelled before we got a chance to start it 369 // we have nothing much to do here. 370 if (is_done()) 371 return; 372 373 // We should not receive a Start request for a pending job. 374 DCHECK(!is_pending()); 375 376 if (!request_) { 377 NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, 378 net::ERR_FAILED)); 379 return; 380 } 381 382 // Register this request with automation message filter. 383 message_filter_->RegisterRequest(this); 384 385 // Strip unwanted headers. 386 net::HttpRequestHeaders new_request_headers; 387 new_request_headers.MergeFrom(request_->extra_request_headers()); 388 for (size_t i = 0; i < arraysize(kFilteredHeaderStrings); ++i) 389 new_request_headers.RemoveHeader(kFilteredHeaderStrings[i]); 390 391 if (request_->context()) { 392 // Only add default Accept-Language and Accept-Charset if the request 393 // didn't have them specified. 394 if (!new_request_headers.HasHeader( 395 net::HttpRequestHeaders::kAcceptLanguage) && 396 !request_->context()->accept_language().empty()) { 397 new_request_headers.SetHeader(net::HttpRequestHeaders::kAcceptLanguage, 398 request_->context()->accept_language()); 399 } 400 if (!new_request_headers.HasHeader( 401 net::HttpRequestHeaders::kAcceptCharset) && 402 !request_->context()->accept_charset().empty()) { 403 new_request_headers.SetHeader(net::HttpRequestHeaders::kAcceptCharset, 404 request_->context()->accept_charset()); 405 } 406 } 407 408 // Ensure that we do not send username and password fields in the referrer. 409 GURL referrer(request_->GetSanitizedReferrer()); 410 411 // The referrer header must be suppressed if the preceding URL was 412 // a secure one and the new one is not. 413 if (referrer.SchemeIsSecure() && !request_->url().SchemeIsSecure()) { 414 DLOG(INFO) << 415 "Suppressing referrer header since going from secure to non-secure"; 416 referrer = GURL(); 417 } 418 419 // Get the resource type (main_frame/script/image/stylesheet etc. 420 ResourceDispatcherHostRequestInfo* request_info = 421 ResourceDispatcherHost::InfoForRequest(request_); 422 ResourceType::Type resource_type = ResourceType::MAIN_FRAME; 423 if (request_info) { 424 resource_type = request_info->resource_type(); 425 } 426 427 // Ask automation to start this request. 428 IPC::AutomationURLRequest automation_request = { 429 request_->url().spec(), 430 request_->method(), 431 referrer.spec(), 432 new_request_headers.ToString(), 433 request_->get_upload(), 434 resource_type, 435 request_->load_flags() 436 }; 437 438 DCHECK(message_filter_); 439 message_filter_->Send(new AutomationMsg_RequestStart(0, tab_, id_, 440 automation_request)); 441} 442 443void URLRequestAutomationJob::DisconnectFromMessageFilter() { 444 if (message_filter_) { 445 message_filter_->UnRegisterRequest(this); 446 message_filter_ = NULL; 447 } 448} 449 450void URLRequestAutomationJob::StartPendingJob( 451 int new_tab_handle, 452 AutomationResourceMessageFilter* new_filter) { 453 DCHECK(new_filter != NULL); 454 tab_ = new_tab_handle; 455 message_filter_ = new_filter; 456 is_pending_ = false; 457 Start(); 458} 459 460void URLRequestAutomationJob::NotifyJobCompletionTask() { 461 if (!is_done()) { 462 NotifyDone(request_status_); 463 } 464 // Reset any pending reads. 465 if (pending_buf_) { 466 pending_buf_ = NULL; 467 pending_buf_size_ = 0; 468 NotifyReadComplete(0); 469 } 470} 471