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