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
Line 
1from gutenbach.server.printer import GutenbachPrinter
2import gutenbach.ipp as ipp
3import logging
4import traceback
5import sys
6
7# initialize logger
8logger = logging.getLogger(__name__)
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
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
40class GutenbachRequestHandler(object):
41
42    def __init__(self):
43        print "init"
44        self.printers = {
45            "test": GutenbachPrinter(name="test")
46            }
47        self.default = "test"
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))
75   
76    def handle(self, request):
77        # look up the handler
78        handler = None
79        handler_name = None
80        for d in dir(self):
81            if getattr(getattr(self, d), "ipp_operation", None) == request.operation_id:
82                handler_name = d
83                break
84
85        # we couldn't find a handler, so default to unknown operation
86        if handler_name is None:
87            handler_name = "unknown_operation"
88
89        # actually get the handler
90        handler = getattr(self, handler_name)
91        logger.info("Handling request of type '%s'" % handler_name)
92
93        # try to handle the request
94        try:
95            self.generic_handle(request)
96            response = make_empty_response(request)
97            handler(request, response)
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()
104            logger.error("%s: %s" % (exctype.__name__, excval.message))
105            response = make_empty_response(request)
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())
112            response = make_empty_response(request)
113            response.operation_id = ipp.StatusCodes.INTERNAL_ERROR
114
115        return response
116
117    def unknown_operation(self, request, response):
118        logger.warning("Received unknown operation 0x%x" % request.operation_id)
119        response = make_empty_response(request)
120        response.operation_id = ipp.StatusCodes.OPERATION_NOT_SUPPORTED
121        return response
122       
123    ##### Printer Commands
124
125    @handler_for(ipp.OperationCodes.PRINT_JOB)
126    def print_job(self, request, response):
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
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
170        """
171       
172        raise ipp.errors.ServerErrorOperationNotSupported
173
174    @handler_for(ipp.OperationCodes.VALIDATE_JOB)
175    def validate_job(self, request, response):
176
177        raise ipp.errors.ServerErrorOperationNotSupported
178
179    @handler_for(ipp.OperationCodes.GET_JOBS)
180    def get_jobs(self, request, response):
181        """3.2.6 Get-Jobs Operation
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
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
215        """
216
217        operation = request.attribute_groups[0]
218
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))
226        if printer_name not in self.printers:
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():
232            attrs = job.get_job_attributes(operation)
233            response.attribute_groups.append(ipp.AttributeGroup(
234                ipp.AttributeTags.JOB, attrs))
235
236    @handler_for(ipp.OperationCodes.PRINT_URI)
237    def print_uri(self, request, response):
238        raise ipp.errors.ServerErrorOperationNotSupported
239
240    @handler_for(ipp.OperationCodes.CREATE_JOB)
241    def create_job(self, request, response):
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
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        --------
272        Group 1: Operation Attributes
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))
285       
286        """
287
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)))
305   
306    @handler_for(ipp.OperationCodes.PAUSE_PRINTER)
307    def pause_printer(self, request, response):
308        raise ipp.errors.ServerErrorOperationNotSupported
309
310    @handler_for(ipp.OperationCodes.RESUME_PRINTER)
311    def resume_printer(self, request, response):
312        raise ipp.errors.ServerErrorOperationNotSupported
313
314    @handler_for(ipp.OperationCodes.GET_PRINTER_ATTRIBUTES)
315    def get_printer_attributes(self, request, response):
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
327        Request
328        -------
329
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)
336            OPTIONAL 'document-format' (mimeMediaType)
337           
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
348
349        """
350
351        operation = request.attribute_groups[0]
352
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))
360        if printer_name not in self.printers:
361            raise ipp.errors.ClientErrorAttributes(str(uri_attr), uri_attr)
362        printer = self.printers[printer_name]
363
364        # get attributes from the printer and add to response
365        response.attribute_groups.append(ipp.AttributeGroup(
366            ipp.AttributeTags.PRINTER, printer.get_printer_attributes(operation)))
367
368    @handler_for(ipp.OperationCodes.SET_PRINTER_ATTRIBUTES)
369    def set_printer_attributes(self, request, response):
370        raise ipp.errors.ServerErrorOperationNotSupported
371
372    ##### Job Commands
373
374    @handler_for(ipp.OperationCodes.CANCEL_JOB)
375    def cancel_job(self, request, response):
376        raise ipp.errors.ServerErrorOperationNotSupported
377
378    @handler_for(ipp.OperationCodes.SEND_DOCUMENT)
379    def send_document(self, request, response):
380        raise ipp.errors.ServerErrorOperationNotSupported
381
382    @handler_for(ipp.OperationCodes.SEND_URI)
383    def send_uri(self, request, response):
384        raise ipp.errors.ServerErrorOperationNotSupported
385
386    @handler_for(ipp.OperationCodes.GET_JOB_ATTRIBUTES)
387    def get_job_attributes(self, request, response):
388       
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))
398        if printer_name not in self.printers:
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
413        attrs = job.get_job_attributes(operation)
414        response.attribute_groups.append(ipp.AttributeGroup(
415            ipp.AttributeTags.JOB, attrs))
416
417    @handler_for(ipp.OperationCodes.SET_JOB_ATTRIBUTES)
418    def set_job_attributes(self, request, response):
419        raise ipp.errors.ServerErrorOperationNotSupported
420
421    @handler_for(ipp.OperationCodes.RESTART_JOB)
422    def restart_job(self, request, response):
423        raise ipp.errors.ServerErrorOperationNotSupported
424
425    @handler_for(ipp.OperationCodes.PROMOTE_JOB)
426    def promote_job(self, request, response):
427        raise ipp.errors.ServerErrorOperationNotSupported
428
429    ##### CUPS Specific Commands
430
431    @handler_for(ipp.OperationCodes.CUPS_GET_DOCUMENT)
432    def cups_get_document(self, request, response):
433        raise ipp.errors.ServerErrorOperationNotSupported
434
435    @handler_for(ipp.OperationCodes.CUPS_GET_DEFAULT)
436    def cups_get_default(self, request, response):
437        """The CUPS-Get-Default operation (0x4001) returns the default
438        printer URI and attributes.
439
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
457        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_DEFAULT )
458
459        """
460
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(
466            ipp.AttributeTags.PRINTER, printer.get_printer_attributes(operation)))
467
468    @handler_for(ipp.OperationCodes.CUPS_GET_PRINTERS)
469    def cups_get_printers(self, request, response):
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.
474
475        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_PRINTERS )
476           
477        """
478
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(
484                ipp.AttributeTags.PRINTER, printer.get_printer_attributes(operation)))
485
486    @handler_for(ipp.OperationCodes.CUPS_GET_CLASSES)
487    def cups_get_classes(self, request, response):
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.
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
517        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_CLASSES )
518
519        """
520
521        raise ipp.errors.ServerErrorOperationNotSupported
522
Note: See TracBrowser for help on using the repository browser.