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/virtual_driver/win/port_monitor/port_monitor.h"
15 #include "base/at_exit.h"
16 #include "base/command_line.h"
17 #include "base/file_util.h"
18 #include "base/files/file_enumerator.h"
19 #include "base/logging.h"
20 #include "base/path_service.h"
21 #include "base/process/process.h"
22 #include "base/process/launch.h"
23 #include "base/strings/string16.h"
24 #include "base/win/registry.h"
25 #include "base/win/scoped_handle.h"
26 #include "base/win/windows_version.h"
27 #include "chrome/common/chrome_switches.h"
28 #include "chrome/installer/launcher_support/chrome_launcher_support.h"
29 #include "cloud_print/common/win/cloud_print_utils.h"
30 #include "cloud_print/virtual_driver/win/port_monitor/spooler_win.h"
31 #include "cloud_print/virtual_driver/win/virtual_driver_consts.h"
32 #include "cloud_print/virtual_driver/win/virtual_driver_helpers.h"
34 namespace cloud_print {
38 const wchar_t kIePath[] = L"Internet Explorer\\iexplore.exe";
40 const char kChromeInstallUrl[] =
41 "http://google.com/cloudprint/learn/chrome.html";
43 const wchar_t kCloudPrintRegKey[] = L"Software\\Google\\CloudPrint";
45 const wchar_t kXpsMimeType[] = L"application/vnd.ms-xpsdocument";
47 const wchar_t kAppDataDir[] = L"Google\\Cloud Printer";
50 scoped_ptr<base::AtExitManager> at_exit_manager;
54 PortData() : job_id(0), printer_handle(NULL), file(0) {
61 ClosePrinter(printer_handle);
62 printer_handle = NULL;
65 file_util::CloseFile(file);
70 HANDLE printer_handle;
72 base::FilePath file_path;
76 ACCESS_MASK granted_access;
80 MONITORUI g_monitor_ui = {
83 MonitorUiConfigureOrDeletePortUI,
84 MonitorUiConfigureOrDeletePortUI
87 MONITOR2 g_monitor_2 = {
91 NULL, // OpenPortEx is not supported.
97 NULL, // AddPort is not supported.
98 NULL, // AddPortEx is not supported.
99 NULL, // ConfigurePort is not supported.
100 NULL, // DeletePort is not supported.
102 NULL, // SetPortTimeOuts is not supported.
105 Monitor2XcvClosePort,
109 base::FilePath GetAppDataDir() {
110 base::FilePath file_path;
111 base::win::Version version = base::win::GetVersion();
112 int path_id = (version >= base::win::VERSION_VISTA) ?
113 base::DIR_LOCAL_APP_DATA_LOW : base::DIR_LOCAL_APP_DATA;
114 if (!PathService::Get(path_id, &file_path)) {
115 LOG(ERROR) << "Can't get DIR_LOCAL_APP_DATA";
116 return base::FilePath();
118 return file_path.Append(kAppDataDir);
121 // Delete files which where not deleted by chrome.
122 void DeleteLeakedFiles(const base::FilePath& dir) {
123 base::Time delete_before = base::Time::Now() - base::TimeDelta::FromDays(1);
124 base::FileEnumerator enumerator(dir, false, base::FileEnumerator::FILES);
125 for (base::FilePath file_path = enumerator.Next(); !file_path.empty();
126 file_path = enumerator.Next()) {
127 if (enumerator.GetInfo().GetLastModifiedTime() < delete_before)
128 base::DeleteFile(file_path, false);
132 // Attempts to retrieve the title of the specified print job.
133 // On success returns TRUE and the first title_chars characters of the job title
134 // are copied into title.
135 // On failure returns FALSE and title is unmodified.
136 bool GetJobTitle(HANDLE printer_handle,
139 DCHECK(printer_handle != NULL);
140 DCHECK(title != NULL);
141 DWORD bytes_needed = 0;
142 GetJob(printer_handle, job_id, 1, NULL, 0, &bytes_needed);
143 if (bytes_needed == 0) {
144 LOG(ERROR) << "Unable to get bytes needed for job info.";
147 scoped_ptr<BYTE[]> buffer(new BYTE[bytes_needed]);
148 if (!GetJob(printer_handle,
154 LOG(ERROR) << "Unable to get job info.";
157 JOB_INFO_1* job_info = reinterpret_cast<JOB_INFO_1*>(buffer.get());
158 *title = job_info->pDocument;
162 // Handler for the UI functions exported by the port monitor.
163 // Verifies that a valid parent Window exists and then just displays an
164 // error message to let the user know that there is no interactive
166 void HandlePortUi(HWND hwnd, const string16& caption) {
167 if (hwnd != NULL && IsWindow(hwnd)) {
168 DisplayWindowsMessage(hwnd, CO_E_NOT_SUPPORTED, cloud_print::kPortName);
172 // Gets the primary token for the user that submitted the print job.
173 bool GetUserToken(HANDLE* primary_token) {
175 if (!OpenThreadToken(GetCurrentThread(),
176 TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
179 LOG(ERROR) << "Unable to get thread token.";
182 base::win::ScopedHandle token_scoped(token);
183 if (!DuplicateTokenEx(token,
184 TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
186 SecurityImpersonation,
189 LOG(ERROR) << "Unable to get primary thread token.";
195 // Launches the Cloud Print dialog in Chrome.
196 // xps_path references a file to print.
197 // job_title is the title to be used for the resulting print job.
198 bool LaunchPrintDialog(const base::FilePath& xps_path,
199 const string16& job_title) {
201 if (!GetUserToken(&token)) {
202 LOG(ERROR) << "Unable to get user token.";
205 base::win::ScopedHandle primary_token_scoped(token);
207 base::FilePath chrome_path = GetChromeExePath();
208 if (chrome_path.empty()) {
209 LOG(ERROR) << "Unable to get chrome exe path.";
213 CommandLine command_line(chrome_path);
215 base::FilePath chrome_profile = GetChromeProfilePath();
216 if (!chrome_profile.empty()) {
217 command_line.AppendSwitchPath(switches::kUserDataDir, chrome_profile);
220 command_line.AppendSwitchPath(switches::kCloudPrintFile,
222 command_line.AppendSwitchNative(switches::kCloudPrintFileType,
224 command_line.AppendSwitchNative(switches::kCloudPrintJobTitle,
226 command_line.AppendSwitch(switches::kCloudPrintDeleteFile);
227 base::LaunchOptions options;
228 options.as_user = primary_token_scoped;
229 base::LaunchProcess(command_line, options, NULL);
233 // Launches a page to allow the user to download chrome.
234 // TODO(abodenha@chromium.org) Point to a custom page explaining what's wrong
235 // rather than the generic chrome download page. See
236 // http://code.google.com/p/chromium/issues/detail?id=112019
237 void LaunchChromeDownloadPage() {
241 if (!GetUserToken(&token)) {
242 LOG(ERROR) << "Unable to get user token.";
245 base::win::ScopedHandle token_scoped(token);
247 base::FilePath ie_path;
248 PathService::Get(base::DIR_PROGRAM_FILESX86, &ie_path);
249 ie_path = ie_path.Append(kIePath);
250 CommandLine command_line(ie_path);
251 command_line.AppendArg(kChromeInstallUrl);
253 base::LaunchOptions options;
254 options.as_user = token_scoped;
255 base::LaunchProcess(command_line, options, NULL);
258 // Returns false if the print job is being run in a context
259 // that shouldn't be launching Chrome.
260 bool ValidateCurrentUser() {
262 if (!GetUserToken(&token)) {
263 // If we can't get the token we're probably not impersonating
264 // the user, so validation should fail.
267 base::win::ScopedHandle token_scoped(token);
269 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
270 DWORD session_id = 0;
272 if (!GetTokenInformation(token_scoped,
274 reinterpret_cast<void *>(&session_id),
279 if (session_id == 0) {
287 base::FilePath ReadPathFromRegistry(HKEY root, const wchar_t* path_name) {
288 base::win::RegKey gcp_key(HKEY_CURRENT_USER, kCloudPrintRegKey, KEY_READ);
290 if (SUCCEEDED(gcp_key.ReadValue(path_name, &data)) &&
291 base::PathExists(base::FilePath(data))) {
292 return base::FilePath(data);
294 return base::FilePath();
297 base::FilePath ReadPathFromAnyRegistry(const wchar_t* path_name) {
298 base::FilePath result = ReadPathFromRegistry(HKEY_CURRENT_USER, path_name);
301 return ReadPathFromRegistry(HKEY_LOCAL_MACHINE, path_name);
304 base::FilePath GetChromeExePath() {
305 base::FilePath path = ReadPathFromAnyRegistry(kChromeExePathRegValue);
308 return chrome_launcher_support::GetAnyChromePath();
311 base::FilePath GetChromeProfilePath() {
312 base::FilePath path = ReadPathFromAnyRegistry(kChromeProfilePathRegValue);
313 if (!path.empty() && base::DirectoryExists(path))
315 return base::FilePath();
318 BOOL WINAPI Monitor2EnumPorts(HANDLE,
325 if (needed_bytes == NULL) {
326 LOG(ERROR) << "needed_bytes should not be NULL.";
327 SetLastError(ERROR_INVALID_PARAMETER);
331 *needed_bytes = sizeof(PORT_INFO_1);
332 } else if (level == 2) {
333 *needed_bytes = sizeof(PORT_INFO_2);
335 LOG(ERROR) << "Level " << level << "is not supported.";
336 SetLastError(ERROR_INVALID_LEVEL);
339 *needed_bytes += static_cast<DWORD>(cloud_print::kPortNameSize);
340 if (ports_size < *needed_bytes) {
341 LOG(WARNING) << *needed_bytes << " bytes are required. Only "
342 << ports_size << " were allocated.";
343 SetLastError(ERROR_INSUFFICIENT_BUFFER);
347 LOG(ERROR) << "ports should not be NULL.";
348 SetLastError(ERROR_INVALID_PARAMETER);
351 if (returned == NULL) {
352 LOG(ERROR) << "returned should not be NULL.";
353 SetLastError(ERROR_INVALID_PARAMETER);
357 // Windows expects any strings refernced by PORT_INFO_X structures to
358 // appear at the END of the buffer referenced by ports. Placing
359 // strings immediately after the PORT_INFO_X structure will cause
360 // EnumPorts to fail until the spooler is restarted.
361 // This is NOT mentioned in the documentation.
362 wchar_t* string_target =
363 reinterpret_cast<wchar_t*>(ports + ports_size -
364 cloud_print::kPortNameSize);
366 PORT_INFO_1* port_info = reinterpret_cast<PORT_INFO_1*>(ports);
367 port_info->pName = string_target;
368 StringCbCopy(port_info->pName,
369 cloud_print::kPortNameSize,
370 cloud_print::kPortName);
372 PORT_INFO_2* port_info = reinterpret_cast<PORT_INFO_2*>(ports);
373 port_info->pPortName = string_target;
374 StringCbCopy(port_info->pPortName,
375 cloud_print::kPortNameSize,
376 cloud_print::kPortName);
377 port_info->pMonitorName = NULL;
378 port_info->pDescription = NULL;
379 port_info->fPortType = PORT_TYPE_WRITE;
380 port_info->Reserved = 0;
386 BOOL WINAPI Monitor2OpenPort(HANDLE, wchar_t*, HANDLE* handle) {
387 if (handle == NULL) {
388 LOG(ERROR) << "handle should not be NULL.";
389 SetLastError(ERROR_INVALID_PARAMETER);
392 *handle = new PortData();
396 BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle,
397 wchar_t* printer_name,
401 SetGoogleUpdateUsage(kGoogleUpdateProductId);
402 if (port_handle == NULL) {
403 LOG(ERROR) << "port_handle should not be NULL.";
404 SetLastError(ERROR_INVALID_PARAMETER);
407 if (printer_name == NULL) {
408 LOG(ERROR) << "printer_name should not be NULL.";
409 SetLastError(ERROR_INVALID_PARAMETER);
412 if (!ValidateCurrentUser()) {
413 // TODO(abodenha@chromium.org) Abort the print job.
416 PortData* port_data = reinterpret_cast<PortData*>(port_handle);
417 port_data->job_id = job_id;
418 if (!OpenPrinter(printer_name, &(port_data->printer_handle), NULL)) {
419 LOG(WARNING) << "Unable to open printer " << printer_name << ".";
420 // We can continue without a handle to the printer.
421 // It just means we can't get the job title or tell the spooler that
422 // the print job is complete.
423 // This is the normal flow during a unit test.
424 port_data->printer_handle = NULL;
426 base::FilePath& file_path = port_data->file_path;
427 base::FilePath app_data_dir = GetAppDataDir();
428 if (app_data_dir.empty())
430 DeleteLeakedFiles(app_data_dir);
431 if (!file_util::CreateDirectory(app_data_dir) ||
432 !file_util::CreateTemporaryFileInDir(app_data_dir, &file_path)) {
433 LOG(ERROR) << "Can't create temporary file in " << app_data_dir.value();
436 port_data->file = file_util::OpenFile(file_path, "wb+");
437 if (port_data->file == NULL) {
438 LOG(ERROR) << "Error opening file " << file_path.value() << ".";
444 BOOL WINAPI Monitor2WritePort(HANDLE port_handle,
447 DWORD* bytes_written) {
448 PortData* port_data = reinterpret_cast<PortData*>(port_handle);
449 if (!ValidateCurrentUser()) {
450 // TODO(abodenha@chromium.org) Abort the print job.
454 static_cast<DWORD>(fwrite(buffer, 1, buffer_size, port_data->file));
455 if (*bytes_written > 0) {
462 BOOL WINAPI Monitor2ReadPort(HANDLE, BYTE*, DWORD, DWORD* read_bytes) {
463 LOG(ERROR) << "Read is not supported.";
465 SetLastError(ERROR_NOT_SUPPORTED);
469 BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle) {
470 if (!ValidateCurrentUser()) {
471 // TODO(abodenha@chromium.org) Abort the print job.
474 PortData* port_data = reinterpret_cast<PortData*>(port_handle);
475 if (port_data == NULL) {
476 SetLastError(ERROR_INVALID_PARAMETER);
480 if (port_data->file != NULL) {
481 file_util::CloseFile(port_data->file);
482 port_data->file = NULL;
483 bool delete_file = true;
485 file_util::GetFileSize(port_data->file_path, &file_size);
488 if (port_data->printer_handle != NULL) {
489 GetJobTitle(port_data->printer_handle,
493 if (!LaunchPrintDialog(port_data->file_path, job_title)) {
494 LaunchChromeDownloadPage();
500 base::DeleteFile(port_data->file_path, false);
502 if (port_data->printer_handle != NULL) {
503 // Tell the spooler that the job is complete.
504 SetJob(port_data->printer_handle,
508 JOB_CONTROL_SENT_TO_PRINTER);
511 // Return success even if we can't display the dialog.
512 // TODO(abodenha@chromium.org) Come up with a better way of handling
517 BOOL WINAPI Monitor2ClosePort(HANDLE port_handle) {
518 if (port_handle == NULL) {
519 LOG(ERROR) << "port_handle should not be NULL.";
520 SetLastError(ERROR_INVALID_PARAMETER);
523 PortData* port_data = reinterpret_cast<PortData*>(port_handle);
528 VOID WINAPI Monitor2Shutdown(HANDLE monitor_handle) {
529 if (monitor_handle != NULL) {
530 MonitorData* monitor_data =
531 reinterpret_cast<MonitorData*>(monitor_handle);
532 delete monitor_handle;
536 BOOL WINAPI Monitor2XcvOpenPort(HANDLE,
538 ACCESS_MASK granted_access,
540 if (handle == NULL) {
541 LOG(ERROR) << "handle should not be NULL.";
542 SetLastError(ERROR_INVALID_PARAMETER);
545 XcvUiData* xcv_data = new XcvUiData();
546 xcv_data->granted_access = granted_access;
551 DWORD WINAPI Monitor2XcvDataPort(HANDLE xcv_handle,
552 const wchar_t* data_name,
556 DWORD output_data_bytes,
557 DWORD* output_data_bytes_needed) {
558 XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(xcv_handle);
559 DWORD ret_val = ERROR_SUCCESS;
560 if ((xcv_data->granted_access & SERVER_ACCESS_ADMINISTER) == 0) {
561 return ERROR_ACCESS_DENIED;
563 if (output_data == NULL || output_data_bytes == 0) {
564 return ERROR_INVALID_PARAMETER;
566 // We don't handle AddPort or DeletePort since we don't support
567 // dynamic creation of ports.
568 if (lstrcmp(L"MonitorUI", data_name) == 0) {
569 DWORD dll_path_len = 0;
570 base::FilePath dll_path(GetPortMonitorDllName());
571 dll_path_len = static_cast<DWORD>(dll_path.value().length());
572 if (output_data_bytes_needed != NULL) {
573 *output_data_bytes_needed = dll_path_len;
575 if (output_data_bytes < dll_path_len) {
576 return ERROR_INSUFFICIENT_BUFFER;
578 ret_val = StringCbCopy(reinterpret_cast<wchar_t*>(output_data),
580 dll_path.value().c_str());
583 return ERROR_INVALID_PARAMETER;
588 BOOL WINAPI Monitor2XcvClosePort(HANDLE handle) {
589 XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(handle);
594 BOOL WINAPI MonitorUiAddPortUi(const wchar_t*,
596 const wchar_t* monitor_name,
598 HandlePortUi(hwnd, monitor_name);
602 BOOL WINAPI MonitorUiConfigureOrDeletePortUI(const wchar_t*,
604 const wchar_t* port_name) {
605 HandlePortUi(hwnd, port_name);
609 } // namespace cloud_print
611 MONITOR2* WINAPI InitializePrintMonitor2(MONITORINIT*,
613 if (handle == NULL) {
614 SetLastError(ERROR_INVALID_PARAMETER);
617 cloud_print::MonitorData* monitor_data = new cloud_print::MonitorData;
618 *handle = monitor_data;
619 if (!cloud_print::kIsUnittest) {
620 // Unit tests set up their own AtExitManager
621 monitor_data->at_exit_manager.reset(new base::AtExitManager());
622 // Single spooler.exe handles verbose users.
623 PathService::DisableCache();
625 return &cloud_print::g_monitor_2;
628 MONITORUI* WINAPI InitializePrintMonitorUI(void) {
629 return &cloud_print::g_monitor_ui;