chromium/printing/backend/win_helper.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "printing/backend/win_helper.h"

#include <stddef.h>
#include <wrl/client.h>

#include <algorithm>
#include <memory>
#include <string_view>

#include "base/check_op.h"
#include "base/containers/fixed_flat_set.h"
#include "base/debug/alias.h"
#include "base/file_version_info.h"
#include "base/files/file_path.h"
#include "base/memory/free_deleter.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/win/registry.h"
#include "base/win/windows_types.h"
#include "base/win/windows_version.h"
#include "printing/backend/print_backend.h"
#include "printing/backend/print_backend_consts.h"
#include "printing/backend/printing_info_win.h"

namespace {

typedef HRESULT(WINAPI* PTOpenProviderProc)(PCWSTR printer_name,
                                            DWORD version,
                                            HPTPROVIDER* provider);

typedef HRESULT(WINAPI* PTGetPrintCapabilitiesProc)(HPTPROVIDER provider,
                                                    IStream* print_ticket,
                                                    IStream* capabilities,
                                                    BSTR* error_message);

typedef HRESULT(WINAPI* PTConvertDevModeToPrintTicketProc)(
    HPTPROVIDER provider,
    ULONG devmode_size_in_bytes,
    PDEVMODE devmode,
    EPrintTicketScope scope,
    IStream* print_ticket);

typedef HRESULT(WINAPI* PTConvertPrintTicketToDevModeProc)(
    HPTPROVIDER provider,
    IStream* print_ticket,
    EDefaultDevmodeType base_devmode_type,
    EPrintTicketScope scope,
    ULONG* devmode_byte_count,
    PDEVMODE* devmode,
    BSTR* error_message);

typedef HRESULT(WINAPI* PTMergeAndValidatePrintTicketProc)(
    HPTPROVIDER provider,
    IStream* base_ticket,
    IStream* delta_ticket,
    EPrintTicketScope scope,
    IStream* result_ticket,
    BSTR* error_message);

typedef HRESULT(WINAPI* PTReleaseMemoryProc)(PVOID buffer);

typedef HRESULT(WINAPI* PTCloseProviderProc)(HPTPROVIDER provider);

typedef HRESULT(WINAPI* StartXpsPrintJobProc)(
    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);

PTOpenProviderProc g_open_provider_proc = nullptr;
PTGetPrintCapabilitiesProc g_get_print_capabilities_proc = nullptr;
PTConvertDevModeToPrintTicketProc g_convert_devmode_to_print_ticket_proc =
    nullptr;
PTConvertPrintTicketToDevModeProc g_convert_print_ticket_to_devmode_proc =
    nullptr;
PTMergeAndValidatePrintTicketProc g_merge_and_validate_print_ticket_proc =
    nullptr;
PTReleaseMemoryProc g_release_memory_proc = nullptr;
PTCloseProviderProc g_close_provider_proc = nullptr;
StartXpsPrintJobProc g_start_xps_print_job_proc = nullptr;

typedef std::string (*GetDisplayNameFunc)(const std::string& printer_name);
GetDisplayNameFunc g_get_display_name_func = nullptr;

HRESULT StreamFromPrintTicket(const std::string& print_ticket,
                              IStream** stream) {
  DCHECK(stream);
  HRESULT hr = CreateStreamOnHGlobal(nullptr, TRUE, stream);
  if (FAILED(hr)) {
    return hr;
  }
  ULONG bytes_written = 0;
  (*stream)->Write(print_ticket.c_str(),
                   base::checked_cast<ULONG>(print_ticket.length()),
                   &bytes_written);
  DCHECK(bytes_written == print_ticket.length());
  LARGE_INTEGER pos = {};
  ULARGE_INTEGER new_pos = {};
  (*stream)->Seek(pos, STREAM_SEEK_SET, &new_pos);
  return S_OK;
}

const char kXpsTicketTemplate[] =
    "<?xml version='1.0' encoding='UTF-8'?>"
    "<psf:PrintTicket "
    "xmlns:psf='"
    "http://schemas.microsoft.com/windows/2003/08/printing/"
    "printschemaframework' "
    "xmlns:psk="
    "'http://schemas.microsoft.com/windows/2003/08/printing/"
    "printschemakeywords' "
    "version='1'>"
    "<psf:Feature name='psk:PageOutputColor'>"
    "<psf:Option name='psk:%s'>"
    "</psf:Option>"
    "</psf:Feature>"
    "</psf:PrintTicket>";

const char kXpsTicketColor[] = "Color";
const char kXpsTicketMonochrome[] = "Monochrome";

// Registry path prefix for the printer drivers' keys.
constexpr wchar_t kDriversRegistryKeyPath[] =
    L"SYSTEM\\CurrentControlSet\\Control\\Print\\Printers\\";

// Registry value name for a port.
constexpr wchar_t kPortRegistryValue[] = L"Port";

// List of printer ports which are known to cause a UI dialog to be displayed
// when printing.
constexpr wchar_t kPrinterDriverPortFile[] = L"FILE:";
constexpr wchar_t kPrinterDriverPortPrompt[] = L"PORTPROMPT:";
constexpr wchar_t kPrinterDriverPortFax[] = L"SHRFAX:";

// Gets the port used for a particular printer driver.  This can be found in
// the Windows registry entry for the driver.
std::wstring GetPrinterDriverPort(const std::string& printer_name) {
  base::win::RegKey reg_key;
  std::wstring root_key(std::wstring(kDriversRegistryKeyPath) +
                        base::UTF8ToWide(printer_name));
  LONG result =
      reg_key.Open(HKEY_LOCAL_MACHINE, root_key.c_str(), KEY_QUERY_VALUE);
  if (result != ERROR_SUCCESS)
    return std::wstring();
  std::wstring port_value;
  result = reg_key.ReadValue(kPortRegistryValue, &port_value);
  if (result != ERROR_SUCCESS)
    return std::wstring();
  return port_value;
}

std::string GetDriverVersionString(DWORDLONG version_number) {
  // A Windows driver version number is four 16-bit unsigned integers
  // concatenated together as w.x.y.z in a 64 bit unsigned int.
  // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/inf-driverver-directive
  return base::StringPrintf(
      "%u.%u.%u.%u", static_cast<uint16_t>((version_number >> 48) & 0xFFFF),
      static_cast<uint16_t>((version_number >> 32) & 0xFFFF),
      static_cast<uint16_t>((version_number >> 16) & 0xFFFF),
      static_cast<uint16_t>(version_number & 0xFFFF));
}

}  // namespace

