source: server/lib/gutenbach/server/requests.py @ 5141ed8

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

fixed merge conflict in last commit

  • Property mode set to 100644
File size: 52.4 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        """3.2.2 Send URI
900
901        This OPTIONAL operation is identical to the Send-Document
902        operation (see section 3.3.1) except that a client MUST supply
903        a URI reference ('document-uri' operation attribute) rather
904        than the document data itself.  If a Printer object supports
905        this operation, clients can use both Send-URI or Send-Document
906        operations to add new documents to an existing multi-document
907        Job object.  However, if a client needs to indicate that the
908        previous Send-URI or Send-Document was the last document, the
909        client MUST use the Send-Document operation with no document
910        data and the 'last-document' flag set to 'true' (rather than
911        using a Send-URI operation with no 'document-uri' operation
912        attribute).
913
914        If a Printer object supports this operation, it MUST also
915        support the Print-URI operation (see section 3.2.2).
916
917        The Printer object MUST validate the syntax and URI scheme of
918        the supplied URI before returning a response, just as in the
919        Print-URI operation.  The IPP Printer MAY validate the
920        accessibility of the document as part of the operation or
921        subsequently (see section 3.2.2).
922
923        Request
924        -------
925
926        Group 1: Operation Attributes
927            REQUIRED 'attributes-charset'
928            REQUIRED 'attributes-natural-language'
929            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
930            REQUIRED 'document-uri' (uri)
931              -or-   'job-uri' (uri)
932            OPTIONAL 'requesting-user-name' (name(MAX))
933            OPTIONAL 'document-name' (name(MAX))
934            OPTIONAL 'compression' (type3 keyword)
935            OPTIONAL 'document-format' (mimeMediaType)
936            OPTIONAL 'document-natural-language' (naturalLanguage)
937        Group 2: Document Content
938           
939        Response
940        --------
941
942        Group 1: Operation Attributes
943            OPTIONAL 'status-message' (text(255))
944            OPTIONAL 'detailed-status-message' (text(MAX))
945            REQUIRED 'attributes-charset'
946            REQUIRED 'attributes-natural-language'
947        Group 2: Unsupported Attributes
948        Group 3: Job Object Attributes
949            REQUIRED 'job-uri' (uri)
950            REQUIRED 'job-id' (integer(1:MAX))
951            REQUIRED 'job-state' (type1 enum)
952            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
953            OPTIONAL 'job-state-message' (text(MAX))
954            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
955
956        """
957       
958        operation = request.attribute_groups[0]
959
960        job_id = None
961        printer_uri = None
962        requesting_user_name = None
963        document_name = None
964        compression = None
965        document_format = None
966        document_natural_language = None
967        last_document = None
968
969        # required attributes
970        if 'job-id' not in operation:
971            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
972        job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
973
974        if 'last-document' not in operation:
975            raise ipp.errors.ClientErrorBadRequest("Missing 'last-document' attribute")
976        last_document = verify_attribute(operation['last-document'], ipp.LastDocument)[0]
977
978        if 'document-uri' not in operation:
979            raise ipp.errors.ClientErrorBadRequest("Missing 'document-uri' attribute")
980        document_uri = verify_attribute(operation['document-uri'], ipp.DocumentUri)[0]
981        if not last_document:
982            raise ipp.errors.ServerErrorMultipleJobsNotSupported
983
984        # optional attributes
985        if 'printer-uri' in operation:
986            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
987            if printer_uri not in self.printer.uris:
988                raise ipp.errors.ClientErrorAttributes(
989                    str(operation['printer-uri']), operation['printer-uri'])
990
991        if 'requesting-user-name' in operation:
992            user_name = verify_attribute(
993                operation['requesting-user-name'], ipp.RequestingUserName)[0]
994
995        if 'document-name' in operation:
996            document_name = verify_attribute(
997                operation['document-name'], ipp.DocumentName)[0]
998
999        if 'compression' in operation:
1000            compression = verify_attribute(
1001                operation['compression'], ipp.Compression)[0]
1002
1003        if 'document-format' in operation:
1004            document_format = verify_attribute(
1005                operation['document-format'], ipp.DocumentFormat)[0]
1006
1007        if 'document-natural-language' in operation:
1008            document_natural_language = verify_attribute(
1009                operation['document_natural_language'],
1010                ipp.DocumentNaturalLanguage)[0]
1011
1012        try:
1013            self.printer.send_uri(
1014                job_id,
1015                document_uri=document_uri,
1016                document_name=document_name,
1017                document_format=document_format,
1018                document_natural_language=document_natural_language,
1019                requesting_user_name=user_name,
1020                compression=compression,
1021                last_document=last_document)
1022            attrs = self.printer.get_job_attributes(job_id)
1023        except InvalidJobException:
1024            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
1025
1026        response.attribute_groups.append(ipp.AttributeGroup(
1027            ipp.AttributeTags.JOB, attrs))
1028
1029    @handler_for(ipp.OperationCodes.GET_JOB_ATTRIBUTES)
1030    def get_job_attributes(self, request, response):
1031        """3.3.4 Get-Job-Attributes Operation
1032
1033        This REQUIRED operation allows a client to request the values
1034        of attributes of a Job object and it is almost identical to
1035        the Get- Printer-Attributes operation (see section 3.2.5). The
1036        only differences are that the operation is directed at a Job
1037        object rather than a Printer object, there is no
1038        'document-format' operation attribute used when querying a Job
1039        object, and the returned attribute group is a set of Job
1040        object attributes rather than a set of Printer object
1041        attributes.
1042
1043        For Jobs, the possible names of attribute groups are:
1044          - 'job-template': the subset of the Job Template attributes
1045            that apply to a Job object (the first column of the table
1046            in Section 4.2) that the implementation supports for Job
1047            objects.
1048          - 'job-description': the subset of the Job Description
1049            attributes specified in Section 4.3 that the
1050            implementation supports for Job objects.
1051          - 'all': the special group 'all' that includes all
1052            attributes that the implementation supports for Job
1053            objects.
1054
1055        Since a client MAY request specific attributes or named
1056        groups, there is a potential that there is some overlap. For
1057        example, if a client requests, 'job-name' and
1058        'job-description', the client is actually requesting the
1059        'job-name' attribute once by naming it explicitly, and once by
1060        inclusion in the 'job-description' group. In such cases, the
1061        Printer object NEED NOT return the attribute only once in the
1062        response even if it is requested multiple times. The client
1063        SHOULD NOT request the same attribute in multiple ways.
1064
1065        It is NOT REQUIRED that a Job object support all attributes
1066        belonging to a group (since some attributes are
1067        OPTIONAL). However it is REQUIRED that each Job object support
1068        all these group names.
1069
1070        Request
1071        -------
1072
1073        Group 1: Operation Attributes
1074            REQUIRED 'attributes-charset'
1075            REQUIRED 'attributes-natural-language'
1076            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
1077              -or-   'job-uri' (uri)
1078            OPTIONAL 'requesting-user-name' (name(MAX))
1079            OPTIONAL 'requested-attributes' (1setOf keyword)
1080           
1081        Response
1082        --------
1083
1084        Group 1: Operation Attributes
1085            OPTIONAL 'status-message' (text(255))
1086            OPTIONAL 'detailed-status-message' (text(MAX))
1087            REQUIRED 'attributes-charset'
1088            REQUIRED 'attributes-natural-language'
1089        Group 2: Unsupported Attributes
1090        Group 3: Job Object Attributes
1091
1092        """
1093       
1094        operation = request.attribute_groups[0]
1095
1096        job_id = None
1097        printer_uri = None
1098        requesting_user_name = None
1099        requested_attributes = None
1100
1101        # required attributes
1102        if 'job-id' not in operation:
1103            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
1104        job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
1105
1106        # optional attributes
1107        if 'printer-uri' in operation:
1108            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
1109            if printer_uri not in self.printer.uris:
1110                raise ipp.errors.ClientErrorAttributes(
1111                    str(operation['printer-uri']), operation['printer-uri'])
1112
1113        # optional attributes
1114        if 'requesting-user-name' in operation:
1115            user_name = verify_attribute(
1116                operation['requesting-user-name'], ipp.RequestingUserName)[0]
1117           
1118        if 'requested-attributes' in operation:
1119            requested_attributes = verify_attribute(
1120                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
1121
1122        # get the job attributes and add them to the response
1123        try:
1124            attrs = self.printer.get_job_attributes(
1125                job_id,
1126                requested_attributes=requested_attributes)
1127        except InvalidJobException:
1128            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
1129
1130        response.attribute_groups.append(ipp.AttributeGroup(
1131            ipp.AttributeTags.JOB, attrs))
1132
1133    @handler_for(ipp.OperationCodes.SET_JOB_ATTRIBUTES)
1134    def set_job_attributes(self, request, response):
1135       
1136        raise ipp.errors.ServerErrorOperationNotSupported
1137
1138    @handler_for(ipp.OperationCodes.RESTART_JOB)
1139    def restart_job(self, request, response):
1140        raise ipp.errors.ServerErrorOperationNotSupported
1141
1142    @handler_for(ipp.OperationCodes.PROMOTE_JOB)
1143    def promote_job(self, request, response):
1144        raise ipp.errors.ServerErrorOperationNotSupported
1145
1146    ##### CUPS Specific Commands
1147
1148    @handler_for(ipp.OperationCodes.CUPS_GET_DOCUMENT)
1149    def cups_get_document(self, request, response):
1150        raise ipp.errors.ServerErrorOperationNotSupported
1151
1152    @handler_for(ipp.OperationCodes.CUPS_GET_DEFAULT)
1153    def cups_get_default(self, request, response):
1154        """The CUPS-Get-Default operation (0x4001) returns the default
1155        printer URI and attributes.
1156
1157        Request
1158        -------
1159
1160        Group 1: Operation Attributes
1161            REQUIRED 'attributes-charset'
1162            REQUIRED 'attributes-natural-language'
1163            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
1164
1165        Response
1166        --------
1167
1168        Group 1: Operation Attributes
1169            OPTIONAL 'status-message' (text(255))
1170            REQUIRED 'attributes-charset'
1171            REQUIRED 'attributes-natural-language'
1172        Group 2: Printer Object Attributes
1173
1174        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_DEFAULT )
1175
1176        """
1177
1178        operation = request.attribute_groups[0]
1179        requested_attributes = None
1180       
1181        if 'requested-attributes' in operation:
1182            requested_attributes = verify_attribute(
1183                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
1184
1185        # get attributes from the printer and add to response
1186        attrs = self.printer.get_printer_attributes(
1187            requested_attributes=requested_attributes)
1188        response.attribute_groups.append(ipp.AttributeGroup(
1189            ipp.AttributeTags.PRINTER, attrs))
1190
1191    @handler_for(ipp.OperationCodes.CUPS_GET_PRINTERS)
1192    def cups_get_printers(self, request, response):
1193        """The CUPS-Get-Printers operation (0x4002) returns the
1194        printer attributes for every printer known to the system. This
1195        may include printers that are not served directly by the
1196        server.
1197
1198        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_PRINTERS )
1199           
1200        """
1201
1202        # get attributes from the printer and add to response
1203        response.attribute_groups.append(ipp.AttributeGroup(
1204            ipp.AttributeTags.PRINTER, self.printer.get_printer_attributes()))
1205
1206    @handler_for(ipp.OperationCodes.CUPS_GET_CLASSES)
1207    def cups_get_classes(self, request, response):
1208        """The CUPS-Get-Classes operation (0x4005) returns the printer
1209        attributes for every printer class known to the system. This
1210        may include printer classes that are not served directly by
1211        the server.
1212
1213        Request
1214        -------
1215
1216        Group 1: Operation Attributes
1217            REQUIRED 'attributes-charset'
1218            REQUIRED 'attributes-natural-language'
1219            OPTIONAL 'first-printer-name' (name(127)) CUPS 1.2/Mac OS X 10.5
1220            OPTIONAL 'limit' (integer (1:MAX))
1221            OPTIONAL 'printer-location' (text(127)) CUPS 1.1.7
1222            OPTIONAL 'printer-type' (type2 enum) CUPS 1.1.7
1223            OPTIONAL 'printer-type-mask' (type2 enum) CUPS 1.1.7
1224            OPTIONAL 'requested-attributes' (1setOf keyword)
1225            OPTOINAL 'requested-user-name' (name(127)) CUPS 1.2/Mac OS X 10.5
1226            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
1227
1228        Response
1229        --------
1230
1231        Group 1: Operation Attributes
1232            OPTIONAL 'status-message' (text(255))
1233            REQUIRED 'attributes-charset'
1234            REQUIRED 'attributes-natural-language'
1235        Group 2: Printer Class Object Attributes
1236
1237        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_CLASSES )
1238
1239        """
1240
1241        raise ipp.errors.ServerErrorOperationNotSupported
1242
Note: See TracBrowser for help on using the repository browser.