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