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.Get(), 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_ticket =
436 CreateDevMode(printer, NULL);
437 if (!default_ticket)
438 return default_ticket.Pass();
439
440 if ((default_ticket->dmFields & DM_COLOR) &&
441 ((default_ticket->dmColor == DMCOLOR_COLOR) == color)) {
442 return default_ticket.Pass();
443 }
444
445 default_ticket->dmFields |= DM_COLOR;
446 default_ticket->dmColor = color ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME;
447
448 DriverInfo6 info_6;
449 if (!info_6.Init(printer))
450 return default_ticket.Pass();
451
452 const DRIVER_INFO_6* p = info_6.get();
453
454 // Only HP known to have issues.
455 if (!p->pszMfgName || wcscmp(p->pszMfgName, L"HP") != 0)
456 return default_ticket.Pass();
457
458 // Need XPS for this workaround.
459 printing::ScopedXPSInitializer xps_initializer;
460 if (!xps_initializer.initialized())
461 return default_ticket.Pass();
462
463 const char* xps_color = color ? kXpsTicketColor : kXpsTicketMonochrome;
464 std::string xps_ticket = base::StringPrintf(kXpsTicketTemplate, xps_color);
465 scoped_ptr<DEVMODE, base::FreeDeleter> ticket =
466 printing::XpsTicketToDevMode(printer_name, xps_ticket);
467 if (!ticket)
468 return default_ticket.Pass();
469
470 return ticket.Pass();
471 }
472
CreateDevMode(HANDLE printer,DEVMODE * in)473 scoped_ptr<DEVMODE, base::FreeDeleter> CreateDevMode(HANDLE printer,
474 DEVMODE* in) {
475 LONG buffer_size = DocumentProperties(
476 NULL, printer, const_cast<wchar_t*>(L""), NULL, NULL, 0);
477 if (buffer_size < static_cast<int>(sizeof(DEVMODE)))
478 return scoped_ptr<DEVMODE, base::FreeDeleter>();
479 scoped_ptr<DEVMODE, base::FreeDeleter> out(
480 reinterpret_cast<DEVMODE*>(malloc(buffer_size)));
481 DWORD flags = (in ? (DM_IN_BUFFER) : 0) | DM_OUT_BUFFER;
482 if (DocumentProperties(
483 NULL, printer, const_cast<wchar_t*>(L""), out.get(), in, flags) !=
484 IDOK) {
485 return scoped_ptr<DEVMODE, base::FreeDeleter>();
486 }
487 CHECK_GE(buffer_size, out.get()->dmSize + out.get()->dmDriverExtra);
488 return out.Pass();
489 }
490
PromptDevMode(HANDLE printer,const base::string16 & printer_name,DEVMODE * in,HWND window,bool * canceled)491 scoped_ptr<DEVMODE, base::FreeDeleter> PromptDevMode(
492 HANDLE printer,
493 const base::string16& printer_name,
494 DEVMODE* in,
495 HWND window,
496 bool* canceled) {
497 LONG buffer_size =
498 DocumentProperties(window,
499 printer,
500 const_cast<wchar_t*>(printer_name.c_str()),
501 NULL,
502 NULL,
503 0);
504 if (buffer_size < static_cast<int>(sizeof(DEVMODE)))
505 return scoped_ptr<DEVMODE, base::FreeDeleter>();
506 scoped_ptr<DEVMODE, base::FreeDeleter> out(
507 reinterpret_cast<DEVMODE*>(malloc(buffer_size)));
508 DWORD flags = (in ? (DM_IN_BUFFER) : 0) | DM_OUT_BUFFER | DM_IN_PROMPT;
509 LONG result = DocumentProperties(window,
510 printer,
511 const_cast<wchar_t*>(printer_name.c_str()),
512 out.get(),
513 in,
514 flags);
515 if (canceled)
516 *canceled = (result == IDCANCEL);
517 if (result != IDOK)
518 return scoped_ptr<DEVMODE, base::FreeDeleter>();
519 CHECK_GE(buffer_size, out.get()->dmSize + out.get()->dmDriverExtra);
520 return out.Pass();
521 }
522
523 } // namespace printing
524