File indexing completed on 2025-01-05 05:09:30

0001 /*
0002     SPDX-FileCopyrightText: 2010-2018 Daniel Nicoletti <dantti12@gmail.com>
0003     SPDX-FileCopyrightText: 2012 Harald Sitter <sitter@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "KCupsConnection.h"
0009 
0010 #include "KCupsPasswordDialog.h"
0011 #include "KIppRequest.h"
0012 #include "kcupslib_log.h"
0013 
0014 #include <config.h>
0015 
0016 #include <QByteArray>
0017 #include <QCoreApplication>
0018 #include <QDBusConnection>
0019 #include <QMutexLocker>
0020 
0021 #include <KLocalizedString>
0022 
0023 #define RENEW_INTERVAL 3500
0024 #define SUBSCRIPTION_DURATION 3600
0025 
0026 #define DBUS_SERVER_RESTARTED "server-restarted" // ServerRestarted
0027 #define DBUS_SERVER_STARTED "server-started" // ServerStarted
0028 #define DBUS_SERVER_STOPPED "server-stopped" // ServerStopped
0029 #define DBUS_SERVER_AUDIT "server-audit" // ServerAudit
0030 
0031 #define DBUS_PRINTER_RESTARTED "printer-restarted" // PrinterRestarted
0032 #define DBUS_PRINTER_SHUTDOWN "printer-shutdown" // PrinterShutdown
0033 #define DBUS_PRINTER_STOPPED "printer-stopped" // PrinterStopped
0034 #define DBUS_PRINTER_STATE_CHANGED "printer-state-changed" // PrinterStateChanged
0035 #define DBUS_PRINTER_FINISHINGS_CHANGED "printer-finishings-changed" // PrinterFinishingsChanged
0036 #define DBUS_PRINTER_MEDIA_CHANGED "printer-media-changed" // PrinterMediaChanged
0037 #define DBUS_PRINTER_ADDED "printer-added" // PrinterAdded
0038 #define DBUS_PRINTER_DELETED "printer-deleted" // PrinterDeleted
0039 #define DBUS_PRINTER_MODIFIED "printer-modified" // PrinterModified
0040 
0041 #define DBUS_JOB_STATE_CHANGED "job-state-changed" // JobState
0042 #define DBUS_JOB_CREATED "job-created" // JobCreated
0043 #define DBUS_JOB_COMPLETED "job-completed" // JobCompleted
0044 #define DBUS_JOB_STOPPED "job-stopped" // JobStopped
0045 #define DBUS_JOB_CONFIG_CHANGED "job-config-changed" // JobConfigChanged
0046 #define DBUS_JOB_PROGRESS "job-progress" // JobProgress
0047 
0048 Q_DECLARE_METATYPE(QList<int>)
0049 Q_DECLARE_METATYPE(QList<bool>)
0050 
0051 KCupsConnection *KCupsConnection::m_instance = nullptr;
0052 static int password_retries = 0;
0053 static int total_retries = 0;
0054 static int internalErrorCount = 0;
0055 const char *password_cb(const char *prompt, http_t *http, const char *method, const char *resource, void *user_data);
0056 
0057 KCupsConnection *KCupsConnection::global()
0058 {
0059     if (!m_instance) {
0060         m_instance = new KCupsConnection(qApp);
0061     }
0062 
0063     return m_instance;
0064 }
0065 
0066 KCupsConnection::KCupsConnection(QObject *parent)
0067     : QThread(parent)
0068 {
0069     init();
0070 }
0071 
0072 KCupsConnection::KCupsConnection(const QUrl &server, QObject *parent)
0073     : QThread(parent)
0074     , m_serverUrl(server)
0075 {
0076     qRegisterMetaType<KIppRequest>("KIppRequest");
0077     init();
0078 }
0079 
0080 KCupsConnection::~KCupsConnection()
0081 {
0082     if (m_instance == this) {
0083         m_instance = nullptr;
0084     }
0085     m_passwordDialog->deleteLater();
0086 
0087     quit();
0088     wait();
0089 
0090     delete m_renewTimer;
0091     delete m_subscriptionTimer;
0092 }
0093 
0094 void KCupsConnection::setPasswordMainWindow(WId mainwindow)
0095 {
0096     m_passwordDialog->setMainWindow(mainwindow);
0097 }
0098 
0099 void KCupsConnection::init()
0100 {
0101     // Creating the dialog before start() will make it run on the gui thread
0102     m_passwordDialog = new KCupsPasswordDialog;
0103 
0104     // setup the DBus subscriptions
0105 
0106     // Server related signals
0107     // ServerStarted
0108     notifierConnect(QLatin1String("ServerStarted"), this, SIGNAL(serverStarted(QString)));
0109 
0110     // ServerStopped
0111     notifierConnect(QLatin1String("ServerStopped"), this, SIGNAL(serverStopped(QString)));
0112 
0113     // ServerRestarted
0114     notifierConnect(QLatin1String("ServerRestarted"), this, SIGNAL(serverRestarted(QString)));
0115 
0116     // ServerAudit
0117     notifierConnect(QLatin1String("ServerAudit"), this, SIGNAL(serverAudit(QString)));
0118 
0119     // Printer related signals
0120     // PrinterAdded
0121     notifierConnect(QLatin1String("PrinterAdded"), this, SIGNAL(printerAdded(QString, QString, QString, uint, QString, bool)));
0122 
0123     // PrinterModified
0124     notifierConnect(QLatin1String("PrinterModified"), this, SIGNAL(printerModified(QString, QString, QString, uint, QString, bool)));
0125 
0126     // PrinterDeleted
0127     notifierConnect(QLatin1String("PrinterDeleted"), this, SIGNAL(printerDeleted(QString, QString, QString, uint, QString, bool)));
0128 
0129     // PrinterStateChanged
0130     notifierConnect(QLatin1String("PrinterStateChanged"), this, SIGNAL(printerStateChanged(QString, QString, QString, uint, QString, bool)));
0131 
0132     // PrinterStopped
0133     notifierConnect(QLatin1String("PrinterStopped"), this, SIGNAL(printerStopped(QString, QString, QString, uint, QString, bool)));
0134 
0135     // PrinterShutdown
0136     notifierConnect(QLatin1String("PrinterShutdown"), this, SIGNAL(printerShutdown(QString, QString, QString, uint, QString, bool)));
0137 
0138     // PrinterRestarted
0139     notifierConnect(QLatin1String("PrinterRestarted"), this, SIGNAL(printerRestarted(QString, QString, QString, uint, QString, bool)));
0140 
0141     // PrinterMediaChanged
0142     notifierConnect(QLatin1String("PrinterMediaChanged"), this, SIGNAL(printerMediaChanged(QString, QString, QString, uint, QString, bool)));
0143 
0144     // PrinterFinishingsChanged
0145     notifierConnect(QLatin1String("PrinterFinishingsChanged"), this, SIGNAL(PrinterFinishingsChanged(QString, QString, QString, uint, QString, bool)));
0146 
0147     // Job related signals
0148     // JobState
0149     notifierConnect(QLatin1String("JobState"), this, SIGNAL(jobState(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint)));
0150 
0151     // JobCreated
0152     notifierConnect(QLatin1String("JobCreated"), this, SIGNAL(jobCreated(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint)));
0153 
0154     // JobStopped
0155     notifierConnect(QLatin1String("JobStopped"), this, SIGNAL(jobStopped(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint)));
0156 
0157     // JobConfigChanged
0158     notifierConnect(QLatin1String("JobConfigChanged"),
0159                     this,
0160                     SIGNAL(jobConfigChanged(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint)));
0161 
0162     // JobProgress
0163     notifierConnect(QLatin1String("JobProgress"),
0164                     this,
0165                     SIGNAL(jobProgress(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint)));
0166 
0167     // JobCompleted
0168     notifierConnect(QLatin1String("JobCompleted"),
0169                     this,
0170                     SIGNAL(jobCompleted(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint)));
0171 
0172     // This signal is needed since the cups registration thing
0173     // doesn't Q_EMIT printerAdded when we add a printer class
0174     // This is emitted when a printer/queue is changed
0175     QDBusConnection::systemBus().connect(QLatin1String(""),
0176                                          QLatin1String("/com/redhat/PrinterSpooler"),
0177                                          QLatin1String("com.redhat.PrinterSpooler"),
0178                                          QLatin1String("PrinterAdded"),
0179                                          this,
0180                                          SIGNAL(rhPrinterAdded(QString)));
0181 
0182     // This signal is needed since the cups registration thing
0183     // sometimes simple stops working... don't ask me why
0184     // This is emitted when a printer/queue is changed
0185     QDBusConnection::systemBus().connect(QLatin1String(""),
0186                                          QLatin1String("/com/redhat/PrinterSpooler"),
0187                                          QLatin1String("com.redhat.PrinterSpooler"),
0188                                          QLatin1String("QueueChanged"),
0189                                          this,
0190                                          SIGNAL(rhQueueChanged(QString)));
0191 
0192     // This signal is needed since the cups registration thing
0193     // doesn't Q_EMIT printerRemoved when we add a printer class
0194     // This is emitted when a printer/queue is changed
0195     QDBusConnection::systemBus().connect(QLatin1String(""),
0196                                          QLatin1String("/com/redhat/PrinterSpooler"),
0197                                          QLatin1String("com.redhat.PrinterSpooler"),
0198                                          QLatin1String("PrinterRemoved"),
0199                                          this,
0200                                          SIGNAL(rhPrinterRemoved(QString)));
0201 
0202     QDBusConnection::systemBus().connect(QLatin1String(""),
0203                                          QLatin1String("/com/redhat/PrinterSpooler"),
0204                                          QLatin1String("com.redhat.PrinterSpooler"),
0205                                          QLatin1String("JobQueuedLocal"),
0206                                          this,
0207                                          SIGNAL(rhJobQueuedLocal(QString, uint, QString)));
0208 
0209     QDBusConnection::systemBus().connect(QLatin1String(""),
0210                                          QLatin1String("/com/redhat/PrinterSpooler"),
0211                                          QLatin1String("com.redhat.PrinterSpooler"),
0212                                          QLatin1String("JobStartedLocal"),
0213                                          this,
0214                                          SIGNAL(rhJobStartedLocal(QString, uint, QString)));
0215 
0216     // Creates the timer that will renew the DBus subscription
0217     m_renewTimer = new QTimer;
0218     m_renewTimer->setInterval(RENEW_INTERVAL * 1000);
0219     m_renewTimer->moveToThread(this);
0220     connect(m_renewTimer, &QTimer::timeout, this, static_cast<void (KCupsConnection::*)()>(&KCupsConnection::renewDBusSubscription), Qt::DirectConnection);
0221 
0222     // Creates the timer to merge updates on the DBus subscription
0223     m_subscriptionTimer = new QTimer;
0224     m_subscriptionTimer->setInterval(0);
0225     m_subscriptionTimer->setSingleShot(true);
0226     m_subscriptionTimer->moveToThread(this);
0227     connect(m_subscriptionTimer, &QTimer::timeout, this, &KCupsConnection::updateSubscription, Qt::DirectConnection);
0228 
0229     // Starts this thread
0230     start();
0231 }
0232 
0233 void KCupsConnection::run()
0234 {
0235     // Check if we need an special connection
0236     if (!m_serverUrl.isEmpty()) {
0237         if (m_serverUrl.port() < 0) {
0238             // TODO find out if there's a better way of hardcoding
0239             // the CUPS port
0240             m_serverUrl.setPort(631);
0241         }
0242 
0243         cupsSetServer(qUtf8Printable(m_serverUrl.authority()));
0244     }
0245 
0246     // This is dead cool, cups will call the thread_password_cb()
0247     // function when a password set is needed, as we passed the
0248     // password dialog pointer the functions just need to call
0249     // it on a blocking mode.
0250     cupsSetPasswordCB2(password_cb, m_passwordDialog);
0251 
0252     m_inited = true;
0253     exec();
0254 
0255     // Event loop quit so cancelDBusSubscription()
0256     if (m_subscriptionId != -1) {
0257         cancelDBusSubscription();
0258     }
0259 }
0260 
0261 bool KCupsConnection::readyToStart()
0262 {
0263     if (QThread::currentThread() == this) {
0264         password_retries = 0;
0265         total_retries = 0;
0266         internalErrorCount = 0;
0267         return true;
0268     }
0269     return false;
0270 }
0271 
0272 ReturnArguments KCupsConnection::request(const KIppRequest &request, ipp_tag_t groupTag) const
0273 {
0274     ReturnArguments ret;
0275     ipp_t *response = nullptr;
0276     do {
0277         ippDelete(response);
0278         response = nullptr;
0279 
0280         response = request.sendIppRequest();
0281     } while (retry(qUtf8Printable(request.resource()), request.operation()));
0282 
0283     if (response && groupTag != IPP_TAG_ZERO) {
0284         ret = parseIPPVars(response, groupTag);
0285     }
0286     ippDelete(response);
0287 
0288     return ret;
0289 }
0290 
0291 int KCupsConnection::renewDBusSubscription(int subscriptionId, int leaseDuration, const QStringList &events)
0292 {
0293     int ret = -1;
0294 
0295     ipp_op_t operation;
0296 
0297     // check if we have a valid subscription ID
0298     if (subscriptionId >= 0) {
0299         // Add the "notify-events" values to the request
0300         operation = IPP_RENEW_SUBSCRIPTION;
0301     } else {
0302         operation = IPP_CREATE_PRINTER_SUBSCRIPTION;
0303     }
0304 
0305     KIppRequest request(operation, QLatin1String("/"));
0306     request.addString(IPP_TAG_OPERATION, IPP_TAG_URI, KCUPS_PRINTER_URI, QLatin1String("/"));
0307     request.addInteger(IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, KCUPS_NOTIFY_LEASE_DURATION, leaseDuration);
0308 
0309     if (operation == IPP_CREATE_PRINTER_SUBSCRIPTION) {
0310         // Add the "notify-events" values to the request
0311         request.addStringList(IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, KCUPS_NOTIFY_EVENTS, events);
0312         request.addString(IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, KCUPS_NOTIFY_PULL_METHOD, QLatin1String("ippget"));
0313         request.addString(IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, KCUPS_NOTIFY_RECIPIENT_URI, QLatin1String("dbus://"));
0314     } else {
0315         request.addInteger(IPP_TAG_OPERATION, IPP_TAG_INTEGER, KCUPS_NOTIFY_SUBSCRIPTION_ID, subscriptionId);
0316     }
0317 
0318     ipp_t *response = nullptr;
0319     do {
0320         // Do the request
0321         response = request.sendIppRequest();
0322     } while (retry("/", operation));
0323 
0324 #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6)
0325     if (response && ippGetStatusCode(response) == IPP_OK) {
0326 #else
0327     if (response && response->request.status.status_code == IPP_OK) {
0328 #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6)
0329         ipp_attribute_t *attr;
0330         if (subscriptionId >= 0) {
0331             // Request was ok, just return the current subscription
0332             ret = subscriptionId;
0333         } else if ((attr = ippFindAttribute(response, "notify-subscription-id", IPP_TAG_INTEGER)) == nullptr) {
0334             qCWarning(LIBKCUPS) << "No notify-subscription-id in response!";
0335             ret = -1;
0336         } else {
0337 #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6)
0338             ret = ippGetInteger(attr, 0);
0339         }
0340     } else if (subscriptionId >= 0 && response && ippGetStatusCode(response) == IPP_NOT_FOUND) {
0341         qCDebug(LIBKCUPS) << "Subscription not found";
0342         // When the subscription is not found try to get a new one
0343         return renewDBusSubscription(-1, leaseDuration, events);
0344 #else
0345             ret = attr->values[0].integer;
0346         }
0347     } else if (subscriptionId >= 0 && response && response->request.status.status_code == IPP_NOT_FOUND) {
0348         qCDebug(LIBKCUPS) << "Subscription not found";
0349         // When the subscription is not found try to get a new one
0350         return renewDBusSubscription(-1, leaseDuration, events);
0351 #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6)
0352     } else {
0353         qCDebug(LIBKCUPS) << "Request failed" << cupsLastError() << httpGetStatus(CUPS_HTTP_DEFAULT);
0354         // When the server stops/restarts we will have some error so ignore it
0355         ret = subscriptionId;
0356     }
0357 
0358     ippDelete(response);
0359 
0360     return ret;
0361 }
0362 
0363 void KCupsConnection::notifierConnect(const QString &signal, QObject *receiver, const char *slot)
0364 {
0365     QDBusConnection systemBus = QDBusConnection::systemBus();
0366     systemBus.connect(QString(), QStringLiteral("/org/cups/cupsd/Notifier"), QStringLiteral("org.cups.cupsd.Notifier"), signal, receiver, slot);
0367 }
0368 
0369 void KCupsConnection::connectNotify(const QMetaMethod &signal)
0370 {
0371     QMutexLocker locker(&m_mutex);
0372     QString event = eventForSignal(signal);
0373     if (!event.isNull()) {
0374         m_connectedEvents << event;
0375         QMetaObject::invokeMethod(m_subscriptionTimer, "start", Qt::QueuedConnection);
0376     }
0377 }
0378 
0379 void KCupsConnection::disconnectNotify(const QMetaMethod &signal)
0380 {
0381     QMutexLocker locker(&m_mutex);
0382     QString event = eventForSignal(signal);
0383     if (!event.isNull()) {
0384         m_connectedEvents.removeOne(event);
0385         QMetaObject::invokeMethod(m_subscriptionTimer, "start", Qt::QueuedConnection);
0386     }
0387 }
0388 
0389 QString KCupsConnection::eventForSignal(const QMetaMethod &signal) const
0390 {
0391     // Server signals
0392     if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverAudit)) {
0393         return QStringLiteral(DBUS_SERVER_AUDIT);
0394     }
0395     if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverStarted)) {
0396         return QStringLiteral(DBUS_SERVER_STARTED);
0397     }
0398     if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverStopped)) {
0399         return QStringLiteral(DBUS_SERVER_STOPPED);
0400     }
0401     if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverRestarted)) {
0402         return QStringLiteral(DBUS_SERVER_RESTARTED);
0403     }
0404 
0405     // Printer signals
0406     if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerAdded)) {
0407         return QStringLiteral(DBUS_PRINTER_ADDED);
0408     }
0409     if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerDeleted)) {
0410         return QStringLiteral(DBUS_PRINTER_DELETED);
0411     }
0412     if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerFinishingsChanged)) {
0413         return QStringLiteral(DBUS_PRINTER_FINISHINGS_CHANGED);
0414     }
0415     if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerMediaChanged)) {
0416         return QStringLiteral(DBUS_PRINTER_MEDIA_CHANGED);
0417     }
0418     if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerModified)) {
0419         return QStringLiteral(DBUS_PRINTER_MODIFIED);
0420     }
0421     if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerRestarted)) {
0422         return QStringLiteral(DBUS_PRINTER_RESTARTED);
0423     }
0424     if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerShutdown)) {
0425         return QStringLiteral(DBUS_PRINTER_SHUTDOWN);
0426     }
0427     if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerStateChanged)) {
0428         return QStringLiteral(DBUS_PRINTER_STATE_CHANGED);
0429     }
0430     if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerStopped)) {
0431         return QStringLiteral(DBUS_PRINTER_STOPPED);
0432     }
0433 
0434     // job signals
0435     if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobCompleted)) {
0436         return QStringLiteral(DBUS_JOB_COMPLETED);
0437     }
0438     if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobConfigChanged)) {
0439         return QStringLiteral(DBUS_JOB_CONFIG_CHANGED);
0440     }
0441     if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobCreated)) {
0442         return QStringLiteral(DBUS_JOB_CREATED);
0443     }
0444     if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobProgress)) {
0445         return QStringLiteral(DBUS_JOB_PROGRESS);
0446     }
0447     if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobState)) {
0448         return QStringLiteral(DBUS_JOB_STATE_CHANGED);
0449     }
0450     if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobStopped)) {
0451         return QStringLiteral(DBUS_JOB_STOPPED);
0452     }
0453 
0454     // No registered event signal matched
0455     return QString();
0456 }
0457 
0458 void KCupsConnection::updateSubscription()
0459 {
0460     QMutexLocker locker(&m_mutex);
0461     // Build the current list
0462     QStringList currentEvents = m_connectedEvents;
0463     currentEvents.sort();
0464     currentEvents.removeDuplicates();
0465 
0466     // Check if the requested events are already being asked
0467     if (m_requestedDBusEvents != currentEvents) {
0468         m_requestedDBusEvents = currentEvents;
0469 
0470         // If we already have a subscription lets cancel
0471         // and create a new one
0472         if (m_subscriptionId >= 0) {
0473             cancelDBusSubscription();
0474         }
0475 
0476         // Calculates the new events
0477         renewDBusSubscription();
0478     }
0479 }
0480 
0481 void KCupsConnection::renewDBusSubscription()
0482 {
0483     // check if we have a valid subscription ID
0484     if (m_subscriptionId >= 0) {
0485         m_subscriptionId = renewDBusSubscription(m_subscriptionId, SUBSCRIPTION_DURATION);
0486     }
0487 
0488     // The above request might fail if the subscription was cancelled
0489     if (m_subscriptionId < 0) {
0490         if (m_requestedDBusEvents.isEmpty()) {
0491             m_renewTimer->stop();
0492         } else {
0493             m_subscriptionId = renewDBusSubscription(m_subscriptionId, SUBSCRIPTION_DURATION, m_requestedDBusEvents);
0494             m_renewTimer->start();
0495         }
0496     }
0497 }
0498 
0499 void KCupsConnection::cancelDBusSubscription()
0500 {
0501     KIppRequest request(IPP_CANCEL_SUBSCRIPTION, QLatin1String("/"));
0502     request.addString(IPP_TAG_OPERATION, IPP_TAG_URI, KCUPS_PRINTER_URI, QLatin1String("/"));
0503     request.addInteger(IPP_TAG_OPERATION, IPP_TAG_INTEGER, KCUPS_NOTIFY_SUBSCRIPTION_ID, m_subscriptionId);
0504 
0505     do {
0506         // Do the request
0507         ippDelete(request.sendIppRequest());
0508     } while (retry(qUtf8Printable(request.resource()), request.operation()));
0509 
0510     // Reset the subscription id
0511     m_subscriptionId = -1;
0512 }
0513 
0514 ReturnArguments KCupsConnection::parseIPPVars(ipp_t *response, ipp_tag_t group_tag)
0515 {
0516     ipp_attribute_t *attr;
0517     ReturnArguments ret;
0518 
0519 #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6)
0520     QVariantMap destAttributes;
0521     for (attr = ippFirstAttribute(response); attr != nullptr; attr = ippNextAttribute(response)) {
0522         // We hit an attribute separator
0523         if (ippGetName(attr) == nullptr) {
0524             ret << destAttributes;
0525             destAttributes.clear();
0526             continue;
0527         }
0528 
0529         // Skip leading attributes until we hit a group which can be a printer, job...
0530         if (ippGetGroupTag(attr) != group_tag
0531             || (ippGetValueTag(attr) != IPP_TAG_INTEGER && ippGetValueTag(attr) != IPP_TAG_ENUM && ippGetValueTag(attr) != IPP_TAG_BOOLEAN
0532                 && ippGetValueTag(attr) != IPP_TAG_TEXT && ippGetValueTag(attr) != IPP_TAG_TEXTLANG && ippGetValueTag(attr) != IPP_TAG_LANGUAGE
0533                 && ippGetValueTag(attr) != IPP_TAG_NAME && ippGetValueTag(attr) != IPP_TAG_NAMELANG && ippGetValueTag(attr) != IPP_TAG_KEYWORD
0534                 && ippGetValueTag(attr) != IPP_TAG_RANGE && ippGetValueTag(attr) != IPP_TAG_URI)) {
0535             continue;
0536         }
0537 
0538         // Add a printer description attribute...
0539         destAttributes[QString::fromUtf8(ippGetName(attr))] = ippAttrToVariant(attr);
0540     }
0541 
0542     if (!destAttributes.isEmpty()) {
0543         ret << destAttributes;
0544     }
0545 #else
0546     for (attr = response->attrs; attr != nullptr; attr = attr->next) {
0547         /*
0548          * Skip leading attributes until we hit a group which can be a printer, job...
0549          */
0550         while (attr && attr->group_tag != group_tag) {
0551             attr = attr->next;
0552         }
0553 
0554         if (attr == nullptr) {
0555             break;
0556         }
0557 
0558         /*
0559          * Pull the needed attributes from this printer...
0560          */
0561         QVariantMap destAttributes;
0562         for (; attr && attr->group_tag == group_tag; attr = attr->next) {
0563             if (attr->value_tag != IPP_TAG_INTEGER && attr->value_tag != IPP_TAG_ENUM && attr->value_tag != IPP_TAG_BOOLEAN && attr->value_tag != IPP_TAG_TEXT
0564                 && attr->value_tag != IPP_TAG_TEXTLANG && attr->value_tag != IPP_TAG_LANGUAGE && attr->value_tag != IPP_TAG_NAME
0565                 && attr->value_tag != IPP_TAG_NAMELANG && attr->value_tag != IPP_TAG_KEYWORD && attr->value_tag != IPP_TAG_RANGE
0566                 && attr->value_tag != IPP_TAG_URI) {
0567                 continue;
0568             }
0569 
0570             /*
0571              * Add a printer description attribute...
0572              */
0573             destAttributes[QString::fromUtf8(attr->name)] = ippAttrToVariant(attr);
0574         }
0575 
0576         ret << destAttributes;
0577 
0578         if (attr == nullptr) {
0579             break;
0580         }
0581     }
0582 #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6)
0583 
0584     return ret;
0585 }
0586 
0587 QVariant KCupsConnection::ippAttrToVariant(ipp_attribute_t *attr)
0588 {
0589     QVariant ret;
0590 #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6)
0591     switch (ippGetValueTag(attr)) {
0592     case IPP_TAG_INTEGER:
0593     case IPP_TAG_ENUM:
0594         if (ippGetCount(attr) == 1) {
0595             ret = ippGetInteger(attr, 0);
0596         } else {
0597             QList<int> values;
0598             for (int i = 0; i < ippGetCount(attr); ++i) {
0599                 values << ippGetInteger(attr, i);
0600             }
0601             ret = QVariant::fromValue(values);
0602         }
0603         break;
0604     case IPP_TAG_BOOLEAN:
0605         if (ippGetCount(attr) == 1) {
0606             ret = ippGetBoolean(attr, 0);
0607         } else {
0608             QList<bool> values;
0609             for (int i = 0; i < ippGetCount(attr); ++i) {
0610                 values << ippGetBoolean(attr, i);
0611             }
0612             ret = QVariant::fromValue(values);
0613         }
0614         break;
0615     case IPP_TAG_RANGE: {
0616         QVariantList values;
0617         for (int i = 0; i < ippGetCount(attr); ++i) {
0618             int rangeUpper;
0619             values << ippGetRange(attr, i, &rangeUpper);
0620             values << rangeUpper;
0621         }
0622         ret = values;
0623     } break;
0624     default:
0625         if (ippGetCount(attr) == 1) {
0626             ret = QString::fromUtf8(ippGetString(attr, 0, nullptr));
0627         } else {
0628             QStringList values;
0629             for (int i = 0; i < ippGetCount(attr); ++i) {
0630                 values << QString::fromUtf8(ippGetString(attr, i, nullptr));
0631             }
0632             ret = values;
0633         }
0634     }
0635 #else
0636     switch (attr->value_tag) {
0637     case IPP_TAG_INTEGER:
0638     case IPP_TAG_ENUM:
0639         if (attr->num_values == 1) {
0640             ret = attr->values[0].integer;
0641         } else {
0642             QList<int> values;
0643             for (int i = 0; i < attr->num_values; ++i) {
0644                 values << attr->values[i].integer;
0645             }
0646             ret = QVariant::fromValue(values);
0647         }
0648         break;
0649     case IPP_TAG_BOOLEAN:
0650         if (attr->num_values == 1) {
0651             ret = static_cast<bool>(attr->values[0].integer);
0652         } else {
0653             QList<bool> values;
0654             for (int i = 0; i < attr->num_values; ++i) {
0655                 values << static_cast<bool>(attr->values[i].integer);
0656             }
0657             ret = QVariant::fromValue(values);
0658         }
0659         break;
0660     case IPP_TAG_RANGE: {
0661         QVariantList values;
0662         for (int i = 0; i < attr->num_values; ++i) {
0663             values << attr->values[i].range.lower;
0664             values << attr->values[i].range.upper;
0665         }
0666         ret = values;
0667     } break;
0668     default:
0669         if (attr->num_values == 1) {
0670             ret = QString::fromUtf8(attr->values[0].string.text);
0671         } else {
0672             QStringList values;
0673             for (int i = 0; i < attr->num_values; ++i) {
0674                 values << QString::fromUtf8(attr->values[i].string.text);
0675             }
0676             ret = values;
0677         }
0678     }
0679 #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6)
0680     return ret;
0681 }
0682 
0683 bool KCupsConnection::retry(const char *resource, int operation) const
0684 {
0685     ipp_status_t status = cupsLastError();
0686 
0687     if (operation != -1) {
0688         qCDebug(LIBKCUPS) << ippOpString(static_cast<ipp_op_t>(operation)) << "last error:" << status << cupsLastErrorString();
0689     } else {
0690         qCDebug(LIBKCUPS) << operation << "last error:" << status << cupsLastErrorString();
0691     }
0692 
0693     // When CUPS process stops our connection
0694     // with it fails and has to be re-established
0695     if (status == IPP_INTERNAL_ERROR) {
0696         // Deleting this connection thread forces it
0697         // to create a new CUPS connection
0698         qCWarning(LIBKCUPS) << "IPP_INTERNAL_ERROR: clearing cookies and reconnecting";
0699 
0700         // Reconnect to CUPS
0701         int cancel = 0;
0702         if (httpReconnect2(CUPS_HTTP_DEFAULT, 10000, &cancel)) {
0703             qCWarning(LIBKCUPS) << "Failed to reconnect" << cupsLastErrorString();
0704         }
0705 
0706         // Try the request again
0707         return ++internalErrorCount < 3;
0708     }
0709 
0710     total_retries++;
0711 
0712     if (total_retries > (password_retries + 3)) {
0713         // Something is wrong.
0714         // This will happen if the password_cb function is not called,
0715         // which will for example be the case if the server has
0716         // an IP blacklist and thus always return 403.
0717         // In this case, there is nothing we can do.
0718         return false;
0719     }
0720 
0721     bool forceAuth = false;
0722     // If our user is forbidden to perform the
0723     // task we try again using the root user
0724     // ONLY if it was the first time
0725     if (status == IPP_FORBIDDEN && password_retries == 0) {
0726         // Pretend to be the root user
0727         // Sometimes setting this just works
0728         cupsSetUser("root");
0729 
0730         // force authentication
0731         forceAuth = true;
0732     }
0733 
0734     if (status == IPP_NOT_AUTHORIZED || status == IPP_NOT_AUTHENTICATED) {
0735         if (password_retries > 3 || password_retries == -1) {
0736             // the authentication failed 3 times
0737             // OR the dialog was canceled (-1)
0738             // reset to 0 and quit the do-while loop
0739             password_retries = 0;
0740             total_retries = 0;
0741             return false;
0742         }
0743 
0744         // force authentication
0745         forceAuth = true;
0746     }
0747 
0748     if (forceAuth) {
0749         // force authentication
0750         int ret = cupsDoAuthentication(CUPS_HTTP_DEFAULT, "POST", resource);
0751         qCWarning(LIBKCUPS) << "cupsDoAuthentication, return:" << ret << "password_retries:" << password_retries;
0752 
0753         // If the authentication was successful
0754         // sometimes just trying to be root works
0755         return ret != 0;
0756     }
0757 
0758     // the action was not forbidden
0759     return false;
0760 }
0761 
0762 const char *password_cb(const char *prompt, http_t *http, const char *method, const char *resource, void *user_data)
0763 {
0764     Q_UNUSED(http)
0765     Q_UNUSED(method)
0766     Q_UNUSED(resource)
0767 
0768     if (++password_retries > 3) {
0769         // cancel the authentication
0770         cupsSetUser(nullptr);
0771         return nullptr;
0772     }
0773 
0774     auto passwordDialog = static_cast<KCupsPasswordDialog *>(user_data);
0775     bool wrongPassword = password_retries > 1;
0776 
0777     // use prompt text from CUPS callback for dialog
0778     passwordDialog->setPromptText(i18n("A CUPS connection requires authentication: \"%1\"", QString::fromUtf8(prompt)));
0779 
0780     // This will block this thread until exec is not finished
0781     qCDebug(LIBKCUPS) << password_retries;
0782     QMetaObject::invokeMethod(passwordDialog, "exec", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromUtf8(cupsUser())), Q_ARG(bool, wrongPassword));
0783     qCDebug(LIBKCUPS) << passwordDialog->accepted();
0784 
0785     // The password dialog has just returned check the result
0786     // method that returns QDialog enums
0787     if (passwordDialog->accepted()) {
0788         cupsSetUser(qUtf8Printable(passwordDialog->username()));
0789         return qUtf8Printable(passwordDialog->password());
0790     } else {
0791         // the dialog was canceled
0792         password_retries = -1;
0793         cupsSetUser(nullptr);
0794         return nullptr;
0795     }
0796 }
0797 
0798 #include "moc_KCupsConnection.cpp"