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

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

Get rid of individual operations files -- too messy. Go back to having a single requests file for handling requests.

  • Property mode set to 100644
File size: 20.0 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("Handling request of type '%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("Received 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        # requesting username
230        if 'requesting-user-name' in operation:
231            username_attr = operation['requesting-user-name']
232            username = username_attr.values[0].value
233            if username_attr != ipp.RequestingUserName(username):
234                raise ipp.errors.ClientErrorBadRequest(str(username_attr))
235
236        # get the job attributes and add them to the response
237        for job in self.printers[printer_name].get_jobs():
238            attrs = job.get_job_attributes(request)
239            response.attribute_groups.append(ipp.AttributeGroup(
240                ipp.AttributeTags.JOB, attrs))
241
242    @handler_for(ipp.OperationCodes.PRINT_URI)
243    def print_uri(self, request, response):
244        raise ipp.errors.ServerErrorOperationNotSupported
245
246    @handler_for(ipp.OperationCodes.CREATE_JOB)
247    def create_job(self, request, response):
248        """RFC 2911: 3.2.4 Create-Job Operation
249
250        This OPTIONAL operation is similar to the Print-Job operation
251        (section 3.2.1) except that in the Create-Job request, a
252        client does not supply document data or any reference to
253        document data. Also, the client does not supply any of the
254        'document-name', 'document- format', 'compression', or
255        'document-natural-language' operation attributes. This
256        operation is followed by one or more Send-Document or Send-URI
257        operations. In each of those operation requests, the client
258        OPTIONALLY supplies the 'document-name', 'document-format',
259        and 'document-natural-language' attributes for each document
260        in the multi-document Job object.
261
262        Group 1: Operation Attributes
263       
264        """
265
266        raise ipp.errors.ServerErrorOperationNotSupported
267   
268    @handler_for(ipp.OperationCodes.PAUSE_PRINTER)
269    def pause_printer(self, request, response):
270        raise ipp.errors.ServerErrorOperationNotSupported
271
272    @handler_for(ipp.OperationCodes.RESUME_PRINTER)
273    def resume_printer(self, request, response):
274        raise ipp.errors.ServerErrorOperationNotSupported
275
276    @handler_for(ipp.OperationCodes.GET_PRINTER_ATTRIBUTES)
277    def get_printer_attributes(self, request, response):
278        """RFC 2911: 3.2.5 Get-Printer-Attributes Operation
279
280        This REQUIRED operation allows a client to request the values
281        of the attributes of a Printer object.
282       
283        In the request, the client supplies the set of Printer
284        attribute names and/or attribute group names in which the
285        requester is interested. In the response, the Printer object
286        returns a corresponding attribute set with the appropriate
287        attribute values filled in.
288
289        Request
290        -------
291
292        Group 1: Operation Attributes
293            REQUIRED 'attributes-charset'
294            REQUIRED 'attributes-natural-language'
295            REQUIRED 'printer-uri' (uri)
296            OPTIONAL 'requesting-user-name' (name(MAX))
297            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
298            OPTIONAL 'document-format' (mimeMediaType):
299
300        Response
301        --------
302
303        Group 1: Operation Attributes
304            OPTIONAL 'status-message' (text(255))
305            OPTIONAL 'detailed-status-message' (text(MAX))
306            REQUIRED 'attributes-charset'
307            REQUIRED 'attributes-natural-language'
308        Group 2: Unsupported Attributes
309        Group 3: Printer Object Attributes
310
311        """
312
313        operation = request.attribute_groups[0]
314
315        # requested printer uri
316        if 'printer-uri' not in operation:
317            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
318        uri_attr = operation['printer-uri']
319        printer_name = uri_attr.values[0].value.split("/")[-1]
320        if uri_attr != ipp.PrinterUri(uri_attr.values[0].value):
321            raise ipp.errors.ClientErrorBadRequest(str(uri_attr))
322        if printer_name not in self.printers:
323            raise ipp.errors.ClientErrorAttributes(str(uri_attr), uri_attr)
324        printer = self.printers[printer_name]
325
326        # requesting username
327        if 'requesting-user-name' in operation:
328            username_attr = operation['requesting-user-name']
329            username = username_attr.values[0].value
330            if username_attr != ipp.RequestingUserName(username):
331                raise ipp.errors.ClientErrorBadRequest(str(username_attr))
332
333        # get attributes from the printer and add to response
334        response.attribute_groups.append(ipp.AttributeGroup(
335            ipp.AttributeTags.PRINTER, printer.get_printer_attributes(request)))
336
337    @handler_for(ipp.OperationCodes.SET_PRINTER_ATTRIBUTES)
338    def set_printer_attributes(self, request, response):
339        raise ipp.errors.ServerErrorOperationNotSupported
340
341    ##### Job Commands
342
343    @handler_for(ipp.OperationCodes.CANCEL_JOB)
344    def cancel_job(self, request, response):
345        raise ipp.errors.ServerErrorOperationNotSupported
346
347    @handler_for(ipp.OperationCodes.SEND_DOCUMENT)
348    def send_document(self, request, response):
349        raise ipp.errors.ServerErrorOperationNotSupported
350
351    @handler_for(ipp.OperationCodes.SEND_URI)
352    def send_uri(self, request, response):
353        raise ipp.errors.ServerErrorOperationNotSupported
354
355    @handler_for(ipp.OperationCodes.GET_JOB_ATTRIBUTES)
356    def get_job_attributes(self, request, response):
357       
358        operation = request.attribute_groups[0]
359
360        # requested printer uri
361        if 'printer-uri' not in operation:
362            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
363        uri_attr = operation['printer-uri']
364        printer_name = uri_attr.values[0].value.split("/")[-1]
365        if uri_attr != ipp.PrinterUri(uri_attr.values[0].value):
366            raise ipp.errors.ClientErrorBadRequest(str(uri_attr))
367        if printer_name not in self.printers:
368            raise ipp.errors.ClientErrorAttributes(str(uri_attr), uri_attr)
369        printer = self.printers[printer_name]
370
371        if 'job-id' not in operation:
372            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
373        job_id_attr = operation['job-id']
374        job_id = job_id_attr.values[0].value
375        if job_id_attr != ipp.JobId(job_id_attr.values[0].value):
376            raise ipp.errors.ClientErrorBadRequest(str(job_id_attr))
377        if job_id not in printer.jobs:
378            raise ipp.errors.ClientErrorAttributes(str(job_id_attr))
379        job = printer.get_job(job_id)
380
381        # requesting username
382        if 'requesting-user-name' in operation:
383            username_attr = operation['requesting-user-name']
384            username = username_attr.values[0].value
385            if username_attr != ipp.RequestingUserName(username):
386                raise ipp.errors.ClientErrorBadRequest(str(username_attr))
387
388        # get the job attributes and add them to the response
389        attrs = job.get_job_attributes(request)
390        response.attribute_groups.append(ipp.AttributeGroup(
391            ipp.AttributeTags.JOB, attrs))
392
393    @handler_for(ipp.OperationCodes.SET_JOB_ATTRIBUTES)
394    def set_job_attributes(self, request, response):
395        raise ipp.errors.ServerErrorOperationNotSupported
396
397    @handler_for(ipp.OperationCodes.RESTART_JOB)
398    def restart_job(self, request, response):
399        raise ipp.errors.ServerErrorOperationNotSupported
400
401    @handler_for(ipp.OperationCodes.PROMOTE_JOB)
402    def promote_job(self, request, response):
403        raise ipp.errors.ServerErrorOperationNotSupported
404
405    ##### CUPS Specific Commands
406
407    @handler_for(ipp.OperationCodes.CUPS_GET_DOCUMENT)
408    def cups_get_document(self, request, response):
409        raise ipp.errors.ServerErrorOperationNotSupported
410
411    @handler_for(ipp.OperationCodes.CUPS_GET_DEFAULT)
412    def cups_get_default(self, request, response):
413        """The CUPS-Get-Default operation (0x4001) returns the default
414        printer URI and attributes.
415
416        Request
417        -------
418
419        Group 1: Operation Attributes
420            REQUIRED 'attributes-charset'
421            REQUIRED 'attributes-natural-language'
422            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
423
424        Response
425        --------
426
427        Group 1: Operation Attributes
428            OPTIONAL 'status-message' (text(255))
429            REQUIRED 'attributes-charset'
430            REQUIRED 'attributes-natural-language'
431        Group 2: Printer Object Attributes
432
433        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_DEFAULT )
434
435        """
436
437        operation = request.attribute_groups[0]
438        printer = self.printers[self.default]
439
440        # requesting username
441        if 'requesting-user-name' in operation:
442            username_attr = operation['requesting-user-name']
443            username = username_attr.values[0].value
444            if username_attr != ipp.RequestingUserName(username):
445                raise ipp.errors.ClientErrorBadRequest(str(username_attr))
446
447        # get attributes from the printer and add to response
448        response.attribute_groups.append(ipp.AttributeGroup(
449            ipp.AttributeTags.PRINTER, printer.get_printer_attributes(request)))
450
451    @handler_for(ipp.OperationCodes.CUPS_GET_PRINTERS)
452    def cups_get_printers(self, request, response):
453        """The CUPS-Get-Printers operation (0x4002) returns the
454        printer attributes for every printer known to the system. This
455        may include printers that are not served directly by the
456        server.
457
458        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_PRINTERS )
459           
460        """
461
462        operation = request.attribute_groups[0]
463
464        # requesting username
465        if 'requesting-user-name' in operation:
466            username_attr = operation['requesting-user-name']
467            username = username_attr.values[0].value
468            if username_attr != ipp.RequestingUserName(username):
469                raise ipp.errors.ClientErrorBadRequest(str(username_attr))
470
471        # get attributes from the printer and add to response
472        for printer in self.printers.values():
473            response.attribute_groups.append(ipp.AttributeGroup(
474                ipp.AttributeTags.PRINTER, printer.get_printer_attributes(request)))
475
476    @handler_for(ipp.OperationCodes.CUPS_GET_CLASSES)
477    def cups_get_classes(self, request, response):
478        """The CUPS-Get-Classes operation (0x4005) returns the printer
479        attributes for every printer class known to the system. This
480        may include printer classes that are not served directly by
481        the server.
482
483        Request
484        -------
485
486        Group 1: Operation Attributes
487            REQUIRED 'attributes-charset'
488            REQUIRED 'attributes-natural-language'
489            OPTIONAL 'first-printer-name' (name(127)) CUPS 1.2/Mac OS X 10.5
490            OPTIONAL 'limit' (integer (1:MAX))
491            OPTIONAL 'printer-location' (text(127)) CUPS 1.1.7
492            OPTIONAL 'printer-type' (type2 enum) CUPS 1.1.7
493            OPTIONAL 'printer-type-mask' (type2 enum) CUPS 1.1.7
494            OPTIONAL 'requested-attributes' (1setOf keyword)
495            OPTOINAL 'requested-user-name' (name(127)) CUPS 1.2/Mac OS X 10.5
496            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
497
498        Response
499        --------
500
501        Group 1: Operation Attributes
502            OPTIONAL 'status-message' (text(255))
503            REQUIRED 'attributes-charset'
504            REQUIRED 'attributes-natural-language'
505        Group 2: Printer Class Object Attributes
506
507        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_CLASSES )
508
509        """
510
511        raise ipp.errors.ServerErrorOperationNotSupported
512
Note: See TracBrowser for help on using the repository browser.