~ci-train-bot/ubuntu-ui-extras/ubuntu-ui-extras-ubuntu-zesty-2515

« back to all changes in this revision

Viewing changes to modules/Ubuntu/Components/Extras/Printers/cups/ippclient.cpp

  • Committer: Andrew Hayzen
  • Date: 2017-02-21 10:46:29 UTC
  • Revision ID: ahayzen@gmail.com-20170221104629-pbm454x5k7rr4ot5
* Add printer-components from ubuntu-settings-components to ubuntu-ui-extras (original branch https://code.launchpad.net/~phablet-team/ubuntu-settings-components/printer-components)
* Add plugin module to Extras/Printers
* Add translation support for cpp/h
* Add tests for Printers
* Add debian depends

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2017 Canonical, Ltd.
 
3
 * Copyright (C) 2014 John Layt <jlayt@kde.org>
 
4
 * Copyright (C) 2002, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014 Red Hat, Inc.
 
5
 * Copyright (C) 2008 Novell, Inc.
 
6
 *
 
7
 * This program is free software; you can redistribute it and/or modify
 
8
 * it under the terms of the GNU Lesser General Public License as published by
 
9
 * the Free Software Foundation; version 3.
 
10
 *
 
11
 * This program is distributed in the hope that it will be useful,
 
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
 * GNU Lesser General Public License for more details.
 
15
 *
 
16
 * You should have received a copy of the GNU Lesser General Public License
 
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
18
 */
 
19
 
 
20
#include "cups/ippclient.h"
 
21
 
 
22
#include <errno.h>
 
23
#include <string.h>
 
24
#include <unistd.h>
 
25
 
 
26
#include <QDebug>
 
27
#include <QDateTime>
 
28
#include <QTimeZone>
 
29
#include <QUrl>
 
30
 
 
31
IppClient::IppClient()
 
32
    : m_connection(httpConnectEncrypt(cupsServer(),
 
33
                                      ippPort(),
 
34
                                      cupsEncryption()))
 
35
{
 
36
    if (!m_connection) {
 
37
        qCritical("Failed to connect to cupsd");
 
38
    } else {
 
39
        qDebug("Successfully connected to cupsd.");
 
40
    }
 
41
}
 
42
 
 
43
IppClient::~IppClient()
 
44
{
 
45
    if (m_connection)
 
46
        httpClose(m_connection);
 
47
}
 
48
 
 
49
bool IppClient::printerDelete(const QString &printerName)
 
50
{
 
51
    return sendNewSimpleRequest(CUPS_DELETE_PRINTER, printerName.toUtf8(),
 
52
                                CupsResource::CupsResourceAdmin);
 
53
}
 
54
 
 
55
bool IppClient::printerAdd(const QString &printerName,
 
56
                           const QString &printerUri,
 
57
                           const QString &ppdFile,
 
58
                           const QString &info,
 
59
                           const QString &location)
 
60
{
 
61
    ipp_t *request;
 
62
 
 
63
    if (!isPrinterNameValid(printerName)) {
 
64
        setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
 
65
        return false;
 
66
    }
 
67
 
 
68
    if (!isStringValid(info)) {
 
69
        setInternalStatus(QString("%1 is not a valid description.").arg(info));
 
70
        return false;
 
71
    }
 
72
 
 
73
    if (!isStringValid(location)) {
 
74
        setInternalStatus(QString("%1 is not a valid location.").arg(location));
 
75
        return false;
 
76
    }
 
77
 
 
78
    if (!isStringValid(ppdFile)) {
 
79
        setInternalStatus(QString("%1 is not a valid ppd file.").arg(ppdFile));
 
80
        return false;
 
81
    }
 
82
 
 
83
    if (!isStringValid(printerUri)) {
 
84
        setInternalStatus(QString("%1 is not a valid printer uri.").arg(printerUri));
 
85
        return false;
 
86
    }
 
87
 
 
88
 
 
89
    request = ippNewRequest (CUPS_ADD_MODIFY_PRINTER);
 
90
    addPrinterUri(request, printerName);
 
91
    addRequestingUsername(request, NULL);
 
92
 
 
93
    ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
 
94
                 "printer-name", NULL, printerName.toUtf8());
 
95
 
 
96
    if (!ppdFile.isEmpty()) {
 
97
        ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
 
98
                     "ppd-name", NULL, ppdFile.toUtf8());
 
99
    }
 
100
    if (!printerUri.isEmpty()) {
 
101
        ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI,
 
102
                     "device-uri", NULL, printerUri.toUtf8());
 
103
    }
 
104
    if (!info.isEmpty()) {
 
105
        ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
 
106
                     "printer-info", NULL, info.toUtf8());
 
107
    }
 
108
    if (!location.isEmpty()) {
 
109
        ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
 
110
                     "printer-location", NULL, location.toUtf8());
 
