source: server/lib/ipprequest.py @ 8403f61

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

Fix ipprequest (had misimplemented the spec)

  • Property mode set to 100644
File size: 14.3 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 = hex(value_tag)
89        self.value = str(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 = str(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            if i == 0:
155                name_length = sys.getsizeof(self.name)
156                value_length = sys.getsizeof(v.value)
157                values.append(struct.pack('>bh%ssh%ss' % (name_length, value_length),
158                                          v.value_tag,
159                                          name_length,
160                                          self.name,
161                                          value_length,
162                                          v.value))
163            else:
164                value_length = sys.getsizeof(v.value)
165                values.append(struct.pack('>bhh%ss' % (value_length),
166                                          v.value_tag,
167                                          name_length,
168                                          value_length,
169                                          v.value))
170               
171        # concatenate everything together and return it
172        return ''.join(values)
173
174class IPPRequest():
175    """
176    From RFC 2565:
177   
178    The encoding for an operation request or response consists of:
179    -----------------------------------------------
180    |                  version-number             |   2 bytes  - required
181    -----------------------------------------------
182    |               operation-id (request)        |
183    |                      or                     |   2 bytes  - required
184    |               status-code (response)        |
185    -----------------------------------------------
186    |                   request-id                |   4 bytes  - required
187    -----------------------------------------------------------
188    |               xxx-attributes-tag            |   1 byte  |
189    -----------------------------------------------           |-0 or more
190    |             xxx-attribute-sequence          |   n bytes |
191    -----------------------------------------------------------
192    |              end-of-attributes-tag          |   1 byte   - required
193    -----------------------------------------------
194    |                     data                    |   q bytes  - optional
195    -----------------------------------------------
196    """
197
198    # either give the version, operation_id, request_id,
199    # attribute_sequence, and data, or a file handler (request) which
200    # can be read from to get the request
201    def __init__(self, version=None, operation_id=None, request_id=None, attributes=[], data=None, request=None):
202        """
203        Create an IPPRequest.  Takes either the segments of the
204        request separately, or a file handle for the request to parse.
205        If the file handle is passed in, all other arguments are
206        ignored.
207
208        Keyword arguments for passing in the segments of the request:
209       
210            version -- a tuple of two signed chars, identifying the
211                       major version and minor version numbers of the
212                       request
213                           
214            operation_id -- a signed short, identifying the id of the
215                            requested operation
216
217            request_id -- a signed int, identifying the id of the
218                          request itself.
219
220            attributes -- (optional) a list of IPPAttributes
221
222            data -- (optional) variable length, containing the actual
223                    data of the request
224
225        Keyword arguments for passing in the raw request:
226
227            request -- a file handle that supports the read()
228                       operation
229        """
230
231        if request is None:
232            # make sure the version number isn't empty
233            assert version is not None
234            # make sure verison is a tuple of length 2
235            assert isinstance(version, tuple)
236            assert len(version) == 2
237            # make sure the operation id isn't empty
238            assert operation_id is not None
239            # make sure the request id isn't empty
240            assert request_id is not None
241           
242        # if the request isn't None, then we'll read directly from
243        # that file handle
244        if request is not None:
245            # read the version-number (two signed chars)
246            self.version        = struct.unpack('>bb', request.read(2))
247            logger.debug("version-number : (0x%X, 0x%X)" % self.version)
248
249            # read the operation-id (or status-code, but that's only
250            # for a response) (signed short)
251            self.operation_id   = struct.unpack('>h', request.read(2))[0]
252            logger.debug("operation-id : 0x%X" % self.operation_id)
253
254            # read the request-id (signed int)
255            self.request_id     = struct.unpack('>i', request.read(4))[0]
256            logger.debug("request-id : 0x%X" % self.request_id)
257
258            # now we have to read in the attributes.  Each attribute
259            # has a tag (1 byte) and a sequence of values (n bytes)
260            self.attributes     = []
261
262            # read in the next byte
263            next_byte = struct.unpack('>b', request.read(1))[0]
264            logger.debug("next byte : 0x%X" % next_byte)
265
266            # as long as the next byte isn't signaling the end of the
267            # attributes, keep looping and parsing attributes
268            while next_byte != IPPTags.END_OF_ATTRIBUTES_TAG:
269               
270                attribute_group_tag = next_byte
271                logger.debug("attribute-tag : %i" % attribute_group_tag)
272
273                next_byte = struct.unpack('>b', request.read(1))[0]
274                logger.debug("next byte : 0x%X" % next_byte)
275
276                while next_byte > 0x0F:
277                   
278                    # read in the value tag (signed char)
279                    value_tag     = next_byte
280                    logger.debug("value-tag : 0x%X" % value_tag)
281                   
282                    # read in the length of the name (signed short)
283                    name_length   = struct.unpack('>h', request.read(2))[0]
284                    logger.debug("name-length : %i" % name_length)
285                   
286                    if name_length != IPPTags.ZERO_NAME_LENGTH:
287                        # read the name (a string of name_length bytes)
288                        name          = request.read(name_length)
289                        logger.debug("name : %s" % name)
290                   
291                        # read in the length of the value (signed short)
292                        value_length  = struct.unpack('>h', request.read(2))[0]
293                        logger.debug("value-length : %i" % value_length)
294                   
295                        # read in the value (string of value_length bytes)
296                        value         = request.read(value_length)
297                        logger.debug("value : %s" % value)
298
299                        # create a new IPPAttribute from the data we just
300                        # read in, and add it to our attributes list
301                        self.attributes.append(IPPAttribute(name,
302                                                            [IPPValue(value_tag, value)]))
303
304                    else:
305                        # read in the length of the value (signed short)
306                        value_length  = struct.unpack('>h', request.read(2))[0]
307                        logger.debug("value-length : %i" % value_length)
308                   
309                        # read in the value (string of value_length bytes)
310                        value         = request.read(value_length)
311                        logger.debug("value : %s" % value)
312
313                        # add another value to the last attribute
314                        self.attributes[-1].values.append(IPPValue(value_tag, value))
315
316                    # read another byte
317                    next_byte = struct.unpack('>b', request.read(1))[0]
318
319            # once we hit the end-of-attributes tag, the only thing
320            # left is the data, so go ahead and read all of it
321            self.data = request.read()
322            logger.debug("data : %s" % self.data)
323
324        # otherwise, just set the class variables to the keyword
325        # arguments passed in
326        else:
327            self.version = (int(version[0]), int(version[1]))
328            self.operation_id = int(operation_id)
329            self.request_id = int(request_id)
330            self.attributes = attributes
331            self.data = data
332
333    def toBinaryData(self):
334        """
335        Packs the value data into binary data.
336        """
337
338        # convert the version, operation id, and request id to binary
339        preattributes = struct.pack('>bbhi',
340                                    self.version[0],
341                                    self.version[1],
342                                    self.operation_id,
343                                    self.request_id)
344
345        # convert the attributes to binary
346        attributes = ''.join([a.toBinaryData() for a in self.attributes])
347
348        # conver the end-of-attributes-tag to binary
349        end_of_attributes_tag = struct.pack('>b', IPPTags.END_OF_ATTRIBUTES_TAG)
350
351        # convert the data to binary
352        if self.data is not None:
353            data = ''.join([struct.pack('>b', x) for x in self.data])
354        else:
355            data = ''
356
357        # append everything together and return it
358        return preattributes + attributes + end_of_attributes_tag + data
Note: See TracBrowser for help on using the repository browser.