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
RevLine 
[7a1c039]1from .attribute import Attribute
[d04a689]2from .attributegroup import AttributeGroup
[7a1c039]3from .constants import AttributeTags
[d04a689]4from .value import Value
[7c143c9]5from .errors import ClientErrorBadRequest
[d04a689]6import sys
7import struct
8import logging
[7c143c9]9import tempfile
[8403f61]10
11# initialize logger
[7a1c039]12logger = logging.getLogger(__name__)
[4ec7caa]13
[ebf327d]14class Request():
[5c5fe6d]15    """From RFC 2565:
[c216863]16   
[84e8137]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    -----------------------------------------------
[5c5fe6d]35
[84e8137]36    """
[c216863]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
[8e43aa8]41    def __init__(self, version=None, operation_id=None, request_id=None,
[89fe6da]42                 attribute_groups=[], data=None, request=None, length=sys.maxint):
[5c5fe6d]43        """Create a Request.  Takes either the segments of the request
[ebf327d]44        separately, or a file handle for the request to parse.  If the
45        file handle is passed in, all other arguments are ignored.
[84e8137]46
47        Keyword arguments for passing in the segments of the request:
48       
[4ec7caa]49            version -- a tuple of two signed chars, identifying the
50                       major version and minor version numbers of the
51                       request
[84e8137]52                           
[4ec7caa]53            operation_id -- a signed short, identifying the id of the
[84e8137]54                            requested operation
55
[4ec7caa]56            request_id -- a signed int, identifying the id of the
[84e8137]57                          request itself.
[c216863]58
[ebf327d]59            attribute_groups -- a list of Attributes, at least length 1
[84e8137]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
[5c5fe6d]68
[84e8137]69        """
70
71        if request is None:
72            # make sure the version number isn't empty
[ffbe41d]73            if version is None:
74                raise ValueError("version must not be None")
[4ec7caa]75            # make sure verison is a tuple of length 2
[ffbe41d]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")
[84e8137]80            # make sure the operation id isn't empty
[ffbe41d]81            if operation_id is None:
82                raise ValueError("operation_id may not be None")
[84e8137]83            # make sure the request id isn't empty
[ffbe41d]84            if request_id is None:
85                raise ValueError("request_id may not be None")
[ebf327d]86            # make sure attribute_groups is a list of Attributes
[ffbe41d]87            for a in attribute_groups:
88                if not isinstance(a, AttributeGroup):
89                    raise ValueError("attribute not of type AttributeGroup")
[84e8137]90           
91        # if the request isn't None, then we'll read directly from
92        # that file handle
[c216863]93        if request is not None:
[7c143c9]94            # minimum length is
95            if length < 9:
96                raise ClientErrorBadRequest("length (%d) < 9" % length)
97           
[4ec7caa]98            # read the version-number (two signed chars)
[ffbe41d]99            self.version = struct.unpack('>bb', request.read(2))
[89fe6da]100            length -= 2
[8403f61]101            logger.debug("version-number : (0x%X, 0x%X)" % self.version)
[84e8137]102
103            # read the operation-id (or status-code, but that's only
[4ec7caa]104            # for a response) (signed short)
[ffbe41d]105            self.operation_id = struct.unpack('>h', request.read(2))[0]
[89fe6da]106            length -= 2
[8403f61]107            logger.debug("operation-id : 0x%X" % self.operation_id)
[84e8137]108
[4ec7caa]109            # read the request-id (signed int)
[ffbe41d]110            self.request_id = struct.unpack('>i', request.read(4))[0]
[89fe6da]111            length -= 4
[8403f61]112            logger.debug("request-id : 0x%X" % self.request_id)
[84e8137]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)
[8e43aa8]116            self.attribute_groups = []
[84e8137]117
118            # read in the next byte
[8403f61]119            next_byte = struct.unpack('>b', request.read(1))[0]
[7c143c9]120            length -= 1
[8403f61]121            logger.debug("next byte : 0x%X" % next_byte)
[84e8137]122
123            # as long as the next byte isn't signaling the end of the
124            # attributes, keep looping and parsing attributes
[2646571]125            while next_byte != AttributeTags.END:
[84e8137]126               
[8403f61]127                attribute_group_tag = next_byte
128                logger.debug("attribute-tag : %i" % attribute_group_tag)
129
[8e43aa8]130                attributes = []
131
[8403f61]132                next_byte = struct.unpack('>b', request.read(1))[0]
[89fe6da]133                length -= 1
[8403f61]134                logger.debug("next byte : 0x%X" % next_byte)
[84e8137]135
[8403f61]136                while next_byte > 0x0F:
137                   
[4ec7caa]138                    # read in the value tag (signed char)
[ffbe41d]139                    value_tag = next_byte
[8403f61]140                    logger.debug("value-tag : 0x%X" % value_tag)
141                   
[4ec7caa]142                    # read in the length of the name (signed short)
[ffbe41d]143                    name_length = struct.unpack('>h', request.read(2))[0]
[89fe6da]144                    length -= 2
[8403f61]145                    logger.debug("name-length : %i" % name_length)
[84e8137]146                   
[2646571]147                    if name_length != AttributeTags.ZERO_NAME_LENGTH:
[8403f61]148                        # read the name (a string of name_length bytes)
[ffbe41d]149                        name = request.read(name_length)
[89fe6da]150                        length -= name_length
[8403f61]151                        logger.debug("name : %s" % name)
152                   
153                        # read in the length of the value (signed short)
[ffbe41d]154                        value_length = struct.unpack('>h', request.read(2))[0]
[89fe6da]155                        length -= 2
[8403f61]156                        logger.debug("value-length : %i" % value_length)
157                   
158                        # read in the value (string of value_length bytes)
[ffbe41d]159                        value = request.read(value_length)
[89fe6da]160                        length -= value_length
161                       
[0e5cdb3]162                        ippvalue = Value.unpack(value_tag, value)
[aaa1da3]163                        logger.debug("value : %s" % ippvalue.value)
[8403f61]164
[ebf327d]165                        # create a new Attribute from the data we just
[8403f61]166                        # read in, and add it to our attributes list
[ebf327d]167                        attributes.append(Attribute(name, [ippvalue]))
[8403f61]168
169                    else:
170                        # read in the length of the value (signed short)
[ffbe41d]171                        value_length = struct.unpack('>h', request.read(2))[0]
[89fe6da]172                        length -= 2
[8403f61]173                        logger.debug("value-length : %i" % value_length)
174                   
175                        # read in the value (string of value_length bytes)
[ffbe41d]176                        value = request.read(value_length)
[89fe6da]177                        length -= value_length
[aaa1da3]178
[0e5cdb3]179                        ippvalue = Value.unpack(value_tag, value)
[aaa1da3]180                        logger.debug("value : %s" % ippvalue.value)
[8403f61]181
182                        # add another value to the last attribute
[aaa1da3]183                        attributes[-1].values.append(ippvalue)
[8403f61]184
185                    # read another byte
186                    next_byte = struct.unpack('>b', request.read(1))[0]
[89fe6da]187                    length -= 1
[c216863]188
[ebf327d]189                self.attribute_groups.append(AttributeGroup(
190                    attribute_group_tag, attributes))
[8e43aa8]191
[84e8137]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
[7c143c9]194            if length < 0:
195                raise ClientErrorBadRequest("length (%d) < 0" % length)
196           
[ce2abc5]197            self.data = tempfile.NamedTemporaryFile()
[7c143c9]198            self.data.write(request.read(length))
199            self.data.seek(0)
200           
201            logger.debug("data : %d bytes" % length)
[c216863]202
[84e8137]203        # otherwise, just set the class variables to the keyword
204        # arguments passed in
[c216863]205        else:
[8e43aa8]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
[c216863]210            self.data = data
[d56a0bc]211
[c5e88d0]212    def getAttributeGroup(self, tag):
213        return filter(lambda x: x.attribute_group_tag == tag,
214                      self.attribute_groups)
215
[0e5cdb3]216    @property
217    def packed_value(self):
[5c5fe6d]218        """Packs the value data into binary data.
219       
[d56a0bc]220        """
221
[1b9d629]222        # make sure the version number isn't empty
[ffbe41d]223        if self.version is None:
224            raise ValueError("version is None")
[1b9d629]225        # make sure verison is a tuple of length 2
[ffbe41d]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")
[1b9d629]230        # make sure the operation id isn't empty
[ffbe41d]231        if self.operation_id is None:
232            raise ValueError("operation_id is None")
[1b9d629]233        # make sure the request id isn't empty
[ffbe41d]234        if self.request_id is None:
235            raise ValueError("request_id is None")
[1b9d629]236        # make sure attribute_groups is a list of Attributes
[ffbe41d]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")
[1b9d629]242
[d56a0bc]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
[8e43aa8]250        # convert the attribute groups to binary
[0e5cdb3]251        attribute_groups = ''.join([a.packed_value for a in self.attribute_groups])
[d56a0bc]252
253        # conver the end-of-attributes-tag to binary
[2646571]254        end_of_attributes_tag = struct.pack('>b', AttributeTags.END)
[d56a0bc]255
256        # append everything together and return it
[7c143c9]257        return preattributes + attribute_groups + end_of_attributes_tag, self.data
[94211df]258
259    def __repr__(self):
[7bd1035]260        val = '<IPPRequest (version=%r, ' % [self.version]
[9eeab06]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.