111
    }
 
112
 
 
113
    return sendRequest(request, CupsResourceAdmin);
 
114
}
 
115
 
 
116
bool IppClient::printerAddWithPpdFile(const QString &printerName,
 
117
                                      const QString &printerUri,
 
118
                                      const QString &ppdFileName,
 
119
                                      const QString &info,
 
120
                                      const QString &location)
 
121
{
 
122
    ipp_t *request;
 
123
 
 
124
    if (!isPrinterNameValid(printerName)) {
 
125
        setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
 
126
        return false;
 
127
    }
 
128
 
 
129
    if (!isStringValid(info)) {
 
130
        setInternalStatus(QString("%1 is not a valid description.").arg(info));
 
131
        return false;
 
132
    }
 
133
 
 
134
    if (!isStringValid(location)) {
 
135
        setInternalStatus(QString("%1 is not a valid location.").arg(location));
 
136
        return false;
 
137
    }
 
138
 
 
139
    if (!isStringValid(ppdFileName)) {
 
140
        setInternalStatus(QString("%1 is not a valid ppd file name.").arg(ppdFileName));
 
141
        return false;
 
142
    }
 
143
 
 
144
    if (!isStringValid(printerUri)) {
 
145
        setInternalStatus(QString("%1 is not a valid printer uri.").arg(printerUri));
 
146
        return false;
 
147
    }
 
148
 
 
149
    request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
 
150
    addPrinterUri(request, printerName);
 
151
    addRequestingUsername(request, NULL);
 
152
 
 
153
    ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
 
154
                 "printer-name", NULL, printerName.toUtf8());
 
155
 
 
156
    /* In this specific case of ADD_MODIFY, the URI can be NULL/empty since
 
157
     * we provide a complete PPD. And cups fails if we pass an empty
 
158
     * string. */
 
159
    if (!printerUri.isEmpty()) {
 
160
        ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI,
 
161
                     "device-uri", NULL, printerUri.toUtf8());
 
162
    }
 
163
 
 
164
    if (!info.isEmpty()) {
 
165
        ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
 
166
                     "printer-info", NULL, info.toUtf8());
 
167
    }
 
168
    if (!location.isEmpty()) {
 
169
        ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
 
170
                     "printer-location", NULL, location.toUtf8());
 
171
    }
 
172
 
 
173
    return postRequest(request, ppdFileName.toUtf8(), CupsResourceAdmin);
 
174
}
 
175
 
 
176
bool IppClient::printerSetDefault(const QString &printerName)
 
177
{
 
178
    return sendNewSimpleRequest(CUPS_SET_DEFAULT, printerName.toUtf8(),
 
179
                                CupsResource::CupsResourceAdmin);
 
180
}
 
181
 
 
182
bool IppClient::printerSetEnabled(const QString &printerName,
 
183
                                  const bool enabled)
 
184
{
 
185
    ipp_op_t op;
 
186
    op = enabled ? IPP_RESUME_PRINTER : IPP_PAUSE_PRINTER;
 
187
    return sendNewSimpleRequest(op, printerName, CupsResourceAdmin);
 
188
}
 
189
 
 
190
/* reason must be empty if accept is true */
 
191
bool IppClient::printerSetAcceptJobs(const QString &printerName,
 
192
                                     const bool accept,
 
193
                                     const QString &reason)
 
194
{
 
195
    ipp_t *request;
 
196
 
 
197
    if (accept && !reason.isEmpty()) {
 
198
        setInternalStatus("Accepting jobs does not take a reason.");
 
199
        return false;
 
200
    }
 
201
 
 
202
    if (!isPrinterNameValid(printerName)) {
 
203
        setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
 
204
        return false;
 
205
    }
 
206
 
 
207
    if (!isStringValid(reason)) {
 
208
        setInternalStatus(QString("%1 is not a valid reason.").arg(reason));
 
209
        return false;
 
210
    }
 
211
 
 
212
    if (accept) {
 
213
        return sendNewSimpleRequest(CUPS_ACCEPT_JOBS, printerName.toUtf8(),
 
214
                                    CupsResourceAdmin);
 
215
    } else {
 
216
        request = ippNewRequest(CUPS_REJECT_JOBS);
 
217
        addPrinterUri(request, printerName);
 
218
        addRequestingUsername(request, NULL);
 
219
 
 
220
        if (!reason.isEmpty())
 
221
            ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
 
222
                         "printer-state-message", NULL, reason.toUtf8());
 
223
 
 
224
        return sendRequest(request, CupsResourceAdmin);
 
225
    }
 
226
}
 
227
 
 
228
 
 
229
bool IppClient::printerClassSetInfo(const QString &name,
 
230
                                       const QString &info)
 
