Home | History | Annotate | Download | only in backend
      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 
     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 
    123 bool XPSModule::Init() {
    124   static bool initialized = InitImpl();
    125   return initialized;
    126 }
    127 
    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 
    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 
    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 
    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 
    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 
    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 
    241 HRESULT XPSModule::ReleaseMemory(PVOID buffer) {
    242   return g_release_memory_proc(buffer);
    243 }
    244 
    245 HRESULT XPSModule::CloseProvider(HPTPROVIDER provider) {
    246   return g_close_provider_proc(provider);
    247 }
    248 
    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 
    275 ScopedXPSInitializer::~ScopedXPSInitializer() {
    276   if (initialized_)
    277     CoUninitialize();
    278   initialized_ = false;
    279 }
    280 
    281 bool XPSPrintModule::Init() {
    282   static bool initialized = InitImpl();
    283   return initialized;
    284 }
    285 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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