1 /*
2  * cups - Python bindings for CUPS
3  * Copyright (C) 2002-2020  Red Hat, Inc
4  * Authors: Tim Waugh <twaugh@redhat.com>
5  *          Zdenek Dohnal <zdohnal@redhat.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20  */
21 
22 #include "cupsconnection.h"
23 #include "cupsppd.h"
24 #include "cupsmodule.h"
25 
26 #ifndef __SVR4
27 #include <paths.h>
28 #endif
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 
34 #ifndef _PATH_TMP
35 #define _PATH_TMP P_tmpdir
36 #endif
37 
38 #define DICT_POS_TYPE Py_ssize_t
39 
40 PyObject *HTTPError;
41 PyObject *IPPError;
42 
43 static Connection **Connections = NULL;
44 static long unsigned int NumConnections = 0;
45 
46 static void
set_http_error(http_status_t status)47 set_http_error (http_status_t status)
48 {
49   PyObject *v = Py_BuildValue ("i", status);
50   debugprintf("set_http_error: %d\n", (int) status);
51   if (v != NULL) {
52     PyErr_SetObject (HTTPError, v);
53     Py_DECREF (v);
54   }
55 }
56 
57 void
set_ipp_error(ipp_status_t status,const char * message)58 set_ipp_error (ipp_status_t status, const char *message)
59 {
60   if (!message)
61     message = ippErrorString (status);
62 
63   debugprintf("set_ipp_error: %d, %s\n", (int) status, message);
64   PyObject *v = Py_BuildValue ("(is)", status, message);
65   if (v != NULL) {
66     PyErr_SetObject (IPPError, v);
67     Py_DECREF (v);
68   }
69 }
70 
71 static PyObject *
PyObj_from_UTF8(const char * utf8)72 PyObj_from_UTF8 (const char *utf8)
73 {
74   PyObject *val = PyUnicode_Decode (utf8, strlen (utf8), "utf-8", NULL);
75   if (!val) {
76     // CUPS 1.2 always gives us UTF-8.  Before CUPS 1.2, the
77     // ppd-* strings come straight from the PPD with no
78     // transcoding, but the attributes-charset is still 'utf-8'
79     // so we've no way of knowing the real encoding.
80     // In that case, detect the error and force it to ASCII.
81     const char *orig = utf8;
82     char *ascii;
83     int i;
84     PyErr_Clear ();
85     ascii = malloc (1 + strlen (orig));
86     for (i = 0; orig[i]; i++)
87       ascii[i] = orig[i] & 0x7f;
88     ascii[i] = '\0';
89     val = PyUnicode_FromString (ascii);
90     free (ascii);
91   }
92 
93   return val;
94 }
95 
96 const char *
UTF8_from_PyObj(char ** const utf8,PyObject * obj)97 UTF8_from_PyObj (char **const utf8, PyObject *obj)
98 // converts PyUnicode or PyBytes to char *
99 {
100   if (PyUnicode_Check (obj)) {
101     const char *string;
102     PyObject *stringobj = PyUnicode_AsUTF8String (obj);
103     if (stringobj == NULL)
104       return NULL;
105 
106     string = PyBytes_AsString (stringobj);
107     if (string == NULL) {
108       Py_DECREF (stringobj);
109       return NULL;
110     }
111 
112     *utf8 = strdup (string);
113     Py_DECREF (stringobj);
114     return *utf8;
115   }
116   else if (PyBytes_Check (obj)) {
117     const char *ret;
118     PyObject *unicodeobj = PyUnicode_FromEncodedObject (obj, "utf-8", NULL);
119     if (unicodeobj == NULL)
120       return NULL;
121 
122     ret = UTF8_from_PyObj (utf8, unicodeobj);
123     Py_DECREF (unicodeobj);
124     return ret;
125   }
126 
127   PyErr_SetString (PyExc_TypeError, "unicode or bytes object required");
128   return NULL;
129 }
130 
131 static void
construct_uri(char * buffer,size_t buflen,const char * base,const char * value)132 construct_uri (char *buffer, size_t buflen, const char *base, const char *value)
133 {
134   char *d = buffer;
135   const unsigned char *s = (const unsigned char *) value;
136   if (strlen (base) < buflen) {
137     strcpy (buffer, base);
138     d += strlen (base);
139   } else {
140     strncpy (buffer, base, buflen);
141     d += buflen;
142   }
143 
144   while (*s && d < buffer + buflen) {
145     if (isalpha (*s) || isdigit (*s) || *s == '-')
146       *d++ = *s++;
147     else if (*s == ' ') {
148       *d++ = '+';
149       s++;
150     } else {
151       if (d + 2 < buffer + buflen) {
152 	*d++ = '%';
153 	*d++ = "0123456789ABCDEF"[((*s) & 0xf0) >> 4];
154 	*d++ = "0123456789ABCDEF"[((*s) & 0x0f)];
155 	s++;
156       } else
157 	break;
158     }
159   }
160 
161   if (d < buffer + buflen)
162     *d = '\0';
163 }
164 
165 ////////////////
166 // Connection //
167 ////////////////
168 
169 static PyObject *
Connection_new(PyTypeObject * type,PyObject * args,PyObject * kwds)170 Connection_new (PyTypeObject *type, PyObject *args, PyObject *kwds)
171 {
172   Connection *self;
173   self = (Connection *) type->tp_alloc (type, 0);
174   if (self != NULL) {
175     self->http = NULL;
176     self->host = NULL;
177     self->tstate = NULL;
178     self->cb_password = NULL;
179   }
180 
181   return (PyObject *) self;
182 }
183 
184 static int
Connection_init(Connection * self,PyObject * args,PyObject * kwds)185 Connection_init (Connection *self, PyObject *args, PyObject *kwds)
186 {
187   const char *host = cupsServer ();
188   int port = ippPort ();
189   int encryption = (http_encryption_t) cupsEncryption ();
190   static char *kwlist[] = { "host", "port", "encryption", NULL };
191 
192   if (!PyArg_ParseTupleAndKeywords (args, kwds, "|sii", kwlist,
193 				    &host, &port, &encryption))
194     return -1;
195 
196   debugprintf ("-> Connection_init(host=%s)\n", host);
197   self->host = strdup (host);
198   if (!self->host) {
199     debugprintf ("<- Connection_init() = -1\n");
200     return -1;
201   }
202 
203   Connection_begin_allow_threads (self);
204   debugprintf ("httpConnect2(...)\n");
205   self->http = httpConnect2(host, port, NULL, AF_UNSPEC, cupsEncryption(), 1, 30000, NULL);
206   Connection_end_allow_threads (self);
207 
208   if (!self->http) {
209     PyErr_SetString (PyExc_RuntimeError, "failed to connect to server");
210     debugprintf ("<- Connection_init() = -1\n");
211     return -1;
212   }
213 
214   if (NumConnections == 0)
215   {
216     Connections = malloc (sizeof (Connection *));
217     if (Connections == NULL) {
218       PyErr_SetString (PyExc_RuntimeError, "insufficient memory");
219       debugprintf ("<- Connection_init() = -1\n");
220       return -1;
221     }
222   }
223   else
224   {
225     Connection **old_array = Connections;
226 
227     if ((1 + NumConnections) >= UINT_MAX / sizeof (Connection *))
228     {
229       PyErr_SetString (PyExc_RuntimeError, "too many connections");
230       debugprintf ("<- Connection_init() == -1\n");
231       return -1;
232     }
233 
234     Connections = realloc (Connections,
235 			   (1 + NumConnections) * sizeof (Connection *));
236     if (Connections == NULL) {
237       Connections = old_array;
238       PyErr_SetString (PyExc_RuntimeError, "insufficient memory");
239       debugprintf ("<- Connection_init() = -1\n");
240       return -1;
241     }
242   }
243 
244   Connections[NumConnections++] = self;
245 
246   debugprintf ("<- Connection_init() = 0\n");
247   return 0;
248 }
249 
250 static void
Connection_dealloc(Connection * self)251 Connection_dealloc (Connection *self)
252 {
253   long unsigned int i, j;
254 
255   for (j = 0; j < NumConnections; j++)
256     if (Connections[j] == self)
257       break;
258 
259   if (j < NumConnections)
260   {
261     if (NumConnections > 1)
262     {
263       Connection **new_array = calloc (NumConnections - 1,
264 				       sizeof (Connection *));
265 
266       if (new_array)
267       {
268 	int k;
269 	for (i = 0, k = 0; i < NumConnections; i++)
270 	{
271 	  if (i == j)
272 	    continue;
273 
274 	  new_array[k++] = Connections[i];
275 	}
276 
277 	free (Connections);
278 	Connections = new_array;
279 	NumConnections--;
280       } else
281 	/* Failed to allocate memory. Just clear out the reference. */
282 	Connections[j] = NULL;
283     }
284     else
285     {
286       /* The only element is the one we no longer need. */
287       free (Connections);
288       Connections = NULL;
289       NumConnections = 0;
290     }
291   }
292 
293   if (self->http) {
294     debugprintf ("httpClose()\n");
295     httpClose (self->http);
296     free (self->host);
297     free (self->cb_password);
298   }
299 
300   ((PyObject *)self)->ob_type->tp_free ((PyObject *) self);
301 }
302 
303 static PyObject *
Connection_repr(Connection * self)304 Connection_repr (Connection *self)
305 {
306   char buffer[256];
307   snprintf (buffer, 256, "<cups.Connection object for %s at %p>",
308 			  self->host, self);
309   return PyUnicode_FromString (buffer);
310 }
311 
312 void
Connection_begin_allow_threads(void * connection)313 Connection_begin_allow_threads (void *connection)
314 {
315   Connection *self = (Connection *) connection;
316   debugprintf ("begin allow threads\n");
317 
318   self->tstate = PyEval_SaveThread ();
319 }
320 
321 void
Connection_end_allow_threads(void * connection)322 Connection_end_allow_threads (void *connection)
323 {
324   Connection *self = (Connection *) connection;
325   debugprintf ("end allow threads\n");
326   PyEval_RestoreThread (self->tstate);
327   self->tstate = NULL;
328 }
329 
330 ////////////////
331 // Connection // METHODS
332 ////////////////
333 
334 static const char *
password_callback(int newstyle,const char * prompt,http_t * http,const char * method,const char * resource,void * user_data)335 password_callback (int newstyle,
336 		   const char *prompt,
337 		   http_t *http,
338 		   const char *method,
339 		   const char *resource,
340 		   void *user_data)
341 {
342   struct TLS *tls = get_TLS ();
343   PyObject *cb_context = user_data;
344   Connection *self = NULL;
345   PyObject *args;
346   PyObject *result;
347   long unsigned int i;
348 
349   debugprintf ("-> password_callback for http=%p, newstyle=%d\n",
350 	       http, newstyle);
351 
352   for (i = 0; i < NumConnections; i++)
353     if (Connections[i]->http == http)
354     {
355       self = Connections[i];
356       break;
357     }
358 
359   if (!self)
360   {
361     debugprintf ("cannot find self!\n");
362     return "";
363   }
364 
365   Connection_end_allow_threads (self);
366   if (newstyle)
367   {
368     /* New-style callback. */
369     if (cb_context)
370       args = Py_BuildValue ("(sOssO)", prompt, self, method, resource,
371 			    cb_context);
372     else
373       args = Py_BuildValue ("(sOss)", prompt, self, method, resource);
374   } else
375     args = Py_BuildValue ("(s)", prompt);
376 
377   result = PyEval_CallObject (tls->cups_password_callback, args);
378   Py_DECREF (args);
379   if (result == NULL)
380   {
381     debugprintf ("<- password_callback (exception)\n");
382     Connection_begin_allow_threads (self);
383     return NULL;
384   }
385 
386   free (self->cb_password);
387   if (result == Py_None ||
388       !UTF8_from_PyObj (&self->cb_password, result))
389     self->cb_password = NULL;
390 
391   Py_DECREF (result);
392   if (!self->cb_password || !*self->cb_password)
393   {
394     debugprintf ("<- password_callback (empty/null)\n");
395     Connection_begin_allow_threads (self);
396     return NULL;
397   }
398 
399   Connection_begin_allow_threads (self);
400   debugprintf ("<- password_callback\n");
401   return self->cb_password;
402 }
403 
404 const char *
password_callback_oldstyle(const char * prompt,http_t * http,const char * method,const char * resource,void * user_data)405 password_callback_oldstyle (const char *prompt,
406 			    http_t *http,
407 			    const char *method,
408 			    const char *resource,
409 			    void *user_data)
410 {
411   return password_callback (0, prompt, http, method, resource, user_data);
412 }
413 
414 const char *
password_callback_newstyle(const char * prompt,http_t * http,const char * method,const char * resource,void * user_data)415 password_callback_newstyle (const char *prompt,
416 			    http_t *http,
417 			    const char *method,
418 			    const char *resource,
419 			    void *user_data)
420 {
421   return password_callback (1, prompt, http, method, resource, user_data);
422 }
423 
424 static PyObject *
do_printer_request(Connection * self,PyObject * args,PyObject * kwds,ipp_op_t op)425 do_printer_request (Connection *self, PyObject *args, PyObject *kwds,
426 		    ipp_op_t op)
427 {
428   PyObject *nameobj;
429   PyObject *reasonobj = NULL;
430   char *name;
431   char uri[HTTP_MAX_URI];
432   ipp_t *request, *answer;
433 
434   switch (op) {
435   case IPP_PAUSE_PRINTER:
436   case CUPS_REJECT_JOBS:
437     {
438       static char *kwlist[] = { "name", "reason", NULL };
439       if (!PyArg_ParseTupleAndKeywords (args, kwds, "O|O", kwlist,
440 					&nameobj, &reasonobj))
441 	return NULL;
442       break;
443     }
444 
445   default:
446     if (!PyArg_ParseTuple (args, "O", &nameobj))
447       return NULL;
448     break;
449   }
450 
451   if (UTF8_from_PyObj (&name, nameobj) == NULL)
452     return NULL;
453 
454   debugprintf ("-> do_printer_request(op:%d, name:%s)\n", (int) op, name);
455 
456   request = ippNewRequest (op);
457   construct_uri (uri, sizeof (uri), "ipp://localhost/printers/", name);
458   free (name);
459 
460   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
461 		"printer-uri", NULL, uri);
462 
463   if (reasonobj) {
464     char *reason;
465     if (UTF8_from_PyObj (&reason, reasonobj) == NULL) {
466       ippDelete (request);
467       return NULL;
468     }
469 
470     debugprintf ("reason: %s\n", reason);
471     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
472 		  "printer-state-message", NULL, reason);
473     free (reason);
474   }
475 
476   debugprintf ("cupsDoRequest(\"/admin/\")\n");
477   Connection_begin_allow_threads (self);
478   answer = cupsDoRequest (self->http, request, "/admin/");
479   Connection_end_allow_threads (self);
480   if (PyErr_Occurred ()) {
481     if (answer)
482       ippDelete (answer);
483     debugprintf("<- do_printer_request (error)\n");
484     return NULL;
485   }
486 
487   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
488     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
489 		   answer ? NULL : cupsLastErrorString ());
490     if (answer)
491       ippDelete (answer);
492     debugprintf("<- do_printer_request (error)\n");
493     return NULL;
494   }
495 
496   ippDelete (answer);
497   debugprintf("<- do_printer_request (None)\n");
498   Py_RETURN_NONE;
499 }
500 
501 static int
copy_dest(Dest * dst,cups_dest_t * src)502 copy_dest (Dest *dst, cups_dest_t *src)
503 {
504   int i;
505   dst->is_default = src->is_default;
506   dst->destname = strdup (src->name);
507   dst->instance = (src->instance ? strdup (src->instance) : NULL );
508   dst->num_options = src->num_options;
509   dst->name = malloc (src->num_options * sizeof (char *));
510   dst->value = malloc (src->num_options * sizeof (char *));
511   for (i = 0; i < src->num_options; i++) {
512     dst->name[i] = strdup (src->options[i].name);
513     dst->value[i] = strdup (src->options[i].value);
514   }
515   return 0;
516 }
517 
518 static PyObject *
Connection_getDests(Connection * self)519 Connection_getDests (Connection *self)
520 {
521   cups_dest_t *dests;
522   int num_dests;
523   PyObject *pydests = PyDict_New ();
524   int i;
525 
526   debugprintf ("-> Connection_getDests()\n");
527   debugprintf ("cupsGetDests2()\n");
528   Connection_begin_allow_threads (self);
529   num_dests = cupsGetDests2 (self->http, &dests);
530   Connection_end_allow_threads (self);
531 
532   // Create a dict indexed by (name,instance)
533   for (i = 0; i <= num_dests; i++) {
534     PyObject *largs = Py_BuildValue ("()");
535     PyObject *lkwlist = Py_BuildValue ("{}");
536     Dest *destobj = (Dest *) PyType_GenericNew (&cups_DestType,
537 						largs, lkwlist);
538     Py_DECREF (largs);
539     Py_DECREF (lkwlist);
540 
541     cups_dest_t *dest;
542     PyObject *nameinstance;
543     if (i == num_dests)
544       {
545 	// Add a (None,None) entry for the default printer.
546 	dest = cupsGetDest (NULL, NULL, num_dests, dests);
547 	if (dest == NULL) {
548 	  /* No default printer. */
549 	  Py_DECREF ((PyObject *) destobj);
550 	  break;
551 	}
552 
553 	nameinstance = Py_BuildValue ("(ss)", NULL, NULL);
554       }
555     else
556       {
557 	dest = dests + i;
558 	nameinstance = Py_BuildValue ("(ss)", dest->name, dest->instance);
559       }
560 
561     copy_dest (destobj, dest);
562 
563     PyDict_SetItem (pydests, nameinstance, (PyObject *) destobj);
564     Py_DECREF ((PyObject *) destobj);
565   }
566 
567   debugprintf ("cupsFreeDests()\n");
568   cupsFreeDests (num_dests, dests);
569   debugprintf ("<- Connection_getDests()\n");
570   return pydests;
571 }
572 
573 int
cups_dest_cb(void * user_data,unsigned flags,cups_dest_t * dest)574 cups_dest_cb (void *user_data, unsigned flags, cups_dest_t *dest)
575 {
576   CallbackContext *context = user_data;
577   PyObject *largs = Py_BuildValue ("()");
578   PyObject *lkwlist = Py_BuildValue ("{}");
579   Dest *destobj;
580   PyObject *args;
581   PyObject *result;
582   int ret = 0;
583 
584   debugprintf ("-> cups_dest_cb\n");
585   destobj = (Dest *) PyType_GenericNew (&cups_DestType,
586 					largs, lkwlist);
587   Py_DECREF (largs);
588   Py_DECREF (lkwlist);
589   copy_dest (destobj, dest);
590   args = Py_BuildValue ("(OiO)",
591 			context->user_data,
592 			flags,
593 			destobj);
594   Py_DECREF ((PyObject *) destobj);
595 
596   result = PyEval_CallObject (context->cb, args);
597   Py_DECREF (args);
598   if (result == NULL) {
599     debugprintf ("<- cups_dest_cb (exception from cb func)\n");
600     ret = 0;
601   }
602 
603 
604   if (result && PyLong_Check (result)) {
605     ret = PyLong_AsLong (result);
606     debugprintf ("   cups_dest_cb: cb func returned %d\n", ret);
607   }
608 
609   debugprintf ("<- cups_dest_cb (%d)\n", ret);
610 
611   return ret;
612 }
613 
614 static PyObject *
PyObject_from_attr_value(ipp_attribute_t * attr,int i)615 PyObject_from_attr_value (ipp_attribute_t *attr, int i)
616 {
617   PyObject *val = NULL;
618   char unknown[100];
619   int lower, upper;
620   int xres, yres;
621   ipp_res_t units;
622 
623   switch (ippGetValueTag(attr)) {
624   case IPP_TAG_NAME:
625   case IPP_TAG_TEXT:
626   case IPP_TAG_KEYWORD:
627   case IPP_TAG_URI:
628   case IPP_TAG_CHARSET:
629   case IPP_TAG_MIMETYPE:
630   case IPP_TAG_LANGUAGE:
631     val = PyObj_from_UTF8 (ippGetString (attr, i, NULL));
632     break;
633   case IPP_TAG_INTEGER:
634   case IPP_TAG_ENUM:
635     val = PyLong_FromLong (ippGetInteger (attr, i));
636     break;
637   case IPP_TAG_BOOLEAN:
638     val = PyBool_FromLong (ippGetBoolean (attr, i));
639     break;
640   case IPP_TAG_RANGE:
641     lower = ippGetRange (attr, i, &upper);
642     val = Py_BuildValue ("(ii)",
643 			 lower,
644 			 upper);
645     break;
646   case IPP_TAG_NOVALUE:
647     Py_RETURN_NONE;
648     break;
649 
650     // TODO:
651   case IPP_TAG_DATE:
652     val = PyUnicode_FromString ("(IPP_TAG_DATE)");
653     break;
654   case IPP_TAG_RESOLUTION:
655     xres = ippGetResolution(attr, i, &yres, &units);
656     val = Py_BuildValue ("(iii)",
657 			 xres,
658 			 yres,
659 			 units);
660     break;
661   default:
662     snprintf (unknown, sizeof (unknown),
663 	      "(unknown IPP value tag 0x%x)", ippGetValueTag(attr));
664     val = PyUnicode_FromString (unknown);
665     break;
666   }
667 
668   return val;
669 }
670 
671 static PyObject *
PyList_from_attr_values(ipp_attribute_t * attr)672 PyList_from_attr_values (ipp_attribute_t *attr)
673 {
674   PyObject *list = PyList_New (0);
675   int i;
676   debugprintf ("-> PyList_from_attr_values()\n");
677   for (i = 0; i < ippGetCount (attr); i++) {
678     PyObject *val = PyObject_from_attr_value (attr, i);
679     if (val) {
680       PyList_Append (list, val);
681       Py_DECREF (val);
682     }
683   }
684 
685   debugprintf ("<- PyList_from_attr_values()\n");
686   return list;
687 }
688 
689 static PyObject *
Connection_getPrinters(Connection * self)690 Connection_getPrinters (Connection *self)
691 {
692   PyObject *result;
693   ipp_t *request = ippNewRequest(CUPS_GET_PRINTERS), *answer;
694   ipp_attribute_t *attr;
695   const char *attributes[] = {
696     "printer-name",
697     "printer-type",
698     "printer-location",
699     "printer-info",
700     "printer-make-and-model",
701     "printer-state",
702     "printer-state-message",
703     "printer-state-reasons",
704     "printer-uri-supported",
705     "device-uri",
706     "printer-is-shared",
707   };
708 
709   debugprintf ("-> Connection_getPrinters()\n");
710 
711   ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
712 		 "requested-attributes",
713 		 sizeof (attributes) / sizeof (attributes[0]),
714 		 NULL, attributes);
715   debugprintf ("cupsDoRequest(\"/\")\n");
716   Connection_begin_allow_threads (self);
717   answer = cupsDoRequest (self->http, request, "/");
718   Connection_end_allow_threads (self);
719   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
720     if (answer && ippGetStatusCode (answer) == IPP_NOT_FOUND) {
721       // No printers.
722       debugprintf ("<- Connection_getPrinters() = {} (no printers)\n");
723       ippDelete (answer);
724       return PyDict_New ();
725     }
726 
727     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
728 		   answer ? NULL : cupsLastErrorString ());
729     if (answer)
730       ippDelete (answer);
731     debugprintf ("<- Connection_getPrinters() (error)\n");
732     return NULL;
733   }
734 
735   result = PyDict_New ();
736   for (attr = ippFirstAttribute (answer); attr; attr = ippNextAttribute (answer)) {
737     PyObject *dict;
738     char *printer = NULL;
739 
740     while (attr && ippGetGroupTag (attr) != IPP_TAG_PRINTER)
741       attr = ippNextAttribute (answer);
742 
743     if (!attr)
744       break;
745 
746     dict = PyDict_New ();
747     for (; attr && ippGetGroupTag (attr) == IPP_TAG_PRINTER;
748 	 attr = ippNextAttribute (answer)) {
749       PyObject *val = NULL;
750 
751       debugprintf ("Attribute: %s\n", ippGetName (attr));
752       if (!strcmp (ippGetName (attr), "printer-name") &&
753 	  ippGetValueTag (attr) == IPP_TAG_NAME)
754 	printer = (char *) ippGetString (attr, 0, NULL);
755       else if ((!strcmp (ippGetName (attr), "printer-type") ||
756 		!strcmp (ippGetName (attr), "printer-state")) &&
757 	       ippGetValueTag (attr) == IPP_TAG_ENUM) {
758 	int ptype = ippGetInteger (attr, 0);
759 	val = PyLong_FromLong (ptype);
760       }
761       else if ((!strcmp (ippGetName (attr),
762 			 "printer-make-and-model") ||
763 		!strcmp (ippGetName (attr), "printer-info") ||
764 		!strcmp (ippGetName (attr), "printer-location") ||
765 		!strcmp (ippGetName (attr), "printer-state-message")) &&
766 	       ippGetValueTag (attr) == IPP_TAG_TEXT) {
767 	val = PyObj_from_UTF8 (ippGetString (attr, 0, NULL));
768       }
769       else if (!strcmp (ippGetName (attr),
770 			"printer-state-reasons") &&
771 	       ippGetValueTag (attr) == IPP_TAG_KEYWORD) {
772 	val = PyList_from_attr_values (attr);
773       }
774       else if (!strcmp (ippGetName (attr),
775 			"printer-is-accepting-jobs") &&
776 	       ippGetValueTag (attr) == IPP_TAG_BOOLEAN) {
777 	int b = ippGetBoolean (attr, 0);
778 	val = PyLong_FromLong (b);
779       }
780       else if ((!strcmp (ippGetName (attr),
781 			 "printer-up-time") ||
782 		!strcmp (ippGetName (attr),
783 			 "queued-job-count")) &&
784 	       ippGetValueTag (attr) == IPP_TAG_INTEGER) {
785 	int u = ippGetInteger (attr, 0);
786 	val = PyLong_FromLong (u);
787       }
788       else if ((!strcmp (ippGetName (attr), "device-uri") ||
789 		!strcmp (ippGetName (attr), "printer-uri-supported")) &&
790 	       ippGetValueTag (attr) == IPP_TAG_URI) {
791 	val = PyObj_from_UTF8 (ippGetString (attr, 0, NULL));
792       }
793       else if (!strcmp (ippGetName (attr), "printer-is-shared") &&
794 	       ippGetValueTag (attr) == IPP_TAG_BOOLEAN) {
795 	val = PyBool_FromLong (ippGetBoolean (attr, 0));
796       }
797 
798       if (val) {
799 	debugprintf ("Added %s to dict\n", ippGetName (attr));
800 	PyDict_SetItemString (dict, ippGetName (attr), val);
801 	Py_DECREF (val);
802       }
803     }
804 
805     if (printer) {
806       PyObject *key = PyObj_from_UTF8 (printer);
807       PyDict_SetItem (result, key, dict);
808       Py_DECREF (key);
809     }
810 
811     Py_DECREF (dict);
812     if (!attr)
813       break;
814   }
815 
816   ippDelete (answer);
817   debugprintf ("<- Connection_getPrinters() = dict\n");
818   return result;
819 }
820 
821 static PyObject *
Connection_getClasses(Connection * self)822 Connection_getClasses (Connection *self)
823 {
824   PyObject *result;
825   ipp_t *request = ippNewRequest(CUPS_GET_CLASSES), *answer;
826   ipp_attribute_t *attr;
827   const char *attributes[] = {
828     "printer-name",
829     "member-names",
830   };
831 
832   debugprintf ("-> Connection_getClasses()\n");
833   ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
834 		 "requested-attributes",
835 		 sizeof (attributes) / sizeof (attributes[0]),
836 		 NULL, attributes);
837   debugprintf ("cupsDoRequest(\"/\")\n");
838   Connection_begin_allow_threads (self);
839   answer = cupsDoRequest (self->http, request, "/");
840   Connection_end_allow_threads (self);
841   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
842     if (answer && ippGetStatusCode (answer) == IPP_NOT_FOUND) {
843       // No classes.
844       debugprintf ("<- Connection_getClasses() = {} (no classes)\n");
845       ippDelete (answer);
846       return PyDict_New ();
847     }
848 
849     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
850 		   answer ? NULL : cupsLastErrorString ());
851     if (answer)
852       ippDelete (answer);
853     debugprintf ("<- Connection_getClasses() (error)\n");
854     return NULL;
855   }
856 
857   result = PyDict_New ();
858   for (attr = ippFirstAttribute (answer); attr; attr = ippNextAttribute (answer)) {
859     PyObject *members = NULL;
860     char *classname = NULL;
861     char *printer_uri = NULL;
862 
863     while (attr && ippGetGroupTag (attr) != IPP_TAG_PRINTER)
864       attr = ippNextAttribute (answer);
865 
866     if (!attr)
867       break;
868 
869      for (; attr && ippGetGroupTag (attr) == IPP_TAG_PRINTER;
870 	 attr = ippNextAttribute (answer)) {
871       debugprintf ("Attribute: %s\n", ippGetName (attr));
872       if (!strcmp (ippGetName (attr), "printer-name") &&
873 	  ippGetValueTag (attr) == IPP_TAG_NAME)
874 	classname = (char *) ippGetString (attr, 0, NULL);
875       else if (!strcmp (ippGetName (attr), "printer-uri-supported") &&
876 	       ippGetValueTag (attr) == IPP_TAG_URI)
877 	printer_uri = (char *) ippGetString (attr, 0, NULL);
878       else if (!strcmp (ippGetName (attr), "member-names") &&
879 	       ippGetValueTag (attr) == IPP_TAG_NAME) {
880 	Py_XDECREF (members);
881 	members = PyList_from_attr_values (attr);
882       }
883     }
884 
885     if (printer_uri) {
886       Py_XDECREF (members);
887       members = PyObj_from_UTF8 (printer_uri);
888     }
889 
890     if (!members)
891       members = PyList_New (0);
892 
893     if (classname) {
894       PyObject *key = PyObj_from_UTF8 (classname);
895       debugprintf ("Added class %s\n", classname);
896       PyDict_SetItem (result, key, members);
897       Py_DECREF (key);
898     }
899 
900     Py_DECREF (members);
901     if (!attr)
902       break;
903   }
904 
905   ippDelete (answer);
906   debugprintf ("<- Connection_getClasses() = dict\n");
907   return result;
908 }
909 
910 static PyObject *
do_getPPDs(Connection * self,PyObject * args,PyObject * kwds,int all_lists)911 do_getPPDs (Connection *self, PyObject *args, PyObject *kwds, int all_lists)
912 {
913   PyObject *result = NULL;
914   ipp_t *request, *answer;
915   ipp_attribute_t *attr;
916   int limit = 0;
917   PyObject *exclude_schemes_obj = NULL;	/* string list */
918   PyObject *include_schemes_obj = NULL;	/* string list */
919   char *ppd_natural_language = NULL;
920   PyObject *ppd_device_id_obj = NULL;	/* UTF-8 string */
921   char *ppd_device_id;
922   PyObject *ppd_make_obj = NULL;	/* UTF-8 string */
923   char *ppd_make;
924   PyObject *ppd_make_and_model_obj = NULL; /* UTF-8 string */
925   char *ppd_make_and_model;
926   int ppd_model_number = -1;
927   PyObject *ppd_product_obj = NULL;	/* UTF-8 string */
928   char *ppd_product;
929   PyObject *ppd_psversion_obj = NULL;	/* UTF-8 string */
930   char *ppd_psversion;
931   char *ppd_type = NULL;
932   static char *kwlist[] = { "limit",
933 			    "exclude_schemes",
934 			    "include_schemes",
935 			    "ppd_natural_language",
936 			    "ppd_device_id",
937 			    "ppd_make",
938 			    "ppd_make_and_model",
939 			    "ppd_model_number",
940 			    "ppd_product",
941 			    "ppd_psversion",
942 			    "ppd_type",
943 			    NULL };
944 
945   if (!PyArg_ParseTupleAndKeywords (args, kwds, "|iOOsOOOiOOs", kwlist,
946 				    &limit,
947 				    &exclude_schemes_obj, &include_schemes_obj,
948 				    &ppd_natural_language,
949 				    &ppd_device_id_obj, &ppd_make_obj,
950 				    &ppd_make_and_model_obj,
951 				    &ppd_model_number,
952 				    &ppd_product_obj, &ppd_psversion_obj,
953 				    &ppd_type))
954     return NULL;
955 
956   request = ippNewRequest(CUPS_GET_PPDS);
957   if (limit > 0)
958     ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
959 		   "limit", limit);
960 
961   if (exclude_schemes_obj)
962     {
963       size_t i, n;
964       char **ss;
965       if (!PyList_Check (exclude_schemes_obj))
966 	{
967 	  PyErr_SetString (PyExc_TypeError, "List required (exclude_schemes)");
968 	  ippDelete (request);
969 	  return NULL;
970 	}
971 
972       n = PyList_Size (exclude_schemes_obj);
973       ss = calloc (n + 1, sizeof (char *));
974       for (i = 0; i < n; i++)
975 	{
976 	  PyObject *val = PyList_GetItem (exclude_schemes_obj, i); // borrowed
977 	  if (!PyUnicode_Check (val) && !PyBytes_Check (val))
978 	    {
979 	      PyErr_SetString (PyExc_TypeError,
980 			       "String list required (exclude_schemes)");
981 	      ippDelete (request);
982 	      while (i > 0)
983 		free (ss[--i]);
984 	      free (ss);
985 	      return NULL;
986 	    }
987 
988 	  UTF8_from_PyObj (&ss[i], val);
989 	}
990       ss[n] = NULL;
991       ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
992 		     "exclude-schemes", n, NULL, (const char **) ss);
993       for (i = 0; i < n; i++)
994 	free (ss[i]);
995       free (ss);
996     }
997 
998   if (include_schemes_obj)
999     {
1000       size_t i, n;
1001       char **ss;
1002       if (!PyList_Check (include_schemes_obj))
1003 	{
1004 	  PyErr_SetString (PyExc_TypeError, "List required (include_schemes)");
1005 	  ippDelete (request);
1006 	  return NULL;
1007 	}
1008 
1009       n = PyList_Size (include_schemes_obj);
1010       ss = calloc (n + 1, sizeof (char *));
1011       for (i = 0; i < n; i++)
1012 	{
1013 	  PyObject *val = PyList_GetItem (include_schemes_obj, i); // borrowed
1014 	  if (!PyUnicode_Check (val) && !PyBytes_Check (val))
1015 	    {
1016 	      PyErr_SetString (PyExc_TypeError,
1017 			       "String list required (include_schemes)");
1018 	      ippDelete (request);
1019 	      while (i > 0)
1020 		free (ss[--i]);
1021 	      free (ss);
1022 	      return NULL;
1023 	    }
1024 
1025 	  UTF8_from_PyObj (&ss[i], val);
1026 	}
1027       ss[n] = NULL;
1028       ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1029 		     "include-schemes", n, NULL, (const char **) ss);
1030       for (i = 0; i < n; i++)
1031 	free (ss[i]);
1032       free (ss);
1033     }
1034 
1035   if (ppd_device_id_obj)
1036     {
1037       if (UTF8_from_PyObj (&ppd_device_id, ppd_device_id_obj) == NULL)
1038 	{
1039 	  ippDelete (request);
1040 	  return NULL;
1041 	}
1042 
1043       ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
1044 		    "ppd-device-id", NULL, ppd_device_id);
1045       free (ppd_device_id);
1046     }
1047 
1048   if (ppd_make_obj)
1049     {
1050       if (UTF8_from_PyObj (&ppd_make, ppd_make_obj) == NULL)
1051 	{
1052 	  ippDelete (request);
1053 	  return NULL;
1054 	}
1055 
1056       ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
1057 		    "ppd-make", NULL, ppd_make);
1058       free (ppd_make);
1059     }
1060 
1061   if (ppd_make_and_model_obj)
1062     {
1063       if (UTF8_from_PyObj (&ppd_make_and_model, ppd_make_and_model_obj) == NULL)
1064 	{
1065 	  ippDelete (request);
1066 	  return NULL;
1067 	}
1068 
1069       ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
1070 		    "ppd-make-and-model", NULL, ppd_make_and_model);
1071       free (ppd_make_and_model);
1072     }
1073 
1074   if (ppd_model_number >= 0)
1075     ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
1076 		   "ppd-model-number", ppd_model_number);
1077 
1078   if (ppd_product_obj)
1079     {
1080       if (UTF8_from_PyObj (&ppd_product, ppd_product_obj) == NULL)
1081 	{
1082 	  ippDelete (request);
1083 	  return NULL;
1084 	}
1085 
1086       ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
1087 		    "ppd-product", NULL, ppd_product);
1088       free (ppd_product);
1089     }
1090 
1091   if (ppd_psversion_obj)
1092     {
1093       if (UTF8_from_PyObj (&ppd_psversion, ppd_psversion_obj) == NULL)
1094 	{
1095 	  ippDelete (request);
1096 	  return NULL;
1097 	}
1098 
1099       ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
1100 		    "ppd-psversion", NULL, ppd_psversion);
1101       free (ppd_psversion);
1102     }
1103 
1104   if (ppd_natural_language)
1105     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
1106 		  "ppd-natural-language", NULL, ppd_natural_language);
1107 
1108   if (ppd_type)
1109     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
1110 		  "ppd-type", NULL, ppd_type);
1111 
1112   debugprintf ("-> Connection_getPPDs()\n");
1113   debugprintf ("cupsDoRequest(\"/\")\n");
1114   Connection_begin_allow_threads (self);
1115   answer = cupsDoRequest (self->http, request, "/");
1116   Connection_end_allow_threads (self);
1117   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
1118     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
1119 		   answer ? NULL : cupsLastErrorString ());
1120     if (answer)
1121       ippDelete (answer);
1122     debugprintf ("<- Connection_getPPDs() (error)\n");
1123     return NULL;
1124   }
1125 
1126   result = PyDict_New ();
1127   for (attr = ippFirstAttribute (answer); attr; attr = ippNextAttribute (answer)) {
1128     PyObject *dict;
1129     char *ppdname = NULL;
1130 
1131     while (attr && ippGetGroupTag (attr) != IPP_TAG_PRINTER)
1132       attr = ippNextAttribute (answer);
1133 
1134     if (!attr)
1135       break;
1136 
1137     dict = PyDict_New ();
1138     for (; attr && ippGetGroupTag (attr) == IPP_TAG_PRINTER;
1139 	 attr = ippNextAttribute (answer)) {
1140       PyObject *val = NULL;
1141       debugprintf ("Attribute: %s\n", ippGetName (attr));
1142       if (!strcmp (ippGetName (attr), "ppd-name") &&
1143 	  ippGetValueTag (attr) == IPP_TAG_NAME)
1144 	ppdname = (char *) ippGetString (attr, 0, NULL);
1145       else {
1146 	if (all_lists)
1147 	  val = PyList_from_attr_values (attr);
1148 	else
1149 	  val = PyObject_from_attr_value (attr, 0);
1150 
1151 	if (val) {
1152 	  debugprintf ("Adding %s to ppd dict\n", ippGetName (attr));
1153 	  PyDict_SetItemString (dict, ippGetName (attr), val);
1154 	  Py_DECREF (val);
1155 	}
1156       }
1157     }
1158 
1159     if (ppdname) {
1160       PyObject *key = PyObj_from_UTF8 (ppdname);
1161       debugprintf ("Adding %s to result dict\n", ppdname);
1162       PyDict_SetItem (result, key, dict);
1163       Py_DECREF (key);
1164     }
1165 
1166     Py_DECREF (dict);
1167     if (!attr)
1168       break;
1169   }
1170 
1171   ippDelete (answer);
1172   debugprintf ("<- Connection_getPPDs() = dict\n");
1173   return result;
1174 }
1175 
1176 static PyObject *
Connection_getPPDs(Connection * self,PyObject * args,PyObject * kwds)1177 Connection_getPPDs (Connection *self, PyObject *args, PyObject *kwds)
1178 {
1179   return do_getPPDs (self, args, kwds, 0);
1180 }
1181 
1182 static PyObject *
Connection_getPPDs2(Connection * self,PyObject * args,PyObject * kwds)1183 Connection_getPPDs2 (Connection *self, PyObject *args, PyObject *kwds)
1184 {
1185   return do_getPPDs (self, args, kwds, 1);
1186 }
1187 
1188 static PyObject *
Connection_getServerPPD(Connection * self,PyObject * args)1189 Connection_getServerPPD (Connection *self, PyObject *args)
1190 {
1191   const char *ppd_name, *filename;
1192   if (!PyArg_ParseTuple (args, "s", &ppd_name))
1193     return NULL;
1194   debugprintf ("-> Connection_getServerPPD()\n");
1195   Connection_begin_allow_threads (self);
1196   filename = cupsGetServerPPD (self->http, ppd_name);
1197   Connection_end_allow_threads (self);
1198   if (!filename) {
1199     set_ipp_error (cupsLastError (), cupsLastErrorString ());
1200     debugprintf ("<- Connection_getServerPPD() (error)\n");
1201     return NULL;
1202   }
1203   debugprintf ("<- Connection_getServerPPD(\"%s\") = \"%s\"\n",
1204 	       ppd_name, filename);
1205   return PyUnicode_FromString (filename);
1206 }
1207 
1208 static PyObject *
Connection_getDocument(Connection * self,PyObject * args)1209 Connection_getDocument (Connection *self, PyObject *args)
1210 {
1211   PyObject *dict;
1212   PyObject *obj;
1213   PyObject *uriobj;
1214   char *uri;
1215   int jobid, docnum;
1216   ipp_t *request, *answer;
1217   ipp_attribute_t *attr;
1218   const char *format = NULL;
1219   const char *name = NULL;
1220   char docfilename[PATH_MAX];
1221   int fd;
1222 
1223   if (!PyArg_ParseTuple (args, "Oii", &uriobj, &jobid, &docnum))
1224     return NULL;
1225 
1226   if (UTF8_from_PyObj (&uri, uriobj) == NULL)
1227     return NULL;
1228 
1229   debugprintf ("-> Connection_getDocument(\"%s\",%d)\n", uri, jobid);
1230   request = ippNewRequest (CUPS_GET_DOCUMENT);
1231   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
1232 		"printer-uri", NULL, uri);
1233   free (uri);
1234   ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
1235 		 "job-id", jobid);
1236   ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
1237 		 "document-number", docnum);
1238 
1239   snprintf(docfilename, sizeof (docfilename), "%s/jobdoc-XXXXXX", _PATH_TMP);
1240   fd = mkstemp (docfilename);
1241   if (fd < 0) {
1242     debugprintf ("<- Connection_getDocument() EXCEPTION\n");
1243     ippDelete (request);
1244     return PyErr_SetFromErrno (PyExc_RuntimeError);
1245   }
1246 
1247   Connection_begin_allow_threads (self);
1248   answer = cupsDoIORequest (self->http, request, "/", -1, fd);
1249   Connection_end_allow_threads (self);
1250 
1251   close (fd);
1252   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
1253     unlink (docfilename);
1254     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
1255 		   answer ? NULL : cupsLastErrorString ());
1256     if (answer)
1257       ippDelete (answer);
1258     debugprintf ("<- Connection_getDocument() (error)\n");
1259     return NULL;
1260   }
1261 
1262   if ((attr = ippFindAttribute (answer, "document-format",
1263 				IPP_TAG_MIMETYPE)) != NULL)
1264     format = ippGetString (attr, 0, NULL);
1265 
1266   if ((attr = ippFindAttribute (answer, "document-name",
1267 				IPP_TAG_NAME)) != NULL)
1268     name = ippGetString (attr, 0, NULL);
1269 
1270   dict = PyDict_New ();
1271 
1272   obj = PyUnicode_FromString (docfilename);
1273   PyDict_SetItemString (dict, "file", obj);
1274   Py_DECREF (obj);
1275 
1276   if (format) {
1277     obj = PyUnicode_FromString (format);
1278     PyDict_SetItemString (dict, "document-format", obj);
1279     Py_DECREF (obj);
1280   }
1281 
1282   if (name) {
1283     obj = PyObj_from_UTF8 (name);
1284     PyDict_SetItemString (dict, "document-name", obj);
1285     Py_DECREF (obj);
1286   }
1287 
1288   debugprintf ("<- Connection_getDocument() = {'file':\"%s\","
1289 	       "'document-format':\"%s\",'document-name':\"%s\"}\n",
1290 	       docfilename, format ? format : "(nul)",
1291 	       name ? name : "(nul)");
1292   ippDelete (answer);
1293   return dict;
1294 }
1295 
1296 static PyObject *
Connection_getDevices(Connection * self,PyObject * args,PyObject * kwds)1297 Connection_getDevices (Connection *self, PyObject *args, PyObject *kwds)
1298 {
1299   PyObject *result;
1300   ipp_t *request, *answer;
1301   ipp_attribute_t *attr;
1302   int limit = 0;
1303   int timeout = 0;
1304   PyObject *exclude_schemes = NULL;
1305   PyObject *include_schemes = NULL;
1306   static char *kwlist[] = { "limit",
1307 			    "exclude_schemes",
1308 			    "include_schemes",
1309 			    "timeout",
1310 			    NULL };
1311 
1312   if (!PyArg_ParseTupleAndKeywords (args, kwds, "|iOOi", kwlist, &limit,
1313 				    &exclude_schemes, &include_schemes,
1314 				    &timeout))
1315     return NULL;
1316 
1317   request = ippNewRequest(CUPS_GET_DEVICES);
1318   if (limit > 0)
1319     ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
1320 		   "limit", limit);
1321 
1322   if (exclude_schemes)
1323     {
1324       size_t i, n;
1325       char **ss;
1326       if (!PyList_Check (exclude_schemes))
1327 	{
1328 	  PyErr_SetString (PyExc_TypeError, "List required (exclude_schemes)");
1329 	  ippDelete (request);
1330 	  return NULL;
1331 	}
1332 
1333       n = PyList_Size (exclude_schemes);
1334       ss = calloc (n + 1, sizeof (char *));
1335       for (i = 0; i < n; i++)
1336 	{
1337 	  PyObject *val = PyList_GetItem (exclude_schemes, i); // borrowed ref
1338 	  if (!PyUnicode_Check (val) && !PyBytes_Check (val))
1339 	    {
1340 	      PyErr_SetString (PyExc_TypeError,
1341 			       "String list required (exclude_schemes)");
1342 	      ippDelete (request);
1343 	      while (i > 0)
1344 		free (ss[--i]);
1345 	      free (ss);
1346 	      return NULL;
1347 	    }
1348 
1349 	  UTF8_from_PyObj (&ss[i], val);
1350 	}
1351 
1352       ss[n] = NULL;
1353       ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1354 		     "exclude-schemes", n, NULL, (const char **) ss);
1355       for (i = 0; i < n; i++)
1356 	free (ss[i]);
1357       free (ss);
1358     }
1359 
1360   if (include_schemes)
1361     {
1362       size_t i, n;
1363       char **ss;
1364       if (!PyList_Check (include_schemes))
1365 	{
1366 	  PyErr_SetString (PyExc_TypeError, "List required (include_schemes)");
1367 	  ippDelete (request);
1368 	  return NULL;
1369 	}
1370 
1371       n = PyList_Size (include_schemes);
1372       ss = calloc (n + 1, sizeof (char *));
1373       for (i = 0; i < n; i++)
1374 	{
1375 	  PyObject *val = PyList_GetItem (include_schemes, i); // borrowed ref
1376 	  if (!PyUnicode_Check (val) && !PyBytes_Check (val))
1377 	    {
1378 	      PyErr_SetString (PyExc_TypeError,
1379 			       "String list required (include_schemes)");
1380 	      ippDelete (request);
1381 	      while (i > 0)
1382 		free (ss[--i]);
1383 	      free (ss);
1384 	      return NULL;
1385 	    }
1386 
1387 	  UTF8_from_PyObj (&ss[i], val);
1388 	}
1389 
1390       ss[n] = NULL;
1391       ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1392 		     "include-schemes", n, NULL, (const char **) ss);
1393       for (i = 0; i < n; i++)
1394 	free (ss[i]);
1395       free (ss);
1396     }
1397 
1398   if (timeout > 0)
1399     ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
1400 		   "timeout", timeout);
1401 
1402   debugprintf ("-> Connection_getDevices()\n");
1403   debugprintf ("cupsDoRequest(\"/\")\n");
1404   Connection_begin_allow_threads (self);
1405   answer = cupsDoRequest (self->http, request, "/");
1406   Connection_end_allow_threads (self);
1407   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
1408     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
1409 		   answer ? NULL : cupsLastErrorString ());
1410     if (answer)
1411       ippDelete (answer);
1412     debugprintf ("<- Connection_getDevices() (error)\n");
1413     return NULL;
1414   }
1415 
1416   result = PyDict_New ();
1417   for (attr = ippFirstAttribute (answer); attr; attr = ippNextAttribute (answer)) {
1418     PyObject *dict;
1419     char *device_uri = NULL;
1420 
1421     while (attr && ippGetGroupTag (attr) != IPP_TAG_PRINTER)
1422       attr = ippNextAttribute (answer);
1423 
1424     if (!attr)
1425       break;
1426 
1427     dict = PyDict_New ();
1428     for (; attr && ippGetGroupTag (attr) == IPP_TAG_PRINTER;
1429 	 attr = ippNextAttribute (answer)) {
1430       PyObject *val = NULL;
1431 
1432       debugprintf ("Attribute: %s\n", ippGetName (attr));
1433       if (!strcmp (ippGetName (attr), "device-uri") &&
1434 	  ippGetValueTag (attr) == IPP_TAG_URI)
1435 	device_uri = (char *) ippGetString (attr, 0, NULL);
1436       else
1437 	val = PyObject_from_attr_value (attr, 0);
1438 
1439       if (val) {
1440 	debugprintf ("Adding %s to device dict\n", ippGetName (attr));
1441 	PyDict_SetItemString (dict, ippGetName (attr), val);
1442 	Py_DECREF (val);
1443       }
1444     }
1445 
1446     if (device_uri) {
1447       PyObject *key = PyObj_from_UTF8 (device_uri);
1448       debugprintf ("Adding %s to result dict\n", device_uri);
1449       PyDict_SetItem (result, key, dict);
1450       Py_DECREF (key);
1451     }
1452 
1453     Py_DECREF (dict);
1454     if (!attr)
1455       break;
1456   }
1457 
1458   ippDelete (answer);
1459   debugprintf ("<- Connection_getDevices() = dict\n");
1460   return result;
1461 }
1462 
1463 static int
get_requested_attrs(PyObject * requested_attrs,size_t * n_attrs,char *** attrs)1464 get_requested_attrs (PyObject *requested_attrs, size_t *n_attrs, char ***attrs)
1465 {
1466   long unsigned int i;
1467   size_t n;
1468   char **as;
1469 
1470   if (!PyList_Check (requested_attrs)) {
1471     PyErr_SetString (PyExc_TypeError, "List required");
1472     return -1;
1473   }
1474 
1475   n = PyList_Size (requested_attrs);
1476   as = malloc ((n + 1) * sizeof (char *));
1477   for (i = 0; i < n; i++) {
1478     PyObject *val = PyList_GetItem (requested_attrs, i); // borrowed ref
1479     if (!PyUnicode_Check (val) && !PyBytes_Check (val)) {
1480       PyErr_SetString (PyExc_TypeError, "String required");
1481       while (--i >= 0)
1482 	free (as[i]);
1483       free (as);
1484       return -1;
1485     }
1486 
1487     UTF8_from_PyObj (&as[i], val);
1488   }
1489   as[n] = NULL;
1490 
1491   debugprintf ("Requested attributes:\n");
1492   for (i = 0; as[i] != NULL; i++)
1493     debugprintf ("  %s\n", as[i]);
1494 
1495   *n_attrs = n;
1496   *attrs = as;
1497   return 0;
1498 }
1499 
1500 static void
free_requested_attrs(size_t n_attrs,char ** attrs)1501 free_requested_attrs (size_t n_attrs, char **attrs)
1502 {
1503   long unsigned int i;
1504   for (i = 0; i < n_attrs; i++)
1505     free (attrs[i]);
1506   free (attrs);
1507 }
1508 
1509 static PyObject *
Connection_getJobs(Connection * self,PyObject * args,PyObject * kwds)1510 Connection_getJobs (Connection *self, PyObject *args, PyObject *kwds)
1511 {
1512   PyObject *result;
1513   ipp_t *request, *answer;
1514   ipp_attribute_t *attr;
1515   char *which = NULL;
1516   int my_jobs = 0;
1517   int limit = -1;
1518   int first_job_id = -1;
1519   PyObject *requested_attrs = NULL;
1520   char **attrs = NULL; /* initialised to calm compiler */
1521   size_t n_attrs = 0; /* initialised to calm compiler */
1522   static char *kwlist[] = { "which_jobs", "my_jobs", "limit", "first_job_id",
1523 			    "requested_attributes", NULL };
1524   if (!PyArg_ParseTupleAndKeywords (args, kwds, "|siiiO", kwlist,
1525 				    &which, &my_jobs, &limit, &first_job_id,
1526 				    &requested_attrs))
1527     return NULL;
1528 
1529   debugprintf ("-> Connection_getJobs(%s,%d)\n",
1530 	       which ? which : "(null)", my_jobs);
1531   request = ippNewRequest(IPP_GET_JOBS);
1532   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
1533 		NULL, "ipp://localhost/printers/");
1534 
1535   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "which-jobs",
1536 		NULL, which ? which : "not-completed");
1537 
1538   ippAddBoolean (request, IPP_TAG_OPERATION, "my-jobs", my_jobs);
1539   if (my_jobs)
1540     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1541 		  "requesting-user-name", NULL, cupsUser());
1542 
1543   if (limit > 0)
1544     ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
1545 		   "limit", limit);
1546 
1547   if (first_job_id > 0)
1548     ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
1549 		   "first-job-id", first_job_id);
1550 
1551   if (requested_attrs) {
1552     if (get_requested_attrs (requested_attrs, &n_attrs, &attrs) == -1) {
1553       ippDelete (request);
1554       return NULL;
1555     }
1556 
1557     ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
1558 		   "requested-attributes", n_attrs, NULL,
1559 		   (const char **) attrs);
1560     free_requested_attrs (n_attrs, attrs);
1561   }
1562 
1563   debugprintf ("cupsDoRequest(\"/\")\n");
1564   Connection_begin_allow_threads (self);
1565   answer = cupsDoRequest (self->http, request, "/");
1566   Connection_end_allow_threads (self);
1567   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
1568     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
1569 		   answer ? NULL : cupsLastErrorString ());
1570     if (answer)
1571       ippDelete (answer);
1572     debugprintf ("<- Connection_getJobs() (error)\n");
1573     return NULL;
1574   }
1575 
1576   result = PyDict_New ();
1577   for (attr = ippFirstAttribute (answer); attr; attr = ippNextAttribute (answer)) {
1578     PyObject *dict;
1579     int job_id = -1;
1580 
1581     while (attr && ippGetGroupTag (attr) != IPP_TAG_JOB)
1582       attr = ippNextAttribute (answer);
1583 
1584     if (!attr)
1585       break;
1586 
1587     dict = PyDict_New ();
1588     for (; attr && ippGetGroupTag (attr) == IPP_TAG_JOB;
1589 	 attr = ippNextAttribute (answer)) {
1590       PyObject *val = NULL;
1591 
1592       debugprintf ("Attribute: %s\n", ippGetName (attr));
1593       if (!strcmp (ippGetName (attr), "job-id") &&
1594 	  ippGetValueTag (attr) == IPP_TAG_INTEGER)
1595 	job_id = ippGetInteger (attr, 0);
1596       else if (((!strcmp (ippGetName (attr), "job-k-octets") ||
1597 		 !strcmp (ippGetName (attr), "job-priority") ||
1598 		 !strcmp (ippGetName (attr), "time-at-creation") ||
1599 		 !strcmp (ippGetName (attr), "time-at-processing") ||
1600 		 !strcmp (ippGetName (attr), "time-at-completed") ||
1601 		 !strcmp (ippGetName (attr), "job-media-sheets") ||
1602 		 !strcmp (ippGetName (attr), "job-media-sheets-completed")) &&
1603 		ippGetValueTag (attr) == IPP_TAG_INTEGER) ||
1604 	       (!strcmp (ippGetName (attr), "job-state") &&
1605 		ippGetValueTag (attr) == IPP_TAG_ENUM))
1606 	val = PyLong_FromLong (ippGetInteger (attr, 0));
1607       else if ((!strcmp (ippGetName (attr), "job-name") &&
1608 		ippGetValueTag (attr) == IPP_TAG_NAME) ||
1609 	       (!strcmp (ippGetName (attr), "job-originating-user-name") &&
1610 		ippGetValueTag (attr) == IPP_TAG_NAME) ||
1611 	       (!strcmp (ippGetName (attr), "job-printer-uri") &&
1612 		ippGetValueTag (attr) == IPP_TAG_URI))
1613 	val = PyObj_from_UTF8 (ippGetString (attr, 0, NULL));
1614       else if (!strcmp (ippGetName (attr), "job-preserved") &&
1615 	       ippGetValueTag (attr) == IPP_TAG_BOOLEAN)
1616 	val = PyBool_FromLong (ippGetInteger (attr, 0));
1617       else {
1618 	if (ippGetCount (attr) > 1)
1619 	  val = PyList_from_attr_values (attr);
1620 	else
1621 	  val = PyObject_from_attr_value (attr, 0);
1622       }
1623 
1624       if (val) {
1625 	debugprintf ("Adding %s to job dict\n", ippGetName (attr));
1626 	PyDict_SetItemString (dict, ippGetName (attr), val);
1627 	Py_DECREF (val);
1628       }
1629     }
1630 
1631     if (job_id != -1) {
1632       debugprintf ("Adding %d to result dict\n", job_id);
1633       PyObject *job_obj = PyLong_FromLong (job_id);
1634       PyDict_SetItem (result, job_obj, dict);
1635       Py_DECREF (job_obj);
1636     }
1637 
1638     Py_DECREF (dict);
1639 
1640     if (!attr)
1641       break;
1642   }
1643 
1644   ippDelete (answer);
1645   debugprintf ("<- Connection_getJobs() = dict\n");
1646   return result;
1647 }
1648 
1649 static PyObject *
Connection_getJobAttributes(Connection * self,PyObject * args,PyObject * kwds)1650 Connection_getJobAttributes (Connection *self, PyObject *args, PyObject *kwds)
1651 {
1652   PyObject *result;
1653   ipp_t *request, *answer;
1654   ipp_attribute_t *attr;
1655   int job_id;
1656   PyObject *requested_attrs = NULL;
1657   char **attrs = NULL; /* initialised to calm compiler */
1658   size_t n_attrs = 0; /* initialised to calm compiler */
1659   char uri[1024];
1660   static char *kwlist[] = { "job_id", "requested_attributes", NULL };
1661   if (!PyArg_ParseTupleAndKeywords (args, kwds, "i|O", kwlist,
1662 				    &job_id, &requested_attrs))
1663     return NULL;
1664 
1665   if (requested_attrs) {
1666     if (get_requested_attrs (requested_attrs, &n_attrs, &attrs) == -1)
1667       return NULL;
1668   }
1669 
1670   debugprintf ("-> Connection_getJobAttributes(%d)\n", job_id);
1671   request = ippNewRequest(IPP_GET_JOB_ATTRIBUTES);
1672   snprintf (uri, sizeof (uri), "ipp://localhost/jobs/%d", job_id);
1673   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri",
1674 		NULL, uri);
1675   if (requested_attrs)
1676     ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
1677 		   "requested-attributes", n_attrs, NULL,
1678 		   (const char **) attrs);
1679 
1680   debugprintf ("cupsDoRequest(\"/\")\n");
1681   Connection_begin_allow_threads (self);
1682   answer = cupsDoRequest (self->http, request, "/");
1683   Connection_end_allow_threads (self);
1684   if (requested_attrs)
1685     free_requested_attrs (n_attrs, attrs);
1686   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
1687     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
1688 		   answer ? NULL : cupsLastErrorString ());
1689     if (answer)
1690       ippDelete (answer);
1691     debugprintf ("<- Connection_getJobAttributes() (error)\n");
1692     return NULL;
1693   }
1694 
1695   result = PyDict_New ();
1696   for (attr = ippFirstAttribute (answer); attr; attr = ippNextAttribute (answer)) {
1697     PyObject *obj;
1698 
1699     debugprintf ("Attr: %s\n", ippGetName (attr));
1700     if (ippGetCount (attr) > 1 ||
1701 	!strcmp (ippGetName (attr), "job-printer-state-reasons"))
1702       obj = PyList_from_attr_values (attr);
1703     else
1704       obj = PyObject_from_attr_value (attr, 0);
1705 
1706     if (!obj)
1707       // Can't represent this.
1708       continue;
1709 
1710     PyDict_SetItemString (result, ippGetName (attr), obj);
1711     Py_DECREF (obj);
1712   }
1713 
1714   ippDelete (answer);
1715   debugprintf ("<- Connection_getJobAttributes() = dict\n");
1716   return result;
1717 }
1718 
1719 static PyObject *
Connection_cancelJob(Connection * self,PyObject * args,PyObject * kwds)1720 Connection_cancelJob (Connection *self, PyObject *args, PyObject *kwds)
1721 {
1722   ipp_t *request, *answer;
1723   int job_id;
1724   int purge_job = 0;
1725   char uri[1024];
1726   static char *kwlist[] = { "job_id", "purge_job", NULL };
1727   if (!PyArg_ParseTupleAndKeywords (args, kwds, "i|i", kwlist,
1728 				    &job_id, &purge_job))
1729     return NULL;
1730 
1731   debugprintf ("-> Connection_cancelJob(%d)\n", job_id);
1732   request = ippNewRequest(IPP_CANCEL_JOB);
1733   snprintf (uri, sizeof (uri), "ipp://localhost/jobs/%d", job_id);
1734   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
1735   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1736 		"requesting-user-name", NULL, cupsUser ());
1737   if (purge_job)
1738     ippAddBoolean(request, IPP_TAG_OPERATION, "purge-job", 1);
1739   debugprintf ("cupsDoRequest(\"/jobs/\")\n");
1740   Connection_begin_allow_threads (self);
1741   answer = cupsDoRequest (self->http, request, "/jobs/");
1742   Connection_end_allow_threads (self);
1743   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
1744     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
1745 		   answer ? NULL : cupsLastErrorString ());
1746     if (answer)
1747       ippDelete (answer);
1748     debugprintf ("<- Connection_cancelJob() (error)\n");
1749     return NULL;
1750   }
1751 
1752   debugprintf ("<- Connection_cancelJob() = None\n");
1753   Py_RETURN_NONE;
1754 }
1755 
1756 static PyObject *
Connection_cancelAllJobs(Connection * self,PyObject * args,PyObject * kwds)1757 Connection_cancelAllJobs (Connection *self, PyObject *args, PyObject *kwds)
1758 {
1759   PyObject *nameobj = NULL;
1760   char *name;
1761   PyObject *uriobj = NULL;
1762   char *uri;
1763   char consuri[HTTP_MAX_URI];
1764   ipp_t *request, *answer;
1765   int my_jobs = 0;
1766   int purge_jobs = 1;
1767   int i;
1768   static char *kwlist[] = { "name", "uri", "my_jobs", "purge_jobs", NULL };
1769   if (!PyArg_ParseTupleAndKeywords (args, kwds, "|OOii", kwlist,
1770 				    &nameobj, &uriobj, &my_jobs, &purge_jobs))
1771     return NULL;
1772 
1773   if (nameobj && uriobj) {
1774     PyErr_SetString (PyExc_RuntimeError,
1775 		     "name or uri must be specified but not both");
1776     return NULL;
1777   }
1778 
1779   if (nameobj) {
1780     if (UTF8_from_PyObj (&name, nameobj) == NULL)
1781       return NULL;
1782   } else if (uriobj) {
1783     if (UTF8_from_PyObj (&uri, uriobj) == NULL)
1784       return NULL;
1785   } else {
1786     PyErr_SetString (PyExc_RuntimeError,
1787 		     "name or uri must be specified");
1788     return NULL;
1789   }
1790 
1791   debugprintf ("-> Connection_cancelAllJobs(%s, my_jobs=%d, purge_jobs=%d)\n",
1792 	       nameobj ? name : uri, my_jobs, purge_jobs);
1793   if (nameobj) {
1794     construct_uri (consuri, sizeof (consuri),
1795 		   "ipp://localhost/printers/", name);
1796     uri = consuri;
1797   }
1798 
1799   for (i = 0; i < 2; i++) {
1800     request = ippNewRequest(IPP_PURGE_JOBS);
1801     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
1802 		  NULL, uri);
1803 
1804     if (my_jobs)
1805     {
1806       ippAddBoolean (request, IPP_TAG_OPERATION, "my-jobs", my_jobs);
1807       ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1808 		    "requesting-user-name", NULL, cupsUser());
1809     }
1810 
1811     ippAddBoolean (request, IPP_TAG_OPERATION, "purge-jobs", purge_jobs);
1812     debugprintf ("cupsDoRequest(\"/admin/\") with printer-uri=%s\n", uri);
1813     Connection_begin_allow_threads (self);
1814     answer = cupsDoRequest (self->http, request, "/admin/");
1815     Connection_end_allow_threads (self);
1816     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
1817       ippDelete (answer);
1818       if (uriobj)
1819 	break;
1820 
1821       // Perhaps it's a class, not a printer.
1822       construct_uri (consuri, sizeof (consuri),
1823 		     "ipp://localhost/classes/", name);
1824     } else break;
1825   }
1826 
1827   if (nameobj)
1828     free (name);
1829 
1830   if (uriobj)
1831     free (uri);
1832 
1833   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
1834     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
1835 		   answer ? NULL : cupsLastErrorString ());
1836     if (answer)
1837       ippDelete (answer);
1838 
1839     debugprintf ("<- Connection_cancelAllJobs() (error)\n");
1840     return NULL;
1841   }
1842 
1843   debugprintf ("<- Connection_cancelAllJobs() = None\n");
1844   Py_RETURN_NONE;
1845 }
1846 
1847 static PyObject *
Connection_createJob(Connection * self,PyObject * args,PyObject * kwds)1848 Connection_createJob (Connection *self, PyObject *args, PyObject *kwds)
1849 {
1850   static char *kwlist[] = { "printer", "title", "options", NULL };
1851   PyObject *printer_obj;
1852   char *printer;
1853   PyObject *title_obj;
1854   char *title;
1855   PyObject *options_obj, *key, *val;
1856   int num_settings = 0;
1857   DICT_POS_TYPE pos = 0;
1858   cups_option_t *settings = NULL;
1859   int jobid;
1860 
1861   if (!PyArg_ParseTupleAndKeywords (args, kwds, "OOO", kwlist,
1862 				    &printer_obj, &title_obj,
1863 				    &options_obj))
1864     return NULL;
1865 
1866   if (UTF8_from_PyObj (&printer, printer_obj) == NULL)
1867     return NULL;
1868   if (UTF8_from_PyObj (&title, title_obj) == NULL) {
1869     free (printer);
1870     return NULL;
1871   }
1872   debugprintf ("-> Connection_createJob(printer=%s, title=%s)\n", printer, title);
1873 
1874   if (!PyDict_Check (options_obj)) {
1875     free (title);
1876     free (printer);
1877     PyErr_SetString (PyExc_TypeError, "options must be a dict");
1878     return NULL;
1879   }
1880   while (PyDict_Next (options_obj, &pos, &key, &val)) {
1881     char *name, *value;
1882     if ((!PyUnicode_Check (key) && !PyBytes_Check (key)) ||
1883         (!PyUnicode_Check (val) && !PyBytes_Check (val))) {
1884       cupsFreeOptions (num_settings, settings);
1885       free (title);
1886       free (printer);
1887       PyErr_SetString (PyExc_TypeError, "Keys and values must be strings");
1888       return NULL;
1889     }
1890 
1891     num_settings = cupsAddOption (UTF8_from_PyObj (&name, key),
1892 				  UTF8_from_PyObj (&value, val),
1893 				  num_settings,
1894 				  &settings);
1895     free (name);
1896     free (value);
1897   }
1898 
1899   Connection_begin_allow_threads (self);
1900   jobid = cupsCreateJob(self->http, printer, title, num_settings, settings);
1901   Connection_end_allow_threads (self);
1902 
1903   if (jobid == 0) {
1904     cupsFreeOptions (num_settings, settings);
1905     free (title);
1906     free (printer);
1907     set_ipp_error (cupsLastError (), cupsLastErrorString ());
1908     debugprintf ("<- Connection_createJob() = NULL\n");
1909     return NULL;
1910   }
1911 
1912   cupsFreeOptions (num_settings, settings);
1913   free (title);
1914   free (printer);
1915   debugprintf ("<- Connection_createJob() = %d\n", jobid);
1916   return PyLong_FromLong (jobid);
1917 }
1918 
1919 static PyObject *
Connection_startDocument(Connection * self,PyObject * args,PyObject * kwds)1920 Connection_startDocument (Connection *self, PyObject *args, PyObject *kwds)
1921 {
1922   static char *kwlist[] = { "printer", "job_id", "doc_name", "format", "last_document", NULL };
1923   PyObject *printer_obj;
1924   char *printer;
1925   int jobid;
1926   PyObject *doc_name_obj;
1927   char *doc_name;
1928   PyObject *format_obj;
1929   char *format;
1930   int last_document;
1931   http_status_t	status;		/* Write status */
1932 
1933   if (!PyArg_ParseTupleAndKeywords (args, kwds, "OiOOi", kwlist,
1934 				    &printer_obj, &jobid, &doc_name_obj,
1935 				    &format_obj, &last_document))
1936     return NULL;
1937 
1938   if (UTF8_from_PyObj (&printer, printer_obj) == NULL)
1939     return NULL;
1940   if (UTF8_from_PyObj (&doc_name, doc_name_obj) == NULL) {
1941     free (printer);
1942     return NULL;
1943   }
1944   if (UTF8_from_PyObj (&format, format_obj) == NULL) {
1945     free (doc_name);
1946     free (printer);
1947     return NULL;
1948   }
1949   debugprintf ("-> Connection_startDocument(printer=%s, jobid=%d, doc_name=%s, format=%s)\n",
1950               printer, jobid, doc_name, format);
1951 
1952   Connection_begin_allow_threads (self);
1953   status = cupsStartDocument(self->http, printer, jobid, doc_name, format, last_document);
1954   Connection_end_allow_threads (self);
1955 
1956   if (status != HTTP_CONTINUE)
1957   {
1958     free (format);
1959     free (doc_name);
1960     free (printer);
1961     set_ipp_error (cupsLastError (), cupsLastErrorString ());
1962     debugprintf ("<- Connection_startDocument() = NULL\n");
1963     return NULL;
1964   }
1965 
1966   free (format);
1967   free (doc_name);
1968   free (printer);
1969   debugprintf ("<- Connection_startDocument() = %d\n", status);
1970   return PyLong_FromLong (status);
1971 }
1972 
1973 static PyObject *
Connection_writeRequestData(Connection * self,PyObject * args,PyObject * kwds)1974 Connection_writeRequestData (Connection *self, PyObject *args, PyObject *kwds)
1975 {
1976   static char *kwlist[] = { "buffer", "length", NULL };
1977   PyObject *buffer_obj;
1978   char *buffer;
1979   int length;
1980   http_status_t	status;		/* Write status */
1981 
1982   if (!PyArg_ParseTupleAndKeywords (args, kwds, "Oi", kwlist,
1983 				    &buffer_obj, &length))
1984     return NULL;
1985 
1986   buffer = (char *) malloc((size_t) length);
1987   memcpy(buffer, PyBytes_AsString(buffer_obj), length);
1988   debugprintf ("-> Connection_writeRequestData(length=%d)\n", length);
1989 
1990   Connection_begin_allow_threads (self);
1991   status = cupsWriteRequestData(self->http, buffer, length);
1992   Connection_end_allow_threads (self);
1993 
1994   if (status != HTTP_CONTINUE)
1995   {
1996     free (buffer);
1997     set_ipp_error (cupsLastError (), cupsLastErrorString ());
1998     debugprintf ("<- Connection_writeRequestData() = NULL\n");
1999     return NULL;
2000   }
2001 
2002   free (buffer);
2003   debugprintf ("<- Connection_writeRequestData() = %d\n", status);
2004   return PyLong_FromLong (status);
2005 }
2006 
2007 static PyObject *
Connection_finishDocument(Connection * self,PyObject * args,PyObject * kwds)2008 Connection_finishDocument (Connection *self, PyObject *args, PyObject *kwds)
2009 {
2010   static char *kwlist[] = { "printer", NULL };
2011   PyObject *printer_obj;
2012   char *printer;
2013   int answer;
2014 
2015   if (!PyArg_ParseTupleAndKeywords (args, kwds, "O", kwlist, &printer_obj))
2016     return NULL;
2017 
2018   if (UTF8_from_PyObj (&printer, printer_obj) == NULL)
2019     return NULL;
2020 
2021   debugprintf ("-> Connection_finishDocument(printer=%s)\n", printer);
2022   Connection_begin_allow_threads (self);
2023   answer = cupsFinishDocument(self->http, printer);
2024   Connection_end_allow_threads (self);
2025 
2026   if (answer != IPP_OK) {
2027     free (printer);
2028     set_ipp_error (cupsLastError (), cupsLastErrorString ());
2029     debugprintf ("<- Connection_finishDocument() = NULL\n");
2030     return NULL;
2031   }
2032 
2033   free (printer);
2034   debugprintf ("<- Connection_finishDicument() = %d\n", answer);
2035   return PyLong_FromLong (answer);
2036 }
2037 
2038 static PyObject *
Connection_moveJob(Connection * self,PyObject * args,PyObject * kwds)2039 Connection_moveJob (Connection *self, PyObject *args, PyObject *kwds)
2040 {
2041   int job_id = -1;
2042   PyObject *printeruriobj = NULL;
2043   char *printeruri;
2044   PyObject *jobprinteruriobj = NULL;
2045   char *jobprinteruri;
2046   ipp_t *request, *answer;
2047   static char *kwlist[] = { "printer_uri", "job_id", "job_printer_uri", NULL };
2048 
2049   if (!PyArg_ParseTupleAndKeywords (args, kwds, "|OiO", kwlist,
2050 				    &printeruriobj, &job_id,
2051 				    &jobprinteruriobj))
2052     return NULL;
2053 
2054   if (!jobprinteruriobj) {
2055     PyErr_SetString (PyExc_RuntimeError,
2056 		     "No job_printer_uri (destination) given");
2057     return NULL;
2058   }
2059 
2060   if (printeruriobj) {
2061     if (UTF8_from_PyObj (&printeruri, printeruriobj) == NULL)
2062       return NULL;
2063   } else if (job_id == -1) {
2064     PyErr_SetString (PyExc_RuntimeError,
2065 		     "job_id or printer_uri required");
2066     return NULL;
2067   }
2068 
2069   if (UTF8_from_PyObj (&jobprinteruri, jobprinteruriobj) == NULL) {
2070     if (printeruriobj)
2071       free (printeruri);
2072     return NULL;
2073   }
2074 
2075   request = ippNewRequest (CUPS_MOVE_JOB);
2076   if (!printeruriobj) {
2077     char joburi[HTTP_MAX_URI];
2078     snprintf (joburi, sizeof (joburi), "ipp://localhost/jobs/%d", job_id);
2079     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL,
2080 		  joburi);
2081   } else {
2082     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
2083 		  printeruri);
2084     free (printeruri);
2085 
2086     if (job_id != -1)
2087       ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id",
2088 		     job_id);
2089   }
2090 
2091   ippAddString (request, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL,
2092 		jobprinteruri);
2093   free (jobprinteruri);
2094   Connection_begin_allow_threads (self);
2095   answer = cupsDoRequest (self->http, request, "/jobs");
2096   Connection_end_allow_threads (self);
2097   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2098     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2099 		   answer ? NULL : cupsLastErrorString ());
2100     if (answer)
2101       ippDelete (answer);
2102     return NULL;
2103   }
2104 
2105   ippDelete (answer);
2106   Py_RETURN_NONE;
2107 }
2108 
2109 static PyObject *
Connection_authenticateJob(Connection * self,PyObject * args)2110 Connection_authenticateJob (Connection *self, PyObject *args)
2111 {
2112   ipp_t *request, *answer;
2113   int job_id;
2114   PyObject *auth_info_list = NULL;
2115   long unsigned int num_auth_info = 0; /* initialised to calm compiler */
2116   char *auth_info_values[3];
2117   long unsigned int i;
2118   char uri[1024];
2119 
2120   if (!PyArg_ParseTuple (args, "i|O", &job_id, &auth_info_list))
2121     return NULL;
2122 
2123   if (auth_info_list) {
2124     if (!PyList_Check (auth_info_list)) {
2125       PyErr_SetString (PyExc_TypeError, "List required");
2126       return NULL;
2127     }
2128 
2129     num_auth_info = PyList_Size (auth_info_list);
2130     debugprintf ("sizeof values = %Zd\n", sizeof (auth_info_values));
2131     if (num_auth_info > sizeof (auth_info_values))
2132       num_auth_info = sizeof (auth_info_values);
2133 
2134     for (i = 0; i < num_auth_info; i++) {
2135       PyObject *val = PyList_GetItem (auth_info_list, i); // borrowed ref
2136       if (UTF8_from_PyObj (&auth_info_values[i], val) == NULL) {
2137 	while (--i >= 0)
2138 	  free (auth_info_values[i]);
2139 	return NULL;
2140       }
2141     }
2142   }
2143 
2144   debugprintf ("-> Connection_authenticateJob(%d)\n", job_id);
2145   request = ippNewRequest(CUPS_AUTHENTICATE_JOB);
2146   snprintf (uri, sizeof (uri), "ipp://localhost/jobs/%d", job_id);
2147   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
2148   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2149 		"requesting-user-name", NULL, cupsUser ());
2150   if (auth_info_list)
2151     {
2152       ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "auth-info",
2153 		     num_auth_info, NULL,
2154 		     (const char *const *) auth_info_values);
2155       for (i = 0; i < num_auth_info; i++)
2156 	free (auth_info_values[i]);
2157     }
2158 
2159   debugprintf ("cupsDoRequest(\"/jobs/\")\n");
2160   Connection_begin_allow_threads (self);
2161   answer = cupsDoRequest (self->http, request, "/jobs/");
2162   Connection_end_allow_threads (self);
2163   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2164     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2165 		   answer ? NULL : cupsLastErrorString ());
2166     if (answer)
2167       ippDelete (answer);
2168     debugprintf ("<- Connection_authenticateJob() (error)\n");
2169     return NULL;
2170   }
2171 
2172   debugprintf ("<- Connection_authenticateJob() = None\n");
2173   Py_RETURN_NONE;
2174 }
2175 
2176 static PyObject *
Connection_setJobHoldUntil(Connection * self,PyObject * args)2177 Connection_setJobHoldUntil (Connection *self, PyObject *args)
2178 {
2179   ipp_t *request, *answer;
2180   int job_id;
2181   PyObject *job_hold_until_obj;
2182   char *job_hold_until;
2183   char uri[1024];
2184   cups_option_t *options = NULL;
2185   int num_options = 0;
2186   if (!PyArg_ParseTuple (args, "iO", &job_id, &job_hold_until_obj))
2187     return NULL;
2188 
2189   if (UTF8_from_PyObj (&job_hold_until, job_hold_until_obj) == NULL)
2190     return NULL;
2191 
2192   debugprintf ("-> Connection_setJobHoldUntil(%d,%s)\n",
2193 	       job_id, job_hold_until);
2194   request = ippNewRequest(IPP_SET_JOB_ATTRIBUTES);
2195   snprintf (uri, sizeof (uri), "ipp://localhost/jobs/%d", job_id);
2196   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
2197   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2198 		"requesting-user-name", NULL, cupsUser ());
2199   num_options = cupsAddOption ("job-hold-until", job_hold_until,
2200 			       num_options, &options);
2201   cupsEncodeOptions (request, num_options, options);
2202   free (job_hold_until);
2203 
2204   debugprintf ("cupsDoRequest(\"/jobs/\")\n");
2205   Connection_begin_allow_threads (self);
2206   answer = cupsDoRequest (self->http, request, "/jobs/");
2207   Connection_end_allow_threads (self);
2208   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2209     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2210 		   answer ? NULL : cupsLastErrorString ());
2211     if (answer)
2212       ippDelete (answer);
2213     debugprintf ("<- Connection_setJobHoldUntil() (error)\n");
2214     return NULL;
2215   }
2216 
2217   debugprintf ("<- Connection_setJobHoldUntil() = None\n");
2218   Py_RETURN_NONE;
2219 }
2220 
2221 static PyObject *
Connection_restartJob(Connection * self,PyObject * args,PyObject * kwds)2222 Connection_restartJob (Connection *self, PyObject *args, PyObject *kwds)
2223 {
2224   static char *kwlist[] = { "job_id", "job_hold_until", NULL };
2225   ipp_t *request, *answer;
2226   int job_id;
2227   char *job_hold_until = NULL;
2228   char uri[1024];
2229   if (!PyArg_ParseTupleAndKeywords (args, kwds, "i|s", kwlist,
2230 				    &job_id, &job_hold_until))
2231     return NULL;
2232 
2233   debugprintf ("-> Connection_restartJob(%d)\n", job_id);
2234   request = ippNewRequest(IPP_RESTART_JOB);
2235   snprintf (uri, sizeof (uri), "ipp://localhost/jobs/%d", job_id);
2236   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
2237   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2238 		"requesting-user-name", NULL, cupsUser ());
2239   if (job_hold_until)
2240     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2241 		  "job-hold-until", NULL, job_hold_until);
2242 
2243   debugprintf ("cupsDoRequest(\"/jobs/\")\n");
2244   Connection_begin_allow_threads (self);
2245   answer = cupsDoRequest (self->http, request, "/jobs/");
2246   Connection_end_allow_threads (self);
2247   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2248     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2249 		   answer ? NULL : cupsLastErrorString ());
2250     if (answer)
2251       ippDelete (answer);
2252     debugprintf ("<- Connection_restartJob() (error)\n");
2253     return NULL;
2254   }
2255 
2256   debugprintf ("<- Connection_restartJob() = None\n");
2257   Py_RETURN_NONE;
2258 }
2259 
2260 static PyObject *
Connection_getFile(Connection * self,PyObject * args,PyObject * kwds)2261 Connection_getFile (Connection *self, PyObject *args, PyObject *kwds)
2262 {
2263   static char *kwlist[] = { "resource", "filename", "fd", "file", NULL };
2264   const char *resource, *filename = NULL;
2265   int fd = -1;
2266   PyObject *fileobj = NULL;
2267   http_status_t status;
2268 
2269   if (!PyArg_ParseTupleAndKeywords (args, kwds, "s|siO", kwlist,
2270 				    &resource, &filename, &fd, &fileobj))
2271     return NULL;
2272 
2273   if ((fd > -1 && (filename || fileobj)) ||
2274       (filename && fileobj)) {
2275     PyErr_SetString (PyExc_RuntimeError,
2276 		     "Only one destination type may be specified");
2277     return NULL;
2278   }
2279 
2280   if (fileobj) {
2281     fd = PyObject_AsFileDescriptor(fileobj);
2282   }
2283 
2284   if (filename) {
2285     debugprintf ("-> Connection_getFile(%s, %s)\n", resource, filename);
2286     debugprintf ("cupsGetFile()\n");
2287     Connection_begin_allow_threads (self);
2288     status = cupsGetFile (self->http, resource, filename);
2289     Connection_end_allow_threads (self);
2290   } else {
2291     debugprintf ("-> Connection_getFile(%s, %d)\n", resource, fd);
2292     debugprintf ("cupsGetFd()\n");
2293     Connection_begin_allow_threads (self);
2294     status = cupsGetFd (self->http, resource, fd);
2295     Connection_end_allow_threads (self);
2296   }
2297 
2298   if (status != HTTP_OK) {
2299     set_http_error (status);
2300     debugprintf ("<- Connection_getFile() (error)\n");
2301     return NULL;
2302   }
2303 
2304   debugprintf ("<- Connection_getFile() = None\n");
2305   Py_RETURN_NONE;
2306 }
2307 
2308 static PyObject *
Connection_putFile(Connection * self,PyObject * args,PyObject * kwds)2309 Connection_putFile (Connection *self, PyObject *args, PyObject *kwds)
2310 {
2311   static char *kwlist[] = { "resource", "filename", "fd", "file", NULL };
2312   const char *resource, *filename = NULL;
2313   int fd = -1;
2314   PyObject *fileobj = NULL;
2315   http_status_t status;
2316 
2317   if (!PyArg_ParseTupleAndKeywords (args, kwds, "s|siO", kwlist,
2318 				    &resource, &filename, &fd, &fileobj))
2319     return NULL;
2320 
2321   if ((fd > -1 && (filename || fileobj)) ||
2322       (filename && fileobj)) {
2323     PyErr_SetString (PyExc_RuntimeError,
2324 		     "Only one destination type may be specified");
2325     return NULL;
2326   }
2327 
2328   if (fileobj) {
2329     fd = PyObject_AsFileDescriptor(fileobj);
2330   }
2331 
2332   if (filename) {
2333     debugprintf ("-> Connection_putFile(%s, %s)\n", resource, filename);
2334     debugprintf ("cupsPutFile()\n");
2335     Connection_begin_allow_threads (self);
2336     status = cupsPutFile (self->http, resource, filename);
2337     Connection_end_allow_threads (self);
2338   } else {
2339     debugprintf ("-> Connection_putFile(%s, %d)\n", resource, fd);
2340     debugprintf ("cupsPutFd()\n");
2341     Connection_begin_allow_threads (self);
2342     status = cupsPutFd (self->http, resource, fd);
2343     Connection_end_allow_threads (self);
2344   }
2345 
2346   if (status != HTTP_OK && status != HTTP_CREATED) {
2347     set_http_error (status);
2348     debugprintf ("<- Connection_putFile() (error)\n");
2349     return NULL;
2350   }
2351 
2352   debugprintf ("<- Connection_putFile() = None\n");
2353   Py_RETURN_NONE;
2354 }
2355 
2356 static ipp_t *
add_modify_printer_request(const char * name)2357 add_modify_printer_request (const char *name)
2358 {
2359   char uri[HTTP_MAX_URI];
2360   ipp_t *request = ippNewRequest (CUPS_ADD_MODIFY_PRINTER);
2361   construct_uri (uri, sizeof (uri), "ipp://localhost/printers/", name);
2362   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
2363 		"printer-uri", NULL, uri);
2364   return request;
2365 }
2366 
2367 static ipp_t *
add_modify_class_request(const char * name)2368 add_modify_class_request (const char *name)
2369 {
2370   char uri[HTTP_MAX_URI];
2371   ipp_t *request = ippNewRequest (CUPS_ADD_MODIFY_CLASS);
2372   construct_uri (uri, sizeof (uri), "ipp://localhost/classes/", name);
2373   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
2374 		"printer-uri", NULL, uri);
2375   return request;
2376 }
2377 
2378 static PyObject *
Connection_addPrinter(Connection * self,PyObject * args,PyObject * kwds)2379 Connection_addPrinter (Connection *self, PyObject *args, PyObject *kwds)
2380 {
2381   PyObject *nameobj = NULL;
2382   char *name = NULL;
2383   PyObject *ppdfileobj = NULL;
2384   char *ppdfile = NULL;
2385   PyObject *ppdnameobj = NULL;
2386   char *ppdname = NULL;
2387   PyObject *infoobj = NULL;
2388   char *info = NULL;
2389   PyObject *locationobj = NULL;
2390   char *location = NULL;
2391   PyObject *deviceobj = NULL;
2392   char *device = NULL;
2393   PyObject *ppd = NULL;
2394   ipp_t *request, *answer;
2395   int ppds_specified = 0;
2396   static char *kwlist[] = { "name", "filename", "ppdname", "info",
2397 			    "location", "device", "ppd", NULL };
2398 
2399   if (!PyArg_ParseTupleAndKeywords (args, kwds, "O|OOOOOO", kwlist,
2400 				    &nameobj, &ppdfileobj, &ppdnameobj,
2401 				    &infoobj, &locationobj, &deviceobj, &ppd))
2402     return NULL;
2403 
2404   if (UTF8_from_PyObj (&name, nameobj) == NULL ||
2405       (ppdfileobj && UTF8_from_PyObj (&ppdfile, ppdfileobj) == NULL) ||
2406       (ppdnameobj && UTF8_from_PyObj (&ppdname, ppdnameobj) == NULL) ||
2407       (infoobj && UTF8_from_PyObj (&info, infoobj) == NULL) ||
2408       (locationobj && UTF8_from_PyObj (&location, locationobj) == NULL) ||
2409       (deviceobj && UTF8_from_PyObj (&device, deviceobj) == NULL)) {
2410     free (name);
2411     free (ppdfile);
2412     free (ppdname);
2413     free (info);
2414     free (location);
2415     free (device);
2416     return NULL;
2417   }
2418 
2419   debugprintf ("-> Connection_addPrinter(%s,%s,%s,%s,%s,%s,%s)\n",
2420 	       name, ppdfile ? ppdfile: "", ppdname ? ppdname: "",
2421 	       info ? info: "", location ? location: "",
2422 	       device ? device: "", ppd ? "(PPD object)": "");
2423 
2424   if (ppdfile)
2425     ppds_specified++;
2426   if (ppdname)
2427     ppds_specified++;
2428   if (ppd) {
2429     if (!PyObject_TypeCheck (ppd, &cups_PPDType)) {
2430       PyErr_SetString (PyExc_TypeError, "Expecting cups.PPD");
2431       debugprintf ("<- Connection_addPrinter() EXCEPTION\n");
2432       free (name);
2433       free (ppdfile);
2434       free (ppdname);
2435       free (info);
2436       free (location);
2437       free (device);
2438       return NULL;
2439     }
2440 
2441     ppds_specified++;
2442   }
2443   if (ppds_specified > 1) {
2444     PyErr_SetString (PyExc_RuntimeError,
2445 		     "Only one PPD may be given");
2446     debugprintf ("<- Connection_addPrinter() EXCEPTION\n");
2447     free (name);
2448     free (ppdfile);
2449     free (ppdname);
2450     free (info);
2451     free (location);
2452     free (device);
2453     return NULL;
2454   }
2455 
2456   if (ppd) {
2457     // We've been given a cups.PPD object.  Construct a PPD file.
2458     char template[PATH_MAX];
2459     int fd;
2460     PyObject *args, *result;
2461 
2462     snprintf(template, sizeof (template), "%s/scp-ppd-XXXXXX", _PATH_TMP);
2463     ppdfile = strdup(template);
2464     fd = mkstemp (ppdfile);
2465     if (fd < 0) {
2466       debugprintf ("<- Connection_addPrinter() EXCEPTION\n");
2467       free (name);
2468       free (ppdfile);
2469       free (ppdname);
2470       free (info);
2471       free (location);
2472       free (device);
2473       return PyErr_SetFromErrno (PyExc_RuntimeError);
2474     }
2475 
2476     args = Py_BuildValue ("(i)", fd);
2477     result = PPD_writeFd ((PPD *) ppd, args);
2478     Py_DECREF (args);
2479     close (fd);
2480 
2481     if (result == NULL) {
2482       unlink (ppdfile);
2483       debugprintf ("<- Connection_addPrinter() EXCEPTION\n");
2484       free (name);
2485       free (ppdfile);
2486       free (ppdname);
2487       free (info);
2488       free (location);
2489       free (device);
2490       return NULL;
2491     }
2492   }
2493 
2494   request = add_modify_printer_request (name);
2495   free (name);
2496   if (ppdname) {
2497     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2498 		  "ppd-name", NULL, ppdname);
2499     free (ppdname);
2500   }
2501   if (info) {
2502     ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2503 		  "printer-info", NULL, info);
2504     free (info);
2505   }
2506   if (location) {
2507     ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2508 		  "printer-location", NULL, location);
2509     free (location);
2510   }
2511   if (device) {
2512     ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_URI,
2513 		  "device-uri", NULL, device);
2514     free (device);
2515   }
2516   if (ppds_specified)
2517     ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
2518 		  "printer-state-reasons", NULL, "none");
2519 
2520   Connection_begin_allow_threads (self);
2521   if (ppdfile) {
2522     answer = cupsDoFileRequest (self->http, request, "/admin/", ppdfile);
2523   } else
2524     answer = cupsDoRequest (self->http, request, "/admin/");
2525   Connection_end_allow_threads (self);
2526 
2527   if (ppd) {
2528     unlink (ppdfile);
2529     free (ppdfile);
2530   } else if (ppdfile)
2531     free (ppdfile);
2532 
2533   if (PyErr_Occurred ()) {
2534     if (answer)
2535       ippDelete (answer);
2536     debugprintf ("<- Connection_addPrinter() EXCEPTION\n");
2537     return NULL;
2538   }
2539 
2540   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2541     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2542 		   answer ? NULL : cupsLastErrorString ());
2543     if (answer)
2544       ippDelete (answer);
2545 
2546     debugprintf ("<- Connection_addPrinter() EXCEPTION\n");
2547     return NULL;
2548   }
2549 
2550   ippDelete (answer);
2551   debugprintf ("<- Connection_addPrinter() = None\n");
2552   Py_RETURN_NONE;
2553 }
2554 
2555 static PyObject *
Connection_setPrinterDevice(Connection * self,PyObject * args)2556 Connection_setPrinterDevice (Connection *self, PyObject *args)
2557 {
2558   PyObject *nameobj;
2559   PyObject *device_uriobj;
2560   char *name;
2561   char *device_uri;
2562   ipp_t *request, *answer;
2563 
2564   if (!PyArg_ParseTuple (args, "OO", &nameobj, &device_uriobj))
2565     return NULL;
2566 
2567   if (UTF8_from_PyObj (&name, nameobj) == NULL)
2568     return NULL;
2569 
2570   if (UTF8_from_PyObj (&device_uri, device_uriobj) == NULL) {
2571     free (name);
2572     return NULL;
2573   }
2574 
2575   request = add_modify_printer_request (name);
2576   free (name);
2577   ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_URI,
2578 		"device-uri", NULL, device_uri);
2579   free (device_uri);
2580   Connection_begin_allow_threads (self);
2581   answer = cupsDoRequest (self->http, request, "/admin/");
2582   Connection_end_allow_threads (self);
2583   if (PyErr_Occurred ()) {
2584     if (answer)
2585       ippDelete (answer);
2586     return NULL;
2587   }
2588 
2589   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2590     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2591 		   answer ? NULL : cupsLastErrorString ());
2592     if (answer)
2593       ippDelete (answer);
2594     return NULL;
2595   }
2596 
2597   ippDelete (answer);
2598   Py_RETURN_NONE;
2599 }
2600 
2601 static PyObject *
Connection_setPrinterInfo(Connection * self,PyObject * args)2602 Connection_setPrinterInfo (Connection *self, PyObject *args)
2603 {
2604   PyObject *nameobj;
2605   PyObject *infoobj;
2606   char *name;
2607   char *info;
2608   ipp_t *request, *answer;
2609   int i;
2610 
2611   if (!PyArg_ParseTuple (args, "OO", &nameobj, &infoobj))
2612     return NULL;
2613 
2614   if (UTF8_from_PyObj (&name, nameobj) == NULL)
2615     return NULL;
2616 
2617   if (UTF8_from_PyObj (&info, infoobj) == NULL) {
2618     free (name);
2619     return NULL;
2620   }
2621 
2622   request = add_modify_printer_request (name);
2623   for (i = 0; i < 2; i++) {
2624     ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2625 		  "printer-info", NULL, info);
2626     Connection_begin_allow_threads (self);
2627     answer = cupsDoRequest (self->http, request, "/admin/");
2628     Connection_end_allow_threads (self);
2629     if (PyErr_Occurred ()) {
2630       if (answer)
2631 	ippDelete (answer);
2632       return NULL;
2633     }
2634 
2635     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
2636       // Perhaps it's a class, not a printer.
2637       ippDelete (answer);
2638       request = add_modify_class_request (name);
2639     } else break;
2640   }
2641 
2642   free (name);
2643   free (info);
2644   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2645     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2646 		   answer ? NULL : cupsLastErrorString ());
2647     if (answer)
2648       ippDelete (answer);
2649     return NULL;
2650   }
2651 
2652   ippDelete (answer);
2653   Py_RETURN_NONE;
2654 }
2655 
2656 static PyObject *
Connection_setPrinterLocation(Connection * self,PyObject * args)2657 Connection_setPrinterLocation (Connection *self, PyObject *args)
2658 {
2659   PyObject *nameobj;
2660   PyObject *locationobj;
2661   char *name;
2662   char *location;
2663   ipp_t *request, *answer;
2664   int i;
2665 
2666   if (!PyArg_ParseTuple (args, "OO", &nameobj, &locationobj))
2667     return NULL;
2668 
2669   if (UTF8_from_PyObj (&name, nameobj) == NULL)
2670     return NULL;
2671 
2672   if (UTF8_from_PyObj (&location, locationobj) == NULL) {
2673     free (name);
2674     return NULL;
2675   }
2676 
2677   request = add_modify_printer_request (name);
2678   for (i = 0; i < 2; i++) {
2679     ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2680 		  "printer-location", NULL, location);
2681     Connection_begin_allow_threads (self);
2682     answer = cupsDoRequest (self->http, request, "/admin/");
2683     Connection_end_allow_threads (self);
2684     if (PyErr_Occurred ()) {
2685       if (answer)
2686 	ippDelete (answer);
2687       return NULL;
2688     }
2689 
2690     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
2691       // Perhaps it's a class, not a printer.
2692       ippDelete (answer);
2693       request = add_modify_class_request (name);
2694     } else break;
2695   }
2696 
2697   free (name);
2698   free (location);
2699   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2700     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2701 		   answer ? NULL : cupsLastErrorString ());
2702     if (answer)
2703       ippDelete (answer);
2704     return NULL;
2705   }
2706 
2707   ippDelete (answer);
2708   Py_RETURN_NONE;
2709 }
2710 
2711 static PyObject *
Connection_setPrinterShared(Connection * self,PyObject * args)2712 Connection_setPrinterShared (Connection *self, PyObject *args)
2713 {
2714   PyObject *nameobj;
2715   char *name;
2716   int sharing;
2717   ipp_t *request, *answer;
2718   int i;
2719 
2720   if (!PyArg_ParseTuple (args, "Oi", &nameobj, &sharing))
2721     return NULL;
2722 
2723   if (UTF8_from_PyObj (&name, nameobj) == NULL)
2724     return NULL;
2725 
2726   request = add_modify_printer_request (name);
2727   for (i = 0; i < 2; i++) {
2728     ippAddBoolean (request, IPP_TAG_OPERATION, "printer-is-shared", sharing);
2729     Connection_begin_allow_threads (self);
2730     answer = cupsDoRequest (self->http, request, "/admin/");
2731     Connection_end_allow_threads (self);
2732     if (PyErr_Occurred ()) {
2733       if (answer)
2734 	ippDelete (answer);
2735       return NULL;
2736     }
2737 
2738     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
2739       // Perhaps it's a class, not a printer.
2740       ippDelete (answer);
2741       request = add_modify_class_request (name);
2742     } else break;
2743   }
2744 
2745   free (name);
2746   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2747     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2748 		   answer ? NULL : cupsLastErrorString ());
2749     if (answer)
2750       ippDelete (answer);
2751     return NULL;
2752   }
2753 
2754   ippDelete (answer);
2755   Py_RETURN_NONE;
2756 }
2757 
2758 static PyObject *
Connection_setPrinterJobSheets(Connection * self,PyObject * args)2759 Connection_setPrinterJobSheets (Connection *self, PyObject *args)
2760 {
2761   PyObject *nameobj;
2762   PyObject *startobj;
2763   PyObject *endobj;
2764   char *name;
2765   char *start;
2766   char *end;
2767   ipp_t *request, *answer;
2768   ipp_attribute_t *a;
2769   int i;
2770 
2771   if (!PyArg_ParseTuple (args, "OOO", &nameobj, &startobj, &endobj))
2772     return NULL;
2773 
2774   if (UTF8_from_PyObj (&name, nameobj) == NULL)
2775     return NULL;
2776 
2777   if (UTF8_from_PyObj (&start, startobj) == NULL) {
2778     free (name);
2779     return NULL;
2780   }
2781 
2782   if (UTF8_from_PyObj (&end, endobj) == NULL) {
2783     free (name);
2784     free (start);
2785     return NULL;
2786   }
2787 
2788   request = add_modify_printer_request (name);
2789   for (i = 0; i < 2; i++) {
2790     a = ippAddStrings (request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2791 		       "job-sheets-default", 2, NULL, NULL);
2792     ippSetString(request, &a, 0, strdup (start));
2793     ippSetString(request, &a, 1, strdup (end));
2794     Connection_begin_allow_threads (self);
2795     answer = cupsDoRequest (self->http, request, "/admin/");
2796     Connection_end_allow_threads (self);
2797     if (PyErr_Occurred ()) {
2798       if (answer)
2799 	ippDelete (answer);
2800       return NULL;
2801     }
2802 
2803     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
2804       ippDelete (answer);
2805       // Perhaps it's a class, not a printer.
2806       request = add_modify_class_request (name);
2807     } else break;
2808   }
2809 
2810   free (name);
2811   free (start);
2812   free (end);
2813   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2814     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2815 		   answer ? NULL : cupsLastErrorString ());
2816     if (answer)
2817       ippDelete (answer);
2818     return NULL;
2819   }
2820 
2821   ippDelete (answer);
2822   Py_RETURN_NONE;
2823 }
2824 
2825 static PyObject *
Connection_setPrinterErrorPolicy(Connection * self,PyObject * args)2826 Connection_setPrinterErrorPolicy (Connection *self, PyObject *args)
2827 {
2828   PyObject *nameobj;
2829   char *name;
2830   PyObject *policyobj;
2831   char *policy;
2832   ipp_t *request, *answer;
2833   int i;
2834 
2835   if (!PyArg_ParseTuple (args, "OO", &nameobj, &policyobj))
2836     return NULL;
2837 
2838   if (UTF8_from_PyObj (&name, nameobj) == NULL)
2839     return NULL;
2840 
2841   if (UTF8_from_PyObj (&policy, policyobj) == NULL) {
2842     free (name);
2843     return NULL;
2844   }
2845 
2846   request = add_modify_printer_request (name);
2847   for (i = 0; i < 2; i++) {
2848     ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2849 		  "printer-error-policy", NULL, policy);
2850     Connection_begin_allow_threads (self);
2851     answer = cupsDoRequest (self->http, request, "/admin/");
2852     Connection_end_allow_threads (self);
2853     if (PyErr_Occurred ()) {
2854       if (answer)
2855 	ippDelete (answer);
2856       return NULL;
2857     }
2858 
2859     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
2860       ippDelete (answer);
2861       // Perhaps it's a class, not a printer.
2862       request = add_modify_class_request (name);
2863     } else break;
2864   }
2865 
2866   free (name);
2867   free (policy);
2868   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2869     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2870 		   answer ? NULL : cupsLastErrorString ());
2871     if (answer)
2872       ippDelete (answer);
2873     return NULL;
2874   }
2875 
2876   ippDelete (answer);
2877   Py_RETURN_NONE;
2878 }
2879 
2880 static PyObject *
Connection_setPrinterOpPolicy(Connection * self,PyObject * args)2881 Connection_setPrinterOpPolicy (Connection *self, PyObject *args)
2882 {
2883   PyObject *nameobj;
2884   char *name;
2885   PyObject *policyobj;
2886   char *policy;
2887   ipp_t *request, *answer;
2888   int i;
2889 
2890   if (!PyArg_ParseTuple (args, "OO", &nameobj, &policyobj))
2891     return NULL;
2892 
2893   if (UTF8_from_PyObj (&name, nameobj) == NULL)
2894     return NULL;
2895 
2896   if (UTF8_from_PyObj (&policy, policyobj) == NULL) {
2897     free (name);
2898     return NULL;
2899   }
2900 
2901   request = add_modify_printer_request (name);
2902   for (i = 0; i < 2; i++) {
2903     ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2904 		  "printer-op-policy", NULL, policy);
2905     Connection_begin_allow_threads (self);
2906     answer = cupsDoRequest (self->http, request, "/admin/");
2907     Connection_end_allow_threads (self);
2908     if (PyErr_Occurred ()) {
2909       if (answer)
2910 	ippDelete (answer);
2911       return NULL;
2912     }
2913 
2914     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
2915       ippDelete (answer);
2916       // Perhaps it's a class, not a printer.
2917       request = add_modify_class_request (name);
2918     } else break;
2919   }
2920 
2921   free (name);
2922   free (policy);
2923   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
2924     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
2925 		   answer ? NULL : cupsLastErrorString ());
2926     if (answer)
2927       ippDelete (answer);
2928     return NULL;
2929   }
2930 
2931   ippDelete (answer);
2932   Py_RETURN_NONE;
2933 }
2934 
2935 static PyObject *
do_requesting_user_names(Connection * self,PyObject * args,const char * requeststr)2936 do_requesting_user_names (Connection *self, PyObject *args,
2937 			  const char *requeststr)
2938 {
2939   PyObject *nameobj;
2940   char *name;
2941   char *tmp;
2942   PyObject *users;
2943   int num_users, i, j;
2944   ipp_t *request, *answer;
2945   ipp_attribute_t *attr;
2946 
2947   if (!PyArg_ParseTuple (args, "OO", &nameobj, &users))
2948     return NULL;
2949 
2950   if (UTF8_from_PyObj (&name, nameobj) == NULL)
2951     return NULL;
2952 
2953   if (!PyList_Check (users)) {
2954     PyErr_SetString (PyExc_TypeError, "List required");
2955     return NULL;
2956   }
2957   request = add_modify_printer_request (name);
2958   for (i = 0; i < 2; i++) {
2959     num_users = PyList_Size (users);
2960     if (num_users) {
2961       attr = ippAddStrings (request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2962 			    requeststr, num_users, NULL, NULL);
2963       for (j = 0; j < num_users; j++) {
2964 	PyObject *username = PyList_GetItem (users, j); // borrowed ref
2965 	if (!PyUnicode_Check (username) && !PyBytes_Check (username)) {
2966 	  int k;
2967 	  PyErr_SetString (PyExc_TypeError, "String required");
2968 	  for (k = 0; k < j; k++) {
2969 	    free ((void *)ippGetString (attr, k, NULL));
2970 	    ippSetString(request, &attr, k, NULL);
2971 	  }
2972 	  ippDelete (request);
2973 	  return NULL;
2974 	}
2975 	ippSetString(request, &attr, j, UTF8_from_PyObj (&tmp, username));
2976 	free(tmp);
2977       }
2978     } else {
2979       attr = ippAddStrings (request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2980 			    requeststr, 1, NULL, NULL);
2981       if (strstr (requeststr, "denied"))
2982 	ippSetString(request, &attr, 0, strdup ("none"));
2983       else
2984 	ippSetString(request, &attr, 0, strdup ("all"));
2985     }
2986     Connection_begin_allow_threads (self);
2987     answer = cupsDoRequest (self->http, request, "/admin/");
2988     Connection_end_allow_threads (self);
2989     if (PyErr_Occurred ()) {
2990       if (answer)
2991 	ippDelete (answer);
2992       return NULL;
2993     }
2994 
2995     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
2996       ippDelete (answer);
2997       // Perhaps it's a class, not a printer.
2998       request = add_modify_class_request (name);
2999     } else break;
3000   }
3001 
3002   free (name);
3003   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
3004     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
3005 		   answer ? NULL : cupsLastErrorString ());
3006     if (answer)
3007       ippDelete (answer);
3008     return NULL;
3009   }
3010 
3011   ippDelete (answer);
3012   Py_RETURN_NONE;
3013 }
3014 
3015 static PyObject *
Connection_setPrinterUsersAllowed(Connection * self,PyObject * args)3016 Connection_setPrinterUsersAllowed (Connection *self, PyObject *args)
3017 {
3018   return do_requesting_user_names (self, args, "requesting-user-name-allowed");
3019 }
3020 
3021 static PyObject *
Connection_setPrinterUsersDenied(Connection * self,PyObject * args)3022 Connection_setPrinterUsersDenied (Connection *self, PyObject *args)
3023 {
3024   return do_requesting_user_names (self, args, "requesting-user-name-denied");
3025 }
3026 
3027 static char *
PyObject_to_string(PyObject * pyvalue)3028 PyObject_to_string (PyObject *pyvalue)
3029 {
3030   char string[BUFSIZ];
3031   char *value = "{unknown type}";
3032 
3033   if (PyUnicode_Check (pyvalue) || PyBytes_Check (pyvalue)) {
3034     UTF8_from_PyObj (&value, pyvalue);
3035   } else if (PyBool_Check (pyvalue)) {
3036     value = (pyvalue == Py_True) ? "true" : "false";
3037   } else if (PyLong_Check (pyvalue)) {
3038     long v = PyLong_AsLong (pyvalue);
3039     snprintf (string, sizeof (string), "%ld", v);
3040     value = string;
3041   } else if (PyFloat_Check (pyvalue)) {
3042     double v = PyFloat_AsDouble (pyvalue);
3043     snprintf (string, sizeof (string), "%f", v);
3044     value = string;
3045   }
3046 
3047   return strdup (value);
3048 }
3049 
3050 static PyObject *
Connection_addPrinterOptionDefault(Connection * self,PyObject * args)3051 Connection_addPrinterOptionDefault (Connection *self, PyObject *args)
3052 {
3053   PyObject *nameobj;
3054   char *name;
3055   PyObject *optionobj;
3056   char *option;
3057   PyObject *pyvalue;
3058   const char suffix[] = "-default";
3059   char *opt;
3060   ipp_t *request, *answer;
3061   int i;
3062   size_t optionlen;
3063 
3064   if (!PyArg_ParseTuple (args, "OOO", &nameobj, &optionobj, &pyvalue))
3065     return NULL;
3066 
3067   if (UTF8_from_PyObj (&name, nameobj) == NULL)
3068     return NULL;
3069 
3070   if (UTF8_from_PyObj (&option, optionobj) == NULL) {
3071     free (name);
3072     return NULL;
3073   }
3074 
3075   optionlen = strlen (option);
3076   opt = malloc (optionlen + sizeof (suffix) + 1);
3077   memcpy (opt, option, optionlen);
3078   strcpy (opt + optionlen, suffix);
3079   request = add_modify_printer_request (name);
3080   for (i = 0; i < 2; i++) {
3081     if (!PyUnicode_Check (pyvalue) && !PyBytes_Check (pyvalue) &&
3082 	PySequence_Check (pyvalue)) {
3083       ipp_attribute_t *attr;
3084       int len = PySequence_Size (pyvalue);
3085       int j;
3086       attr = ippAddStrings (request, IPP_TAG_PRINTER, IPP_TAG_NAME,
3087 			    opt, len, NULL, NULL);
3088       for (j = 0; j < len; j++) {
3089 	PyObject *each = PySequence_GetItem (pyvalue, j);
3090 	ippSetString(request, &attr, j, PyObject_to_string (each));
3091       }
3092     } else
3093       ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_NAME,
3094 		    opt, NULL, PyObject_to_string (pyvalue));
3095 
3096     Connection_begin_allow_threads (self);
3097     answer = cupsDoRequest (self->http, request, "/admin/");
3098     Connection_end_allow_threads (self);
3099     if (PyErr_Occurred ()) {
3100       if (answer)
3101 	ippDelete (answer);
3102       return NULL;
3103     }
3104 
3105     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
3106       ippDelete (answer);
3107       // Perhaps it's a class, not a printer.
3108       request = add_modify_class_request (name);
3109     } else break;
3110   }
3111 
3112   free (name);
3113   free (option);
3114   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
3115     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
3116 		   answer ? NULL : cupsLastErrorString ());
3117     if (answer)
3118       ippDelete (answer);
3119     return NULL;
3120   }
3121 
3122   ippDelete (answer);
3123   Py_RETURN_NONE;
3124 }
3125 
3126 static PyObject *
Connection_deletePrinterOptionDefault(Connection * self,PyObject * args)3127 Connection_deletePrinterOptionDefault (Connection *self, PyObject *args)
3128 {
3129   PyObject *nameobj;
3130   char *name;
3131   PyObject *optionobj;
3132   char *option;
3133   const char suffix[] = "-default";
3134   char *opt;
3135   ipp_t *request, *answer;
3136   int i;
3137   size_t optionlen;
3138 
3139   if (!PyArg_ParseTuple (args, "OO", &nameobj, &optionobj))
3140     return NULL;
3141 
3142   if (UTF8_from_PyObj (&name, nameobj) == NULL)
3143     return NULL;
3144 
3145   if (UTF8_from_PyObj (&option, optionobj) == NULL) {
3146     free (name);
3147     return NULL;
3148   }
3149 
3150   optionlen = strlen (option);
3151   opt = malloc (optionlen + sizeof (suffix) + 1);
3152   memcpy (opt, option, optionlen);
3153   strcpy (opt + optionlen, suffix);
3154   request = add_modify_printer_request (name);
3155   for (i = 0; i < 2; i++) {
3156     ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_DELETEATTR,
3157 		  opt, NULL, NULL);
3158     Connection_begin_allow_threads (self);
3159     answer = cupsDoRequest (self->http, request, "/admin/");
3160     Connection_end_allow_threads (self);
3161     if (PyErr_Occurred ()) {
3162       if (answer)
3163 	ippDelete (answer);
3164       return NULL;
3165     }
3166 
3167     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
3168       ippDelete (answer);
3169       // Perhaps it's a class, not a printer.
3170       request = add_modify_class_request (name);
3171     } else break;
3172   }
3173 
3174   free (name);
3175   free (option);
3176   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
3177     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
3178 		   answer ? NULL : cupsLastErrorString ());
3179     if (answer)
3180       ippDelete (answer);
3181     return NULL;
3182   }
3183 
3184   ippDelete (answer);
3185   Py_RETURN_NONE;
3186 }
3187 
3188 static PyObject *
Connection_deletePrinter(Connection * self,PyObject * args,PyObject * kwds)3189 Connection_deletePrinter (Connection *self, PyObject *args, PyObject *kwds)
3190 {
3191   return do_printer_request (self, args, kwds, CUPS_DELETE_PRINTER);
3192 }
3193 
3194 static PyObject *
Connection_getPrinterAttributes(Connection * self,PyObject * args,PyObject * kwds)3195 Connection_getPrinterAttributes (Connection *self, PyObject *args,
3196 				 PyObject *kwds)
3197 {
3198   PyObject *ret;
3199   PyObject *nameobj = NULL;
3200   char *name;
3201   PyObject *uriobj = NULL;
3202   char *uri;
3203   PyObject *requested_attrs = NULL;
3204   char **attrs = NULL; /* initialised to calm compiler */
3205   size_t n_attrs = 0; /* initialised to calm compiler */
3206   ipp_t *request, *answer;
3207   ipp_attribute_t *attr;
3208   char consuri[HTTP_MAX_URI];
3209   int i;
3210   static char *kwlist[] = { "name", "uri", "requested_attributes", NULL };
3211 
3212   if (!PyArg_ParseTupleAndKeywords (args, kwds, "|OOO", kwlist,
3213 				    &nameobj, &uriobj, &requested_attrs))
3214     return NULL;
3215 
3216   if (nameobj && uriobj) {
3217     PyErr_SetString (PyExc_RuntimeError,
3218 		     "name or uri must be specified but not both");
3219     return NULL;
3220   }
3221 
3222   if (nameobj) {
3223     if (UTF8_from_PyObj (&name, nameobj) == NULL)
3224       return NULL;
3225   } else if (uriobj) {
3226     if (UTF8_from_PyObj (&uri, uriobj) == NULL)
3227       return NULL;
3228   } else {
3229     PyErr_SetString (PyExc_RuntimeError,
3230 		     "name or uri must be specified");
3231     return NULL;
3232   }
3233 
3234   if (requested_attrs) {
3235     if (get_requested_attrs (requested_attrs, &n_attrs, &attrs) == -1) {
3236       if (nameobj)
3237 	free (name);
3238       else if (uriobj)
3239 	free (uri);
3240       return NULL;
3241     }
3242   }
3243 
3244   debugprintf ("-> Connection_getPrinterAttributes(%s)\n",
3245 	       nameobj ? name : uri);
3246 
3247   if (nameobj) {
3248     construct_uri (consuri, sizeof (consuri),
3249 		   "ipp://localhost/printers/", name);
3250     uri = consuri;
3251   }
3252 
3253   for (i = 0; i < 2; i++) {
3254     request = ippNewRequest (IPP_GET_PRINTER_ATTRIBUTES);
3255     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
3256 		  "printer-uri", NULL, uri);
3257     if (requested_attrs)
3258       ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
3259 		     "requested-attributes", n_attrs, NULL,
3260 		     (const char **) attrs);
3261     debugprintf ("trying request with uri %s\n", uri);
3262     Connection_begin_allow_threads (self);
3263     answer = cupsDoRequest (self->http, request, "/");
3264     Connection_end_allow_threads (self);
3265     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
3266       ippDelete (answer);
3267       if (uriobj)
3268 	break;
3269 
3270       // Perhaps it's a class, not a printer.
3271       construct_uri (consuri, sizeof (consuri),
3272 		     "ipp://localhost/classes/", name);
3273     } else break;
3274   }
3275 
3276   if (nameobj)
3277     free (name);
3278 
3279   if (uriobj)
3280     free (uri);
3281 
3282   if (requested_attrs)
3283     free_requested_attrs (n_attrs, attrs);
3284 
3285   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
3286     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
3287 		   answer ? NULL : cupsLastErrorString ());
3288     if (answer)
3289       ippDelete (answer);
3290 
3291     debugprintf ("<- Connection_getPrinterAttributes() (error)\n");
3292     return NULL;
3293   }
3294 
3295   ret = PyDict_New ();
3296   for (attr = ippFirstAttribute (answer); attr; attr = ippNextAttribute (answer)) {
3297     while (attr && ippGetGroupTag (attr) != IPP_TAG_PRINTER)
3298       attr = ippNextAttribute (answer);
3299 
3300     if (!attr)
3301       break;
3302 
3303     for (; attr && ippGetGroupTag (attr) == IPP_TAG_PRINTER;
3304 	 attr = ippNextAttribute (answer)) {
3305       size_t namelen = strlen (ippGetName (attr));
3306       int is_list = ippGetCount (attr) > 1;
3307 
3308       debugprintf ("Attribute: %s\n", ippGetName (attr));
3309       // job-sheets-default is special, since it is always two values.
3310       // Make it a tuple.
3311       if (!strcmp (ippGetName (attr), "job-sheets-default") &&
3312 	  ippGetValueTag (attr) == IPP_TAG_NAME) {
3313 	PyObject *startobj, *endobj, *tuple;
3314 	const char *start, *end;
3315 	start = ippGetString (attr, 0, NULL);
3316 	if (ippGetCount (attr) >= 2)
3317 	  end = ippGetString (attr, 1, NULL);
3318 	else
3319 	  end = "";
3320 
3321 	startobj = PyObj_from_UTF8 (start);
3322 	endobj = PyObj_from_UTF8 (end);
3323 	tuple = Py_BuildValue ("(OO)", startobj, endobj);
3324 	Py_DECREF (startobj);
3325 	Py_DECREF (endobj);
3326 	PyDict_SetItemString (ret, "job-sheets-default", tuple);
3327 	Py_DECREF (tuple);
3328 	continue;
3329       }
3330 
3331       // Check for '-supported' suffix.  Any xxx-supported attribute
3332       // that is a text type must be a list.
3333       //
3334       // Also check for attributes that are known to allow multiple
3335       // string values, and make them lists.
3336       if (!is_list && namelen > 10) {
3337 	const char *multivalue_options[] =
3338 	  {
3339 	    "notify-events-default",
3340 	    "requesting-user-name-allowed",
3341 	    "requesting-user-name-denied",
3342 	    "printer-state-reasons",
3343 	    "marker-colors",
3344 	    "marker-names",
3345 	    "marker-types",
3346 	    "marker-levels",
3347 	    "member-names",
3348 	    NULL
3349 	  };
3350 
3351 	switch (ippGetValueTag (attr)) {
3352 	case IPP_TAG_NAME:
3353 	case IPP_TAG_TEXT:
3354 	case IPP_TAG_KEYWORD:
3355 	case IPP_TAG_URI:
3356 	case IPP_TAG_CHARSET:
3357 	case IPP_TAG_MIMETYPE:
3358 	case IPP_TAG_LANGUAGE:
3359 	case IPP_TAG_ENUM:
3360 	case IPP_TAG_INTEGER:
3361 	case IPP_TAG_RESOLUTION:
3362 	  is_list = !strcmp (ippGetName (attr) + namelen - 10, "-supported");
3363 
3364 	  if (!is_list) {
3365 	    const char **opt;
3366 	    for (opt = multivalue_options; !is_list && *opt; opt++)
3367 	      is_list = !strcmp (ippGetName (attr), *opt);
3368 	  }
3369 
3370 	default:
3371 	  break;
3372 	}
3373       }
3374 
3375       if (is_list) {
3376 	PyObject *list = PyList_from_attr_values (attr);
3377 	PyDict_SetItemString (ret, ippGetName (attr), list);
3378 	Py_DECREF (list);
3379       } else {
3380 	PyObject *val = PyObject_from_attr_value (attr, i);
3381 	PyDict_SetItemString (ret, ippGetName (attr), val);
3382       }
3383     }
3384 
3385     if (!attr)
3386       break;
3387   }
3388 
3389   debugprintf ("<- Connection_getPrinterAttributes() = dict\n");
3390   return ret;
3391 }
3392 
3393 static PyObject *
Connection_addPrinterToClass(Connection * self,PyObject * args)3394 Connection_addPrinterToClass (Connection *self, PyObject *args)
3395 {
3396   PyObject *printernameobj;
3397   char *printername;
3398   PyObject *classnameobj;
3399   char *classname;
3400   char classuri[HTTP_MAX_URI];
3401   char printeruri[HTTP_MAX_URI];
3402   ipp_t *request, *answer;
3403 
3404   if (!PyArg_ParseTuple (args, "OO", &printernameobj, &classnameobj))
3405     return NULL;
3406 
3407   if (UTF8_from_PyObj (&printername, printernameobj) == NULL)
3408     return NULL;
3409 
3410   if (UTF8_from_PyObj (&classname, classnameobj) == NULL) {
3411     free (printername);
3412     return NULL;
3413   }
3414 
3415   // Does the class exist, and is the printer already in it?
3416   request = ippNewRequest (IPP_GET_PRINTER_ATTRIBUTES);
3417   construct_uri (classuri, sizeof (classuri),
3418 		 "ipp://localhost/classes/", classname);
3419 
3420   free (classname);
3421   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
3422 		"printer-uri", NULL, classuri);
3423   Connection_begin_allow_threads (self);
3424   answer = cupsDoRequest (self->http, request, "/");
3425   Connection_end_allow_threads (self);
3426   if (answer) {
3427     ipp_attribute_t *printers;
3428     printers = ippFindAttribute (answer, "member-names", IPP_TAG_NAME);
3429     if (printers) {
3430       int i;
3431       for (i = 0; i < ippGetCount (printers); i++) {
3432 	if (!strcasecmp (ippGetString (printers, i, NULL), printername)) {
3433 	  ippDelete (answer);
3434 	  PyErr_SetString (PyExc_RuntimeError, "Printer already in class");
3435 	  free (printername);
3436 	  return NULL;
3437 	}
3438       }
3439     }
3440   }
3441 
3442   request = ippNewRequest (CUPS_ADD_CLASS);
3443   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
3444 		"printer-uri", NULL, classuri);
3445   construct_uri (printeruri, sizeof (printeruri),
3446 		 "ipp://localhost/printers/", printername);
3447   free (printername);
3448   if (answer) {
3449     ipp_attribute_t *printers;
3450     printers = ippFindAttribute (answer, "member-uris", IPP_TAG_URI);
3451     if (printers) {
3452       ipp_attribute_t *attr;
3453       int i;
3454       attr = ippAddStrings (request, IPP_TAG_PRINTER, IPP_TAG_URI,
3455 			    "member-uris", ippGetCount (printers) + 1,
3456 			    NULL, NULL);
3457       for (i = 0; i < ippGetCount (printers); i++)
3458         ippSetString(request, &attr, i,
3459                      strdup (ippGetString (printers, i, NULL)));
3460       ippSetString(request, &attr, ippGetCount (printers), strdup (printeruri));
3461 
3462     }
3463 
3464     ippDelete (answer);
3465   }
3466 
3467   // If the class didn't exist, create a new one.
3468   if (!ippFindAttribute (request, "member-uris", IPP_TAG_URI))
3469     ippAddString (request, IPP_TAG_PRINTER, IPP_TAG_URI,
3470 		  "member-uris", NULL, printeruri);
3471 
3472   Connection_begin_allow_threads (self);
3473   answer = cupsDoRequest (self->http, request, "/admin/");
3474   Connection_end_allow_threads (self);
3475   if (PyErr_Occurred ()) {
3476     if (answer)
3477       ippDelete (answer);
3478     return NULL;
3479   }
3480 
3481   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
3482     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
3483 		   answer ? NULL : cupsLastErrorString ());
3484     if (answer)
3485       ippDelete (answer);
3486     return NULL;
3487   }
3488 
3489   ippDelete (answer);
3490   Py_RETURN_NONE;
3491 }
3492 
3493 static PyObject *
Connection_deletePrinterFromClass(Connection * self,PyObject * args)3494 Connection_deletePrinterFromClass (Connection *self, PyObject *args)
3495 {
3496   const char *requested_attrs[] = {
3497     "member-names",
3498     "member-uris"
3499   };
3500   PyObject *printernameobj;
3501   char *printername;
3502   PyObject *classnameobj;
3503   char *classname;
3504   char classuri[HTTP_MAX_URI];
3505   ipp_t *request, *answer;
3506   ipp_attribute_t *printers;
3507   int i;
3508 
3509   if (!PyArg_ParseTuple (args, "OO", &printernameobj, &classnameobj))
3510     return NULL;
3511 
3512   if (UTF8_from_PyObj (&printername, printernameobj) == NULL)
3513     return NULL;
3514 
3515   if (UTF8_from_PyObj (&classname, classnameobj) == NULL) {
3516     free (printername);
3517     return NULL;
3518   }
3519 
3520   // Does the class exist, and is the printer in it?
3521   request = ippNewRequest (IPP_GET_PRINTER_ATTRIBUTES);
3522   construct_uri (classuri, sizeof (classuri),
3523 		 "ipp://localhost/classes/", classname);
3524   free (classname);
3525   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
3526 		"printer-uri", NULL, classuri);
3527   ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
3528 		 "requested-attributes",
3529 		 sizeof(requested_attrs) / sizeof(requested_attrs[0]),
3530 		 NULL, requested_attrs);
3531   Connection_begin_allow_threads (self);
3532   answer = cupsDoRequest (self->http, request, "/");
3533   Connection_end_allow_threads (self);
3534   if (!answer) {
3535     set_ipp_error (cupsLastError (), cupsLastErrorString ());
3536     free (printername);
3537     return NULL;
3538   }
3539 
3540   printers = ippFindAttribute (answer, "member-names", IPP_TAG_NAME);
3541   for (i = 0; printers && i < ippGetCount (printers); i++)
3542     if (!strcasecmp (ippGetString (printers, i, NULL), printername))
3543       break;
3544 
3545   free (printername);
3546   if (!printers || i == ippGetCount (printers)) {
3547     ippDelete (answer);
3548     PyErr_SetString (PyExc_RuntimeError, "Printer not in class");
3549     return NULL;
3550   }
3551 
3552   printers = ippFindAttribute (answer, "member-uris", IPP_TAG_URI);
3553   if (!printers || i >= ippGetCount (printers)) {
3554     ippDelete (answer);
3555     PyErr_SetString (PyExc_RuntimeError, "No member URIs returned");
3556     return NULL;
3557   }
3558 
3559   request = ippNewRequest (CUPS_ADD_CLASS);
3560   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
3561 		"printer-uri", NULL, classuri);
3562 
3563   // Only printer in class?  Delete the class.
3564   if (ippGetCount (printers) == 1)
3565     ippSetOperation (request, CUPS_DELETE_CLASS);
3566   else {
3567     // Trim the printer from the list.
3568     ipp_attribute_t *newlist;
3569     int j;
3570     newlist = ippAddStrings (request, IPP_TAG_PRINTER, IPP_TAG_URI,
3571 			     "member-uris", ippGetCount (printers) - 1,
3572 			     NULL, NULL);
3573     for (j = 0; j < i; j++)
3574       ippSetString(request, &newlist, j,
3575                    strdup (ippGetString (printers, j, NULL)));
3576     for (j = i; j < ippGetCount(newlist); j++)
3577       ippSetString(request, &newlist, j,
3578                    strdup (ippGetString (printers, j+1, NULL)));
3579   }
3580 
3581   ippDelete (answer);
3582   Connection_begin_allow_threads (self);
3583   answer = cupsDoRequest (self->http, request, "/admin/");
3584   Connection_end_allow_threads (self);
3585   if (PyErr_Occurred ()) {
3586     if (answer)
3587       ippDelete (answer);
3588     return NULL;
3589   }
3590 
3591   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
3592     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
3593 		   answer ? NULL : cupsLastErrorString ());
3594     if (answer)
3595       ippDelete (answer);
3596     return NULL;
3597   }
3598 
3599   ippDelete (answer);
3600   Py_RETURN_NONE;
3601 }
3602 
3603 static PyObject *
Connection_deleteClass(Connection * self,PyObject * args)3604 Connection_deleteClass (Connection *self, PyObject *args)
3605 {
3606   PyObject *classnameobj;
3607   char *classname;
3608   char classuri[HTTP_MAX_URI];
3609   ipp_t *request, *answer;
3610 
3611   if (!PyArg_ParseTuple (args, "O", &classnameobj))
3612     return NULL;
3613 
3614   if (UTF8_from_PyObj (&classname, classnameobj) == NULL)
3615     return NULL;
3616 
3617   request = ippNewRequest (CUPS_DELETE_CLASS);
3618   construct_uri (classuri, sizeof (classuri),
3619 		 "ipp://localhost/classes/", classname);
3620   free (classname);
3621   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
3622 		"printer-uri", NULL, classuri);
3623   Connection_begin_allow_threads (self);
3624   answer = cupsDoRequest (self->http, request, "/admin/");
3625   Connection_end_allow_threads (self);
3626   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
3627     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
3628 		   answer ? NULL : cupsLastErrorString ());
3629     if (answer)
3630       ippDelete (answer);
3631     return NULL;
3632   }
3633 
3634   ippDelete (answer);
3635   Py_RETURN_NONE;
3636 }
3637 
3638 static PyObject *
Connection_enablePrinter(Connection * self,PyObject * args,PyObject * kwds)3639 Connection_enablePrinter (Connection *self, PyObject *args, PyObject *kwds)
3640 {
3641   return do_printer_request (self, args, kwds, IPP_RESUME_PRINTER);
3642 }
3643 
3644 static PyObject *
Connection_disablePrinter(Connection * self,PyObject * args,PyObject * kwds)3645 Connection_disablePrinter (Connection *self, PyObject *args, PyObject *kwds)
3646 {
3647   return do_printer_request (self, args, kwds, IPP_PAUSE_PRINTER);
3648 }
3649 
3650 static PyObject *
Connection_acceptJobs(Connection * self,PyObject * args,PyObject * kwds)3651 Connection_acceptJobs (Connection *self, PyObject *args, PyObject *kwds)
3652 {
3653   return do_printer_request (self, args, kwds, CUPS_ACCEPT_JOBS);
3654 }
3655 
3656 static PyObject *
Connection_rejectJobs(Connection * self,PyObject * args,PyObject * kwds)3657 Connection_rejectJobs (Connection *self, PyObject *args, PyObject *kwds)
3658 {
3659   return do_printer_request (self, args, kwds, CUPS_REJECT_JOBS);
3660 }
3661 
3662 static PyObject *
Connection_getDefault(Connection * self,PyObject * args)3663 Connection_getDefault (Connection *self, PyObject *args)
3664 {
3665   const char *def;
3666   PyObject *ret;
3667   debugprintf ("-> Connection_getDefault()\n");
3668   Connection_begin_allow_threads (self);
3669   def = cupsGetDefault2 (self->http);
3670   Connection_end_allow_threads (self);
3671   if (def == NULL) {
3672     debugprintf ("<- Connection_getDefault() = None\n");
3673     Py_RETURN_NONE;
3674   } else {
3675     debugprintf ("<- Connection_getDefault() = \"%s\"\n", def);
3676     ret = PyUnicode_FromString (def);
3677   }
3678 
3679   return ret;
3680 }
3681 
3682 static PyObject *
Connection_setDefault(Connection * self,PyObject * args,PyObject * kwds)3683 Connection_setDefault (Connection *self, PyObject *args, PyObject *kwds)
3684 {
3685   return do_printer_request (self, args, kwds, CUPS_SET_DEFAULT);
3686 }
3687 
3688 static PyObject *
Connection_getPPD(Connection * self,PyObject * args)3689 Connection_getPPD (Connection *self, PyObject *args)
3690 {
3691   PyObject *ret;
3692   PyObject *printerobj;
3693   char *printer;
3694   const char *ppdfile;
3695 
3696   if (!PyArg_ParseTuple (args, "O", &printerobj))
3697     return NULL;
3698 
3699   if (UTF8_from_PyObj (&printer, printerobj) == NULL)
3700     return NULL;
3701 
3702   debugprintf ("-> Connection_getPPD()\n");
3703   Connection_begin_allow_threads (self);
3704   ppdfile = cupsGetPPD2 (self->http, printer);
3705   Connection_end_allow_threads (self);
3706   free (printer);
3707   if (!ppdfile) {
3708     ipp_status_t err = cupsLastError ();
3709     if (err)
3710       set_ipp_error (err, cupsLastErrorString ());
3711     else
3712       PyErr_SetString (PyExc_RuntimeError, "cupsGetPPD2 failed");
3713 
3714     debugprintf ("<- Connection_getPPD() (error)\n");
3715     return NULL;
3716   }
3717 
3718   ret = PyUnicode_FromString (ppdfile);
3719   debugprintf ("<- Connection_getPPD() = %s\n", ppdfile);
3720   return ret;
3721 }
3722 
3723 static PyObject *
Connection_getPPD3(Connection * self,PyObject * args,PyObject * kwds)3724 Connection_getPPD3 (Connection *self, PyObject *args, PyObject *kwds)
3725 {
3726   PyObject *ret, *obj;
3727   PyObject *printerobj;
3728   PyObject *fmodtime = NULL;
3729   char *printer;
3730   time_t modtime;
3731   PyObject *filenameobj = NULL;
3732   char *filename = NULL;
3733   char fname[PATH_MAX];
3734   http_status_t status;
3735   static char *kwlist[] = { "name", "modtime", "filename", NULL };
3736 
3737   if (!PyArg_ParseTupleAndKeywords (args, kwds, "O|OO", kwlist,
3738 				    &printerobj, &fmodtime, &filenameobj))
3739     return NULL;
3740 
3741   if (UTF8_from_PyObj (&printer, printerobj) == NULL)
3742     return NULL;
3743 
3744   if (fmodtime) {
3745     double d = PyFloat_AsDouble (fmodtime);
3746     if (PyErr_Occurred()) {
3747       free (printer);
3748       return NULL;
3749     }
3750 
3751     modtime = (time_t) d;
3752   } else
3753     modtime = 0;
3754 
3755   if (filenameobj)
3756     if (UTF8_from_PyObj (&filename, filenameobj) == NULL) {
3757       free (printer);
3758       return NULL;
3759     }
3760   if (filename) {
3761     if (strlen (filename) > sizeof (fname)) {
3762       PyErr_SetString (PyExc_TypeError, "overlength filename");
3763       free (printer);
3764       free (filename);
3765       return NULL;
3766     }
3767     strcpy (fname, filename);
3768   } else
3769     fname[0] = '\0';
3770 
3771   debugprintf ("-> Connection_getPPD3()\n");
3772   Connection_begin_allow_threads (self);
3773   status = cupsGetPPD3 (self->http, printer, &modtime,
3774 			fname, sizeof (fname));
3775   Connection_end_allow_threads (self);
3776 
3777   free (printer);
3778   free (filename);
3779 
3780   ret = PyTuple_New (3);
3781   if (!ret)
3782     return NULL;
3783 
3784   obj = PyLong_FromLong ((long) status);
3785 
3786   if (!obj) {
3787     Py_DECREF (ret);
3788     return NULL;
3789   }
3790 
3791   PyTuple_SetItem (ret, 0, obj);
3792 
3793   obj = PyFloat_FromDouble ((double) modtime);
3794   if (!obj) {
3795     Py_DECREF (ret);
3796     return NULL;
3797   }
3798 
3799   PyTuple_SetItem (ret, 1, obj);
3800 
3801   obj = PyUnicode_FromString (fname);
3802   if (!obj) {
3803     Py_DECREF (ret);
3804     return NULL;
3805   }
3806 
3807   PyTuple_SetItem (ret, 2, obj);
3808 
3809   debugprintf ("<- Connection_getPPD3() = (%d,%ld,%s)\n",
3810 	       status, modtime, fname);
3811   return ret;
3812 }
3813 
3814 static PyObject *
Connection_printTestPage(Connection * self,PyObject * args,PyObject * kwds)3815 Connection_printTestPage (Connection *self, PyObject *args, PyObject *kwds)
3816 {
3817   PyObject *printerobj;
3818   char *printer;
3819   PyObject *fileobj = NULL;
3820   char *file = NULL;
3821   PyObject *titleobj = NULL;
3822   char *title = NULL;
3823   PyObject *formatobj = NULL;
3824   char *format = NULL;
3825   PyObject *userobj = NULL;
3826   char *user = NULL;
3827   const char *datadir;
3828   char filename[PATH_MAX];
3829   char uri[HTTP_MAX_URI];
3830   ipp_t *request, *answer;
3831   ipp_attribute_t *attr;
3832   char *resource;
3833   int jobid = 0;
3834   int i;
3835   static char *kwlist[] = { "name", "file", "title", "format", "user", NULL };
3836 
3837   if (!PyArg_ParseTupleAndKeywords (args, kwds, "O|OOOO", kwlist,
3838 				    &printerobj, &fileobj, &titleobj,
3839 				    &formatobj, &userobj))
3840     return NULL;
3841 
3842   if (UTF8_from_PyObj (&printer, printerobj) == NULL)
3843     return NULL;
3844 
3845   if ((fileobj && UTF8_from_PyObj (&file, fileobj) == NULL) ||
3846       (titleobj && UTF8_from_PyObj (&title, titleobj) == NULL) ||
3847       (formatobj && UTF8_from_PyObj (&format, formatobj) == NULL) ||
3848       (userobj && UTF8_from_PyObj (&user, userobj) == NULL)) {
3849     free (printer);
3850     free (file);
3851     free (title);
3852     free (format);
3853     free (user);
3854     return NULL;
3855   }
3856 
3857   if (!fileobj) {
3858     const char *testprint[] = { "%s/data/testprint",
3859 				"%s/data/testprint.ps",
3860 				NULL };
3861     if ((datadir = getenv ("CUPS_DATADIR")) != NULL) {
3862       const char **pattern;
3863       for (pattern = testprint; *pattern != NULL; pattern++) {
3864 	snprintf (filename, sizeof (filename), *pattern, datadir);
3865 	if (access (filename, R_OK) == 0)
3866 	  break;
3867       }
3868     } else {
3869       const char *const dirs[] = { "/usr/share/cups",
3870 				   "/usr/local/share/cups",
3871 				   NULL };
3872       int found = 0;
3873       int i;
3874       for (i = 0; (datadir = dirs[i]) != NULL; i++) {
3875 	const char **pattern;
3876 	for (pattern = testprint; *pattern != NULL; pattern++) {
3877 	  snprintf (filename, sizeof (filename), *pattern, datadir);
3878 	  if (access (filename, R_OK) == 0) {
3879 	    found = 1;
3880 	    break;
3881 	  }
3882 	}
3883 
3884 	if (found)
3885 	  break;
3886       }
3887 
3888       if (datadir == NULL)
3889 	/* We haven't found the testprint.ps file, so just pick a path
3890 	 * and try it.  This will certainly fail with
3891 	 * client-error-not-found, but we'll let that happen rather
3892 	 * than raising an exception so as to be consistent with the
3893 	 * case where CUPS_DATADIR is set and we trust it. */
3894 	snprintf (filename, sizeof (filename), testprint[0], dirs[0]);
3895     }
3896 
3897     file = filename;
3898   }
3899 
3900   if (!titleobj)
3901     title = "Test Page";
3902 
3903   if (!userobj)
3904 	  user = (char *) cupsUser();
3905 
3906   construct_uri (uri, sizeof (uri),
3907 		 "ipp://localhost/printers/", printer);
3908   resource = uri + strlen ("ipp://localhost");
3909   for (i = 0; i < 2; i++) {
3910     request = ippNewRequest (IPP_PRINT_JOB);
3911     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
3912 		  NULL, uri);
3913     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
3914 		  "requesting-user-name", NULL, user);
3915     ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name",
3916 		  NULL, title);
3917     if (format)
3918       ippAddString (request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, "document-format",
3919 		    NULL, format);
3920 
3921     Connection_begin_allow_threads (self);
3922     answer = cupsDoFileRequest (self->http, request, resource, file);
3923     Connection_end_allow_threads (self);
3924     if (answer && ippGetStatusCode (answer) == IPP_NOT_POSSIBLE) {
3925       ippDelete (answer);
3926       // Perhaps it's a class, not a printer.
3927       construct_uri (uri, sizeof (uri),
3928 		     "ipp://localhost/classes/", printer);
3929     } else break;
3930   }
3931 
3932   free (printer);
3933   if (fileobj)
3934     free (file);
3935   if (titleobj)
3936     free (title);
3937   if (formatobj)
3938     free (format);
3939   if (userobj)
3940     free (user);
3941 
3942   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
3943     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
3944 		   answer ? NULL : cupsLastErrorString ());
3945     if (answer)
3946       ippDelete (answer);
3947     return NULL;
3948   }
3949 
3950   attr = ippFindAttribute (answer, "job-id", IPP_TAG_INTEGER);
3951   if (attr)
3952     jobid = ippGetInteger (attr, 0);
3953 
3954   ippDelete (answer);
3955   return Py_BuildValue ("i", jobid);
3956 }
3957 
3958 static PyObject *
Connection_adminExportSamba(Connection * self,PyObject * args)3959 Connection_adminExportSamba (Connection *self, PyObject *args)
3960 {
3961   int ret;
3962   PyObject *nameobj;
3963   char *name;
3964   PyObject *serverobj;
3965   char *server;
3966   PyObject *userobj;
3967   char  *user;
3968   PyObject *passwordobj;
3969   char *password;
3970   char ppdfile[1024];
3971   FILE *tmpfile(void);
3972   FILE *tf;
3973   char str[80];
3974 
3975   if (!PyArg_ParseTuple (args, "OOOO", &nameobj, &serverobj, &userobj,
3976                          &passwordobj))
3977     return NULL;
3978 
3979   if (UTF8_from_PyObj (&name, nameobj) == NULL ||
3980       UTF8_from_PyObj (&server, serverobj) == NULL ||
3981       UTF8_from_PyObj (&user, userobj) == NULL ||
3982       UTF8_from_PyObj (&password, passwordobj) == NULL) {
3983     free (name);
3984     free (server);
3985     free (user);
3986     free (password);
3987     PyErr_SetString(PyExc_RuntimeError,
3988                     "name, samba_server, samba_username, samba_password "
3989                     "must be specified");
3990     return NULL;
3991   }
3992 
3993   if (!cupsAdminCreateWindowsPPD(self->http, name, ppdfile, sizeof(ppdfile))) {
3994     PyErr_SetString(PyExc_RuntimeError,
3995                     "No PPD file found for the printer");
3996     return NULL;
3997   }
3998 
3999   debugprintf ("-> Connection_adminExportSamba()\n");
4000   tf = tmpfile();
4001   Connection_begin_allow_threads (self);
4002   ret = cupsAdminExportSamba(name, ppdfile, server, user, password, tf);
4003   Connection_end_allow_threads (self);
4004 
4005   free (name);
4006   free (server);
4007   free (user);
4008   free (password);
4009   unlink (ppdfile);
4010 
4011   if (!ret) {
4012     rewind(tf);
4013     // Read logfile line by line to get Exit status message on the last line.
4014     while (fgets (str, sizeof(str), tf) != NULL) { }
4015     fclose (tf);
4016     if (str[strlen(str) -1] == '\n') {
4017       str[strlen(str) -1] = '\0';
4018     }
4019     PyErr_SetString (PyExc_RuntimeError, str);
4020     debugprintf ("<- Connection_adminExportSamba() EXCEPTION\n");
4021     return NULL;
4022   }
4023   fclose (tf);
4024   debugprintf ("<- Connection_adminExportSamba()\n");
4025   Py_RETURN_NONE;
4026 }
4027 
4028 static PyObject *
Connection_adminGetServerSettings(Connection * self)4029 Connection_adminGetServerSettings (Connection *self)
4030 {
4031   PyObject *ret = PyDict_New ();
4032   int num_settings, i;
4033   cups_option_t *settings;
4034   debugprintf ("-> Connection_adminGetServerSettings()\n");
4035   Connection_begin_allow_threads (self);
4036   cupsAdminGetServerSettings (self->http, &num_settings, &settings);
4037   Connection_end_allow_threads (self);
4038   for (i = 0; i < num_settings; i++) {
4039     PyObject *string = PyUnicode_FromString (settings[i].value);
4040     PyDict_SetItemString (ret, settings[i].name, string);
4041     Py_DECREF (string);
4042   }
4043 
4044   cupsFreeOptions (num_settings, settings);
4045   debugprintf ("<- Connection_adminGetServerSettings()\n");
4046   return ret;
4047 }
4048 
4049 static PyObject *
Connection_adminSetServerSettings(Connection * self,PyObject * args)4050 Connection_adminSetServerSettings (Connection *self, PyObject *args)
4051 {
4052   PyObject *dict, *key, *val;
4053   int ret;
4054   int num_settings = 0;
4055   DICT_POS_TYPE pos = 0;
4056   cups_option_t *settings = NULL;
4057   if (!PyArg_ParseTuple (args, "O", &dict))
4058     return NULL;
4059   if (!PyDict_Check (dict)) {
4060     PyErr_SetString (PyExc_TypeError, "Expecting dict");
4061     return NULL;
4062   }
4063 
4064   debugprintf ("-> Connection_adminSetServerSettings()\n");
4065   while (PyDict_Next (dict, &pos, &key, &val)) {
4066     char *name, *value;
4067     if ((!PyUnicode_Check (key) && !PyBytes_Check (key)) ||
4068         (!PyUnicode_Check (val) && !PyBytes_Check (val))) {
4069       cupsFreeOptions (num_settings, settings);
4070       PyErr_SetString (PyExc_TypeError, "Keys and values must be strings");
4071       debugprintf ("<- Connection_adminSetServerSettings() EXCEPTION\n");
4072       return NULL;
4073     }
4074 
4075     UTF8_from_PyObj (&name, key);
4076     UTF8_from_PyObj (&value, val);
4077     debugprintf ("%s: %s\n", name, value);
4078     num_settings = cupsAddOption (name,
4079 				  value,
4080 				  num_settings,
4081 				  &settings);
4082     free (name);
4083     free (value);
4084   }
4085 
4086   debugprintf ("num_settings=%d, settings=%p\n", num_settings, settings);
4087   Connection_begin_allow_threads (self);
4088   ret = cupsAdminSetServerSettings (self->http, num_settings, settings);
4089   Connection_end_allow_threads (self);
4090   if (!ret) {
4091     cupsFreeOptions (num_settings, settings);
4092     PyErr_SetString (PyExc_RuntimeError, "Failed to set settings");
4093     debugprintf ("<- Connection_adminSetServerSettings() EXCEPTION\n");
4094     return NULL;
4095   }
4096 
4097   cupsFreeOptions (num_settings, settings);
4098   debugprintf ("<- Connection_adminSetServerSettings()\n");
4099   Py_RETURN_NONE;
4100 }
4101 
4102 static PyObject *
Connection_getSubscriptions(Connection * self,PyObject * args,PyObject * kwds)4103 Connection_getSubscriptions (Connection *self, PyObject *args, PyObject *kwds)
4104 {
4105   PyObject *uriobj;
4106   char *uri;
4107   PyObject *my_subscriptions = Py_False;
4108   int job_id = -1;
4109   ipp_t *request, *answer;
4110   ipp_attribute_t *attr;
4111   PyObject *result, *subscription;
4112   static char *kwlist[] = { "uri", "my_subscriptions", "job_id", NULL };
4113 
4114   if (!PyArg_ParseTupleAndKeywords (args, kwds, "O|Oi", kwlist,
4115 				    &uriobj, &my_subscriptions, &job_id))
4116     return NULL;
4117 
4118   if (UTF8_from_PyObj (&uri, uriobj) == NULL)
4119     return NULL;
4120 
4121   if (my_subscriptions && !PyBool_Check (my_subscriptions)) {
4122     PyErr_SetString (PyExc_TypeError, "my_subscriptions must be a bool");
4123     return NULL;
4124   }
4125 
4126   debugprintf ("-> Connection_getSubscriptions()\n");
4127   request = ippNewRequest (IPP_GET_SUBSCRIPTIONS);
4128   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
4129 		"printer-uri", NULL, uri);
4130   free (uri);
4131   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
4132 		"requesting-user-name", NULL, cupsUser ());
4133 
4134   if (my_subscriptions == Py_True)
4135     ippAddBoolean (request, IPP_TAG_OPERATION, "my-subscriptions", 1);
4136 
4137   if (job_id != -1)
4138     ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
4139 		   "job-id", job_id);
4140 
4141   Connection_begin_allow_threads (self);
4142   answer = cupsDoRequest (self->http, request, "/");
4143   Connection_end_allow_threads (self);
4144   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
4145     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
4146 		   answer ? NULL : cupsLastErrorString ());
4147     if (answer)
4148       ippDelete (answer);
4149     debugprintf ("<- Connection_getSubscriptions() EXCEPTION\n");
4150     return NULL;
4151   }
4152 
4153   result = PyList_New (0);
4154   for (attr = ippFirstAttribute (answer); attr; attr = ippNextAttribute (answer))
4155     if (ippGetGroupTag (attr) == IPP_TAG_SUBSCRIPTION)
4156       break;
4157 
4158   subscription = NULL;
4159   for (; attr; attr = ippNextAttribute (answer)) {
4160     PyObject *obj;
4161     if (ippGetGroupTag (attr) == IPP_TAG_ZERO) {
4162       // End of subscription.
4163       if (subscription) {
4164 	PyList_Append (result, subscription);
4165 	Py_DECREF (subscription);
4166       }
4167 
4168       subscription = NULL;
4169       continue;
4170     }
4171 
4172     if (ippGetCount (attr) > 1 || !strcmp (ippGetName (attr), "notify-events"))
4173       obj = PyList_from_attr_values (attr);
4174     else
4175       obj = PyObject_from_attr_value (attr, 0);
4176 
4177     if (!obj)
4178       // Can't represent this.
4179       continue;
4180 
4181     if (!subscription)
4182       subscription = PyDict_New ();
4183 
4184     PyDict_SetItemString (subscription, ippGetName (attr), obj);
4185     Py_DECREF (obj);
4186   }
4187 
4188   if (subscription) {
4189     PyList_Append (result, subscription);
4190     Py_DECREF (subscription);
4191   }
4192 
4193   ippDelete (answer);
4194   debugprintf ("<- Connection_getSubscriptions()\n");
4195   return result;
4196 }
4197 
4198 static PyObject *
Connection_createSubscription(Connection * self,PyObject * args,PyObject * kwds)4199 Connection_createSubscription (Connection *self, PyObject *args,
4200 			       PyObject *kwds)
4201 {
4202   PyObject *resource_uriobj;
4203   char *resource_uri;
4204   PyObject *events = NULL;
4205   int job_id = -1, lease_duration = -1, time_interval = -1;
4206   PyObject *recipient_uriobj = NULL, *user_dataobj = NULL;
4207   char *recipient_uri = NULL, *user_data = NULL;
4208   ipp_t *request, *answer;
4209   int i, n = 0;
4210   char *tmp;
4211   ipp_attribute_t *attr;
4212   static char *kwlist[] = { "uri", "events", "job_id", "recipient_uri",
4213 			    "lease_duration", "time_interval", "user_data",
4214 			    NULL };
4215 
4216   if (!PyArg_ParseTupleAndKeywords (args, kwds, "O|OiOiiO", kwlist,
4217 				    &resource_uriobj, &events, &job_id,
4218 				    &recipient_uriobj, &lease_duration,
4219 				    &time_interval, &user_dataobj))
4220     return NULL;
4221 
4222   if (UTF8_from_PyObj (&resource_uri, resource_uriobj) == NULL)
4223     return NULL;
4224 
4225   if (recipient_uriobj &&
4226       UTF8_from_PyObj (&recipient_uri, recipient_uriobj) == NULL) {
4227     free (resource_uri);
4228     return NULL;
4229   }
4230 
4231   if (user_dataobj && UTF8_from_PyObj (&user_data, user_dataobj) == NULL) {
4232     free (resource_uri);
4233     if (recipient_uriobj)
4234       free (recipient_uri);
4235     return NULL;
4236   }
4237 
4238   if (events) {
4239     if (!PyList_Check (events)) {
4240       PyErr_SetString (PyExc_TypeError, "events must be a list");
4241       return NULL;
4242     }
4243 
4244     n = PyList_Size (events);
4245     for (i = 0; i < n; i++) {
4246       PyObject *event = PyList_GetItem (events, i);
4247       if (!PyUnicode_Check (event) && !PyBytes_Check (event)) {
4248 	PyErr_SetString (PyExc_TypeError, "events must be a list of strings");
4249 	return NULL;
4250       }
4251     }
4252   }
4253 
4254   debugprintf ("-> Connection_createSubscription(%s)\n", resource_uri);
4255   request = ippNewRequest (IPP_CREATE_PRINTER_SUBSCRIPTION);
4256   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
4257 		"printer-uri", NULL, resource_uri);
4258   ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
4259 		"notify-pull-method", NULL, "ippget");
4260   ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_CHARSET,
4261 		"notify-charset", NULL, "utf-8");
4262   ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_NAME,
4263 		"requesting-user-name", NULL, cupsUser ());
4264 
4265   if (recipient_uriobj) {
4266     ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI,
4267 		  "notify-recipient-uri", NULL, recipient_uri);
4268     free (recipient_uri);
4269   }
4270 
4271   if (user_dataobj) {
4272     ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_STRING,
4273 		  "notify-user-data", NULL, user_data);
4274     free (user_data);
4275   }
4276 
4277   if (events) {
4278     attr = ippAddStrings (request, IPP_TAG_SUBSCRIPTION,
4279 			  IPP_TAG_KEYWORD, "notify-events",
4280 			  n, NULL, NULL);
4281     for (i = 0; i < n; i++) {
4282       PyObject *event = PyList_GetItem (events, i);
4283       ippSetString(request, &attr, i, UTF8_from_PyObj (&tmp, event));
4284       free(tmp);
4285     }
4286   }
4287 
4288   if (lease_duration != -1)
4289     ippAddInteger (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
4290 		   "notify-lease-duration", lease_duration);
4291 
4292   if (time_interval != -1)
4293     ippAddInteger (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
4294 		   "notify-time-interval", time_interval);
4295 
4296   if (job_id != -1)
4297     ippAddInteger (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
4298 		   "notify-job-id", job_id);
4299 
4300   Connection_begin_allow_threads (self);
4301   answer = cupsDoRequest (self->http, request, "/");
4302   Connection_end_allow_threads (self);
4303   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
4304     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
4305 		   answer ? NULL : cupsLastErrorString ());
4306     if (answer)
4307       ippDelete (answer);
4308     debugprintf ("<- Connection_createSubscription() EXCEPTION\n");
4309     return NULL;
4310   }
4311 
4312   i = -1;
4313   for (attr = ippFirstAttribute (answer); attr; attr = ippNextAttribute (answer)) {
4314     if (ippGetGroupTag (attr) == IPP_TAG_SUBSCRIPTION) {
4315       if (ippGetValueTag (attr) == IPP_TAG_INTEGER &&
4316 	  !strcmp (ippGetName (attr), "notify-subscription-id"))
4317 	i = ippGetInteger (attr, 0);
4318       else if (ippGetValueTag (attr) == IPP_TAG_ENUM &&
4319 	       !strcmp (ippGetName (attr), "notify-status-code"))
4320 	debugprintf ("notify-status-code = %d\n", ippGetInteger (attr, 0));
4321     }
4322   }
4323 
4324   ippDelete (answer);
4325   debugprintf ("<- Connection_createSubscription() = %d\n", i);
4326   return PyLong_FromLong (i);
4327 }
4328 
4329 static PyObject *
Connection_getNotifications(Connection * self,PyObject * args,PyObject * kwds)4330 Connection_getNotifications (Connection *self, PyObject *args, PyObject *kwds)
4331 {
4332   PyObject *subscription_ids, *sequence_numbers = NULL;
4333   ipp_t *request, *answer;
4334   int i, num_ids, num_seqs = 0;
4335   ipp_attribute_t *attr;
4336   PyObject *result, *events, *event;
4337   static char *kwlist[] = { "subscription_ids", "sequence_numbers", NULL };
4338 
4339   if (!PyArg_ParseTupleAndKeywords (args, kwds, "O|O", kwlist,
4340 				    &subscription_ids, &sequence_numbers))
4341     return NULL;
4342 
4343   if (!PyList_Check (subscription_ids)) {
4344     PyErr_SetString (PyExc_TypeError, "subscriptions_ids must be a list");
4345     return NULL;
4346   }
4347 
4348   num_ids = PyList_Size (subscription_ids);
4349   for (i = 0; i < num_ids; i++) {
4350     PyObject *id = PyList_GetItem (subscription_ids, i);
4351     if (!PyLong_Check (id)) {
4352       PyErr_SetString (PyExc_TypeError, "subscription_ids must be a list "
4353 		       "of integers");
4354       return NULL;
4355     }
4356   }
4357 
4358   if (sequence_numbers) {
4359     if (!PyList_Check (sequence_numbers)) {
4360       PyErr_SetString (PyExc_TypeError, "sequence_numbers must be a list");
4361       return NULL;
4362     }
4363 
4364     num_seqs = PyList_Size (sequence_numbers);
4365     for (i = 0; i < num_seqs; i++) {
4366       PyObject *id = PyList_GetItem (sequence_numbers, i);
4367       if (!PyLong_Check (id)) {
4368 	PyErr_SetString (PyExc_TypeError, "sequence_numbers must be a list "
4369 			 "of integers");
4370 	return NULL;
4371       }
4372     }
4373   }
4374 
4375   debugprintf ("-> Connection_getNotifications()\n");
4376   request = ippNewRequest (IPP_GET_NOTIFICATIONS);
4377   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
4378 		"printer-uri", NULL, "/");
4379   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
4380 		"requesting-user-name", NULL, cupsUser ());
4381 
4382   attr = ippAddIntegers (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
4383 			 "notify-subscription-ids", num_ids, NULL);
4384   for (i = 0; i < num_ids; i++) {
4385     PyObject *id = PyList_GetItem (subscription_ids, i);
4386     ippSetInteger (request, &attr, i, PyLong_AsLong (id));
4387   }
4388 
4389   if (sequence_numbers) {
4390     attr = ippAddIntegers (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
4391 			   "notify-sequence-numbers", num_seqs, NULL);
4392     for (i = 0; i < num_seqs; i++) {
4393       PyObject *num = PyList_GetItem (sequence_numbers, i);
4394       ippSetInteger (request, &attr, i, PyLong_AsLong (num));
4395     }
4396   }
4397 
4398   Connection_begin_allow_threads (self);
4399   answer = cupsDoRequest (self->http, request, "/");
4400   Connection_end_allow_threads (self);
4401   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
4402     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
4403 		   answer ? NULL : cupsLastErrorString ());
4404     if (answer)
4405       ippDelete (answer);
4406     debugprintf ("<- Connection_getNotifications() EXCEPTION\n");
4407     return NULL;
4408   }
4409 
4410   result = PyDict_New ();
4411 
4412   // Result-wide attributes.
4413   attr = ippFindAttribute (answer, "notify-get-interval", IPP_TAG_INTEGER);
4414   if (attr) {
4415     PyObject *val = PyLong_FromLong (ippGetInteger (attr, 0));
4416     PyDict_SetItemString (result, ippGetName (attr), val);
4417     Py_DECREF (val);
4418   }
4419 
4420   attr = ippFindAttribute (answer, "printer-up-time", IPP_TAG_INTEGER);
4421   if (attr) {
4422     PyObject *val = PyLong_FromLong (ippGetInteger (attr, 0));
4423     PyDict_SetItemString (result, ippGetName (attr), val);
4424     Py_DECREF (val);
4425   }
4426 
4427   events = PyList_New (0);
4428   for (attr = ippFirstAttribute (answer); attr; attr = ippNextAttribute (answer))
4429     if (ippGetGroupTag (attr) == IPP_TAG_EVENT_NOTIFICATION)
4430       break;
4431 
4432   event = NULL;
4433   for (; attr; attr = ippNextAttribute (answer)) {
4434     PyObject *obj;
4435     if (ippGetGroupTag (attr) == IPP_TAG_ZERO) {
4436       // End of event notification.
4437       if (event) {
4438 	PyList_Append (events, event);
4439 	Py_DECREF (event);
4440       }
4441 
4442       event = NULL;
4443       continue;
4444     }
4445 
4446     if (ippGetCount (attr) > 1 ||
4447 	!strcmp (ippGetName (attr), "notify-events") ||
4448 	!strcmp (ippGetName (attr), "printer-state-reasons") ||
4449 	!strcmp (ippGetName (attr), "job-printer-state-reasons"))
4450       obj = PyList_from_attr_values (attr);
4451     else
4452       obj = PyObject_from_attr_value (attr, 0);
4453 
4454     if (!obj)
4455       // Can't represent this.
4456       continue;
4457 
4458     if (!event)
4459       event = PyDict_New ();
4460 
4461     PyDict_SetItemString (event, ippGetName (attr), obj);
4462     Py_DECREF (obj);
4463   }
4464 
4465   if (event) {
4466     PyList_Append (events, event);
4467     Py_DECREF (event);
4468   }
4469 
4470   ippDelete (answer);
4471   PyDict_SetItemString (result, "events", events);
4472   Py_DECREF (events);
4473   debugprintf ("<- Connection_getNotifications()\n");
4474   return result;
4475 }
4476 
4477 static PyObject *
Connection_renewSubscription(Connection * self,PyObject * args,PyObject * kwds)4478 Connection_renewSubscription (Connection *self, PyObject *args, PyObject *kwds)
4479 {
4480   int id;
4481   int lease_duration = -1;
4482   ipp_t *request, *answer;
4483   static char *kwlist[] = { "id", "lease_duration", NULL };
4484 
4485   if (!PyArg_ParseTupleAndKeywords (args, kwds, "i|i", kwlist,
4486 				    &id, &lease_duration))
4487     return NULL;
4488 
4489   debugprintf ("-> Connection_renewSubscription()\n");
4490   request = ippNewRequest (IPP_RENEW_SUBSCRIPTION);
4491   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
4492 		"printer-uri", NULL, "/");
4493   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
4494 		"requesting-user-name", NULL, cupsUser ());
4495   ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
4496 		 "notify-subscription-id", id);
4497 
4498   if (lease_duration != -1)
4499     ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
4500 		   "notify-lease-duration", lease_duration);
4501 
4502   Connection_begin_allow_threads (self);
4503   answer = cupsDoRequest (self->http, request, "/");
4504   Connection_end_allow_threads (self);
4505   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
4506     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
4507 		   answer ? NULL : cupsLastErrorString ());
4508     if (answer)
4509       ippDelete (answer);
4510     debugprintf ("<- Connection_renewSubscription() EXCEPTION\n");
4511     return NULL;
4512   }
4513 
4514   ippDelete (answer);
4515   debugprintf ("<- Connection_renewSubscription()\n");
4516   Py_RETURN_NONE;
4517 }
4518 
4519 static PyObject *
Connection_cancelSubscription(Connection * self,PyObject * args)4520 Connection_cancelSubscription (Connection *self, PyObject *args)
4521 {
4522   int id;
4523   ipp_t *request, *answer;
4524 
4525   if (!PyArg_ParseTuple (args, "i", &id))
4526     return NULL;
4527 
4528   debugprintf ("-> Connection_cancelSubscription()\n");
4529   request = ippNewRequest (IPP_CANCEL_SUBSCRIPTION);
4530   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
4531 		"printer-uri", NULL, "/");
4532   ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
4533 		"requesting-user-name", NULL, cupsUser ());
4534   ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
4535 		"notify-subscription-id", id);
4536 
4537   Connection_begin_allow_threads (self);
4538   answer = cupsDoRequest (self->http, request, "/");
4539   Connection_end_allow_threads (self);
4540   if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
4541     set_ipp_error (answer ? ippGetStatusCode (answer) : cupsLastError (),
4542 		   answer ? NULL : cupsLastErrorString ());
4543     if (answer)
4544       ippDelete (answer);
4545     debugprintf ("<- Connection_cancelSubscription() EXCEPTION\n");
4546     return NULL;
4547   }
4548 
4549   ippDelete (answer);
4550   debugprintf ("<- Connection_cancelSubscription()\n");
4551   Py_RETURN_NONE;
4552 }
4553 
4554 static PyObject *
Connection_printFile(Connection * self,PyObject * args,PyObject * kwds)4555 Connection_printFile (Connection *self, PyObject *args, PyObject *kwds)
4556 {
4557   static char *kwlist[] = { "printer", "filename", "title", "options", NULL };
4558   PyObject *printer_obj;
4559   char *printer;
4560   PyObject *filename_obj;
4561   char *filename;
4562   PyObject *title_obj;
4563   char *title;
4564   PyObject *options_obj, *key, *val;
4565   int num_settings = 0;
4566   DICT_POS_TYPE pos = 0;
4567   cups_option_t *settings = NULL;
4568   int jobid;
4569 
4570   if (!PyArg_ParseTupleAndKeywords (args, kwds, "OOOO", kwlist,
4571 				    &printer_obj, &filename_obj, &title_obj,
4572 				    &options_obj))
4573     return NULL;
4574 
4575   if (UTF8_from_PyObj (&printer, printer_obj) == NULL)
4576     return NULL;
4577   if (UTF8_from_PyObj (&filename, filename_obj) == NULL) {
4578     free (printer);
4579     return NULL;
4580   }
4581   if (UTF8_from_PyObj (&title, title_obj) == NULL) {
4582     free (filename);
4583     free (printer);
4584     return NULL;
4585   }
4586 
4587   if (!PyDict_Check (options_obj)) {
4588     free (title);
4589     free (filename);
4590     free (printer);
4591     PyErr_SetString (PyExc_TypeError, "options must be a dict");
4592     return NULL;
4593   }
4594   while (PyDict_Next (options_obj, &pos, &key, &val)) {
4595     char *name, *value;
4596     if ((!PyUnicode_Check (key) && !PyBytes_Check (key)) ||
4597         (!PyUnicode_Check (val) && !PyBytes_Check (val))) {
4598       cupsFreeOptions (num_settings, settings);
4599       free (title);
4600       free (filename);
4601       free (printer);
4602       PyErr_SetString (PyExc_TypeError, "Keys and values must be strings");
4603       return NULL;
4604     }
4605 
4606     num_settings = cupsAddOption (UTF8_from_PyObj (&name, key),
4607 				  UTF8_from_PyObj (&value, val),
4608 				  num_settings,
4609 				  &settings);
4610     free (name);
4611     free (value);
4612   }
4613 
4614   Connection_begin_allow_threads (self);
4615   jobid = cupsPrintFile2 (self->http, printer, filename, title, num_settings,
4616                           settings);
4617   Connection_end_allow_threads (self);
4618 
4619   if (jobid == 0) {
4620     cupsFreeOptions (num_settings, settings);
4621     free (title);
4622     free (filename);
4623     free (printer);
4624     set_ipp_error (cupsLastError (), cupsLastErrorString ());
4625     return NULL;
4626   }
4627 
4628   cupsFreeOptions (num_settings, settings);
4629   free (title);
4630   free (filename);
4631   free (printer);
4632   return PyLong_FromLong (jobid);
4633 }
4634 
4635 static void
free_string_list(int num_string,char ** strings)4636 free_string_list (int num_string, char **strings)
4637 {
4638   int i;
4639   for (i = 0; i < num_string; ++i) {
4640     free (strings[i]);
4641   }
4642   free (strings);
4643 }
4644 
4645 static PyObject *
Connection_printFiles(Connection * self,PyObject * args,PyObject * kwds)4646 Connection_printFiles (Connection *self, PyObject *args, PyObject *kwds)
4647 {
4648   static char *kwlist[] = { "printer", "filenames", "title", "options", NULL };
4649   PyObject *printer_obj;
4650   char *printer;
4651   PyObject *filenames_obj;
4652   int num_filenames;
4653   char **filenames;
4654   PyObject *title_obj;
4655   char *title;
4656   PyObject *options_obj, *key, *val;
4657   int num_settings = 0;
4658   DICT_POS_TYPE pos = 0;
4659   cups_option_t *settings = NULL;
4660   int jobid;
4661 
4662   if (!PyArg_ParseTupleAndKeywords (args, kwds, "OOOO", kwlist,
4663 				    &printer_obj, &filenames_obj, &title_obj,
4664 				    &options_obj))
4665     return NULL;
4666 
4667   if (UTF8_from_PyObj (&printer, printer_obj) == NULL)
4668     return NULL;
4669 
4670   if (!PyList_Check (filenames_obj)) {
4671     free (printer);
4672     PyErr_SetString (PyExc_TypeError, "filenames must be a list");
4673     return NULL;
4674   }
4675   num_filenames = PyList_Size (filenames_obj);
4676   if (num_filenames == 0) {
4677     free (printer);
4678     PyErr_SetString (PyExc_RuntimeError, "filenames list is empty");
4679     return NULL;
4680   }
4681   filenames = malloc (num_filenames * sizeof(char*));
4682   for (pos = 0; pos < num_filenames; ++pos) {
4683     PyObject *filename_obj = PyList_GetItem(filenames_obj, pos);
4684     if (UTF8_from_PyObj (&filenames[pos], filename_obj) == NULL) {
4685       free_string_list (pos, filenames);
4686       free (printer);
4687       return NULL;
4688     }
4689   }
4690   if (UTF8_from_PyObj (&title, title_obj) == NULL) {
4691     free_string_list (num_filenames, filenames);
4692     free (printer);
4693     return NULL;
4694   }
4695 
4696   if (!PyDict_Check (options_obj)) {
4697     free (title);
4698     free_string_list (num_filenames, filenames);
4699     free (printer);
4700     PyErr_SetString (PyExc_TypeError, "options must be a dict");
4701     return NULL;
4702   }
4703   while (PyDict_Next (options_obj, &pos, &key, &val)) {
4704     char *name, *value;
4705     if ((!PyUnicode_Check (key) && !PyBytes_Check (key)) ||
4706         (!PyUnicode_Check (val) && !PyBytes_Check (val))) {
4707       cupsFreeOptions (num_settings, settings);
4708       free (title);
4709       free_string_list (num_filenames, filenames);
4710       free (printer);
4711       PyErr_SetString (PyExc_TypeError, "Keys and values must be strings");
4712       return NULL;
4713     }
4714 
4715     num_settings = cupsAddOption (UTF8_from_PyObj (&name, key),
4716 				  UTF8_from_PyObj (&value, key),
4717 				  num_settings,
4718 				  &settings);
4719     free (name);
4720     free (value);
4721   }
4722 
4723   Connection_begin_allow_threads (self);
4724   jobid = cupsPrintFiles2 (self->http, printer, num_filenames,
4725                            (const char **) filenames, title, num_settings,
4726 						   settings);
4727   Connection_end_allow_threads (self);
4728 
4729   if (jobid == 0) {
4730     cupsFreeOptions (num_settings, settings);
4731     free (title);
4732     free_string_list (num_filenames, filenames);
4733     free (printer);
4734     set_ipp_error (cupsLastError (), cupsLastErrorString ());
4735     return NULL;
4736   }
4737 
4738   cupsFreeOptions (num_settings, settings);
4739   free (title);
4740   free_string_list (num_filenames, filenames);
4741   free (printer);
4742   return PyLong_FromLong (jobid);
4743 }
4744 
4745 PyMethodDef Connection_methods[] =
4746   {
4747     { "getPrinters",
4748       (PyCFunction) Connection_getPrinters, METH_NOARGS,
4749       "getPrinters() -> dict\n\n"
4750       "@return: a dict, indexed by name, of dicts representing\n"
4751       "queues, indexed by attribute.\n"
4752       "@raise IPPError: IPP problem" },
4753 
4754     { "getDests",
4755       (PyCFunction) Connection_getDests, METH_NOARGS,
4756       "getDests() -> dict\n\n"
4757       "@return: a dict representing available destinations.  Each \n"
4758       "dictionary key is a pair of (queue, instance) strings, and the \n"
4759       "dictionary value is a L{cups.Dest} object.  In addition to the \n"
4760       "available destinations, a special dictionary key (None,None) is \n"
4761       "provided for looking up the default destination; this destination \n"
4762       "will also be available under its own key.\n"
4763       "@raise IPPError: IPP problem" },
4764 
4765     { "getClasses",
4766       (PyCFunction) Connection_getClasses, METH_NOARGS,
4767       "getClasses() -> dict\n\n"
4768       "@return: a dict, indexed by name, of objects representing\n"
4769       "classes.  Each class object is either a string, in which case it\n"
4770       "is for the remote class; or a list, in which case it is a list of\n"
4771       "queue names.\n"
4772       "@raise IPPError: IPP problem" },
4773 
4774     { "getPPDs",
4775       (PyCFunction) Connection_getPPDs, METH_VARARGS | METH_KEYWORDS,
4776       "getPPDs(limit=0, exclude_schemes=None, include_schemes=None, \n"
4777       "ppd_natural_language=None, ppd_device_id=None, ppd_make=None, \n"
4778       "ppd_make_and_model=None, ppd_model_number=-1, ppd_product=None, \n"
4779       "ppd_psversion=None, ppd_type=None) -> dict\n\n"
4780       "@type limit: integer\n"
4781       "@param limit: maximum number of PPDs to return\n"
4782       "@type exclude_schemes: string list\n"
4783       "@param exclude_schemes: list of PPD schemes to exclude\n"
4784       "@type include_schemes: string list\n"
4785       "@param include_schemes: list of PPD schemes to include\n"
4786       "@type ppd_natural_language: string\n"
4787       "@param ppd_natural_language: required language\n"
4788       "@type ppd_device_id: string\n"
4789       "@param ppd_device_id: IEEE 1284 Device ID to match against\n"
4790       "@type ppd_make: string\n"
4791       "@param ppd_make: required printer manufacturer\n"
4792       "@type ppd_make_and_model: string\n"
4793       "@param ppd_make_and_model: required make and model\n"
4794       "@type ppd_model_number: integer\n"
4795       "@param ppd_model_number: model number required (from cupsModelNumber \n"
4796       "in PPD file)\n"
4797       "@type ppd_product: string\n"
4798       "@param ppd_product: required PostScript product string (Product)\n"
4799       "@type ppd_psversion: string\n"
4800       "@param ppd_psversion: required PostScript version (PSVersion)\n"
4801       "@type ppd_type: string\n"
4802       "@param ppd_type: required type of PPD. Valid values are fax; pdf; \n"
4803       "postscript; raster; unknown.\n"
4804       "@return: a dict, indexed by PPD name, of dicts representing\n"
4805       "PPDs, indexed by attribute.\n"
4806       "@raise IPPError: IPP problem" },
4807 
4808     { "getPPDs2",
4809       (PyCFunction) Connection_getPPDs2, METH_VARARGS | METH_KEYWORDS,
4810       "getPPDs2(limit=0, exclude_schemes=None, include_schemes=None, \n"
4811       "ppd_natural_language=None, ppd_device_id=None, ppd_make=None, \n"
4812       "ppd_make_and_model=None, ppd_model_number=-1, ppd_product=None, \n"
4813       "ppd_psversion=None, ppd_type=None) -> dict\n\n"
4814       "@type limit: integer\n"
4815       "@param limit: maximum number of PPDs to return\n"
4816       "@type exclude_schemes: string list\n"
4817       "@param exclude_schemes: list of PPD schemes to exclude\n"
4818       "@type include_schemes: string list\n"
4819       "@param include_schemes: list of PPD schemes to include\n"
4820       "@type ppd_natural_language: string\n"
4821       "@param ppd_natural_language: required language\n"
4822       "@type ppd_device_id: string\n"
4823       "@param ppd_device_id: IEEE 1284 Device ID to match against\n"
4824       "@type ppd_make: string\n"
4825       "@param ppd_make: required printer manufacturer\n"
4826       "@type ppd_make_and_model: string\n"
4827       "@param ppd_make_and_model: required make and model\n"
4828       "@type ppd_model_number: integer\n"
4829       "@param ppd_model_number: model number required (from cupsModelNumber \n"
4830       "in PPD file)\n"
4831       "@type ppd_product: string\n"
4832       "@param ppd_product: required PostScript product string (Product)\n"
4833       "@type ppd_psversion: string\n"
4834       "@param ppd_psversion: required PostScript version (PSVersion)\n"
4835       "@type ppd_type: string\n"
4836       "@param ppd_type: required type of PPD. Valid values are fax; pdf; \n"
4837       "postscript; raster; unknown.\n"
4838       "@return: a dict, indexed by PPD name, of dicts representing\n"
4839       "PPDs, indexed by attribute.  All attribute values are lists.\n"
4840       "@raise IPPError: IPP problem" },
4841 
4842     { "getServerPPD",
4843       (PyCFunction) Connection_getServerPPD, METH_VARARGS,
4844       "getServerPPD(ppd_name) -> string\n\n"
4845       "Fetches the named PPD and stores it in a temporary file.\n\n"
4846       "@type ppd_name: string\n"
4847       "@param ppd_name: the ppd-name of a PPD\n"
4848       "@return: temporary filename holding the PPD\n"
4849       "@raise RuntimeError: Not supported in libcups until 1.3\n"
4850       "@raise IPPError: IPP problem" },
4851 
4852     { "getDocument",
4853       (PyCFunction) Connection_getDocument, METH_VARARGS,
4854       "getDocument(printer_uri, job_id, document_number) -> dict\n\n"
4855       "Fetches the job document and stores it in a temporary file.\n\n"
4856       "@type printer_uri: string\n"
4857       "@param printer_uri: the printer-uri for the printer\n"
4858       "@type job_id: integer\n"
4859       "@param job_id: the job ID\n"
4860       "@type document_number: integer\n"
4861       "@param document_number: the document number to retrieve\n"
4862       "@return: a dict with the following keys: "
4863       " 'file' (string), temporary filename holding the job document; "
4864       " 'document-format' (string), its MIME type.  There may also be a "
4865       " 'document-name' key, in which case this is for the document name.\n"
4866       "@raise RuntimeError: Not supported in libcups until 1.4\n"
4867       "@raise IPPError: IPP problem" },
4868 
4869     { "getDevices",
4870       (PyCFunction) Connection_getDevices, METH_VARARGS | METH_KEYWORDS,
4871       "getDevices(limit=0, exclude_schemes=None, include_schemes=None) -> dict\n\n"
4872       "@type limit: integer\n"
4873       "@param limit: maximum number of devices to return\n"
4874       "@type exclude_schemes: string list\n"
4875       "@param exclude_schemes: URI schemes to exclude\n"
4876       "@type include_schemes: string list\n"
4877       "@param include_schemes: URI schemes to include\n"
4878       "@return: a dict, indexed by device URI, of dicts representing\n"
4879       "devices, indexed by attribute.\n"
4880       "@raise IPPError: IPP problem" },
4881 
4882     { "getJobs",
4883       (PyCFunction) Connection_getJobs, METH_VARARGS | METH_KEYWORDS,
4884       "getJobs(which_jobs='not-completed', my_jobs=False, limit=-1, first_job_id=-1, requested_attributes=None) -> dict\n"
4885       "Fetch a list of jobs.\n"
4886       "@type which_jobs: string\n"
4887       "@param which_jobs: which jobs to fetch; possible values: \n"
4888       "'completed', 'not-completed', 'all'\n"
4889       "@type my_jobs: boolean\n"
4890       "@param my_jobs: whether to restrict the returned jobs to those \n"
4891       "owned by the current CUPS user (as set by L{cups.setUser}).\n"
4892       "@return: a dict, indexed by job ID, of dicts representing job\n"
4893       "attributes.\n"
4894       "@type limit: integer\n"
4895       "@param limit: maximum number of jobs to return\n"
4896       "@type first_job_id: integer\n"
4897       "@param first_job_id: lowest job ID to return\n"
4898       "@type requested_attributes: string list\n"
4899       "@param requested_attributes: list of requested attribute names\n"
4900       "@raise IPPError: IPP problem" },
4901 
4902     { "getJobAttributes",
4903       (PyCFunction) Connection_getJobAttributes, METH_VARARGS | METH_KEYWORDS,
4904       "getJobAttributes(jobid, requested_attributes=None) -> dict\n\n"
4905       "Fetch job attributes.\n"
4906       "@type jobid: integer\n"
4907       "@param jobid: job ID\n"
4908       "@type requested_attributes: string list\n"
4909       "@param requested_attributes: list of requested attribute names\n"
4910       "@return: a dict representing job attributes.\n"
4911       "@raise IPPError: IPP problem" },
4912 
4913     { "cancelJob",
4914       (PyCFunction) Connection_cancelJob, METH_VARARGS | METH_KEYWORDS,
4915       "cancelJob(jobid, purge_job=False) -> None\n\n"
4916       "@type jobid: integer\n"
4917       "@param jobid: job ID to cancel\n"
4918       "@type purge_job: boolean\n"
4919       "@param purge_job: whether to remove data and control files\n"
4920       "@raise IPPError: IPP problem" },
4921 
4922     { "cancelAllJobs",
4923       (PyCFunction) Connection_cancelAllJobs, METH_VARARGS | METH_KEYWORDS,
4924       "cancelAllJobs(name=None, uri=None, my_jobs=False, purge_jobs=True) -> None\n\n"
4925       "@type name: string\n"
4926       "@param name: queue name\n"
4927       "@type uri: string\n"
4928       "@param uri: printer URI\n"
4929       "@type my_jobs: boolean\n"
4930       "@param my_jobs: whether to restrict operation to jobs owned by \n"
4931       "the current CUPS user (as set by L{cups.setUser}).\n"
4932       "@type purge_jobs: boolean\n"
4933       "@param purge_jobs: whether to remove data and control files\n"
4934       "@raise IPPError: IPP problem" },
4935 
4936     { "moveJob",
4937       (PyCFunction) Connection_moveJob, METH_VARARGS | METH_KEYWORDS,
4938       "moveJob(printer_uri=None, job_id=-1, job_printer_uri) -> None\n\n"
4939       "Move a job specified by printer_uri and jobid (only one need be given)\n"
4940       "to the printer specified by job_printer_uri.\n\n"
4941       "@type job_id: integer\n"
4942       "@param job_id: job ID to move\n"
4943       "@type printer_uri: string\n"
4944       "@param printer_uri: printer to move job(s) from\n"
4945       "@type job_printer_uri: string\n"
4946       "@param job_printer_uri: printer to move job(s) to\n"
4947       "@raise IPPError: IPP problem" },
4948 
4949     { "authenticateJob",
4950       (PyCFunction) Connection_authenticateJob, METH_VARARGS,
4951       "authenticateJob(jobid, auth_info=None) -> None\n\n"
4952       "@type jobid: integer\n"
4953       "@param jobid: job ID to authenticate\n"
4954       "@type auth_info: optional string list\n"
4955       "@param auth_info: authentication details\n"
4956       "@raise IPPError: IPP problem" },
4957 
4958     { "setJobHoldUntil",
4959       (PyCFunction) Connection_setJobHoldUntil, METH_VARARGS,
4960       "setJobHoldUntil(jobid, job_hold_until) -> None\n\n"
4961       "Specifies when a job should be printed.\n"
4962       "@type jobid: integer\n"
4963       "@param jobid: job ID to adjust\n"
4964       "@type job_hold_until: string\n"
4965       "@param job_hold_until: when to print the job; examples: 'hold', \n"
4966       "'immediate', 'restart', resume'\n"
4967       "@raise IPPError: IPP problem"},
4968 
4969     { "restartJob",
4970       (PyCFunction) Connection_restartJob, METH_VARARGS | METH_KEYWORDS,
4971       "restartJob(job_id, job_hold_until=None) -> None\n\n"
4972       "Restart a job.\n\n"
4973       "@type job_id: integer\n"
4974       "@param job_id: job ID to restart\n"
4975       "@type job_hold_until: string\n"
4976       "@param job_hold_until: new job-hold-until value for job\n"
4977       "@raise IPPError: IPP problem" },
4978 
4979     { "getFile",
4980       (PyCFunction) Connection_getFile, METH_VARARGS | METH_KEYWORDS,
4981       "getFile(resource, filename=None, fd=-1, file=None) -> None\n\n"
4982       "Fetch a CUPS server resource to a local file.\n\n"
4983       "This is for obtaining CUPS server configuration files and \n"
4984       "log files.\n\n"
4985       "@type resource: string\n"
4986       "@param resource: resource name\n"
4987       "@type filename: string\n"
4988       "@param filename: name of local file for storage\n"
4989       "@type fd: int\n"
4990       "@param fd: file descriptor of local file\n"
4991       "@type file: file\n"
4992       "@param file: Python file object for local file\n"
4993       "@raise HTTPError: HTTP problem" },
4994 
4995     { "putFile",
4996       (PyCFunction) Connection_putFile, METH_VARARGS | METH_KEYWORDS,
4997       "putFile(resource, filename=None, fd=-1, file=None) -> None\n\n"
4998       "This is for uploading new configuration files for the CUPS \n"
4999       "server.  Note: L{adminSetServerSettings} is a way of \n"
5000       "adjusting server settings without needing to parse the \n"
5001       "configuration file.\n"
5002       "@type resource: string\n"
5003       "@param resource: resource name\n"
5004       "@type filename: string\n"
5005       "@param filename: name of local file to upload\n"
5006       "@type fd: int\n"
5007       "@param fd: file descriptor of local file\n"
5008       "@type file: file\n"
5009       "@param file: Python file object for local file\n"
5010       "@raise HTTPError: HTTP problem"},
5011 
5012     { "addPrinter",
5013       (PyCFunction) Connection_addPrinter, METH_VARARGS | METH_KEYWORDS,
5014       "addPrinter(name) -> None\n\n"
5015       "Add or adjust a print queue.  Several parameters can select which\n"
5016       "PPD to use (filename, ppdname, and ppd) but only one may be\n"
5017       "given.\n\n"
5018       "@type filename: string\n"
5019       "@keyword filename: local filename of PPD file\n"
5020       "@type ppdname: string\n"
5021       "@keyword ppdname: filename from L{getPPDs}\n"
5022       "@type info: string\n"
5023       "@keyword info: human-readable information about the printer\n"
5024       "@type location: string\n"
5025       "@keyword location: human-readable printer location\n"
5026       "@type device: string\n"
5027       "@keyword device: device URI string\n"
5028       "@type ppd: L{cups.PPD} instance\n"
5029       "@keyword ppd: PPD object\n"
5030       "@raise IPPError: IPP problem" },
5031 
5032     { "setPrinterDevice",
5033       (PyCFunction) Connection_setPrinterDevice, METH_VARARGS,
5034       "setPrinterDevice(name, device_uri) -> None\n\n"
5035       "Set the device URI for a printer.\n\n"
5036       "@type name: string\n"
5037       "@param name: queue name\n"
5038       "@type device_uri: string\n"
5039       "@param device_uri: device URI\n"
5040       "@raise IPPError: IPP problem" },
5041 
5042     { "setPrinterInfo",
5043       (PyCFunction) Connection_setPrinterInfo, METH_VARARGS,
5044       "setPrinterInfo(name, info) -> None\n\n"
5045       "Set the human-readable information about a printer.\n\n"
5046       "@type name: string\n"
5047       "@param name: queue name\n"
5048       "@type info: string\n"
5049       "@param info: human-readable information about the printer\n"
5050       "@raise IPPError: IPP problem" },
5051 
5052     { "setPrinterLocation",
5053       (PyCFunction) Connection_setPrinterLocation, METH_VARARGS,
5054       "setPrinterLocation(name, location) -> None\n\n"
5055       "Set the human-readable printer location\n\n"
5056       "@type name: string\n"
5057       "@param name: queue name\n"
5058       "@type location: string\n"
5059       "@param location: human-readable printer location\n"
5060       "@raise IPPError: IPP problem" },
5061 
5062     { "setPrinterShared",
5063       (PyCFunction) Connection_setPrinterShared, METH_VARARGS,
5064       "setPrinterShared(name, shared) -> None\n\n"
5065       "Set whether a printer is shared with other people.  This works \n"
5066       "with CUPS servers of at least version 1.2, by setting the \n"
5067       "printer-is-shared printer attribute.\n\n"
5068       "@type name: string\n"
5069       "@param name: queue name\n"
5070       "@type shared: boolean\n"
5071       "@param shared: whether printer should be shared\n"
5072       "@raise IPPError: IPP problem" },
5073 
5074     { "setPrinterJobSheets",
5075       (PyCFunction) Connection_setPrinterJobSheets, METH_VARARGS,
5076       "setPrinterJobSheets(name, start, end) -> None\n\n"
5077       "Specifies job sheets for a printer.\n\n"
5078       "@type name: string\n"
5079       "@param name: queue name\n"
5080       "@type start: string\n"
5081       "@param start: name of a sheet to print before each job\n"
5082       "@type end: string\n"
5083       "@param end: name of a sheet to print after each job\n"
5084       "@raise IPPError: IPP problem" },
5085 
5086     { "setPrinterErrorPolicy",
5087       (PyCFunction) Connection_setPrinterErrorPolicy, METH_VARARGS,
5088       "setPrinterErrorPolicy(name, policy) -> None\n\n"
5089       "Set the printer's error policy.\n\n"
5090       "@type name: string\n"
5091       "@param name: queue name\n"
5092       "@type policy: string\n"
5093       "@param policy: policy name; supported policy names can be found \n"
5094       "by using the L{getPrinterAttributes} function and looking for the \n"
5095       "'printer-error-policy-supported' attribute\n"
5096       "@raise IPPError: IPP problem" },
5097 
5098     { "setPrinterOpPolicy",
5099       (PyCFunction) Connection_setPrinterOpPolicy, METH_VARARGS,
5100       "setPrinterOpPolicy(name, policy) -> None\n\n"
5101       "Set the printer's operation policy.\n\n"
5102       "@type name: string\n"
5103       "@param name: queue name\n"
5104       "@type policy: string\n"
5105       "@param policy: policy name; supported policy names can be found \n"
5106       "by using the L{getPrinterAttributes} function and looking for the \n"
5107       "'printer-op-policy-supported' attribute\n"
5108       "@raise IPPError: IPP problem" },
5109 
5110     { "setPrinterUsersAllowed",
5111       (PyCFunction) Connection_setPrinterUsersAllowed, METH_VARARGS,
5112       "setPrinterUsersAllowed(name, allowed) -> None\n\n"
5113       "Set the list of users allowed to use a printer.  This works \n"
5114       "with CUPS server of at least version 1.2, by setting the \n"
5115       "requesting-user-name-allowed printer attribute.\n\n"
5116       "@type name: string\n"
5117       "@param name: queue name\n"
5118       "@type allowed: string list\n"
5119       "@param allowed: list of allowed users; ['all'] \n"
5120       "means there will be no user-name restriction.\n"
5121       "@raise IPPError: IPP problem" },
5122 
5123     { "setPrinterUsersDenied",
5124       (PyCFunction) Connection_setPrinterUsersDenied, METH_VARARGS,
5125       "setPrinterUsersDenied(name, denied) -> None\n\n"
5126       "Set the list of users denied the use of a printer.  This works \n"
5127       "with CUPS servers of at least version 1.2, by setting the \n"
5128       "requesting-user-name-denied printer attribute.\n\n"
5129       "@type name: string\n"
5130       "@param name: queue name\n"
5131       "@type denied: string list\n"
5132       "@param denied: list of denied users; ['none'] \n"
5133       "means there will be no user-name restriction.\n"
5134       "@raise IPPError: IPP problem" },
5135 
5136     { "addPrinterOptionDefault",
5137       (PyCFunction) Connection_addPrinterOptionDefault, METH_VARARGS,
5138       "addPrinterOptionDefault(name, option, value) -> None\n\n"
5139       "Set a network default option.  Jobs submitted to the named queue \n"
5140       "will have the job option added if it is not already present in the \n"
5141       "job.  This works with CUPS servers of at least version 1.2.\n\n"
5142       "@type name: string\n"
5143       "@param name: queue name\n"
5144       "@type option: string\n"
5145       "@param option: option name, for example 'job-priority'\n"
5146       "@type value: string\n"
5147       "@param value: option value as a string\n"
5148       "@raise IPPError: IPP problem" },
5149 
5150     { "deletePrinterOptionDefault",
5151       (PyCFunction) Connection_deletePrinterOptionDefault, METH_VARARGS,
5152       "deletePrinterOptionDefault(name, option) -> None\n\n"
5153       "Removes a network default option.  See L{addPrinterOptionDefault}.\n\n"
5154       "@type name: string\n"
5155       "@param name: queue name\n"
5156       "@type option: string\n"
5157       "@param option: option name, for example 'job-priority'\n"
5158       "@raise IPPError: IPP problem" },
5159 
5160     { "deletePrinter",
5161       (PyCFunction) Connection_deletePrinter, METH_VARARGS | METH_KEYWORDS,
5162       "deletePrinter(name) -> None\n\n"
5163       "Delete a printer.\n\n"
5164       "@type name: string\n"
5165       "@param name: queue name\n"
5166       "@raise IPPError: IPP problem" },
5167 
5168     { "getPrinterAttributes",
5169       (PyCFunction) Connection_getPrinterAttributes,
5170       METH_VARARGS | METH_KEYWORDS,
5171       "getPrinterAttributes(name=None, uri=None, requested_attributes=None) -> dict\n"
5172       "Fetch the attributes for a printer, specified either by name or by \n"
5173       "uri but not both.\n\n"
5174       "@type name: string\n"
5175       "@param name: queue name\n"
5176       "@type uri: string\n"
5177       "@param uri: queue URI\n"
5178       "@type requested_attributes: string list\n"
5179       "@param requested_attributes: list of requested attribute names\n"
5180       "@return: a dict, indexed by attribute, of printer attributes\n"
5181       "for the specified printer.\n\n"
5182       "Attributes:\n"
5183       "  - 'job-sheets-supported': list of strings\n"
5184       "  - 'job-sheets-default': tuple of strings (start, end)\n"
5185       "  - 'printer-error-policy-supported': if present, list of strings\n"
5186       "  - 'printer-error-policy': if present, string\n"
5187       "  - 'printer-op-policy-supported': if present, list of strings\n"
5188       "  - 'printer-op-policy': if present, string\n\n"
5189       "There are other attributes; the exact list of attributes returned \n"
5190       "will depend on the IPP server.\n"
5191       "@raise IPPError: IPP problem"},
5192 
5193     { "addPrinterToClass",
5194       (PyCFunction) Connection_addPrinterToClass, METH_VARARGS,
5195       "addPrinterToClass(name, class) -> None\n\n"
5196       "Add a printer to a class.  If the class does not yet exist, \n"
5197       "it is created.\n\n"
5198       "@type name: string\n"
5199       "@param name: queue name\n"
5200       "@type class: string\n"
5201       "@param class: class name\n"
5202       "@raise IPPError: IPP problem" },
5203 
5204     { "deletePrinterFromClass",
5205       (PyCFunction) Connection_deletePrinterFromClass, METH_VARARGS,
5206       "deletePrinterFromClass(name, class) -> None\n\n"
5207       "Remove a printer from a class.  If the class would be left empty, \n"
5208       "it is removed.\n\n"
5209       "@type name: string\n"
5210       "@param name: queue name\n"
5211       "@type class: string\n"
5212       "@param class: class name\n"
5213       "@raise IPPError: IPP problem" },
5214 
5215     { "deleteClass",
5216       (PyCFunction) Connection_deleteClass, METH_VARARGS,
5217       "deleteClass(class) -> None\n\n"
5218       "Delete a class.\n\n"
5219       "@type class: string\n"
5220       "@param class: class name\n"
5221       "@raise IPPError: IPP problem" },
5222 
5223     { "getDefault",
5224       (PyCFunction) Connection_getDefault, METH_NOARGS,
5225       "getDefault() -> string or None\n\n"
5226       "Get the system default printer.\n\n"
5227       "@return: default printer name or None" },
5228 
5229     { "setDefault",
5230       (PyCFunction) Connection_setDefault, METH_VARARGS | METH_KEYWORDS,
5231       "setDefault(name) -> None\n\n"
5232       "Set the system default printer.  Note that this can be over-ridden \n"
5233       "on a per-user basis using the lpoptions command.\n\n"
5234       "@type name: string\n"
5235       "@param name: queue name\n"
5236       "@raise IPPError: IPP problem" },
5237 
5238     { "getPPD",
5239       (PyCFunction) Connection_getPPD, METH_VARARGS,
5240       "getPPD(name) -> string\n\n"
5241       "Fetch a printer's PPD.\n\n"
5242       "@type name: string\n"
5243       "@param name: queue name\n"
5244       "@return: temporary PPD file name\n"
5245       "@raise IPPError: IPP problem" },
5246 
5247     { "getPPD3",
5248       (PyCFunction) Connection_getPPD3, METH_VARARGS | METH_KEYWORDS,
5249       "getPPD3(name[, modtime, filename]) -> (status,modtime,filename)\n\n"
5250       "Fetch a printer's PPD if it is newer.\n\n"
5251       "@type name: string\n"
5252       "@param name: queue name\n"
5253       "@type modtime: float\n"
5254       "@param modtime: modification time of existing file\n"
5255       "@type filename: string\n"
5256       "@param filename: filename of existing file\n"
5257       "@return: tuple of HTTP status, modification time, and filename\n" },
5258 
5259     { "enablePrinter",
5260       (PyCFunction) Connection_enablePrinter, METH_VARARGS | METH_KEYWORDS,
5261       "enablePrinter(name) -> None\n\n"
5262       "Enable printer.  This allows the printer to process its job queue.\n\n"
5263       "@type name: string\n"
5264       "@param name: queue name\n"
5265       "@raise IPPError: IPP problem" },
5266 
5267     { "disablePrinter",
5268       (PyCFunction) Connection_disablePrinter, METH_VARARGS | METH_KEYWORDS,
5269       "disablePrinter(name) -> None\n\n"
5270       "Disable printer.  This prevents the printer from processing its \n"
5271       "job queue.\n\n"
5272       "@type name: string\n"
5273       "@param name: queue name\n"
5274       "@type reason: string\n"
5275       "@keyword reason: optional human-readable reason for disabling the \n"
5276       "printer\n"
5277       "@raise IPPError: IPP problem" },
5278 
5279     { "acceptJobs",
5280       (PyCFunction) Connection_acceptJobs, METH_VARARGS | METH_KEYWORDS,
5281       "acceptJobs(name) -> None\n\n"
5282       "Cause printer to accept jobs.\n"
5283       "@type name: string\n"
5284       "@param name: queue name\n"
5285       "@raise IPPError: IPP problem" },
5286 
5287     { "rejectJobs",
5288       (PyCFunction) Connection_rejectJobs, METH_VARARGS | METH_KEYWORDS,
5289       "rejectJobs(name)\n\n"
5290       "Cause printer to reject jobs.\n\n"
5291       "@type name: string\n"
5292       "@param name: queue name\n"
5293       "@type reason: string\n"
5294       "@keyword reason: optional human-readable reason for rejecting jobs\n"
5295       "@raise IPPError: IPP problem" },
5296 
5297     { "printTestPage",
5298       (PyCFunction) Connection_printTestPage, METH_VARARGS | METH_KEYWORDS,
5299       "printTestPage(name) -> job ID\n\n"
5300       "Print a test page.\n\n"
5301       "@type name: string\n"
5302       "@param name: queue name\n"
5303       "@type file: string\n"
5304       "@keyword file: input file (default is CUPS test page)\n"
5305       "@type title: string\n"
5306       "@keyword title: job title (default 'Test Page')\n"
5307       "@type format: string\n"
5308       "@keyword format: document format (default 'application/postscript')\n"
5309       "@type user: string\n"
5310       "@keyword user: user to submit the job as\n"
5311       "@raise IPPError: IPP problem" },
5312 
5313     { "adminExportSamba",
5314       (PyCFunction) Connection_adminExportSamba, METH_VARARGS,
5315       "adminExportSamba(name, samba_server, samba_username,\n"
5316       "                 samba_password) -> None\n\n"
5317       "Export a printer to Samba.\n\n"
5318       "@type name: string\n"
5319       "@param name: queue name\n"
5320       "@type samba_server: string\n"
5321       "@param samba_server: samba server\n"
5322       "@type samba_username: string\n"
5323       "@param samba_username: samba username\n"
5324       "@type samba_password: string\n"
5325       "@param samba_password: samba password\n"
5326       "@raise IPPError: IPP problem" },
5327 
5328     { "adminGetServerSettings",
5329       (PyCFunction) Connection_adminGetServerSettings, METH_NOARGS,
5330       "adminGetServerSettings() -> dict\n\n"
5331       "Get server settings.\n\n"
5332       "@return: dict representing server settings; keywords include \n"
5333       "L{CUPS_SERVER_DEBUG_LOGGING}, L{CUPS_SERVER_REMOTE_ADMIN}, \n"
5334       "L{CUPS_SERVER_REMOTE_PRINTERS}, L{CUPS_SERVER_SHARE_PRINTERS}, \n"
5335       "L{CUPS_SERVER_USER_CANCEL_ANY}\n"
5336       "@see: L{adminSetServerSettings}\n"
5337       "@raise IPPError: IPP problem" },
5338 
5339     { "adminSetServerSettings",
5340       (PyCFunction) Connection_adminSetServerSettings, METH_VARARGS,
5341       "adminSetServerSettings(settings) -> None\n\n"
5342       "Set server settings.\n\n"
5343       "@type settings: dict\n"
5344       "@param settings: dict of server settings\n"
5345       "@see: L{adminGetServerSettings}\n"
5346       "@raise IPPError: IPP problem" },
5347 
5348     { "getSubscriptions",
5349       (PyCFunction) Connection_getSubscriptions, METH_VARARGS | METH_KEYWORDS,
5350       "getSubscriptions(uri) -> integer list\n\n"
5351       "Get subscriptions.\n\n"
5352       "@type uri: string\n"
5353       "@param uri: URI for object\n"
5354       "@type my_subscriptions: boolean\n"
5355       "@keyword my_subscriptions: only return subscriptions belonging to \n"
5356       "the current user (default False)\n"
5357       "@type job_id: integer\n"
5358       "@keyword job_id: only return subscriptions relating to this job\n"
5359       "@return: list of subscriptions\n"
5360       "@raise IPPError: IPP problem" },
5361 
5362     { "createSubscription",
5363       (PyCFunction) Connection_createSubscription,
5364       METH_VARARGS | METH_KEYWORDS,
5365       "createSubscription(uri, events=[], job_id=-1, recipient_uri="",\n"
5366       "                   lease_duration=-1, time_interval=-1,\n"
5367       "                   user_data="") -> integer\n\n"
5368       "Create a subscription.\n\n"
5369       "@type uri: string\n"
5370       "@param uri: URI for object\n"
5371       "@type events: string list\n"
5372       "@keyword events: events to receive notifications for\n"
5373       "@type job_id: integer\n"
5374       "@keyword job_id: job ID to receive notifications for\n"
5375       "@type recipient_uri: string\n"
5376       "@keyword recipient_uri: URI for notifications recipient\n"
5377       "@type lease_duration: integer\n"
5378       "@keyword lease_duration: lease duration in seconds\n"
5379       "@type time_interval: integer\n"
5380       "@keyword time_interval: time interval\n"
5381       "@type user_data: string\n"
5382       "@keyword user_data: user data to receive with notifications\n"
5383       "@return: subscription ID\n"
5384       "@raise IPPError: IPP problem" },
5385 
5386     { "getNotifications",
5387       (PyCFunction) Connection_getNotifications, METH_VARARGS | METH_KEYWORDS,
5388       "getNotifications(subscription_ids) -> list\n\n"
5389       "Get notifications for subscribed events.\n\n"
5390       "@type subscription_ids: integer list\n"
5391       "@param subscription_ids: list of subscription IDs to receive \n"
5392       "notifications for\n"
5393       "@return: list of dicts, each representing an event\n"
5394       "@raise IPPError: IPP problem" },
5395 
5396     { "cancelSubscription",
5397       (PyCFunction) Connection_cancelSubscription, METH_VARARGS,
5398       "cancelSubscription(id) -> None\n\n"
5399       "Cancel a subscription.\n\n"
5400       "@type id: integer\n"
5401       "@param id: subscription ID\n"
5402       "@raise IPPError: IPP problem" },
5403 
5404     { "renewSubscription",
5405       (PyCFunction) Connection_renewSubscription, METH_VARARGS | METH_KEYWORDS,
5406       "renewSubscription(id, lease_duration=-1) -> None\n\n"
5407       "Renew a subscription.\n\n"
5408       "@type id: integer\n"
5409       "@param id: subscription ID\n"
5410       "@type lease_duration: integer\n"
5411       "@param lease_duration: lease duration in seconds\n"
5412       "@raise IPPError: IPP problem" },
5413 
5414     { "createJob",
5415       (PyCFunction) Connection_createJob, METH_VARARGS | METH_KEYWORDS,
5416       "createJob(printer, title, options) -> integer\n\n"
5417       "Create an empty job for streaming.\n\n"
5418       "@type printer: string\n"
5419       "@param printer: queue name\n"
5420       "@type title: string\n"
5421       "@param title: title of the print job\n"
5422       "@type options: dict\n"
5423       "@param options: dict of options\n"
5424       "@return: job ID\n"
5425       "@raise IPPError: IPP problem" },
5426 
5427     { "startDocument",
5428       (PyCFunction) Connection_startDocument, METH_VARARGS | METH_KEYWORDS,
5429       "startDocument(printer, job_id, doc_name, format, last_document) -> integer\n\n"
5430       "Add a document to a job created with createJob.\n\n"
5431       "@type printer: string\n"
5432       "@param printer: queue name\n"
5433       "@type job_id: integer\n"
5434       "@param job_id: job ID to create document\n"
5435       "@type doc_name: string\n"
5436       "@param doc_name: name of the document\n"
5437       "@type format: string\n"
5438       "@param format: MIME type\n"
5439       "@type last_document: integer\n"
5440       "@param last_document: 1 for last document of job, 0 otherwise\n"
5441       "@return: HTTP status\n"
5442       "@raise IPPError: IPP problem" },
5443 
5444     { "writeRequestData",
5445       (PyCFunction) Connection_writeRequestData, METH_VARARGS | METH_KEYWORDS,
5446       "writeRequestData(buffer, length) -> integer\n\n"
5447       "Write data after an IPP request.\n\n"
5448       "@type buffer: string\n"
5449       "@param buffer: bytes to write\n"
5450       "@type length: integer\n"
5451       "@param length: number of bytes to write\n"
5452       "@return: HTTP status\n"
5453       "@raise IPPError: IPP problem" },
5454 
5455     { "finishDocument",
5456       (PyCFunction) Connection_finishDocument, METH_VARARGS | METH_KEYWORDS,
5457       "finishDocument(printer) -> integer\n\n"
5458       "Finish sending a document.\n\n"
5459       "@type printer: string\n"
5460       "@param printer: queue name\n"
5461       "@return: HTTP status\n"
5462       "@raise IPPError: IPP problem" },
5463 
5464     { "printFile",
5465       (PyCFunction) Connection_printFile, METH_VARARGS | METH_KEYWORDS,
5466       "printFile(printer, filename, title, options) -> integer\n\n"
5467       "Print a file.\n\n"
5468       "@type printer: string\n"
5469       "@param printer: queue name\n"
5470       "@type filename: string\n"
5471       "@param filename: local file path to the document\n"
5472       "@type title: string\n"
5473       "@param title: title of the print job\n"
5474       "@type options: dict\n"
5475       "@param options: dict of options\n"
5476       "@return: job ID\n"
5477       "@raise IPPError: IPP problem" },
5478 
5479     { "printFiles",
5480       (PyCFunction) Connection_printFiles, METH_VARARGS | METH_KEYWORDS,
5481       "printFiles(printer, filenames, title, options) -> integer\n\n"
5482       "Print a list of files.\n\n"
5483       "@type printer: string\n"
5484       "@param printer: queue name\n"
5485       "@type filenames: list\n"
5486       "@param filenames: list of local file paths to the documents\n"
5487       "@type title: string\n"
5488       "@param title: title of the print job\n"
5489       "@type options: dict\n"
5490       "@param options: dict of options\n"
5491       "@return: job ID\n"
5492       "@raise IPPError: IPP problem" },
5493 
5494     { NULL } /* Sentinel */
5495   };
5496 
5497 PyTypeObject cups_ConnectionType =
5498   {
5499     PyVarObject_HEAD_INIT(NULL, 0)
5500     "cups.Connection",         /*tp_name*/
5501     sizeof(Connection),        /*tp_basicsize*/
5502     0,                         /*tp_itemsize*/
5503     (destructor)Connection_dealloc, /*tp_dealloc*/
5504     0,                         /*tp_print*/
5505     0,                         /*tp_getattr*/
5506     0,                         /*tp_setattr*/
5507     0,                         /*tp_compare*/
5508     (reprfunc)Connection_repr, /*tp_repr*/
5509     0,                         /*tp_as_number*/
5510     0,                         /*tp_as_sequence*/
5511     0,                         /*tp_as_mapping*/
5512     0,                         /*tp_hash */
5513     0,                         /*tp_call*/
5514     0,                         /*tp_str*/
5515     0,                         /*tp_getattro*/
5516     0,                         /*tp_setattro*/
5517     0,                         /*tp_as_buffer*/
5518     Py_TPFLAGS_DEFAULT |
5519     Py_TPFLAGS_BASETYPE,       /*tp_flags*/
5520     "CUPS connection\n"
5521     "===============\n\n"
5522 
5523     "  A connection to the CUPS server.  Before it is created the \n"
5524     "  connection server and username should be set using \n"
5525     "  L{cups.setServer} and L{cups.setUser}; otherwise the defaults will \n"
5526     "  be used.  When a Connection object is instantiated it results in a \n"
5527     "  call to the libcups function httpConnectEncrypt().\n\n"
5528     "  The constructor takes optional arguments host, port, and encryption, \n"
5529     "  which default to the values of L{cups.getServer}(), \n"
5530     "  L{cups.getPort}(), and L{cups.getEncryption}().\n"
5531     "",         /* tp_doc */
5532     0,                         /* tp_traverse */
5533     0,                         /* tp_clear */
5534     0,                         /* tp_richcompare */
5535     0,                         /* tp_weaklistoffset */
5536     0,                         /* tp_iter */
5537     0,                         /* tp_iternext */
5538     Connection_methods,        /* tp_methods */
5539     0,                         /* tp_members */
5540     0,                         /* tp_getset */
5541     0,                         /* tp_base */
5542     0,                         /* tp_dict */
5543     0,                         /* tp_descr_get */
5544     0,                         /* tp_descr_set */
5545     0,                         /* tp_dictoffset */
5546     (initproc)Connection_init, /* tp_init */
5547     0,                         /* tp_alloc */
5548     Connection_new,            /* tp_new */
5549   };
5550 
5551 //////////
5552 // Dest //
5553 //////////
5554 
5555 static PyObject *
Dest_new(PyTypeObject * type,PyObject * args,PyObject * kwds)5556 Dest_new (PyTypeObject *type, PyObject *args, PyObject *kwds)
5557 {
5558   Dest *self;
5559   self = (Dest *) type->tp_alloc (type, 0);
5560   return (PyObject *) self;
5561 }
5562 
5563 static int
Dest_init(Dest * self,PyObject * args,PyObject * kwds)5564 Dest_init (Dest *self, PyObject *args, PyObject *kwds)
5565 {
5566   self->num_options = 0;
5567   return 0;
5568 }
5569 
5570 static void
Dest_dealloc(Dest * self)5571 Dest_dealloc (Dest *self)
5572 {
5573   if (self->num_options) {
5574     int i;
5575     for (i = 0; i < self->num_options; i++) {
5576       free (self->name[i]);
5577       free (self->value[i]);
5578     }
5579 
5580     free (self->name);
5581     free (self->value);
5582     self->num_options = 0;
5583 
5584     free (self->destname);
5585     free (self->instance);
5586   }
5587   ((PyObject *)self)->ob_type->tp_free ((PyObject *) self);
5588 }
5589 
5590 static PyObject *
Dest_repr(Dest * self)5591 Dest_repr (Dest *self)
5592 {
5593   char buffer[256];
5594   snprintf (buffer, 256, "<cups.Dest %s%s%s%s>",
5595 			  self->destname,
5596 			  self->instance ? "/" : "",
5597 			  self->instance ? self->instance : "",
5598 			  self->is_default ? " (default)" : "");
5599   return PyUnicode_FromString (buffer);
5600 }
5601 
5602 //////////
5603 // Dest // ATTRIBUTES
5604 //////////
5605 
5606 static PyObject *
Dest_getName(Dest * self,void * closure)5607 Dest_getName (Dest *self, void *closure)
5608 {
5609   return PyUnicode_FromString (self->destname);
5610 }
5611 
5612 static PyObject *
Dest_getInstance(Dest * self,void * closure)5613 Dest_getInstance (Dest *self, void *closure)
5614 {
5615   if (self->instance)
5616     return PyUnicode_FromString (self->instance);
5617 
5618   Py_RETURN_NONE;
5619 }
5620 
5621 static PyObject *
Dest_getIsDefault(Dest * self,void * closure)5622 Dest_getIsDefault (Dest *self, void *closure)
5623 {
5624   return PyBool_FromLong (self->is_default);
5625 }
5626 
5627 static PyObject *
Dest_getOptions(Dest * self,void * closure)5628 Dest_getOptions (Dest *self, void *closure)
5629 {
5630   PyObject *pyoptions = PyDict_New ();
5631   int i;
5632   for (i = 0; i < self->num_options; i++) {
5633     PyObject *string = PyUnicode_FromString (self->value[i]);
5634     PyDict_SetItemString (pyoptions, self->name[i], string);
5635     Py_DECREF (string);
5636   }
5637 
5638   return pyoptions;
5639 }
5640 
5641 PyGetSetDef Dest_getseters[] =
5642   {
5643     { "name",
5644       (getter) Dest_getName, (setter) NULL,
5645       "name", NULL },
5646 
5647     { "instance",
5648       (getter) Dest_getInstance, (setter) NULL,
5649       "instance", NULL },
5650 
5651     { "is_default",
5652       (getter) Dest_getIsDefault, (setter) NULL,
5653       "is_default", NULL },
5654 
5655     { "options",
5656       (getter) Dest_getOptions, (setter) NULL,
5657       "options", NULL },
5658 
5659     { NULL }
5660   };
5661 
5662 PyTypeObject cups_DestType =
5663   {
5664     PyVarObject_HEAD_INIT(NULL, 0)
5665     "cups.Dest",               /*tp_name*/
5666     sizeof(Dest),              /*tp_basicsize*/
5667     0,                         /*tp_itemsize*/
5668     (destructor)Dest_dealloc,  /*tp_dealloc*/
5669     0,                         /*tp_print*/
5670     0,                         /*tp_getattr*/
5671     0,                         /*tp_setattr*/
5672     0,                         /*tp_compare*/
5673     (reprfunc)Dest_repr,       /*tp_repr*/
5674     0,                         /*tp_as_number*/
5675     0,                         /*tp_as_sequence*/
5676     0,                         /*tp_as_mapping*/
5677     0,                         /*tp_hash */
5678     0,                         /*tp_call*/
5679     0,                         /*tp_str*/
5680     0,                         /*tp_getattro*/
5681     0,                         /*tp_setattro*/
5682     0,                         /*tp_as_buffer*/
5683     Py_TPFLAGS_DEFAULT,        /*tp_flags*/
5684     "CUPS destination\n"
5685     "================\n\n"
5686     "  A destination print queue, as returned by L{Connection.getDests}.\n\n"
5687     "@type name: string\n"
5688     "@ivar name: destination queue name\n"
5689     "@type instance: string\n"
5690     "@ivar instance: destination instance name\n"
5691     "@type is_default: boolean\n"
5692     "@ivar is_default: whether this is the default destination\n"
5693     "@type options: dict\n"
5694     "@ivar options: string:string dict of default options for this \n"
5695     "destination, indexed by option name\n"
5696     "",        /* tp_doc */
5697     0,                         /* tp_traverse */
5698     0,                         /* tp_clear */
5699     0,                         /* tp_richcompare */
5700     0,                         /* tp_weaklistoffset */
5701     0,                         /* tp_iter */
5702     0,                         /* tp_iternext */
5703     0,                         /* tp_methods */
5704     0,                         /* tp_members */
5705     Dest_getseters,            /* tp_getset */
5706     0,                         /* tp_base */
5707     0,                         /* tp_dict */
5708     0,                         /* tp_descr_get */
5709     0,                         /* tp_descr_set */
5710     0,                         /* tp_dictoffset */
5711     (initproc)Dest_init,       /* tp_init */
5712     0,                         /* tp_alloc */
5713     Dest_new,                  /* tp_new */
5714   };
5715