- add sources.
[platform/framework/web/crosswalk.git] / src / cloud_print / virtual_driver / win / port_monitor / port_monitor.cc
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/virtual_driver/win/port_monitor/port_monitor.h"
6
7 #include <lmcons.h>
8 #include <shellapi.h>
9 #include <shlobj.h>
10 #include <strsafe.h>
11 #include <userenv.h>
12 #include <windows.h>
13 #include <winspool.h>
14
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"
33
34 namespace cloud_print {
35
36 namespace {
37
38 const wchar_t kIePath[] = L"Internet Explorer\\iexplore.exe";
39
40 const char kChromeInstallUrl[] =
41     "http://google.com/cloudprint/learn/chrome.html";
42
43 const wchar_t kCloudPrintRegKey[] = L"Software\\Google\\CloudPrint";
44
45 const wchar_t kXpsMimeType[] = L"application/vnd.ms-xpsdocument";
46
47 const wchar_t kAppDataDir[] = L"Google\\Cloud Printer";
48
49 struct MonitorData {
50   scoped_ptr<base::AtExitManager> at_exit_manager;
51 };
52
53 struct PortData {
54   PortData() : job_id(0), printer_handle(NULL), file(0) {
55   }
56   ~PortData() {
57     Close();
58   }
59   void Close() {
60     if (printer_handle) {
61       ClosePrinter(printer_handle);
62       printer_handle = NULL;
63     }
64     if (file) {
65       file_util::CloseFile(file);
66       file = NULL;
67     }
68   }
69   DWORD job_id;
70   HANDLE printer_handle;
71   FILE* file;
72   base::FilePath file_path;
73 };
74
75 typedef struct {
76   ACCESS_MASK granted_access;
77 } XcvUiData;
78
79
80 MONITORUI g_monitor_ui = {
81   sizeof(MONITORUI),
82   MonitorUiAddPortUi,
83   MonitorUiConfigureOrDeletePortUI,
84   MonitorUiConfigureOrDeletePortUI
85 };
86
87 MONITOR2 g_monitor_2 = {
88   sizeof(MONITOR2),
89   Monitor2EnumPorts,
90   Monitor2OpenPort,
91   NULL,           // OpenPortEx is not supported.
92   Monitor2StartDocPort,
93   Monitor2WritePort,
94   Monitor2ReadPort,
95   Monitor2EndDocPort,
96   Monitor2ClosePort,
97   NULL,           // AddPort is not supported.
98   NULL,           // AddPortEx is not supported.
99   NULL,           // ConfigurePort is not supported.
100   NULL,           // DeletePort is not supported.
101   NULL,
102   NULL,           // SetPortTimeOuts is not supported.
103   Monitor2XcvOpenPort,
104   Monitor2XcvDataPort,
105   Monitor2XcvClosePort,
106   Monitor2Shutdown
107 };
108
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();
117   }
118   return file_path.Append(kAppDataDir);
119 }
120
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);
129   }
130 }
131
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,
137                  DWORD job_id,
138                  string16 *title) {
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.";
145     return false;
146   }
147   scoped_ptr<BYTE[]> buffer(new BYTE[bytes_needed]);
148   if (!GetJob(printer_handle,
149               job_id,
150               1,
151               buffer.get(),
152               bytes_needed,
153               &bytes_needed)) {
154     LOG(ERROR) << "Unable to get job info.";
155     return false;
156   }
157   JOB_INFO_1* job_info = reinterpret_cast<JOB_INFO_1*>(buffer.get());
158   *title = job_info->pDocument;
159   return true;
160 }
161
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
165 // configuration.
166 void HandlePortUi(HWND hwnd, const string16& caption) {
167   if (hwnd != NULL && IsWindow(hwnd)) {
168     DisplayWindowsMessage(hwnd, CO_E_NOT_SUPPORTED, cloud_print::kPortName);
169   }
170 }
171
172 // Gets the primary token for the user that submitted the print job.
173 bool GetUserToken(HANDLE* primary_token) {
174   HANDLE token = NULL;
175   if (!OpenThreadToken(GetCurrentThread(),
176                       TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
177                       FALSE,
178                       &token)) {
179     LOG(ERROR) << "Unable to get thread token.";
180     return false;
181   }
182   base::win::ScopedHandle token_scoped(token);
183   if (!DuplicateTokenEx(token,
184                         TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
185                         NULL,
186                         SecurityImpersonation,
187                         TokenPrimary,
188                         primary_token)) {
189     LOG(ERROR) << "Unable to get primary thread token.";
190     return false;
191   }
192   return true;
193 }
194
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) {
200   HANDLE token = NULL;
201   if (!GetUserToken(&token)) {
202     LOG(ERROR) << "Unable to get user token.";
203     return false;
204   }
205   base::win::ScopedHandle primary_token_scoped(token);
206
207   base::FilePath chrome_path = GetChromeExePath();
208   if (chrome_path.empty()) {
209     LOG(ERROR) << "Unable to get chrome exe path.";
210     return false;
211   }
212
213   CommandLine command_line(chrome_path);
214
215   base::FilePath chrome_profile = GetChromeProfilePath();
216   if (!chrome_profile.empty()) {
217     command_line.AppendSwitchPath(switches::kUserDataDir, chrome_profile);
218   }
219
220   command_line.AppendSwitchPath(switches::kCloudPrintFile,
221                                 xps_path);
222   command_line.AppendSwitchNative(switches::kCloudPrintFileType,
223                                   kXpsMimeType);
224   command_line.AppendSwitchNative(switches::kCloudPrintJobTitle,
225                                   job_title);
226   command_line.AppendSwitch(switches::kCloudPrintDeleteFile);
227   base::LaunchOptions options;
228   options.as_user = primary_token_scoped;
229   base::LaunchProcess(command_line, options, NULL);
230   return true;
231 }
232
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() {
238   if (kIsUnittest)
239     return;
240   HANDLE token = NULL;
241   if (!GetUserToken(&token)) {
242     LOG(ERROR) << "Unable to get user token.";
243     return;
244   }
245   base::win::ScopedHandle token_scoped(token);
246
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);
252
253   base::LaunchOptions options;
254   options.as_user = token_scoped;
255   base::LaunchProcess(command_line, options, NULL);
256 }
257
258 // Returns false if the print job is being run in a context
259 // that shouldn't be launching Chrome.
260 bool ValidateCurrentUser() {
261   HANDLE token = NULL;
262   if (!GetUserToken(&token)) {
263     // If we can't get the token we're probably not impersonating
264     // the user, so validation should fail.
265     return false;
266   }
267   base::win::ScopedHandle token_scoped(token);
268
269   if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
270     DWORD session_id = 0;
271     DWORD dummy;
272     if (!GetTokenInformation(token_scoped,
273                              TokenSessionId,
274                              reinterpret_cast<void *>(&session_id),
275                              sizeof(DWORD),
276                              &dummy)) {
277       return false;
278     }
279     if (session_id == 0) {
280       return false;
281     }
282   }
283   return true;
284 }
285 }  // namespace
286
287 base::FilePath ReadPathFromRegistry(HKEY root, const wchar_t* path_name) {
288   base::win::RegKey gcp_key(HKEY_CURRENT_USER, kCloudPrintRegKey, KEY_READ);
289   string16 data;
290   if (SUCCEEDED(gcp_key.ReadValue(path_name, &data)) &&
291       base::PathExists(base::FilePath(data))) {
292     return base::FilePath(data);
293   }
294   return base::FilePath();
295 }
296
297 base::FilePath ReadPathFromAnyRegistry(const wchar_t* path_name) {
298   base::FilePath result = ReadPathFromRegistry(HKEY_CURRENT_USER, path_name);
299   if (!result.empty())
300     return result;
301   return ReadPathFromRegistry(HKEY_LOCAL_MACHINE, path_name);
302 }
303
304 base::FilePath GetChromeExePath() {
305   base::FilePath path = ReadPathFromAnyRegistry(kChromeExePathRegValue);
306   if (!path.empty())
307     return path;
308   return chrome_launcher_support::GetAnyChromePath();
309 }
310
311 base::FilePath GetChromeProfilePath() {
312   base::FilePath path = ReadPathFromAnyRegistry(kChromeProfilePathRegValue);
313   if (!path.empty() && base::DirectoryExists(path))
314     return path;
315   return base::FilePath();
316 }
317
318 BOOL WINAPI Monitor2EnumPorts(HANDLE,
319                               wchar_t*,
320                               DWORD level,
321                               BYTE*  ports,
322                               DWORD   ports_size,
323                               DWORD* needed_bytes,
324                               DWORD* returned) {
325   if (needed_bytes == NULL) {
326     LOG(ERROR) << "needed_bytes should not be NULL.";
327     SetLastError(ERROR_INVALID_PARAMETER);
328     return FALSE;
329   }
330   if (level == 1) {
331     *needed_bytes = sizeof(PORT_INFO_1);
332   } else if (level == 2) {
333     *needed_bytes = sizeof(PORT_INFO_2);
334   } else {
335     LOG(ERROR) << "Level "  << level << "is not supported.";
336     SetLastError(ERROR_INVALID_LEVEL);
337     return FALSE;
338   }
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);
344     return FALSE;
345   }
346   if (ports == NULL) {
347     LOG(ERROR) << "ports should not be NULL.";
348     SetLastError(ERROR_INVALID_PARAMETER);
349     return FALSE;
350   }
351   if (returned == NULL) {
352     LOG(ERROR) << "returned should not be NULL.";
353     SetLastError(ERROR_INVALID_PARAMETER);
354     return FALSE;
355   }
356
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);
365   if (level == 1) {
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);
371   } else {
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;
381   }
382   *returned = 1;
383   return TRUE;
384 }
385
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);
390     return FALSE;
391   }
392   *handle = new PortData();
393   return TRUE;
394 }
395
396 BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle,
397                                  wchar_t* printer_name,
398                                  DWORD job_id,
399                                  DWORD,
400                                  BYTE*) {
401   SetGoogleUpdateUsage(kGoogleUpdateProductId);
402   if (port_handle == NULL) {
403     LOG(ERROR) << "port_handle should not be NULL.";
404     SetLastError(ERROR_INVALID_PARAMETER);
405     return FALSE;
406   }
407   if (printer_name == NULL) {
408     LOG(ERROR) << "printer_name should not be NULL.";
409     SetLastError(ERROR_INVALID_PARAMETER);
410     return FALSE;
411   }
412   if (!ValidateCurrentUser()) {
413     // TODO(abodenha@chromium.org) Abort the print job.
414     return FALSE;
415   }
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;
425   }
426   base::FilePath& file_path = port_data->file_path;
427   base::FilePath app_data_dir = GetAppDataDir();
428   if (app_data_dir.empty())
429     return FALSE;
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();
434     return FALSE;
435   }
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() << ".";
439     return FALSE;
440   }
441   return TRUE;
442 }
443
444 BOOL WINAPI Monitor2WritePort(HANDLE port_handle,
445                               BYTE* buffer,
446                               DWORD buffer_size,
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.
451     return FALSE;
452   }
453   *bytes_written =
454       static_cast<DWORD>(fwrite(buffer, 1, buffer_size, port_data->file));
455   if (*bytes_written > 0) {
456     return TRUE;
457   } else {
458     return FALSE;
459   }
460 }
461
462 BOOL WINAPI Monitor2ReadPort(HANDLE, BYTE*, DWORD, DWORD* read_bytes) {
463   LOG(ERROR) << "Read is not supported.";
464   *read_bytes = 0;
465   SetLastError(ERROR_NOT_SUPPORTED);
466   return FALSE;
467 }
468
469 BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle) {
470   if (!ValidateCurrentUser()) {
471     // TODO(abodenha@chromium.org) Abort the print job.
472     return FALSE;
473   }
474   PortData* port_data = reinterpret_cast<PortData*>(port_handle);
475   if (port_data == NULL) {
476     SetLastError(ERROR_INVALID_PARAMETER);
477     return FALSE;
478   }
479
480   if (port_data->file != NULL) {
481     file_util::CloseFile(port_data->file);
482     port_data->file = NULL;
483     bool delete_file = true;
484     int64 file_size = 0;
485     file_util::GetFileSize(port_data->file_path, &file_size);
486     if (file_size > 0) {
487       string16 job_title;
488       if (port_data->printer_handle != NULL) {
489         GetJobTitle(port_data->printer_handle,
490                     port_data->job_id,
491                     &job_title);
492       }
493       if (!LaunchPrintDialog(port_data->file_path, job_title)) {
494         LaunchChromeDownloadPage();
495       } else {
496         delete_file = false;
497       }
498     }
499     if (delete_file)
500       base::DeleteFile(port_data->file_path, false);
501   }
502   if (port_data->printer_handle != NULL) {
503     // Tell the spooler that the job is complete.
504     SetJob(port_data->printer_handle,
505            port_data->job_id,
506            0,
507            NULL,
508            JOB_CONTROL_SENT_TO_PRINTER);
509   }
510   port_data->Close();
511   // Return success even if we can't display the dialog.
512   // TODO(abodenha@chromium.org) Come up with a better way of handling
513   // this situation.
514   return TRUE;
515 }
516
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);
521     return FALSE;
522   }
523   PortData* port_data = reinterpret_cast<PortData*>(port_handle);
524   delete port_data;
525   return TRUE;
526 }
527
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;
533   }
534 }
535
536 BOOL WINAPI Monitor2XcvOpenPort(HANDLE,
537                                 const wchar_t*,
538                                 ACCESS_MASK granted_access,
539                                 HANDLE* handle) {
540   if (handle == NULL) {
541     LOG(ERROR) << "handle should not be NULL.";
542     SetLastError(ERROR_INVALID_PARAMETER);
543     return FALSE;
544   }
545   XcvUiData* xcv_data = new XcvUiData();
546   xcv_data->granted_access = granted_access;
547   *handle = xcv_data;
548   return TRUE;
549 }
550
551 DWORD WINAPI Monitor2XcvDataPort(HANDLE xcv_handle,
552                                  const wchar_t* data_name,
553                                  BYTE*,
554                                  DWORD,
555                                  BYTE* output_data,
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;
562   }
563   if (output_data == NULL || output_data_bytes == 0) {
564     return ERROR_INVALID_PARAMETER;
565   }
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;
574     }
575     if (output_data_bytes < dll_path_len) {
576         return ERROR_INSUFFICIENT_BUFFER;
577     } else {
578         ret_val = StringCbCopy(reinterpret_cast<wchar_t*>(output_data),
579                                output_data_bytes,
580                                dll_path.value().c_str());
581     }
582   } else {
583     return ERROR_INVALID_PARAMETER;
584   }
585   return ret_val;
586 }
587
588 BOOL WINAPI Monitor2XcvClosePort(HANDLE handle) {
589   XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(handle);
590   delete xcv_data;
591   return TRUE;
592 }
593
594 BOOL WINAPI MonitorUiAddPortUi(const wchar_t*,
595                                HWND hwnd,
596                                const wchar_t* monitor_name,
597                                wchar_t**) {
598   HandlePortUi(hwnd, monitor_name);
599   return TRUE;
600 }
601
602 BOOL WINAPI MonitorUiConfigureOrDeletePortUI(const wchar_t*,
603                                              HWND hwnd,
604                                              const wchar_t* port_name) {
605   HandlePortUi(hwnd, port_name);
606   return TRUE;
607 }
608
609 }   // namespace cloud_print
610
611 MONITOR2* WINAPI InitializePrintMonitor2(MONITORINIT*,
612                                          HANDLE* handle) {
613   if (handle == NULL) {
614     SetLastError(ERROR_INVALID_PARAMETER);
615     return NULL;
616   }
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();
624   }
625   return &cloud_print::g_monitor_2;
626 }
627
628 MONITORUI* WINAPI InitializePrintMonitorUI(void) {
629   return &cloud_print::g_monitor_ui;
630 }
631