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/utility/importer/firefox_importer_unittest_utils.h"
6
7#include "base/base_switches.h"
8#include "base/bind.h"
9#include "base/command_line.h"
10#include "base/files/file_path.h"
11#include "base/files/file_util.h"
12#include "base/files/scoped_file.h"
13#include "base/message_loop/message_loop.h"
14#include "base/posix/global_descriptors.h"
15#include "base/process/kill.h"
16#include "base/process/launch.h"
17#include "base/test/test_timeouts.h"
18#include "chrome/common/importer/firefox_importer_utils.h"
19#include "ipc/ipc_channel.h"
20#include "ipc/ipc_descriptors.h"
21#include "ipc/ipc_listener.h"
22#include "ipc/ipc_message.h"
23#include "ipc/ipc_multiprocess_test.h"
24#include "testing/multiprocess_func_list.h"
25
26#define IPC_MESSAGE_IMPL
27#include "chrome/utility/importer/firefox_importer_unittest_messages_internal.h"
28
29namespace {
30
31// Name of IPC Channel to use for Server<-> Child Communications.
32const char kTestChannelID[] = "T1";
33
34// Launch the child process:
35// |nss_path| - path to the NSS directory holding the decryption libraries.
36// |channel| - IPC Channel to use for communication.
37// |handle| - On return, the process handle to use to communicate with the
38// child.
39bool LaunchNSSDecrypterChildProcess(const base::FilePath& nss_path,
40    IPC::Channel* channel, base::ProcessHandle* handle) {
41  CommandLine cl(*CommandLine::ForCurrentProcess());
42  cl.AppendSwitchASCII(switches::kTestChildProcess, "NSSDecrypterChildProcess");
43
44  // Set env variable needed for FF encryption libs to load.
45  // See "chrome/utility/importer/nss_decryptor_mac.mm" for an explanation of
46  // why we need this.
47  base::LaunchOptions options;
48  options.environ["DYLD_FALLBACK_LIBRARY_PATH"] = nss_path.value();
49
50  base::ScopedFD ipcfd(channel->TakeClientFileDescriptor());
51  if (!ipcfd.is_valid())
52    return false;
53
54  base::FileHandleMappingVector fds_to_map;
55  fds_to_map.push_back(std::pair<int,int>(ipcfd.get(),
56      kPrimaryIPCChannel + base::GlobalDescriptors::kBaseDescriptor));
57
58  options.fds_to_remap = &fds_to_map;
59  return base::LaunchProcess(cl.argv(), options, handle);
60}
61
62}  // namespace
63
64//----------------------- Server --------------------
65
66// Class to communicate on the server side of the IPC Channel.
67// Method calls are sent over IPC and replies are read back into class
68// variables.
69// This class needs to be called on a single thread.
70class FFDecryptorServerChannelListener : public IPC::Listener {
71 public:
72  FFDecryptorServerChannelListener()
73      : got_result(false), sender_(NULL) {}
74
75  void SetSender(IPC::Sender* sender) {
76    sender_ = sender;
77  }
78
79  void OnInitDecryptorResponse(bool result) {
80    DCHECK(!got_result);
81    result_bool = result;
82    got_result = true;
83    base::MessageLoop::current()->Quit();
84  }
85
86  void OnDecryptedTextResponse(const base::string16& decrypted_text) {
87    DCHECK(!got_result);
88    result_string = decrypted_text;
89    got_result = true;
90    base::MessageLoop::current()->Quit();
91  }
92
93  void OnParseSignonsResponse(
94      const std::vector<autofill::PasswordForm>& parsed_vector) {
95    DCHECK(!got_result);
96    result_vector = parsed_vector;
97    got_result = true;
98    base::MessageLoop::current()->Quit();
99  }
100
101  void QuitClient() {
102    if (sender_)
103      sender_->Send(new Msg_Decryptor_Quit());
104  }
105
106  virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE {
107    bool handled = true;
108    IPC_BEGIN_MESSAGE_MAP(FFDecryptorServerChannelListener, msg)
109      IPC_MESSAGE_HANDLER(Msg_Decryptor_InitReturnCode, OnInitDecryptorResponse)
110      IPC_MESSAGE_HANDLER(Msg_Decryptor_Response, OnDecryptedTextResponse)
111      IPC_MESSAGE_HANDLER(Msg_ParseSignons_Response, OnParseSignonsResponse)
112      IPC_MESSAGE_UNHANDLED(handled = false)
113    IPC_END_MESSAGE_MAP()
114    return handled;
115  }
116
117  // If an error occured, just kill the message Loop.
118  virtual void OnChannelError() OVERRIDE {
119    got_result = false;
120    base::MessageLoop::current()->Quit();
121  }
122
123  // Results of IPC calls.
124  base::string16 result_string;
125  std::vector<autofill::PasswordForm> result_vector;
126  bool result_bool;
127  // True if IPC call succeeded and data in above variables is valid.
128  bool got_result;
129
130 private:
131  IPC::Sender* sender_;  // weak
132};
133
134FFUnitTestDecryptorProxy::FFUnitTestDecryptorProxy()
135    : child_process_(0) {
136}
137
138bool FFUnitTestDecryptorProxy::Setup(const base::FilePath& nss_path) {
139  // Create a new message loop and spawn the child process.
140  message_loop_.reset(new base::MessageLoopForIO());
141
142  listener_.reset(new FFDecryptorServerChannelListener());
143  channel_ = IPC::Channel::CreateServer(kTestChannelID, listener_.get());
144  CHECK(channel_->Connect());
145  listener_->SetSender(channel_.get());
146
147  // Spawn child and set up sync IPC connection.
148  bool ret = LaunchNSSDecrypterChildProcess(nss_path,
149                                            channel_.get(),
150                                            &child_process_);
151  return ret && (child_process_ != 0);
152}
153
154FFUnitTestDecryptorProxy::~FFUnitTestDecryptorProxy() {
155  listener_->QuitClient();
156  channel_->Close();
157
158  if (child_process_) {
159    base::WaitForSingleProcess(child_process_, base::TimeDelta::FromSeconds(5));
160    base::CloseProcessHandle(child_process_);
161  }
162}
163
164// A message_loop task that quits the message loop when invoked, setting cancel
165// causes the task to do nothing when invoked.
166class CancellableQuitMsgLoop : public base::RefCounted<CancellableQuitMsgLoop> {
167 public:
168  CancellableQuitMsgLoop() : cancelled_(false) {}
169  void QuitNow() {
170    if (!cancelled_)
171      base::MessageLoop::current()->Quit();
172  }
173  bool cancelled_;
174
175 private:
176  friend class base::RefCounted<CancellableQuitMsgLoop>;
177  ~CancellableQuitMsgLoop() {}
178};
179
180// Spin until either a client response arrives or a timeout occurs.
181bool FFUnitTestDecryptorProxy::WaitForClientResponse() {
182  // What we're trying to do here is to wait for an RPC message to go over the
183  // wire and the client to reply.  If the client does not reply by a given
184  // timeout we kill the message loop.
185  // The way we do this is to post a CancellableQuitMsgLoop for 3 seconds in
186  // the future and cancel it if an RPC message comes back earlier.
187  // This relies on the IPC listener class to quit the message loop itself when
188  // a message comes in.
189  scoped_refptr<CancellableQuitMsgLoop> quit_task(
190      new CancellableQuitMsgLoop());
191  base::MessageLoop::current()->PostDelayedTask(
192      FROM_HERE,
193      base::Bind(&CancellableQuitMsgLoop::QuitNow, quit_task.get()),
194      TestTimeouts::action_max_timeout());
195
196  message_loop_->Run();
197  bool ret = !quit_task->cancelled_;
198  quit_task->cancelled_ = false;
199  return ret;
200}
201
202bool FFUnitTestDecryptorProxy::DecryptorInit(const base::FilePath& dll_path,
203                                             const base::FilePath& db_path) {
204  channel_->Send(new Msg_Decryptor_Init(dll_path, db_path));
205  bool ok = WaitForClientResponse();
206  if (ok && listener_->got_result) {
207    listener_->got_result = false;
208    return listener_->result_bool;
209  }
210  return false;
211}
212
213base::string16 FFUnitTestDecryptorProxy::Decrypt(const std::string& crypt) {
214  channel_->Send(new Msg_Decrypt(crypt));
215  bool ok = WaitForClientResponse();
216  if (ok && listener_->got_result) {
217    listener_->got_result = false;
218    return listener_->result_string;
219  }
220  return base::string16();
221}
222
223std::vector<autofill::PasswordForm> FFUnitTestDecryptorProxy::ParseSignons(
224    const base::FilePath& signons_path) {
225  channel_->Send(new Msg_ParseSignons(signons_path));
226  bool ok = WaitForClientResponse();
227  if (ok && listener_->got_result) {
228    listener_->got_result = false;
229    return listener_->result_vector;
230  }
231  return std::vector<autofill::PasswordForm>();
232}
233
234//---------------------------- Child Process -----------------------
235
236// Class to listen on the client side of the ipc channel, it calls through
237// to the NSSDecryptor and sends back a reply.
238class FFDecryptorClientChannelListener : public IPC::Listener {
239 public:
240  FFDecryptorClientChannelListener()
241      : sender_(NULL) {}
242
243  void SetSender(IPC::Sender* sender) {
244    sender_ = sender;
245  }
246
247  void OnDecryptor_Init(base::FilePath dll_path, base::FilePath db_path) {
248    bool ret = decryptor_.Init(dll_path, db_path);
249    sender_->Send(new Msg_Decryptor_InitReturnCode(ret));
250  }
251
252  void OnDecrypt(std::string crypt) {
253    base::string16 unencrypted_str = decryptor_.Decrypt(crypt);
254    sender_->Send(new Msg_Decryptor_Response(unencrypted_str));
255  }
256
257  void OnParseSignons(base::FilePath signons_path) {
258    std::vector<autofill::PasswordForm> forms;
259    decryptor_.ReadAndParseSignons(signons_path, &forms);
260    sender_->Send(new Msg_ParseSignons_Response(forms));
261  }
262
263  void OnQuitRequest() {
264    base::MessageLoop::current()->Quit();
265  }
266
267  virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE {
268    bool handled = true;
269    IPC_BEGIN_MESSAGE_MAP(FFDecryptorClientChannelListener, msg)
270      IPC_MESSAGE_HANDLER(Msg_Decryptor_Init, OnDecryptor_Init)
271      IPC_MESSAGE_HANDLER(Msg_Decrypt, OnDecrypt)
272      IPC_MESSAGE_HANDLER(Msg_ParseSignons, OnParseSignons)
273      IPC_MESSAGE_HANDLER(Msg_Decryptor_Quit, OnQuitRequest)
274      IPC_MESSAGE_UNHANDLED(handled = false)
275    IPC_END_MESSAGE_MAP()
276    return handled;
277  }
278
279  virtual void OnChannelError() OVERRIDE {
280    base::MessageLoop::current()->Quit();
281  }
282
283 private:
284  NSSDecryptor decryptor_;
285  IPC::Sender* sender_;
286};
287
288// Entry function in child process.
289MULTIPROCESS_IPC_TEST_MAIN(NSSDecrypterChildProcess) {
290  base::MessageLoopForIO main_message_loop;
291  FFDecryptorClientChannelListener listener;
292
293  scoped_ptr<IPC::Channel> channel = IPC::Channel::CreateClient(
294      kTestChannelID, &listener);
295  CHECK(channel->Connect());
296  listener.SetSender(channel.get());
297
298  // run message loop
299  base::MessageLoop::current()->Run();
300
301  return 0;
302}
303