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