• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
ShutdownChrome(HANDLE process,DWORD thread_id)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 
CloseIfPidEqual(HWND wnd,LPARAM lparam)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 
CloseAllProcessWindows(HANDLE process)56 void CloseAllProcessWindows(HANDLE process) {
57   ::EnumWindows(&CloseIfPidEqual, GetProcessId(process));
58 }
59 
60 // Close Chrome browser window.
CloseChrome(HANDLE process,DWORD thread_id)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 
LaunchProcess(const CommandLine & cmdline,base::win::ScopedHandle * process_handle,DWORD * thread_id)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 
GetCloudPrintServiceEnableURL(const std::string & proxy_id)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 
GetCloudPrintServiceEnableURLWithSignin(const std::string & proxy_id)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 
ReadAndUpdateServiceState(const base::FilePath & directory,const std::string & proxy_id)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 
DeleteAutorunKeys(const base::FilePath & user_data_dir)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 
ChromeLauncher(const base::FilePath & user_data)191 ChromeLauncher::ChromeLauncher(const base::FilePath& user_data)
192     : stop_event_(true, true),
193       user_data_(user_data) {
194 }
195 
~ChromeLauncher()196 ChromeLauncher::~ChromeLauncher() {
197 }
198 
Start()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 
Stop()207 void ChromeLauncher::Stop() {
208   stop_event_.Signal();
209   thread_->Join();
210   thread_.reset();
211 }
212 
Run()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 
CreateServiceStateFile(const std::string & proxy_id,const std::vector<std::string> & printers)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