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
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    COLLECTION                        = 0x34
40    TEXT_WITH_LANGUAGE                = 0x35
41    NAME_WITH_LANGUAGE                = 0x36
42
43    # character-string value tags
44    GENERIC_CHAR_STRING               = 0x40
45    TEXT_WITHOUT_LANGUAGE             = 0x41
46    NAME_WITHOUT_LANGUAGE             = 0x42
47    KEYWORD                           = 0x44
48    URI                               = 0x45
49    URI_SCHEME                        = 0x46
50    CHARSET                           = 0x47
51    NATURAL_LANGUAGE                  = 0x48
52    MIME_MEDIA_TYPE                   = 0x49                                   
53
54class IPPValue():
55    """
56    An IPP value consists of a tag and a value, and optionally, a name.
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     -----------------------------------------------   
70    """
71
72    def __init__(self, value_tag, value):
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
87
88        self.value_tag = value_tag
89        self.value = value
90
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.
96
97    From RFC 2565:
98
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    -----------------------------------------------
111
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    """
123
124    def __init__(self, name, values):
125        """
126        Initialize an IPPAttribute.
127       
128        Arguments:
129
130            name -- the name of the attribute
131
132            values -- a list of IPPValues.  May not be empty.
133        """
134
135        # make sure name isn't empty
136        assert name is not None
137         
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         
143        self.name = name
144        self.values = values
145
146    def toBinaryData(self):
147        """
148        Packs the attribute data into binary data.
149        """
150
151        # get the binary data for all the values
152        values = []
153        for v, i in zip(self.values, xrange(len(self.values))):
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
178            if i == 0:
179                values.append(''.join([value_tag_bin,
180                                       name_length_bin,
181                                       name_bin,
182                                       value_length_bin,
183                                       value_bin]))
184            else:
185                values.append(''.join([value_tag_bin,
186                                       name_length_bin,
187                                       value_length_bin,
188                                       value_bin]))
189               
190        # concatenate everything together and return it
191        return ''.join(values)
192
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
235class IPPRequest():
236    """
237    From RFC 2565:
238   
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    """
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
262    def __init__(self, version=None, operation_id=None, request_id=None,
263                 attribute_groups=[], data=None, request=None):
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       
272            version -- a tuple of two signed chars, identifying the
273                       major version and minor version numbers of the
274                       request
275                           
276            operation_id -- a signed short, identifying the id of the
277                            requested operation
278
279            request_id -- a signed int, identifying the id of the
280                          request itself.
281
282            attribute_groups -- a list of IPPAttributes, at least length 1
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
296            # make sure verison is a tuple of length 2
297            assert isinstance(version, tuple)
298            assert len(version) == 2
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
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)
306           
307        # if the request isn't None, then we'll read directly from
308        # that file handle
309        if request is not None:
310            # read the version-number (two signed chars)
311            self.version        = struct.unpack('>bb', request.read(2))
312            logger.debug("version-number : (0x%X, 0x%X)" % self.version)
313
314            # read the operation-id (or status-code, but that's only
315            # for a response) (signed short)
316            self.operation_id   = struct.unpack('>h', request.read(2))[0]
317            logger.debug("operation-id : 0x%X" % self.operation_id)
318
319            # read the request-id (signed int)
320            self.request_id     = struct.unpack('>i', request.read(4))[0]
321            logger.debug("request-id : 0x%X" % self.request_id)
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)
325            self.attribute_groups = []
326
327            # read in the next byte
328            next_byte = struct.unpack('>b', request.read(1))[0]
329            logger.debug("next byte : 0x%X" % next_byte)
330
331            # as long as the next byte isn't signaling the end of the
332            # attributes, keep looping and parsing attributes
333            while next_byte != IPPTags.END_OF_ATTRIBUTES_TAG:
334               
335                attribute_group_tag = next_byte
336                logger.debug("attribute-tag : %i" % attribute_group_tag)
337
338                attributes = []
339
340                next_byte = struct.unpack('>b', request.read(1))[0]
341                logger.debug("next byte : 0x%X" % next_byte)
342
343                while next_byte > 0x0F:
344                   
345                    # read in the value tag (signed char)
346                    value_tag     = next_byte
347                    logger.debug("value-tag : 0x%X" % value_tag)
348                   
349                    # read in the length of the name (signed short)
350                    name_length   = struct.unpack('>h', request.read(2))[0]
351                    logger.debug("name-length : %i" % name_length)
352                   
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
368                        attributes.append(IPPAttribute(name,
369                                                       [IPPValue(value_tag, value)]))
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
381                        attributes[-1].values.append(IPPValue(value_tag, value))
382
383                    # read another byte
384                    next_byte = struct.unpack('>b', request.read(1))[0]
385
386                self.attribute_groups.append(IPPAttributeGroup(attribute_group_tag, attributes))
387
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
390            self.data = request.read()
391            logger.debug("data : %s" % self.data)
392
393        # otherwise, just set the class variables to the keyword
394        # arguments passed in
395        else:
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
400            self.data = data
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
414        # convert the attribute groups to binary
415        attribute_groups = ''.join([a.toBinaryData() for a in self.attribute_groups])
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
421        if self.data is not None:
422            data = ''.join([struct.pack('>b', x) for x in self.data])
423        else:
424            data = ''
425
426        # append everything together and return it
427        return preattributes + attribute_groups + end_of_attributes_tag + data
Note: See TracBrowser for help on using the repository browser.