231
{
 
232
    if (!isPrinterNameValid(name)) {
 
233
        setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
 
234
        return false;
 
235
    }
 
236
 
 
237
    if (!isStringValid(info)) {
 
238
        setInternalStatus(QString("%1 is not a valid description.").arg(info));
 
239
        return false;
 
240
    }
 
241
 
 
242
    return sendNewPrinterClassRequest(name, IPP_TAG_PRINTER, IPP_TAG_TEXT,
 
243
                                      "printer-info", info);
 
244
}
 
245
 
 
246
bool IppClient::printerClassSetOption(const QString &name,
 
247
                                      const QString &option,
 
248
                                      const QStringList &values)
 
249
{
 
250
    bool isClass;
 
251
    int length = 0;
 
252
    ipp_t *request;
 
253
    ipp_attribute_t *attr;
 
254
    QString newPpdFile;
 
255
    bool retval;
 
256
 
 
257
    if (!isPrinterNameValid(name)) {
 
258
        setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
 
259
        return false;
 
260
    }
 
261
 
 
262
    if (!isStringValid(option)) {
 
263
        setInternalStatus(QString("%1 is not a valid option.").arg(option));
 
264
        return false;
 
265
    }
 
266
 
 
267
    Q_FOREACH(const QString &val, values) {
 
268
        if (!isStringValid(val)) {
 
269
            setInternalStatus(QString("%1 is not a valid value.").arg(val));
 
270
            return false;
 
271
        }
 
272
        length++;
 
273
    }
 
274
 
 
275
    if (length == 0) {
 
276
        setInternalStatus("No valid values.");
 
277
        return false;
 
278
    }
 
279
 
 
280
    isClass = printerIsClass(name);
 
281
 
 
282
    /* We permit only one value to change in PPD file because we are setting
 
283
     * default value in it. */
 
284
    if (!isClass && length == 1) {
 
285
        cups_option_t *options = NULL;
 
286
        int numOptions = 0;
 
287
        QString ppdfile;
 
288
 
 
289
        numOptions = cupsAddOption(option.toUtf8(),
 
290
                                   values[0].toUtf8(),
 
291
                                   numOptions, &options);
 
292
 
 
293
        ppdfile = QString(cupsGetPPD(name.toUtf8()));
 
294
 
 
295
        newPpdFile = preparePpdForOptions(ppdfile.toUtf8(),
 
296
                                          options, numOptions).toLatin1().data();
 
297
 
 
298
        unlink(ppdfile.toUtf8());
 
299
        cupsFreeOptions(numOptions, options);
 
300
    }
 
301
 
 
302
    if (isClass) {
 
303
        request = ippNewRequest(CUPS_ADD_MODIFY_CLASS);
 
304
        addClassUri(request, name);
 
305
    } else {
 
306
        request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
 
307
        addPrinterUri(request, name);
 
308
    }
 
309
 
 
310
    addRequestingUsername(request, NULL);
 
311
 
 
312
    if (length == 1) {
 
313
        ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
 
314
                     option.toUtf8(),
 
315
                     NULL,
 
316
                     values[0].toUtf8());
 
317
    } else {
 
318
        int i;
 
319
 
 
320
        attr = ippAddStrings(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
 
321
                             option.toUtf8(), length, NULL, NULL);
 
322
 
 
323
        for (i = 0; i < length; i++)
 
324
            ippSetString(request, &attr, i, values[i].toUtf8());
 
325
    }
 
326
 
 
327
    if (!newPpdFile.isEmpty()) {
 
328
        retval = postRequest(request, newPpdFile, CupsResourceAdmin);
 
329
 
 
330
        unlink(newPpdFile.toUtf8());
 
331
        // TODO: fix leak here.
 
332
    } else {
 
333
        retval = sendRequest(request, CupsResourceAdmin);
 
334
    }
 
335
 
 
336
    return retval;
 
337
}
 
338
 
 
339
QMap<QString, QVariant> IppClient::printerGetJobAttributes(const int jobId)
 
340
{
 
341
    ipp_t *request;
 
342
    QMap<QString, QVariant> map;
 
343
 
 
344
    // Construct request
 
345
    request = ippNewRequest(IPP_GET_JOB_ATTRIBUTES);
 
346
    QString uri = QStringLiteral("ipp://localhost/jobs/") + QString::number(jobId);
 
347
    qDebug() << "URI:" << uri;
 
348
 
 
349
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri.toStdString().data());
 
350
 
 
351
 
 
352
    // Send request and construct reply
 
353
    ipp_t *reply;
 
354
    const QString resourceChar = getResource(CupsResourceRoot);
 
355
    reply = cupsDoRequest(m_connection, request,
 
356
                          resourceChar.toUtf8());
 
357
 
 
358
    // Check if the reply is OK
 
