source: server/lib/ipprequest.py @ c5e88d0

no-cups
Last change on this file since c5e88d0 was c5e88d0, checked in by Jessica B. Hamrick <jhamrick@…>, 13 years ago

get-jobs response in GutenbachIPPserver

  • Property mode set to 100644
File size: 24.5 KB
Line 
1#!/usr/bin/python
2
3import sys, struct, logging
4
5# initialize logger
6logger = logging.getLogger("ippLogger")
7
8class IPPTags():
9    """
10    Contains constants for the various IPP tags, as defined by RFC
11    2565.
12    """
13   
14    # various tags
15    ZERO_NAME_LENGTH                  = 0x00
16    OPERATION_ATTRIBUTES_TAG          = 0x01
17    JOB_ATTRIBUTES_TAG                = 0x02
18    END_OF_ATTRIBUTES_TAG             = 0x03
19    PRINTER_ATTRIBUTES_TAG            = 0x04
20    UNSUPPORTED_ATTRIBUTES_TAG        = 0x05
21   
22    # "out of band" value tags
23    UNSUPPORTED                       = 0x10
24    DEFAULT                           = 0x11
25    UNKNOWN                           = 0x12
26    NO_VALUE                          = 0x13
27   
28    # integer value tags
29    GENERIC_INTEGER                   = 0x20
30    INTEGER                           = 0x21
31    BOOLEAN                           = 0x22
32    ENUM                              = 0x23
33
34    # octetstring value tags
35    UNSPECIFIED_OCTETSTRING           = 0x30
36    DATETIME                          = 0x31
37    RESOLUTION                        = 0x32
38    RANGE_OF_INTEGER                  = 0x33
39    TEXT_WITH_LANGUAGE                = 0x35
40    NAME_WITH_LANGUAGE                = 0x36
41
42    # character-string value tags
43    GENERIC_CHAR_STRING               = 0x40
44    TEXT_WITHOUT_LANGUAGE             = 0x41
45    NAME_WITHOUT_LANGUAGE             = 0x42
46    KEYWORD                           = 0x44
47    URI                               = 0x45
48    URI_SCHEME                        = 0x46
49    CHARSET                           = 0x47
50    NATURAL_LANGUAGE                  = 0x48
51    MIME_MEDIA_TYPE                   = 0x49                                   
52
53class IPPValue():
54    """
55    An IPP value consists of a tag and a value, and optionally, a name.
56
57    From RFC 2565:
58     -----------------------------------------------
59     |                   value-tag                 |   1 byte
60     -----------------------------------------------
61     |               name-length  (value is u)     |   2 bytes
62     -----------------------------------------------
63     |                     name                    |   u bytes
64     -----------------------------------------------
65     |              value-length  (value is v)     |   2 bytes
66     -----------------------------------------------
67     |                     value                   |   v bytes
68     -----------------------------------------------   
69    """
70
71    def __init__(self, value_tag, value, unpack=True):
72        """
73        Initialize an IPPValue:
74
75        Arguments:
76
77            value_tag -- one byte, identifying the type of value
78
79            value -- variable size, containing the actual value
80        """
81
82        # make sure value_tag isn't empty
83        assert value_tag is not None
84        # make sure value isn't empty
85        assert value is not None
86
87        self.value_tag = value_tag
88        self.value = value
89
90        if not unpack: return
91
92        # out-of-band value tags
93        if self.value_tag == IPPTags.UNSUPPORTED or \
94               self.value_tag == IPPTags.DEFAULT or \
95               self.value_tag == IPPTags.UNKNOWN or \
96               self.value_tag == IPPTags.NO_VALUE:
97            self.value = ''
98
99        # integer value tags
100        elif self.value_tag == IPPTags.GENERIC_INTEGER:
101            pass # not supported
102        elif self.value_tag == IPPTags.INTEGER:
103            self.value = struct.unpack('>i', value)[0]
104        elif self.value_tag == IPPTags.BOOLEAN:
105            self.value = struct.unpack('>?', value)[0]
106        elif self.value_tag == IPPTags.ENUM:
107            self.value = struct.unpack('>i', value)[0]
108
109        # octet string value tags
110        elif self.value_tag == IPPTags.UNSPECIFIED_OCTETSTRING:
111            pass
112       
113        elif self.value_tag == IPPTags.DATETIME:
114            # field  octets  contents                  range
115            # -----  ------  --------                  -----
116            #   1      1-2   year                      0..65536
117            #   2       3    month                     1..12
118            #   3       4    day                       1..31
119            #   4       5    hour                      0..23
120            #   5       6    minutes                   0..59
121            #   6       7    seconds                   0..60
122            #                (use 60 for leap-second)
123            #   7       8    deci-seconds              0..9
124            #   8       9    direction from UTC        '+' / '-'
125            #   9      10    hours from UTC            0..11
126            #  10      11    minutes from UTC          0..59
127
128            self.value = struct.unpack('>hbbbbbbcbb', value)[0]
129           
130        elif self.value_tag == IPPTags.RESOLUTION:
131            # OCTET-STRING consisting of nine octets of 2
132            # SIGNED-INTEGERs followed by a SIGNED-BYTE. The first
133            # SIGNED-INTEGER contains the value of cross feed
134            # direction resolution. The second SIGNED-INTEGER contains
135            # the value of feed direction resolution. The SIGNED-BYTE
136            # contains the units
137
138            self.value = struct.unpack('>iib', value)
139           
140        elif self.value_tag == IPPTags.RANGE_OF_INTEGER:
141            # Eight octets consisting of 2 SIGNED-INTEGERs.  The first
142            # SIGNED-INTEGER contains the lower bound and the second
143            # SIGNED-INTEGER contains the upper bound.
144
145            self.value = struct.unpack('>ii', value)
146
147        elif self.value_tag == IPPTags.TEXT_WITH_LANGUAGE or \
148                 self.value_tag == IPPTags.NAME_WITH_LANGUAGE:
149            a = struct.unpack('>h', value[:2])[0]
150            b = struct.unpack('>%ss' % a, value[2:a+2])[0]
151            c = struct.unpack('>h', value[a+2:a+4])[0]
152            d = struct.unpack('>%ss' % c, value[a+4:][0])
153            self.value = (a, b, c, d)
154
155        # character string value tags
156        elif self.value_tag == IPPTags.TEXT_WITHOUT_LANGUAGE or \
157                 self.value_tag == IPPTags.NAME_WITHOUT_LANGUAGE:
158            self.value = str(value)
159        elif self.value_tag == IPPTags.GENERIC_CHAR_STRING or \
160                 self.value_tag == IPPTags.KEYWORD or \
161                 self.value_tag == IPPTags.URI or \
162                 self.value_tag == IPPTags.URI_SCHEME or \
163                 self.value_tag == IPPTags.CHARSET or \
164                 self.value_tag == IPPTags.NATURAL_LANGUAGE or \
165                 self.value_tag == IPPTags.MIME_MEDIA_TYPE:
166            self.value = str(value)
167
168    def valueToBinary(self):
169
170        # out-of-band value tags
171        if self.value_tag == IPPTags.UNSUPPORTED or \
172               self.value_tag == IPPTags.DEFAULT or \
173               self.value_tag == IPPTags.UNKNOWN or \
174               self.value_tag == IPPTags.NO_VALUE:
175            return (0, '')
176
177        # integer value tags
178        elif self.value_tag == IPPTags.GENERIC_INTEGER:
179            pass
180        elif self.value_tag == IPPTags.INTEGER:
181            return (4, struct.pack('>i', self.value))
182        elif self.value_tag == IPPTags.BOOLEAN:
183            return (1, struct.pack('>?', self.value))
184        elif self.value_tag == IPPTags.ENUM:
185            return (4, struct.pack('>i', self.value))
186
187        # octet string value tags
188        elif self.value_tag == IPPTags.UNSPECIFIED_OCTETSTRING:
189            pass
190        elif self.value_tag == IPPTags.DATETIME:
191            # field  octets  contents                  range
192            # -----  ------  --------                  -----
193            #   1      1-2   year                      0..65536
194            #   2       3    month                     1..12
195            #   3       4    day                       1..31
196            #   4       5    hour                      0..23
197            #   5       6    minutes                   0..59
198            #   6       7    seconds                   0..60
199            #                (use 60 for leap-second)
200            #   7       8    deci-seconds              0..9
201            #   8       9    direction from UTC        '+' / '-'
202            #   9      10    hours from UTC            0..11
203            #  10      11    minutes from UTC          0..59
204           
205            return (11, struct.pack('>hbbbbbbcbb', self.value))
206           
207        elif self.value_tag == IPPTags.RESOLUTION:
208            # OCTET-STRING consisting of nine octets of 2
209            # SIGNED-INTEGERs followed by a SIGNED-BYTE. The first
210            # SIGNED-INTEGER contains the value of cross feed
211            # direction resolution. The second SIGNED-INTEGER contains
212            # the value of feed direction resolution. The SIGNED-BYTE
213            # contains the units
214           
215            return (9, struct.pack('>iib', self.value))
216           
217        elif self.value_tag == IPPTags.RANGE_OF_INTEGER:
218            # Eight octets consisting of 2 SIGNED-INTEGERs.  The first
219            # SIGNED-INTEGER contains the lower bound and the second
220            # SIGNED-INTEGER contains the upper bound.
221           
222            return (8, struct.pack('>ii', self.value))
223
224        elif self.value_tag == IPPTags.TEXT_WITH_LANGUAGE or \
225                 self.value_tag == IPPTags.NAME_WITH_LANGUAGE:
226            a_bin = struct.pack('>h', self.value[0])
227            b_bin = struct.pack('>%ss' % self.value[0], self.value[1])
228            c_bin = struct.pack('>h', self.value[2])
229            d_bin = struct.pack('>%ss' % self.value[2], self.value[3])
230            return (4 + self.value[0] + self.value[2],
231                    a_bin + b_bin + c_bin + d_bin)
232
233        # character string value tags
234        elif self.value_tag == IPPTags.TEXT_WITHOUT_LANGUAGE or \
235                 self.value_tag == IPPTags.NAME_WITHOUT_LANGUAGE:
236            return (len(self.value), struct.pack('>%ss' % len(self.value), self.value))
237        elif self.value_tag == IPPTags.GENERIC_CHAR_STRING or \
238                 self.value_tag == IPPTags.KEYWORD or \
239                 self.value_tag == IPPTags.URI or \
240                 self.value_tag == IPPTags.URI_SCHEME or \
241                 self.value_tag == IPPTags.CHARSET or \
242                 self.value_tag == IPPTags.NATURAL_LANGUAGE or \
243                 self.value_tag == IPPTags.MIME_MEDIA_TYPE:
244            return (len(self.value), struct.pack('>%ss' % len(self.value), self.value))
245
246        return len(self.value), self.value
247
248class IPPAttribute():
249    """
250    In addition to what the RFC reports, an attribute has an
251    'attribute tag', which specifies what type of attribute it is.
252    It is 1 bytes long, and comes before the list of values.
253
254    From RFC 2565:
255
256    Each attribute consists of:
257    -----------------------------------------------
258    |                   value-tag                 |   1 byte
259    -----------------------------------------------
260    |               name-length  (value is u)     |   2 bytes
261    -----------------------------------------------
262    |                     name                    |   u bytes
263    -----------------------------------------------
264    |              value-length  (value is v)     |   2 bytes
265    -----------------------------------------------
266    |                     value                   |   v bytes
267    -----------------------------------------------
268
269    An additional value consists of:
270    -----------------------------------------------------------
271    |                   value-tag                 |   1 byte  |
272    -----------------------------------------------           |
273    |            name-length  (value is 0x0000)   |   2 bytes |
274    -----------------------------------------------           |-0 or more
275    |              value-length (value is w)      |   2 bytes |
276    -----------------------------------------------           |
277    |                     value                   |   w bytes |
278    -----------------------------------------------------------
279    """
280
281    def __init__(self, name, values):
282        """
283        Initialize an IPPAttribute.
284       
285        Arguments:
286
287            name -- the name of the attribute
288
289            values -- a list of IPPValues.  May not be empty.
290        """
291
292        # make sure name isn't empty
293        assert name is not None
294         
295        # make sure the list of values isn't empty
296        assert len(values) > 0
297        # make sure each value is an IPPValue
298        for value in values: assert isinstance(value, IPPValue)
299         
300        self.name = name
301        self.values = values
302
303    def toBinaryData(self):
304        """
305        Packs the attribute data into binary data.
306        """
307
308        # get the binary data for all the values
309        values = []
310        for v, i in zip(self.values, xrange(len(self.values))):
311
312            # get the name length (0 for everything but the first
313            # value)
314            if i == 0:
315                name_length = len(self.name)
316            else:
317                name_length = 0
318
319            # get the value length and binary value
320            value_length, value_bin = v.valueToBinary()
321
322            logger.debug("dumping name_length : %i" % name_length)
323            logger.debug("dumping name : %s" % self.name)
324            logger.debug("dumping value_length : %i" % value_length)
325            logger.debug("dumping value : %s" % v.value)
326
327            # the value tag in binary
328            value_tag_bin = struct.pack('>b', v.value_tag)
329
330            # the name length in binary
331            name_length_bin = struct.pack('>h', name_length)
332
333            # the name in binary
334            name_bin = self.name
335
336            # the value length in binary
337            value_length_bin = struct.pack('>h', value_length)
338
339            if i == 0:
340                values.append(''.join([value_tag_bin,
341                                       name_length_bin,
342                                       name_bin,
343                                       value_length_bin,
344                                       value_bin]))
345            else:
346                values.append(''.join([value_tag_bin,
347                                       name_length_bin,
348                                       value_length_bin,
349                                       value_bin]))
350               
351        # concatenate everything together and return it
352        return ''.join(values)
353
354class IPPAttributeGroup():
355    """
356    An IPPAttributeGroup consists of an attribute-group-tag, followed
357    by a sequence of IPPAttributes.
358    """
359
360    def __init__(self, attribute_group_tag, attributes=[]):
361        """
362        Initialize an IPPAttributeGroup.
363
364        Arguments:
365
366            attribute_group_tag -- a signed char, holds the tag of the
367                                   attribute group
368
369            attributes -- (optional) a list of attributes
370        """
371
372        # make sure attribute_group_tag isn't empty
373        assert attribute_group_tag is not None
374
375        # make sure attributes is a list or tuple of IPPAttributes
376        assert isinstance(attributes, (list, tuple))
377        for a in attributes: assert isinstance(a, IPPAttribute)
378
379        self.attribute_group_tag = attribute_group_tag
380        self.attributes = attributes
381
382    def getAttribute(self, name):
383        return filter(lambda x: x.name == name, self.attributes)
384
385    def toBinaryData(self):
386        """
387        Convert the IPPAttributeGroup to binary.
388        """
389
390        # conver the attribute_group_tag to binary
391        tag = struct.pack('>b', self.attribute_group_tag)
392
393        # convert each of the attributes to binary
394        attributes = [a.toBinaryData() for a in self.attributes]
395
396        # concatenate everything and return
397        return tag + ''.join(attributes)
398
399class IPPRequest():
400    """
401    From RFC 2565:
402   
403    The encoding for an operation request or response consists of:
404    -----------------------------------------------
405    |                  version-number             |   2 bytes  - required
406    -----------------------------------------------
407    |               operation-id (request)        |
408    |                      or                     |   2 bytes  - required
409    |               status-code (response)        |
410    -----------------------------------------------
411    |                   request-id                |   4 bytes  - required
412    -----------------------------------------------------------
413    |               xxx-attributes-tag            |   1 byte  |
414    -----------------------------------------------           |-0 or more
415    |             xxx-attribute-sequence          |   n bytes |
416    -----------------------------------------------------------
417    |              end-of-attributes-tag          |   1 byte   - required
418    -----------------------------------------------
419    |                     data                    |   q bytes  - optional
420    -----------------------------------------------
421    """
422
423    # either give the version, operation_id, request_id,
424    # attribute_sequence, and data, or a file handler (request) which
425    # can be read from to get the request
426    def __init__(self, version=None, operation_id=None, request_id=None,
427                 attribute_groups=[], data=None, request=None, length=sys.maxint):
428        """
429        Create an IPPRequest.  Takes either the segments of the
430        request separately, or a file handle for the request to parse.
431        If the file handle is passed in, all other arguments are
432        ignored.
433
434        Keyword arguments for passing in the segments of the request:
435       
436            version -- a tuple of two signed chars, identifying the
437                       major version and minor version numbers of the
438                       request
439                           
440            operation_id -- a signed short, identifying the id of the
441                            requested operation
442
443            request_id -- a signed int, identifying the id of the
444                          request itself.
445
446            attribute_groups -- a list of IPPAttributes, at least length 1
447
448            data -- (optional) variable length, containing the actual
449                    data of the request
450
451        Keyword arguments for passing in the raw request:
452
453            request -- a file handle that supports the read()
454                       operation
455        """
456
457        if request is None:
458            # make sure the version number isn't empty
459            assert version is not None
460            # make sure verison is a tuple of length 2
461            assert isinstance(version, tuple)
462            assert len(version) == 2
463            # make sure the operation id isn't empty
464            assert operation_id is not None
465            # make sure the request id isn't empty
466            assert request_id is not None
467            # make sure attribute_groups is a list of IPPAttributes
468            assert len(attribute_groups) > 0
469            for a in attribute_groups: assert isinstance(a, IPPAttributeGroup)
470           
471        # if the request isn't None, then we'll read directly from
472        # that file handle
473        if request is not None:
474            # read the version-number (two signed chars)
475            self.version        = struct.unpack('>bb', request.read(2))
476            length -= 2
477            logger.debug("version-number : (0x%X, 0x%X)" % self.version)
478
479            # read the operation-id (or status-code, but that's only
480            # for a response) (signed short)
481            self.operation_id   = struct.unpack('>h', request.read(2))[0]
482            length -= 2
483            logger.debug("operation-id : 0x%X" % self.operation_id)
484
485            # read the request-id (signed int)
486            self.request_id     = struct.unpack('>i', request.read(4))[0]
487            length -= 4
488            logger.debug("request-id : 0x%X" % self.request_id)
489
490            # now we have to read in the attributes.  Each attribute
491            # has a tag (1 byte) and a sequence of values (n bytes)
492            self.attribute_groups = []
493
494            # read in the next byte
495            next_byte = struct.unpack('>b', request.read(1))[0]
496            length -=1
497            logger.debug("next byte : 0x%X" % next_byte)
498
499            # as long as the next byte isn't signaling the end of the
500            # attributes, keep looping and parsing attributes
501            while next_byte != IPPTags.END_OF_ATTRIBUTES_TAG:
502               
503                attribute_group_tag = next_byte
504                logger.debug("attribute-tag : %i" % attribute_group_tag)
505
506                attributes = []
507
508                next_byte = struct.unpack('>b', request.read(1))[0]
509                length -= 1
510                logger.debug("next byte : 0x%X" % next_byte)
511
512                while next_byte > 0x0F:
513                   
514                    # read in the value tag (signed char)
515                    value_tag     = next_byte
516                    logger.debug("value-tag : 0x%X" % value_tag)
517                   
518                    # read in the length of the name (signed short)
519                    name_length   = struct.unpack('>h', request.read(2))[0]
520                    length -= 2
521                    logger.debug("name-length : %i" % name_length)
522                   
523                    if name_length != IPPTags.ZERO_NAME_LENGTH:
524                        # read the name (a string of name_length bytes)
525                        name          = request.read(name_length)
526                        length -= name_length
527                        logger.debug("name : %s" % name)
528                   
529                        # read in the length of the value (signed short)
530                        value_length  = struct.unpack('>h', request.read(2))[0]
531                        length -= 2
532                        logger.debug("value-length : %i" % value_length)
533                   
534                        # read in the value (string of value_length bytes)
535                        value         = request.read(value_length)
536                        length -= value_length
537                       
538                        ippvalue = IPPValue(value_tag, value)
539                        logger.debug("value : %s" % ippvalue.value)
540
541                        # create a new IPPAttribute from the data we just
542                        # read in, and add it to our attributes list
543                        attributes.append(IPPAttribute(name, [ippvalue]))
544
545                    else:
546                        # read in the length of the value (signed short)
547                        value_length  = struct.unpack('>h', request.read(2))[0]
548                        length -= 2
549                        logger.debug("value-length : %i" % value_length)
550                   
551                        # read in the value (string of value_length bytes)
552                        value         = request.read(value_length)
553                        length -= value_length
554
555                        ippvalue = IPPValue(value_tag, value)
556                        logger.debug("value : %s" % ippvalue.value)
557
558                        # add another value to the last attribute
559                        attributes[-1].values.append(ippvalue)
560
561                    # read another byte
562                    next_byte = struct.unpack('>b', request.read(1))[0]
563                    length -= 1
564
565                self.attribute_groups.append(IPPAttributeGroup(attribute_group_tag, attributes))
566
567            # once we hit the end-of-attributes tag, the only thing
568            # left is the data, so go ahead and read all of it
569            assert length >= 0
570            self.data = request.read(length)
571            logger.debug("data : %s" % self.data)
572
573        # otherwise, just set the class variables to the keyword
574        # arguments passed in
575        else:
576            self.version = (version[0], version[1])
577            self.operation_id = operation_id
578            self.request_id = request_id
579            self.attribute_groups = attribute_groups
580            self.data = data
581
582    def getAttributeGroup(self, tag):
583        return filter(lambda x: x.attribute_group_tag == tag,
584                      self.attribute_groups)
585
586    def toBinaryData(self):
587        """
588        Packs the value data into binary data.
589        """
590
591        # convert the version, operation id, and request id to binary
592        preattributes = struct.pack('>bbhi',
593                                    self.version[0],
594                                    self.version[1],
595                                    self.operation_id,
596                                    self.request_id)
597
598        # convert the attribute groups to binary
599        attribute_groups = ''.join([a.toBinaryData() for a in self.attribute_groups])
600
601        # conver the end-of-attributes-tag to binary
602        end_of_attributes_tag = struct.pack('>b', IPPTags.END_OF_ATTRIBUTES_TAG)
603
604        # convert the data to binary
605        if self.data is not None:
606            data = ''.join([struct.pack('>b', x) for x in self.data])
607        else:
608            data = ''
609
610        # append everything together and return it
611        return preattributes + attribute_groups + end_of_attributes_tag + data
Note: See TracBrowser for help on using the repository browser.