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

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

Send URI added to printer.py

  • Property mode set to 100644
File size: 52.3 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
294        of the successful status codes, the Printer adds the
295        'document-access- error' value to the job's 'job-state-reasons'
296        attribute and MAY populate the job's 'job-document-access-errors'
297        Job Description attribute (see section 4.3.11).  See The
298        Implementer's Guide [IPP- IIG] for suggested additional checks.
299                                                                             
300        If the Printer object supports this operation, it MUST support the
301        'reference-uri-schemes-supported' Printer attribute (see section 4.4.27).
302
303        It is up to the IPP object to interpret the URI and subsequently
304        'pull' the document from the source referenced by the URI string."""
305        raise ipp.errors.ServerErrorOperationNotSupported
306
307    @handler_for(ipp.OperationCodes.CREATE_JOB)
308    def create_job(self, request, response):
309        """RFC 2911: 3.2.4 Create-Job Operation
310
311        This OPTIONAL operation is similar to the Print-Job operation
312        (section 3.2.1) except that in the Create-Job request, a
313        client does not supply document data or any reference to
314        document data. Also, the client does not supply any of the
315        'document-name', 'document- format', 'compression', or
316        'document-natural-language' operation attributes. This
317        operation is followed by one or more Send-Document or Send-URI
318        operations. In each of those operation requests, the client
319        OPTIONALLY supplies the 'document-name', 'document-format',
320        and 'document-natural-language' attributes for each document
321        in the multi-document Job object.
322
323        Request
324        -------
325        Group 1: Operation Attributes
326            REQUIRED 'attributes-charset'
327            REQUIRED 'attributes-natural-language'
328            REQUIRED 'printer-uri' (uri)
329            OPTIONAL 'requesting-user-name' (name(MAX))
330            OPTIONAL 'job-name' (name(MAX))
331            OPTIONAL 'ipp-attribute-fidelity' (boolean)
332            OPTIONAL 'job-k-octets' (integer(0:MAX))
333            OPTIONAL 'job-impressions' (integer(0:MAX))
334            OPTIONAL 'job-media-sheets' (integer(0:MAX))
335        Group 2: Job Template Attributes
336
337        Response
338        --------
339        Group 1: Operation Attributes
340            OPTIONAL 'status-message' (text(255))
341            OPTIONAL 'detailed-status-message' (text(MAX))
342            REQUIRED 'attributes-charset'
343            REQUIRED 'attributes-natural-language'
344        Group 2: Unsupported Attributes
345        Group 3: Job Object Attributes
346            REQUIRED 'job-uri' (uri)
347            REQUIRED 'job-id' (integer(1:MAX))
348            REQUIRED 'job-state' (type1 enum)
349            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
350            OPTIONAL 'job-state-message' (text(MAX))
351            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
352       
353        """
354
355        operation = request.attribute_groups[0]
356
357        printer_uri = None
358        requesting_user_name = None
359        job_name = None
360        ipp_attribute_fidelity=None
361        job_k_octets = None
362
363        # requested printer uri
364        if 'printer-uri' not in operation:
365            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
366        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
367        if printer_uri not in self.printer.uris:
368            raise ipp.errors.ClientErrorAttributes(
369                str(operation['printer-uri']), operation['printer-uri'])
370
371        if 'requesting-user-name' in operation:
372            user_name = verify_attribute(
373                operation['requesting-user-name'], ipp.RequestingUserName)[0]
374
375        if 'job-name' in operation:
376            job_name = verify_attribute(
377                operation['job-name'], ipp.JobName)[0]
378
379        if 'job-k-octets' in operation:
380            job_k_octets = verify_attribute(
381                operation['job-k-octets'], ipp.JobKOctets)[0]
382
383        if 'ipp-attribute-fidelity' in operation:
384            pass # don't care
385        if 'job-impressions' in operation:
386            pass # don't care
387        if 'job-media-sheets' in operation:
388            pass # don't care
389
390        # get attributes from the printer and add to response
391        job_id = self.printer.create_job(
392            requesting_user_name=requesting_user_name,
393            job_name=job_name,
394            job_k_octets=job_k_octets)
395        attrs = self.printer.get_job_attributes(job_id)
396        response.attribute_groups.append(ipp.AttributeGroup(
397            ipp.AttributeTags.JOB, attrs))
398   
399    @handler_for(ipp.OperationCodes.PAUSE_PRINTER)
400    def pause_printer(self, request, response):
401        """
402            3.2.7 Pause-Printer Operation
403
404            This OPTIONAL operation allows a client to stop the Printer object
405            from scheduling jobs on all its devices.  Depending on
406            implementation, the Pause-Printer operation MAY also stop the Printer
407            from processing the current job or jobs.  Any job that is currently
408            being printed is either stopped as soon as the implementation permits
409            or is completed, depending on implementation.  The Printer object
410            MUST still accept create operations to create new jobs, but MUST
411            prevent any jobs from entering the 'processing' state.
412
413            If the Pause-Printer operation is supported, then the Resume-Printer
414            operation MUST be supported, and vice-versa.
415
416            The IPP Printer stops the current job(s) on its device(s) that were
417            in the 'processing' or 'processing-stopped' states as soon as the
418            implementation permits.  If the implementation will take appreciable
419            time to stop, the IPP Printer adds the 'moving-to-paused' value to
420            the Printer object's 'printer-state-reasons' attribute (see section
421            4.4.12).  When the device(s) have all stopped, the IPP Printer
422            transitions the Printer object to the 'stopped' state, removes the
423            'moving-to-paused' value, if present, and adds the 'paused' value to
424            the Printer object's 'printer-state-reasons' attribute.
425
426            When the current job(s) complete that were in the 'processing' state,
427            the IPP Printer transitions them to the 'completed' state.  When the
428            current job(s) stop in mid processing that were in the 'processing'
429            state, the IPP Printer transitions them to the 'processing-stopped'
430            state and adds the 'printer-stopped' value to the job's 'job-state-
431            reasons' attribute.
432
433            For any jobs that are 'pending' or 'pending-held', the 'printer-
434            stopped' value of the jobs' 'job-state-reasons' attribute also
435            applies.  However, the IPP Printer NEED NOT update those jobs' 'job-
436            state-reasons' attributes and only need return the 'printer-stopped'
437            value when those jobs are queried (so-called 'lazy evaluation').
438
439            Whether the Pause-Printer operation affects jobs that were submitted
440            to the device from other sources than the IPP Printer object in the
441            same way that the Pause-Printer operation affects jobs that were
442            submitted to the IPP Printer object using IPP, depends on
443            implementation, i.e., on whether the IPP protocol is being used as a
444            universal management protocol or just to manage IPP jobs,
445            respectively.
446
447            The IPP Printer MUST accept the request in any state and transition
448            the Printer to the indicated new 'printer-state' before returning as
449            follows:
450
451            Current        New      'printer   IPP Printer's response status
452            'printer-    'printer-   -state-          code and action:
453            state'       state'    reasons'
454
455            'idle'       'stopped'    'paused'  'successful-ok'
456            'processing' 'processing' 'moving-  OPTION 1: 'successful-ok';
457                                                      to-       Later, when all output has
458                                                      paused'   stopped, the 'printer-state'
459                                                                            becomes 'stopped', and the
460                                                                            'paused' value replaces the
461                                                                            'moving-to-paused' value in the
462                                                                            'printer-state-reasons'
463                                                                            attribute
464            'processing' 'stopped'    'paused'  OPTION 2: 'successful-ok';
465                                                                            all device output stopped
466                                                                            immediately
467            'stopped'    'stopped'    'paused'  'successful-ok'
468
469            Access Rights: The authenticated user (see section 8.3) performing
470            this operation must be an operator or administrator of the Printer
471            object (see Sections 1 and 8.5).   Otherwise, the IPP Printer MUST
472            reject the operation and return:  'client-error-forbidden', 'client-
473            error-not-authenticated', or 'client-error-not-authorized' as
474            appropriate.
475
476            3.2.7.1 Pause-Printer Request
477
478            The following groups of attributes are part of the Pause-Printer
479            Request:
480
481            Group 1: Operation Attributes
482
483            Natural Language and Character Set:
484            The 'attributes-charset' and 'attributes-natural-language'
485            attributes as described in section 3.1.4.1.
486
487            Target:
488            The 'printer-uri' (uri) operation attribute which is the target
489            for this operation as described in section 3.1.5.
490
491            Requesting User Name:
492            The 'requesting-user-name' (name(MAX)) attribute SHOULD be
493            supplied by the client as described in section 8.3.
494
495            3.2.7.2 Pause-Printer Response
496
497            The following groups of attributes are part of the Pause-Printer
498            Response:
499
500            Group 1: Operation Attributes
501
502            Status Message:
503            In addition to the REQUIRED status code returned in every
504            response, the response OPTIONALLY includes a 'status-message'
505            (text(255)) and/or a 'detailed-status-message' (text(MAX))
506            operation attribute as described in sections 13 and  3.1.6.
507
508            Natural Language and Character Set:
509            The 'attributes-charset' and 'attributes-natural-language'
510            attributes as described in section 3.1.4.2.
511
512            Group 2: Unsupported Attributes
513
514            See section 3.1.7 for details on returning Unsupported Attributes.
515
516   
517    """
518        raise ipp.errors.ServerErrorOperationNotSupported
519
520    @handler_for(ipp.OperationCodes.RESUME_PRINTER)
521    def resume_printer(self, request, response):
522        """
523        3.2.8 Resume-Printer Operation
524
525        This operation allows a client to resume the Printer object
526        scheduling jobs on all its devices.  The Printer object MUST remove
527        the 'paused' and 'moving-to-paused' values from the Printer object's
528        'printer-state-reasons' attribute, if present.  If there are no other
529        reasons to keep a device paused (such as media-jam), the IPP Printer
530        is free to transition itself to the 'processing' or 'idle' states,
531        depending on whether there are jobs to be processed or not,
532        respectively, and the device(s) resume processing jobs.
533
534        If the Pause-Printer operation is supported, then the Resume-Printer
535        operation MUST be supported, and vice-versa.
536
537        The IPP Printer removes the 'printer-stopped' value from any job's
538        'job-state-reasons' attributes contained in that Printer.
539
540        The IPP Printer MUST accept the request in any state, transition the
541        Printer object to the indicated new state as follows:
542
543
544        Current    New 'printer-  IPP Printer's response status code and
545        'printer-      state'                     action:
546        state'
547
548        'idle'       'idle'         'successful-ok'
549        'processing' 'processing'   'successful-ok'
550
551        'stopped'    'processing'   'successful-ok';
552                                                   when there are jobs to be processed
553        'stopped'    'idle'         'successful-ok';
554                                                   when there are no jobs to be processed.
555
556        Access Rights: The authenticated user (see section 8.3) performing
557        this operation must be an operator or administrator of the Printer
558        object (see Sections 1 and 8.5).  Otherwise, the IPP Printer MUST
559        reject the operation and return:  'client-error-forbidden', 'client-
560        error-not-authenticated', or 'client-error-not-authorized' as
561        appropriate.
562
563        The Resume-Printer Request and Resume-Printer Response have the same
564        attribute groups and attributes as the Pause-Printer operation (see
565        sections 3.2.7.1 and 3.2.7.2).                 
566        """
567        raise ipp.errors.ServerErrorOperationNotSupported
568
569    @handler_for(ipp.OperationCodes.GET_PRINTER_ATTRIBUTES)
570    def get_printer_attributes(self, request, response):
571        """RFC 2911: 3.2.5 Get-Printer-Attributes Operation
572
573        This REQUIRED operation allows a client to request the values
574        of the attributes of a Printer object.
575       
576        In the request, the client supplies the set of Printer
577        attribute names and/or attribute group names in which the
578        requester is interested. In the response, the Printer object
579        returns a corresponding attribute set with the appropriate
580        attribute values filled in.
581
582        Request
583        -------
584
585        Group 1: Operation Attributes
586            REQUIRED 'attributes-charset'
587            REQUIRED 'attributes-natural-language'
588            REQUIRED 'printer-uri' (uri)
589            OPTIONAL 'requesting-user-name' (name(MAX))
590            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
591            OPTIONAL 'document-format' (mimeMediaType)
592           
593        Response
594        --------
595
596        Group 1: Operation Attributes
597            OPTIONAL 'status-message' (text(255))
598            OPTIONAL 'detailed-status-message' (text(MAX))
599            REQUIRED 'attributes-charset'
600            REQUIRED 'attributes-natural-language'
601        Group 2: Unsupported Attributes
602        Group 3: Printer Object Attributes
603
604        """
605
606        operation = request.attribute_groups[0]
607
608        printer_uri = None
609        requesting_user_name = None
610        requested_attributes = None
611        document_format = None
612
613        # requested printer uri
614        if 'printer-uri' not in operation:
615            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
616        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
617        if printer_uri not in self.printer.uris:
618            raise ipp.errors.ClientErrorAttributes(
619                str(operation['printer-uri']), operation['printer-uri'])
620
621        # optional attributes
622        if 'requesting-user-name' in operation:
623            user_name = verify_attribute(
624                operation['requesting-user-name'], ipp.RequestingUserName)[0]
625           
626        if 'requested-attributes' in operation:
627            requested_attributes = verify_attribute(
628                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
629
630        if 'document-format' in operation:
631            pass # XXX: todo
632
633        # get attributes from the printer and add to response
634        response.attribute_groups.append(ipp.AttributeGroup(
635            ipp.AttributeTags.PRINTER,
636            self.printer.get_printer_attributes(
637                requested_attributes=requested_attributes)))
638
639    @handler_for(ipp.OperationCodes.SET_PRINTER_ATTRIBUTES)
640    def set_printer_attributes(self, request, response):
641
642        raise ipp.errors.ServerErrorOperationNotSupported
643
644    ##### Job Commands
645
646    @handler_for(ipp.OperationCodes.CANCEL_JOB)
647    def cancel_job(self, request, response):
648        """3.3.3 Cancel-Job Operation
649
650        This REQUIRED operation allows a client to cancel a Print Job from
651        the time the job is created up to the time it is completed, canceled,
652        or aborted. Since a Job might already be printing by the time a
653        Cancel-Job is received, some media sheet pages might be printed
654        before the job is actually terminated.
655
656        The IPP object MUST accept or reject the request based on the job's
657        current state and transition the job to the indicated new state as
658        follows:
659
660        Current State       New State           Response
661        -----------------------------------------------------------------
662        pending             canceled            successful-ok
663        pending-held        canceled            successful-ok
664        processing          canceled            successful-ok
665        processing          processing          successful-ok               See Rule 1
666        processing          processing          client-error-not-possible   See Rule 2
667        processing-stopped  canceled            successful-ok
668        processing-stopped  processing-stopped  successful-ok               See Rule 1
669        processing-stopped  processing-stopped  client-error-not-possible   See Rule 2
670        completed           completed           client-error-not-possible
671        canceled            canceled            client-error-not-possible
672        aborted             aborted             client-error-not-possible
673
674        Rule 1: If the implementation requires some measurable time to
675        cancel the job in the 'processing' or 'processing-stopped' job
676        states, the IPP object MUST add the 'processing-to-stop-point'
677        value to the job's 'job-state-reasons' attribute and then
678        transition the job to the 'canceled' state when the processing
679        ceases (see section 4.3.8).
680
681        Rule 2: If the Job object already has the
682        'processing-to-stop-point' value in its 'job-state-reasons'
683        attribute, then the Printer object MUST reject a Cancel-Job
684        operation.
685
686        Access Rights: The authenticated user (see section 8.3)
687        performing this operation must either be the job owner or an
688        operator or administrator of the Printer object (see Sections
689        1 and 8.5).  Otherwise, the IPP object MUST reject the
690        operation and return: 'client-error-forbidden',
691        'client-error-not-authenticated', or
692        'client-error-not-authorized' as appropriate.
693
694        Request
695        -------
696
697        Group 1: Operation Attributes
698            REQUIRED 'attributes-charset'
699            REQUIRED 'attributes-natural-language'
700            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
701              -or-   'job-uri' (uri)
702            OPTIONAL 'requesting-user-name' (name(MAX))
703            OPTIONAL 'message' (text(127))
704           
705        Response
706        --------
707
708        Group 1: Operation Attributes
709            OPTIONAL 'status-message' (text(255))
710            OPTIONAL 'detailed-status-message' (text(MAX))
711            REQUIRED 'attributes-charset'
712            REQUIRED 'attributes-natural-language'
713        Group 2: Unsupported Attributes
714
715        """
716
717        operation = request.attribute_groups[0]
718
719        job_id = None
720        printer_uri = None
721        requesting_user_name = None
722        message = None
723
724        # required attributes
725        if 'job-id' in operation and 'printer-uri' in operation:
726            job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
727            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
728            if printer_uri not in self.printer.uris:
729                raise ipp.errors.ClientErrorAttributes(
730                    str(operation['printer-uri']), operation['printer-uri'])
731
732        elif 'job-uri' in operation:
733            job_uri = verify_attribute(operation['job-uri'], ipp.JobUri)[0]
734            job_id = int(job_uri.split("/")[-1])
735
736        if 'requesting-user-name' in operation:
737            requesting_user_name = verify_attribute(
738                operation['requesting-user-name'], ipp.RequestingUserName)[0]
739
740        try:
741            self.printer.cancel_job(job_id, requesting_user_name=requesting_user_name)
742        except InvalidJobException:
743            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
744
745    @handler_for(ipp.OperationCodes.SEND_DOCUMENT)
746    def send_document(self, request, response):
747        """3.3.1 Send-Document Operation
748       
749        This OPTIONAL operation allows a client to create a
750        multi-document Job object that is initially 'empty' (contains
751        no documents). In the Create-Job response, the Printer object
752        returns the Job object's URI (the 'job-uri' attribute) and the
753        Job object's 32-bit identifier (the 'job-id' attribute). For
754        each new document that the client desires to add, the client
755        uses a Send-Document operation. Each Send- Document Request
756        contains the entire stream of document data for one document.
757
758        If the Printer supports this operation but does not support
759        multiple documents per job, the Printer MUST reject subsequent
760        Send-Document operations supplied with data and return the
761        'server-error-multiple- document-jobs-not-supported'. However,
762        the Printer MUST accept the first document with a 'true' or
763        'false' value for the 'last-document' operation attribute (see
764        below), so that clients MAY always submit one document jobs
765        with a 'false' value for 'last-document' in the first
766        Send-Document and a 'true' for 'last-document' in the second
767        Send-Document (with no data).
768       
769        Since the Create-Job and the send operations (Send-Document or
770        Send- URI operations) that follow could occur over an
771        arbitrarily long period of time for a particular job, a client
772        MUST send another send operation within an IPP Printer defined
773        minimum time interval after the receipt of the previous
774        request for the job. If a Printer object supports the
775        Create-Job and Send-Document operations, the Printer object
776        MUST support the 'multiple-operation-time-out' attribute (see
777        section 4.4.31). This attribute indicates the minimum number
778        of seconds the Printer object will wait for the next send
779        operation before taking some recovery action.
780
781        An IPP object MUST recover from an errant client that does not
782        supply a send operation, sometime after the minimum time
783        interval specified by the Printer object's
784        'multiple-operation-time-out' attribute.
785
786        Access Rights: The authenticated user (see section 8.3)
787        performing this operation must either be the job owner (as
788        determined in the Create-Job operation) or an operator or
789        administrator of the Printer object (see Sections 1 and
790        8.5). Otherwise, the IPP object MUST reject the operation and
791        return: 'client-error-forbidden', 'client-
792        error-not-authenticated', or 'client-error-not-authorized' as
793        appropriate.
794
795        Request
796        -------
797
798        Group 1: Operation Attributes
799            REQUIRED 'attributes-charset'
800            REQUIRED 'attributes-natural-language'
801            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
802              -or-   'job-uri' (uri)
803            OPTIONAL 'requesting-user-name' (name(MAX))
804            OPTIONAL 'document-name' (name(MAX))
805            OPTIONAL 'compression' (type3 keyword)
806            OPTIONAL 'document-format' (mimeMediaType)
807            OPTIONAL 'document-natural-language' (naturalLanguage)
808            OPTIONAL 'last-document' (boolean)
809        Group 2: Document Content
810           
811        Response
812        --------
813
814        Group 1: Operation Attributes
815            OPTIONAL 'status-message' (text(255))
816            OPTIONAL 'detailed-status-message' (text(MAX))
817            REQUIRED 'attributes-charset'
818            REQUIRED 'attributes-natural-language'
819        Group 2: Unsupported Attributes
820        Group 3: Job Object Attributes
821            REQUIRED 'job-uri' (uri)
822            REQUIRED 'job-id' (integer(1:MAX))
823            REQUIRED 'job-state' (type1 enum)
824            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
825            OPTIONAL 'job-state-message' (text(MAX))
826            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
827
828        """
829       
830        operation = request.attribute_groups[0]
831
832        job_id = None
833        printer_uri = None
834        requesting_user_name = None
835        document_name = None
836        compression = None
837        document_format = None
838        document_natural_language = None
839        last_document = None
840
841        # required attributes
842        if 'job-id' not in operation:
843            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
844        job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
845
846        if 'last-document' not in operation:
847            raise ipp.errors.ClientErrorBadRequest("Missing 'last-document' attribute")
848        last_document = verify_attribute(operation['last-document'], ipp.LastDocument)[0]
849        if not last_document:
850            raise ipp.errors.ServerErrorMultipleJobsNotSupported
851
852        # optional attributes
853        if 'printer-uri' in operation:
854            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
855            if printer_uri not in self.printer.uris:
856                raise ipp.errors.ClientErrorAttributes(
857                    str(operation['printer-uri']), operation['printer-uri'])
858
859        if 'requesting-user-name' in operation:
860            user_name = verify_attribute(
861                operation['requesting-user-name'], ipp.RequestingUserName)[0]
862
863        if 'document-name' in operation:
864            document_name = verify_attribute(
865                operation['document-name'], ipp.DocumentName)[0]
866
867        if 'compression' in operation:
868            compression = verify_attribute(
869                operation['compression'], ipp.Compression)[0]
870
871        if 'document-format' in operation:
872            document_format = verify_attribute(
873                operation['document-format'], ipp.DocumentFormat)[0]
874
875        if 'document-natural-language' in operation:
876            document_natural_language = verify_attribute(
877                operation['document_natural_language'],
878                ipp.DocumentNaturalLanguage)[0]
879
880        try:
881            self.printer.send_document(
882                job_id,
883                request.data,
884                document_name=document_name,
885                document_format=document_format,
886                document_natural_language=document_natural_language,
887                requesting_user_name=user_name,
888                compression=compression,
889                last_document=last_document)
890            attrs = self.printer.get_job_attributes(job_id)
891        except InvalidJobException:
892            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
893
894        response.attribute_groups.append(ipp.AttributeGroup(
895            ipp.AttributeTags.JOB, attrs))
896
897    @handler_for(ipp.OperationCodes.SEND_URI)
898    def send_uri(self, request, response):
899
900        """3.2.2 Send URI
901
902        This OPTIONAL operation is identical to the Send-Document
903        operation (see section 3.3.1) except that a client MUST supply
904        a URI reference ('document-uri' operation attribute) rather
905        than the document data itself.  If a Printer object supports
906        this operation, clients can use both Send-URI or Send-Document
907        operations to add new documents to an existing multi-document
908        Job object.  However, if a client needs to indicate that the
909        previous Send-URI or Send-Document was the last document, the
910        client MUST use the Send-Document operation with no document
911        data and the 'last-document' flag set to 'true' (rather than
912        using a Send-URI operation with no 'document-uri' operation
913        attribute).
914
915        If a Printer object supports this operation, it MUST also
916        support the Print-URI operation (see section 3.2.2).
917
918        The Printer object MUST validate the syntax and URI scheme of
919        the supplied URI before returning a response, just as in the
920        Print-URI operation.  The IPP Printer MAY validate the
921        accessibility of the document as part of the operation or
922        subsequently (see section 3.2.2).
923
924        Request
925        -------
926
927        Group 1: Operation Attributes
928            REQUIRED 'attributes-charset'
929            REQUIRED 'attributes-natural-language'
930            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
931            REQUIRED 'document-uri' (uri)
932            OPTIONAL 'job-uri' (uri)
933            OPTIONAL 'requesting-user-name' (name(MAX))
934            OPTIONAL 'document-name' (name(MAX))
935            OPTIONAL 'compression' (type3 keyword)
936            OPTIONAL 'document-format' (mimeMediaType)
937            OPTIONAL 'document-natural-language' (naturalLanguage)
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,
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.