359
    if (isReplyOk(reply, false)) {
 
360
        // Loop through the attributes
 
361
        ipp_attribute_t *attr;
 
362
 
 
363
        for (attr = ippFirstAttribute(reply); attr; attr = ippNextAttribute(reply)) {
 
364
            QVariant value = getAttributeValue(attr);
 
365
            map.insert(ippGetName(attr), value);
 
366
        }
 
367
    } else {
 
368
        qWarning() << "Not able to get attributes of job:" << jobId;
 
369
    }
 
370
 
 
371
    // Destruct the reply if valid
 
372
    if (reply) {
 
373
        ippDelete(reply);
 
374
    }
 
375
 
 
376
    return map;
 
377
}
 
378
 
 
379
 
 
380
/* This function sets given options to specified values in file 'ppdfile'.
 
381
 * This needs to be done because of applications which use content of PPD files
 
382
 * instead of IPP attributes.
 
383
 * CUPS doesn't do this automatically (but hopefully will starting with 1.6) */
 
384
QString IppClient::preparePpdForOptions(const QString &ppdfile,
 
385
                                        cups_option_t *options, int numOptions)
 
386
{
 
387
    auto ppdfile_c = ppdfile.toUtf8();
 
388
    ppd_file_t *ppd;
 
389
    bool ppdchanged = false;
 
390
    QString result;
 
391
    QString error;
 
392
    char newppdfile[PATH_MAX];
 
393
    cups_file_t  *in = NULL;
 
394
    cups_file_t  *out = NULL;
 
395
    char line[CPH_STR_MAXLEN];
 
396
    char keyword[CPH_STR_MAXLEN];
 
397
    char *keyptr;
 
398
    ppd_choice_t *choice;
 
399
    QString value;
 
400
    QLatin1String defaultStr("*Default");
 
401
 
 
402
    ppd = ppdOpenFile(ppdfile_c);
 
403
    if (!ppd) {
 
404
        error = QString("Unable to open PPD file \"%1\": %2")
 
405
                .arg(ppdfile).arg(strerror(errno));
 
406
        setInternalStatus(error);
 
407
        goto out;
 
408
    }
 
409
 
 
410
    in = cupsFileOpen(ppdfile_c, "r");
 
411
    if (!in) {
 
412
        error = QString("Unable to open PPD file \"%1\": %2")
 
413
            .arg(ppdfile).arg(strerror(errno));
 
414
        setInternalStatus(error);
 
415
        goto out;
 
416
    }
 
417
 
 
418
    out = cupsTempFile2(newppdfile, sizeof(newppdfile));
 
419
    if (!out) {
 
420
        setInternalStatus("Unable to create temporary file");
 
421
        goto out;
 
422
    }
 
423
 
 
424
    /* Mark default values and values of options we are changing. */
 
425
    ppdMarkDefaults(ppd);
 
426
    cupsMarkOptions(ppd, numOptions, options);
 
427
 
 
428
    while (cupsFileGets(in, line, sizeof(line))) {
 
429
        QString line_qs(line);
 
430
        if (!line_qs.startsWith(defaultStr)) {
 
431
            cupsFilePrintf(out, "%s\n", line);
 
432
        } else {
 
433
            /* This part parses lines with *Default on their
 
434
             * beginning. For instance:
 
435
             *   "*DefaultResolution: 1200dpi" becomes:
 
436
             *     - keyword: Resolution
 
437
             *     - keyptr: 1200dpi
 
438
             */
 
439
            strncpy(keyword, line + defaultStr.size(), sizeof(keyword));
 
440
 
 
441
            for (keyptr = keyword; *keyptr; keyptr++)
 
442
                    if (*keyptr == ':' || isspace (*keyptr & 255))
 
443
                            break;
 
444
 
 
445
            *keyptr++ = '\0';
 
446
            while (isspace (*keyptr & 255))
 
447
                    keyptr++;
 
448
 
 
449
            QString keyword_sq(keyword);
 
450
            QString keyptr_qs(keyptr);
 
451
 
 
452
            /* We have to change PageSize if any of PageRegion,
 
453
             * PageSize, PaperDimension or ImageableArea changes.
 
454
             * We change PageRegion if PageSize is not available. */
 
455
            if (keyword_sq == "PageRegion" ||
 
456
                keyword_sq == "PageSize" ||
 
457
                keyword_sq == "PaperDimension" ||
 
458
                keyword_sq == "ImageableArea") {
 
459
 
 
460
                choice = ppdFindMarkedChoice(ppd, "PageSize");
 
461
                if (!choice)
 
462
                    choice = ppdFindMarkedChoice(ppd, "PageRegion");
 
463
            } else {
 
464
                choice = ppdFindMarkedChoice(ppd, keyword);
 
465
            }
 
466
 
 
467
 
 
468
            QString choice_qs;
 
469
            if (choice) {
 
470
                choice_qs = choice->choice;
 
471
            }
 
472
 
 
473
            if (choice && choice_qs != keyptr_qs) {
 
474
                /* We have to set the value in PPD manually if
 
475
                 * a custom value was passed in:
 
476
                 * cupsMarkOptions() marks the choice as
 
477
                 * "Custom". We want to set this value with our
 
478
                 * input. */
 
479
                if (choice_qs != "Custom") {
 
480
                    cupsFilePrintf(out,
 
481
                                   "*Default%s: %s\n",
 
482
                                   keyword,
 
483
                                   choice->choice);
 
484
                    ppdchanged = true;
 
485
                } else {
 
486
                    value = cupsGetOption(keyword, numOptions, options);
 
487
                    if (!value.isEmpty()) {
 
488
                        cupsFilePrintf(out,
 
489
                                       "*Default%s: %s\n",
 
490
                                       keyword,
 
491
                                       value.toStdString().c_str());
 
492
                        ppdchanged = true;
 
493
                    } else {
 
494
                        cupsFilePrintf(out, "%s\n", line);
 
495
                    }
 
496
                }
 
497
            } else {
 
498
                cupsFilePrintf(out, "%s\n", line);
 
499
            }
 
500
        }
 
501
    }
 
502
 
 
503
    if (ppdchanged)
 
504
        result = QString::fromUtf8(newppdfile);
 
505
    else
 
506
        unlink(newppdfile);
 
507
 
 
508
out:
 
509
    if (in)
 
510
        cupsFileClose(in);
 
511
    if (out)
 
512
        cupsFileClose(out);
 
513
    if (ppd)
 
514
        ppdClose(ppd);
 
515
 
 
516
    return result;
 
517
}
 
