source: server/lib/gutenbach/ipp/core/request.py @ ffbe41d

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

Clean up core ipp code a bit

  • Property mode set to 100644
File size: 11.1 KB
Line 
1from .attribute import Attribute
2from .attributegroup import AttributeGroup
3from .constants import AttributeTags
4from .value import Value
5from .errors import ClientErrorBadRequest
6import sys
7import struct
8import logging
9import tempfile
10
11# initialize logger
12logger = logging.getLogger(__name__)
13
14class Request():
15    """From RFC 2565:
16   
17    The encoding for an operation request or response consists of:
18    -----------------------------------------------
19    |                  version-number             |   2 bytes  - required
20    -----------------------------------------------
21    |               operation-id (request)        |
22    |                      or                     |   2 bytes  - required
23    |               status-code (response)        |
24    -----------------------------------------------
25    |                   request-id                |   4 bytes  - required
26    -----------------------------------------------------------
27    |               xxx-attributes-tag            |   1 byte  |
28    -----------------------------------------------           |-0 or more
29    |             xxx-attribute-sequence          |   n bytes |
30    -----------------------------------------------------------
31    |              end-of-attributes-tag          |   1 byte   - required
32    -----------------------------------------------
33    |                     data                    |   q bytes  - optional
34    -----------------------------------------------
35
36    """
37
38    # either give the version, operation_id, request_id,
39    # attribute_sequence, and data, or a file handler (request) which
40    # can be read from to get the request
41    def __init__(self, version=None, operation_id=None, request_id=None,
42                 attribute_groups=[], data=None, request=None, length=sys.maxint):
43        """Create a Request.  Takes either the segments of the request
44        separately, or a file handle for the request to parse.  If the
45        file handle is passed in, all other arguments are ignored.
46
47        Keyword arguments for passing in the segments of the request:
48       
49            version -- a tuple of two signed chars, identifying the
50                       major version and minor version numbers of the
51                       request
52                           
53            operation_id -- a signed short, identifying the id of the
54                            requested operation
55
56            request_id -- a signed int, identifying the id of the
57                          request itself.
58
59            attribute_groups -- a list of Attributes, at least length 1
60
61            data -- (optional) variable length, containing the actual
62                    data of the request
63
64        Keyword arguments for passing in the raw request:
65
66            request -- a file handle that supports the read()
67                       operation
68
69        """
70
71        if request is None:
72            # make sure the version number isn't empty
73            if version is None:
74                raise ValueError("version must not be None")
75            # make sure verison is a tuple of length 2
76            if not hasattr(version, '__iter__'):
77                raise ValueError("version must be iterable")
78            if len(version) != 2:
79                raise ValueError("version must be of length 2")
80            # make sure the operation id isn't empty
81            if operation_id is None:
82                raise ValueError("operation_id may not be None")
83            # make sure the request id isn't empty
84            if request_id is None:
85                raise ValueError("request_id may not be None")
86            # make sure attribute_groups is a list of Attributes
87            for a in attribute_groups:
88                if not isinstance(a, AttributeGroup):
89                    raise ValueError("attribute not of type AttributeGroup")
90           
91        # if the request isn't None, then we'll read directly from
92        # that file handle
93        if request is not None:
94            # minimum length is
95            if length < 9:
96                raise ClientErrorBadRequest("length (%d) < 9" % length)
97           
98            # read the version-number (two signed chars)
99            self.version = struct.unpack('>bb', request.read(2))
100            length -= 2
101            logger.debug("version-number : (0x%X, 0x%X)" % self.version)
102
103            # read the operation-id (or status-code, but that's only
104            # for a response) (signed short)
105            self.operation_id = struct.unpack('>h', request.read(2))[0]
106            length -= 2
107            logger.debug("operation-id : 0x%X" % self.operation_id)
108
109            # read the request-id (signed int)
110            self.request_id = struct.unpack('>i', request.read(4))[0]
111            length -= 4
112            logger.debug("request-id : 0x%X" % self.request_id)
113
114            # now we have to read in the attributes.  Each attribute
115            # has a tag (1 byte) and a sequence of values (n bytes)
116            self.attribute_groups = []
117
118            # read in the next byte
119            next_byte = struct.unpack('>b', request.read(1))[0]
120            length -= 1
121            logger.debug("next byte : 0x%X" % next_byte)
122
123            # as long as the next byte isn't signaling the end of the
124            # attributes, keep looping and parsing attributes
125            while next_byte != AttributeTags.END:
126               
127                attribute_group_tag = next_byte
128                logger.debug("attribute-tag : %i" % attribute_group_tag)
129
130                attributes = []
131
132                next_byte = struct.unpack('>b', request.read(1))[0]
133                length -= 1
134                logger.debug("next byte : 0x%X" % next_byte)
135
136                while next_byte > 0x0F:
137                   
138                    # read in the value tag (signed char)
139                    value_tag = next_byte
140                    logger.debug("value-tag : 0x%X" % value_tag)
141                   
142                    # read in the length of the name (signed short)
143                    name_length = struct.unpack('>h', request.read(2))[0]
144                    length -= 2
145                    logger.debug("name-length : %i" % name_length)
146                   
147                    if name_length != AttributeTags.ZERO_NAME_LENGTH:
148                        # read the name (a string of name_length bytes)
149                        name = request.read(name_length)
150                        length -= name_length
151                        logger.debug("name : %s" % name)
152                   
153                        # read in the length of the value (signed short)
154                        value_length = struct.unpack('>h', request.read(2))[0]
155                        length -= 2
156                        logger.debug("value-length : %i" % value_length)
157                   
158                        # read in the value (string of value_length bytes)
159                        value = request.read(value_length)
160                        length -= value_length
161                       
162                        ippvalue = Value.unpack(value_tag, value)
163                        logger.debug("value : %s" % ippvalue.value)
164
165                        # create a new Attribute from the data we just
166                        # read in, and add it to our attributes list
167                        attributes.append(Attribute(name, [ippvalue]))
168
169                    else:
170                        # read in the length of the value (signed short)
171                        value_length = struct.unpack('>h', request.read(2))[0]
172                        length -= 2
173                        logger.debug("value-length : %i" % value_length)
174                   
175                        # read in the value (string of value_length bytes)
176                        value = request.read(value_length)
177                        length -= value_length
178
179                        ippvalue = Value.unpack(value_tag, value)
180                        logger.debug("value : %s" % ippvalue.value)
181
182                        # add another value to the last attribute
183                        attributes[-1].values.append(ippvalue)
184
185                    # read another byte
186                    next_byte = struct.unpack('>b', request.read(1))[0]
187                    length -= 1
188
189                self.attribute_groups.append(AttributeGroup(
190                    attribute_group_tag, attributes))
191
192            # once we hit the end-of-attributes tag, the only thing
193            # left is the data, so go ahead and read all of it
194            if length < 0:
195                raise ClientErrorBadRequest("length (%d) < 0" % length)
196           
197            self.data = tempfile.NamedTemporaryFile()
198            self.data.write(request.read(length))
199            self.data.seek(0)
200           
201            logger.debug("data : %d bytes" % length)
202
203        # otherwise, just set the class variables to the keyword
204        # arguments passed in
205        else:
206            self.version = (version[0], version[1])
207            self.operation_id = operation_id
208            self.request_id = request_id
209            self.attribute_groups = attribute_groups
210            self.data = data
211
212    def getAttributeGroup(self, tag):
213        return filter(lambda x: x.attribute_group_tag == tag,
214                      self.attribute_groups)
215
216    @property
217    def packed_value(self):
218        """Packs the value data into binary data.
219       
220        """
221
222        # make sure the version number isn't empty
223        if self.version is None:
224            raise ValueError("version is None")
225        # make sure verison is a tuple of length 2
226        if not hasattr(self.version, '__iter__'):
227            raise ValueError("version is not iterable")
228        if len(self.version) != 2:
229            raise ValueError("version is not of length 2")
230        # make sure the operation id isn't empty
231        if self.operation_id is None:
232            raise ValueError("operation_id is None")
233        # make sure the request id isn't empty
234        if self.request_id is None:
235            raise ValueError("request_id is None")
236        # make sure attribute_groups is a list of Attributes
237        if len(self.attribute_groups) == 0:
238            raise ValueError("no attribute groups")
239        for a in self.attribute_groups:
240            if not isinstance(a, AttributeGroup):
241                raise ValueError("not of type AttributeGroup")
242
243        # convert the version, operation id, and request id to binary
244        preattributes = struct.pack('>bbhi',
245                                    self.version[0],
246                                    self.version[1],
247                                    self.operation_id,
248                                    self.request_id)
249
250        # convert the attribute groups to binary
251        attribute_groups = ''.join([a.packed_value for a in self.attribute_groups])
252
253        # conver the end-of-attributes-tag to binary
254        end_of_attributes_tag = struct.pack('>b', AttributeTags.END)
255
256        # append everything together and return it
257        return preattributes + attribute_groups + end_of_attributes_tag, self.data
258
259    def __repr__(self):
260        val = '<IPPRequest (version=%r, ' % [self.version]
261        val += 'operation_id=%x, ' % self.operation_id
262        val += 'request_id=%r, ' % self.request_id
263        val += 'attribute_groups=%r)>' % self.attribute_groups
264        return val
Note: See TracBrowser for help on using the repository browser.