namespace printing {

// static
bool PrinterHandleTraits::CloseHandle(HANDLE handle) {
  return ::ClosePrinter(handle) != FALSE;
}

// static
bool PrinterChangeHandleTraits::CloseHandle(HANDLE handle) {
  ::FindClosePrinterChangeNotification(handle);
  return true;
}

bool ScopedPrinterHandle::OpenPrinterWithName(const wchar_t* printer) {
  HANDLE temp_handle;
  // ::OpenPrinter may return error but assign some value into handle.
  if (::OpenPrinter(const_cast<LPTSTR>(printer), &temp_handle, nullptr)) {
    Set(temp_handle);
  }
  return IsValid();
}

bool XPSModule::Init() {
  static bool initialized = InitImpl();
  return initialized;
}

bool XPSModule::InitImpl() {
  HMODULE prntvpt_module = LoadLibrary(L"prntvpt.dll");
  if (!prntvpt_module)
    return false;
  g_open_provider_proc = reinterpret_cast<PTOpenProviderProc>(
      GetProcAddress(prntvpt_module, "PTOpenProvider"));
  if (!g_open_provider_proc) {
    NOTREACHED_IN_MIGRATION();
    return false;
  }
  g_get_print_capabilities_proc = reinterpret_cast<PTGetPrintCapabilitiesProc>(
      GetProcAddress(prntvpt_module, "PTGetPrintCapabilities"));
  if (!g_get_print_capabilities_proc) {
    NOTREACHED_IN_MIGRATION();
    return false;
  }
  g_convert_devmode_to_print_ticket_proc =
      reinterpret_cast<PTConvertDevModeToPrintTicketProc>(
          GetProcAddress(prntvpt_module, "PTConvertDevModeToPrintTicket"));
  if (!g_convert_devmode_to_print_ticket_proc) {
    NOTREACHED_IN_MIGRATION();
    return false;
  }
  g_convert_print_ticket_to_devmode_proc =
      reinterpret_cast<PTConvertPrintTicketToDevModeProc>(
          GetProcAddress(prntvpt_module, "PTConvertPrintTicketToDevMode"));
  if (!g_convert_print_ticket_to_devmode_proc) {
    NOTREACHED_IN_MIGRATION();
    return false;
  }
  g_merge_and_validate_print_ticket_proc =
      reinterpret_cast<PTMergeAndValidatePrintTicketProc>(
          GetProcAddress(prntvpt_module, "PTMergeAndValidatePrintTicket"));
  if (!g_merge_and_validate_print_ticket_proc) {
    NOTREACHED_IN_MIGRATION();
    return false;
  }
  g_release_memory_proc = reinterpret_cast<PTReleaseMemoryProc>(
      GetProcAddress(prntvpt_module, "PTReleaseMemory"));
  if (!g_release_memory_proc) {
    NOTREACHED_IN_MIGRATION();
    return false;
  }
  g_close_provider_proc = reinterpret_cast<PTCloseProviderProc>(
      GetProcAddress(prntvpt_module, "PTCloseProvider"));
  if (!g_close_provider_proc) {
    NOTREACHED_IN_MIGRATION();
    return false;
  }
  return true;
}

HRESULT XPSModule::OpenProvider(const std::wstring& printer_name,
                                DWORD version,
                                HPTPROVIDER* provider) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);
  return g_open_provider_proc(printer_name.c_str(), version, provider);
}