518
 
 
519
 
 
520
bool IppClient::sendNewPrinterClassRequest(const QString &printerName,
 
521
                                           ipp_tag_t group, ipp_tag_t type,
 
522
                                           const QString &name,
 
523
                                           const QString &value)
 
524
{
 
525
    ipp_t *request;
 
526
 
 
527
    request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
 
528
    addPrinterUri(request, printerName);
 
529
    addRequestingUsername(request, QString());
 
530
    ippAddString(request, group, type, name.toUtf8(), NULL,
 
531
                 value.toUtf8());
 
532
 
 
533
    if (sendRequest(request, CupsResource::CupsResourceAdmin))
 
534
        return true;
 
535
 
 
536
    // it failed, maybe it was a class?
 
537
    if (m_lastStatus != IPP_NOT_POSSIBLE) {
 
538
        return false;
 
539
    }
 
540
 
 
541
    // TODO: implement class modification <here>.
 
542
    return false;
 
543
}
 
544
 
 
545
void IppClient::addPrinterUri(ipp_t *request, const QString &name)
 
546
{
 
547
    QUrl uri(QString("ipp://localhost/printers/%1").arg(name));
 
548
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
 
549
                 "printer-uri", NULL, uri.toEncoded().data());
 
550
}
 
551
 
 
552
void IppClient::addRequestingUsername(ipp_t *request, const QString &username)
 
553
{
 
554
    if (!username.isEmpty())
 
555
        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
 
556
                     "requesting-user-name", NULL,
 
557
                     username.toUtf8());
 
558
    else
 
559
        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
 
560
                     "requesting-user-name", NULL, cupsUser());
 
561
}
 
562
 
 
563
QString IppClient::getLastError() const
 
564
{
 
565
    return m_internalStatus;
 
566
}
 
567
 
 
568
const QString IppClient::getResource(const IppClient::CupsResource &resource)
 
569
{
 
570
    switch (resource) {
 
571
    case CupsResourceRoot:
 
572
        return "/";
 
573
    case CupsResourceAdmin:
 
574
        return "/admin/";
 
575
    case CupsResourceJobs:
 
576
        return "/jobs/";
 
577
    default:
 
578
        qCritical("Asking for a resource with no match.");
 
579
        return "/";
 
580
    }
 
581
}
 
582
 
 
583
bool IppClient::isPrinterNameValid(const QString &name)
 
584
{
 
585
    int i;
 
586
    int len;
 
587
 
 
588
    /* Quoting the lpadmin man page:
 
589
     *    CUPS allows printer names to contain any printable character
 
590
     *    except SPACE, TAB, "/", or  "#".
 
591
     * On top of that, validate_name() in lpadmin.c (from cups) checks that
 
592
     * the string is 127 characters long, or shorter. */
 
593
 
 
594
    /* no empty string */
 
595
    if (name.isEmpty())
 
596
        return false;
 
597
 
 
598
    len = name.size();
 
599
    /* no string that is too long; see comment at the beginning of the
 
600
     * validation code block */
 
601
    if (len > 127)
 
602
        return false;
 
603
 
 
604
    /* only printable characters, no space, no /, no # */
 
605
    for (i = 0; i < len; i++) {
 
606
        const QChar c = name.at(i);
 
607
        if (!c.isPrint())
 
608
            return false;
 
609
        if (c.isSpace())
 
610
            return false;
 
611
        if (c == '/' || c == '#')
 
612
            return false;
 
613
    }
 
614
    return true;
 
615
}
 
