source: server/lib/gutenbach/server/requests.py @ 72c3fcb

no-cups
Last change on this file since 72c3fcb was 72c3fcb, checked in by Kyle Brogle <broglek@…>, 12 years ago

fixed doc formatting and send_uri formatting

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