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

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

Implement send-document operation and add threading so that the gutenbach server can play jobs

  • 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
284        operation = request.attribute_groups[0]
285
286        # requested printer uri
287        if 'printer-uri' not in operation:
288            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
289        uri_attr = operation['printer-uri']
290        printer_name = uri_attr.values[0].value.split("/")[-1]
291        if uri_attr != ipp.PrinterUri(uri_attr.values[0].value):
292            raise ipp.errors.ClientErrorBadRequest(str(uri_attr))
293        if printer_name != self.printer.name:
294            raise ipp.errors.ClientErrorAttributes(str(uri_attr), uri_attr)
295
296        # get attributes from the printer and add to response
297        job = self.printer.create_job(request)
298        response.attribute_groups.append(ipp.AttributeGroup(
299            ipp.AttributeTags.JOB, job.get_job_attributes(operation)))
300   
301    @handler_for(ipp.OperationCodes.PAUSE_PRINTER)
302    def pause_printer(self, request, response):
303        raise ipp.errors.ServerErrorOperationNotSupported
304
305    @handler_for(ipp.OperationCodes.RESUME_PRINTER)
306    def resume_printer(self, request, response):
307        raise ipp.errors.ServerErrorOperationNotSupported
308
309    @handler_for(ipp.OperationCodes.GET_PRINTER_ATTRIBUTES)
310    def get_printer_attributes(self, request, response):
311        """RFC 2911: 3.2.5 Get-Printer-Attributes Operation
312
313        This REQUIRED operation allows a client to request the values
314        of the attributes of a Printer object.
315       
316        In the request, the client supplies the set of Printer
317        attribute names and/or attribute group names in which the
318        requester is interested. In the response, the Printer object
319        returns a corresponding attribute set with the appropriate
320        attribute values filled in.
321
322        Request
323        -------
324
325        Group 1: Operation Attributes
326            REQUIRED 'attributes-charset'
327            REQUIRED 'attributes-natural-language'
328            REQUIRED 'printer-uri' (uri)
329            OPTIONAL 'requesting-user-name' (name(MAX))
330            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
331            OPTIONAL 'document-format' (mimeMediaType)
332           
333        Response
334        --------
335
336        Group 1: Operation Attributes
337            OPTIONAL 'status-message' (text(255))
338            OPTIONAL 'detailed-status-message' (text(MAX))
339            REQUIRED 'attributes-charset'
340            REQUIRED 'attributes-natural-language'
341        Group 2: Unsupported Attributes
342        Group 3: Printer Object Attributes
343
344        """
345
346        operation = request.attribute_groups[0]
347
348        # requested printer uri
349        if 'printer-uri' not in operation:
350            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
351        uri_attr = operation['printer-uri']
352        printer_name = uri_attr.values[0].value.split("/")[-1]
353        if uri_attr != ipp.PrinterUri(uri_attr.values[0].value):
354            raise ipp.errors.ClientErrorBadRequest(str(uri_attr))
355        if printer_name != self.printer.name:
356            raise ipp.errors.ClientErrorAttributes(str(uri_attr), uri_attr)
357        printer = self.printer
358
359        # get attributes from the printer and add to response
360        response.attribute_groups.append(ipp.AttributeGroup(
361            ipp.AttributeTags.PRINTER, printer.get_printer_attributes(operation)))
362
363    @handler_for(ipp.OperationCodes.SET_PRINTER_ATTRIBUTES)
364    def set_printer_attributes(self, request, response):
365        raise ipp.errors.ServerErrorOperationNotSupported
366
367    ##### Job Commands
368
369    @handler_for(ipp.OperationCodes.CANCEL_JOB)
370    def cancel_job(self, request, response):
371        raise ipp.errors.ServerErrorOperationNotSupported
372
373    @handler_for(ipp.OperationCodes.SEND_DOCUMENT)
374    def send_document(self, request, response):
375        operation = request.attribute_groups[0]
376
377        # requested printer uri
378        if 'printer-uri' in operation:
379            uri_attr = operation['printer-uri']
380            printer_name = uri_attr.values[0].value.split("/")[-1]
381            if uri_attr != ipp.PrinterUri(uri_attr.values[0].value):
382                raise ipp.errors.ClientErrorBadRequest(str(uri_attr))
383            if printer_name != self.printer.name:
384                raise ipp.errors.ClientErrorAttributes(str(uri_attr), uri_attr)
385        printer = self.printer
386
387        if 'job-id' not in operation:
388            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
389        job_id_attr = operation['job-id']
390        job_id = job_id_attr.values[0].value
391        if job_id_attr != ipp.JobId(job_id_attr.values[0].value):
392            raise ipp.errors.ClientErrorBadRequest(str(job_id_attr))
393        if job_id not in printer.jobs:
394            raise ipp.errors.ClientErrorAttributes(str(job_id_attr))
395        job = printer.jobs[job_id]
396
397        if 'last-document' not in operation:
398            raise ipp.errors.ClientErrorBadRequest("Missing 'last-document' attribute")
399        last_attr = operation['last-document']
400        last = last_attr.values[0].value
401        if last_attr != ipp.LastDocument(last):
402            raise ipp.errors.ClientErrorBadRequest(str(last_attr))
403        if not last:
404            raise ipp.errors.ServerErrorMultipleJobsNotSupported
405
406        printer.send_document(job_id, request.data)
407        attrs = job.get_job_attributes()
408        response.attribute_groups.append(ipp.AttributeGroup(
409            ipp.AttributeTags.JOB, attrs))
410
411    @handler_for(ipp.OperationCodes.SEND_URI)
412    def send_uri(self, request, response):
413        raise ipp.errors.ServerErrorOperationNotSupported
414
415    @handler_for(ipp.OperationCodes.GET_JOB_ATTRIBUTES)
416    def get_job_attributes(self, request, response):
417        operation = request.attribute_groups[0]
418
419        # requested printer uri
420        if 'printer-uri' not in operation:
421            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
422        uri_attr = operation['printer-uri']
423        printer_name = uri_attr.values[0].value.split("/")[-1]
424        if uri_attr != ipp.PrinterUri(uri_attr.values[0].value):
425            raise ipp.errors.ClientErrorBadRequest(str(uri_attr))
426        if printer_name != self.printer.name:
427            raise ipp.errors.ClientErrorAttributes(str(uri_attr), uri_attr)
428        printer = self.printer
429
430        if 'job-id' not in operation:
431            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
432        job_id_attr = operation['job-id']
433        job_id = job_id_attr.values[0].value
434        if job_id_attr != ipp.JobId(job_id_attr.values[0].value):
435            raise ipp.errors.ClientErrorBadRequest(str(job_id_attr))
436        if job_id not in printer.jobs:
437            raise ipp.errors.ClientErrorAttributes(str(job_id_attr))
438        job = printer.get_job(job_id)
439
440        # get the job attributes and add them to the response
441        attrs = job.get_job_attributes(operation)
442        response.attribute_groups.append(ipp.AttributeGroup(
443            ipp.AttributeTags.JOB, attrs))
444
445    @handler_for(ipp.OperationCodes.SET_JOB_ATTRIBUTES)
446    def set_job_attributes(self, request, response):
447        raise ipp.errors.ServerErrorOperationNotSupported
448
449    @handler_for(ipp.OperationCodes.RESTART_JOB)
450    def restart_job(self, request, response):
451        raise ipp.errors.ServerErrorOperationNotSupported
452
453    @handler_for(ipp.OperationCodes.PROMOTE_JOB)
454    def promote_job(self, request, response):
455        raise ipp.errors.ServerErrorOperationNotSupported
456
457    ##### CUPS Specific Commands
458
459    @handler_for(ipp.OperationCodes.CUPS_GET_DOCUMENT)
460    def cups_get_document(self, request, response):
461        raise ipp.errors.ServerErrorOperationNotSupported
462
463    @handler_for(ipp.OperationCodes.CUPS_GET_DEFAULT)
464    def cups_get_default(self, request, response):
465        """The CUPS-Get-Default operation (0x4001) returns the default
466        printer URI and attributes.
467
468        Request
469        -------
470
471        Group 1: Operation Attributes
472            REQUIRED 'attributes-charset'
473            REQUIRED 'attributes-natural-language'
474            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
475
476        Response
477        --------
478
479        Group 1: Operation Attributes
480            OPTIONAL 'status-message' (text(255))
481            REQUIRED 'attributes-charset'
482            REQUIRED 'attributes-natural-language'
483        Group 2: Printer Object Attributes
484
485        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_DEFAULT )
486
487        """
488
489        operation = request.attribute_groups[0]
490        printer = self.printer
491
492        # get attributes from the printer and add to response
493        response.attribute_groups.append(ipp.AttributeGroup(
494            ipp.AttributeTags.PRINTER, printer.get_printer_attributes(operation)))
495
496    @handler_for(ipp.OperationCodes.CUPS_GET_PRINTERS)
497    def cups_get_printers(self, request, response):
498        """The CUPS-Get-Printers operation (0x4002) returns the
499        printer attributes for every printer known to the system. This
500        may include printers that are not served directly by the
501        server.
502
503        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_PRINTERS )
504           
505        """
506
507        operation = request.attribute_groups[0]
508
509        # get attributes from the printer and add to response
510        response.attribute_groups.append(ipp.AttributeGroup(
511            ipp.AttributeTags.PRINTER, self.printer.get_printer_attributes(operation)))
512
513    @handler_for(ipp.OperationCodes.CUPS_GET_CLASSES)
514    def cups_get_classes(self, request, response):
515        """The CUPS-Get-Classes operation (0x4005) returns the printer
516        attributes for every printer class known to the system. This
517        may include printer classes that are not served directly by
518        the server.
519
520        Request
521        -------
522
523        Group 1: Operation Attributes
524            REQUIRED 'attributes-charset'
525            REQUIRED 'attributes-natural-language'
526            OPTIONAL 'first-printer-name' (name(127)) CUPS 1.2/Mac OS X 10.5
527            OPTIONAL 'limit' (integer (1:MAX))
528            OPTIONAL 'printer-location' (text(127)) CUPS 1.1.7
529            OPTIONAL 'printer-type' (type2 enum) CUPS 1.1.7
530            OPTIONAL 'printer-type-mask' (type2 enum) CUPS 1.1.7
531            OPTIONAL 'requested-attributes' (1setOf keyword)
532            OPTOINAL 'requested-user-name' (name(127)) CUPS 1.2/Mac OS X 10.5
533            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
534
535        Response
536        --------
537
538        Group 1: Operation Attributes
539            OPTIONAL 'status-message' (text(255))
540            REQUIRED 'attributes-charset'
541            REQUIRED 'attributes-natural-language'
542        Group 2: Printer Class Object Attributes
543
544        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_CLASSES )
545
546        """
547
548        raise ipp.errors.ServerErrorOperationNotSupported
549
Note: See TracBrowser for help on using the repository browser.