616
 
 
617
bool IppClient::isStringValid(const QString &string, const bool checkNull,
 
618
                              const int maxLength)
 
619
{
 
620
    if (isStringPrintable(string, checkNull, maxLength))
 
621
        return true;
 
622
    return false;
 
623
}
 
624
 
 
625
bool IppClient::isStringPrintable(const QString &string, const bool checkNull,
 
626
                                  const int maxLength)
 
627
{
 
628
    int i;
 
629
    int len;
 
630
 
 
631
    /* no null string */
 
632
    if (string.isNull())
 
633
        return !checkNull;
 
634
 
 
635
    len = string.size();
 
636
    if (maxLength > 0 && len > maxLength)
 
637
        return false;
 
638
 
 
639
    /* only printable characters */
 
640
    for (i = 0; i < len; i++) {
 
641
        const QChar c = string.at(i);
 
642
        if (!c.isPrint())
 
643
            return false;
 
644
    }
 
645
    return true;
 
646
}
 
647
 
 
648
void IppClient::setInternalStatus(const QString &status)
 
649
{
 
650
    if (!m_internalStatus.isNull()) {
 
651
        m_internalStatus = QString::null;
 
652
    }
 
653
 
 
654
    if (status.isNull()) {
 
655
        m_internalStatus = QString::null;
 
656
    } else {
 
657
        m_internalStatus = status;
 
658
 
 
659
        // Only used for errors for now.
 
660
        qCritical() << status;
 
661
    }
 
662
}
 
663
 
 
664
bool IppClient::postRequest(ipp_t *request, const QString &file,
 
665
                            const CupsResource &resource)
 
666
{
 
667
    ipp_t *reply;
 
668
    QString resourceChar;
 
669
 
 
670
    resourceChar = getResource(resource);
 
671
 
 
672
    if (!file.isEmpty())
 
673
        reply = cupsDoFileRequest(m_connection, request, resourceChar.toUtf8(),
 
674
                                  file.toUtf8());
 
675
    else
 
676
        reply = cupsDoFileRequest(m_connection, request, resourceChar.toUtf8(),
 
677
                                  NULL);
 
678
 
 
679
    return handleReply(reply);
 
680
}
 
681
 
 
682
 
 
683
bool IppClient::sendRequest(ipp_t *request, const CupsResource &resource)
 
684
{
 
685
    ipp_t *reply;
 
686
    const QString resourceChar = getResource(resource);
 
687
    reply = cupsDoRequest(m_connection, request,
 
688
                          resourceChar.toUtf8());
 
689
    return handleReply(reply);
 
690
}
 
691
 
 
692
bool IppClient::sendNewSimpleRequest(ipp_op_t op, const QString &printerName,
 
693
                                     const IppClient::CupsResource &resource)
 
694
{
 
695
    ipp_t *request;
 
696
 
 
697
    if (!isPrinterNameValid(printerName))
 
698
        return false;
 
699
 
 
700
    request = ippNewRequest(op);
 
701
    addPrinterUri(request, printerName);
 
702
    addRequestingUsername(request, NULL);
 
703
 
 
704
    return sendRequest(request, resource);
 
705
}
 
706
 
 
707
bool IppClient::handleReply(ipp_t *reply)
 
708
{
 
709
    bool retval;
 
710
    retval = isReplyOk(reply, false);
 
711
    if (reply)
 
712
        ippDelete(reply);
 
713
 
 
714
    return retval;
 
715
}
 
716
 
 
717
bool IppClient::isReplyOk(ipp_t *reply, bool deleteIfReplyNotOk)
 
718
{
 
719
    /* reset the internal status: we'll use the cups status */
 
720
    m_lastStatus = IPP_STATUS_CUPS_INVALID;
 
721
 
 
722
    if (reply && ippGetStatusCode(reply) <= IPP_OK_CONFLICT) {
 
723
        m_lastStatus = IPP_OK;
 
724
        return true;
 
725
    } else {
 
726
        setErrorFromReply(reply);
 
727
        qWarning() << Q_FUNC_INFO << "Cups HTTP error:" << cupsLastErrorString();
 
728
 
 
729
        if (deleteIfReplyNotOk && reply)
 
730
            ippDelete(reply);
 
731
 
 
732
        return false;
 
733
    }
 
734
}
 
735
 
 
736
void IppClient::setErrorFromReply(ipp_t *reply)
 
737
{
 
738
    if (reply)
 
739
        m_lastStatus = ippGetStatusCode(reply);
 
740
    else
 
741
        m_lastStatus = cupsLastError();
 
742
}
 
