Bug 1777562 [wpt PR 34663] - [FedCM] Rename FederatedCredential to IdentityCredential...
[gecko.git] / widget / nsPrinterCUPS.cpp
blob08ec1e9889fae0a1b9f15d355b6a2c1de4c3c5c9
1 /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsPrinterCUPS.h"
8 #include "mozilla/gfx/2D.h"
9 #include "mozilla/GkRustUtils.h"
10 #include "mozilla/Preferences.h"
11 #include "mozilla/StaticPrefs_print.h"
12 #include "nsTHashtable.h"
13 #include "nsPaper.h"
14 #include "nsPrinterBase.h"
15 #include "nsPrintSettingsImpl.h"
16 #include "plstr.h"
18 using namespace mozilla;
19 using MarginDouble = mozilla::gfx::MarginDouble;
21 // Requested attributes for IPP requests, just the CUPS version now.
22 static constexpr Array<const char* const, 1> requestedAttributes{
23 "cups-version"};
25 static constexpr double kPointsPerHundredthMillimeter = 72.0 / 2540.0;
27 static PaperInfo MakePaperInfo(const nsAString& aName,
28 const cups_size_t& aMedia) {
29 // XXX Do we actually have the guarantee that this is utf-8?
30 NS_ConvertUTF8toUTF16 paperId(aMedia.media); // internal paper name/ID
31 return PaperInfo(
32 paperId, aName,
33 {aMedia.width * kPointsPerHundredthMillimeter,
34 aMedia.length * kPointsPerHundredthMillimeter},
35 Some(gfx::MarginDouble{aMedia.top * kPointsPerHundredthMillimeter,
36 aMedia.right * kPointsPerHundredthMillimeter,
37 aMedia.bottom * kPointsPerHundredthMillimeter,
38 aMedia.left * kPointsPerHundredthMillimeter}));
41 // Fetches the CUPS version for the print server controlling the printer. This
42 // will only modify the output arguments if the fetch succeeds.
43 static void FetchCUPSVersionForPrinter(const nsCUPSShim& aShim,
44 const cups_dest_t* const aDest,
45 uint64_t& aOutMajor, uint64_t& aOutMinor,
46 uint64_t& aOutPatch) {
47 // Make an IPP request to the server for the printer.
48 const char* const uri = aShim.cupsGetOption(
49 "printer-uri-supported", aDest->num_options, aDest->options);
50 if (!uri) {
51 return;
54 ipp_t* const ippRequest = aShim.ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
56 // Set the URI we want to use.
57 aShim.ippAddString(ippRequest, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
58 nullptr, uri);
60 // Set the attributes to request.
61 aShim.ippAddStrings(ippRequest, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
62 "requested-attributes", requestedAttributes.Length,
63 nullptr, &(requestedAttributes[0]));
65 // Use the default HTTP connection to query the CUPS server itself to get
66 // the CUPS version.
67 // Note that cupsDoRequest will delete the request whether it succeeds or
68 // fails, so we should not use ippDelete on it.
69 if (ipp_t* const ippResponse =
70 aShim.cupsDoRequest(CUPS_HTTP_DEFAULT, ippRequest, "/")) {
71 ipp_attribute_t* const versionAttrib =
72 aShim.ippFindAttribute(ippResponse, "cups-version", IPP_TAG_TEXT);
73 if (versionAttrib && aShim.ippGetCount(versionAttrib) == 1) {
74 const char* versionString = aShim.ippGetString(versionAttrib, 0, nullptr);
75 MOZ_ASSERT(versionString);
76 // On error, GkRustUtils::ParseSemVer will not modify its arguments.
77 GkRustUtils::ParseSemVer(
78 nsDependentCSubstring{MakeStringSpan(versionString)}, aOutMajor,
79 aOutMinor, aOutPatch);
81 aShim.ippDelete(ippResponse);
85 nsPrinterCUPS::~nsPrinterCUPS() {
86 auto printerInfoLock = mPrinterInfoMutex.Lock();
87 if (printerInfoLock->mPrinterInfo) {
88 mShim.cupsFreeDestInfo(printerInfoLock->mPrinterInfo);
90 if (mPrinter) {
91 mShim.cupsFreeDests(1, mPrinter);
92 mPrinter = nullptr;
96 NS_IMETHODIMP
97 nsPrinterCUPS::GetName(nsAString& aName) {
98 GetPrinterName(aName);
99 return NS_OK;
102 NS_IMETHODIMP
103 nsPrinterCUPS::GetSystemName(nsAString& aName) {
104 CopyUTF8toUTF16(MakeStringSpan(mPrinter->name), aName);
105 return NS_OK;
108 void nsPrinterCUPS::GetPrinterName(nsAString& aName) const {
109 if (mDisplayName.IsEmpty()) {
110 aName.Truncate();
111 CopyUTF8toUTF16(MakeStringSpan(mPrinter->name), aName);
112 } else {
113 aName = mDisplayName;
117 const char* nsPrinterCUPS::LocalizeMediaName(http_t& aConnection,
118 cups_size_t& aMedia) const {
119 // The returned string is owned by mPrinterInfo.
120 // https://www.cups.org/doc/cupspm.html#cupsLocalizeDestMedia
121 if (!mShim.cupsLocalizeDestMedia) {
122 return aMedia.media;
124 auto printerInfoLock = TryEnsurePrinterInfo();
125 cups_dinfo_t* const printerInfo = printerInfoLock->mPrinterInfo;
126 return mShim.cupsLocalizeDestMedia(&aConnection, mPrinter, printerInfo,
127 CUPS_MEDIA_FLAGS_DEFAULT, &aMedia);
130 bool nsPrinterCUPS::SupportsDuplex() const {
131 return Supports(CUPS_SIDES, CUPS_SIDES_TWO_SIDED_PORTRAIT);
134 bool nsPrinterCUPS::SupportsMonochrome() const {
135 if (!SupportsColor()) {
136 return true;
138 return StaticPrefs::print_cups_monochrome_enabled();
141 bool nsPrinterCUPS::SupportsColor() const {
142 // CUPS 2.1 (particularly as used in Ubuntu 16) is known to have inaccurate
143 // results for CUPS_PRINT_COLOR_MODE.
144 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1660658#c15
145 if (!IsCUPSVersionAtLeast(2, 2, 0)) {
146 return true; // See comment for PrintSettingsInitializer.mPrintInColor
148 return Supports(CUPS_PRINT_COLOR_MODE, CUPS_PRINT_COLOR_MODE_AUTO) ||
149 Supports(CUPS_PRINT_COLOR_MODE, CUPS_PRINT_COLOR_MODE_COLOR) ||
150 !Supports(CUPS_PRINT_COLOR_MODE, CUPS_PRINT_COLOR_MODE_MONOCHROME);
153 bool nsPrinterCUPS::SupportsCollation() const {
154 // We can't depend on cupsGetIntegerOption existing.
155 const char* const value = FindCUPSOption("printer-type");
156 if (!value) {
157 return false;
159 // If the value is non-numeric, then atoi will return 0, which will still
160 // cause this function to return false.
161 const int type = atoi(value);
162 return type & CUPS_PRINTER_COLLATE;
165 nsPrinterBase::PrinterInfo nsPrinterCUPS::CreatePrinterInfo() const {
166 Connection connection{mShim};
167 return PrinterInfo{PaperList(connection), DefaultSettings(connection)};
170 bool nsPrinterCUPS::Supports(const char* aOption, const char* aValue) const {
171 auto printerInfoLock = TryEnsurePrinterInfo();
172 cups_dinfo_t* const printerInfo = printerInfoLock->mPrinterInfo;
173 return mShim.cupsCheckDestSupported(CUPS_HTTP_DEFAULT, mPrinter, printerInfo,
174 aOption, aValue);
177 bool nsPrinterCUPS::IsCUPSVersionAtLeast(uint64_t aCUPSMajor,
178 uint64_t aCUPSMinor,
179 uint64_t aCUPSPatch) const {
180 auto printerInfoLock = TryEnsurePrinterInfo();
181 // Compare major version.
182 if (printerInfoLock->mCUPSMajor > aCUPSMajor) {
183 return true;
185 if (printerInfoLock->mCUPSMajor < aCUPSMajor) {
186 return false;
189 // Compare minor version.
190 if (printerInfoLock->mCUPSMinor > aCUPSMinor) {
191 return true;
193 if (printerInfoLock->mCUPSMinor < aCUPSMinor) {
194 return false;
197 // Compare patch.
198 return aCUPSPatch <= printerInfoLock->mCUPSPatch;
201 http_t* nsPrinterCUPS::Connection::GetConnection(cups_dest_t* aDest) {
202 if (mWasInited) {
203 return mConnection;
205 mWasInited = true;
207 // blocking call
208 http_t* const connection = mShim.cupsConnectDest(aDest, CUPS_DEST_FLAGS_NONE,
209 /* timeout(ms) */ 5000,
210 /* cancel */ nullptr,
211 /* resource */ nullptr,
212 /* resourcesize */ 0,
213 /* callback */ nullptr,
214 /* user_data */ nullptr);
215 if (connection) {
216 mConnection = connection;
218 return mConnection;
221 nsPrinterCUPS::Connection::~Connection() {
222 if (mWasInited && mConnection) {
223 mShim.httpClose(mConnection);
227 PrintSettingsInitializer nsPrinterCUPS::DefaultSettings(
228 Connection& aConnection) const {
229 nsString printerName;
230 GetPrinterName(printerName);
231 auto printerInfoLock = TryEnsurePrinterInfo();
232 cups_dinfo_t* const printerInfo = printerInfoLock->mPrinterInfo;
234 cups_size_t media;
236 bool hasDefaultMedia = false;
237 // cupsGetDestMediaDefault appears to return more accurate defaults on macOS,
238 // and the IPP attribute appears to return more accurate defaults on Linux.
239 #ifdef XP_MACOSX
240 hasDefaultMedia =
241 mShim.cupsGetDestMediaDefault(CUPS_HTTP_DEFAULT, mPrinter, printerInfo,
242 CUPS_MEDIA_FLAGS_DEFAULT, &media);
243 #else
245 ipp_attribute_t* defaultMediaIPP =
246 mShim.cupsFindDestDefault
247 ? mShim.cupsFindDestDefault(CUPS_HTTP_DEFAULT, mPrinter,
248 printerInfo, "media")
249 : nullptr;
251 const char* defaultMediaName =
252 defaultMediaIPP ? mShim.ippGetString(defaultMediaIPP, 0, nullptr)
253 : nullptr;
255 hasDefaultMedia = defaultMediaName &&
256 mShim.cupsGetDestMediaByName(
257 CUPS_HTTP_DEFAULT, mPrinter, printerInfo,
258 defaultMediaName, CUPS_MEDIA_FLAGS_DEFAULT, &media);
260 #endif
262 if (!hasDefaultMedia) {
263 return PrintSettingsInitializer{
264 std::move(printerName),
265 PaperInfo(),
266 SupportsColor(),
270 // Check if this is a localized fallback paper size, in which case we can
271 // avoid using the CUPS localization methods.
272 const gfx::SizeDouble sizeDouble{
273 media.width * kPointsPerHundredthMillimeter,
274 media.length * kPointsPerHundredthMillimeter};
275 if (const PaperInfo* const paperInfo = FindCommonPaperSize(sizeDouble)) {
276 return PrintSettingsInitializer{
277 std::move(printerName),
278 MakePaperInfo(paperInfo->mName, media),
279 SupportsColor(),
283 http_t* const connection = aConnection.GetConnection(mPrinter);
284 // XXX Do we actually have the guarantee that this is utf-8?
285 NS_ConvertUTF8toUTF16 localizedName{
286 connection ? LocalizeMediaName(*connection, media) : ""};
288 return PrintSettingsInitializer{
289 std::move(printerName),
290 MakePaperInfo(localizedName, media),
291 SupportsColor(),
295 nsTArray<mozilla::PaperInfo> nsPrinterCUPS::PaperList(
296 Connection& aConnection) const {
297 http_t* const connection = aConnection.GetConnection(mPrinter);
298 auto printerInfoLock = TryEnsurePrinterInfo(connection);
299 cups_dinfo_t* const printerInfo = printerInfoLock->mPrinterInfo;
301 if (!printerInfo) {
302 return {};
305 const int paperCount =
306 mShim.cupsGetDestMediaCount
307 ? mShim.cupsGetDestMediaCount(connection, mPrinter, printerInfo,
308 CUPS_MEDIA_FLAGS_DEFAULT)
309 : 0;
310 nsTArray<PaperInfo> paperList;
311 nsTHashtable<nsCharPtrHashKey> paperSet(std::max(paperCount, 0));
313 paperList.SetCapacity(paperCount);
314 for (int i = 0; i < paperCount; ++i) {
315 cups_size_t media;
316 const int getInfoSucceeded = mShim.cupsGetDestMediaByIndex(
317 connection, mPrinter, printerInfo, i, CUPS_MEDIA_FLAGS_DEFAULT, &media);
319 if (!getInfoSucceeded || !paperSet.EnsureInserted(media.media)) {
320 continue;
322 // Check if this is a PWG paper size, in which case we can avoid using the
323 // CUPS localization methods.
324 const gfx::SizeDouble sizeDouble{
325 media.width * kPointsPerHundredthMillimeter,
326 media.length * kPointsPerHundredthMillimeter};
327 if (const PaperInfo* const paperInfo = FindCommonPaperSize(sizeDouble)) {
328 paperList.AppendElement(MakePaperInfo(paperInfo->mName, media));
329 } else {
330 const char* const mediaName =
331 connection ? LocalizeMediaName(*connection, media) : media.media;
332 paperList.AppendElement(
333 MakePaperInfo(NS_ConvertUTF8toUTF16(mediaName), media));
337 return paperList;
340 nsPrinterCUPS::PrinterInfoLock nsPrinterCUPS::TryEnsurePrinterInfo(
341 http_t* const aConnection) const {
342 PrinterInfoLock lock = mPrinterInfoMutex.Lock();
343 if (lock->mPrinterInfo ||
344 (aConnection == CUPS_HTTP_DEFAULT ? lock->mTriedInitWithDefault
345 : lock->mTriedInitWithConnection)) {
346 return lock;
349 if (aConnection == CUPS_HTTP_DEFAULT) {
350 lock->mTriedInitWithDefault = true;
351 } else {
352 lock->mTriedInitWithConnection = true;
355 MOZ_ASSERT(mPrinter);
357 // httpGetAddress was only added in CUPS 2.0, and some systems still use
358 // CUPS 1.7.
359 if (aConnection && MOZ_LIKELY(mShim.httpGetAddress && mShim.httpAddrPort)) {
360 // This is a workaround for the CUPS Bug seen in bug 1691347.
361 // This is to avoid a null string being passed to strstr in CUPS. The path
362 // in CUPS that leads to this is as follows:
364 // In cupsCopyDestInfo, CUPS_DEST_FLAG_DEVICE is set when the connection is
365 // not null (same as CUPS_HTTP_DEFAULT), the print server is not the same
366 // as our hostname and is not path-based (starts with a '/'), or the IPP
367 // port is different than the global server IPP port.
369 // https://github.com/apple/cups/blob/c9da6f63b263faef5d50592fe8cf8056e0a58aa2/cups/dest-options.c#L718-L722
371 // In _cupsGetDestResource, CUPS fetches the IPP options "device-uri" and
372 // "printer-uri-supported". Note that IPP options are returned as null when
373 // missing.
375 // https://github.com/apple/cups/blob/23c45db76a8520fd6c3b1d9164dbe312f1ab1481/cups/dest.c#L1138-L1141
377 // If the CUPS_DEST_FLAG_DEVICE is set or the "printer-uri-supported"
378 // option is not set, CUPS checks for "._tcp" in the "device-uri" option
379 // without doing a NULL-check first.
381 // https://github.com/apple/cups/blob/23c45db76a8520fd6c3b1d9164dbe312f1ab1481/cups/dest.c#L1144
383 // If we find that those branches will be taken, don't actually fetch the
384 // CUPS data and instead just return an empty printer info.
386 const char* const serverNameBytes = mShim.cupsServer();
388 if (MOZ_LIKELY(serverNameBytes)) {
389 const nsDependentCString serverName{serverNameBytes};
391 // We only need enough characters to determine equality with serverName.
392 // + 2 because we need one byte for the null-character, and we also want
393 // to get more characters of the host name than the server name if
394 // possible. Otherwise, if the hostname starts with the same text as the
395 // entire server name, it would compare equal when it's not.
396 const size_t hostnameMemLength = serverName.Length() + 2;
397 auto hostnameMem = MakeUnique<char[]>(hostnameMemLength);
399 // We don't expect httpGetHostname to return null when a connection is
400 // passed, but it's better not to make assumptions.
401 const char* const hostnameBytes = mShim.httpGetHostname(
402 aConnection, hostnameMem.get(), hostnameMemLength);
404 if (MOZ_LIKELY(hostnameBytes)) {
405 const nsDependentCString hostname{hostnameBytes};
407 // Attempt to match the condional at
408 // https://github.com/apple/cups/blob/c9da6f63b263faef5d50592fe8cf8056e0a58aa2/cups/dest-options.c#L718
410 // To find the result of the comparison CUPS performs of
411 // `strcmp(http->hostname, cg->server)`, we use httpGetHostname to try
412 // to get the value of `http->hostname`, but this isn't quite the same.
413 // For local addresses, httpGetHostName will normalize the result to be
414 // localhost", rather than the actual value of `http->hostname`.
416 // https://github.com/apple/cups/blob/2201569857f225c9874bfae19713ffb2f4bdfdeb/cups/http-addr.c#L794-L818
418 // Because of this, if both serverName and hostname equal "localhost",
419 // then the actual hostname might be a different local address that CUPS
420 // normalized in httpGetHostName, and `http->hostname` won't be equal to
421 // `cg->server` in CUPS.
422 const bool namesMightNotMatch =
423 hostname != serverName || hostname == "localhost";
424 const bool portsDiffer =
425 mShim.httpAddrPort(mShim.httpGetAddress(aConnection)) !=
426 mShim.ippPort();
427 const bool cupsDestDeviceFlag =
428 (namesMightNotMatch && serverName[0] != '/') || portsDiffer;
430 // Match the conditional at
431 // https://github.com/apple/cups/blob/23c45db76a8520fd6c3b1d9164dbe312f1ab1481/cups/dest.c#L1144
432 // but if device-uri is null do not call into CUPS.
433 if ((cupsDestDeviceFlag || !FindCUPSOption("printer-uri-supported")) &&
434 !FindCUPSOption("device-uri")) {
435 return lock;
441 // All CUPS calls that take the printer info do null-checks internally, so we
442 // can fetch this info and only worry about the result of the later CUPS
443 // functions.
444 lock->mPrinterInfo = mShim.cupsCopyDestInfo(aConnection, mPrinter);
446 // Even if we failed to fetch printer info, it is still possible we can talk
447 // to the print server and get its CUPS version.
448 FetchCUPSVersionForPrinter(mShim, mPrinter, lock->mCUPSMajor,
449 lock->mCUPSMinor, lock->mCUPSPatch);
450 return lock;
453 void nsPrinterCUPS::ForEachExtraMonochromeSetting(
454 FunctionRef<void(const nsACString&, const nsACString&)> aCallback) {
455 nsAutoCString pref;
456 Preferences::GetCString("print.cups.monochrome.extra_settings", pref);
457 if (pref.IsEmpty()) {
458 return;
461 for (const auto& pair : pref.Split(',')) {
462 auto splitter = pair.Split(':');
463 auto end = splitter.end();
465 auto key = splitter.begin();
466 if (key == end) {
467 continue;
470 auto value = ++splitter.begin();
471 if (value == end) {
472 continue;
475 aCallback(*key, *value);