source: server/lib/ipprequest.py @ aaa1da3

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

Add support for parsing different value tags

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