743
 
 
744
bool IppClient::printerIsClass(const QString &name)
 
745
{
 
746
    const char * const attrs[1] = { "member-names" };
 
747
    ipp_t *request;
 
748
    QString resource;
 
749
    ipp_t *reply;
 
750
    bool retval;
 
751
 
 
752
    // Class/Printer name validation is equal.
 
753
    if (!isPrinterNameValid(name)) {
 
754
        setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
 
755
        return false;
 
756
    }
 
757
 
 
758
    request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
 
759
    addClassUri(request, name);
 
760
    addRequestingUsername(request, QString());
 
761
    ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
 
762
                  "requested-attributes", 1, NULL, attrs);
 
763
 
 
764
    resource = getResource(CupsResource::CupsResourceRoot);
 
765
    reply = cupsDoRequest(m_connection, request, resource.toUtf8());
 
766
 
 
767
    if (!isReplyOk(reply, true))
 
768
        return true;
 
769
 
 
770
    /* Note: we need to look if the attribute is there, since we get a
 
771
     * reply if the name is a printer name and not a class name. The
 
772
     * attribute is the only way to distinguish the two cases. */
 
773
    retval = ippFindAttribute(reply, attrs[0], IPP_TAG_NAME) != NULL;
 
774
 
 
775
    if (reply)
 
776
        ippDelete(reply);
 
777
 
 
778
    return retval;
 
779
}
 
780
 
 
781
void IppClient::addClassUri(ipp_t *request, const QString &name)
 
782
{
 
783
    QUrl uri(QString("ipp://localhost/printers/%1").arg(name));
 
784
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
 
785
                 "printer-uri", NULL, uri.toEncoded().data());
 
786
}
 
787
 
 
788
ppd_file_t* IppClient::getPpdFile(const QString &name,
 
789
                                  const QString &instance) const
 
790
{
 
791
    Q_UNUSED(instance);
 
792
 
 
793
    ppd_file_t* file = 0;
 
794
    const char *ppdFile = cupsGetPPD(name.toUtf8());
 
795
    if (ppdFile) {
 
796
        file = ppdOpenFile(ppdFile);
 
797
        unlink(ppdFile);
 
798
    }
 
799
    if (file) {
 
800
        ppdMarkDefaults(file);
 
801
    } else {
 
802
        file = 0;
 
803
    }
 
804
 
 
805
    return file;
 
806
}
 
807
 
 
808
cups_dest_t* IppClient::getDest(const QString &name,
 
809
                                const QString &instance) const
 
810
{
 
811
    cups_dest_t *dest = 0;
 
812
 
 
813
    if (instance.isEmpty()) {
 
814
        dest = cupsGetNamedDest(m_connection, name.toUtf8(), NULL);
 
815
    } else {
 
816
        dest = cupsGetNamedDest(m_connection, name.toUtf8(), instance.toUtf8());
 
817
    }
 
818
    return dest;
 
819
}
 
820
 
 
821
ipp_t* IppClient::createPrinterDriversRequest(
 
822
    const QString &deviceId, const QString &language, const QString &makeModel,
 
823
    const QString &product, const QStringList &includeSchemes,
 
824
    const QStringList &excludeSchemes
 
825
)
 
826
{
 
827
    Q_UNUSED(includeSchemes);
 
828
    Q_UNUSED(excludeSchemes);
 
829
 
 
830
    ipp_t *request;
 
831
 
 
832
    request = ippNewRequest(CUPS_GET_PPDS);
 
833
 
 
834
    if (!deviceId.isEmpty())
 
835
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-device-id",
 
836
                 NULL, deviceId.toUtf8());
 
837
    if (!language.isEmpty())
 
838
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "ppd-language",
 
839
                 NULL, language.toUtf8());
 
840
    if (!makeModel.isEmpty())
 
841
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-make-and-model",
 
842
                 NULL, makeModel.toUtf8());
 
843
    if (!product.isEmpty())
 
844
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-product",
 
845
                 NULL, product.toUtf8());
 
846
 
 
847
    // Do the request and get return the response.
 
848
    const QString resourceChar = getResource(CupsResourceRoot);
 
849
    return cupsDoRequest(m_connection, request,
 
850
                         resourceChar.toUtf8());
 
851
}
 
852
 
 
853
int IppClient::createSubscription()
 
854
{
 
855
    ipp_t *req;
 
856
    ipp_t *resp;
 
857
    ipp_attribute_t *attr;
 
858
    int subscriptionId = -1;
 
859
 
 
860
    req = ippNewRequest(IPP_CREATE_PRINTER_SUBSCRIPTION);
 
861
    ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI,
 
862
                 "printer-uri", NULL, "/");
 
863
    ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
 
864
                 "notify-events", NULL, "all");
 
865
    ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI,
 
