diff --git a/libkcups/KCupsConnection.cpp b/libkcups/KCupsConnection.cpp index 7ea0274..c1a80d2 100644 --- a/libkcups/KCupsConnection.cpp +++ b/libkcups/KCupsConnection.cpp @@ -1,899 +1,899 @@ /*************************************************************************** * Copyright (C) 2010-2018 by Daniel Nicoletti * * dantti12@gmail.com * * Copyright (C) 2012 Harald Sitter * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "KCupsConnection.h" #include "Debug.h" #include "KCupsPasswordDialog.h" #include "KIppRequest.h" #include #include #include #include #include #include #include #define RENEW_INTERVAL 3500 #define SUBSCRIPTION_DURATION 3600 #define DBUS_SERVER_RESTARTED "server-restarted" // ServerRestarted #define DBUS_SERVER_STARTED "server-started" // ServerStarted #define DBUS_SERVER_STOPPED "server-stopped" // ServerStopped #define DBUS_SERVER_AUDIT "server-audit" // ServerAudit #define DBUS_PRINTER_RESTARTED "printer-restarted" // PrinterRestarted #define DBUS_PRINTER_SHUTDOWN "printer-shutdown" // PrinterShutdown #define DBUS_PRINTER_STOPPED "printer-stopped" // PrinterStopped #define DBUS_PRINTER_STATE_CHANGED "printer-state-changed" // PrinterStateChanged #define DBUS_PRINTER_FINISHINGS_CHANGED "printer-finishings-changed" // PrinterFinishingsChanged #define DBUS_PRINTER_MEDIA_CHANGED "printer-media-changed" // PrinterMediaChanged #define DBUS_PRINTER_ADDED "printer-added" // PrinterAdded #define DBUS_PRINTER_DELETED "printer-deleted" // PrinterDeleted #define DBUS_PRINTER_MODIFIED "printer-modified" // PrinterModified #define DBUS_JOB_STATE_CHANGED "job-state-changed" // JobState #define DBUS_JOB_CREATED "job-created" // JobCreated #define DBUS_JOB_COMPLETED "job-completed" // JobCompleted #define DBUS_JOB_STOPPED "job-stopped" // JobStopped #define DBUS_JOB_CONFIG_CHANGED "job-config-changed" // JobConfigChanged #define DBUS_JOB_PROGRESS "job-progress" // JobProgress Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QList) KCupsConnection* KCupsConnection::m_instance = nullptr; static int password_retries = 0; static int total_retries = 0; static int internalErrorCount = 0; const char * password_cb(const char *prompt, http_t *http, const char *method, const char *resource, void *user_data); KCupsConnection* KCupsConnection::global() { if (!m_instance) { m_instance = new KCupsConnection(qApp); } return m_instance; } KCupsConnection::KCupsConnection(QObject *parent) : QThread(parent) { init(); } KCupsConnection::KCupsConnection(const QUrl &server, QObject *parent) : QThread(parent), m_serverUrl(server) { qRegisterMetaType("KIppRequest"); init(); } KCupsConnection::~KCupsConnection() { if (m_instance == this) { m_instance = nullptr; } m_passwordDialog->deleteLater(); quit(); wait(); delete m_renewTimer; delete m_subscriptionTimer; } void KCupsConnection::setPasswordMainWindow(WId mainwindow) { m_passwordDialog->setMainWindow(mainwindow); } void KCupsConnection::init() { // Creating the dialog before start() will make it run on the gui thread m_passwordDialog = new KCupsPasswordDialog; // setup the DBus subscriptions // Server related signals // ServerStarted notifierConnect(QLatin1String("ServerStarted"), this, SIGNAL(serverStarted(QString))); // ServerStopped notifierConnect(QLatin1String("ServerStopped"), this, SIGNAL(serverStopped(QString))); // ServerRestarted notifierConnect(QLatin1String("ServerRestarted"), this, SIGNAL(serverRestarted(QString))); // ServerAudit notifierConnect(QLatin1String("ServerAudit"), this, SIGNAL(serverAudit(QString))); // Printer related signals // PrinterAdded notifierConnect(QLatin1String("PrinterAdded"), this, SIGNAL(printerAdded(QString,QString,QString,uint,QString,bool))); // PrinterModified notifierConnect(QLatin1String("PrinterModified"), this, SIGNAL(printerModified(QString,QString,QString,uint,QString,bool))); // PrinterDeleted notifierConnect(QLatin1String("PrinterDeleted"), this, SIGNAL(printerDeleted(QString,QString,QString,uint,QString,bool))); // PrinterStateChanged notifierConnect(QLatin1String("PrinterStateChanged"), this, SIGNAL(printerStateChanged(QString,QString,QString,uint,QString,bool))); // PrinterStopped notifierConnect(QLatin1String("PrinterStopped"), this, SIGNAL(printerStopped(QString,QString,QString,uint,QString,bool))); // PrinterShutdown notifierConnect(QLatin1String("PrinterShutdown"), this, SIGNAL(printerShutdown(QString,QString,QString,uint,QString,bool))); // PrinterRestarted notifierConnect(QLatin1String("PrinterRestarted"), this, SIGNAL(printerRestarted(QString,QString,QString,uint,QString,bool))); // PrinterMediaChanged notifierConnect(QLatin1String("PrinterMediaChanged"), this, SIGNAL(printerMediaChanged(QString,QString,QString,uint,QString,bool))); // PrinterFinishingsChanged notifierConnect(QLatin1String("PrinterFinishingsChanged"), this, SIGNAL(PrinterFinishingsChanged(QString,QString,QString,uint,QString,bool))); // Job related signals // JobState notifierConnect(QLatin1String("JobState"), this, SIGNAL(jobState(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // JobCreated notifierConnect(QLatin1String("JobCreated"), this, SIGNAL(jobCreated(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // JobStopped notifierConnect(QLatin1String("JobStopped"), this, SIGNAL(jobStopped(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // JobConfigChanged notifierConnect(QLatin1String("JobConfigChanged"), this, SIGNAL(jobConfigChanged(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // JobProgress notifierConnect(QLatin1String("JobProgress"), this, SIGNAL(jobProgress(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // JobCompleted notifierConnect(QLatin1String("JobCompleted"), this, SIGNAL(jobCompleted(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // This signal is needed since the cups registration thing // doesn't emit printerAdded when we add a printer class // This is emitted when a printer/queue is changed QDBusConnection::systemBus().connect(QLatin1String(""), QLatin1String("/com/redhat/PrinterSpooler"), QLatin1String("com.redhat.PrinterSpooler"), QLatin1String("PrinterAdded"), this, SIGNAL(rhPrinterAdded(QString))); // This signal is needed since the cups registration thing // sometimes simple stops working... don't ask me why // This is emitted when a printer/queue is changed QDBusConnection::systemBus().connect(QLatin1String(""), QLatin1String("/com/redhat/PrinterSpooler"), QLatin1String("com.redhat.PrinterSpooler"), QLatin1String("QueueChanged"), this, SIGNAL(rhQueueChanged(QString))); // This signal is needed since the cups registration thing // doesn't emit printerRemoved when we add a printer class // This is emitted when a printer/queue is changed QDBusConnection::systemBus().connect(QLatin1String(""), QLatin1String("/com/redhat/PrinterSpooler"), QLatin1String("com.redhat.PrinterSpooler"), QLatin1String("PrinterRemoved"), this, SIGNAL(rhPrinterRemoved(QString))); QDBusConnection::systemBus().connect(QLatin1String(""), QLatin1String("/com/redhat/PrinterSpooler"), QLatin1String("com.redhat.PrinterSpooler"), QLatin1String("JobQueuedLocal"), this, SIGNAL(rhJobQueuedLocal(QString,uint,QString))); QDBusConnection::systemBus().connect(QLatin1String(""), QLatin1String("/com/redhat/PrinterSpooler"), QLatin1String("com.redhat.PrinterSpooler"), QLatin1String("JobStartedLocal"), this, SIGNAL(rhJobStartedLocal(QString,uint,QString))); // Creates the timer that will renew the DBus subscription m_renewTimer = new QTimer; m_renewTimer->setInterval(RENEW_INTERVAL*1000); m_renewTimer->moveToThread(this); connect(m_renewTimer, &QTimer::timeout, this, static_cast(&KCupsConnection::renewDBusSubscription), Qt::DirectConnection); // Creates the timer to merge updates on the DBus subscription m_subscriptionTimer = new QTimer; m_subscriptionTimer->setInterval(0); m_subscriptionTimer->setSingleShot(true); m_subscriptionTimer->moveToThread(this); connect(m_subscriptionTimer, &QTimer::timeout, this, &KCupsConnection::updateSubscription, Qt::DirectConnection); // Starts this thread start(); } void KCupsConnection::run() { // Check if we need an special connection if (!m_serverUrl.isEmpty()) { if (m_serverUrl.port() < 0) { // TODO find out if there's a better way of hardcoding // the CUPS port m_serverUrl.setPort(631); } cupsSetServer(qUtf8Printable(m_serverUrl.authority())); } // This is dead cool, cups will call the thread_password_cb() // function when a password set is needed, as we passed the // password dialog pointer the functions just need to call // it on a blocking mode. cupsSetPasswordCB2(password_cb, m_passwordDialog); m_inited = true; exec(); // Event loop quit so cancelDBusSubscription() if (m_subscriptionId != -1) { cancelDBusSubscription(); } } bool KCupsConnection::readyToStart() { if (QThread::currentThread() == this) { password_retries = 0; total_retries = 0; internalErrorCount = 0; return true; } return false; } ReturnArguments KCupsConnection::request(const KIppRequest &request, ipp_tag_t groupTag) const { ReturnArguments ret; ipp_t *response = nullptr; do { ippDelete(response); response = nullptr; response = request.sendIppRequest(); } while (retry(qUtf8Printable(request.resource()), request.operation())); if (response && groupTag != IPP_TAG_ZERO) { ret = parseIPPVars(response, groupTag); } ippDelete(response); return ret; } int KCupsConnection::renewDBusSubscription(int subscriptionId, int leaseDuration, const QStringList &events) { int ret = -1; ipp_op_t operation; // check if we have a valid subscription ID if (subscriptionId >= 0) { // Add the "notify-events" values to the request operation = IPP_RENEW_SUBSCRIPTION; } else { operation = IPP_CREATE_PRINTER_SUBSCRIPTION; } KIppRequest request(operation, QLatin1String("/")); request.addString(IPP_TAG_OPERATION, IPP_TAG_URI, KCUPS_PRINTER_URI, QLatin1String("/")); request.addInteger(IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, KCUPS_NOTIFY_LEASE_DURATION, leaseDuration); if (operation == IPP_CREATE_PRINTER_SUBSCRIPTION) { // Add the "notify-events" values to the request request.addStringList(IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, KCUPS_NOTIFY_EVENTS, events); request.addString(IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, KCUPS_NOTIFY_PULL_METHOD, QLatin1String("ippget")); request.addString(IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, KCUPS_NOTIFY_RECIPIENT_URI, QLatin1String("dbus://")); } else { request.addInteger(IPP_TAG_OPERATION, IPP_TAG_INTEGER, KCUPS_NOTIFY_SUBSCRIPTION_ID, subscriptionId); } ipp_t *response = nullptr; do { // Do the request response = request.sendIppRequest(); } while (retry("/", operation)); #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) if (response && ippGetStatusCode(response) == IPP_OK) { #else if (response && response->request.status.status_code == IPP_OK) { #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) ipp_attribute_t *attr; if (subscriptionId >= 0) { // Request was ok, just return the current subscription ret = subscriptionId; } else if ((attr = ippFindAttribute(response, "notify-subscription-id", IPP_TAG_INTEGER)) == nullptr) { qCWarning(LIBKCUPS) << "No notify-subscription-id in response!"; ret = -1; } else { #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) ret = ippGetInteger(attr, 0); } } else if (subscriptionId >= 0 && response && ippGetStatusCode(response) == IPP_NOT_FOUND) { qCDebug(LIBKCUPS) << "Subscription not found"; // When the subscription is not found try to get a new one return renewDBusSubscription(-1, leaseDuration, events); #else ret = attr->values[0].integer; } } else if (subscriptionId >= 0 && response && response->request.status.status_code == IPP_NOT_FOUND) { qCDebug(LIBKCUPS) << "Subscription not found"; // When the subscription is not found try to get a new one return renewDBusSubscription(-1, leaseDuration, events); #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) } else { qCDebug(LIBKCUPS) << "Request failed" << cupsLastError() << httpGetStatus(CUPS_HTTP_DEFAULT); // When the server stops/restarts we will have some error so ignore it ret = subscriptionId; } ippDelete(response); return ret; } void KCupsConnection::notifierConnect(const QString &signal, QObject *receiver, const char *slot) { QDBusConnection systemBus = QDBusConnection::systemBus(); systemBus.connect(QString(), QStringLiteral("/org/cups/cupsd/Notifier"), QStringLiteral("org.cups.cupsd.Notifier"), signal, receiver, slot); } void KCupsConnection::connectNotify(const QMetaMethod & signal) { QMutexLocker locker(&m_mutex); QString event = eventForSignal(signal); if (!event.isNull()) { m_connectedEvents << event; QMetaObject::invokeMethod(m_subscriptionTimer, "start", Qt::QueuedConnection); } } void KCupsConnection::disconnectNotify(const QMetaMethod & signal) { QMutexLocker locker(&m_mutex); QString event = eventForSignal(signal); if (!event.isNull()) { m_connectedEvents.removeOne(event); QMetaObject::invokeMethod(m_subscriptionTimer, "start", Qt::QueuedConnection); } } QString KCupsConnection::eventForSignal(const QMetaMethod & signal) const { // Server signals if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverAudit)) { return QStringLiteral(DBUS_SERVER_AUDIT); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverStarted)) { return QStringLiteral(DBUS_SERVER_STARTED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverStopped)) { return QStringLiteral(DBUS_SERVER_STOPPED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverRestarted)) { return QStringLiteral(DBUS_SERVER_RESTARTED); } // Printer signals if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerAdded)) { return QStringLiteral(DBUS_PRINTER_ADDED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerDeleted)) { return QStringLiteral(DBUS_PRINTER_DELETED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerFinishingsChanged)) { return QStringLiteral(DBUS_PRINTER_FINISHINGS_CHANGED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerMediaChanged)) { return QStringLiteral(DBUS_PRINTER_MEDIA_CHANGED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerModified)) { return QStringLiteral(DBUS_PRINTER_MODIFIED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerRestarted)) { return QStringLiteral(DBUS_PRINTER_RESTARTED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerShutdown)) { return QStringLiteral(DBUS_PRINTER_SHUTDOWN); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerStateChanged)) { return QStringLiteral(DBUS_PRINTER_STATE_CHANGED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerStopped)) { return QStringLiteral(DBUS_PRINTER_STOPPED); } // job signals if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobCompleted)) { return QStringLiteral(DBUS_JOB_COMPLETED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobConfigChanged)) { return QStringLiteral(DBUS_JOB_CONFIG_CHANGED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobCreated)) { return QStringLiteral(DBUS_JOB_CREATED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobProgress)) { return QStringLiteral(DBUS_JOB_PROGRESS); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobState)) { return QStringLiteral(DBUS_JOB_STATE_CHANGED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobStopped)) { return QStringLiteral(DBUS_JOB_STOPPED); } // No registered event signal matched return QString(); } void KCupsConnection::updateSubscription() { QMutexLocker locker(&m_mutex); // Build the current list QStringList currentEvents = m_connectedEvents; currentEvents.sort(); currentEvents.removeDuplicates(); // Check if the requested events are already being asked if (m_requestedDBusEvents != currentEvents) { m_requestedDBusEvents = currentEvents; // If we already have a subscription lets cancel // and create a new one if (m_subscriptionId >= 0) { cancelDBusSubscription(); } // Canculates the new events renewDBusSubscription(); } } void KCupsConnection::renewDBusSubscription() { // check if we have a valid subscription ID if (m_subscriptionId >= 0) { m_subscriptionId = renewDBusSubscription(m_subscriptionId, SUBSCRIPTION_DURATION); } // The above request might fail if the subscription was cancelled if (m_subscriptionId < 0) { if (m_requestedDBusEvents.isEmpty()) { m_renewTimer->stop(); } else { m_subscriptionId = renewDBusSubscription(m_subscriptionId, SUBSCRIPTION_DURATION, m_requestedDBusEvents); m_renewTimer->start(); } } } void KCupsConnection::cancelDBusSubscription() { KIppRequest request(IPP_CANCEL_SUBSCRIPTION, QLatin1String("/")); request.addString(IPP_TAG_OPERATION, IPP_TAG_URI, KCUPS_PRINTER_URI, QLatin1String("/")); request.addInteger(IPP_TAG_OPERATION, IPP_TAG_INTEGER, KCUPS_NOTIFY_SUBSCRIPTION_ID, m_subscriptionId); do { // Do the request ippDelete(request.sendIppRequest()); } while (retry(qUtf8Printable(request.resource()), request.operation())); // Reset the subscription id m_subscriptionId = -1; } ReturnArguments KCupsConnection::parseIPPVars(ipp_t *response, ipp_tag_t group_tag) { ipp_attribute_t *attr; ReturnArguments ret; #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) QVariantHash destAttributes; for (attr = ippFirstAttribute(response); attr != nullptr; attr = ippNextAttribute(response)) { // We hit an attribute sepparator if (ippGetName(attr) == nullptr) { ret << destAttributes; destAttributes.clear(); continue; } // Skip leading attributes until we hit a a group which can be a printer, job... if (ippGetGroupTag(attr) != group_tag || (ippGetValueTag(attr) != IPP_TAG_INTEGER && ippGetValueTag(attr) != IPP_TAG_ENUM && ippGetValueTag(attr) != IPP_TAG_BOOLEAN && ippGetValueTag(attr) != IPP_TAG_TEXT && ippGetValueTag(attr) != IPP_TAG_TEXTLANG && ippGetValueTag(attr) != IPP_TAG_LANGUAGE && ippGetValueTag(attr) != IPP_TAG_NAME && ippGetValueTag(attr) != IPP_TAG_NAMELANG && ippGetValueTag(attr) != IPP_TAG_KEYWORD && ippGetValueTag(attr) != IPP_TAG_RANGE && ippGetValueTag(attr) != IPP_TAG_URI)) { continue; } // Add a printer description attribute... destAttributes[QString::fromUtf8(ippGetName(attr))] = ippAttrToVariant(attr); } if (!destAttributes.isEmpty()) { ret << destAttributes; } #else for (attr = response->attrs; attr != nullptr; attr = attr->next) { /* * Skip leading attributes until we hit a a group which can be a printer, job... */ while (attr && attr->group_tag != group_tag) { attr = attr->next; } if (attr == nullptr) { break; } /* * Pull the needed attributes from this printer... */ QVariantHash destAttributes; for (; attr && attr->group_tag == group_tag; attr = attr->next) { 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 && attr->value_tag != IPP_TAG_TEXTLANG && attr->value_tag != IPP_TAG_LANGUAGE && attr->value_tag != IPP_TAG_NAME && attr->value_tag != IPP_TAG_NAMELANG && attr->value_tag != IPP_TAG_KEYWORD && attr->value_tag != IPP_TAG_RANGE && attr->value_tag != IPP_TAG_URI) { continue; } /* * Add a printer description attribute... */ destAttributes[QString::fromUtf8(attr->name)] = ippAttrToVariant(attr); } ret << destAttributes; if (attr == nullptr) { break; } } #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) return ret; } QVariant KCupsConnection::ippAttrToVariant(ipp_attribute_t *attr) { QVariant ret; #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) switch (ippGetValueTag(attr)) { case IPP_TAG_INTEGER: case IPP_TAG_ENUM: if (ippGetCount(attr) == 1) { ret = ippGetInteger(attr, 0); } else { QList values; for (int i = 0; i < ippGetCount(attr); ++i) { values << ippGetInteger(attr, i); } ret = qVariantFromValue(values); } break; case IPP_TAG_BOOLEAN: if (ippGetCount(attr)== 1) { ret = ippGetBoolean(attr, 0); } else { QList values; for (int i = 0; i < ippGetCount(attr); ++i) { values << ippGetBoolean(attr, i); } ret = qVariantFromValue(values); } break; case IPP_TAG_RANGE: { QVariantList values; for (int i = 0; i < ippGetCount(attr); ++i) { int rangeUpper; values << ippGetRange(attr, i, &rangeUpper); values << rangeUpper; } ret = values; } break; default: if (ippGetCount(attr)== 1) { ret = QString::fromUtf8(ippGetString(attr, 0, nullptr)); } else { QStringList values; for (int i = 0; i < ippGetCount(attr); ++i) { values << QString::fromUtf8(ippGetString(attr, i, nullptr)); } ret = values; } } #else switch (attr->value_tag) { case IPP_TAG_INTEGER: case IPP_TAG_ENUM: if (attr->num_values == 1) { ret = attr->values[0].integer; } else { QList values; for (int i = 0; i < attr->num_values; ++i) { values << attr->values[i].integer; } ret = qVariantFromValue(values); } break; case IPP_TAG_BOOLEAN: if (attr->num_values == 1) { ret = static_cast(attr->values[0].integer); } else { QList values; for (int i = 0; i < attr->num_values; ++i) { values << static_cast(attr->values[i].integer); } ret = qVariantFromValue(values); } break; case IPP_TAG_RANGE: { QVariantList values; for (int i = 0; i < attr->num_values; ++i) { values << attr->values[i].range.lower; values << attr->values[i].range.upper; } ret = values; } break; default: if (attr->num_values == 1) { ret = QString::fromUtf8(attr->values[0].string.text); } else { QStringList values; for (int i = 0; i < attr->num_values; ++i) { values << QString::fromUtf8(attr->values[i].string.text); } ret = values; } } #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) return ret; } bool KCupsConnection::retry(const char *resource, int operation) const { ipp_status_t status = cupsLastError(); if (operation != -1) { qCDebug(LIBKCUPS) << ippOpString(static_cast(operation)) << "last error:" << status << cupsLastErrorString(); } else { qCDebug(LIBKCUPS) << operation << "last error:" << status << cupsLastErrorString(); } // When CUPS process stops our connection // with it fails and has to be re-established if (status == IPP_INTERNAL_ERROR) { // Deleting this connection thread forces it // to create a new CUPS connection qCWarning(LIBKCUPS) << "IPP_INTERNAL_ERROR: clearing cookies and reconnecting"; // TODO maybe reconnect is enough // httpClearCookie(CUPS_HTTP_DEFAULT); // Reconnect to CUPS if (httpReconnect(CUPS_HTTP_DEFAULT)) { qCWarning(LIBKCUPS) << "Failed to reconnect" << cupsLastErrorString(); // Server might be restarting sleep for a few ms msleep(500); } // Try the request again return ++internalErrorCount < 3; } total_retries++; if (total_retries > (password_retries + 3)) { // Something is wrong. // This will happen if the password_cb function is not called, // which will for example be the case if the server has // an IP blacklist and thus always return 403. // In this case, there is nothing we can do. return false; } bool forceAuth = false; // If our user is forbidden to perform the // task we try again using the root user // ONLY if it was the first time if (status == IPP_FORBIDDEN && password_retries == 0) { // Pretend to be the root user // Sometimes setting this just works cupsSetUser("root"); // force authentication forceAuth = true; } if (status == IPP_NOT_AUTHORIZED || status == IPP_NOT_AUTHENTICATED) { if (password_retries > 3 || password_retries == -1) { // the authentication failed 3 times // OR the dialog was canceld (-1) // reset to 0 and quit the do-while loop password_retries = 0; total_retries = 0; return false; } // force authentication forceAuth = true; } if (forceAuth) { // force authentication qCDebug(LIBKCUPS) << "Calling cupsDoAuthentication() password_retries:" << password_retries; int ret = cupsDoAuthentication(CUPS_HTTP_DEFAULT, "POST", resource); qCDebug(LIBKCUPS) << "Called cupsDoAuthentication(), success:" << (ret == -1 ? true : false); // If the authentication was succefull // sometimes just trying to be root works return ret == -1 ? true : false; } // the action was not forbidden return false; } const char * password_cb(const char *prompt, http_t *http, const char *method, const char *resource, void *user_data) { Q_UNUSED(http) Q_UNUSED(method) Q_UNUSED(resource) if (++password_retries > 3) { // cancel the authentication cupsSetUser(nullptr); return nullptr; } auto passwordDialog = static_cast(user_data); bool wrongPassword = password_retries > 1; // use prompt text from CUPS callback for dialog - passwordDialog->setPromptText(i18n("A CUPS connection requires authentication: \"") + QString::fromUtf8(prompt) + QStringLiteral("\"")); + passwordDialog->setPromptText(i18n("A CUPS connection requires authentication: \"%1\"", QString::fromUtf8(prompt))); // This will block this thread until exec is not finished qCDebug(LIBKCUPS) << password_retries; QMetaObject::invokeMethod(passwordDialog, "exec", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromUtf8(cupsUser())), Q_ARG(bool, wrongPassword)); qCDebug(LIBKCUPS) << passwordDialog->accepted(); // The password dialog has just returned check the result // method that returns QDialog enums if (passwordDialog->accepted()) { cupsSetUser(qUtf8Printable(passwordDialog->username())); return qUtf8Printable(passwordDialog->password()); } else { // the dialog was canceled password_retries = -1; cupsSetUser(nullptr); return nullptr; } } #include "moc_KCupsConnection.cpp"