root / pkipplib / trunk / ipplib / ipplib.py @ 3

Revision 3, 18.9 kB (checked in by jerome, 18 years ago)

First import of the files

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