/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsPrinterListCUPS.h" #include "mozilla/GkRustUtils.h" #include "mozilla/IntegerRange.h" #include "nsCUPSShim.h" #include "nsPrinterCUPS.h" #include "nsString.h" #include "prenv.h" static nsCUPSShim sCupsShim; // Requested attributes for IPP requests, just the CUPS version now. static constexpr mozilla::Array requestedAttributes{ "cups-version"}; using PrinterInfo = nsPrinterListBase::PrinterInfo; /** * Retrieves a human-readable name for the printer from CUPS. * https://www.cups.org/doc/cupspm.html#basic-destination-information */ static void GetDisplayNameForPrinter(const cups_dest_t& aDest, nsAString& aName) { // macOS clients expect prettified printer names // while GTK clients expect non-prettified names. // If you change this, please change NamedPrinter accordingly. #ifdef XP_MACOSX const char* displayName = sCupsShim.cupsGetOption("printer-info", aDest.num_options, aDest.options); if (displayName) { CopyUTF8toUTF16(MakeStringSpan(displayName), aName); } #endif } // Fetches the CUPS version for the print server controlling the printer. This // will only modify the output arguments if the fetch succeeds. static void FetchCUPSVersionForPrinter(const cups_dest_t& aDest, uint64_t& aOutMajor, uint64_t& aOutMinor, uint64_t& aOutPatch) { // Make an IPP request to the server for the printer. const char* const uri = sCupsShim.cupsGetOption( "printer-uri-supported", aDest.num_options, aDest.options); if (!uri) { return; } ipp_t* const ippRequest = sCupsShim.ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); // Set the URI we want to use. sCupsShim.ippAddString(ippRequest, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", nullptr, uri); // Set the attributes to request. sCupsShim.ippAddStrings(ippRequest, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", requestedAttributes.Length, nullptr, &(requestedAttributes[0])); // Use the default HTTP connection to query the CUPS server itself to get // the CUPS version. // Note that cupsDoRequest will delete the request whether it succeeds or // fails, so we should not use ippDelete on it. if (ipp_t* const ippResponse = sCupsShim.cupsDoRequest(CUPS_HTTP_DEFAULT, ippRequest, "/")) { ipp_attribute_t* const versionAttrib = sCupsShim.ippFindAttribute(ippResponse, "cups-version", IPP_TAG_TEXT); if (versionAttrib && sCupsShim.ippGetCount(versionAttrib) == 1) { const char* versionString = sCupsShim.ippGetString(versionAttrib, 0, nullptr); MOZ_ASSERT(versionString); // On error, GkRustUtils::ParseSemVer will not modify its arguments. GkRustUtils::ParseSemVer( nsDependentCSubstring{MakeStringSpan(versionString)}, aOutMajor, aOutMinor, aOutPatch); } sCupsShim.ippDelete(ippResponse); } } nsTArray nsPrinterListCUPS::Printers() const { if (!sCupsShim.EnsureInitialized()) { return {}; } nsTArray printerInfoList; cups_dest_t* printers = nullptr; auto numPrinters = sCupsShim.cupsGetDests(&printers); printerInfoList.SetCapacity(numPrinters); for (auto i : mozilla::IntegerRange(0, numPrinters)) { cups_dest_t* dest = printers + i; cups_dest_t* ownedDest = nullptr; mozilla::DebugOnly numCopied = sCupsShim.cupsCopyDest(dest, 0, &ownedDest); MOZ_ASSERT(numCopied == 1); cups_dinfo_t* ownedInfo = sCupsShim.cupsCopyDestInfo(CUPS_HTTP_DEFAULT, ownedDest); nsString name; GetDisplayNameForPrinter(*dest, name); uint64_t major = 0; uint64_t minor = 0; uint64_t patch = 0; FetchCUPSVersionForPrinter(*dest, major, minor, patch); printerInfoList.AppendElement(PrinterInfo{ std::move(name), {ownedDest, ownedInfo}, major, minor, patch}); } sCupsShim.cupsFreeDests(numPrinters, printers); return printerInfoList; } RefPtr nsPrinterListCUPS::CreatePrinter(PrinterInfo aInfo) const { return mozilla::MakeRefPtr( sCupsShim, std::move(aInfo.mName), static_cast(aInfo.mCupsHandles[0]), static_cast(aInfo.mCupsHandles[1]), aInfo.mServerMajor, aInfo.mServerMinor, aInfo.mServerPatch); } Maybe nsPrinterListCUPS::NamedPrinter( nsString aPrinterName) const { Maybe rv; if (!sCupsShim.EnsureInitialized()) { return rv; } // Will contain the printer, if found. This must be fully owned, and not a // member of another array of printers. cups_dest_t* printer = nullptr; #ifdef XP_MACOSX // On OS X the printer name given to this function is the readable/display // name and not the CUPS name, so we iterate over all the printers for now. // See bug 1659807 for one approach to improve perf here. { nsAutoCString printerName; CopyUTF16toUTF8(aPrinterName, printerName); cups_dest_t* printers = nullptr; const auto numPrinters = sCupsShim.cupsGetDests(&printers); for (auto i : mozilla::IntegerRange(0, numPrinters)) { const char* const displayName = sCupsShim.cupsGetOption( "printer-info", printers[i].num_options, printers[i].options); if (printerName == displayName) { // The second arg to sCupsShim.cupsCopyDest is called num_dests, but // it actually copies num_dests + 1 elements. sCupsShim.cupsCopyDest(printers + i, 0, &printer); break; } } sCupsShim.cupsFreeDests(numPrinters, printers); } #else // On GTK, we only ever show the CUPS name of printers, so we can use // cupsGetNamedDest directly. { const auto printerName = NS_ConvertUTF16toUTF8(aPrinterName); printer = sCupsShim.cupsGetNamedDest(CUPS_HTTP_DEFAULT, printerName.get(), nullptr); } #endif if (printer) { cups_dinfo_t* const info = sCupsShim.cupsCopyDestInfo(CUPS_HTTP_DEFAULT, printer); MOZ_ASSERT(info); // Since the printer name had to be passed by-value, we can move the // name from that. rv.emplace(PrinterInfo{std::move(aPrinterName), {printer, info}}); } return rv; } nsresult nsPrinterListCUPS::SystemDefaultPrinterName(nsAString& aName) const { aName.Truncate(); if (!sCupsShim.EnsureInitialized()) { return NS_ERROR_FAILURE; } // Passing in nullptr for the name will return the default, if any. cups_dest_t* dest = sCupsShim.cupsGetNamedDest(CUPS_HTTP_DEFAULT, /* name */ nullptr, /* instance */ nullptr); if (!dest) { return NS_OK; } GetDisplayNameForPrinter(*dest, aName); if (aName.IsEmpty()) { CopyUTF8toUTF16(mozilla::MakeStringSpan(dest->name), aName); } sCupsShim.cupsFreeDests(1, dest); return NS_OK; } NS_IMETHODIMP nsPrinterListCUPS::InitPrintSettingsFromPrinter( const nsAString& aPrinterName, nsIPrintSettings* aPrintSettings) { MOZ_ASSERT(aPrintSettings); // Set a default file name. nsAutoString filename; nsresult rv = aPrintSettings->GetToFileName(filename); if (NS_FAILED(rv) || filename.IsEmpty()) { const char* path = PR_GetEnv("PWD"); if (!path) { path = PR_GetEnv("HOME"); } if (path) { CopyUTF8toUTF16(mozilla::MakeStringSpan(path), filename); filename.AppendLiteral("/mozilla.pdf"); } else { filename.AssignLiteral("mozilla.pdf"); } aPrintSettings->SetToFileName(filename); } aPrintSettings->SetIsInitializedFromPrinter(true); return NS_OK; }