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