source: server/lib/gutenbach/server/requests.py @ 21b140d

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

Added Send_URI

  • Property mode set to 100644
File size: 41.8 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        raise ipp.errors.ServerErrorOperationNotSupported
271
272    @handler_for(ipp.OperationCodes.CREATE_JOB)
273    def create_job(self, request, response):
274        """RFC 2911: 3.2.4 Create-Job Operation
275
276        This OPTIONAL operation is similar to the Print-Job operation
277        (section 3.2.1) except that in the Create-Job request, a
278        client does not supply document data or any reference to
279        document data. Also, the client does not supply any of the
280        'document-name', 'document- format', 'compression', or
281        'document-natural-language' operation attributes. This
282        operation is followed by one or more Send-Document or Send-URI
283        operations. In each of those operation requests, the client
284        OPTIONALLY supplies the 'document-name', 'document-format',
285        and 'document-natural-language' attributes for each document
286        in the multi-document Job object.
287
288        Request
289        -------
290        Group 1: Operation Attributes
291            REQUIRED 'attributes-charset'
292            REQUIRED 'attributes-natural-language'
293            REQUIRED 'printer-uri' (uri)
294            OPTIONAL 'requesting-user-name' (name(MAX))
295            OPTIONAL 'job-name' (name(MAX))
296            OPTIONAL 'ipp-attribute-fidelity' (boolean)
297            OPTIONAL 'job-k-octets' (integer(0:MAX))
298            OPTIONAL 'job-impressions' (integer(0:MAX))
299            OPTIONAL 'job-media-sheets' (integer(0:MAX))
300        Group 2: Job Template Attributes
301
302        Response
303        --------
304        Group 1: Operation Attributes
305            OPTIONAL 'status-message' (text(255))
306            OPTIONAL 'detailed-status-message' (text(MAX))
307            REQUIRED 'attributes-charset'
308            REQUIRED 'attributes-natural-language'
309        Group 2: Unsupported Attributes
310        Group 3: Job Object Attributes
311            REQUIRED 'job-uri' (uri)
312            REQUIRED 'job-id' (integer(1:MAX))
313            REQUIRED 'job-state' (type1 enum)
314            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
315            OPTIONAL 'job-state-message' (text(MAX))
316            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
317       
318        """
319
320        operation = request.attribute_groups[0]
321
322        printer_uri = None
323        requesting_user_name = None
324        job_name = None
325        ipp_attribute_fidelity=None
326        job_k_octets = None
327
328        # requested printer uri
329        if 'printer-uri' not in operation:
330            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
331        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
332        if printer_uri not in self.printer.uris:
333            raise ipp.errors.ClientErrorAttributes(
334                str(operation['printer-uri']), operation['printer-uri'])
335
336        if 'requesting-user-name' in operation:
337            user_name = verify_attribute(
338                operation['requesting-user-name'], ipp.RequestingUserName)[0]
339
340        if 'job-name' in operation:
341            job_name = verify_attribute(
342                operation['job-name'], ipp.JobName)[0]
343
344        if 'job-k-octets' in operation:
345            job_k_octets = verify_attribute(
346                operation['job-k-octets'], ipp.JobKOctets)[0]
347
348        if 'ipp-attribute-fidelity' in operation:
349            pass # don't care
350        if 'job-impressions' in operation:
351            pass # don't care
352        if 'job-media-sheets' in operation:
353            pass # don't care
354
355        # get attributes from the printer and add to response
356        job_id = self.printer.create_job(
357            requesting_user_name=requesting_user_name,
358            job_name=job_name,
359            job_k_octets=job_k_octets)
360        attrs = self.printer.get_job_attributes(job_id)
361        response.attribute_groups.append(ipp.AttributeGroup(
362            ipp.AttributeTags.JOB, attrs))
363   
364    @handler_for(ipp.OperationCodes.PAUSE_PRINTER)
365    def pause_printer(self, request, response):
366        raise ipp.errors.ServerErrorOperationNotSupported
367
368    @handler_for(ipp.OperationCodes.RESUME_PRINTER)
369    def resume_printer(self, request, response):
370        raise ipp.errors.ServerErrorOperationNotSupported
371
372    @handler_for(ipp.OperationCodes.GET_PRINTER_ATTRIBUTES)
373    def get_printer_attributes(self, request, response):
374        """RFC 2911: 3.2.5 Get-Printer-Attributes Operation
375
376        This REQUIRED operation allows a client to request the values
377        of the attributes of a Printer object.
378       
379        In the request, the client supplies the set of Printer
380        attribute names and/or attribute group names in which the
381        requester is interested. In the response, the Printer object
382        returns a corresponding attribute set with the appropriate
383        attribute values filled in.
384
385        Request
386        -------
387
388        Group 1: Operation Attributes
389            REQUIRED 'attributes-charset'
390            REQUIRED 'attributes-natural-language'
391            REQUIRED 'printer-uri' (uri)
392            OPTIONAL 'requesting-user-name' (name(MAX))
393            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
394            OPTIONAL 'document-format' (mimeMediaType)
395           
396        Response
397        --------
398
399        Group 1: Operation Attributes
400            OPTIONAL 'status-message' (text(255))
401            OPTIONAL 'detailed-status-message' (text(MAX))
402            REQUIRED 'attributes-charset'
403            REQUIRED 'attributes-natural-language'
404        Group 2: Unsupported Attributes
405        Group 3: Printer Object Attributes
406
407        """
408
409        operation = request.attribute_groups[0]
410
411        printer_uri = None
412        requesting_user_name = None
413        requested_attributes = None
414        document_format = None
415
416        # requested printer uri
417        if 'printer-uri' not in operation:
418            raise ipp.errors.ClientErrorBadRequest("Missing 'printer-uri' attribute")
419        printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
420        if printer_uri not in self.printer.uris:
421            raise ipp.errors.ClientErrorAttributes(
422                str(operation['printer-uri']), operation['printer-uri'])
423
424        # optional attributes
425        if 'requesting-user-name' in operation:
426            user_name = verify_attribute(
427                operation['requesting-user-name'], ipp.RequestingUserName)[0]
428           
429        if 'requested-attributes' in operation:
430            requested_attributes = verify_attribute(
431                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
432
433        if 'document-format' in operation:
434            pass # XXX: todo
435
436        # get attributes from the printer and add to response
437        response.attribute_groups.append(ipp.AttributeGroup(
438            ipp.AttributeTags.PRINTER,
439            self.printer.get_printer_attributes(
440                requested_attributes=requested_attributes)))
441
442    @handler_for(ipp.OperationCodes.SET_PRINTER_ATTRIBUTES)
443    def set_printer_attributes(self, request, response):
444        raise ipp.errors.ServerErrorOperationNotSupported
445
446    ##### Job Commands
447
448    @handler_for(ipp.OperationCodes.CANCEL_JOB)
449    def cancel_job(self, request, response):
450        """3.3.3 Cancel-Job Operation
451
452        This REQUIRED operation allows a client to cancel a Print Job from
453        the time the job is created up to the time it is completed, canceled,
454        or aborted. Since a Job might already be printing by the time a
455        Cancel-Job is received, some media sheet pages might be printed
456        before the job is actually terminated.
457
458        The IPP object MUST accept or reject the request based on the job's
459        current state and transition the job to the indicated new state as
460        follows:
461
462        Current State       New State           Response
463        -----------------------------------------------------------------
464        pending             canceled            successful-ok
465        pending-held        canceled            successful-ok
466        processing          canceled            successful-ok
467        processing          processing          successful-ok               See Rule 1
468        processing          processing          client-error-not-possible   See Rule 2
469        processing-stopped  canceled            successful-ok
470        processing-stopped  processing-stopped  successful-ok               See Rule 1
471        processing-stopped  processing-stopped  client-error-not-possible   See Rule 2
472        completed           completed           client-error-not-possible
473        canceled            canceled            client-error-not-possible
474        aborted             aborted             client-error-not-possible
475
476        Rule 1: If the implementation requires some measurable time to
477        cancel the job in the 'processing' or 'processing-stopped' job
478        states, the IPP object MUST add the 'processing-to-stop-point'
479        value to the job's 'job-state-reasons' attribute and then
480        transition the job to the 'canceled' state when the processing
481        ceases (see section 4.3.8).
482
483        Rule 2: If the Job object already has the
484        'processing-to-stop-point' value in its 'job-state-reasons'
485        attribute, then the Printer object MUST reject a Cancel-Job
486        operation.
487
488        Access Rights: The authenticated user (see section 8.3)
489        performing this operation must either be the job owner or an
490        operator or administrator of the Printer object (see Sections
491        1 and 8.5).  Otherwise, the IPP object MUST reject the
492        operation and return: 'client-error-forbidden',
493        'client-error-not-authenticated', or
494        'client-error-not-authorized' as appropriate.
495
496        Request
497        -------
498
499        Group 1: Operation Attributes
500            REQUIRED 'attributes-charset'
501            REQUIRED 'attributes-natural-language'
502            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
503              -or-   'job-uri' (uri)
504            OPTIONAL 'requesting-user-name' (name(MAX))
505            OPTIONAL 'message' (text(127))
506           
507        Response
508        --------
509
510        Group 1: Operation Attributes
511            OPTIONAL 'status-message' (text(255))
512            OPTIONAL 'detailed-status-message' (text(MAX))
513            REQUIRED 'attributes-charset'
514            REQUIRED 'attributes-natural-language'
515        Group 2: Unsupported Attributes
516
517        """
518
519        operation = request.attribute_groups[0]
520
521        job_id = None
522        printer_uri = None
523        requesting_user_name = None
524        message = None
525
526        # required attributes
527        if 'job-id' in operation and 'printer-uri' in operation:
528            job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
529            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
530            if printer_uri not in self.printer.uris:
531                raise ipp.errors.ClientErrorAttributes(
532                    str(operation['printer-uri']), operation['printer-uri'])
533
534        elif 'job-uri' in operation:
535            job_uri = verify_attribute(operation['job-uri'], ipp.JobUri)[0]
536            job_id = int(job_uri.split("/")[-1])
537
538        if 'requesting-user-name' in operation:
539            requesting_user_name = verify_attribute(
540                operation['requesting-user-name'], ipp.RequestingUserName)[0]
541
542        try:
543            self.printer.cancel_job(job_id, requesting_user_name=requesting_user_name)
544        except InvalidJobException:
545            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
546
547    @handler_for(ipp.OperationCodes.SEND_DOCUMENT)
548    def send_document(self, request, response):
549        """3.3.1 Send-Document Operation
550       
551        This OPTIONAL operation allows a client to create a
552        multi-document Job object that is initially 'empty' (contains
553        no documents). In the Create-Job response, the Printer object
554        returns the Job object's URI (the 'job-uri' attribute) and the
555        Job object's 32-bit identifier (the 'job-id' attribute). For
556        each new document that the client desires to add, the client
557        uses a Send-Document operation. Each Send- Document Request
558        contains the entire stream of document data for one document.
559
560        If the Printer supports this operation but does not support
561        multiple documents per job, the Printer MUST reject subsequent
562        Send-Document operations supplied with data and return the
563        'server-error-multiple- document-jobs-not-supported'. However,
564        the Printer MUST accept the first document with a 'true' or
565        'false' value for the 'last-document' operation attribute (see
566        below), so that clients MAY always submit one document jobs
567        with a 'false' value for 'last-document' in the first
568        Send-Document and a 'true' for 'last-document' in the second
569        Send-Document (with no data).
570       
571        Since the Create-Job and the send operations (Send-Document or
572        Send- URI operations) that follow could occur over an
573        arbitrarily long period of time for a particular job, a client
574        MUST send another send operation within an IPP Printer defined
575        minimum time interval after the receipt of the previous
576        request for the job. If a Printer object supports the
577        Create-Job and Send-Document operations, the Printer object
578        MUST support the 'multiple-operation-time-out' attribute (see
579        section 4.4.31). This attribute indicates the minimum number
580        of seconds the Printer object will wait for the next send
581        operation before taking some recovery action.
582
583        An IPP object MUST recover from an errant client that does not
584        supply a send operation, sometime after the minimum time
585        interval specified by the Printer object's
586        'multiple-operation-time-out' attribute.
587
588        Access Rights: The authenticated user (see section 8.3)
589        performing this operation must either be the job owner (as
590        determined in the Create-Job operation) or an operator or
591        administrator of the Printer object (see Sections 1 and
592        8.5). Otherwise, the IPP object MUST reject the operation and
593        return: 'client-error-forbidden', 'client-
594        error-not-authenticated', or 'client-error-not-authorized' as
595        appropriate.
596
597        Request
598        -------
599
600        Group 1: Operation Attributes
601            REQUIRED 'attributes-charset'
602            REQUIRED 'attributes-natural-language'
603            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
604              -or-   'job-uri' (uri)
605            OPTIONAL 'requesting-user-name' (name(MAX))
606            OPTIONAL 'document-name' (name(MAX))
607            OPTIONAL 'compression' (type3 keyword)
608            OPTIONAL 'document-format' (mimeMediaType)
609            OPTIONAL 'document-natural-language' (naturalLanguage)
610            OPTIONAL 'last-document' (boolean)
611        Group 2: Document Content
612           
613        Response
614        --------
615
616        Group 1: Operation Attributes
617            OPTIONAL 'status-message' (text(255))
618            OPTIONAL 'detailed-status-message' (text(MAX))
619            REQUIRED 'attributes-charset'
620            REQUIRED 'attributes-natural-language'
621        Group 2: Unsupported Attributes
622        Group 3: Job Object Attributes
623            REQUIRED 'job-uri' (uri)
624            REQUIRED 'job-id' (integer(1:MAX))
625            REQUIRED 'job-state' (type1 enum)
626            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
627            OPTIONAL 'job-state-message' (text(MAX))
628            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))
629
630        """
631       
632        operation = request.attribute_groups[0]
633
634        job_id = None
635        printer_uri = None
636        requesting_user_name = None
637        document_name = None
638        compression = None
639        document_format = None
640        document_natural_language = None
641        last_document = None
642
643        # required attributes
644        if 'job-id' not in operation:
645            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
646        job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
647
648        if 'last-document' not in operation:
649            raise ipp.errors.ClientErrorBadRequest("Missing 'last-document' attribute")
650        last_document = verify_attribute(operation['last-document'], ipp.LastDocument)[0]
651        if not last_document:
652            raise ipp.errors.ServerErrorMultipleJobsNotSupported
653
654        # optional attributes
655        if 'printer-uri' in operation:
656            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
657            if printer_uri not in self.printer.uris:
658                raise ipp.errors.ClientErrorAttributes(
659                    str(operation['printer-uri']), operation['printer-uri'])
660
661        if 'requesting-user-name' in operation:
662            user_name = verify_attribute(
663                operation['requesting-user-name'], ipp.RequestingUserName)[0]
664
665        if 'document-name' in operation:
666            document_name = verify_attribute(
667                operation['document-name'], ipp.DocumentName)[0]
668
669        if 'compression' in operation:
670            compression = verify_attribute(
671                operation['compression'], ipp.Compression)[0]
672
673        if 'document-format' in operation:
674            document_format = verify_attribute(
675                operation['document-format'], ipp.DocumentFormat)[0]
676
677        if 'document-natural-language' in operation:
678            document_natural_language = verify_attribute(
679                operation['document_natural_language'],
680                ipp.DocumentNaturalLanguage)[0]
681
682        try:
683            self.printer.send_document(
684                job_id,
685                request.data,
686                document_name=document_name,
687                document_format=document_format,
688                document_natural_language=document_natural_language,
689                requesting_user_name=user_name,
690                compression=compression,
691                last_document=last_document)
692            attrs = self.printer.get_job_attributes(job_id)
693        except InvalidJobException:
694            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
695
696        response.attribute_groups.append(ipp.AttributeGroup(
697            ipp.AttributeTags.JOB, attrs))
698
699    @handler_for(ipp.OperationCodes.SEND_URI)
700    def send_uri(self, request, response):
701        """This OPTIONAL operation is identical to the Send-Document operation
702        (see section 3.3.1) except that a client MUST supply a URI reference
703        ("document-uri" operation attribute) rather than the document data
704        itself.  If a Printer object supports this operation, clients can use
705        both Send-URI or Send-Document operations to add new documents to an
706        existing multi-document Job object.  However, if a client needs to
707        indicate that the previous Send-URI or Send-Document was the last
708        document,  the client MUST use the Send-Document operation with no
709        document data and the "last-document" flag set to 'true' (rather than
710        using a Send-URI operation with no "document-uri" operation
711        attribute).
712
713        If a Printer object supports this operation, it MUST also support the
714        Print-URI operation (see section 3.2.2).
715
716        The Printer object MUST validate the syntax and URI scheme of the
717        supplied URI before returning a response, just as in the Print-URI
718        operation.  The IPP Printer MAY validate the accessibility of the
719        document as part of the operation or subsequently (see section
720        3.2.2).
721
722        Request
723        -------
724
725        Group 1: Operation Attributes
726            REQUIRED 'attributes-charset'
727            REQUIRED 'attributes-natural-language'
728            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
729            REQUIRED 'document-uri' (uri)
730              -or-   'job-uri' (uri)
731            OPTIONAL 'requesting-user-name' (name(MAX))
732            OPTIONAL 'document-name' (name(MAX))
733            OPTIONAL 'compression' (type3 keyword)
734            OPTIONAL 'document-format' (mimeMediaType)
735            OPTIONAL 'document-natural-language' (naturalLanguage)
736        Group 2: Document Content
737           
738        Response
739        --------
740
741        Group 1: Operation Attributes
742            OPTIONAL 'status-message' (text(255))
743            OPTIONAL 'detailed-status-message' (text(MAX))
744            REQUIRED 'attributes-charset'
745            REQUIRED 'attributes-natural-language'
746        Group 2: Unsupported Attributes
747        Group 3: Job Object Attributes
748            REQUIRED 'job-uri' (uri)
749            REQUIRED 'job-id' (integer(1:MAX))
750            REQUIRED 'job-state' (type1 enum)
751            REQUIRED 'job-state-reasons' (1setOf type2 keyword)
752            OPTIONAL 'job-state-message' (text(MAX))
753            OPTIONAL 'number-of-intervening-jobs' (integer(0:MAX))"""
754       
755        operation = request.attribute_groups[0]
756
757        job_id = None
758        printer_uri = None
759        requesting_user_name = None
760        document_name = None
761        compression = None
762        document_format = None
763        document_natural_language = None
764        last_document = None
765
766        # required attributes
767        if 'job-id' not in operation:
768            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
769        job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
770
771        if 'last-document' not in operation:
772            raise ipp.errors.ClientErrorBadRequest("Missing 'last-document' attribute")
773        last_document = verify_attribute(operation['last-document'], ipp.LastDocument)[0]
774
775        if 'document-uri' not in operation:
776            raise ipp.errors.ClientErrorBadRequest("Missing 'document-uri' attribute")
777        document_uri = verify_attribute(operation['document-uri'], ipp.DocumentUri)[0]
778        if not last_document:
779            raise ipp.errors.ServerErrorMultipleJobsNotSupported
780
781        # optional attributes
782        if 'printer-uri' in operation:
783            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
784            if printer_uri not in self.printer.uris:
785                raise ipp.errors.ClientErrorAttributes(
786                    str(operation['printer-uri']), operation['printer-uri'])
787
788        if 'requesting-user-name' in operation:
789            user_name = verify_attribute(
790                operation['requesting-user-name'], ipp.RequestingUserName)[0]
791
792        if 'document-name' in operation:
793            document_name = verify_attribute(
794                operation['document-name'], ipp.DocumentName)[0]
795
796        if 'compression' in operation:
797            compression = verify_attribute(
798                operation['compression'], ipp.Compression)[0]
799
800        if 'document-format' in operation:
801            document_format = verify_attribute(
802                operation['document-format'], ipp.DocumentFormat)[0]
803
804        if 'document-natural-language' in operation:
805            document_natural_language = verify_attribute(
806                operation['document_natural_language'],
807                ipp.DocumentNaturalLanguage)[0]
808
809        try:
810            self.printer.send_uri(
811                job_id,
812                document_uri=document_uri,
813                document_name=document_name,
814                document_format=document_format,
815                document_natural_language=document_natural_language,
816                requesting_user_name=user_name,
817                compression=compression,
818                last_document=last_document)
819            attrs = self.printer.get_job_attributes(job_id)
820        except InvalidJobException:
821            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
822
823        response.attribute_groups.append(ipp.AttributeGroup(
824            ipp.AttributeTags.JOB, attrs))
825
826    @handler_for(ipp.OperationCodes.GET_JOB_ATTRIBUTES)
827    def get_job_attributes(self, request, response):
828        """3.3.4 Get-Job-Attributes Operation
829
830        This REQUIRED operation allows a client to request the values
831        of attributes of a Job object and it is almost identical to
832        the Get- Printer-Attributes operation (see section 3.2.5). The
833        only differences are that the operation is directed at a Job
834        object rather than a Printer object, there is no
835        'document-format' operation attribute used when querying a Job
836        object, and the returned attribute group is a set of Job
837        object attributes rather than a set of Printer object
838        attributes.
839
840        For Jobs, the possible names of attribute groups are:
841          - 'job-template': the subset of the Job Template attributes
842            that apply to a Job object (the first column of the table
843            in Section 4.2) that the implementation supports for Job
844            objects.
845          - 'job-description': the subset of the Job Description
846            attributes specified in Section 4.3 that the
847            implementation supports for Job objects.
848          - 'all': the special group 'all' that includes all
849            attributes that the implementation supports for Job
850            objects.
851
852        Since a client MAY request specific attributes or named
853        groups, there is a potential that there is some overlap. For
854        example, if a client requests, 'job-name' and
855        'job-description', the client is actually requesting the
856        'job-name' attribute once by naming it explicitly, and once by
857        inclusion in the 'job-description' group. In such cases, the
858        Printer object NEED NOT return the attribute only once in the
859        response even if it is requested multiple times. The client
860        SHOULD NOT request the same attribute in multiple ways.
861
862        It is NOT REQUIRED that a Job object support all attributes
863        belonging to a group (since some attributes are
864        OPTIONAL). However it is REQUIRED that each Job object support
865        all these group names.
866
867        Request
868        -------
869
870        Group 1: Operation Attributes
871            REQUIRED 'attributes-charset'
872            REQUIRED 'attributes-natural-language'
873            REQUIRED 'job-id' (integer(1:MAX)) and 'printer-uri' (uri)
874              -or-   'job-uri' (uri)
875            OPTIONAL 'requesting-user-name' (name(MAX))
876            OPTIONAL 'requested-attributes' (1setOf keyword)
877           
878        Response
879        --------
880
881        Group 1: Operation Attributes
882            OPTIONAL 'status-message' (text(255))
883            OPTIONAL 'detailed-status-message' (text(MAX))
884            REQUIRED 'attributes-charset'
885            REQUIRED 'attributes-natural-language'
886        Group 2: Unsupported Attributes
887        Group 3: Job Object Attributes
888
889        """
890       
891        operation = request.attribute_groups[0]
892
893        job_id = None
894        printer_uri = None
895        requesting_user_name = None
896        requested_attributes = None
897
898        # required attributes
899        if 'job-id' not in operation:
900            raise ipp.errors.ClientErrorBadRequest("Missing 'job-id' attribute")
901        job_id = verify_attribute(operation['job-id'], ipp.JobId)[0]
902
903        # optional attributes
904        if 'printer-uri' in operation:
905            printer_uri = verify_attribute(operation['printer-uri'], ipp.PrinterUri)[0]
906            if printer_uri not in self.printer.uris:
907                raise ipp.errors.ClientErrorAttributes(
908                    str(operation['printer-uri']), operation['printer-uri'])
909
910        # optional attributes
911        if 'requesting-user-name' in operation:
912            user_name = verify_attribute(
913                operation['requesting-user-name'], ipp.RequestingUserName)[0]
914           
915        if 'requested-attributes' in operation:
916            requested_attributes = verify_attribute(
917                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
918
919        # get the job attributes and add them to the response
920        try:
921            attrs = self.printer.get_job_attributes(
922                job_id,
923                requested_attributes=requested_attributes)
924        except InvalidJobException:
925            raise ipp.errors.ClientErrorNotFound("bad job: %d" % job_id)
926
927        response.attribute_groups.append(ipp.AttributeGroup(
928            ipp.AttributeTags.JOB, attrs))
929
930    @handler_for(ipp.OperationCodes.SET_JOB_ATTRIBUTES)
931    def set_job_attributes(self, request, response):
932        raise ipp.errors.ServerErrorOperationNotSupported
933
934    @handler_for(ipp.OperationCodes.RESTART_JOB)
935    def restart_job(self, request, response):
936        raise ipp.errors.ServerErrorOperationNotSupported
937
938    @handler_for(ipp.OperationCodes.PROMOTE_JOB)
939    def promote_job(self, request, response):
940        raise ipp.errors.ServerErrorOperationNotSupported
941
942    ##### CUPS Specific Commands
943
944    @handler_for(ipp.OperationCodes.CUPS_GET_DOCUMENT)
945    def cups_get_document(self, request, response):
946        raise ipp.errors.ServerErrorOperationNotSupported
947
948    @handler_for(ipp.OperationCodes.CUPS_GET_DEFAULT)
949    def cups_get_default(self, request, response):
950        """The CUPS-Get-Default operation (0x4001) returns the default
951        printer URI and attributes.
952
953        Request
954        -------
955
956        Group 1: Operation Attributes
957            REQUIRED 'attributes-charset'
958            REQUIRED 'attributes-natural-language'
959            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
960
961        Response
962        --------
963
964        Group 1: Operation Attributes
965            OPTIONAL 'status-message' (text(255))
966            REQUIRED 'attributes-charset'
967            REQUIRED 'attributes-natural-language'
968        Group 2: Printer Object Attributes
969
970        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_DEFAULT )
971
972        """
973
974        operation = request.attribute_groups[0]
975        requested_attributes = None
976       
977        if 'requested-attributes' in operation:
978            requested_attributes = verify_attribute(
979                operation['requested-attributes'], ipp.RequestedAttributes, length=None)
980
981        # get attributes from the printer and add to response
982        attrs = self.printer.get_printer_attributes(
983            requested_attributes=requested_attributes)
984        response.attribute_groups.append(ipp.AttributeGroup(
985            ipp.AttributeTags.PRINTER, attrs))
986
987    @handler_for(ipp.OperationCodes.CUPS_GET_PRINTERS)
988    def cups_get_printers(self, request, response):
989        """The CUPS-Get-Printers operation (0x4002) returns the
990        printer attributes for every printer known to the system. This
991        may include printers that are not served directly by the
992        server.
993
994        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_PRINTERS )
995           
996        """
997
998        # get attributes from the printer and add to response
999        response.attribute_groups.append(ipp.AttributeGroup(
1000            ipp.AttributeTags.PRINTER, self.printer.get_printer_attributes()))
1001
1002    @handler_for(ipp.OperationCodes.CUPS_GET_CLASSES)
1003    def cups_get_classes(self, request, response):
1004        """The CUPS-Get-Classes operation (0x4005) returns the printer
1005        attributes for every printer class known to the system. This
1006        may include printer classes that are not served directly by
1007        the server.
1008
1009        Request
1010        -------
1011
1012        Group 1: Operation Attributes
1013            REQUIRED 'attributes-charset'
1014            REQUIRED 'attributes-natural-language'
1015            OPTIONAL 'first-printer-name' (name(127)) CUPS 1.2/Mac OS X 10.5
1016            OPTIONAL 'limit' (integer (1:MAX))
1017            OPTIONAL 'printer-location' (text(127)) CUPS 1.1.7
1018            OPTIONAL 'printer-type' (type2 enum) CUPS 1.1.7
1019            OPTIONAL 'printer-type-mask' (type2 enum) CUPS 1.1.7
1020            OPTIONAL 'requested-attributes' (1setOf keyword)
1021            OPTOINAL 'requested-user-name' (name(127)) CUPS 1.2/Mac OS X 10.5
1022            OPTIONAL 'requested-attributes' (1setOf type2 keyword)
1023
1024        Response
1025        --------
1026
1027        Group 1: Operation Attributes
1028            OPTIONAL 'status-message' (text(255))
1029            REQUIRED 'attributes-charset'
1030            REQUIRED 'attributes-natural-language'
1031        Group 2: Printer Class Object Attributes
1032
1033        (Source: http://www.cups.org/documentation.php/spec-ipp.html#CUPS_GET_CLASSES )
1034
1035        """
1036
1037        raise ipp.errors.ServerErrorOperationNotSupported
1038
Note: See TracBrowser for help on using the repository browser.