Home | History | Annotate | Download | only in importer
      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 
     29 namespace {
     30 
     31 // Name of IPC Channel to use for Server<-> Child Communications.
     32 const 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.
     39 bool 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.
     70 class 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 
    134 FFUnitTestDecryptorProxy::FFUnitTestDecryptorProxy()
    135     : child_process_(0) {
    136 }
    137 
    138 bool 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 
    154 FFUnitTestDecryptorProxy::~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.
    166 class 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.
    181 bool 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 
    202 bool 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 
    213 base::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 
    223 std::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.
    238 class 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.
    289 MULTIPROCESS_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