HRESULT XPSModule::GetPrintCapabilities(HPTPROVIDER provider,
                                        IStream* print_ticket,
                                        IStream* capabilities,
                                        BSTR* error_message) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);
  return g_get_print_capabilities_proc(provider, print_ticket, capabilities,
                                       error_message);
}

HRESULT XPSModule::ConvertDevModeToPrintTicket(HPTPROVIDER provider,
                                               ULONG devmode_size_in_bytes,
                                               PDEVMODE devmode,
                                               EPrintTicketScope scope,
                                               IStream* print_ticket) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);
  return g_convert_devmode_to_print_ticket_proc(provider, devmode_size_in_bytes,
                                                devmode, scope, print_ticket);
}

HRESULT XPSModule::ConvertPrintTicketToDevMode(
    HPTPROVIDER provider,
    IStream* print_ticket,
    EDefaultDevmodeType base_devmode_type,
    EPrintTicketScope scope,
    ULONG* devmode_byte_count,
    PDEVMODE* devmode,
    BSTR* error_message) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);
  return g_convert_print_ticket_to_devmode_proc(
      provider, print_ticket, base_devmode_type, scope, devmode_byte_count,
      devmode, error_message);
}

HRESULT XPSModule::MergeAndValidatePrintTicket(HPTPROVIDER provider,
                                               IStream* base_ticket,
                                               IStream* delta_ticket,
                                               EPrintTicketScope scope,
                                               IStream* result_ticket,
                                               BSTR* error_message) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);
  return g_merge_and_validate_print_ticket_proc(
      provider, base_ticket, delta_ticket, scope, result_ticket, error_message);
}

HRESULT XPSModule::ReleaseMemory(PVOID buffer) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);
  return g_release_memory_proc(buffer);
}

HRESULT XPSModule::CloseProvider(HPTPROVIDER provider) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);
  return g_close_provider_proc(provider);
}

ScopedXPSInitializer::ScopedXPSInitializer() : initialized_(false) {
  if (!XPSModule::Init())
    return;
  // Calls to XPS APIs typically require the XPS provider to be opened with
  // PTOpenProvider. PTOpenProvider calls CoInitializeEx with
  // COINIT_MULTITHREADED. We have seen certain buggy HP printer driver DLLs
  // that call CoInitializeEx with COINIT_APARTMENTTHREADED in the context of
  // PTGetPrintCapabilities. This call fails but the printer driver calls
  // CoUninitialize anyway. This results in the apartment being torn down too
  // early and the msxml DLL being unloaded which in turn causes code in
  // unidrvui.dll to have a dangling pointer to an XML document which causes a
  // crash. To protect ourselves from such drivers we make sure we always have
  // an extra CoInitialize (calls to CoInitialize/CoUninitialize are
  // refcounted).
  HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  // If this succeeded we are done because the PTOpenProvider call will provide
  // the extra refcount on the apartment. If it failed because someone already
  // called CoInitializeEx with COINIT_APARTMENTTHREADED, we try the other model
  // to provide the additional refcount (since we don't know which model buggy
  // printer drivers will use).
  if (!SUCCEEDED(hr))
    hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
  DCHECK(SUCCEEDED(hr));
  initialized_ = true;
}

