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