source: server/lib/gutenbach/server/requests.py @ ee8e6d0

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

Fix error with HTTP server recreating printer objects

  • Property mode set to 100644
File size: 20.3 KB
RevLine 
[738d179]1from gutenbach.server.printer import GutenbachPrinter
[b2e077a]2import gutenbach.ipp as ipp
[287d6ec]3import logging
[5e44432]4import traceback
[aded2d1]5import sys
[287d6ec]6
[d04a689]7# initialize logger
8logger = logging.getLogger(__name__)
[287d6ec]9
10def handler_for(operation):
11    """A decorator method to mark a function with the operation id
12    that it handles.  This value will be stored in
13    'func.ipp_operation'.
14
15    """
16   
17    def f(func):
18        func.ipp_operation = operation
19        return func
20    return f
21
[1037115]22def make_empty_response(request):
23    # Operation attributes -- typically the same for any request
24    attribute_group = ipp.AttributeGroup(
25        ipp.AttributeTags.OPERATION,
26        [ipp.AttributesCharset('utf-8'),
27         ipp.AttributesNaturalLanguage('en-us')])
28   
29    # Set up the default response -- handlers will override these
30    # values if they need to
31    response_kwargs = {}
32    response_kwargs['version']          = request.version
33    response_kwargs['operation_id']     = ipp.StatusCodes.OK
34    response_kwargs['request_id']       = request.request_id
35    response_kwargs['attribute_groups'] = [attribute_group]
36    response = ipp.Request(**response_kwargs)
37   
38    return response
39
[b2e077a]40class GutenbachRequestHandler(object):
[287d6ec]41
[b2e077a]42    def __init__(self):
[ee8e6d0]43        print "init"
[b2e077a]44        self.printers = {
45            "test": GutenbachPrinter(name="test")
46            }
47        self.default = "test"
[1037115]48
49    def generic_handle(self, request):
50        # check the IPP version number
51        if request.version != (1, 1):
52            raise ipp.errors.ServerErrorVersionNotSupported(str(request.version))
53
54        # make sure the operation attribute group has the correct tag
55        operation = request.attribute_groups[0]
56        if operation.tag != ipp.AttributeTags.OPERATION:
57            raise ipp.errors.ClientErrorBadRequest(
58                "Attribute group does not have OPERATION tag: 0x%x" % operation.tag)
59
60        # check charset
61        charset_attr = operation.attributes[0]
62        expected = ipp.AttributesCharset(charset_attr.values[0].value)
63        if charset_attr != expected:
64            raise ipp.errors.ClientErrorBadRequest(str(charset_attr))
65        if charset_attr.values[0].value != 'utf-8':
66            raise ipp.errors.ClientErrorAttributes(str(charset_attr))
67
68        # check for attributes-natural-language
69        natlang_attr = operation.attributes[1]
70        expected = ipp.AttributesNaturalLanguage(natlang_attr.values[0].value)
71        if natlang_attr != expected:
72            raise ipp.errors.ClientErrorBadRequest(str(natlang_attr))
73        if natlang_attr.values[0].value != 'en-us':
74            raise ipp.errors.ClientErrorAttributes(str(natlang_attr))
[287d6ec]75   
[ef8df33]76    def handle(self, request):
[b2e077a]77        # look up the handler
78        handler = None
[9eeab06]79        handler_name = None
[287d6ec]80        for d in dir(self):
81            if getattr(getattr(self, d), "ipp_operation", None) == request.operation_id:
[9eeab06]82                handler_name = d
[b2e077a]83                break
[1037115]84
[b2e077a]85        # we couldn't find a handler, so default to unknown operation
[9eeab06]86        if handler_name is None:
87            handler_name = "unknown_operation"
[1037115]88
89        # actually get the handler
[9eeab06]90        handler = getattr(self, handler_name)
[7bd1035]91        logger.info("Handling request of type '%s'" % handler_name)
[aef164a]92
[1037115]93        # try to handle the request
[aef164a]94        try:
[1037115]95            self.generic_handle(request)
96            response = make_empty_response(request)
97            handler(request, response)
[aef164a]98
99        # Handle any errors that occur.  If an exception occurs that
100        # is an IPP error, then we can get the error code from the
101        # exception itself.
102        except ipp.errors.IPPException:
103            exctype, excval, exctb = sys.exc_info()
[b828a96]104            logger.error("%s: %s" % (exctype.__name__, excval.message))
[1037115]105            response = make_empty_response(request)
[aef164a]106            excval.update_response(response)
107
108        # If it wasn't an IPP error, then it's our fault, so mark it
109        # as an internal server error
110        except Exception:
111            logger.error(traceback.format_exc())
[1037115]112            response = make_empty_response(request)
[aef164a]113            response.operation_id = ipp.StatusCodes.INTERNAL_ERROR
114
[ef8df33]115        return response
[287d6ec]116
[1037115]117    def unknown_operation(self, request, response):
[9eeab06]118        logger.warning("Received unknown operation 0x%x" % request.operation_id)
[1037115]119        response = make_empty_response(request)
[793432f]120        response.operation_id = ipp.StatusCodes.OPERATION_NOT_SUPPORTED
[ef8df33]121        return response
[0ede474]122       
[9eeab06]123    ##### Printer Commands
124
[793432f]125    @handler_for(ipp.OperationCodes.PRINT_JOB)
[1037115]126    def print_job(self, request, response):
[7a7a09e]127        """RFC 2911: 3.2.1 Print-Job Operation
128
129        This REQUIRED operation allows a client to submit a print job
130        with only one document and supply the document data (rather
131        than just a reference to the data). See Section 15 for the
132        suggested steps for processing create operations and their
133        Operation and Job Template attributes.
134
[1037115]135        Request
136        -------
137        Group 1: Operation Attributes
138            REQUIRED 'attributes-charset'
139            REQUIRED 'attributes-natural-language'
140            REQUIRED 'printer-uri' (uri)
141            OPTIONAL 'requesting-user-name' (name(MAX))
142            OPTIONAL 'job-name' (name(MAX))
143            OPTIONAL 'ipp-attribute-fidelity' (boolean)
144            OPTIONAL 'document-name' (name(MAX))
145            OPTIONAL 'compression' (type3 keyword)
146            OPTIONAL 'document-format' (mimeMediaType)
147            OPTIONAL 'document-natural-language' (naturalLanguage)
148            OPTIONAL 'job-k-octets' (integer(0:MAX))
149            OPTIONAL 'job-impressions' (integer(0:MAX))
150            OPTIONAL 'job-media-sheets' (integer(0:MAX))
151        Group 2: Job Template Attributes
152        Group 3: Document Content
153
154        Response
155        --------
156        Group 1: Operation Attributes
157            OPTIONAL 'status-message' (text(255))
158            OPTIONAL 'detailed-status-message' (text(MAX))
159            REQUIRED 'attributes-charset'
160            REQUIRED 'attributes-natural-language'
161        Group 2: Unsupported Attributes
162        Group 3: Job Object Attributes
163            REQUIRED 'job-uri' (uri)
164            REQUIRED 'job-id' (integer(1:MAX))
165            REQUIRED 'job-state' (type1 enum)
166            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
167            OPTIONAL 'job-state-message' (text(MAX))
168            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
169
[7a7a09e]170        """
171       
[b828a96]172        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]173
[793432f]174    @handler_for(ipp.OperationCodes.VALIDATE_JOB)
[1037115]175    def validate_job(self, request, response):
[94a4825]176
[b828a96]177        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]178
[793432f]179    @handler_for(ipp.OperationCodes.GET_JOBS)
[1037115]180    def get_jobs(self, request, response):
181        """3.2.6 Get-Jobs Operation
[6ed9d7a]182       
183        This REQUIRED operation allows a client to retrieve the list
184        of Job objects belonging to the target Printer object. The
185        client may also supply a list of Job attribute names and/or
186        attribute group names. A group of Job object attributes will
187        be returned for each Job object that is returned.
188
189        This operation is similar to the Get-Job-Attributes operation,
190        except that this Get-Jobs operation returns attributes from
191        possibly more than one object.
192
[1037115]193        Request
194        -------
195        Group 1: Operation Attributes
196            REQUIRED 'attributes-charset'
197            REQUIRED 'attributes-natural-language'
198            REQUIRED 'printer-uri' (uri)
199            OPTIONAL 'requesting-user-name' (name(MAX))
200            OPTIONAL 'limit' (integer(1:MAX))
201            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
202            OPTIONAL 'which-jobs' (type2 keyword)
203            OPTIONAL 'my-jobs' (boolean)
204
205        Response
206        --------
207        Group 1: Operation Attributes
208            OPTIONAL 'status-message' (text(255))
209            OPTIONAL 'detailed-status-message' (text(MAX))
210            REQUIRED 'attributes-charset'
211            REQUIRED 'attributes-natural-language'
212        Group 2: Unsupported Attributes
213        Groups 3 to N: Job Object Attributes
214
[6ed9d7a]215        """
[59a1d4a]216
[1037115]217        operation = request.attribute_groups[0]
[59a1d4a]218
[1037115]219        # requested printer uri
220        if 'printer-uri' not in operation:
221            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
222        uri_attr = operation['printer-uri']
223        printer_name = uri_attr.values[0].value.split("/")[-1]
224        if uri_attr != ipp.PrinterUri(uri_attr.values[0].value):
225            raise ipp.errors.ClientErrorBadRequest(str(uri_attr))
[5fe360e]226        if printer_name not in self.printers:
[1037115]227            raise ipp.errors.ClientErrorAttributes(str(uri_attr), uri_attr)
228        jobs = self.printers[printer_name].get_jobs()
229
230        # get the job attributes and add them to the response
231        for job in self.printers[printer_name].get_jobs():
[ee8e6d0]232            attrs = job.get_job_attributes(operation)
[1037115]233            response.attribute_groups.append(ipp.AttributeGroup(
234                ipp.AttributeTags.JOB, attrs))
[287d6ec]235
[793432f]236    @handler_for(ipp.OperationCodes.PRINT_URI)
[1037115]237    def print_uri(self, request, response):
[b828a96]238        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]239
[793432f]240    @handler_for(ipp.OperationCodes.CREATE_JOB)
[1037115]241    def create_job(self, request, response):
[94a4825]242        """RFC 2911: 3.2.4 Create-Job Operation
243
244        This OPTIONAL operation is similar to the Print-Job operation
245        (section 3.2.1) except that in the Create-Job request, a
246        client does not supply document data or any reference to
247        document data. Also, the client does not supply any of the
248        'document-name', 'document- format', 'compression', or
249        'document-natural-language' operation attributes. This
250        operation is followed by one or more Send-Document or Send-URI
251        operations. In each of those operation requests, the client
252        OPTIONALLY supplies the 'document-name', 'document-format',
253        and 'document-natural-language' attributes for each document
254        in the multi-document Job object.
255
[ee8e6d0]256        Request
257        -------
258        Group 1: Operation Attributes
259            REQUIRED 'attributes-charset'
260            REQUIRED 'attributes-natural-language'
261            REQUIRED 'printer-uri' (uri)
262            OPTIONAL 'requesting-user-name' (name(MAX))
263            OPTIONAL 'job-name' (name(MAX))
264            OPTIONAL 'ipp-attribute-fidelity' (boolean)
265            OPTIONAL 'job-k-octets' (integer(0:MAX))
266            OPTIONAL 'job-impressions' (integer(0:MAX))
267            OPTIONAL 'job-media-sheets' (integer(0:MAX))
268        Group 2: Job Template Attributes
269
270        Response
271        --------
[1037115]272        Group 1: Operation Attributes
[ee8e6d0]273            OPTIONAL 'status-message' (text(255))
274            OPTIONAL 'detailed-status-message' (text(MAX))
275            REQUIRED 'attributes-charset'
276            REQUIRED 'attributes-natural-language'
277        Group 2: Unsupported Attributes
278        Group 3: Job Object Attributes
279            REQUIRED 'job-uri' (uri)
280            REQUIRED 'job-id' (integer(1:MAX))
281            REQUIRED 'job-state' (type1 enum)
282            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
283            OPTIONAL 'job-state-message' (text(MAX))
284            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
[94a4825]285       
286        """
[9eeab06]287
[ee8e6d0]288
289        operation = request.attribute_groups[0]
290
291        # requested printer uri
292        if 'printer-uri' not in operation:
293            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
294        uri_attr = operation['printer-uri']
295        printer_name = uri_attr.values[0].value.split("/")[-1]
296        if uri_attr != ipp.PrinterUri(uri_attr.values[0].value):
297            raise ipp.errors.ClientErrorBadRequest(str(uri_attr))
298        if printer_name not in self.printers:
299            raise ipp.errors.ClientErrorAttributes(str(uri_attr), uri_attr)
300
301        # get attributes from the printer and add to response
302        job = self.printers[printer_name].create_job(request)
303        response.attribute_groups.append(ipp.AttributeGroup(
304            ipp.AttributeTags.JOB, job.get_job_attributes(operation)))
[94a4825]305   
[793432f]306    @handler_for(ipp.OperationCodes.PAUSE_PRINTER)
[1037115]307    def pause_printer(self, request, response):
[b828a96]308        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]309
[793432f]310    @handler_for(ipp.OperationCodes.RESUME_PRINTER)
[1037115]311    def resume_printer(self, request, response):
[b828a96]312        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]313
[793432f]314    @handler_for(ipp.OperationCodes.GET_PRINTER_ATTRIBUTES)
[1037115]315    def get_printer_attributes(self, request, response):
[cad7502]316        """RFC 2911: 3.2.5 Get-Printer-Attributes Operation
317
318        This REQUIRED operation allows a client to request the values
319        of the attributes of a Printer object.
320       
321        In the request, the client supplies the set of Printer
322        attribute names and/or attribute group names in which the
323        requester is interested. In the response, the Printer object
324        returns a corresponding attribute set with the appropriate
325        attribute values filled in.
326
[1037115]327        Request
328        -------
[cad7502]329
[1037115]330        Group 1: Operation Attributes
331            REQUIRED 'attributes-charset'
332            REQUIRED 'attributes-natural-language'
333            REQUIRED 'printer-uri' (uri)
334            OPTIONAL 'requesting-user-name' (name(MAX))
335            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
[ee8e6d0]336            OPTIONAL 'document-format' (mimeMediaType)
337           
[1037115]338        Response
339        --------
340
341        Group 1: Operation Attributes
342            OPTIONAL 'status-message' (text(255))
343            OPTIONAL 'detailed-status-message' (text(MAX))
344            REQUIRED 'attributes-charset'
345            REQUIRED 'attributes-natural-language'
346        Group 2: Unsupported Attributes
347        Group 3: Printer Object Attributes
[cad7502]348
349        """
350
[1037115]351        operation = request.attribute_groups[0]
[59a1d4a]352
[1037115]353        # requested printer uri
354        if 'printer-uri' not in operation:
355            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
356        uri_attr = operation['printer-uri']
357        printer_name = uri_attr.values[0].value.split("/")[-1]
358        if uri_attr != ipp.PrinterUri(uri_attr.values[0].value):
359            raise ipp.errors.ClientErrorBadRequest(str(uri_attr))
[5fe360e]360        if printer_name not in self.printers:
[1037115]361            raise ipp.errors.ClientErrorAttributes(str(uri_attr), uri_attr)
362        printer = self.printers[printer_name]
[59a1d4a]363
[1037115]364        # get attributes from the printer and add to response
365        response.attribute_groups.append(ipp.AttributeGroup(
[ee8e6d0]366            ipp.AttributeTags.PRINTER, printer.get_printer_attributes(operation)))
[9eeab06]367
[793432f]368    @handler_for(ipp.OperationCodes.SET_PRINTER_ATTRIBUTES)
[1037115]369    def set_printer_attributes(self, request, response):
[b828a96]370        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]371
372    ##### Job Commands
373
[793432f]374    @handler_for(ipp.OperationCodes.CANCEL_JOB)
[1037115]375    def cancel_job(self, request, response):
[b828a96]376        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]377
[793432f]378    @handler_for(ipp.OperationCodes.SEND_DOCUMENT)
[1037115]379    def send_document(self, request, response):
[b828a96]380        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]381
[793432f]382    @handler_for(ipp.OperationCodes.SEND_URI)
[1037115]383    def send_uri(self, request, response):
[b828a96]384        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]385
[793432f]386    @handler_for(ipp.OperationCodes.GET_JOB_ATTRIBUTES)
[1037115]387    def get_job_attributes(self, request, response):
[59a1d4a]388       
[1037115]389        operation = request.attribute_groups[0]
390
391        # requested printer uri
392        if 'printer-uri' not in operation:
393            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
394        uri_attr = operation['printer-uri']
395        printer_name = uri_attr.values[0].value.split("/")[-1]
396        if uri_attr != ipp.PrinterUri(uri_attr.values[0].value):
397            raise ipp.errors.ClientErrorBadRequest(str(uri_attr))
[5fe360e]398        if printer_name not in self.printers:
[1037115]399            raise ipp.errors.ClientErrorAttributes(str(uri_attr), uri_attr)
400        printer = self.printers[printer_name]
401
402        if 'job-id' not in operation:
403            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
404        job_id_attr = operation['job-id']
405        job_id = job_id_attr.values[0].value
406        if job_id_attr != ipp.JobId(job_id_attr.values[0].value):
407            raise ipp.errors.ClientErrorBadRequest(str(job_id_attr))
408        if job_id not in printer.jobs:
409            raise ipp.errors.ClientErrorAttributes(str(job_id_attr))
410        job = printer.get_job(job_id)
411
412        # get the job attributes and add them to the response
[ee8e6d0]413        attrs = job.get_job_attributes(operation)
[1037115]414        response.attribute_groups.append(ipp.AttributeGroup(
415            ipp.AttributeTags.JOB, attrs))
[9eeab06]416
[793432f]417    @handler_for(ipp.OperationCodes.SET_JOB_ATTRIBUTES)
[1037115]418    def set_job_attributes(self, request, response):
[b828a96]419        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]420
[793432f]421    @handler_for(ipp.OperationCodes.RESTART_JOB)
[1037115]422    def restart_job(self, request, response):
[b828a96]423        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]424
[793432f]425    @handler_for(ipp.OperationCodes.PROMOTE_JOB)
[1037115]426    def promote_job(self, request, response):
[b828a96]427        raise ipp.errors.ServerErrorOperationNotSupported
[9eeab06]428
429    ##### CUPS Specific Commands
430
[793432f]431    @handler_for(ipp.OperationCodes.CUPS_GET_DOCUMENT)
[1037115]432    def cups_get_document(self, request, response):
[b828a96]433        raise ipp.errors.ServerErrorOperationNotSupported
[e70c020]434
[793432f]435    @handler_for(ipp.OperationCodes.CUPS_GET_DEFAULT)
[1037115]436    def cups_get_default(self, request, response):
[0ede474]437        """The CUPS-Get-Default operation (0x4001) returns the default
438        printer URI and attributes.
439
[1037115]440        Request
441        -------
442
443        Group 1: Operation Attributes
444            REQUIRED 'attributes-charset'
445            REQUIRED 'attributes-natural-language'
446            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
447
448        Response
449        --------
450
451        Group 1: Operation Attributes
452            OPTIONAL 'status-message' (text(255))
453            REQUIRED 'attributes-charset'
454            REQUIRED 'attributes-natural-language'
455        Group 2: Printer Object Attributes
456
[0ede474]457        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_DEFAULT )
458
459        """
[287d6ec]460
[1037115]461        operation = request.attribute_groups[0]
462        printer = self.printers[self.default]
463
464        # get attributes from the printer and add to response
465        response.attribute_groups.append(ipp.AttributeGroup(
[ee8e6d0]466            ipp.AttributeTags.PRINTER, printer.get_printer_attributes(operation)))
[0ede474]467
[793432f]468    @handler_for(ipp.OperationCodes.CUPS_GET_PRINTERS)
[1037115]469    def cups_get_printers(self, request, response):
[ef8df33]470        """The CUPS-Get-Printers operation (0x4002) returns the
471        printer attributes for every printer known to the system. This
472        may include printers that are not served directly by the
473        server.
[0ede474]474
475        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_PRINTERS )
476           
477        """
478
[1037115]479        operation = request.attribute_groups[0]
480
481        # get attributes from the printer and add to response
482        for printer in self.printers.values():
483            response.attribute_groups.append(ipp.AttributeGroup(
[ee8e6d0]484                ipp.AttributeTags.PRINTER, printer.get_printer_attributes(operation)))
[287d6ec]485
[793432f]486    @handler_for(ipp.OperationCodes.CUPS_GET_CLASSES)
[1037115]487    def cups_get_classes(self, request, response):
[0ede474]488        """The CUPS-Get-Classes operation (0x4005) returns the printer
489        attributes for every printer class known to the system. This
490        may include printer classes that are not served directly by
491        the server.
[1037115]492
493        Request
494        -------
495
496        Group 1: Operation Attributes
497            REQUIRED 'attributes-charset'
498            REQUIRED 'attributes-natural-language'
499            OPTIONAL 'first-printer-name' (name(127)) CUPS 1.2/Mac OS X 10.5
500            OPTIONAL 'limit' (integer (1:MAX))
501            OPTIONAL 'printer-location' (text(127)) CUPS 1.1.7
502            OPTIONAL 'printer-type' (type2 enum) CUPS 1.1.7
503            OPTIONAL 'printer-type-mask' (type2 enum) CUPS 1.1.7
504            OPTIONAL 'requested-attributes' (1setOf keyword)
505            OPTOINAL 'requested-user-name' (name(127)) CUPS 1.2/Mac OS X 10.5
506            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
507
508        Response
509        --------
510
511        Group 1: Operation Attributes
512            OPTIONAL 'status-message' (text(255))
513            REQUIRED 'attributes-charset'
514            REQUIRED 'attributes-natural-language'
515        Group 2: Printer Class Object Attributes
516
[0ede474]517        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_CLASSES )
518
519        """
[ef8df33]520
[1037115]521        raise ipp.errors.ServerErrorOperationNotSupported
522
Note: See TracBrowser for help on using the repository browser.