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