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.
5 #include "cloud_print/service/win/chrome_launcher.h"
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"
32 const int kShutdownTimeoutMs
= 30 * 1000;
33 const int kUsageUpdateTimeoutMs
= 6 * 3600 * 1000; // 6 hours.
35 static const char16 kAutoRunKeyPath
[] =
36 L
"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
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
)) {
44 LOG(ERROR
) << "Failed to shutdown process.";
45 base::KillProcess(process
, 0, true);
48 BOOL CALLBACK
CloseIfPidEqual(HWND wnd
, LPARAM lparam
) {
50 ::GetWindowThreadProcessId(wnd
, &pid
);
51 if (pid
== static_cast<DWORD
>(lparam
))
52 ::PostMessage(wnd
, WM_CLOSE
, 0, 0);
56 void CloseAllProcessWindows(HANDLE process
) {
57 ::EnumWindows(&CloseIfPidEqual
, GetProcessId(process
));
60 // Close Chrome browser window.
61 void CloseChrome(HANDLE process
, DWORD thread_id
) {
62 CloseAllProcessWindows(process
);
63 if (WAIT_OBJECT_0
== ::WaitForSingleObject(process
, kShutdownTimeoutMs
)) {
66 ShutdownChrome(process
, thread_id
);
69 bool LaunchProcess(const CommandLine
& cmdline
,
70 base::ProcessHandle
* process_handle
,
72 STARTUPINFO startup_info
= {};
73 startup_info
.cb
= sizeof(startup_info
);
74 startup_info
.dwFlags
= STARTF_USESHOWWINDOW
;
75 startup_info
.wShowWindow
= SW_SHOW
;
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())) {
85 *process_handle
= process_info
.TakeProcessHandle();
88 *thread_id
= process_info
.thread_id();
93 GURL
GetCloudPrintServiceEnableURL(const std::string
& proxy_id
) {
95 CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
96 switches::kCloudPrintServiceURL
));
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
);
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());
114 std::string
ReadAndUpdateServiceState(const base::FilePath
& directory
,
115 const std::string
& proxy_id
) {
117 base::FilePath file_path
= directory
.Append(chrome::kServiceStateFileName
);
118 if (!base::ReadFileToString(file_path
, &json
)) {
119 return std::string();
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();
128 bool enabled
= false;
129 if (!dictionary
->GetBoolean(prefs::kCloudPrintProxyEnabled
, &enabled
) ||
131 return std::string();
134 std::string refresh_token
;
135 if (!dictionary
->GetString(prefs::kCloudPrintRobotRefreshToken
,
137 refresh_token
.empty()) {
138 return std::string();
141 // Remove everything except kCloudPrintRoot.
142 scoped_ptr
<base::Value
> cloud_print_root
;
143 dictionary
->Remove(prefs::kCloudPrintRoot
, &cloud_print_root
);
145 dictionary
->Set(prefs::kCloudPrintRoot
, cloud_print_root
.release());
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
);
151 base::JSONWriter::WriteWithOptions(dictionary
,
152 base::JSONWriter::OPTIONS_PRETTY_PRINT
,
157 void DeleteAutorunKeys(const base::FilePath
& user_data_dir
) {
158 base::win::RegKey
key(HKEY_CURRENT_USER
, kAutoRunKeyPath
, KEY_SET_VALUE
);
161 std::vector
<string16
> to_delete
;
163 base::FilePath abs_user_data_dir
= base::MakeAbsoluteFilePath(user_data_dir
);
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());
183 for (size_t i
= 0; i
< to_delete
.size(); ++i
) {
184 key
.DeleteValue(to_delete
[i
].c_str());
190 ChromeLauncher::ChromeLauncher(const base::FilePath
& user_data
)
191 : stop_event_(true, true),
192 user_data_(user_data
) {
195 ChromeLauncher::~ChromeLauncher() {
198 bool ChromeLauncher::Start() {
199 DeleteAutorunKeys(user_data_
);
201 thread_
.reset(new base::DelegateSimpleThread(this, "chrome_launcher"));
206 void ChromeLauncher::Stop() {
207 stop_event_
.Signal();
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);
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();
220 if (!chrome_path
.empty()) {
221 CommandLine
cmd(chrome_path
);
222 CopyChromeSwitchesFromCurrentProcess(&cmd
);
224 // Required switches.
225 cmd
.AppendSwitchASCII(switches::kProcessType
, switches::kServiceProcess
);
226 cmd
.AppendSwitchPath(switches::kUserDataDir
, user_data_
);
227 cmd
.AppendSwitch(switches::kNoServiceAutorun
);
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
);
240 base::win::ScopedHandle chrome_handle
;
241 base::Time started
= base::Time::Now();
243 LaunchProcess(cmd
, chrome_handle
.Receive(), &thread_id
);
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
);
252 if (wait_result
== WAIT_OBJECT_0
) {
253 ShutdownChrome(chrome_handle
, thread_id
);
255 } else if (wait_result
== WAIT_OBJECT_0
+ 1) {
256 LOG(ERROR
) << "Chrome process exited.";
258 LOG(ERROR
) << "Error waiting Chrome (" << ::GetLastError() << ").";
260 if (base::Time::Now() - started
> base::TimeDelta::FromHours(1)) {
261 // Reset timeout because process worked long enough.
262 time_out
= default_time_out
;
265 if (stop_event_
.TimedWait(time_out
))
270 std::string
ChromeLauncher::CreateServiceStateFile(
271 const std::string
& proxy_id
,
272 const std::vector
<std::string
>& printers
) {
275 base::ScopedTempDir temp_user_data
;
276 if (!temp_user_data
.CreateUniqueTempDir()) {
277 LOG(ERROR
) << "Can't create temp dir.";
281 base::FilePath chrome_path
= chrome_launcher_support::GetAnyChromePath();
283 if (chrome_path
.empty()) {
284 LOG(ERROR
) << "Can't find Chrome.";
288 base::FilePath printers_file
= temp_user_data
.path().Append(L
"printers.json");
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.";
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
);
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
);
316 cmd
.AppendArg(GetCloudPrintServiceEnableURLWithSignin(proxy_id
).spec());
318 base::win::ScopedHandle chrome_handle
;
320 if (!LaunchProcess(cmd
, chrome_handle
.Receive(), &thread_id
)) {
321 LOG(ERROR
) << "Unable to launch Chrome.";
326 DWORD wait_result
= ::WaitForSingleObject(chrome_handle
, 500);
327 std::string json
= ReadAndUpdateServiceState(temp_user_data
.path(),
329 if (wait_result
== WAIT_OBJECT_0
) {
330 // Return what we have because browser is closed.
332 } else if (wait_result
== WAIT_TIMEOUT
) {
334 // Close chrome because Service State is ready.
335 CloseChrome(chrome_handle
, thread_id
);
339 LOG(ERROR
) << "Chrome launch failed.";
344 return std::string();