source: server/lib/ipprequest.py @ 8e43aa8

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

Can correctly parse and pack IPP requests\!

  • Property mode set to 100644
File size: 16.4 KB
RevLine 
[c216863]1#!/usr/bin/python
2
[8403f61]3import sys, struct, logging
4
5# initialize logger
6logger = logging.getLogger("ippLogger")
[4ec7caa]7
8class IPPTags():
9    """
10    Contains constants for the various IPP tags, as defined by RFC
11    2565.
12    """
13   
14    # various tags
[d56a0bc]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
[4ec7caa]21   
22    # "out of band" value tags
[d56a0bc]23    UNSUPPORTED                       = 0x10
24    DEFAULT                           = 0x11
25    UNKNOWN                           = 0x12
26    NO_VALUE                          = 0x13
[4ec7caa]27   
28    # integer value tags
[d56a0bc]29    GENERIC_INTEGER                   = 0x20
30    INTEGER                           = 0x21
31    BOOLEAN                           = 0x22
32    ENUM                              = 0x23
[4ec7caa]33
34    # octetstring value tags
[d56a0bc]35    UNSPECIFIED_OCTETSTRING           = 0x30
36    DATETIME                          = 0x31
37    RESOLUTION                        = 0x32
[8403f61]38    RANGE_OF_INTEGER                  = 0x33
[d56a0bc]39    COLLECTION                        = 0x34
[8403f61]40    TEXT_WITH_LANGUAGE                = 0x35
41    NAME_WITH_LANGUAGE                = 0x36
[4ec7caa]42
43    # character-string value tags
[d56a0bc]44    GENERIC_CHAR_STRING               = 0x40
[8403f61]45    TEXT_WITHOUT_LANGUAGE             = 0x41
46    NAME_WITHOUT_LANGUAGE             = 0x42
[d56a0bc]47    KEYWORD                           = 0x44
48    URI                               = 0x45
[8403f61]49    URI_SCHEME                        = 0x46
[d56a0bc]50    CHARSET                           = 0x47
[8403f61]51    NATURAL_LANGUAGE                  = 0x48
52    MIME_MEDIA_TYPE                   = 0x49                                   
[84e8137]53
[c216863]54class IPPValue():
[84e8137]55    """
56    An IPP value consists of a tag and a value, and optionally, a name.
[d56a0bc]57
58    From RFC 2565:
59     -----------------------------------------------
60     |                   value-tag                 |   1 byte
61     -----------------------------------------------
62     |               name-length  (value is u)     |   2 bytes
63     -----------------------------------------------
64     |                     name                    |   u bytes
65     -----------------------------------------------
66     |              value-length  (value is v)     |   2 bytes
67     -----------------------------------------------
68     |                     value                   |   v bytes
69     -----------------------------------------------   
[84e8137]70    """
71
[8403f61]72    def __init__(self, value_tag, value):
[84e8137]73        """
74        Initialize an IPPValue:
75
76        Arguments:
77
78            value_tag -- one byte, identifying the type of value
79
80            value -- variable size, containing the actual value
81        """
82
83        # make sure value_tag isn't empty
84        assert value_tag is not None
85        # make sure value isn't empty
86        assert value is not None
[c216863]87
[8e43aa8]88        self.value_tag = value_tag
89        self.value = value
[c216863]90
[8403f61]91class IPPAttribute():
92    """
93    In addition to what the RFC reports, an attribute has an
94    'attribute tag', which specifies what type of attribute it is.
95    It is 1 bytes long, and comes before the list of values.
[d56a0bc]96
[8403f61]97    From RFC 2565:
[d56a0bc]98
[8403f61]99    Each attribute consists of:
100    -----------------------------------------------
101    |                   value-tag                 |   1 byte
102    -----------------------------------------------
103    |               name-length  (value is u)     |   2 bytes
104    -----------------------------------------------
105    |                     name                    |   u bytes
106    -----------------------------------------------
107    |              value-length  (value is v)     |   2 bytes
108    -----------------------------------------------
109    |                     value                   |   v bytes
110    -----------------------------------------------
[4ec7caa]111
[8403f61]112    An additional value consists of:
113    -----------------------------------------------------------
114    |                   value-tag                 |   1 byte  |
115    -----------------------------------------------           |
116    |            name-length  (value is 0x0000)   |   2 bytes |
117    -----------------------------------------------           |-0 or more
118    |              value-length (value is w)      |   2 bytes |
119    -----------------------------------------------           |
120    |                     value                   |   w bytes |
121    -----------------------------------------------------------
122    """
[84e8137]123
[8403f61]124    def __init__(self, name, values):
125        """
126        Initialize an IPPAttribute.
127       
128        Arguments:
[84e8137]129
[8e43aa8]130            name -- the name of the attribute
[8403f61]131
[8e43aa8]132            values -- a list of IPPValues.  May not be empty.
[8403f61]133        """
134
135        # make sure name isn't empty
136        assert name is not None
[84e8137]137         
[8403f61]138        # make sure the list of values isn't empty
139        assert len(values) > 0
140        # make sure each value is an IPPValue
141        for value in values: assert isinstance(value, IPPValue)
142         
[8e43aa8]143        self.name = name
[8403f61]144        self.values = values
[c216863]145
[d56a0bc]146    def toBinaryData(self):
147        """
148        Packs the attribute data into binary data.
149        """
150
151        # get the binary data for all the values
[8403f61]152        values = []
153        for v, i in zip(self.values, xrange(len(self.values))):
[8e43aa8]154            name_length = len(self.name)
155            if i > 0: name_length = 0
156            value_length = len(v.value)
157
158            logger.debug("dumping name_length : %i" % name_length)
159            logger.debug("dumping name : %s" % self.name)
160            logger.debug("dumping value_length : %i" % value_length)
161            logger.debug("dumping value : %s" % v.value)
162
163            # the value tag in binary
164            value_tag_bin = struct.pack('>b', v.value_tag)
165
166            # the name length in binary
167            name_length_bin = struct.pack('>h', name_length)
168
169            # the name in binary
170            name_bin = self.name
171
172            # the value length in binary
173            value_length_bin = struct.pack('>h', value_length)
174
175            # the value in binary
176            value_bin = v.value
177
[8403f61]178            if i == 0:
[8e43aa8]179                values.append(''.join([value_tag_bin,
180                                       name_length_bin,
181                                       name_bin,
182                                       value_length_bin,
183                                       value_bin]))
[8403f61]184            else:
[8e43aa8]185                values.append(''.join([value_tag_bin,
186                                       name_length_bin,
187                                       value_length_bin,
188                                       value_bin]))
[8403f61]189               
[d56a0bc]190        # concatenate everything together and return it
[8403f61]191        return ''.join(values)
[d56a0bc]192
[8e43aa8]193class IPPAttributeGroup():
194    """
195    An IPPAttributeGroup consists of an attribute-group-tag, followed
196    by a sequence of IPPAttributes.
197    """
198
199    def __init__(self, attribute_group_tag, attributes=[]):
200        """
201        Initialize an IPPAttributeGroup.
202
203        Arguments:
204
205            attribute_group_tag -- a signed char, holds the tag of the
206                                   attribute group
207
208            attributes -- (optional) a list of attributes
209        """
210
211        # make sure attribute_group_tag isn't empty
212        assert attribute_group_tag is not None
213
214        # make sure attributes is a list or tuple of IPPAttributes
215        assert isinstance(attributes, (list, tuple))
216        for a in attributes: assert isinstance(a, IPPAttribute)
217
218        self.attribute_group_tag = attribute_group_tag
219        self.attributes = attributes
220
221    def toBinaryData(self):
222        """
223        Convert the IPPAttributeGroup to binary.
224        """
225
226        # conver the attribute_group_tag to binary
227        tag = struct.pack('>b', self.attribute_group_tag)
228
229        # convert each of the attributes to binary
230        attributes = [a.toBinaryData() for a in self.attributes]
231
232        # concatenate everything and return
233        return tag + ''.join(attributes)
234
[c216863]235class IPPRequest():
[84e8137]236    """
237    From RFC 2565:
[c216863]238   
[84e8137]239    The encoding for an operation request or response consists of:
240    -----------------------------------------------
241    |                  version-number             |   2 bytes  - required
242    -----------------------------------------------
243    |               operation-id (request)        |
244    |                      or                     |   2 bytes  - required
245    |               status-code (response)        |
246    -----------------------------------------------
247    |                   request-id                |   4 bytes  - required
248    -----------------------------------------------------------
249    |               xxx-attributes-tag            |   1 byte  |
250    -----------------------------------------------           |-0 or more
251    |             xxx-attribute-sequence          |   n bytes |
252    -----------------------------------------------------------
253    |              end-of-attributes-tag          |   1 byte   - required
254    -----------------------------------------------
255    |                     data                    |   q bytes  - optional
256    -----------------------------------------------
257    """
[c216863]258
259    # either give the version, operation_id, request_id,
260    # attribute_sequence, and data, or a file handler (request) which
261    # can be read from to get the request
[8e43aa8]262    def __init__(self, version=None, operation_id=None, request_id=None,
263                 attribute_groups=[], data=None, request=None):
[84e8137]264        """
265        Create an IPPRequest.  Takes either the segments of the
266        request separately, or a file handle for the request to parse.
267        If the file handle is passed in, all other arguments are
268        ignored.
269
270        Keyword arguments for passing in the segments of the request:
271       
[4ec7caa]272            version -- a tuple of two signed chars, identifying the
273                       major version and minor version numbers of the
274                       request
[84e8137]275                           
[4ec7caa]276            operation_id -- a signed short, identifying the id of the
[84e8137]277                            requested operation
278
[4ec7caa]279            request_id -- a signed int, identifying the id of the
[84e8137]280                          request itself.
[c216863]281
[8e43aa8]282            attribute_groups -- a list of IPPAttributes, at least length 1
[84e8137]283
284            data -- (optional) variable length, containing the actual
285                    data of the request
286
287        Keyword arguments for passing in the raw request:
288
289            request -- a file handle that supports the read()
290                       operation
291        """
292
293        if request is None:
294            # make sure the version number isn't empty
295            assert version is not None
[4ec7caa]296            # make sure verison is a tuple of length 2
297            assert isinstance(version, tuple)
298            assert len(version) == 2
[84e8137]299            # make sure the operation id isn't empty
300            assert operation_id is not None
301            # make sure the request id isn't empty
302            assert request_id is not None
[8e43aa8]303            # make sure attribute_groups is a list of IPPAttributes
304            assert len(attribute_groups) > 0
305            for a in attribute_groups: assert isinstance(a, IPPAttribute)
[84e8137]306           
307        # if the request isn't None, then we'll read directly from
308        # that file handle
[c216863]309        if request is not None:
[4ec7caa]310            # read the version-number (two signed chars)
[d56a0bc]311            self.version        = struct.unpack('>bb', request.read(2))
[8403f61]312            logger.debug("version-number : (0x%X, 0x%X)" % self.version)
[84e8137]313
314            # read the operation-id (or status-code, but that's only
[4ec7caa]315            # for a response) (signed short)
[8403f61]316            self.operation_id   = struct.unpack('>h', request.read(2))[0]
317            logger.debug("operation-id : 0x%X" % self.operation_id)
[84e8137]318
[4ec7caa]319            # read the request-id (signed int)
[8403f61]320            self.request_id     = struct.unpack('>i', request.read(4))[0]
321            logger.debug("request-id : 0x%X" % self.request_id)
[84e8137]322
323            # now we have to read in the attributes.  Each attribute
324            # has a tag (1 byte) and a sequence of values (n bytes)
[8e43aa8]325            self.attribute_groups = []
[84e8137]326
327            # read in the next byte
[8403f61]328            next_byte = struct.unpack('>b', request.read(1))[0]
329            logger.debug("next byte : 0x%X" % next_byte)
[84e8137]330
331            # as long as the next byte isn't signaling the end of the
332            # attributes, keep looping and parsing attributes
[4ec7caa]333            while next_byte != IPPTags.END_OF_ATTRIBUTES_TAG:
[84e8137]334               
[8403f61]335                attribute_group_tag = next_byte
336                logger.debug("attribute-tag : %i" % attribute_group_tag)
337
[8e43aa8]338                attributes = []
339
[8403f61]340                next_byte = struct.unpack('>b', request.read(1))[0]
341                logger.debug("next byte : 0x%X" % next_byte)
[84e8137]342
[8403f61]343                while next_byte > 0x0F:
344                   
[4ec7caa]345                    # read in the value tag (signed char)
[8403f61]346                    value_tag     = next_byte
347                    logger.debug("value-tag : 0x%X" % value_tag)
348                   
[4ec7caa]349                    # read in the length of the name (signed short)
[8403f61]350                    name_length   = struct.unpack('>h', request.read(2))[0]
351                    logger.debug("name-length : %i" % name_length)
[84e8137]352                   
[8403f61]353                    if name_length != IPPTags.ZERO_NAME_LENGTH:
354                        # read the name (a string of name_length bytes)
355                        name          = request.read(name_length)
356                        logger.debug("name : %s" % name)
357                   
358                        # read in the length of the value (signed short)
359                        value_length  = struct.unpack('>h', request.read(2))[0]
360                        logger.debug("value-length : %i" % value_length)
361                   
362                        # read in the value (string of value_length bytes)
363                        value         = request.read(value_length)
364                        logger.debug("value : %s" % value)
365
366                        # create a new IPPAttribute from the data we just
367                        # read in, and add it to our attributes list
[8e43aa8]368                        attributes.append(IPPAttribute(name,
369                                                       [IPPValue(value_tag, value)]))
[8403f61]370
371                    else:
372                        # read in the length of the value (signed short)
373                        value_length  = struct.unpack('>h', request.read(2))[0]
374                        logger.debug("value-length : %i" % value_length)
375                   
376                        # read in the value (string of value_length bytes)
377                        value         = request.read(value_length)
378                        logger.debug("value : %s" % value)
379
380                        # add another value to the last attribute
[8e43aa8]381                        attributes[-1].values.append(IPPValue(value_tag, value))
[8403f61]382
383                    # read another byte
384                    next_byte = struct.unpack('>b', request.read(1))[0]
[c216863]385
[8e43aa8]386                self.attribute_groups.append(IPPAttributeGroup(attribute_group_tag, attributes))
387
[84e8137]388            # once we hit the end-of-attributes tag, the only thing
389            # left is the data, so go ahead and read all of it
[8403f61]390            self.data = request.read()
391            logger.debug("data : %s" % self.data)
[c216863]392
[84e8137]393        # otherwise, just set the class variables to the keyword
394        # arguments passed in
[c216863]395        else:
[8e43aa8]396            self.version = (version[0], version[1])
397            self.operation_id = operation_id
398            self.request_id = request_id
399            self.attribute_groups = attribute_groups
[c216863]400            self.data = data
[d56a0bc]401
402    def toBinaryData(self):
403        """
404        Packs the value data into binary data.
405        """
406
407        # convert the version, operation id, and request id to binary
408        preattributes = struct.pack('>bbhi',
409                                    self.version[0],
410                                    self.version[1],
411                                    self.operation_id,
412                                    self.request_id)
413
[8e43aa8]414        # convert the attribute groups to binary
415        attribute_groups = ''.join([a.toBinaryData() for a in self.attribute_groups])
[d56a0bc]416
417        # conver the end-of-attributes-tag to binary
418        end_of_attributes_tag = struct.pack('>b', IPPTags.END_OF_ATTRIBUTES_TAG)
419
420        # convert the data to binary
[8403f61]421        if self.data is not None:
422            data = ''.join([struct.pack('>b', x) for x in self.data])
423        else:
424            data = ''
[d56a0bc]425
426        # append everything together and return it
[8e43aa8]427        return preattributes + attribute_groups + end_of_attributes_tag + data
Note: See TracBrowser for help on using the repository browser.