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
31namespace {
32
33const int kShutdownTimeoutMs = 30 * 1000;
34const int kUsageUpdateTimeoutMs = 6 * 3600 * 1000;  // 6 hours.
35
36static const base::char16 kAutoRunKeyPath[] =
37    L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
38
39// Terminates any process.
40void 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
49BOOL 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
57void CloseAllProcessWindows(HANDLE process) {
58  ::EnumWindows(&CloseIfPidEqual, GetProcessId(process));
59}
60
61// Close Chrome browser window.
62void 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
70bool 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
97std::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
140void 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
173ChromeLauncher::ChromeLauncher(const base::FilePath& user_data)
174    : stop_event_(true, true),
175      user_data_(user_data) {
176}
177
178ChromeLauncher::~ChromeLauncher() {
179}
180
181bool 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
189void ChromeLauncher::Stop() {
190  stop_event_.Signal();
191  thread_->Join();
192  thread_.reset();
193}
194
195void 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
252std::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