source: server/lib/ipprequest.py @ 89fe6da

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

Add 'length' keyword to IPPRequest constructor

  • Property mode set to 100644
File size: 24.2 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, length=sys.maxint):
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            length -= 2
472            logger.debug("version-number : (0x%X, 0x%X)" % self.version)
473
474            # read the operation-id (or status-code, but that's only
475            # for a response) (signed short)
476            self.operation_id   = struct.unpack('>h', request.read(2))[0]
477            length -= 2
478            logger.debug("operation-id : 0x%X" % self.operation_id)
479
480            # read the request-id (signed int)
481            self.request_id     = struct.unpack('>i', request.read(4))[0]
482            length -= 4
483            logger.debug("request-id : 0x%X" % self.request_id)
484
485            # now we have to read in the attributes.  Each attribute
486            # has a tag (1 byte) and a sequence of values (n bytes)
487            self.attribute_groups = []
488
489            # read in the next byte
490            next_byte = struct.unpack('>b', request.read(1))[0]
491            length -=1
492            logger.debug("next byte : 0x%X" % next_byte)
493
494            # as long as the next byte isn't signaling the end of the
495            # attributes, keep looping and parsing attributes
496            while next_byte != IPPTags.END_OF_ATTRIBUTES_TAG:
497               
498                attribute_group_tag = next_byte
499                logger.debug("attribute-tag : %i" % attribute_group_tag)
500
501                attributes = []
502
503                next_byte = struct.unpack('>b', request.read(1))[0]
504                length -= 1
505                logger.debug("next byte : 0x%X" % next_byte)
506
507                while next_byte > 0x0F:
508                   
509                    # read in the value tag (signed char)
510                    value_tag     = next_byte
511                    logger.debug("value-tag : 0x%X" % value_tag)
512                   
513                    # read in the length of the name (signed short)
514                    name_length   = struct.unpack('>h', request.read(2))[0]
515                    length -= 2
516                    logger.debug("name-length : %i" % name_length)
517                   
518                    if name_length != IPPTags.ZERO_NAME_LENGTH:
519                        # read the name (a string of name_length bytes)
520                        name          = request.read(name_length)
521                        length -= name_length
522                        logger.debug("name : %s" % name)
523                   
524                        # read in the length of the value (signed short)
525                        value_length  = struct.unpack('>h', request.read(2))[0]
526                        length -= 2
527                        logger.debug("value-length : %i" % value_length)
528                   
529                        # read in the value (string of value_length bytes)
530                        value         = request.read(value_length)
531                        length -= value_length
532                       
533                        ippvalue = IPPValue(value_tag, value)
534                        logger.debug("value : %s" % ippvalue.value)
535
536                        # create a new IPPAttribute from the data we just
537                        # read in, and add it to our attributes list
538                        attributes.append(IPPAttribute(name, [ippvalue]))
539
540                    else:
541                        # read in the length of the value (signed short)
542                        value_length  = struct.unpack('>h', request.read(2))[0]
543                        length -= 2
544                        logger.debug("value-length : %i" % value_length)
545                   
546                        # read in the value (string of value_length bytes)
547                        value         = request.read(value_length)
548                        length -= value_length
549
550                        ippvalue = IPPValue(value_tag, value)
551                        logger.debug("value : %s" % ippvalue.value)
552
553                        # add another value to the last attribute
554                        attributes[-1].values.append(ippvalue)
555
556                    # read another byte
557                    next_byte = struct.unpack('>b', request.read(1))[0]
558                    length -= 1
559
560                self.attribute_groups.append(IPPAttributeGroup(attribute_group_tag, attributes))
561
562            # once we hit the end-of-attributes tag, the only thing
563            # left is the data, so go ahead and read all of it
564            assert length >= 0
565            self.data = request.read(length)
566            logger.debug("data : %s" % self.data)
567
568        # otherwise, just set the class variables to the keyword
569        # arguments passed in
570        else:
571            self.version = (version[0], version[1])
572            self.operation_id = operation_id
573            self.request_id = request_id
574            self.attribute_groups = attribute_groups
575            self.data = data
576
577    def toBinaryData(self):
578        """
579        Packs the value data into binary data.
580        """
581
582        # convert the version, operation id, and request id to binary
583        preattributes = struct.pack('>bbhi',
584                                    self.version[0],
585                                    self.version[1],
586                                    self.operation_id,
587                                    self.request_id)
588
589        # convert the attribute groups to binary
590        attribute_groups = ''.join([a.toBinaryData() for a in self.attribute_groups])
591
592        # conver the end-of-attributes-tag to binary
593        end_of_attributes_tag = struct.pack('>b', IPPTags.END_OF_ATTRIBUTES_TAG)
594
595        # convert the data to binary
596        if self.data is not None:
597            data = ''.join([struct.pack('>b', x) for x in self.data])
598        else:
599            data = ''
600
601        # append everything together and return it
602        return preattributes + attribute_groups + end_of_attributes_tag + data
Note: See TracBrowser for help on using the repository browser.