root / pkipplib / trunk / pkipplib / pkipplib.py @ 23

Revision 23, 24.3 kB (checked in by jerome, 17 years ago)

Now keeps the latest error and error messages seen.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3#
4# pkipplib : IPP and CUPS support for Python
5#
6# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
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# $Id$
22#
23#
24
25import sys
26import urllib2
27import socket
28from struct import pack, unpack
29
30IPP_VERSION = "1.1"     # default version number
31
32IPP_PORT = 631
33
34IPP_MAX_NAME = 256
35IPP_MAX_VALUES = 8
36
37IPP_TAG_ZERO = 0x00
38IPP_TAG_OPERATION = 0x01
39IPP_TAG_JOB = 0x02
40IPP_TAG_END = 0x03
41IPP_TAG_PRINTER = 0x04
42IPP_TAG_UNSUPPORTED_GROUP = 0x05
43IPP_TAG_SUBSCRIPTION = 0x06
44IPP_TAG_EVENT_NOTIFICATION = 0x07
45IPP_TAG_UNSUPPORTED_VALUE = 0x10
46IPP_TAG_DEFAULT = 0x11
47IPP_TAG_UNKNOWN = 0x12
48IPP_TAG_NOVALUE = 0x13
49IPP_TAG_NOTSETTABLE = 0x15
50IPP_TAG_DELETEATTR = 0x16
51IPP_TAG_ADMINDEFINE = 0x17
52IPP_TAG_INTEGER = 0x21
53IPP_TAG_BOOLEAN = 0x22
54IPP_TAG_ENUM = 0x23
55IPP_TAG_STRING = 0x30
56IPP_TAG_DATE = 0x31
57IPP_TAG_RESOLUTION = 0x32
58IPP_TAG_RANGE = 0x33
59IPP_TAG_BEGIN_COLLECTION = 0x34
60IPP_TAG_TEXTLANG = 0x35
61IPP_TAG_NAMELANG = 0x36
62IPP_TAG_END_COLLECTION = 0x37
63IPP_TAG_TEXT = 0x41
64IPP_TAG_NAME = 0x42
65IPP_TAG_KEYWORD = 0x44
66IPP_TAG_URI = 0x45
67IPP_TAG_URISCHEME = 0x46
68IPP_TAG_CHARSET = 0x47
69IPP_TAG_LANGUAGE = 0x48
70IPP_TAG_MIMETYPE = 0x49
71IPP_TAG_MEMBERNAME = 0x4a
72IPP_TAG_MASK = 0x7fffffff
73IPP_TAG_COPY = -0x7fffffff-1
74
75IPP_RES_PER_INCH = 3
76IPP_RES_PER_CM = 4
77
78IPP_FINISHINGS_NONE = 3
79IPP_FINISHINGS_STAPLE = 4
80IPP_FINISHINGS_PUNCH = 5
81IPP_FINISHINGS_COVER = 6
82IPP_FINISHINGS_BIND = 7
83IPP_FINISHINGS_SADDLE_STITCH = 8
84IPP_FINISHINGS_EDGE_STITCH = 9
85IPP_FINISHINGS_FOLD = 10
86IPP_FINISHINGS_TRIM = 11
87IPP_FINISHINGS_BALE = 12
88IPP_FINISHINGS_BOOKLET_MAKER = 13
89IPP_FINISHINGS_JOB_OFFSET = 14
90IPP_FINISHINGS_STAPLE_TOP_LEFT = 20
91IPP_FINISHINGS_STAPLE_BOTTOM_LEFT = 21
92IPP_FINISHINGS_STAPLE_TOP_RIGHT = 22
93IPP_FINISHINGS_STAPLE_BOTTOM_RIGHT = 23
94IPP_FINISHINGS_EDGE_STITCH_LEFT = 24
95IPP_FINISHINGS_EDGE_STITCH_TOP = 25
96IPP_FINISHINGS_EDGE_STITCH_RIGHT = 26
97IPP_FINISHINGS_EDGE_STITCH_BOTTOM = 27
98IPP_FINISHINGS_STAPLE_DUAL_LEFT = 28
99IPP_FINISHINGS_STAPLE_DUAL_TOP = 29
100IPP_FINISHINGS_STAPLE_DUAL_RIGHT = 30
101IPP_FINISHINGS_STAPLE_DUAL_BOTTOM = 31
102IPP_FINISHINGS_BIND_LEFT = 50
103IPP_FINISHINGS_BIND_TOP = 51
104IPP_FINISHINGS_BIND_RIGHT = 52
105IPP_FINISHINGS_BIND_BOTTO = 53
106
107IPP_PORTRAIT = 3
108IPP_LANDSCAPE = 4
109IPP_REVERSE_LANDSCAPE = 5
110IPP_REVERSE_PORTRAIT = 6
111
112IPP_QUALITY_DRAFT = 3
113IPP_QUALITY_NORMAL = 4
114IPP_QUALITY_HIGH = 5
115
116IPP_JOB_PENDING = 3
117IPP_JOB_HELD = 4
118IPP_JOB_PROCESSING = 5
119IPP_JOB_STOPPED = 6
120IPP_JOB_CANCELLED = 7
121IPP_JOB_ABORTED = 8
122IPP_JOB_COMPLETE = 9
123
124IPP_PRINTER_IDLE = 3
125IPP_PRINTER_PROCESSING = 4
126IPP_PRINTER_STOPPED = 5
127
128IPP_ERROR = -1
129IPP_IDLE = 0
130IPP_HEADER = 1
131IPP_ATTRIBUTE = 2
132IPP_DATA = 3
133
134IPP_PRINT_JOB = 0x0002
135IPP_PRINT_URI = 0x0003
136IPP_VALIDATE_JOB = 0x0004
137IPP_CREATE_JOB = 0x0005
138IPP_SEND_DOCUMENT = 0x0006
139IPP_SEND_URI = 0x0007
140IPP_CANCEL_JOB = 0x0008
141IPP_GET_JOB_ATTRIBUTES = 0x0009
142IPP_GET_JOBS = 0x000a
143IPP_GET_PRINTER_ATTRIBUTES = 0x000b
144IPP_HOLD_JOB = 0x000c
145IPP_RELEASE_JOB = 0x000d
146IPP_RESTART_JOB = 0x000e
147IPP_PAUSE_PRINTER = 0x0010
148IPP_RESUME_PRINTER = 0x0011
149IPP_PURGE_JOBS = 0x0012
150IPP_SET_PRINTER_ATTRIBUTES = 0x0013
151IPP_SET_JOB_ATTRIBUTES = 0x0014
152IPP_GET_PRINTER_SUPPORTED_VALUES = 0x0015
153IPP_CREATE_PRINTER_SUBSCRIPTION = 0x0016
154IPP_CREATE_JOB_SUBSCRIPTION = 0x0017
155IPP_GET_SUBSCRIPTION_ATTRIBUTES = 0x0018
156IPP_GET_SUBSCRIPTIONS = 0x0019
157IPP_RENEW_SUBSCRIPTION = 0x001a
158IPP_CANCEL_SUBSCRIPTION = 0x001b
159IPP_GET_NOTIFICATIONS = 0x001c
160IPP_SEND_NOTIFICATIONS = 0x001d
161IPP_GET_PRINT_SUPPORT_FILES = 0x0021
162IPP_ENABLE_PRINTER = 0x0022
163IPP_DISABLE_PRINTER = 0x0023
164IPP_PAUSE_PRINTER_AFTER_CURRENT_JOB = 0x0024
165IPP_HOLD_NEW_JOBS = 0x0025
166IPP_RELEASE_HELD_NEW_JOBS = 0x0026
167IPP_DEACTIVATE_PRINTER = 0x0027
168IPP_ACTIVATE_PRINTER = 0x0028
169IPP_RESTART_PRINTER = 0x0029
170IPP_SHUTDOWN_PRINTER = 0x002a
171IPP_STARTUP_PRINTER = 0x002b
172IPP_REPROCESS_JOB = 0x002c
173IPP_CANCEL_CURRENT_JOB = 0x002d
174IPP_SUSPEND_CURRENT_JOB = 0x002e
175IPP_RESUME_JOB = 0x002f
176IPP_PROMOTE_JOB = 0x0030
177IPP_SCHEDULE_JOB_AFTER = 0x0031
178IPP_PRIVATE = 0x4000
179CUPS_GET_DEFAULT = 0x4001
180CUPS_GET_PRINTERS = 0x4002
181CUPS_ADD_PRINTER = 0x4003
182CUPS_DELETE_PRINTER = 0x4004
183CUPS_GET_CLASSES = 0x4005
184CUPS_ADD_CLASS = 0x4006
185CUPS_DELETE_CLASS = 0x4007
186CUPS_ACCEPT_JOBS = 0x4008
187CUPS_REJECT_JOBS = 0x4009
188CUPS_SET_DEFAULT = 0x400a
189CUPS_GET_DEVICES = 0x400b
190CUPS_GET_PPDS = 0x400c
191CUPS_MOVE_JOB = 0x400d
192CUPS_AUTHENTICATE_JOB = 0x400e
193
194IPP_OK = 0x0000
195IPP_OK_SUBST = 0x0001
196IPP_OK_CONFLICT = 0x0002
197IPP_OK_IGNORED_SUBSCRIPTIONS = 0x0003
198IPP_OK_IGNORED_NOTIFICATIONS = 0x0004
199IPP_OK_TOO_MANY_EVENTS = 0x0005
200IPP_OK_BUT_CANCEL_SUBSCRIPTION = 0x0006
201IPP_REDIRECTION_OTHER_SITE = 0x0300
202IPP_BAD_REQUEST = 0x0400
203IPP_FORBIDDEN = 0x0401
204IPP_NOT_AUTHENTICATED = 0x0402
205IPP_NOT_AUTHORIZED = 0x0403
206IPP_NOT_POSSIBLE = 0x0404
207IPP_TIMEOUT = 0x0405
208IPP_NOT_FOUND = 0x0406
209IPP_GONE = 0x0407
210IPP_REQUEST_ENTITY = 0x0408
211IPP_REQUEST_VALUE = 0x0409
212IPP_DOCUMENT_FORMAT = 0x040a
213IPP_ATTRIBUTES = 0x040b
214IPP_URI_SCHEME = 0x040c
215IPP_CHARSET = 0x040d
216IPP_CONFLICT = 0x040e
217IPP_COMPRESSION_NOT_SUPPORTED = 0x040f
218IPP_COMPRESSION_ERROR = 0x0410
219IPP_DOCUMENT_FORMAT_ERROR = 0x0411
220IPP_DOCUMENT_ACCESS_ERROR = 0x0412
221IPP_ATTRIBUTES_NOT_SETTABLE = 0x0413
222IPP_IGNORED_ALL_SUBSCRIPTIONS = 0x0414
223IPP_TOO_MANY_SUBSCRIPTIONS = 0x0415
224IPP_IGNORED_ALL_NOTIFICATIONS = 0x0416
225IPP_PRINT_SUPPORT_FILE_NOT_FOUND = 0x0417
226
227IPP_INTERNAL_ERROR = 0x0500
228IPP_OPERATION_NOT_SUPPORTED = 0x0501
229IPP_SERVICE_UNAVAILABLE = 0x0502
230IPP_VERSION_NOT_SUPPORTED = 0x0503
231IPP_DEVICE_ERROR = 0x0504
232IPP_TEMPORARY_ERROR = 0x0505
233IPP_NOT_ACCEPTING = 0x0506
234IPP_PRINTER_BUSY = 0x0507
235IPP_ERROR_JOB_CANCELLED = 0x0508
236IPP_MULTIPLE_JOBS_NOT_SUPPORTED = 0x0509
237IPP_PRINTER_IS_DEACTIVATED = 0x50a
238 
239class IPPError(Exception) :
240    """An exception for IPP related stuff."""
241    def __init__(self, message = ""):
242        self.message = message
243        Exception.__init__(self, message)
244    def __repr__(self):
245        return self.message
246    __str__ = __repr__
247
248class FakeAttribute :
249    """Fakes an IPPRequest attribute to simplify usage syntax."""
250    def __init__(self, request, name) :
251        """Initializes the fake attribute."""
252        self.request = request
253        self.name = name
254       
255    def __setitem__(self, key, value) :
256        """Appends the value to the real attribute."""
257        attributeslist = getattr(self.request, "_%s_attributes" % self.name)
258        for i in range(len(attributeslist)) :
259            attribute = attributeslist[i]
260            for j in range(len(attribute)) :
261                (attrname, attrvalue) = attribute[j]
262                if attrname == key :
263                    attribute[j][1].append(value)
264                    return
265            attribute.append((key, [value]))       
266           
267    def __getitem__(self, key) :
268        """Returns an attribute's value."""
269        attributeslist = getattr(self.request, "_%s_attributes" % self.name)
270        for i in range(len(attributeslist)) :
271            attribute = attributeslist[i]
272            for j in range(len(attribute)) :
273                (attrname, attrvalue) = attribute[j]
274                if attrname == key :
275                    return attrvalue
276        raise KeyError, key           
277   
278class IPPRequest :
279    """A class for IPP requests."""
280    attributes_types = ("operation", "job", "printer", "unsupported", \
281                                     "subscription", "event_notification")
282    def __init__(self, data="", version=IPP_VERSION, 
283                                operation_id=None, \
284                                request_id=None, \
285                                url = "http://localhost:631", \
286                                username = None, \
287                                password = None, \
288                                debug=False) :
289        """Initializes an IPP Message object.
290       
291           Parameters :
292           
293             data : the complete IPP Message's content.
294             debug : a boolean value to output debug info on stderr.
295        """
296        if url.endswith("/") :
297            url = url[:-1]
298        self.url = url.replace("ipp://", "http://")
299        self.username = username
300        self.password = password
301        self.debug = debug
302        self._data = data
303        self.parsed = False
304        self.error = None
305       
306        # Initializes message
307        self.setVersion(version)               
308        self.setOperationId(operation_id)
309        self.setRequestId(request_id)
310        self.data = ""
311       
312        for attrtype in self.attributes_types :
313            setattr(self, "_%s_attributes" % attrtype, [[]])
314       
315        # Initialize tags   
316        self.tags = [ None ] * 256 # by default all tags reserved
317       
318        # Delimiter tags
319        self.tags[0x01] = "operation-attributes-tag"
320        self.tags[0x02] = "job-attributes-tag"
321        self.tags[0x03] = "end-of-attributes-tag"
322        self.tags[0x04] = "printer-attributes-tag"
323        self.tags[0x05] = "unsupported-attributes-tag"
324        self.tags[0x06] = "subscription-attributes-tag"
325        self.tags[0x07] = "event-notification-attributes-tag"
326       
327        # out of band values
328        self.tags[0x10] = "unsupported"
329        self.tags[0x11] = "reserved-for-future-default"
330        self.tags[0x12] = "unknown"
331        self.tags[0x13] = "no-value"
332        self.tags[0x15] = "not-settable"
333        self.tags[0x16] = "delete-attribute"
334        self.tags[0x17] = "admin-define"
335 
336        # integer values
337        self.tags[0x20] = "generic-integer"
338        self.tags[0x21] = "integer"
339        self.tags[0x22] = "boolean"
340        self.tags[0x23] = "enum"
341       
342        # octetString
343        self.tags[0x30] = "octetString-with-an-unspecified-format"
344        self.tags[0x31] = "dateTime"
345        self.tags[0x32] = "resolution"
346        self.tags[0x33] = "rangeOfInteger"
347        self.tags[0x34] = "begCollection" # TODO : find sample files for testing
348        self.tags[0x35] = "textWithLanguage"
349        self.tags[0x36] = "nameWithLanguage"
350        self.tags[0x37] = "endCollection"
351       
352        # character strings
353        self.tags[0x40] = "generic-character-string"
354        self.tags[0x41] = "textWithoutLanguage"
355        self.tags[0x42] = "nameWithoutLanguage"
356        self.tags[0x44] = "keyword"
357        self.tags[0x45] = "uri"
358        self.tags[0x46] = "uriScheme"
359        self.tags[0x47] = "charset"
360        self.tags[0x48] = "naturalLanguage"
361        self.tags[0x49] = "mimeMediaType"
362        self.tags[0x4a] = "memberAttrName"
363       
364        # Reverse mapping to generate IPP messages
365        self.tagvalues = {}
366        for i in range(len(self.tags)) :
367            value = self.tags[i]
368            if value is not None :
369                self.tagvalues[value] = i
370                                     
371    def __getattr__(self, name) :                                 
372        """Fakes attribute access."""
373        if name in self.attributes_types :
374            return FakeAttribute(self, name)
375        else :
376            raise AttributeError, name
377           
378    def __str__(self) :       
379        """Returns the parsed IPP message in a readable form."""
380        if not self.parsed :
381            return ""
382        mybuffer = []
383        mybuffer.append("IPP version : %s.%s" % self.version)
384        mybuffer.append("IPP operation Id : 0x%04x" % self.operation_id)
385        mybuffer.append("IPP request Id : 0x%08x" % self.request_id)
386        for attrtype in self.attributes_types :
387            for attribute in getattr(self, "_%s_attributes" % attrtype) :
388                if attribute :
389                    mybuffer.append("%s attributes :" % attrtype.title())
390                for (name, value) in attribute :
391                    mybuffer.append("  %s : %s" % (name, value))
392        if self.data :           
393            mybuffer.append("IPP datas : %s" % repr(self.data))
394        return "\n".join(mybuffer)
395       
396    def logDebug(self, msg) :   
397        """Prints a debug message."""
398        if self.debug :
399            sys.stderr.write("%s\n" % msg)
400            sys.stderr.flush()
401           
402    def setVersion(self, version) :
403        """Sets the request's operation id."""
404        if version is not None :
405            try :
406                self.version = [int(p) for p in version.split(".")]
407            except AttributeError :
408                if len(version) == 2 : # 2-tuple
409                    self.version = version
410                else :   
411                    try :
412                        self.version = [int(p) for p in str(float(version)).split(".")]
413                    except :
414                        self.version = [int(p) for p in IPP_VERSION.split(".")]
415       
416    def setOperationId(self, opid) :       
417        """Sets the request's operation id."""
418        self.operation_id = opid
419       
420    def setRequestId(self, reqid) :       
421        """Sets the request's request id."""
422        self.request_id = reqid
423       
424    def nextRequestId(self) :       
425        """Increments the current request id and returns the new value."""
426        try :
427            self.request_id += 1
428        except TypeError :   
429            self.request_id = 1
430        return self.request_id
431           
432    def dump(self) :   
433        """Generates an IPP Message.
434       
435           Returns the message as a string of text.
436        """   
437        mybuffer = []
438        if None not in (self.version, self.operation_id) :
439            if self.request_id is None :
440                self.nextRequestId()
441            mybuffer.append(chr(self.version[0]) + chr(self.version[1]))
442            mybuffer.append(pack(">H", self.operation_id))
443            mybuffer.append(pack(">I", self.request_id))
444            for attrtype in self.attributes_types :
445                for attribute in getattr(self, "_%s_attributes" % attrtype) :
446                    if attribute :
447                        mybuffer.append(chr(self.tagvalues["%s-attributes-tag" % attrtype]))
448                    for (attrname, value) in attribute :
449                        nameprinted = 0
450                        for (vtype, val) in value :
451                            mybuffer.append(chr(self.tagvalues[vtype]))
452                            if not nameprinted :
453                                mybuffer.append(pack(">H", len(attrname)))
454                                mybuffer.append(attrname)
455                                nameprinted = 1
456                            else :     
457                                mybuffer.append(pack(">H", 0))
458                            if vtype in ("integer", "enum") :
459                                mybuffer.append(pack(">H", 4))
460                                mybuffer.append(pack(">I", val))
461                            elif vtype == "boolean" :
462                                mybuffer.append(pack(">H", 1))
463                                mybuffer.append(chr(val))
464                            else :   
465                                mybuffer.append(pack(">H", len(val)))
466                                mybuffer.append(val)
467            mybuffer.append(chr(self.tagvalues["end-of-attributes-tag"]))
468        mybuffer.append(self.data)   
469        return "".join(mybuffer)
470           
471    def parse(self) :
472        """Parses an IPP Request.
473       
474           NB : Only a subset of RFC2910 is implemented.
475        """
476        self._curname = None
477        self._curattributes = None
478       
479        self.setVersion((ord(self._data[0]), ord(self._data[1])))
480        self.setOperationId(unpack(">H", self._data[2:4])[0])
481        self.setRequestId(unpack(">I", self._data[4:8])[0])
482        self.position = 8
483        endofattributes = self.tagvalues["end-of-attributes-tag"]
484        maxdelimiter = self.tagvalues["event-notification-attributes-tag"]
485        nulloffset = lambda : 0
486        try :
487            tag = ord(self._data[self.position])
488            while tag != endofattributes :
489                self.position += 1
490                name = self.tags[tag]
491                if name is not None :
492                    func = getattr(self, name.replace("-", "_"), nulloffset)
493                    self.position += func()
494                    if ord(self._data[self.position]) > maxdelimiter :
495                        self.position -= 1
496                        continue
497                oldtag = tag       
498                tag = ord(self._data[self.position])
499                if tag == oldtag :
500                    self._curattributes.append([])
501        except IndexError :
502            raise IPPError, "Unexpected end of IPP message."
503           
504        self.data = self._data[self.position+1:]           
505        self.parsed = True
506       
507    def parseTag(self) :   
508        """Extracts information from an IPP tag."""
509        pos = self.position
510        tagtype = self.tags[ord(self._data[pos])]
511        pos += 1
512        posend = pos2 = pos + 2
513        namelength = unpack(">H", self._data[pos:pos2])[0]
514        if not namelength :
515            name = self._curname
516        else :   
517            posend += namelength
518            self._curname = name = self._data[pos2:posend]
519        pos2 = posend + 2
520        valuelength = unpack(">H", self._data[posend:pos2])[0]
521        posend = pos2 + valuelength
522        value = self._data[pos2:posend]
523        if tagtype in ("integer", "enum") :
524            value = unpack(">I", value)[0]
525        elif tagtype == "boolean" :   
526            value = ord(value)
527        try :   
528            (oldname, oldval) = self._curattributes[-1][-1]
529            if oldname == name :
530                oldval.append((tagtype, value))
531            else :   
532                raise IndexError
533        except IndexError :   
534            self._curattributes[-1].append((name, [(tagtype, value)]))
535        self.logDebug("%s(%s) : %s" % (name, tagtype, value))
536        return posend - self.position
537       
538    def operation_attributes_tag(self) : 
539        """Indicates that the parser enters into an operation-attributes-tag group."""
540        self._curattributes = self._operation_attributes
541        return self.parseTag()
542       
543    def job_attributes_tag(self) : 
544        """Indicates that the parser enters into a job-attributes-tag group."""
545        self._curattributes = self._job_attributes
546        return self.parseTag()
547       
548    def printer_attributes_tag(self) : 
549        """Indicates that the parser enters into a printer-attributes-tag group."""
550        self._curattributes = self._printer_attributes
551        return self.parseTag()
552       
553    def unsupported_attributes_tag(self) : 
554        """Indicates that the parser enters into an unsupported-attributes-tag group."""
555        self._curattributes = self._unsupported_attributes
556        return self.parseTag()
557       
558    def subscription_attributes_tag(self) : 
559        """Indicates that the parser enters into a subscription-attributes-tag group."""
560        self._curattributes = self._subscription_attributes
561        return self.parseTag()
562       
563    def event_notification_attributes_tag(self) : 
564        """Indicates that the parser enters into an event-notification-attributes-tag group."""
565        self._curattributes = self._event_notification_attributes
566        return self.parseTag()
567       
568    def doRequest(self, url=None, username=None, password=None, samerequestid=False) :
569        """Sends the current request to the URL.
570           NB : only ipp:// URLs are currently unsupported so use
571           either http://host:631/ or https://host:443 instead...
572           
573           returns a new IPPRequest object, containing the parsed answer.
574        """   
575        if not samerequestid :
576            self.nextRequestId()
577           
578        url = url or self.url or "http://localhost:631"
579        cx = urllib2.Request(url=url, \
580                             data=self.dump())
581        cx.add_header("Content-Type", "application/ipp")
582       
583        username = username or self.username
584        password = password or self.password
585        if username :
586            password = password or ""
587            pwmanager = urllib2.HTTPPasswordMgrWithDefaultRealm()
588            pwmanager.add_password(None, \
589                                   "%s%s" % (cx.get_host(), cx.get_selector()), \
590                                   username, \
591                                   password)
592            authhandler = urllib2.HTTPBasicAuthHandler(pwmanager)                       
593            opener = urllib2.build_opener(authhandler)
594            urllib2.install_opener(opener)
595           
596        try :   
597            response = urllib2.urlopen(cx)
598        except (urllib2.URLError, urllib2.HTTPError, socket.error), error :   
599            self.error = error
600            return None
601        else :   
602            self.error = None
603            datas = response.read()
604            ippresponse = IPPRequest(datas)
605            ippresponse.parse()
606            return ippresponse
607       
608           
609class CUPS :
610    """A class for a CUPS instance."""
611    def __init__(self, url="http://localhost:631", username=None, password=None, charset="utf-8", language="en-us") :
612        """Initializes the CUPS instance."""
613        self.url = url
614        self.username = username
615        self.password = password
616        self.charset = charset
617        self.language = language
618        self.lastError = None
619        self.lastErrorMessage = None
620       
621    def identifierToURI(self, service, ident) :
622        """Transforms an identifier into a particular URI depending on requested service."""
623        return "%s/%s/%s" % (self.url.replace("http://", "ipp://"),
624                             service,
625                             ident)
626       
627    def newRequest(self, operationid=None) :
628        """Generates a new empty request."""
629        if operationid is not None :
630            req = IPPRequest(operation_id=operationid, \
631                             url=self.url, \
632                             username=self.username, \
633                             password=self.password)
634            req.operation["attributes-charset"] = ("charset", self.charset)
635            req.operation["attributes-natural-language"] = ("naturalLanguage", self.language)
636            return req
637   
638    def doRequest(self, req) :
639        """Does a request and saves its error status in the lastErrorMessage attribute."""
640        result = req.doRequest()
641        self.lastError = req.error
642        self.lastErrorMessage = str(req.error)
643        return result
644       
645    def getDefault(self) :
646        """Retrieves CUPS' default printer."""
647        return self.doRequest(self.newRequest(CUPS_GET_DEFAULT))
648   
649    def getJobAttributes(self, jobid) :   
650        """Retrieves a print job's attributes."""
651        req = self.newRequest(IPP_GET_JOB_ATTRIBUTES)
652        req.operation["job-uri"] = ("uri", self.identifierToURI("jobs", jobid))
653        return self.doRequest(req)
654       
655    def getPPD(self, queuename) :   
656        """Retrieves the PPD for a particular queuename."""
657        req = self.newRequest(IPP_GET_PRINTER_ATTRIBUTES)
658        req.operation["printer-uri"] = ("uri", self.identifierToURI("printers", queuename))
659        for attrib in ("printer-uri-supported", "printer-type", "member-uris") :
660            req.operation["requested-attributes"] = ("nameWithoutLanguage", attrib)
661        return self.doRequest(req)  # TODO : get the PPD from the actual print server
662       
663           
664if __name__ == "__main__" :           
665    if (len(sys.argv) < 2) or (sys.argv[1] == "--debug") :
666        print "usage : python pkipplib.py /var/spool/cups/c00005 [--debug] (for example)\n"
667    else :   
668        infile = open(sys.argv[1], "rb")
669        data = infile.read()
670        infile.close()
671       
672        message = IPPRequest(data, debug=(sys.argv[-1]=="--debug"))
673        message.parse()
674        message2 = IPPRequest(message.dump())
675        message2.parse()
676        data2 = message2.dump()
677       
678        if data == data2 :
679            print "Test OK : parsing original and parsing the output of the dump produce the same dump !"
680            print str(message)
681        else :   
682            print "Test Failed !"
683            print str(message)
684            print
685            print str(message2)
686       
Note: See TracBrowser for help on using the browser.