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