ScopedXPSInitializer::~ScopedXPSInitializer() {
  if (initialized_)
    CoUninitialize();
  initialized_ = false;
}

bool XPSPrintModule::Init() {
  static bool initialized = InitImpl();
  return initialized;
}

bool XPSPrintModule::InitImpl() {
  HMODULE xpsprint_module = LoadLibrary(L"xpsprint.dll");
  if (!xpsprint_module)
    return false;
  g_start_xps_print_job_proc = reinterpret_cast<StartXpsPrintJobProc>(
      GetProcAddress(xpsprint_module, "StartXpsPrintJob"));
  if (!g_start_xps_print_job_proc) {
    NOTREACHED_IN_MIGRATION();
    return false;
  }
  return true;
}

HRESULT XPSPrintModule::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) {
  return g_start_xps_print_job_proc(
      printer_name, job_name, output_file_name, progress_event,
      completion_event, printable_pages_on, printable_pages_on_count,
      xps_print_job, document_stream, print_ticket_stream);
}

void SetGetDisplayNameFunction(GetDisplayNameFunc get_display_name_func) {
  DCHECK(get_display_name_func);
  DCHECK(!g_get_display_name_func);
  g_get_display_name_func = get_display_name_func;
}

bool InitBasicPrinterInfo(HANDLE printer, PrinterBasicInfo* printer_info) {
  DCHECK(printer);
  DCHECK(printer_info);
  if (!printer)
    return false;

  PrinterInfo2 info_2;
  if (!info_2.Init(printer))
    return false;

  printer_info->printer_name = base::WideToUTF8(info_2.get()->pPrinterName);
  if (g_get_display_name_func) {
    printer_info->display_name =
        g_get_display_name_func(printer_info->printer_name);
  } else {
    printer_info->display_name = printer_info->printer_name;
  }
  if (info_2.get()->pComment) {
    printer_info->printer_description =
        base::WideToUTF8(info_2.get()->pComment);
  }
  if (info_2.get()->pLocation) {
    printer_info->options[kLocationTagName] =
        base::WideToUTF8(info_2.get()->pLocation);
  }
  if (info_2.get()->pDriverName) {
    printer_info->options[kDriverNameTagName] =
        base::WideToUTF8(info_2.get()->pDriverName);
  }
  printer_info->printer_status = info_2.get()->Status;

  std::vector<std::string> driver_info = GetDriverInfo(printer);
  if (!driver_info.empty()) {
    printer_info->options[kDriverInfoTagName] =
        base::JoinString(driver_info, ";");
  }
  return true;
}

std::vector<std::string> GetDriverInfo(HANDLE printer) {
  DCHECK(printer);
  std::vector<std::string> driver_info;

  if (!printer) {
    return driver_info;
  }

  DriverInfo6 info_6;
  if (!info_6.Init(printer)) {
    return driver_info;
  }

  driver_info.emplace_back(info_6.get()->pName
                               ? base::WideToUTF8(info_6.get()->pName)
                               : std::string());

  driver_info.emplace_back(
      info_6.get()->dwlDriverVersion
          ? GetDriverVersionString(info_6.get()->dwlDriverVersion)
          : std::string());

  if (info_6.get()->pDriverPath) {
    std::unique_ptr<FileVersionInfo> version_info(
        FileVersionInfo::CreateFileVersionInfo(
            base::FilePath(info_6.get()->pDriverPath)));
    if (version_info.get()) {
      driver_info.emplace_back(base::UTF16ToUTF8(version_info->file_version()));
      driver_info.emplace_back(base::UTF16ToUTF8(version_info->product_name()));
    }
  }

  return driver_info;
}

bool DoesDriverDisplayFileDialogForPrinting(const std::string& printer_name) {
  static constexpr auto kPortNames = base::MakeFixedFlatSet<std::wstring_view>(
      {kPrinterDriverPortFile, kPrinterDriverPortPrompt,
       kPrinterDriverPortFax});
  return kPortNames.contains(GetPrinterDriverPort(printer_name));
}

