Home | History | Annotate | Download | only in win
      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 "cloud_print/service/win/chrome_launcher.h"
      6 
      7 #include "base/base_switches.h"
      8 #include "base/command_line.h"
      9 #include "base/files/file_util.h"
     10 #include "base/files/scoped_temp_dir.h"
     11 #include "base/json/json_reader.h"
     12 #include "base/json/json_writer.h"
     13 #include "base/process/kill.h"
     14 #include "base/process/process.h"
     15 #include "base/values.h"
     16 #include "base/win/registry.h"
     17 #include "base/win/scoped_handle.h"
     18 #include "base/win/scoped_process_information.h"
     19 #include "chrome/common/chrome_constants.h"
     20 #include "chrome/common/chrome_switches.h"
     21 #include "chrome/common/pref_names.h"
     22 #include "chrome/installer/launcher_support/chrome_launcher_support.h"
     23 #include "cloud_print/common/win/cloud_print_utils.h"
     24 #include "cloud_print/service/service_constants.h"
     25 #include "cloud_print/service/win/service_utils.h"
     26 #include "components/cloud_devices/common/cloud_devices_urls.h"
     27 #include "google_apis/gaia/gaia_urls.h"
     28 #include "net/base/url_util.h"
     29 #include "url/gurl.h"
     30 
     31 namespace {
     32 
     33 const int kShutdownTimeoutMs = 30 * 1000;
     34 const int kUsageUpdateTimeoutMs = 6 * 3600 * 1000;  // 6 hours.
     35 
     36 static const base::char16 kAutoRunKeyPath[] =
     37     L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
     38 
     39 // Terminates any process.
     40 void ShutdownChrome(HANDLE process, DWORD thread_id) {
     41   if (::PostThreadMessage(thread_id, WM_QUIT, 0, 0) &&
     42       WAIT_OBJECT_0 == ::WaitForSingleObject(process, kShutdownTimeoutMs)) {
     43     return;
     44   }
     45   LOG(ERROR) << "Failed to shutdown process.";
     46   base::KillProcess(process, 0, true);
     47 }
     48 
     49 BOOL CALLBACK CloseIfPidEqual(HWND wnd, LPARAM lparam) {
     50   DWORD pid = 0;
     51   ::GetWindowThreadProcessId(wnd, &pid);
     52   if (pid == static_cast<DWORD>(lparam))
     53     ::PostMessage(wnd, WM_CLOSE, 0, 0);
     54   return TRUE;
     55 }
     56 
     57 void CloseAllProcessWindows(HANDLE process) {
     58   ::EnumWindows(&CloseIfPidEqual, GetProcessId(process));
     59 }
     60 
     61 // Close Chrome browser window.
     62 void CloseChrome(HANDLE process, DWORD thread_id) {
     63   CloseAllProcessWindows(process);
     64   if (WAIT_OBJECT_0 == ::WaitForSingleObject(process, kShutdownTimeoutMs)) {
     65     return;
     66   }
     67   ShutdownChrome(process, thread_id);
     68 }
     69 
     70 bool LaunchProcess(const CommandLine& cmdline,
     71                    base::win::ScopedHandle* process_handle,
     72                    DWORD* thread_id) {
     73   STARTUPINFO startup_info = {};
     74   startup_info.cb = sizeof(startup_info);
     75   startup_info.dwFlags = STARTF_USESHOWWINDOW;
     76   startup_info.wShowWindow = SW_SHOW;
     77 
     78   PROCESS_INFORMATION temp_process_info = {};
     79   base::FilePath::StringType writable_cmdline_str(
     80       cmdline.GetCommandLineString());
     81   if (!CreateProcess(NULL,
     82       &writable_cmdline_str[0], NULL, NULL,
     83       FALSE, 0, NULL, NULL, &startup_info, &temp_process_info)) {
     84     return false;
     85   }
     86   base::win::ScopedProcessInformation process_info(temp_process_info);
     87 
     88   if (process_handle)
     89     process_handle->Set(process_info.TakeProcessHandle());
     90 
     91   if (thread_id)
     92     *thread_id = process_info.thread_id();
     93 
     94   return true;
     95 }
     96 
     97 std::string ReadAndUpdateServiceState(const base::FilePath& directory,
     98                                       const std::string& proxy_id) {
     99   std::string json;
    100   base::FilePath file_path = directory.Append(chrome::kServiceStateFileName);
    101   if (!base::ReadFileToString(file_path, &json)) {
    102     return std::string();
    103   }
    104 
    105   scoped_ptr<base::Value> service_state(base::JSONReader::Read(json));
    106   base::DictionaryValue* dictionary = NULL;
    107   if (!service_state->GetAsDictionary(&dictionary) || !dictionary) {
    108     return std::string();
    109   }
    110 
    111   bool enabled = false;
    112   if (!dictionary->GetBoolean(prefs::kCloudPrintProxyEnabled, &enabled) ||
    113       !enabled) {
    114     return std::string();
    115   }
    116 
    117   std::string refresh_token;
    118   if (!dictionary->GetString(prefs::kCloudPrintRobotRefreshToken,
    119                              &refresh_token) ||
    120       refresh_token.empty()) {
    121     return std::string();
    122   }
    123 
    124   // Remove everything except kCloudPrintRoot.
    125   scoped_ptr<base::Value> cloud_print_root;
    126   dictionary->Remove(prefs::kCloudPrintRoot, &cloud_print_root);
    127   dictionary->Clear();
    128   dictionary->Set(prefs::kCloudPrintRoot, cloud_print_root.release());
    129 
    130   dictionary->SetBoolean(prefs::kCloudPrintXmppPingEnabled, true);
    131   if (!proxy_id.empty())  // Reuse proxy id if we already had one.
    132     dictionary->SetString(prefs::kCloudPrintProxyId, proxy_id);
    133   std::string result;
    134   base::JSONWriter::WriteWithOptions(dictionary,
    135                                      base::JSONWriter::OPTIONS_PRETTY_PRINT,
    136                                      &result);
    137   return result;
    138 }
    139 
    140 void DeleteAutorunKeys(const base::FilePath& user_data_dir) {
    141   base::win::RegKey key(HKEY_CURRENT_USER, kAutoRunKeyPath, KEY_SET_VALUE);
    142   if (!key.Valid())
    143     return;
    144   std::vector<base::string16> to_delete;
    145 
    146   base::FilePath abs_user_data_dir = base::MakeAbsoluteFilePath(user_data_dir);
    147 
    148   {
    149     base::win::RegistryValueIterator value(HKEY_CURRENT_USER, kAutoRunKeyPath);
    150     for (; value.Valid(); ++value) {
    151       if (value.Type() == REG_SZ && value.Value()) {
    152         CommandLine cmd = CommandLine::FromString(value.Value());
    153         if (cmd.GetSwitchValueASCII(switches::kProcessType) ==
    154             switches::kServiceProcess &&
    155             cmd.HasSwitch(switches::kUserDataDir)) {
    156           base::FilePath path_from_reg = base::MakeAbsoluteFilePath(
    157               cmd.GetSwitchValuePath(switches::kUserDataDir));
    158           if (path_from_reg == abs_user_data_dir) {
    159             to_delete.push_back(value.Name());
    160           }
    161         }
    162       }
    163     }
    164   }
    165 
    166   for (size_t i = 0; i < to_delete.size(); ++i) {
    167     key.DeleteValue(to_delete[i].c_str());
    168   }
    169 }
    170 
    171 }  // namespace
    172 
    173 ChromeLauncher::ChromeLauncher(const base::FilePath& user_data)
    174     : stop_event_(true, true),
    175       user_data_(user_data) {
    176 }
    177 
    178 ChromeLauncher::~ChromeLauncher() {
    179 }
    180 
    181 bool ChromeLauncher::Start() {
    182   DeleteAutorunKeys(user_data_);
    183   stop_event_.Reset();
    184   thread_.reset(new base::DelegateSimpleThread(this, "chrome_launcher"));
    185   thread_->Start();
    186   return true;
    187 }
    188 
    189 void ChromeLauncher::Stop() {
    190   stop_event_.Signal();
    191   thread_->Join();
    192   thread_.reset();
    193 }
    194 
    195 void ChromeLauncher::Run() {
    196   const base::TimeDelta default_time_out = base::TimeDelta::FromSeconds(1);
    197   const base::TimeDelta max_time_out = base::TimeDelta::FromHours(1);
    198 
    199   for (base::TimeDelta time_out = default_time_out;;
    200        time_out = std::min(time_out * 2, max_time_out)) {
    201     base::FilePath chrome_path = chrome_launcher_support::GetAnyChromePath();
    202 
    203     if (!chrome_path.empty()) {
    204       CommandLine cmd(chrome_path);
    205       CopyChromeSwitchesFromCurrentProcess(&cmd);
    206 
    207       // Required switches.
    208       cmd.AppendSwitchASCII(switches::kProcessType, switches::kServiceProcess);
    209       cmd.AppendSwitchPath(switches::kUserDataDir, user_data_);
    210       cmd.AppendSwitch(switches::kNoServiceAutorun);
    211 
    212       // Optional.
    213       cmd.AppendSwitch(switches::kAutoLaunchAtStartup);
    214       cmd.AppendSwitch(switches::kDisableDefaultApps);
    215       cmd.AppendSwitch(switches::kDisableExtensions);
    216       cmd.AppendSwitch(switches::kDisableGpu);
    217       cmd.AppendSwitch(switches::kDisableSoftwareRasterizer);
    218       cmd.AppendSwitch(switches::kDisableSync);
    219       cmd.AppendSwitch(switches::kNoFirstRun);
    220       cmd.AppendSwitch(switches::kNoStartupWindow);
    221 
    222       base::win::ScopedHandle chrome_handle;
    223       base::Time started = base::Time::Now();
    224       DWORD thread_id = 0;
    225       LaunchProcess(cmd, &chrome_handle, &thread_id);
    226 
    227       HANDLE handles[] = { stop_event_.handle(), chrome_handle.Get() };
    228       DWORD wait_result = WAIT_TIMEOUT;
    229       while (wait_result == WAIT_TIMEOUT) {
    230         cloud_print::SetGoogleUpdateUsage(kGoogleUpdateId);
    231         wait_result = ::WaitForMultipleObjects(arraysize(handles), handles,
    232                                                FALSE, kUsageUpdateTimeoutMs);
    233       }
    234       if (wait_result == WAIT_OBJECT_0) {
    235         ShutdownChrome(chrome_handle.Get(), thread_id);
    236         break;
    237       } else if (wait_result == WAIT_OBJECT_0 + 1) {
    238         LOG(ERROR) << "Chrome process exited.";
    239       } else {
    240         LOG(ERROR) << "Error waiting Chrome (" << ::GetLastError() << ").";
    241       }
    242       if (base::Time::Now() - started > base::TimeDelta::FromHours(1)) {
    243         // Reset timeout because process worked long enough.
    244         time_out = default_time_out;
    245       }
    246     }
    247     if (stop_event_.TimedWait(time_out))
    248       break;
    249   }
    250 }
    251 
    252 std::string ChromeLauncher::CreateServiceStateFile(
    253     const std::string& proxy_id,
    254     const std::vector<std::string>& printers) {
    255   base::ScopedTempDir temp_user_data;
    256   if (!temp_user_data.CreateUniqueTempDir()) {
    257     LOG(ERROR) << "Can't create temp dir.";
    258     return std::string();
    259   }
    260 
    261   base::FilePath chrome_path = chrome_launcher_support::GetAnyChromePath();
    262   if (chrome_path.empty()) {
    263     LOG(ERROR) << "Can't find Chrome.";
    264     return std::string();
    265   }
    266 
    267   base::FilePath printers_file = temp_user_data.path().Append(L"printers.json");
    268 
    269   base::ListValue printer_list;
    270   printer_list.AppendStrings(printers);
    271   std::string printers_json;
    272   base::JSONWriter::Write(&printer_list, &printers_json);
    273   size_t written = base::WriteFile(printers_file,
    274                                    printers_json.c_str(),
    275                                    printers_json.size());
    276   if (written != printers_json.size()) {
    277     LOG(ERROR) << "Can't write file.";
    278     return std::string();
    279   }
    280 
    281   CommandLine cmd(chrome_path);
    282   CopyChromeSwitchesFromCurrentProcess(&cmd);
    283   cmd.AppendSwitchPath(switches::kUserDataDir, temp_user_data.path());
    284   cmd.AppendSwitchPath(switches::kCloudPrintSetupProxy, printers_file);
    285   cmd.AppendSwitch(switches::kNoServiceAutorun);
    286 
    287   // Optional.
    288   cmd.AppendSwitch(switches::kDisableDefaultApps);
    289   cmd.AppendSwitch(switches::kDisableExtensions);
    290   cmd.AppendSwitch(switches::kDisableSync);
    291   cmd.AppendSwitch(switches::kNoDefaultBrowserCheck);
    292   cmd.AppendSwitch(switches::kNoFirstRun);
    293 
    294   cmd.AppendArg(
    295       cloud_devices::GetCloudPrintEnableWithSigninURL(proxy_id).spec());
    296 
    297   base::win::ScopedHandle chrome_handle;
    298   DWORD thread_id = 0;
    299   if (!LaunchProcess(cmd, &chrome_handle, &thread_id)) {
    300     LOG(ERROR) << "Unable to launch Chrome.";
    301     return std::string();
    302   }
    303 
    304   for (;;) {
    305     DWORD wait_result = ::WaitForSingleObject(chrome_handle.Get(), 500);
    306     std::string json = ReadAndUpdateServiceState(temp_user_data.path(),
    307                                                  proxy_id);
    308     if (wait_result == WAIT_OBJECT_0) {
    309       // Return what we have because browser is closed.
    310       return json;
    311     }
    312     if (wait_result != WAIT_TIMEOUT) {
    313       LOG(ERROR) << "Chrome launch failed.";
    314       return std::string();
    315     }
    316     if (!json.empty()) {
    317       // Close chrome because Service State is ready.
    318       CloseChrome(chrome_handle.Get(), thread_id);
    319       return json;
    320     }
    321   }
    322 }
    323