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
22namespace {
23
24typedef HRESULT (WINAPI* PTOpenProviderProc)(PCWSTR printer_name,
25                                             DWORD version,
26                                             HPTPROVIDER* provider);
27
28typedef HRESULT (WINAPI* PTGetPrintCapabilitiesProc)(HPTPROVIDER provider,
29                                                     IStream* print_ticket,
30                                                     IStream* capabilities,
31                                                     BSTR* error_message);
32
33typedef HRESULT (WINAPI* PTConvertDevModeToPrintTicketProc)(
34    HPTPROVIDER provider,
35    ULONG devmode_size_in_bytes,
36    PDEVMODE devmode,
37    EPrintTicketScope scope,
38    IStream* print_ticket);
39
40typedef 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
49typedef 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
57typedef HRESULT (WINAPI* PTReleaseMemoryProc)(PVOID buffer);
58
59typedef HRESULT (WINAPI* PTCloseProviderProc)(HPTPROVIDER provider);
60
61typedef 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
73PTOpenProviderProc g_open_provider_proc = NULL;
74PTGetPrintCapabilitiesProc g_get_print_capabilities_proc = NULL;
75PTConvertDevModeToPrintTicketProc g_convert_devmode_to_print_ticket_proc = NULL;
76PTConvertPrintTicketToDevModeProc g_convert_print_ticket_to_devmode_proc = NULL;
77PTMergeAndValidatePrintTicketProc g_merge_and_validate_print_ticket_proc = NULL;
78PTReleaseMemoryProc g_release_memory_proc = NULL;
79PTCloseProviderProc g_close_provider_proc = NULL;
80StartXpsPrintJobProc g_start_xps_print_job_proc = NULL;
81
82HRESULT 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
100const 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
114const char kXpsTicketColor[] = "Color";
115const char kXpsTicketMonochrome[] = "Monochrome";
116
117
118}  // namespace
119
120
121namespace printing {
122
123bool XPSModule::Init() {
124  static bool initialized = InitImpl();
125  return initialized;
126}
127
128bool 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
182HRESULT 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
188HRESULT 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
198HRESULT 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
210HRESULT 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
227HRESULT 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
241HRESULT XPSModule::ReleaseMemory(PVOID buffer) {
242  return g_release_memory_proc(buffer);
243}
244
245HRESULT XPSModule::CloseProvider(HPTPROVIDER provider) {
246  return g_close_provider_proc(provider);
247}
248
249ScopedXPSInitializer::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
275ScopedXPSInitializer::~ScopedXPSInitializer() {
276  if (initialized_)
277    CoUninitialize();
278  initialized_ = false;
279}
280
281bool XPSPrintModule::Init() {
282  static bool initialized = InitImpl();
283  return initialized;
284}
285
286bool 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
299HRESULT 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
322bool 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
353std::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
388scoped_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
431scoped_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
473scoped_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
491scoped_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