std::unique_ptr<DEVMODE, base::FreeDeleter> XpsTicketToDevMode(
    const std::wstring& printer_name,
    const std::string& print_ticket) {
  std::unique_ptr<DEVMODE, base::FreeDeleter> dev_mode;
  ScopedXPSInitializer xps_initializer;
  if (!xps_initializer.initialized()) {
    // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll)
    return dev_mode;
  }

  ScopedPrinterHandle printer;
  if (!printer.OpenPrinterWithName(printer_name.c_str()))
    return dev_mode;

  Microsoft::WRL::ComPtr<IStream> pt_stream;
  HRESULT hr = StreamFromPrintTicket(print_ticket, &pt_stream);
  if (FAILED(hr))
    return dev_mode;

  HPTPROVIDER provider = nullptr;
  hr = XPSModule::OpenProvider(printer_name, 1, &provider);
  if (SUCCEEDED(hr)) {
    ULONG size = 0;
    DEVMODE* dm = nullptr;
    // Use kPTJobScope, because kPTDocumentScope breaks duplex.
    hr = XPSModule::ConvertPrintTicketToDevMode(
        provider, pt_stream.Get(), kUserDefaultDevmode, kPTJobScope, &size, &dm,
        nullptr);
    if (SUCCEEDED(hr)) {
      // Correct DEVMODE using DocumentProperties. See documentation for
      // PTConvertPrintTicketToDevMode.
      dev_mode = CreateDevMode(printer.Get(), dm);
      XPSModule::ReleaseMemory(dm);
    }
    XPSModule::CloseProvider(provider);
  }
  return dev_mode;
}

bool IsDevModeWithColor(const DEVMODE* devmode) {
  return (devmode->dmFields & DM_COLOR) && (devmode->dmColor == DMCOLOR_COLOR);
}

std::unique_ptr<DEVMODE, base::FreeDeleter> CreateDevModeWithColor(
    HANDLE printer,
    const std::wstring& printer_name,
    bool color) {
  std::unique_ptr<DEVMODE, base::FreeDeleter> default_ticket =
      CreateDevMode(printer, nullptr);
  if (!default_ticket || IsDevModeWithColor(default_ticket.get()) == color)
    return default_ticket;

  default_ticket->dmFields |= DM_COLOR;
  default_ticket->dmColor = color ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME;

  DriverInfo6 info_6;
  if (!info_6.Init(printer))
    return default_ticket;

  const DRIVER_INFO_6* p = info_6.get();

  // Only HP known to have issues.
  if (!p->pszMfgName || wcscmp(p->pszMfgName, L"HP") != 0)
    return default_ticket;

  // Need XPS for this workaround.
  ScopedXPSInitializer xps_initializer;
  if (!xps_initializer.initialized())
    return default_ticket;

  const char* xps_color = color ? kXpsTicketColor : kXpsTicketMonochrome;
  std::string xps_ticket = base::StringPrintf(kXpsTicketTemplate, xps_color);
  std::unique_ptr<DEVMODE, base::FreeDeleter> ticket =
      XpsTicketToDevMode(printer_name, xps_ticket);
  if (!ticket)
    return default_ticket;

  return ticket;
}

bool PrinterHasValidPaperSize(const wchar_t* name, const wchar_t* port) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);
  return DeviceCapabilities(name, port, DC_PAPERSIZE, nullptr, nullptr) > 0;
}