866
                 "notify-recipient-uri", NULL, "dbus://");
 
867
    ippAddInteger(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
 
868
                  "notify-lease-duration", 0);
 
869
 
 
870
    resp = cupsDoRequest(m_connection, req,
 
871
                         getResource(CupsResourceRoot).toUtf8());
 
872
    if (!isReplyOk(resp, true)) {
 
873
        return subscriptionId;
 
874
    }
 
875
 
 
876
    attr = ippFindAttribute(resp, "notify-subscription-id", IPP_TAG_INTEGER);
 
877
 
 
878
    if (!attr) {
 
879
        qWarning() << "ipp-create-printer-subscription response doesn't"
 
880
                       "  contain subscription id.";
 
881
    } else {
 
882
        subscriptionId = ippGetInteger(attr, 0);
 
883
    }
 
884
 
 
885
    ippDelete (resp);
 
886
 
 
887
    return subscriptionId;
 
888
}
 
889
 
 
890
void IppClient::cancelSubscription(const int &subscriptionId)
 
891
{
 
892
    ipp_t *req;
 
893
    ipp_t *resp;
 
894
 
 
895
    if (subscriptionId <= 0) {
 
896
        return;
 
897
    }
 
898
 
 
899
    req = ippNewRequest(IPP_CANCEL_SUBSCRIPTION);
 
900
    ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI,
 
901
                 "printer-uri", NULL, "/");
 
902
    ippAddInteger(req, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
 
903
                  "notify-subscription-id", subscriptionId);
 
904
 
 
905
    resp = cupsDoRequest(m_connection, req,
 
906
                         getResource(CupsResourceRoot).toUtf8());
 
907
    if (!isReplyOk(resp, true)) {
 
908
        return;
 
909
    }
 
910
 
 
911
    ippDelete(resp);
 
912
}
 
913
 
 
914
QVariant IppClient::getAttributeValue(ipp_attribute_t *attr, int index) const
 
915
{
 
916
    QVariant var;
 
917
 
 
918
    if (ippGetCount(attr) > 1 && index < 0) {
 
919
        QList<QVariant> list;
 
920
 
 
921
        for (int i=0; i < ippGetCount(attr); i++) {
 
922
            list.append(getAttributeValue(attr, i));
 
923
        }
 
924
 
 
925
        var = QVariant::fromValue<QList<QVariant>>(list);
 
926
    } else {
 
927
        if (index == -1) {
 
928
            index = 0;
 
929
        }
 
930
 
 
931
        switch (ippGetValueTag(attr)) {
 
932
        case IPP_TAG_NAME:
 
933
        case IPP_TAG_TEXT:
 
934
        case IPP_TAG_KEYWORD:
 
935
        case IPP_TAG_URI:
 
936
        case IPP_TAG_CHARSET:
 
937
        case IPP_TAG_MIMETYPE:
 
938
        case IPP_TAG_LANGUAGE:
 
939
            var = QVariant::fromValue<QString>(ippGetString(attr, index, NULL));
 
940
            break;
 
941
        case IPP_TAG_INTEGER:
 
942
        case IPP_TAG_ENUM:
 
943
            var = QVariant::fromValue<int>(ippGetInteger(attr, index));
 
944
            break;
 
945
        case IPP_TAG_BOOLEAN:
 
946
            var = QVariant::fromValue<bool>(ippGetBoolean(attr, index));
 
947
            break;
 
948
        case IPP_TAG_RANGE: {
 
949
            QString range;
 
950
            int upper;
 
951
            int lower = ippGetRange(attr, index, &upper);
 
952
 
 
953
            // Build a string similar to "1-3" "5-" "8" "-4"
 
954
            if (lower != INT_MIN) {
 
955
                range += QString::number(lower);
 
956
            }
 
957
 
 
958
            if (lower != upper) {
 
959
                range += QStringLiteral("-");
 
960
 
 
961
                if (upper != INT_MAX) {
 
962
                    range += QString::number(upper);
 
963
                }
 
964
            }
 
965
 
 
966
            var = QVariant(range);
 
967
            break;
 
968
        }
 
969
        case IPP_TAG_NOVALUE:
 
970
            var = QVariant();
 
971
            break;
 
972
        case IPP_TAG_DATE: {
 
973
            time_t time = ippDateToTime(ippGetDate(attr, index));
 
974
            QDateTime datetime;
 
975
            datetime.setTimeZone(QTimeZone::systemTimeZone());
 
976
            datetime.setTime_t(time);
 
977
 
 
978
            var = QVariant::fromValue<QDateTime>(datetime);
 
979
            break;
 
980
        }
 
981
        default:
 
982
            qWarning() << "Unknown IPP value tab 0x" << ippGetValueTag(attr);
 
983
            break;
 
984
        }
 
985
    }
 
986
 
 
987
    return var;
 
988
}