1// Copyright (c) 2012 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/automation_resource_message_filter.h"
6
7#include "base/bind.h"
8#include "base/metrics/histogram.h"
9#include "base/path_service.h"
10#include "base/stl_util.h"
11#include "chrome/browser/automation/url_request_automation_job.h"
12#include "chrome/browser/content_settings/tab_specific_content_settings.h"
13#include "chrome/browser/net/url_request_mock_util.h"
14#include "chrome/common/automation_messages.h"
15#include "chrome/common/chrome_paths.h"
16#include "chrome/common/render_messages.h"
17#include "content/public/browser/browser_message_filter.h"
18#include "content/public/browser/browser_thread.h"
19#include "net/base/net_errors.h"
20#include "net/url_request/url_request_context.h"
21#include "net/url_request/url_request_filter.h"
22#include "url/gurl.h"
23
24using content::BrowserMessageFilter;
25using content::BrowserThread;
26
27base::LazyInstance<AutomationResourceMessageFilter::RenderViewMap>
28    AutomationResourceMessageFilter::filtered_render_views_ =
29        LAZY_INSTANCE_INITIALIZER;
30
31base::LazyInstance<AutomationResourceMessageFilter::CompletionCallbackMap>
32    AutomationResourceMessageFilter::completion_callback_map_ =
33        LAZY_INSTANCE_INITIALIZER;
34
35int AutomationResourceMessageFilter::unique_request_id_ = 1;
36int AutomationResourceMessageFilter::next_completion_callback_id_ = 0;
37
38AutomationResourceMessageFilter::AutomationDetails::AutomationDetails()
39    : tab_handle(0),
40      ref_count(1),
41      is_pending_render_view(false) {
42}
43
44AutomationResourceMessageFilter::AutomationDetails::AutomationDetails(
45    int tab,
46    AutomationResourceMessageFilter* flt,
47    bool pending_view)
48    : tab_handle(tab), ref_count(1), filter(flt),
49      is_pending_render_view(pending_view) {
50}
51
52AutomationResourceMessageFilter::AutomationDetails::~AutomationDetails() {}
53
54struct AutomationResourceMessageFilter::CookieCompletionInfo {
55  scoped_refptr<BrowserMessageFilter> filter;
56  net::URLRequestContext* context;
57  int render_process_id;
58  IPC::Message* reply_msg;
59  scoped_refptr<AutomationResourceMessageFilter> automation_message_filter;
60};
61
62AutomationResourceMessageFilter::AutomationResourceMessageFilter()
63    : channel_(NULL) {
64  // Ensure that an instance of the callback map is created.
65  completion_callback_map_.Get();
66  // Ensure that an instance of the render view map is created.
67  filtered_render_views_.Get();
68
69  BrowserThread::PostTask(
70      BrowserThread::IO, FROM_HERE,
71      base::Bind(&URLRequestAutomationJob::EnsureProtocolFactoryRegistered));
72}
73
74AutomationResourceMessageFilter::~AutomationResourceMessageFilter() {
75}
76
77// Called on the IPC thread:
78void AutomationResourceMessageFilter::OnFilterAdded(IPC::Channel* channel) {
79  DCHECK(!channel_);
80  channel_ = channel;
81}
82
83void AutomationResourceMessageFilter::OnFilterRemoved() {
84  channel_ = NULL;
85}
86
87// Called on the IPC thread:
88void AutomationResourceMessageFilter::OnChannelConnected(int32 peer_pid) {
89}
90
91// Called on the IPC thread:
92void AutomationResourceMessageFilter::OnChannelClosing() {
93  channel_ = NULL;
94  request_map_.clear();
95
96  // Only erase RenderViews which are associated with this
97  // AutomationResourceMessageFilter instance.
98  RenderViewMap::iterator index = filtered_render_views_.Get().begin();
99  while (index != filtered_render_views_.Get().end()) {
100    const AutomationDetails& details = (*index).second;
101    if (details.filter.get() == this) {
102      filtered_render_views_.Get().erase(index++);
103    } else {
104      index++;
105    }
106  }
107
108  CompletionCallbackMap::iterator callback_index =
109      completion_callback_map_.Get().begin();
110  while (callback_index != completion_callback_map_.Get().end()) {
111    const CookieCompletionInfo& cookie_completion_info =
112        (*callback_index).second;
113    if (cookie_completion_info.automation_message_filter.get() == this) {
114      completion_callback_map_.Get().erase(callback_index++);
115    } else {
116      callback_index++;
117    }
118  }
119}
120
121// Called on the IPC thread:
122bool AutomationResourceMessageFilter::OnMessageReceived(
123    const IPC::Message& message) {
124  int request_id;
125  if (URLRequestAutomationJob::MayFilterMessage(message, &request_id)) {
126    RequestMap::iterator it = request_map_.find(request_id);
127    if (it != request_map_.end()) {
128      URLRequestAutomationJob* job = it->second;
129      DCHECK(job);
130      if (job) {
131        job->OnMessage(message);
132        return true;
133      }
134    } else {
135      // This could occur if the request was stopped from Chrome which would
136      // delete it from the request map. If we receive data for this request
137      // from the host we should ignore it.
138      LOG(ERROR) << "Failed to find request id:" << request_id;
139      return true;
140    }
141  }
142
143  bool handled = true;
144  bool deserialize_success = true;
145  IPC_BEGIN_MESSAGE_MAP_EX(AutomationResourceMessageFilter,
146                           message,
147                           deserialize_success)
148    IPC_MESSAGE_HANDLER(AutomationMsg_GetCookiesHostResponse,
149                        OnGetCookiesHostResponse)
150    IPC_MESSAGE_UNHANDLED(handled = false)
151  IPC_END_MESSAGE_MAP_EX()
152
153  if (!deserialize_success) {
154    LOG(ERROR) << "Failed to deserialize IPC message. "
155               << "Closing the automation channel.";
156    channel_->Close();
157  }
158
159  return handled;
160}
161
162// Called on the IPC thread:
163bool AutomationResourceMessageFilter::Send(IPC::Message* message) {
164  // This has to be called on the IO thread.
165  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
166  if (!channel_) {
167    delete message;
168    return false;
169  }
170
171  return channel_->Send(message);
172}
173
174bool AutomationResourceMessageFilter::RegisterRequest(
175    URLRequestAutomationJob* job) {
176  if (!job) {
177    NOTREACHED();
178    return false;
179  }
180  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
181
182  // Register pending jobs in the pending request map for servicing later.
183  if (job->is_pending()) {
184    DCHECK(!ContainsKey(pending_request_map_, job->id()));
185    DCHECK(!ContainsKey(request_map_, job->id()));
186    pending_request_map_[job->id()] = job;
187  } else {
188    DCHECK(!ContainsKey(request_map_, job->id()));
189    DCHECK(!ContainsKey(pending_request_map_, job->id()));
190    request_map_[job->id()] = job;
191  }
192
193  return true;
194}
195
196void AutomationResourceMessageFilter::UnRegisterRequest(
197    URLRequestAutomationJob* job) {
198  if (!job) {
199    NOTREACHED();
200    return;
201  }
202  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
203
204  if (job->is_pending()) {
205    DCHECK(ContainsKey(pending_request_map_, job->id()));
206    pending_request_map_.erase(job->id());
207  } else {
208    request_map_.erase(job->id());
209  }
210}
211
212bool AutomationResourceMessageFilter::RegisterRenderView(
213    int renderer_pid, int renderer_id, int tab_handle,
214    AutomationResourceMessageFilter* filter,
215    bool pending_view) {
216  if (!renderer_pid || !renderer_id || !tab_handle) {
217    NOTREACHED();
218    return false;
219  }
220
221  BrowserThread::PostTask(
222      BrowserThread::IO, FROM_HERE,
223      base::Bind(&AutomationResourceMessageFilter::RegisterRenderViewInIOThread,
224                 renderer_pid, renderer_id, tab_handle,
225                 make_scoped_refptr(filter), pending_view));
226  return true;
227}
228
229void AutomationResourceMessageFilter::UnRegisterRenderView(
230    int renderer_pid, int renderer_id) {
231  BrowserThread::PostTask(
232      BrowserThread::IO, FROM_HERE,
233      base::Bind(
234          &AutomationResourceMessageFilter::UnRegisterRenderViewInIOThread,
235          renderer_pid, renderer_id));
236}
237
238bool AutomationResourceMessageFilter::ResumePendingRenderView(
239    int renderer_pid, int renderer_id, int tab_handle,
240    AutomationResourceMessageFilter* filter) {
241  if (!renderer_pid || !renderer_id || !tab_handle) {
242    NOTREACHED();
243    return false;
244  }
245
246  BrowserThread::PostTask(
247      BrowserThread::IO, FROM_HERE,
248      base::Bind(
249          &AutomationResourceMessageFilter::ResumePendingRenderViewInIOThread,
250          renderer_pid, renderer_id, tab_handle, make_scoped_refptr(filter)));
251  return true;
252}
253
254void AutomationResourceMessageFilter::RegisterRenderViewInIOThread(
255    int renderer_pid, int renderer_id,
256    int tab_handle, AutomationResourceMessageFilter* filter,
257    bool pending_view) {
258  RendererId renderer_key(renderer_pid, renderer_id);
259
260  RenderViewMap::iterator automation_details_iter(
261      filtered_render_views_.Get().find(renderer_key));
262  // We need to match the renderer key and the AutomationResourceMessageFilter
263  // instances. If the filter instances are different it means that a new
264  // automation channel (External host process) was created for this tab.
265  if (automation_details_iter != filtered_render_views_.Get().end() &&
266      automation_details_iter->second.filter.get() == filter) {
267    DCHECK_GT(automation_details_iter->second.ref_count, 0);
268    automation_details_iter->second.ref_count++;
269    // The tab handle and the pending status may have changed:-
270    // 1.A external tab container is being destroyed and a new one is being
271    //   created.
272    // 2.The external tab container being destroyed receives a RVH created
273    //   notification for the new RVH created to host the newly created tab.
274    //   In this case the tab handle in the AutomationDetails structure would
275    //   be invalid as it points to a destroyed tab.
276    // We need to replace the handle of the external tab being destroyed with
277    // the new one that is being created."
278    automation_details_iter->second.tab_handle = tab_handle;
279    automation_details_iter->second.is_pending_render_view = pending_view;
280  } else {
281    filtered_render_views_.Get()[renderer_key] =
282        AutomationDetails(tab_handle, filter, pending_view);
283  }
284}
285
286// static
287void AutomationResourceMessageFilter::UnRegisterRenderViewInIOThread(
288    int renderer_pid, int renderer_id) {
289  RenderViewMap::iterator automation_details_iter(
290      filtered_render_views_.Get().find(RendererId(renderer_pid,
291                                                   renderer_id)));
292
293  if (automation_details_iter == filtered_render_views_.Get().end()) {
294    // This is called for all RenderViewHosts, so it's fine if we don't find a
295    // match.
296    return;
297  }
298
299  automation_details_iter->second.ref_count--;
300
301  if (automation_details_iter->second.ref_count <= 0) {
302    filtered_render_views_.Get().erase(RendererId(renderer_pid, renderer_id));
303  }
304}
305
306// static
307void AutomationResourceMessageFilter::ResumePendingRenderViewInIOThread(
308    int renderer_pid, int renderer_id, int tab_handle,
309    AutomationResourceMessageFilter* filter) {
310  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
311
312  RendererId renderer_key(renderer_pid, renderer_id);
313
314  RenderViewMap::iterator automation_details_iter(
315      filtered_render_views_.Get().find(renderer_key));
316
317  DCHECK(automation_details_iter != filtered_render_views_.Get().end())
318      << "Failed to find pending view for renderer pid:"
319      << renderer_pid << ", render view id:" << renderer_id;
320
321  DCHECK(automation_details_iter->second.is_pending_render_view);
322
323  AutomationResourceMessageFilter* old_filter =
324      automation_details_iter->second.filter.get();
325  DCHECK(old_filter != NULL);
326
327  filtered_render_views_.Get()[renderer_key] =
328      AutomationDetails(tab_handle, filter, false);
329
330  ResumeJobsForPendingView(tab_handle, old_filter, filter);
331}
332
333bool AutomationResourceMessageFilter::LookupRegisteredRenderView(
334    int renderer_pid, int renderer_id, AutomationDetails* details) {
335  bool found = false;
336  RenderViewMap::iterator it = filtered_render_views_.Get().find(RendererId(
337      renderer_pid, renderer_id));
338  if (it != filtered_render_views_.Get().end()) {
339    found = true;
340    if (details)
341      *details = it->second;
342  }
343
344  return found;
345}
346
347bool AutomationResourceMessageFilter::GetAutomationRequestId(
348    int request_id, int* automation_request_id) {
349  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
350
351  RequestMap::iterator it = request_map_.begin();
352  while (it != request_map_.end()) {
353    URLRequestAutomationJob* job = it->second;
354    DCHECK(job);
355    if (job && job->request_id() == request_id) {
356      *automation_request_id = job->id();
357      return true;
358    }
359    it++;
360  }
361
362  return false;
363}
364
365bool AutomationResourceMessageFilter::SendDownloadRequestToHost(
366    int routing_id, int tab_handle, int request_id) {
367  int automation_request_id = 0;
368  bool valid_id = GetAutomationRequestId(request_id, &automation_request_id);
369  if (!valid_id) {
370    LOG(ERROR) << "Invalid request id: " << request_id;
371    return false;
372  }
373
374  return Send(new AutomationMsg_DownloadRequestInHost(tab_handle,
375                                                      automation_request_id));
376}
377
378bool AutomationResourceMessageFilter::ShouldFilterCookieMessages(
379    int render_process_id, int render_view_id) {
380  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
381  RendererId renderer_key(render_process_id, render_view_id);
382  RenderViewMap::iterator automation_details_iter(
383      filtered_render_views_.Get().find(renderer_key));
384  return automation_details_iter != filtered_render_views_.Get().end();
385}
386
387void AutomationResourceMessageFilter::GetCookiesForUrl(
388    BrowserMessageFilter* filter, net::URLRequestContext* context,
389    int render_process_id, IPC::Message* reply_msg, const GURL& url) {
390
391  RendererId renderer_key(render_process_id, reply_msg->routing_id());
392
393  RenderViewMap::iterator automation_details_iter(
394      filtered_render_views_.Get().find(renderer_key));
395
396  DCHECK(automation_details_iter != filtered_render_views_.Get().end());
397  DCHECK(automation_details_iter->second.filter.get() != NULL);
398
399  int completion_callback_id = GetNextCompletionCallbackId();
400  DCHECK(!ContainsKey(completion_callback_map_.Get(), completion_callback_id));
401
402  CookieCompletionInfo cookie_info;
403  cookie_info.filter = filter;
404  cookie_info.context = context;
405  cookie_info.render_process_id = render_process_id;
406  cookie_info.reply_msg = reply_msg;
407  cookie_info.automation_message_filter =
408      automation_details_iter->second.filter;
409
410  completion_callback_map_.Get()[completion_callback_id] = cookie_info;
411
412  DCHECK(automation_details_iter->second.filter.get() != NULL);
413
414  if (automation_details_iter->second.filter.get()) {
415    automation_details_iter->second.filter
416        ->Send(new AutomationMsg_GetCookiesFromHost(
417              automation_details_iter->second.tab_handle,
418              url,
419              completion_callback_id));
420  }
421}
422
423void AutomationResourceMessageFilter::OnGetCookiesHostResponse(
424    int tab_handle, bool success, const GURL& url, const std::string& cookies,
425    int cookie_id) {
426  CompletionCallbackMap::iterator index =
427      completion_callback_map_.Get().find(cookie_id);
428  if (index == completion_callback_map_.Get().end()) {
429    NOTREACHED() << "Received invalid completion callback id:"
430                 << cookie_id;
431    return;
432  }
433
434  ChromeViewHostMsg_GetCookies::WriteReplyParams(index->second.reply_msg,
435                                                 cookies);
436  index->second.filter->Send(index->second.reply_msg);
437
438  completion_callback_map_.Get().erase(index);
439}
440
441void AutomationResourceMessageFilter::SetCookiesForUrl(
442    int render_process_id,
443    int render_view_id,
444    const GURL& url,
445    const std::string& cookie_line) {
446  RenderViewMap::iterator automation_details_iter(
447      filtered_render_views_.Get().find(RendererId(
448          render_process_id, render_view_id)));
449  DCHECK(automation_details_iter != filtered_render_views_.Get().end());
450  DCHECK(automation_details_iter->second.filter.get() != NULL);
451
452  if (automation_details_iter->second.filter.get()) {
453    automation_details_iter->second.filter
454        ->Send(new AutomationMsg_SetCookieAsync(
455              automation_details_iter->second.tab_handle, url, cookie_line));
456  }
457}
458
459// static
460void AutomationResourceMessageFilter::ResumeJobsForPendingView(
461    int tab_handle,
462    AutomationResourceMessageFilter* old_filter,
463    AutomationResourceMessageFilter* new_filter) {
464  DCHECK(old_filter != NULL);
465  DCHECK(new_filter != NULL);
466
467  RequestMap pending_requests = old_filter->pending_request_map_;
468  old_filter->pending_request_map_.clear();
469
470  for (RequestMap::iterator index = pending_requests.begin();
471       index != pending_requests.end(); index++) {
472    URLRequestAutomationJob* job = (*index).second;
473    DCHECK_EQ(job->message_filter(), old_filter);
474    DCHECK(job->is_pending());
475    // StartPendingJob will register the job with the new filter.
476    job->StartPendingJob(tab_handle, new_filter);
477  }
478}
479