• 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 "printing/backend/win_helper.h"
6 
7 #include <algorithm>
8 
9 #include "base/file_version_info.h"
10 #include "base/files/file_path.h"
11 #include "base/logging.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/numerics/safe_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/win/scoped_comptr.h"
18 #include "printing/backend/print_backend.h"
19 #include "printing/backend/print_backend_consts.h"
20 #include "printing/backend/printing_info_win.h"
21 
22 namespace {
23 
24 typedef HRESULT (WINAPI* PTOpenProviderProc)(PCWSTR printer_name,
25                                              DWORD version,
26                                              HPTPROVIDER* provider);
27 
28 typedef HRESULT (WINAPI* PTGetPrintCapabilitiesProc)(HPTPROVIDER provider,
29                                                      IStream* print_ticket,
30                                                      IStream* capabilities,
31                                                      BSTR* error_message);
32 
33 typedef HRESULT (WINAPI* PTConvertDevModeToPrintTicketProc)(
34     HPTPROVIDER provider,
35     ULONG devmode_size_in_bytes,
36     PDEVMODE devmode,
37     EPrintTicketScope scope,
38     IStream* print_ticket);
39 
40 typedef HRESULT (WINAPI* PTConvertPrintTicketToDevModeProc)(
41     HPTPROVIDER provider,
42     IStream* print_ticket,
43     EDefaultDevmodeType base_devmode_type,
44     EPrintTicketScope scope,
45     ULONG* devmode_byte_count,
46     PDEVMODE* devmode,
47     BSTR* error_message);
48 
49 typedef HRESULT (WINAPI* PTMergeAndValidatePrintTicketProc)(
50     HPTPROVIDER provider,
51     IStream* base_ticket,
52     IStream* delta_ticket,
53     EPrintTicketScope scope,
54     IStream* result_ticket,
55     BSTR* error_message);
56 
57 typedef HRESULT (WINAPI* PTReleaseMemoryProc)(PVOID buffer);
58 
59 typedef HRESULT (WINAPI* PTCloseProviderProc)(HPTPROVIDER provider);
60 
61 typedef HRESULT (WINAPI* StartXpsPrintJobProc)(
62     const LPCWSTR printer_name,
63     const LPCWSTR job_name,
64     const LPCWSTR output_file_name,
65     HANDLE progress_event,
66     HANDLE completion_event,
67     UINT8* printable_pages_on,
68     UINT32 printable_pages_on_count,
69     IXpsPrintJob** xps_print_job,
70     IXpsPrintJobStream** document_stream,
71     IXpsPrintJobStream** print_ticket_stream);
72 
73 PTOpenProviderProc g_open_provider_proc = NULL;
74 PTGetPrintCapabilitiesProc g_get_print_capabilities_proc = NULL;
75 PTConvertDevModeToPrintTicketProc g_convert_devmode_to_print_ticket_proc = NULL;
76 PTConvertPrintTicketToDevModeProc g_convert_print_ticket_to_devmode_proc = NULL;
77 PTMergeAndValidatePrintTicketProc g_merge_and_validate_print_ticket_proc = NULL;
78 PTReleaseMemoryProc g_release_memory_proc = NULL;
79 PTCloseProviderProc g_close_provider_proc = NULL;
80 StartXpsPrintJobProc g_start_xps_print_job_proc = NULL;
81 
StreamFromPrintTicket(const std::string & print_ticket,IStream ** stream)82 HRESULT StreamFromPrintTicket(const std::string& print_ticket,
83                               IStream** stream) {
84   DCHECK(stream);
85   HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, stream);
86   if (FAILED(hr)) {
87     return hr;
88   }
89   ULONG bytes_written = 0;
90   (*stream)->Write(print_ticket.c_str(),
91                    base::checked_cast<ULONG>(print_ticket.length()),
92                    &bytes_written);
93   DCHECK(bytes_written == print_ticket.length());
94   LARGE_INTEGER pos = {0};
95   ULARGE_INTEGER new_pos = {0};
96   (*stream)->Seek(pos, STREAM_SEEK_SET, &new_pos);
97   return S_OK;
98 }
99 
100 const char kXpsTicketTemplate[] =
101   "<?xml version='1.0' encoding='UTF-8'?>"
102   "<psf:PrintTicket "
103   "xmlns:psf='"
104   "http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework' "
105   "xmlns:psk="
106   "'http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords' "
107   "version='1'>"
108   "<psf:Feature name='psk:PageOutputColor'>"
109   "<psf:Option name='psk:%s'>"
110   "</psf:Option>"
111   "</psf:Feature>"
112   "</psf:PrintTicket>";
113 
114 const char kXpsTicketColor[] = "Color";
115 const char kXpsTicketMonochrome[] = "Monochrome";
116 
117 
118 }  // namespace
119 
120 
121 namespace printing {
122 
Init()123 bool XPSModule::Init() {
124   static bool initialized = InitImpl();
125   return initialized;
126 }
127 
InitImpl()128 bool XPSModule::InitImpl() {
129   HMODULE prntvpt_module = LoadLibrary(L"prntvpt.dll");
130   if (prntvpt_module == NULL)
131     return false;
132   g_open_provider_proc = reinterpret_cast<PTOpenProviderProc>(
133       GetProcAddress(prntvpt_module, "PTOpenProvider"));
134   if (!g_open_provider_proc) {
135     NOTREACHED();
136     return false;
137   }
138   g_get_print_capabilities_proc = reinterpret_cast<PTGetPrintCapabilitiesProc>(
139       GetProcAddress(prntvpt_module, "PTGetPrintCapabilities"));
140   if (!g_get_print_capabilities_proc) {
141     NOTREACHED();
142     return false;
143   }
144   g_convert_devmode_to_print_ticket_proc =
145       reinterpret_cast<PTConvertDevModeToPrintTicketProc>(
146           GetProcAddress(prntvpt_module, "PTConvertDevModeToPrintTicket"));
147   if (!g_convert_devmode_to_print_ticket_proc) {
148     NOTREACHED();
149     return false;
150   }
151   g_convert_print_ticket_to_devmode_proc =
152       reinterpret_cast<PTConvertPrintTicketToDevModeProc>(
153           GetProcAddress(prntvpt_module, "PTConvertPrintTicketToDevMode"));
154   if (!g_convert_print_ticket_to_devmode_proc) {
155     NOTREACHED();
156     return false;
157   }
158   g_merge_and_validate_print_ticket_proc =
159       reinterpret_cast<PTMergeAndValidatePrintTicketProc>(
160           GetProcAddress(prntvpt_module, "PTMergeAndValidatePrintTicket"));
161   if (!g_merge_and_validate_print_ticket_proc) {
162     NOTREACHED();
163     return false;
164   }
165   g_release_memory_proc =
166       reinterpret_cast<PTReleaseMemoryProc>(
167           GetProcAddress(prntvpt_module, "PTReleaseMemory"));
168   if (!g_release_memory_proc) {
169     NOTREACHED();
170     return false;
171   }
172   g_close_provider_proc =
173       reinterpret_cast<PTCloseProviderProc>(
174           GetProcAddress(prntvpt_module, "PTCloseProvider"));
175   if (!g_close_provider_proc) {
176     NOTREACHED();
177     return false;
178   }
179   return true;
180 }
181 
OpenProvider(const base::string16 & printer_name,DWORD version,HPTPROVIDER * provider)182 HRESULT XPSModule::OpenProvider(const base::string16& printer_name,
183                                 DWORD version,
184                                 HPTPROVIDER* provider) {
185   return g_open_provider_proc(printer_name.c_str(), version, provider);
186 }
187 
GetPrintCapabilities(HPTPROVIDER provider,IStream * print_ticket,IStream * capabilities,BSTR * error_message)188 HRESULT XPSModule::GetPrintCapabilities(HPTPROVIDER provider,
189                                         IStream* print_ticket,
190                                         IStream* capabilities,
191                                         BSTR* error_message) {
192   return g_get_print_capabilities_proc(provider,
193                                        print_ticket,
194                                        capabilities,
195                                        error_message);
196 }
197 
ConvertDevModeToPrintTicket(HPTPROVIDER provider,ULONG devmode_size_in_bytes,PDEVMODE devmode,EPrintTicketScope scope,IStream * print_ticket)198 HRESULT XPSModule::ConvertDevModeToPrintTicket(HPTPROVIDER provider,
199                                                ULONG devmode_size_in_bytes,
200                                                PDEVMODE devmode,
201                                                EPrintTicketScope scope,
202                                                IStream* print_ticket) {
203   return g_convert_devmode_to_print_ticket_proc(provider,
204                                                 devmode_size_in_bytes,
205                                                 devmode,
206                                                 scope,
207                                                 print_ticket);
208 }
209 
ConvertPrintTicketToDevMode(HPTPROVIDER provider,IStream * print_ticket,EDefaultDevmodeType base_devmode_type,EPrintTicketScope scope,ULONG * devmode_byte_count,PDEVMODE * devmode,BSTR * error_message)210 HRESULT XPSModule::ConvertPrintTicketToDevMode(
211     HPTPROVIDER provider,
212     IStream* print_ticket,
213     EDefaultDevmodeType base_devmode_type,
214     EPrintTicketScope scope,
215     ULONG* devmode_byte_count,
216     PDEVMODE* devmode,
217     BSTR* error_message) {
218   return g_convert_print_ticket_to_devmode_proc(provider,
219                                                 print_ticket,
220                                                 base_devmode_type,
221                                                 scope,
222                                                 devmode_byte_count,
223                                                 devmode,
224                                                 error_message);
225 }
226 
MergeAndValidatePrintTicket(HPTPROVIDER provider,IStream * base_ticket,IStream * delta_ticket,EPrintTicketScope scope,IStream * result_ticket,BSTR * error_message)227 HRESULT XPSModule::MergeAndValidatePrintTicket(HPTPROVIDER provider,
228                                                IStream* base_ticket,
229                                                IStream* delta_ticket,
230                                                EPrintTicketScope scope,
231                                                IStream* result_ticket,
232                                                BSTR* error_message) {
233   return g_merge_and_validate_print_ticket_proc(provider,
234                                                 base_ticket,
235                                                 delta_ticket,
236                                                 scope,
237                                                 result_ticket,
238                                                 error_message);
239 }
240 
ReleaseMemory(PVOID buffer)241 HRESULT XPSModule::ReleaseMemory(PVOID buffer) {
242   return g_release_memory_proc(buffer);
243 }
244 
CloseProvider(HPTPROVIDER provider)245 HRESULT XPSModule::CloseProvider(HPTPROVIDER provider) {
246   return g_close_provider_proc(provider);
247 }
248 
ScopedXPSInitializer()249 ScopedXPSInitializer::ScopedXPSInitializer() : initialized_(false) {
250   if (!XPSModule::Init())
251     return;
252   // Calls to XPS APIs typically require the XPS provider to be opened with
253   // PTOpenProvider. PTOpenProvider calls CoInitializeEx with
254   // COINIT_MULTITHREADED. We have seen certain buggy HP printer driver DLLs
255   // that call CoInitializeEx with COINIT_APARTMENTTHREADED in the context of
256   // PTGetPrintCapabilities. This call fails but the printer driver calls
257   // CoUninitialize anyway. This results in the apartment being torn down too
258   // early and the msxml DLL being unloaded which in turn causes code in
259   // unidrvui.dll to have a dangling pointer to an XML document which causes a
260   // crash. To protect ourselves from such drivers we make sure we always have
261   // an extra CoInitialize (calls to CoInitialize/CoUninitialize are
262   // refcounted).
263   HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
264   // If this succeeded we are done because the PTOpenProvider call will provide
265   // the extra refcount on the apartment. If it failed because someone already
266   // called CoInitializeEx with COINIT_APARTMENTTHREADED, we try the other model
267   // to provide the additional refcount (since we don't know which model buggy
268   // printer drivers will use).
269   if (!SUCCEEDED(hr))
270     hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
271   DCHECK(SUCCEEDED(hr));
272   initialized_ = true;
273 }
274 
~ScopedXPSInitializer()275 ScopedXPSInitializer::~ScopedXPSInitializer() {
276   if (initialized_)
277     CoUninitialize();
278   initialized_ = false;
279 }
280 
Init()281 bool XPSPrintModule::Init() {
282   static bool initialized = InitImpl();
283   return initialized;
284 }
285 
InitImpl()286 bool XPSPrintModule::InitImpl() {
287   HMODULE xpsprint_module = LoadLibrary(L"xpsprint.dll");
288   if (xpsprint_module == NULL)
289     return false;
290   g_start_xps_print_job_proc = reinterpret_cast<StartXpsPrintJobProc>(
291       GetProcAddress(xpsprint_module, "StartXpsPrintJob"));
292   if (!g_start_xps_print_job_proc) {
293     NOTREACHED();
294     return false;
295   }
296   return true;
297 }
298 
StartXpsPrintJob(const LPCWSTR printer_name,const LPCWSTR job_name,const LPCWSTR output_file_name,HANDLE progress_event,HANDLE completion_event,UINT8 * printable_pages_on,UINT32 printable_pages_on_count,IXpsPrintJob ** xps_print_job,IXpsPrintJobStream ** document_stream,IXpsPrintJobStream ** print_ticket_stream)299 HRESULT XPSPrintModule::StartXpsPrintJob(
300     const LPCWSTR printer_name,
301     const LPCWSTR job_name,
302     const LPCWSTR output_file_name,
303     HANDLE progress_event,
304     HANDLE completion_event,
305     UINT8* printable_pages_on,
306     UINT32 printable_pages_on_count,
307     IXpsPrintJob** xps_print_job,
308     IXpsPrintJobStream** document_stream,
309     IXpsPrintJobStream** print_ticket_stream) {
310   return g_start_xps_print_job_proc(printer_name,
311                                     job_name,
312                                     output_file_name,
313                                     progress_event,
314                                     completion_event,
315                                     printable_pages_on,
316                                     printable_pages_on_count,
317                                     xps_print_job,
318                                     document_stream,
319                                     print_ticket_stream);
320 }
321 
InitBasicPrinterInfo(HANDLE printer,PrinterBasicInfo * printer_info)322 bool InitBasicPrinterInfo(HANDLE printer, PrinterBasicInfo* printer_info) {
323   DCHECK(printer);
324   DCHECK(printer_info);
325   if (!printer)
326     return false;
327 
328   PrinterInfo2 info_2;
329   if (!info_2.Init(printer))
330     return false;
331 
332   printer_info->printer_name = base::WideToUTF8(info_2.get()->pPrinterName);
333   if (info_2.get()->pComment) {
334     printer_info->printer_description =
335         base::WideToUTF8(info_2.get()->pComment);
336   }
337   if (info_2.get()->pLocation) {
338     printer_info->options[kLocationTagName] =
339         base::WideToUTF8(info_2.get()->pLocation);
340   }
341   if (info_2.get()->pDriverName) {
342     printer_info->options[kDriverNameTagName] =
343         base::WideToUTF8(info_2.get()->pDriverName);
344   }
345   printer_info->printer_status = info_2.get()->Status;
346 
347   std::string driver_info = GetDriverInfo(printer);
348   if (!driver_info.empty())
349     printer_info->options[kDriverInfoTagName] = driver_info;
350   return true;
351 }
352 
GetDriverInfo(HANDLE printer)353 std::string GetDriverInfo(HANDLE printer) {
354   DCHECK(printer);
355   std::string driver_info;
356 
357   if (!printer)
358     return driver_info;
359 
360   DriverInfo6 info_6;
361   if (!info_6.Init(printer))
362     return driver_info;
363 
364   std::string info[4];
365   if (info_6.get()->pName)
366     info[0] = base::WideToUTF8(info_6.get()->pName);
367 
368   if (info_6.get()->pDriverPath) {
369     scoped_ptr<FileVersionInfo> version_info(
370         FileVersionInfo::CreateFileVersionInfo(
371             base::FilePath(info_6.get()->pDriverPath)));
372     if (version_info.get()) {
373       info[1] = base::WideToUTF8(version_info->file_version());
374       info[2] = base::WideToUTF8(version_info->product_name());
375       info[3] = base::WideToUTF8(version_info->product_version());
376     }
377   }
378 
379   for (size_t i = 0; i < arraysize(info); ++i) {
380     std::replace(info[i].begin(), info[i].end(), ';', ',');
381     driver_info.append(info[i]);
382     if (i < arraysize(info) - 1)
383       driver_info.append(";");
384   }
385   return driver_info;
386 }
387 
XpsTicketToDevMode(const base::string16 & printer_name,const std::string & print_ticket)388 scoped_ptr<DEVMODE, base::FreeDeleter> XpsTicketToDevMode(
389     const base::string16& printer_name,
390     const std::string& print_ticket) {
391   scoped_ptr<DEVMODE, base::FreeDeleter> dev_mode;
392   printing::ScopedXPSInitializer xps_initializer;
393   if (!xps_initializer.initialized()) {
394     // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll)
395     return dev_mode.Pass();
396   }
397 
398   printing::ScopedPrinterHandle printer;
399   if (!printer.OpenPrinter(printer_name.c_str()))
400     return dev_mode.Pass();
401 
402   base::win::ScopedComPtr<IStream> pt_stream;
403   HRESULT hr = StreamFromPrintTicket(print_ticket, pt_stream.Receive());
404   if (FAILED(hr))
405     return dev_mode.Pass();
406 
407   HPTPROVIDER provider = NULL;
408   hr = printing::XPSModule::OpenProvider(printer_name, 1, &provider);
409   if (SUCCEEDED(hr)) {
410     ULONG size = 0;
411     DEVMODE* dm = NULL;
412     // Use kPTJobScope, because kPTDocumentScope breaks duplex.
413     hr = printing::XPSModule::ConvertPrintTicketToDevMode(provider,
414                                                           pt_stream,
415                                                           kUserDefaultDevmode,
416                                                           kPTJobScope,
417                                                           &size,
418                                                           &dm,
419                                                           NULL);
420     if (SUCCEEDED(hr)) {
421       // Correct DEVMODE using DocumentProperties. See documentation for
422       // PTConvertPrintTicketToDevMode.
423       dev_mode = CreateDevMode(printer, dm);
424       printing::XPSModule::ReleaseMemory(dm);
425     }
426     printing::XPSModule::CloseProvider(provider);
427   }
428   return dev_mode.Pass();
429 }
430 
CreateDevModeWithColor(HANDLE printer,const base::string16 & printer_name,bool color)431 scoped_ptr<DEVMODE, base::FreeDeleter> CreateDevModeWithColor(
432     HANDLE printer,
433     const base::string16& printer_name,
434     bool color) {
435   scoped_ptr<DEVMODE, base::FreeDeleter> default = CreateDevMode(printer, NULL);
436   if (!default)
437     return default.Pass();
438 
439   if ((default->dmFields & DM_COLOR) &&
440       ((default->dmColor == DMCOLOR_COLOR) == color)) {
441     return default.Pass();
442   }
443 
444   default->dmFields |= DM_COLOR;
445   default->dmColor = color ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME;
446 
447   DriverInfo6 info_6;
448   if (!info_6.Init(printer))
449     return default.Pass();
450 
451   const DRIVER_INFO_6* p = info_6.get();
452 
453   // Only HP known to have issues.
454   if (!p->pszMfgName || wcscmp(p->pszMfgName, L"HP") != 0)
455     return default.Pass();
456 
457   // Need XPS for this workaround.
458   printing::ScopedXPSInitializer xps_initializer;
459   if (!xps_initializer.initialized())
460     return default.Pass();
461 
462   const char* xps_color = color ? kXpsTicketColor : kXpsTicketMonochrome;
463   std::string xps_ticket = base::StringPrintf(kXpsTicketTemplate, xps_color);
464   scoped_ptr<DEVMODE, base::FreeDeleter> ticket =
465       printing::XpsTicketToDevMode(printer_name, xps_ticket);
466   if (!ticket)
467     return default.Pass();
468 
469   return ticket.Pass();
470 }
471 
CreateDevMode(HANDLE printer,DEVMODE * in)472 scoped_ptr<DEVMODE, base::FreeDeleter> CreateDevMode(HANDLE printer,
473                                                      DEVMODE* in) {
474   LONG buffer_size = DocumentProperties(NULL, printer, L"", NULL, NULL, 0);
475   if (buffer_size < static_cast<int>(sizeof(DEVMODE)))
476     return scoped_ptr<DEVMODE, base::FreeDeleter>();
477   scoped_ptr<DEVMODE, base::FreeDeleter> out(
478       reinterpret_cast<DEVMODE*>(malloc(buffer_size)));
479   DWORD flags = (in ? (DM_IN_BUFFER) : 0) | DM_OUT_BUFFER;
480   if (DocumentProperties(NULL, printer, L"", out.get(), in, flags) != IDOK)
481     return scoped_ptr<DEVMODE, base::FreeDeleter>();
482   CHECK_GE(buffer_size, out.get()->dmSize + out.get()->dmDriverExtra);
483   return out.Pass();
484 }
485 
486 }  // namespace printing
487