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

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

Add support for chunking, i.e. receiving file data

  • Property mode set to 100644
File size: 10.4 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            assert version is not None
74            # make sure verison is a tuple of length 2
75            assert isinstance(version, tuple)
76            assert len(version) == 2
77            # make sure the operation id isn't empty
78            assert operation_id is not None
79            # make sure the request id isn't empty
80            assert request_id is not None
81            # make sure attribute_groups is a list of Attributes
82            for a in attribute_groups: assert isinstance(a, AttributeGroup)
83           
84        # if the request isn't None, then we'll read directly from
85        # that file handle
86        if request is not None:
87            # minimum length is
88            if length < 9:
89                raise ClientErrorBadRequest("length (%d) < 9" % length)
90           
91            # read the version-number (two signed chars)
92            self.version        = struct.unpack('>bb', request.read(2))
93            length -= 2
94            logger.debug("version-number : (0x%X, 0x%X)" % self.version)
95
96            # read the operation-id (or status-code, but that's only
97            # for a response) (signed short)
98            self.operation_id   = struct.unpack('>h', request.read(2))[0]
99            length -= 2
100            logger.debug("operation-id : 0x%X" % self.operation_id)
101
102            # read the request-id (signed int)
103            self.request_id     = struct.unpack('>i', request.read(4))[0]
104            length -= 4
105            logger.debug("request-id : 0x%X" % self.request_id)
106
107            # now we have to read in the attributes.  Each attribute
108            # has a tag (1 byte) and a sequence of values (n bytes)
109            self.attribute_groups = []
110
111            # read in the next byte
112            next_byte = struct.unpack('>b', request.read(1))[0]
113            length -= 1
114            logger.debug("next byte : 0x%X" % next_byte)
115
116            # as long as the next byte isn't signaling the end of the
117            # attributes, keep looping and parsing attributes
118            while next_byte != AttributeTags.END:
119               
120                attribute_group_tag = next_byte
121                logger.debug("attribute-tag : %i" % attribute_group_tag)
122
123                attributes = []
124
125                next_byte = struct.unpack('>b', request.read(1))[0]
126                length -= 1
127                logger.debug("next byte : 0x%X" % next_byte)
128
129                while next_byte > 0x0F:
130                   
131                    # read in the value tag (signed char)
132                    value_tag     = next_byte
133                    logger.debug("value-tag : 0x%X" % value_tag)
134                   
135                    # read in the length of the name (signed short)
136                    name_length   = struct.unpack('>h', request.read(2))[0]
137                    length -= 2
138                    logger.debug("name-length : %i" % name_length)
139                   
140                    if name_length != AttributeTags.ZERO_NAME_LENGTH:
141                        # read the name (a string of name_length bytes)
142                        name          = request.read(name_length)
143                        length -= name_length
144                        logger.debug("name : %s" % name)
145                   
146                        # read in the length of the value (signed short)
147                        value_length  = struct.unpack('>h', request.read(2))[0]
148                        length -= 2
149                        logger.debug("value-length : %i" % value_length)
150                   
151                        # read in the value (string of value_length bytes)
152                        value         = request.read(value_length)
153                        length -= value_length
154                       
155                        ippvalue = Value.unpack(value_tag, value)
156                        logger.debug("value : %s" % ippvalue.value)
157
158                        # create a new Attribute from the data we just
159                        # read in, and add it to our attributes list
160                        attributes.append(Attribute(name, [ippvalue]))
161
162                    else:
163                        # read in the length of the value (signed short)
164                        value_length  = struct.unpack('>h', request.read(2))[0]
165                        length -= 2
166                        logger.debug("value-length : %i" % value_length)
167                   
168                        # read in the value (string of value_length bytes)
169                        value         = request.read(value_length)
170                        length -= value_length
171
172                        ippvalue = Value.unpack(value_tag, value)
173                        logger.debug("value : %s" % ippvalue.value)
174
175                        # add another value to the last attribute
176                        attributes[-1].values.append(ippvalue)
177
178                    # read another byte
179                    next_byte = struct.unpack('>b', request.read(1))[0]
180                    length -= 1
181
182                self.attribute_groups.append(AttributeGroup(
183                    attribute_group_tag, attributes))
184
185            # once we hit the end-of-attributes tag, the only thing
186            # left is the data, so go ahead and read all of it
187            if length < 0:
188                raise ClientErrorBadRequest("length (%d) < 0" % length)
189           
190            self.data = tempfile.TemporaryFile()
191            self.data.write(request.read(length))
192            self.data.seek(0)
193           
194            logger.debug("data : %d bytes" % length)
195
196        # otherwise, just set the class variables to the keyword
197        # arguments passed in
198        else:
199            self.version = (version[0], version[1])
200            self.operation_id = operation_id
201            self.request_id = request_id
202            self.attribute_groups = attribute_groups
203            self.data = data
204
205    def getAttributeGroup(self, tag):
206        return filter(lambda x: x.attribute_group_tag == tag,
207                      self.attribute_groups)
208
209    @property
210    def packed_value(self):
211        """Packs the value data into binary data.
212       
213        """
214
215        # make sure the version number isn't empty
216        assert self.version is not None
217        # make sure verison is a tuple of length 2
218        assert isinstance(self.version, tuple)
219        assert len(self.version) == 2
220        # make sure the operation id isn't empty
221        assert self.operation_id is not None
222        # make sure the request id isn't empty
223        assert self.request_id is not None
224        # make sure attribute_groups is a list of Attributes
225        assert len(self.attribute_groups) > 0
226        for a in self.attribute_groups: assert isinstance(a, AttributeGroup)
227
228        # convert the version, operation id, and request id to binary
229        preattributes = struct.pack('>bbhi',
230                                    self.version[0],
231                                    self.version[1],
232                                    self.operation_id,
233                                    self.request_id)
234
235        # convert the attribute groups to binary
236        attribute_groups = ''.join([a.packed_value for a in self.attribute_groups])
237
238        # conver the end-of-attributes-tag to binary
239        end_of_attributes_tag = struct.pack('>b', AttributeTags.END)
240
241        # append everything together and return it
242        return preattributes + attribute_groups + end_of_attributes_tag, self.data
243
244    def __repr__(self):
245        val = '<IPPRequest (version=%r, ' % [self.version]
246        val += 'operation_id=%x, ' % self.operation_id
247        val += 'request_id=%r, ' % self.request_id
248        val += 'attribute_groups=%r)>' % self.attribute_groups
249        return val
Note: See TracBrowser for help on using the repository browser.