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