source: server/lib/gutenbach/server/requests.py @ 8d89b3d

no-cups
Last change on this file since 8d89b3d was 8d89b3d, checked in by Daniel Cooper <danny@…>, 12 years ago

added comments from the RFC to several functions

  • Property mode set to 100644
File size: 47.0 KB
Line 
1from . import InvalidJobException, InvalidPrinterStateException, InvalidJobStateException
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
40def verify_attribute(attr, cls, length=1):
41    vals = [val.value for val in attr.values]
42    if attr != cls(*vals):
43        raise ipp.errors.ClientErrorBadRequest(str(attr))
44    if length is not None and len(vals) != length:
45        raise ipp.errors.ClientErrorBadRequest(str(attr))
46    return vals
47
48class GutenbachRequestHandler(object):
49
50    def __init__(self, gutenbach_printer):
51        self.printer = gutenbach_printer
52
53    def generic_handle(self, request):
54        # check the IPP version number
55        if request.version != (1, 1):
56            raise ipp.errors.ServerErrorVersionNotSupported(str(request.version))
57
58        # make sure the operation attribute group has the correct tag
59        operation = request.attribute_groups[0]
60        if operation.tag != ipp.AttributeTags.OPERATION:
61            raise ipp.errors.ClientErrorBadRequest(
62                "Attribute group does not have OPERATION tag: 0x%x" % operation.tag)
63
64        # check charset
65        charset_attr = operation.attributes[0]
66        charset = verify_attribute(charset_attr, ipp.AttributesCharset)[0]
67        if charset != 'utf-8':
68            raise ipp.errors.ClientErrorAttributes(str(charset_attr))
69
70        # check for attributes-natural-language
71        natlang_attr = operation.attributes[1]
72        natlang = verify_attribute(natlang_attr, ipp.AttributesNaturalLanguage)[0]
73        if natlang != 'en-us':
74            raise ipp.errors.ClientErrorAttributes(str(natlang_attr))
75   
76    def handle(self, request):
77        # look up the handler
78        handler = None
79        handler_name = None
80        for d in dir(self):
81            if getattr(getattr(self, d), "ipp_operation", None) == request.operation_id:
82                handler_name = d
83                break
84
85        # we couldn't find a handler, so default to unknown operation
86        if handler_name is None:
87            handler_name = "unknown_operation"
88
89        # actually get the handler
90        handler = getattr(self, handler_name)
91        logger.info("request is '%s'" % handler_name)
92
93        # try to handle the request
94        try:
95            self.generic_handle(request)
96            response = make_empty_response(request)
97            handler(request, response)
98
99        # Handle any errors that occur.  If an exception occurs that
100        # is an IPP error, then we can get the error code from the
101        # exception itself.
102        except ipp.errors.IPPException:
103            exctype, excval, exctb = sys.exc_info()
104            logger.error("%s: %s" % (exctype.__name__, excval.message))
105            response = make_empty_response(request)
106            excval.update_response(response)
107
108        # If it wasn't an IPP error, then it's our fault, so mark it
109        # as an internal server error
110        except Exception:
111            logger.error(traceback.format_exc())
112            response = make_empty_response(request)
113            response.operation_id = ipp.StatusCodes.INTERNAL_ERROR
114
115        return response
116
117    def unknown_operation(self, request, response):
118        logger.warning("unknown operation 0x%x" % request.operation_id)
119        response = make_empty_response(request)
120        response.operation_id = ipp.StatusCodes.OPERATION_NOT_SUPPORTED
121        return response
122       
123    ##### Printer Commands
124
125    @handler_for(ipp.OperationCodes.PRINT_JOB)
126    def print_job(self, request, response):
127        """RFC 2911: 3.2.1 Print-Job Operation
128
129        This REQUIRED operation allows a client to submit a print job
130        with only one document and supply the document data (rather
131        than just a reference to the data). See Section 15 for the
132        suggested steps for processing create operations and their
133        Operation and Job Template attributes.
134
135        Request
136        -------
137        Group 1: Operation Attributes
138            REQUIRED 'attributes-charset'
139            REQUIRED 'attributes-natural-language'
140            REQUIRED 'printer-uri' (uri)
141            OPTIONAL 'requesting-user-name' (name(MAX))
142            OPTIONAL 'job-name' (name(MAX))
143            OPTIONAL 'ipp-attribute-fidelity' (boolean)
144            OPTIONAL 'document-name' (name(MAX))
145            OPTIONAL 'compression' (type3 keyword)
146            OPTIONAL 'document-format' (mimeMediaType)
147            OPTIONAL 'document-natural-language' (naturalLanguage)
148            OPTIONAL 'job-k-octets' (integer(0:MAX))
149            OPTIONAL 'job-impressions' (integer(0:MAX))
150            OPTIONAL 'job-media-sheets' (integer(0:MAX))
151        Group 2: Job Template Attributes
152        Group 3: Document Content
153
154        Response
155        --------
156        Group 1: Operation Attributes
157            OPTIONAL 'status-message' (text(255))
158            OPTIONAL 'detailed-status-message' (text(MAX))
159            REQUIRED 'attributes-charset'
160            REQUIRED 'attributes-natural-language'
161        Group 2: Unsupported Attributes
162        Group 3: Job Object Attributes
163            REQUIRED 'job-uri' (uri)
164            REQUIRED 'job-id' (integer(1:MAX))
165            REQUIRED 'job-state' (type1 enum)
166            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
167            OPTIONAL 'job-state-message' (text(MAX))
168            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
169
170        """
171       
172        raise ipp.errors.ServerErrorOperationNotSupported
173
174    @handler_for(ipp.OperationCodes.VALIDATE_JOB)
175    def validate_job(self, request, response):
176
177        raise ipp.errors.ServerErrorOperationNotSupported
178
179    @handler_for(ipp.OperationCodes.GET_JOBS)
180    def get_jobs(self, request, response):
181        """3.2.6 Get-Jobs Operation
182       
183        This REQUIRED operation allows a client to retrieve the list
184        of Job objects belonging to the target Printer object. The
185        client may also supply a list of Job attribute names and/or
186        attribute group names. A group of Job object attributes will
187        be returned for each Job object that is returned.
188
189        This operation is similar to the Get-Job-Attributes operation,
190        except that this Get-Jobs operation returns attributes from
191        possibly more than one object.
192
193        Request
194        -------
195        Group 1: Operation Attributes
196            REQUIRED 'attributes-charset'
197            REQUIRED 'attributes-natural-language'
198            REQUIRED 'printer-uri' (uri)
199            OPTIONAL 'requesting-user-name' (name(MAX))
200            OPTIONAL 'limit' (integer(1:MAX))
201            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
202            OPTIONAL 'which-jobs' (type2 keyword)
203            OPTIONAL 'my-jobs' (boolean)
204
205        Response
206        --------
207        Group 1: Operation Attributes
208            OPTIONAL 'status-message' (text(255))
209            OPTIONAL 'detailed-status-message' (text(MAX))
210            REQUIRED 'attributes-charset'
211            REQUIRED 'attributes-natural-language'
212        Group 2: Unsupported Attributes
213        Groups 3 to N: Job Object Attributes
214
215        """
216
217        operation = request.attribute_groups[0]
218
219        # initialize operation attribute variables
220        printer_name = None
221        user = None
222        limit = None
223        attributes = None
224        which_jobs = None
225        my_jobs = None
226
227        # requested printer uri
228        if 'printer-uri' not in operation:
229            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
230        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
231        if printer_uri not in self.printer.uris and printer_uri != "ipp://localhost/":
232            raise ipp.errors.ClientErrorAttributes(
233                str(operation['printer-uri']), operation['printer-uri'])
234
235        # optional attributes
236        if 'limit' in operation:
237            limit = verify_attribute(
238                operation['limit'], ipp.Limit)[0]
239           
240        if 'requested-attributes' in operation:
241            attributes = verify_attribute(
242                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
243           
244        if 'which-jobs' in operation:
245            which_jobs = verify_attribute(
246                operation['which-jobs'], ipp.WhichJobs)[0]
247           
248        if 'my-jobs' in operation:
249            my_jobs = verify_attribute(
250                operation['my-jobs'], ipp.MyJobs)[0]
251
252        if 'requesting-user-name' in operation:
253            user = verify_attribute(
254                operation['requesting-user-name'], ipp.RequestingUserName)[0]
255            # ignore if we're not filtering jobs by user
256            if not my_jobs:
257                user = None
258           
259        # get the job attributes and add them to the response
260        job_attrs = self.printer.get_jobs(
261            which_jobs=which_jobs,
262            requesting_user_name=user,
263            requested_attributes=attributes)
264        for attrs in job_attrs:
265            response.attribute_groups.append(ipp.AttributeGroup(
266                ipp.AttributeTags.JOB, attrs))
267
268    @handler_for(ipp.OperationCodes.PRINT_URI)
269    def print_uri(self, request, response):
270        """3.2.2 Print-URI Operation
271
272        This OPTIONAL operation is identical to the Print-Job operation
273        (section 3.2.1) except that a client supplies a URI reference to the
274        document data using the 'document-uri' (uri) operation attribute (in
275        Group 1) rather than including the document data itself.  Before
276        returning the response, the Printer MUST validate that the Printer
277        supports the retrieval method (e.g., http, ftp, etc.) implied by the
278        URI, and MUST check for valid URI syntax.  If the client-supplied URI
279        scheme is not supported, i.e. the value is not in the Printer
280        object's 'referenced-uri-scheme-supported' attribute, the Printer
281        object MUST reject the request and return the 'client-error-uri-
282        scheme-not-supported' status code.
283
284        The IPP Printer MAY validate the accessibility of the document as
285        part of the operation or subsequently.  If the Printer determines an
286        accessibility problem before returning an operation response, it
287        rejects the request and returns the 'client-error-document-access-
288        error' status code.  The Printer MAY also return a specific document
289        access error code using the 'document-access-error' operation
290        attribute (see section 3.1.6.4).
291
292        If the Printer determines this document accessibility problem after
293        accepting the request and returning an operation response with one of
294        the successful status codes, the Printer adds the 'document-access-
295        error' value to the job's 'job-state-reasons' attribute and MAY                                                 populate the job's 'job-document-access-errors' Job Description
296        attribute (see section 4.3.11).  See The Implementer's Guide [IPP-
297        IIG] for suggested additional checks.
298                                                                             
299        If the Printer object supports this operation, it MUST support the
300        'reference-uri-schemes-supported' Printer attribute (see section 4.4.27).
301
302        It is up to the IPP object to interpret the URI and subsequently
303        'pull' the document from the source referenced by the URI string."""
304        raise ipp.errors.ServerErrorOperationNotSupported
305
306    @handler_for(ipp.OperationCodes.CREATE_JOB)
307    def create_job(self, request, response):
308        """RFC 2911: 3.2.4 Create-Job Operation
309
310        This OPTIONAL operation is similar to the Print-Job operation
311        (section 3.2.1) except that in the Create-Job request, a
312        client does not supply document data or any reference to
313        document data. Also, the client does not supply any of the
314        'document-name', 'document- format', 'compression', or
315        'document-natural-language' operation attributes. This
316        operation is followed by one or more Send-Document or Send-URI
317        operations. In each of those operation requests, the client
318        OPTIONALLY supplies the 'document-name', 'document-format',
319        and 'document-natural-language' attributes for each document
320        in the multi-document Job object.
321
322        Request
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 'job-name' (name(MAX))
330            OPTIONAL 'ipp-attribute-fidelity' (boolean)
331            OPTIONAL 'job-k-octets' (integer(0:MAX))
332            OPTIONAL 'job-impressions' (integer(0:MAX))
333            OPTIONAL 'job-media-sheets' (integer(0:MAX))
334        Group 2: Job Template Attributes
335
336        Response
337        --------
338        Group 1: Operation Attributes
339            OPTIONAL 'status-message' (text(255))
340            OPTIONAL 'detailed-status-message' (text(MAX))
341            REQUIRED 'attributes-charset'
342            REQUIRED 'attributes-natural-language'
343        Group 2: Unsupported Attributes
344        Group 3: Job Object Attributes
345            REQUIRED 'job-uri' (uri)
346            REQUIRED 'job-id' (integer(1:MAX))
347            REQUIRED 'job-state' (type1 enum)
348            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
349            OPTIONAL 'job-state-message' (text(MAX))
350            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
351       
352        """
353
354        operation = request.attribute_groups[0]
355
356        printer_uri = None
357        requesting_user_name = None
358        job_name = None
359        ipp_attribute_fidelity=None
360        job_k_octets = None
361
362        # requested printer uri
363        if 'printer-uri' not in operation:
364            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
365        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
366        if printer_uri not in self.printer.uris:
367            raise ipp.errors.ClientErrorAttributes(
368                str(operation['printer-uri']), operation['printer-uri'])
369
370        if 'requesting-user-name' in operation:
371            user_name = verify_attribute(
372                operation['requesting-user-name'], ipp.RequestingUserName)[0]
373
374        if 'job-name' in operation:
375            job_name = verify_attribute(
376                operation['job-name'], ipp.JobName)[0]
377
378        if 'job-k-octets' in operation:
379            job_k_octets = verify_attribute(
380                operation['job-k-octets'], ipp.JobKOctets)[0]
381
382        if 'ipp-attribute-fidelity' in operation:
383            pass # don't care
384        if 'job-impressions' in operation:
385            pass # don't care
386        if 'job-media-sheets' in operation:
387            pass # don't care
388
389        # get attributes from the printer and add to response
390        job_id = self.printer.create_job(
391            requesting_user_name=requesting_user_name,
392            job_name=job_name,
393            job_k_octets=job_k_octets)
394        attrs = self.printer.get_job_attributes(job_id)
395        response.attribute_groups.append(ipp.AttributeGroup(
396            ipp.AttributeTags.JOB, attrs))
397   
398    @handler_for(ipp.OperationCodes.PAUSE_PRINTER)
399    def pause_printer(self, request, response):
400        """
401            3.2.7 Pause-Printer Operation
402
403            This OPTIONAL operation allows a client to stop the Printer object
404            from scheduling jobs on all its devices.  Depending on
405            implementation, the Pause-Printer operation MAY also stop the Printer
406            from processing the current job or jobs.  Any job that is currently
407            being printed is either stopped as soon as the implementation permits
408            or is completed, depending on implementation.  The Printer object
409            MUST still accept create operations to create new jobs, but MUST
410            prevent any jobs from entering the 'processing' state.
411
412            If the Pause-Printer operation is supported, then the Resume-Printer
413            operation MUST be supported, and vice-versa.
414
415            The IPP Printer stops the current job(s) on its device(s) that were
416            in the 'processing' or 'processing-stopped' states as soon as the
417            implementation permits.  If the implementation will take appreciable
418            time to stop, the IPP Printer adds the 'moving-to-paused' value to
419            the Printer object's 'printer-state-reasons' attribute (see section
420            4.4.12).  When the device(s) have all stopped, the IPP Printer
421            transitions the Printer object to the 'stopped' state, removes the
422            'moving-to-paused' value, if present, and adds the 'paused' value to
423            the Printer object's 'printer-state-reasons' attribute.
424
425            When the current job(s) complete that were in the 'processing' state,
426            the IPP Printer transitions them to the 'completed' state.  When the
427            current job(s) stop in mid processing that were in the 'processing'
428            state, the IPP Printer transitions them to the 'processing-stopped'
429            state and adds the 'printer-stopped' value to the job's 'job-state-
430            reasons' attribute.
431
432            For any jobs that are 'pending' or 'pending-held', the 'printer-
433            stopped' value of the jobs' 'job-state-reasons' attribute also
434            applies.  However, the IPP Printer NEED NOT update those jobs' 'job-
435            state-reasons' attributes and only need return the 'printer-stopped'
436            value when those jobs are queried (so-called 'lazy evaluation').
437
438            Whether the Pause-Printer operation affects jobs that were submitted
439            to the device from other sources than the IPP Printer object in the
440            same way that the Pause-Printer operation affects jobs that were
441            submitted to the IPP Printer object using IPP, depends on
442            implementation, i.e., on whether the IPP protocol is being used as a
443            universal management protocol or just to manage IPP jobs,
444            respectively.
445
446            The IPP Printer MUST accept the request in any state and transition
447            the Printer to the indicated new 'printer-state' before returning as
448            follows:
449
450            Current        New      'printer   IPP Printer's response status
451            'printer-    'printer-   -state-          code and action:
452            state'       state'    reasons'
453
454            'idle'       'stopped'    'paused'  'successful-ok'
455            'processing' 'processing' 'moving-  OPTION 1: 'successful-ok';
456                                                      to-       Later, when all output has
457                                                      paused'   stopped, the 'printer-state'
458                                                                            becomes 'stopped', and the
459                                                                            'paused' value replaces the
460                                                                            'moving-to-paused' value in the
461                                                                            'printer-state-reasons'
462                                                                            attribute
463            'processing' 'stopped'    'paused'  OPTION 2: 'successful-ok';
464                                                                            all device output stopped
465                                                                            immediately
466            'stopped'    'stopped'    'paused'  'successful-ok'
467
468            Access Rights: The authenticated user (see section 8.3) performing
469            this operation must be an operator or administrator of the Printer
470            object (see Sections 1 and 8.5).   Otherwise, the IPP Printer MUST
471            reject the operation and return:  'client-error-forbidden', 'client-
472            error-not-authenticated', or 'client-error-not-authorized' as
473            appropriate.
474
475            3.2.7.1 Pause-Printer Request
476
477            The following groups of attributes are part of the Pause-Printer
478            Request:
479
480            Group 1: Operation Attributes
481
482            Natural Language and Character Set:
483            The 'attributes-charset' and 'attributes-natural-language'
484            attributes as described in section 3.1.4.1.
485
486            Target:
487            The 'printer-uri' (uri) operation attribute which is the target
488            for this operation as described in section 3.1.5.
489
490            Requesting User Name:
491            The 'requesting-user-name' (name(MAX)) attribute SHOULD be
492            supplied by the client as described in section 8.3.
493
494            3.2.7.2 Pause-Printer Response
495
496            The following groups of attributes are part of the Pause-Printer
497            Response:
498
499            Group 1: Operation Attributes
500
501            Status Message:
502            In addition to the REQUIRED status code returned in every
503            response, the response OPTIONALLY includes a 'status-message'
504            (text(255)) and/or a 'detailed-status-message' (text(MAX))
505            operation attribute as described in sections 13 and  3.1.6.
506
507            Natural Language and Character Set:
508            The 'attributes-charset' and 'attributes-natural-language'
509            attributes as described in section 3.1.4.2.
510
511            Group 2: Unsupported Attributes
512
513            See section 3.1.7 for details on returning Unsupported Attributes.
514
515   
516    """
517        raise ipp.errors.ServerErrorOperationNotSupported
518
519    @handler_for(ipp.OperationCodes.RESUME_PRINTER)
520    def resume_printer(self, request, response):
521        """
522        3.2.8 Resume-Printer Operation
523
524        This operation allows a client to resume the Printer object
525        scheduling jobs on all its devices.  The Printer object MUST remove
526        the 'paused' and 'moving-to-paused' values from the Printer object's
527        'printer-state-reasons' attribute, if present.  If there are no other
528        reasons to keep a device paused (such as media-jam), the IPP Printer
529        is free to transition itself to the 'processing' or 'idle' states,
530        depending on whether there are jobs to be processed or not,
531        respectively, and the device(s) resume processing jobs.
532
533        If the Pause-Printer operation is supported, then the Resume-Printer
534        operation MUST be supported, and vice-versa.
535
536        The IPP Printer removes the 'printer-stopped' value from any job's
537        'job-state-reasons' attributes contained in that Printer.
538
539        The IPP Printer MUST accept the request in any state, transition the
540        Printer object to the indicated new state as follows:
541
542
543        Current    New 'printer-  IPP Printer's response status code and
544        'printer-      state'                     action:
545        state'
546
547        'idle'       'idle'         'successful-ok'
548        'processing' 'processing'   'successful-ok'
549
550        'stopped'    'processing'   'successful-ok';
551                                                   when there are jobs to be processed
552        'stopped'    'idle'         'successful-ok';
553                                                   when there are no jobs to be processed.
554
555        Access Rights: The authenticated user (see section 8.3) performing
556        this operation must be an operator or administrator of the Printer
557        object (see Sections 1 and 8.5).  Otherwise, the IPP Printer MUST
558        reject the operation and return:  'client-error-forbidden', 'client-
559        error-not-authenticated', or 'client-error-not-authorized' as
560        appropriate.
561
562        The Resume-Printer Request and Resume-Printer Response have the same
563        attribute groups and attributes as the Pause-Printer operation (see
564        sections 3.2.7.1 and 3.2.7.2).                 
565        """
566        raise ipp.errors.ServerErrorOperationNotSupported
567
568    @handler_for(ipp.OperationCodes.GET_PRINTER_ATTRIBUTES)
569    def get_printer_attributes(self, request, response):
570        """RFC 2911: 3.2.5 Get-Printer-Attributes Operation
571
572        This REQUIRED operation allows a client to request the values
573        of the attributes of a Printer object.
574       
575        In the request, the client supplies the set of Printer
576        attribute names and/or attribute group names in which the
577        requester is interested. In the response, the Printer object
578        returns a corresponding attribute set with the appropriate
579        attribute values filled in.
580
581        Request
582        -------
583
584        Group 1: Operation Attributes
585            REQUIRED 'attributes-charset'
586            REQUIRED 'attributes-natural-language'
587            REQUIRED 'printer-uri' (uri)
588            OPTIONAL 'requesting-user-name' (name(MAX))
589            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
590            OPTIONAL 'document-format' (mimeMediaType)
591           
592        Response
593        --------
594
595        Group 1: Operation Attributes
596            OPTIONAL 'status-message' (text(255))
597            OPTIONAL 'detailed-status-message' (text(MAX))
598            REQUIRED 'attributes-charset'
599            REQUIRED 'attributes-natural-language'
600        Group 2: Unsupported Attributes
601        Group 3: Printer Object Attributes
602
603        """
604
605        operation = request.attribute_groups[0]
606
607        printer_uri = None
608        requesting_user_name = None
609        requested_attributes = None
610        document_format = None
611
612        # requested printer uri
613        if 'printer-uri' not in operation:
614            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
615        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
616        if printer_uri not in self.printer.uris:
617            raise ipp.errors.ClientErrorAttributes(
618                str(operation['printer-uri']), operation['printer-uri'])
619
620        # optional attributes
621        if 'requesting-user-name' in operation:
622            user_name = verify_attribute(
623                operation['requesting-user-name'], ipp.RequestingUserName)[0]
624           
625        if 'requested-attributes' in operation:
626            requested_attributes = verify_attribute(
627                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
628
629        if 'document-format' in operation:
630            pass # XXX: todo
631
632        # get attributes from the printer and add to response
633        response.attribute_groups.append(ipp.AttributeGroup(
634            ipp.AttributeTags.PRINTER,
635            self.printer.get_printer_attributes(
636                requested_attributes=requested_attributes)))
637
638    @handler_for(ipp.OperationCodes.SET_PRINTER_ATTRIBUTES)
639    def set_printer_attributes(self, request, response):
640
641        raise ipp.errors.ServerErrorOperationNotSupported
642
643    ##### Job Commands
644
645    @handler_for(ipp.OperationCodes.CANCEL_JOB)
646    def cancel_job(self, request, response):
647        """3.3.3 Cancel-Job Operation
648
649        This REQUIRED operation allows a client to cancel a Print Job from
650        the time the job is created up to the time it is completed, canceled,
651        or aborted. Since a Job might already be printing by the time a
652        Cancel-Job is received, some media sheet pages might be printed
653        before the job is actually terminated.
654
655        The IPP object MUST accept or reject the request based on the job's
656        current state and transition the job to the indicated new state as
657        follows:
658
659        Current State       New State           Response
660        -----------------------------------------------------------------
661        pending             canceled            successful-ok
662        pending-held        canceled            successful-ok
663        processing          canceled            successful-ok
664        processing          processing          successful-ok               See Rule 1
665        processing          processing          client-error-not-possible   See Rule 2
666        processing-stopped  canceled            successful-ok
667        processing-stopped  processing-stopped  successful-ok               See Rule 1
668        processing-stopped  processing-stopped  client-error-not-possible   See Rule 2
669        completed           completed           client-error-not-possible
670        canceled            canceled            client-error-not-possible
671        aborted             aborted             client-error-not-possible
672
673        Rule 1: If the implementation requires some measurable time to
674        cancel the job in the 'processing' or 'processing-stopped' job
675        states, the IPP object MUST add the 'processing-to-stop-point'
676        value to the job's 'job-state-reasons' attribute and then
677        transition the job to the 'canceled' state when the processing
678        ceases (see section 4.3.8).
679
680        Rule 2: If the Job object already has the
681        'processing-to-stop-point' value in its 'job-state-reasons'
682        attribute, then the Printer object MUST reject a Cancel-Job
683        operation.
684
685        Access Rights: The authenticated user (see section 8.3)
686        performing this operation must either be the job owner or an
687        operator or administrator of the Printer object (see Sections
688        1 and 8.5).  Otherwise, the IPP object MUST reject the
689        operation and return: 'client-error-forbidden',
690        'client-error-not-authenticated', or
691        'client-error-not-authorized' as appropriate.
692
693        Request
694        -------
695
696        Group 1: Operation Attributes
697            REQUIRED 'attributes-charset'
698            REQUIRED 'attributes-natural-language'
699            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
700              -or-   'job-uri' (uri)
701            OPTIONAL 'requesting-user-name' (name(MAX))
702            OPTIONAL 'message' (text(127))
703           
704        Response
705        --------
706
707        Group 1: Operation Attributes
708            OPTIONAL 'status-message' (text(255))
709            OPTIONAL 'detailed-status-message' (text(MAX))
710            REQUIRED 'attributes-charset'
711            REQUIRED 'attributes-natural-language'
712        Group 2: Unsupported Attributes
713
714        """
715
716        operation = request.attribute_groups[0]
717
718        job_id = None
719        printer_uri = None
720        requesting_user_name = None
721        message = None
722
723        # required attributes
724        if 'job-id' in operation and 'printer-uri' in operation:
725            job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
726            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
727            if printer_uri not in self.printer.uris:
728                raise ipp.errors.ClientErrorAttributes(
729                    str(operation['printer-uri']), operation['printer-uri'])
730
731        elif 'job-uri' in operation:
732            job_uri = verify_attribute(operation['job-uri'], ipp.JobUri)[0]
733            job_id = int(job_uri.split("/")[-1])
734
735        if 'requesting-user-name' in operation:
736            requesting_user_name = verify_attribute(
737                operation['requesting-user-name'], ipp.RequestingUserName)[0]
738
739        try:
740            self.printer.cancel_job(job_id, requesting_user_name=requesting_user_name)
741        except InvalidJobException:
742            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
743
744    @handler_for(ipp.OperationCodes.SEND_DOCUMENT)
745    def send_document(self, request, response):
746        """3.3.1 Send-Document Operation
747       
748        This OPTIONAL operation allows a client to create a
749        multi-document Job object that is initially 'empty' (contains
750        no documents). In the Create-Job response, the Printer object
751        returns the Job object's URI (the 'job-uri' attribute) and the
752        Job object's 32-bit identifier (the 'job-id' attribute). For
753        each new document that the client desires to add, the client
754        uses a Send-Document operation. Each Send- Document Request
755        contains the entire stream of document data for one document.
756
757        If the Printer supports this operation but does not support
758        multiple documents per job, the Printer MUST reject subsequent
759        Send-Document operations supplied with data and return the
760        'server-error-multiple- document-jobs-not-supported'. However,
761        the Printer MUST accept the first document with a 'true' or
762        'false' value for the 'last-document' operation attribute (see
763        below), so that clients MAY always submit one document jobs
764        with a 'false' value for 'last-document' in the first
765        Send-Document and a 'true' for 'last-document' in the second
766        Send-Document (with no data).
767       
768        Since the Create-Job and the send operations (Send-Document or
769        Send- URI operations) that follow could occur over an
770        arbitrarily long period of time for a particular job, a client
771        MUST send another send operation within an IPP Printer defined
772        minimum time interval after the receipt of the previous
773        request for the job. If a Printer object supports the
774        Create-Job and Send-Document operations, the Printer object
775        MUST support the 'multiple-operation-time-out' attribute (see
776        section 4.4.31). This attribute indicates the minimum number
777        of seconds the Printer object will wait for the next send
778        operation before taking some recovery action.
779
780        An IPP object MUST recover from an errant client that does not
781        supply a send operation, sometime after the minimum time
782        interval specified by the Printer object's
783        'multiple-operation-time-out' attribute.
784
785        Access Rights: The authenticated user (see section 8.3)
786        performing this operation must either be the job owner (as
787        determined in the Create-Job operation) or an operator or
788        administrator of the Printer object (see Sections 1 and
789        8.5). Otherwise, the IPP object MUST reject the operation and
790        return: 'client-error-forbidden', 'client-
791        error-not-authenticated', or 'client-error-not-authorized' as
792        appropriate.
793
794        Request
795        -------
796
797        Group 1: Operation Attributes
798            REQUIRED 'attributes-charset'
799            REQUIRED 'attributes-natural-language'
800            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
801              -or-   'job-uri' (uri)
802            OPTIONAL 'requesting-user-name' (name(MAX))
803            OPTIONAL 'document-name' (name(MAX))
804            OPTIONAL 'compression' (type3 keyword)
805            OPTIONAL 'document-format' (mimeMediaType)
806            OPTIONAL 'document-natural-language' (naturalLanguage)
807            OPTIONAL 'last-document' (boolean)
808        Group 2: Document Content
809           
810        Response
811        --------
812
813        Group 1: Operation Attributes
814            OPTIONAL 'status-message' (text(255))
815            OPTIONAL 'detailed-status-message' (text(MAX))
816            REQUIRED 'attributes-charset'
817            REQUIRED 'attributes-natural-language'
818        Group 2: Unsupported Attributes
819        Group 3: Job Object Attributes
820            REQUIRED 'job-uri' (uri)
821            REQUIRED 'job-id' (integer(1:MAX))
822            REQUIRED 'job-state' (type1 enum)
823            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
824            OPTIONAL 'job-state-message' (text(MAX))
825            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
826
827        """
828       
829        operation = request.attribute_groups[0]
830
831        job_id = None
832        printer_uri = None
833        requesting_user_name = None
834        document_name = None
835        compression = None
836        document_format = None
837        document_natural_language = None
838        last_document = None
839
840        # required attributes
841        if 'job-id' not in operation:
842            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
843        job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
844
845        if 'last-document' not in operation:
846            raise ipp.errors.ClientErrorBadRequest("Missing 'last-document' attribute")
847        last_document = verify_attribute(operation['last-document'], ipp.LastDocument)[0]
848        if not last_document:
849            raise ipp.errors.ServerErrorMultipleJobsNotSupported
850
851        # optional attributes
852        if 'printer-uri' in operation:
853            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
854            if printer_uri not in self.printer.uris:
855                raise ipp.errors.ClientErrorAttributes(
856                    str(operation['printer-uri']), operation['printer-uri'])
857
858        if 'requesting-user-name' in operation:
859            user_name = verify_attribute(
860                operation['requesting-user-name'], ipp.RequestingUserName)[0]
861
862        if 'document-name' in operation:
863            document_name = verify_attribute(
864                operation['document-name'], ipp.DocumentName)[0]
865
866        if 'compression' in operation:
867            compression = verify_attribute(
868                operation['compression'], ipp.Compression)[0]
869
870        if 'document-format' in operation:
871            document_format = verify_attribute(
872                operation['document-format'], ipp.DocumentFormat)[0]
873
874        if 'document-natural-language' in operation:
875            document_natural_language = verify_attribute(
876                operation['document_natural_language'],
877                ipp.DocumentNaturalLanguage)[0]
878
879        try:
880            self.printer.send_document(
881                job_id,
882                request.data,
883                document_name=document_name,
884                document_format=document_format,
885                document_natural_language=document_natural_language,
886                requesting_user_name=user_name,
887                compression=compression,
888                last_document=last_document)
889            attrs = self.printer.get_job_attributes(job_id)
890        except InvalidJobException:
891            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
892
893        response.attribute_groups.append(ipp.AttributeGroup(
894            ipp.AttributeTags.JOB, attrs))
895
896    @handler_for(ipp.OperationCodes.SEND_URI)
897    def send_uri(self, request, response):
898       raise ipp.errors.ServerErrorOperationNotSupported
899
900    @handler_for(ipp.OperationCodes.GET_JOB_ATTRIBUTES)
901    def get_job_attributes(self, request, response):
902        """3.3.4 Get-Job-Attributes Operation
903
904        This REQUIRED operation allows a client to request the values
905        of attributes of a Job object and it is almost identical to
906        the Get- Printer-Attributes operation (see section 3.2.5). The
907        only differences are that the operation is directed at a Job
908        object rather than a Printer object, there is no
909        'document-format' operation attribute used when querying a Job
910        object, and the returned attribute group is a set of Job
911        object attributes rather than a set of Printer object
912        attributes.
913
914        For Jobs, the possible names of attribute groups are:
915          - 'job-template': the subset of the Job Template attributes
916            that apply to a Job object (the first column of the table
917            in Section 4.2) that the implementation supports for Job
918            objects.
919          - 'job-description': the subset of the Job Description
920            attributes specified in Section 4.3 that the
921            implementation supports for Job objects.
922          - 'all': the special group 'all' that includes all
923            attributes that the implementation supports for Job
924            objects.
925
926        Since a client MAY request specific attributes or named
927        groups, there is a potential that there is some overlap. For
928        example, if a client requests, 'job-name' and
929        'job-description', the client is actually requesting the
930        'job-name' attribute once by naming it explicitly, and once by
931        inclusion in the 'job-description' group. In such cases, the
932        Printer object NEED NOT return the attribute only once in the
933        response even if it is requested multiple times. The client
934        SHOULD NOT request the same attribute in multiple ways.
935
936        It is NOT REQUIRED that a Job object support all attributes
937        belonging to a group (since some attributes are
938        OPTIONAL). However it is REQUIRED that each Job object support
939        all these group names.
940
941        Request
942        -------
943
944        Group 1: Operation Attributes
945            REQUIRED 'attributes-charset'
946            REQUIRED 'attributes-natural-language'
947            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
948              -or-   'job-uri' (uri)
949            OPTIONAL 'requesting-user-name' (name(MAX))
950            OPTIONAL 'requested-attributes' (1setOf keyword)
951           
952        Response
953        --------
954
955        Group 1: Operation Attributes
956            OPTIONAL 'status-message' (text(255))
957            OPTIONAL 'detailed-status-message' (text(MAX))
958            REQUIRED 'attributes-charset'
959            REQUIRED 'attributes-natural-language'
960        Group 2: Unsupported Attributes
961        Group 3: Job Object Attributes
962
963        """
964       
965        operation = request.attribute_groups[0]
966
967        job_id = None
968        printer_uri = None
969        requesting_user_name = None
970        requested_attributes = None
971
972        # required attributes
973        if 'job-id' not in operation:
974            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
975        job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
976
977        # optional attributes
978        if 'printer-uri' in operation:
979            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
980            if printer_uri not in self.printer.uris:
981                raise ipp.errors.ClientErrorAttributes(
982                    str(operation['printer-uri']), operation['printer-uri'])
983
984        # optional attributes
985        if 'requesting-user-name' in operation:
986            user_name = verify_attribute(
987                operation['requesting-user-name'], ipp.RequestingUserName)[0]
988           
989        if 'requested-attributes' in operation:
990            requested_attributes = verify_attribute(
991                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
992
993        # get the job attributes and add them to the response
994        try:
995            attrs = self.printer.get_job_attributes(
996                job_id,
997                requested_attributes=requested_attributes)
998        except InvalidJobException:
999            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
1000
1001        response.attribute_groups.append(ipp.AttributeGroup(
1002            ipp.AttributeTags.JOB, attrs))
1003
1004    @handler_for(ipp.OperationCodes.SET_JOB_ATTRIBUTES)
1005    def set_job_attributes(self, request, response):
1006       
1007        raise ipp.errors.ServerErrorOperationNotSupported
1008
1009    @handler_for(ipp.OperationCodes.RESTART_JOB)
1010    def restart_job(self, request, response):
1011        raise ipp.errors.ServerErrorOperationNotSupported
1012
1013    @handler_for(ipp.OperationCodes.PROMOTE_JOB)
1014    def promote_job(self, request, response):
1015        raise ipp.errors.ServerErrorOperationNotSupported
1016
1017    ##### CUPS Specific Commands
1018
1019    @handler_for(ipp.OperationCodes.CUPS_GET_DOCUMENT)
1020    def cups_get_document(self, request, response):
1021        raise ipp.errors.ServerErrorOperationNotSupported
1022
1023    @handler_for(ipp.OperationCodes.CUPS_GET_DEFAULT)
1024    def cups_get_default(self, request, response):
1025        """The CUPS-Get-Default operation (0x4001) returns the default
1026        printer URI and attributes.
1027
1028        Request
1029        -------
1030
1031        Group 1: Operation Attributes
1032            REQUIRED 'attributes-charset'
1033            REQUIRED 'attributes-natural-language'
1034            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
1035
1036        Response
1037        --------
1038
1039        Group 1: Operation Attributes
1040            OPTIONAL 'status-message' (text(255))
1041            REQUIRED 'attributes-charset'
1042            REQUIRED 'attributes-natural-language'
1043        Group 2: Printer Object Attributes
1044
1045        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_DEFAULT )
1046
1047        """
1048
1049        operation = request.attribute_groups[0]
1050        requested_attributes = None
1051       
1052        if 'requested-attributes' in operation:
1053            requested_attributes = verify_attribute(
1054                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
1055
1056        # get attributes from the printer and add to response
1057        attrs = self.printer.get_printer_attributes(
1058            requested_attributes=requested_attributes)
1059        response.attribute_groups.append(ipp.AttributeGroup(
1060            ipp.AttributeTags.PRINTER, attrs))
1061
1062    @handler_for(ipp.OperationCodes.CUPS_GET_PRINTERS)
1063    def cups_get_printers(self, request, response):
1064        """The CUPS-Get-Printers operation (0x4002) returns the
1065        printer attributes for every printer known to the system. This
1066        may include printers that are not served directly by the
1067        server.
1068
1069        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_PRINTERS )
1070           
1071        """
1072
1073        # get attributes from the printer and add to response
1074        response.attribute_groups.append(ipp.AttributeGroup(
1075            ipp.AttributeTags.PRINTER, self.printer.get_printer_attributes()))
1076
1077    @handler_for(ipp.OperationCodes.CUPS_GET_CLASSES)
1078    def cups_get_classes(self, request, response):
1079        """The CUPS-Get-Classes operation (0x4005) returns the printer
1080        attributes for every printer class known to the system. This
1081        may include printer classes that are not served directly by
1082        the server.
1083
1084        Request
1085        -------
1086
1087        Group 1: Operation Attributes
1088            REQUIRED 'attributes-charset'
1089            REQUIRED 'attributes-natural-language'
1090            OPTIONAL 'first-printer-name' (name(127)) CUPS 1.2/Mac OS X 10.5
1091            OPTIONAL 'limit' (integer (1:MAX))
1092            OPTIONAL 'printer-location' (text(127)) CUPS 1.1.7
1093            OPTIONAL 'printer-type' (type2 enum) CUPS 1.1.7
1094            OPTIONAL 'printer-type-mask' (type2 enum) CUPS 1.1.7
1095            OPTIONAL 'requested-attributes' (1setOf keyword)
1096            OPTOINAL 'requested-user-name' (name(127)) CUPS 1.2/Mac OS X 10.5
1097            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
1098
1099        Response
1100        --------
1101
1102        Group 1: Operation Attributes
1103            OPTIONAL 'status-message' (text(255))
1104            REQUIRED 'attributes-charset'
1105            REQUIRED 'attributes-natural-language'
1106        Group 2: Printer Class Object Attributes
1107
1108        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_CLASSES )
1109
1110        """
1111
1112        raise ipp.errors.ServerErrorOperationNotSupported
1113
Note: See TracBrowser for help on using the repository browser.