blob: bf0028be4d311113338fe96e2ae07daeec934357 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/utility/importer/firefox_importer_unittest_utils.h"
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/location.h"
#include "base/message_loop/message_loop.h"
#include "base/posix/global_descriptors.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/common/importer/firefox_importer_utils.h"
#include "content/public/common/content_descriptors.h"
#include "content/public/common/mojo_channel_switches.h"
#include "ipc/ipc_channel.h"
#include "ipc/ipc_listener.h"
#include "ipc/ipc_message.h"
#include "mojo/edk/embedder/embedder.h"
#include "mojo/edk/embedder/incoming_broker_client_invitation.h"
#include "mojo/edk/embedder/outgoing_broker_client_invitation.h"
#include "mojo/edk/embedder/platform_channel_pair.h"
#include "mojo/edk/embedder/scoped_platform_handle.h"
#include "testing/multiprocess_func_list.h"
#define IPC_MESSAGE_IMPL
#include "chrome/utility/importer/firefox_importer_unittest_messages_internal.h"
namespace {
const char kMojoChannelToken[] = "mojo-channel-token";
// Launch the child process:
// |nss_path| - path to the NSS directory holding the decryption libraries.
// |mojo_handle| - platform handle for Mojo transport.
// |mojo_channel_token| - token for creating the IPC channel over Mojo.
base::Process LaunchNSSDecrypterChildProcess(
const base::FilePath& nss_path,
mojo::edk::ScopedPlatformHandle mojo_handle,
const std::string& mojo_channel_token) {
base::CommandLine cl(*base::CommandLine::ForCurrentProcess());
cl.AppendSwitchASCII(switches::kTestChildProcess, "NSSDecrypterChildProcess");
cl.AppendSwitchASCII(kMojoChannelToken, mojo_channel_token);
// Set env variable needed for FF encryption libs to load.
// See "chrome/utility/importer/nss_decryptor_mac.mm" for an explanation of
// why we need this.
base::LaunchOptions options;
options.environ["DYLD_FALLBACK_LIBRARY_PATH"] = nss_path.value();
options.fds_to_remap.push_back(std::pair<int, int>(
mojo_handle.get().handle,
kMojoIPCChannel + base::GlobalDescriptors::kBaseDescriptor));
return base::LaunchProcess(cl.argv(), options);
}
} // namespace
//----------------------- Server --------------------
// Class to communicate on the server side of the IPC Channel.
// Method calls are sent over IPC and replies are read back into class
// variables.
// This class needs to be called on a single thread.
class FFDecryptorServerChannelListener : public IPC::Listener {
public:
FFDecryptorServerChannelListener()
: got_result(false), sender_(NULL) {}
void SetSender(IPC::Sender* sender) {
sender_ = sender;
}
void OnInitDecryptorResponse(bool result) {
DCHECK(!got_result);
result_bool = result;
got_result = true;
base::RunLoop::QuitCurrentWhenIdleDeprecated();
}
void OnDecryptedTextResponse(const base::string16& decrypted_text) {
DCHECK(!got_result);
result_string = decrypted_text;
got_result = true;
base::RunLoop::QuitCurrentWhenIdleDeprecated();
}
void OnParseSignonsResponse(
const std::vector<autofill::PasswordForm>& parsed_vector) {
DCHECK(!got_result);
result_vector = parsed_vector;
got_result = true;
base::RunLoop::QuitCurrentWhenIdleDeprecated();
}
void QuitClient() {
if (sender_)
sender_->Send(new Msg_Decryptor_Quit());
}
bool OnMessageReceived(const IPC::Message& msg) override {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(FFDecryptorServerChannelListener, msg)
IPC_MESSAGE_HANDLER(Msg_Decryptor_InitReturnCode, OnInitDecryptorResponse)
IPC_MESSAGE_HANDLER(Msg_Decryptor_Response, OnDecryptedTextResponse)
IPC_MESSAGE_HANDLER(Msg_ParseSignons_Response, OnParseSignonsResponse)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
// If an error occured, just kill the message Loop.
void OnChannelError() override {
got_result = false;
base::RunLoop::QuitCurrentWhenIdleDeprecated();
}
// Results of IPC calls.
base::string16 result_string;
std::vector<autofill::PasswordForm> result_vector;
bool result_bool;
// True if IPC call succeeded and data in above variables is valid.
bool got_result;
private:
IPC::Sender* sender_; // weak
};
FFUnitTestDecryptorProxy::FFUnitTestDecryptorProxy() {
}
bool FFUnitTestDecryptorProxy::Setup(const base::FilePath& nss_path) {
// Create a new message loop and spawn the child process.
message_loop_.reset(new base::MessageLoopForIO());
listener_.reset(new FFDecryptorServerChannelListener());
// Set up IPC channel using ChannelMojo.
mojo::edk::OutgoingBrokerClientInvitation invitation;
std::string token = mojo::edk::GenerateRandomToken();
mojo::ScopedMessagePipeHandle parent_pipe =
invitation.AttachMessagePipe(token);
channel_ = IPC::Channel::CreateServer(parent_pipe.release(), listener_.get());
CHECK(channel_->Connect());
listener_->SetSender(channel_.get());
// Spawn child and set up sync IPC connection.
mojo::edk::PlatformChannelPair channel_pair;
child_process_ = LaunchNSSDecrypterChildProcess(
nss_path, channel_pair.PassClientHandle(), token);
if (child_process_.IsValid()) {
invitation.Send(
child_process_.Handle(),
mojo::edk::ConnectionParams(mojo::edk::TransportProtocol::kLegacy,
channel_pair.PassServerHandle()));
}
return child_process_.IsValid();
}
FFUnitTestDecryptorProxy::~FFUnitTestDecryptorProxy() {
listener_->QuitClient();
channel_->Close();
if (child_process_.IsValid()) {
int exit_code;
child_process_.WaitForExitWithTimeout(base::TimeDelta::FromSeconds(5),
&exit_code);
}
}
// Spin until either a client response arrives or a timeout occurs.
void FFUnitTestDecryptorProxy::WaitForClientResponse() {
// What we're trying to do here is to wait for an RPC message to go over the
// wire and the client to reply. If the client does not reply by a given
// timeout we kill the message loop.
// This relies on the IPC listener class to quit the message loop itself when
// a message comes in.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitWhenIdleClosure(),
TestTimeouts::action_max_timeout());
run_loop.Run();
}
bool FFUnitTestDecryptorProxy::DecryptorInit(const base::FilePath& dll_path,
const base::FilePath& db_path) {
channel_->Send(new Msg_Decryptor_Init(dll_path, db_path));
WaitForClientResponse();
if (listener_->got_result) {
listener_->got_result = false;
return listener_->result_bool;
}
return false;
}
base::string16 FFUnitTestDecryptorProxy::Decrypt(const std::string& crypt) {
channel_->Send(new Msg_Decrypt(crypt));
WaitForClientResponse();
if (listener_->got_result) {
listener_->got_result = false;
return listener_->result_string;
}
return base::string16();
}
std::vector<autofill::PasswordForm> FFUnitTestDecryptorProxy::ParseSignons(
const base::FilePath& signons_path) {
channel_->Send(new Msg_ParseSignons(signons_path));
WaitForClientResponse();
if (listener_->got_result) {
listener_->got_result = false;
return listener_->result_vector;
}
return std::vector<autofill::PasswordForm>();
}
//---------------------------- Child Process -----------------------
// Class to listen on the client side of the ipc channel, it calls through
// to the NSSDecryptor and sends back a reply.
class FFDecryptorClientChannelListener : public IPC::Listener {
public:
FFDecryptorClientChannelListener()
: sender_(NULL) {}
void SetSender(IPC::Sender* sender) {
sender_ = sender;
}
void OnDecryptor_Init(base::FilePath dll_path, base::FilePath db_path) {
bool ret = decryptor_.Init(dll_path, db_path);
sender_->Send(new Msg_Decryptor_InitReturnCode(ret));
}
void OnDecrypt(const std::string& crypt) {
base::string16 unencrypted_str = decryptor_.Decrypt(crypt);
sender_->Send(new Msg_Decryptor_Response(unencrypted_str));
}
void OnParseSignons(base::FilePath signons_path) {
std::vector<autofill::PasswordForm> forms;
decryptor_.ReadAndParseSignons(signons_path, &forms);
sender_->Send(new Msg_ParseSignons_Response(forms));
}
void OnQuitRequest() { base::RunLoop::QuitCurrentWhenIdleDeprecated(); }
bool OnMessageReceived(const IPC::Message& msg) override {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(FFDecryptorClientChannelListener, msg)
IPC_MESSAGE_HANDLER(Msg_Decryptor_Init, OnDecryptor_Init)
IPC_MESSAGE_HANDLER(Msg_Decrypt, OnDecrypt)
IPC_MESSAGE_HANDLER(Msg_ParseSignons, OnParseSignons)
IPC_MESSAGE_HANDLER(Msg_Decryptor_Quit, OnQuitRequest)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void OnChannelError() override {
base::RunLoop::QuitCurrentWhenIdleDeprecated();
}
private:
NSSDecryptor decryptor_;
IPC::Sender* sender_;
};
// Entry function in child process.
MULTIPROCESS_TEST_MAIN(NSSDecrypterChildProcess) {
base::MessageLoopForIO main_message_loop;
FFDecryptorClientChannelListener listener;
auto invitation = mojo::edk::IncomingBrokerClientInvitation::Accept(
mojo::edk::ConnectionParams(
mojo::edk::TransportProtocol::kLegacy,
mojo::edk::ScopedPlatformHandle(mojo::edk::PlatformHandle(
kMojoIPCChannel + base::GlobalDescriptors::kBaseDescriptor))));
mojo::ScopedMessagePipeHandle mojo_handle = invitation->ExtractMessagePipe(
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kMojoChannelToken));
std::unique_ptr<IPC::Channel> channel =
IPC::Channel::CreateClient(mojo_handle.release(), &listener);
CHECK(channel->Connect());
listener.SetSender(channel.get());
// run message loop
base::RunLoop().Run();
return 0;
}