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.
5 #include "chrome/utility/importer/firefox_importer_unittest_utils.h"
7 #include "base/base_switches.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"
26 #define IPC_MESSAGE_IMPL
27 #include "chrome/utility/importer/firefox_importer_unittest_messages_internal.h"
31 // Name of IPC Channel to use for Server<-> Child Communications.
32 const char kTestChannelID
[] = "T1";
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 base::Process
LaunchNSSDecrypterChildProcess(const base::FilePath
& nss_path
,
38 IPC::Channel
* channel
) {
39 base::CommandLine
cl(*base::CommandLine::ForCurrentProcess());
40 cl
.AppendSwitchASCII(switches::kTestChildProcess
, "NSSDecrypterChildProcess");
42 // Set env variable needed for FF encryption libs to load.
43 // See "chrome/utility/importer/nss_decryptor_mac.mm" for an explanation of
45 base::LaunchOptions options
;
46 options
.environ
["DYLD_FALLBACK_LIBRARY_PATH"] = nss_path
.value();
48 base::ScopedFD
ipcfd(channel
->TakeClientFileDescriptor());
49 if (!ipcfd
.is_valid())
50 return base::Process();
52 base::FileHandleMappingVector fds_to_map
;
53 fds_to_map
.push_back(std::pair
<int,int>(ipcfd
.get(),
54 kPrimaryIPCChannel
+ base::GlobalDescriptors::kBaseDescriptor
));
56 options
.fds_to_remap
= &fds_to_map
;
57 return base::LaunchProcess(cl
.argv(), options
);
62 //----------------------- Server --------------------
64 // Class to communicate on the server side of the IPC Channel.
65 // Method calls are sent over IPC and replies are read back into class
67 // This class needs to be called on a single thread.
68 class FFDecryptorServerChannelListener
: public IPC::Listener
{
70 FFDecryptorServerChannelListener()
71 : got_result(false), sender_(NULL
) {}
73 void SetSender(IPC::Sender
* sender
) {
77 void OnInitDecryptorResponse(bool result
) {
81 base::MessageLoop::current()->Quit();
84 void OnDecryptedTextResponse(const base::string16
& decrypted_text
) {
86 result_string
= decrypted_text
;
88 base::MessageLoop::current()->Quit();
91 void OnParseSignonsResponse(
92 const std::vector
<autofill::PasswordForm
>& parsed_vector
) {
94 result_vector
= parsed_vector
;
96 base::MessageLoop::current()->Quit();
101 sender_
->Send(new Msg_Decryptor_Quit());
104 bool OnMessageReceived(const IPC::Message
& msg
) override
{
106 IPC_BEGIN_MESSAGE_MAP(FFDecryptorServerChannelListener
, msg
)
107 IPC_MESSAGE_HANDLER(Msg_Decryptor_InitReturnCode
, OnInitDecryptorResponse
)
108 IPC_MESSAGE_HANDLER(Msg_Decryptor_Response
, OnDecryptedTextResponse
)
109 IPC_MESSAGE_HANDLER(Msg_ParseSignons_Response
, OnParseSignonsResponse
)
110 IPC_MESSAGE_UNHANDLED(handled
= false)
111 IPC_END_MESSAGE_MAP()
115 // If an error occured, just kill the message Loop.
116 void OnChannelError() override
{
118 base::MessageLoop::current()->Quit();
121 // Results of IPC calls.
122 base::string16 result_string
;
123 std::vector
<autofill::PasswordForm
> result_vector
;
125 // True if IPC call succeeded and data in above variables is valid.
129 IPC::Sender
* sender_
; // weak
132 FFUnitTestDecryptorProxy::FFUnitTestDecryptorProxy() {
135 bool FFUnitTestDecryptorProxy::Setup(const base::FilePath
& nss_path
) {
136 // Create a new message loop and spawn the child process.
137 message_loop_
.reset(new base::MessageLoopForIO());
139 listener_
.reset(new FFDecryptorServerChannelListener());
140 channel_
= IPC::Channel::CreateServer(kTestChannelID
, listener_
.get());
141 CHECK(channel_
->Connect());
142 listener_
->SetSender(channel_
.get());
144 // Spawn child and set up sync IPC connection.
145 child_process_
= LaunchNSSDecrypterChildProcess(nss_path
, channel_
.get());
146 return child_process_
.IsValid();
149 FFUnitTestDecryptorProxy::~FFUnitTestDecryptorProxy() {
150 listener_
->QuitClient();
153 if (child_process_
.IsValid()) {
155 child_process_
.WaitForExitWithTimeout(base::TimeDelta::FromSeconds(5),
160 // A message_loop task that quits the message loop when invoked, setting cancel
161 // causes the task to do nothing when invoked.
162 class CancellableQuitMsgLoop
: public base::RefCounted
<CancellableQuitMsgLoop
> {
164 CancellableQuitMsgLoop() : cancelled_(false) {}
167 base::MessageLoop::current()->Quit();
172 friend class base::RefCounted
<CancellableQuitMsgLoop
>;
173 ~CancellableQuitMsgLoop() {}
176 // Spin until either a client response arrives or a timeout occurs.
177 bool FFUnitTestDecryptorProxy::WaitForClientResponse() {
178 // What we're trying to do here is to wait for an RPC message to go over the
179 // wire and the client to reply. If the client does not reply by a given
180 // timeout we kill the message loop.
181 // The way we do this is to post a CancellableQuitMsgLoop for 3 seconds in
182 // the future and cancel it if an RPC message comes back earlier.
183 // This relies on the IPC listener class to quit the message loop itself when
184 // a message comes in.
185 scoped_refptr
<CancellableQuitMsgLoop
> quit_task(
186 new CancellableQuitMsgLoop());
187 base::MessageLoop::current()->PostDelayedTask(
189 base::Bind(&CancellableQuitMsgLoop::QuitNow
, quit_task
.get()),
190 TestTimeouts::action_max_timeout());
192 message_loop_
->Run();
193 bool ret
= !quit_task
->cancelled_
;
194 quit_task
->cancelled_
= false;
198 bool FFUnitTestDecryptorProxy::DecryptorInit(const base::FilePath
& dll_path
,
199 const base::FilePath
& db_path
) {
200 channel_
->Send(new Msg_Decryptor_Init(dll_path
, db_path
));
201 bool ok
= WaitForClientResponse();
202 if (ok
&& listener_
->got_result
) {
203 listener_
->got_result
= false;
204 return listener_
->result_bool
;
209 base::string16
FFUnitTestDecryptorProxy::Decrypt(const std::string
& crypt
) {
210 channel_
->Send(new Msg_Decrypt(crypt
));
211 bool ok
= WaitForClientResponse();
212 if (ok
&& listener_
->got_result
) {
213 listener_
->got_result
= false;
214 return listener_
->result_string
;
216 return base::string16();
219 std::vector
<autofill::PasswordForm
> FFUnitTestDecryptorProxy::ParseSignons(
220 const base::FilePath
& signons_path
) {
221 channel_
->Send(new Msg_ParseSignons(signons_path
));
222 bool ok
= WaitForClientResponse();
223 if (ok
&& listener_
->got_result
) {
224 listener_
->got_result
= false;
225 return listener_
->result_vector
;
227 return std::vector
<autofill::PasswordForm
>();
230 //---------------------------- Child Process -----------------------
232 // Class to listen on the client side of the ipc channel, it calls through
233 // to the NSSDecryptor and sends back a reply.
234 class FFDecryptorClientChannelListener
: public IPC::Listener
{
236 FFDecryptorClientChannelListener()
239 void SetSender(IPC::Sender
* sender
) {
243 void OnDecryptor_Init(base::FilePath dll_path
, base::FilePath db_path
) {
244 bool ret
= decryptor_
.Init(dll_path
, db_path
);
245 sender_
->Send(new Msg_Decryptor_InitReturnCode(ret
));
248 void OnDecrypt(std::string crypt
) {
249 base::string16 unencrypted_str
= decryptor_
.Decrypt(crypt
);
250 sender_
->Send(new Msg_Decryptor_Response(unencrypted_str
));
253 void OnParseSignons(base::FilePath signons_path
) {
254 std::vector
<autofill::PasswordForm
> forms
;
255 decryptor_
.ReadAndParseSignons(signons_path
, &forms
);
256 sender_
->Send(new Msg_ParseSignons_Response(forms
));
259 void OnQuitRequest() {
260 base::MessageLoop::current()->Quit();
263 bool OnMessageReceived(const IPC::Message
& msg
) override
{
265 IPC_BEGIN_MESSAGE_MAP(FFDecryptorClientChannelListener
, msg
)
266 IPC_MESSAGE_HANDLER(Msg_Decryptor_Init
, OnDecryptor_Init
)
267 IPC_MESSAGE_HANDLER(Msg_Decrypt
, OnDecrypt
)
268 IPC_MESSAGE_HANDLER(Msg_ParseSignons
, OnParseSignons
)
269 IPC_MESSAGE_HANDLER(Msg_Decryptor_Quit
, OnQuitRequest
)
270 IPC_MESSAGE_UNHANDLED(handled
= false)
271 IPC_END_MESSAGE_MAP()
275 void OnChannelError() override
{ base::MessageLoop::current()->Quit(); }
278 NSSDecryptor decryptor_
;
279 IPC::Sender
* sender_
;
282 // Entry function in child process.
283 MULTIPROCESS_IPC_TEST_MAIN(NSSDecrypterChildProcess
) {
284 base::MessageLoopForIO main_message_loop
;
285 FFDecryptorClientChannelListener listener
;
287 scoped_ptr
<IPC::Channel
> channel
= IPC::Channel::CreateClient(
288 kTestChannelID
, &listener
);
289 CHECK(channel
->Connect());
290 listener
.SetSender(channel
.get());
293 base::MessageLoop::current()->Run();