1// Copyright 2014 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/renderer_host/chrome_extension_message_filter.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/files/file_path.h"
10#include "base/strings/utf_string_conversions.h"
11#include "chrome/browser/browser_process.h"
12#include "chrome/browser/chrome_notification_types.h"
13#include "chrome/browser/extensions/activity_log/activity_action_constants.h"
14#include "chrome/browser/extensions/activity_log/activity_actions.h"
15#include "chrome/browser/extensions/activity_log/activity_log.h"
16#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h"
17#include "chrome/browser/extensions/api/messaging/message_service.h"
18#include "chrome/browser/profiles/profile.h"
19#include "chrome/browser/profiles/profile_manager.h"
20#include "chrome/common/extensions/api/i18n/default_locale_handler.h"
21#include "chrome/common/extensions/chrome_extension_messages.h"
22#include "content/public/browser/notification_service.h"
23#include "content/public/browser/render_process_host.h"
24#include "extensions/browser/extension_system.h"
25#include "extensions/common/api/messaging/message.h"
26#include "extensions/common/extension_messages.h"
27#include "extensions/common/file_util.h"
28#include "extensions/common/message_bundle.h"
29
30using content::BrowserThread;
31
32namespace {
33
34const uint32 kFilteredMessageClasses[] = {
35  ChromeExtensionMsgStart,
36  ExtensionMsgStart,
37};
38
39// Logs an action to the extension activity log for the specified profile.  Can
40// be called from any thread.
41void AddActionToExtensionActivityLog(
42    Profile* profile,
43    scoped_refptr<extensions::Action> action) {
44  // The ActivityLog can only be accessed from the main (UI) thread.  If we're
45  // running on the wrong thread, re-dispatch from the main thread.
46  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
47    BrowserThread::PostTask(
48        BrowserThread::UI, FROM_HERE,
49        base::Bind(&AddActionToExtensionActivityLog, profile, action));
50  } else {
51    if (!g_browser_process->profile_manager()->IsValidProfile(profile))
52      return;
53    // If the action included a URL, check whether it is for an incognito
54    // profile.  The check is performed here so that it can safely be done from
55    // the UI thread.
56    if (action->page_url().is_valid() || !action->page_title().empty())
57      action->set_page_incognito(profile->IsOffTheRecord());
58    extensions::ActivityLog* activity_log =
59        extensions::ActivityLog::GetInstance(profile);
60    activity_log->LogAction(action);
61  }
62}
63
64}  // namespace
65
66ChromeExtensionMessageFilter::ChromeExtensionMessageFilter(
67    int render_process_id,
68    Profile* profile)
69    : BrowserMessageFilter(kFilteredMessageClasses,
70                           arraysize(kFilteredMessageClasses)),
71      render_process_id_(render_process_id),
72      profile_(profile),
73      extension_info_map_(
74          extensions::ExtensionSystem::Get(profile)->info_map()) {
75  DCHECK_CURRENTLY_ON(BrowserThread::UI);
76  notification_registrar_.Add(this,
77                              chrome::NOTIFICATION_PROFILE_DESTROYED,
78                              content::Source<Profile>(profile));
79}
80
81ChromeExtensionMessageFilter::~ChromeExtensionMessageFilter() {
82  DCHECK_CURRENTLY_ON(BrowserThread::UI);
83}
84
85bool ChromeExtensionMessageFilter::OnMessageReceived(
86    const IPC::Message& message) {
87  bool handled = true;
88  IPC_BEGIN_MESSAGE_MAP(ChromeExtensionMessageFilter, message)
89    IPC_MESSAGE_HANDLER(ExtensionHostMsg_OpenChannelToExtension,
90                        OnOpenChannelToExtension)
91    IPC_MESSAGE_HANDLER(ExtensionHostMsg_OpenChannelToTab, OnOpenChannelToTab)
92    IPC_MESSAGE_HANDLER(ExtensionHostMsg_OpenChannelToNativeApp,
93                        OnOpenChannelToNativeApp)
94    IPC_MESSAGE_HANDLER(ExtensionHostMsg_PostMessage, OnPostMessage)
95    IPC_MESSAGE_HANDLER_DELAY_REPLY(ExtensionHostMsg_GetMessageBundle,
96                                    OnGetExtMessageBundle)
97    IPC_MESSAGE_HANDLER(ExtensionHostMsg_CloseChannel, OnExtensionCloseChannel)
98    IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddAPIActionToActivityLog,
99                        OnAddAPIActionToExtensionActivityLog);
100    IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddDOMActionToActivityLog,
101                        OnAddDOMActionToExtensionActivityLog);
102    IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddEventToActivityLog,
103                        OnAddEventToExtensionActivityLog);
104    IPC_MESSAGE_UNHANDLED(handled = false)
105  IPC_END_MESSAGE_MAP()
106
107  return handled;
108}
109
110void ChromeExtensionMessageFilter::OverrideThreadForMessage(
111    const IPC::Message& message, BrowserThread::ID* thread) {
112  switch (message.type()) {
113    case ExtensionHostMsg_PostMessage::ID:
114    case ExtensionHostMsg_CloseChannel::ID:
115      *thread = BrowserThread::UI;
116      break;
117    default:
118      break;
119  }
120}
121
122void ChromeExtensionMessageFilter::OnDestruct() const {
123  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
124    delete this;
125  } else {
126    BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
127  }
128}
129
130void ChromeExtensionMessageFilter::OnOpenChannelToExtension(
131    int routing_id,
132    const ExtensionMsg_ExternalConnectionInfo& info,
133    const std::string& channel_name,
134    bool include_tls_channel_id,
135    int* port_id) {
136  int port2_id;
137  extensions::MessageService::AllocatePortIdPair(port_id, &port2_id);
138
139  BrowserThread::PostTask(
140      BrowserThread::UI, FROM_HERE,
141      base::Bind(
142          &ChromeExtensionMessageFilter::OpenChannelToExtensionOnUIThread,
143          this, render_process_id_, routing_id, port2_id, info,
144          channel_name, include_tls_channel_id));
145}
146
147void ChromeExtensionMessageFilter::OpenChannelToExtensionOnUIThread(
148    int source_process_id, int source_routing_id,
149    int receiver_port_id,
150    const ExtensionMsg_ExternalConnectionInfo& info,
151    const std::string& channel_name,
152    bool include_tls_channel_id) {
153  DCHECK_CURRENTLY_ON(BrowserThread::UI);
154  if (profile_) {
155    extensions::MessageService::Get(profile_)
156        ->OpenChannelToExtension(source_process_id,
157                                 source_routing_id,
158                                 receiver_port_id,
159                                 info.source_id,
160                                 info.target_id,
161                                 info.source_url,
162                                 channel_name,
163                                 include_tls_channel_id);
164  }
165}
166
167void ChromeExtensionMessageFilter::OnOpenChannelToNativeApp(
168    int routing_id,
169    const std::string& source_extension_id,
170    const std::string& native_app_name,
171    int* port_id) {
172  int port2_id;
173  extensions::MessageService::AllocatePortIdPair(port_id, &port2_id);
174
175  BrowserThread::PostTask(
176      BrowserThread::UI, FROM_HERE,
177      base::Bind(
178          &ChromeExtensionMessageFilter::OpenChannelToNativeAppOnUIThread,
179          this, routing_id, port2_id, source_extension_id, native_app_name));
180}
181
182void ChromeExtensionMessageFilter::OpenChannelToNativeAppOnUIThread(
183    int source_routing_id,
184    int receiver_port_id,
185    const std::string& source_extension_id,
186    const std::string& native_app_name) {
187  DCHECK_CURRENTLY_ON(BrowserThread::UI);
188  if (profile_) {
189    extensions::MessageService::Get(profile_)
190        ->OpenChannelToNativeApp(render_process_id_,
191                                 source_routing_id,
192                                 receiver_port_id,
193                                 source_extension_id,
194                                 native_app_name);
195  }
196}
197
198void ChromeExtensionMessageFilter::OnOpenChannelToTab(
199    int routing_id, int tab_id, const std::string& extension_id,
200    const std::string& channel_name, int* port_id) {
201  int port2_id;
202  extensions::MessageService::AllocatePortIdPair(port_id, &port2_id);
203
204  BrowserThread::PostTask(
205      BrowserThread::UI, FROM_HERE,
206      base::Bind(&ChromeExtensionMessageFilter::OpenChannelToTabOnUIThread,
207                 this, render_process_id_, routing_id, port2_id, tab_id,
208                 extension_id, channel_name));
209}
210
211void ChromeExtensionMessageFilter::OpenChannelToTabOnUIThread(
212    int source_process_id, int source_routing_id,
213    int receiver_port_id,
214    int tab_id,
215    const std::string& extension_id,
216    const std::string& channel_name) {
217  DCHECK_CURRENTLY_ON(BrowserThread::UI);
218  if (profile_) {
219    extensions::MessageService::Get(profile_)
220        ->OpenChannelToTab(source_process_id,
221                           source_routing_id,
222                           receiver_port_id,
223                           tab_id,
224                           extension_id,
225                           channel_name);
226  }
227}
228
229void ChromeExtensionMessageFilter::OnPostMessage(
230    int port_id,
231    const extensions::Message& message) {
232  if (!profile_)
233    return;
234
235  extensions::MessageService::Get(profile_)->PostMessage(port_id, message);
236}
237
238void ChromeExtensionMessageFilter::OnGetExtMessageBundle(
239    const std::string& extension_id, IPC::Message* reply_msg) {
240  const extensions::Extension* extension =
241      extension_info_map_->extensions().GetByID(extension_id);
242  base::FilePath extension_path;
243  std::string default_locale;
244  if (extension) {
245    extension_path = extension->path();
246    default_locale = extensions::LocaleInfo::GetDefaultLocale(extension);
247  }
248
249  BrowserThread::PostBlockingPoolTask(
250      FROM_HERE,
251      base::Bind(
252          &ChromeExtensionMessageFilter::OnGetExtMessageBundleOnBlockingPool,
253          this, extension_path, extension_id, default_locale, reply_msg));
254}
255
256void ChromeExtensionMessageFilter::OnGetExtMessageBundleOnBlockingPool(
257    const base::FilePath& extension_path,
258    const std::string& extension_id,
259    const std::string& default_locale,
260    IPC::Message* reply_msg) {
261  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
262
263  scoped_ptr<extensions::MessageBundle::SubstitutionMap> dictionary_map(
264      extensions::file_util::LoadMessageBundleSubstitutionMap(
265          extension_path, extension_id, default_locale));
266
267  ExtensionHostMsg_GetMessageBundle::WriteReplyParams(reply_msg,
268                                                      *dictionary_map);
269  Send(reply_msg);
270}
271
272void ChromeExtensionMessageFilter::OnExtensionCloseChannel(
273    int port_id,
274    const std::string& error_message) {
275  if (!profile_)
276    return;
277
278  if (!content::RenderProcessHost::FromID(render_process_id_))
279    return;  // To guard against crash in browser_tests shutdown.
280
281  extensions::MessageService* message_service =
282      extensions::MessageService::Get(profile_);
283  if (message_service)
284    message_service->CloseChannel(port_id, error_message);
285}
286
287void ChromeExtensionMessageFilter::OnAddAPIActionToExtensionActivityLog(
288    const std::string& extension_id,
289    const ExtensionHostMsg_APIActionOrEvent_Params& params) {
290  scoped_refptr<extensions::Action> action = new extensions::Action(
291      extension_id, base::Time::Now(), extensions::Action::ACTION_API_CALL,
292      params.api_call);
293  action->set_args(make_scoped_ptr(params.arguments.DeepCopy()));
294  if (!params.extra.empty()) {
295    action->mutable_other()->SetString(
296        activity_log_constants::kActionExtra, params.extra);
297  }
298  AddActionToExtensionActivityLog(profile_, action);
299}
300
301void ChromeExtensionMessageFilter::OnAddDOMActionToExtensionActivityLog(
302    const std::string& extension_id,
303    const ExtensionHostMsg_DOMAction_Params& params) {
304  scoped_refptr<extensions::Action> action = new extensions::Action(
305      extension_id, base::Time::Now(), extensions::Action::ACTION_DOM_ACCESS,
306      params.api_call);
307  action->set_args(make_scoped_ptr(params.arguments.DeepCopy()));
308  action->set_page_url(params.url);
309  action->set_page_title(base::UTF16ToUTF8(params.url_title));
310  action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
311                                      params.call_type);
312  AddActionToExtensionActivityLog(profile_, action);
313}
314
315void ChromeExtensionMessageFilter::OnAddEventToExtensionActivityLog(
316    const std::string& extension_id,
317    const ExtensionHostMsg_APIActionOrEvent_Params& params) {
318  scoped_refptr<extensions::Action> action = new extensions::Action(
319      extension_id, base::Time::Now(), extensions::Action::ACTION_API_EVENT,
320      params.api_call);
321  action->set_args(make_scoped_ptr(params.arguments.DeepCopy()));
322  if (!params.extra.empty()) {
323    action->mutable_other()->SetString(activity_log_constants::kActionExtra,
324                                       params.extra);
325  }
326  AddActionToExtensionActivityLog(profile_, action);
327}
328
329void ChromeExtensionMessageFilter::Observe(
330    int type,
331    const content::NotificationSource& source,
332    const content::NotificationDetails& details) {
333  DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type);
334  profile_ = NULL;
335}
336