std::unique_ptr<DEVMODE, base::FreeDeleter> CreateDevMode(HANDLE printer,
                                                          DEVMODE* in) {
  wchar_t* device_name_ptr = const_cast<wchar_t*>(L"");
  LONG buffer_size;
  {
    base::ScopedBlockingCall scoped_blocking_call(
        FROM_HERE, base::BlockingType::MAY_BLOCK);
    buffer_size = DocumentProperties(nullptr, printer, device_name_ptr, nullptr,
                                     nullptr, 0);
  }
  // TODO(thestig): Consider limiting `buffer_size` to avoid buggy printer
  // drivers that return excessively large values.
  if (buffer_size < static_cast<LONG>(sizeof(DEVMODE)))
    return nullptr;

  // Some drivers request buffers with size smaller than dmSize + dmDriverExtra.
  // Examples: crbug.com/421402, crbug.com/780016
  // Pad the `out` buffer so there is plenty of space. Calculate the size using
  // `base::CheckedNumeric` to avoid a potential integer overflow.
  base::CheckedNumeric<LONG> safe_buffer_size = buffer_size;
  safe_buffer_size *= 2;
  safe_buffer_size += 8192;
  buffer_size = safe_buffer_size.ValueOrDie();

  std::unique_ptr<DEVMODE, base::FreeDeleter> out(
      reinterpret_cast<DEVMODE*>(calloc(buffer_size, 1)));
  DWORD flags = (in ? (DM_IN_BUFFER) : 0) | DM_OUT_BUFFER;

  PrinterInfo5 info_5;
  if (!info_5.Init(printer))
    return nullptr;

  // Check that valid paper sizes exist; some old drivers return no paper sizes
  // and crash in DocumentProperties if used with Win10. See crbug.com/679160,
  // crbug.com/724595
  const wchar_t* name = info_5.get()->pPrinterName;
  const wchar_t* port = info_5.get()->pPortName;
  if (!PrinterHasValidPaperSize(name, port)) {
    return nullptr;
  }

  {
    base::ScopedBlockingCall scoped_blocking_call(
        FROM_HERE, base::BlockingType::MAY_BLOCK);
    if (DocumentProperties(nullptr, printer, device_name_ptr, out.get(), in,
                           flags) != IDOK) {
      return nullptr;
    }
  }

  int size = out->dmSize;
  int extra_size = out->dmDriverExtra;

  // The CHECK_GE() below can fail. Alias the variable values so they are
  // recorded in crash dumps.
  // See https://crbug.com/780016 and https://crbug.com/806016 for example
  // crashes.
  // TODO(crbug.com/41352705): Remove this debug code if the CHECK_GE() below
  // stops failing.
  base::debug::Alias(&size);
  base::debug::Alias(&extra_size);
  base::debug::Alias(&buffer_size);
  CHECK_GE(buffer_size, size + extra_size);
  return out;
}

std::unique_ptr<DEVMODE, base::FreeDeleter> PromptDevMode(
    HANDLE printer,
    const std::wstring& printer_name,
    DEVMODE* in,
    HWND window,
    bool* canceled) {
  wchar_t* printer_name_ptr = const_cast<wchar_t*>(printer_name.c_str());
  LONG buffer_size = DocumentProperties(window, printer, printer_name_ptr,
                                        nullptr, nullptr, 0);
  if (buffer_size < static_cast<int>(sizeof(DEVMODE)))
    return std::unique_ptr<DEVMODE, base::FreeDeleter>();

  // Some drivers request buffers with size smaller than dmSize + dmDriverExtra.
  // crbug.com/421402
  buffer_size *= 2;

  std::unique_ptr<DEVMODE, base::FreeDeleter> out(
      reinterpret_cast<DEVMODE*>(calloc(buffer_size, 1)));
  DWORD flags = (in ? (DM_IN_BUFFER) : 0) | DM_OUT_BUFFER | DM_IN_PROMPT;
  LONG result;
  {
    base::ScopedBlockingCall scoped_blocking_call(
        FROM_HERE, base::BlockingType::MAY_BLOCK);
    result = DocumentProperties(window, printer, printer_name_ptr, out.get(),
                                in, flags);
  }

  if (canceled)
    *canceled = (result == IDCANCEL);
  if (result != IDOK)
    return std::unique_ptr<DEVMODE, base::FreeDeleter>();

  int size = out->dmSize;
  int extra_size = out->dmDriverExtra;
  CHECK_GE(buffer_size, size + extra_size);
  return out;
}

std::string GetDriverVersionStringForTesting(DWORDLONG version_number) {
  return GetDriverVersionString(version_number);
}

mojom::ResultCode GetResultCodeFromSystemErrorCode(
    logging::SystemErrorCode system_code) {
  if (system_code == ERROR_ACCESS_DENIED) {
    return mojom::ResultCode::kAccessDenied;
  }
  return mojom::ResultCode::kFailed;
}

}  // namespace printing