url_request_automation_job.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
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/chrome_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_request_headers.h"
19#include "net/http/http_util.h"
20#include "net/url_request/url_request_context.h"
21
22using base::Time;
23using base::TimeDelta;
24
25// The list of filtered headers that are removed from requests sent via
26// StartAsync(). These must be lower case.
27static const char* const kFilteredHeaderStrings[] = {
28  "accept",
29  "cache-control",
30  "connection",
31  "cookie",
32  "expect",
33  "max-forwards",
34  "proxy-authorization",
35  "te",
36  "upgrade",
37  "via"
38};
39
40int URLRequestAutomationJob::instance_count_ = 0;
41bool URLRequestAutomationJob::is_protocol_factory_registered_ = false;
42
43URLRequest::ProtocolFactory* URLRequestAutomationJob::old_http_factory_
44    = NULL;
45URLRequest::ProtocolFactory* URLRequestAutomationJob::old_https_factory_
46    = NULL;
47
48URLRequestAutomationJob::URLRequestAutomationJob(URLRequest* request, int tab,
49    int request_id, AutomationResourceMessageFilter* filter, bool is_pending)
50    : URLRequestJob(request),
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(ChromeThread::CurrentlyOn(ChromeThread::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    ChromeThread::PostTask(ChromeThread::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 = 0;
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  // Ask automation to start this request.
420  IPC::AutomationURLRequest automation_request = {
421    request_->url().spec(),
422    request_->method(),
423    referrer.spec(),
424    new_request_headers.ToString(),
425    request_->get_upload()
426  };
427
428  DCHECK(message_filter_);
429  message_filter_->Send(new AutomationMsg_RequestStart(0, tab_, id_,
430      automation_request));
431}
432
433void URLRequestAutomationJob::DisconnectFromMessageFilter() {
434  if (message_filter_) {
435    message_filter_->UnRegisterRequest(this);
436    message_filter_ = NULL;
437  }
438}
439
440void URLRequestAutomationJob::StartPendingJob(
441    int new_tab_handle,
442    AutomationResourceMessageFilter* new_filter) {
443  DCHECK(new_filter != NULL);
444  tab_ = new_tab_handle;
445  message_filter_ = new_filter;
446  is_pending_ = false;
447  Start();
448}
449
450void URLRequestAutomationJob::NotifyJobCompletionTask() {
451  if (!is_done()) {
452    NotifyDone(request_status_);
453  }
454  // Reset any pending reads.
455  if (pending_buf_) {
456    pending_buf_ = NULL;
457    pending_buf_size_ = 0;
458    NotifyReadComplete(0);
459  }
460}
461