diff --git a/ippserver/__main__.py b/ippserver/__main__.py index bbc44de..200a513 100644 --- a/ippserver/__main__.py +++ b/ippserver/__main__.py @@ -28,15 +28,18 @@ def parse_args(args=None): parser_save = parser_action.add_parser('save', help='Write any print jobs to disk') parser_save.add_argument('--pdf', action='store_true', default=False, help=pdf_help) + parser_save.add_argument('--ppd', type=str, metavar='PPD', help='Name of the PPD file') parser_save.add_argument('directory', metavar='DIRECTORY', help='Directory to save files into') parser_command = parser_action.add_parser('run', help='Run a command when recieving a print job') parser_command.add_argument('command', nargs=argparse.REMAINDER, metavar='COMMAND', help='Command to run') parser_command.add_argument('--pdf', action='store_true', default=False, help=pdf_help) + parser_command.add_argument('--ppd', type=str, metavar='PPD', help='Name of the PPD file') parser_command.add_argument('--env', action='store_true', default=False, help="Store Job attributes in environment (IPP_JOB_ATTRIBUTES)") parser_saverun = parser_action.add_parser('saveandrun', help='Write any print jobs to disk and the run a command on them') parser_saverun.add_argument('--pdf', action='store_true', default=False, help=pdf_help) + parser_saverun.add_argument('--ppd', type=str, metavar='PPD', help='Name of the PPD file') parser_saverun.add_argument('--env', action='store_true', default=False, help="Store Job attributes in environment (IPP_JOB_ATTRIBUTES)") parser_saverun.add_argument('directory', metavar='DIRECTORY', help='Directory to save files into') parser_saverun.add_argument('command', nargs=argparse.REMAINDER, metavar='COMMAND', help='Command to run (the filename will be added at the end)') @@ -45,6 +48,7 @@ def parse_args(args=None): parser_command = parser_action.add_parser('pc2paper', help='Post print jobs using http://www.pc2paper.org/') parser_command.add_argument('--pdf', action='store_true', default=False, help=pdf_help) + parser_command.add_argument('--ppd', type=str, metavar='PPD', help='Name of the PPD file') parser_command.add_argument('--config', metavar='CONFIG', help='File containing an address to send to, in json format') parser_loader = parser_action.add_parser('load', help='Load own behaviour') parser_loader.add_argument('path', nargs=1, metavar=['PATH'], help='Module implementing behaviour') @@ -56,15 +60,21 @@ def parse_args(args=None): def behaviour_from_parsed_args(args): if args.action == 'save': return behaviour.SaveFilePrinter( + uri=args.host + ":" + str(args.port), + ppdfile=args.ppd, directory=args.directory, filename_ext='pdf' if args.pdf else 'ps') if args.action == 'run': return behaviour.RunCommandPrinter( + uri=args.host + ":" + str(args.port), + ppdfile=args.ppd, command=args.command, use_env=args.env, filename_ext='pdf' if args.pdf else 'ps') if args.action == 'saveandrun': return behaviour.SaveAndRunPrinter( + uri=args.host + ":" + str(args.port), + ppdfile=args.ppd, command=args.command, use_env=args.env, directory=args.directory, @@ -72,6 +82,8 @@ def behaviour_from_parsed_args(args): if args.action == 'pc2paper': pc2paper_config = Pc2Paper.from_config_file(args.config) return behaviour.PostageServicePrinter( + uri=args.host + ":" + str(args.port), + ppdfile=args.ppd, service_api=pc2paper_config, filename_ext='pdf' if args.pdf else 'ps') if args.action == 'load': diff --git a/ippserver/behaviour.py b/ippserver/behaviour.py index edec9e9..b147efb 100644 --- a/ippserver/behaviour.py +++ b/ippserver/behaviour.py @@ -17,7 +17,7 @@ from .constants import ( JobStateEnum, OperationEnum, StatusCodeEnum, SectionEnum, TagEnum ) -from .ppd import BasicPostscriptPPD, BasicPdfPPD +from .ppd import BasicPostscriptPPD, BasicPdfPPD, FilePPD from .request import IppRequest @@ -48,13 +48,21 @@ def prepare_environment(ipp_request): return env +def env(var): + """Get (bytes) value from environment""" + value = os.environ.get(var) + if value: + return value.encode('utf-8') + + class Behaviour(object): """Do anything in response to IPP requests""" version = (1, 1) - base_uri = b'ipp://localhost:1234/' - printer_uri = b'ipp://localhost:1234/printer' - def __init__(self, ppd=BasicPostscriptPPD()): + def __init__(self, uri="localhost:1234", ppd=BasicPostscriptPPD()): + self.uri = uri.encode("utf-8") + self.base_uri = b'ipp://' + self.uri + self.printer_uri = b'ipp://' + self.uri + b'/printer' self.ppd = ppd def expect_page_data_follows(self, ipp_request): @@ -225,17 +233,17 @@ def printer_list_attributes(self): SectionEnum.printer, b'printer-name', TagEnum.name_without_language - ): [b'ipp-printer.py'], + ): [env("IPP_PRINTER_NAME") or b'ipp-printer.py'], ( SectionEnum.printer, b'printer-info', TagEnum.text_without_language - ): [b'Printer using ipp-printer.py'], + ): [env("IPP_PRINTER_INFO") or b'Printer using ipp-printer.py'], ( SectionEnum.printer, b'printer-make-and-model', TagEnum.text_without_language - ): [b'h2g2bob\'s ipp-printer.py 0.00'], + ): [env("IPP_PRINTER_MAKE_AND_MODEL") or b'h2g2bob\'s ipp-printer.py 0.00'], ( SectionEnum.printer, b'printer-state', @@ -293,12 +301,12 @@ def printer_list_attributes(self): SectionEnum.printer, b'document-format-default', TagEnum.mime_media_type - ): [b'application/pdf'], + ): self.ppd.document_format_default(), ( SectionEnum.printer, b'document-format-supported', TagEnum.mime_media_type - ): [b'application/pdf'], + ): self.ppd.document_format_supported(), ( SectionEnum.printer, b'printer-is-accepting-jobs', @@ -324,6 +332,13 @@ def printer_list_attributes(self): b'compression-supported', TagEnum.keyword ): [b'none'], + ( + SectionEnum.printer, + b'printer-icons', + TagEnum.uri + ): [b'http://' + self.uri + b'/printer-small.png', + b'http://' + self.uri + b'/printer.png', + b'http://' + self.uri + b'/printer-large.png'], } attr.update(self.minimal_attributes()) return attr @@ -453,16 +468,19 @@ def operation_get_job_attributes_response(self, req, _psfile): class SaveFilePrinter(StatelessPrinter): - def __init__(self, directory, filename_ext): + def __init__(self, uri, ppdfile, directory, filename_ext): self.directory = directory self.filename_ext = filename_ext - ppd = { - 'ps': BasicPostscriptPPD(), - 'pdf': BasicPdfPPD(), - }[filename_ext] + if ppdfile: + ppd = FilePPD(ppdfile) + else: + ppd = { + 'ps': BasicPostscriptPPD(), + 'pdf': BasicPdfPPD(), + }[filename_ext] - super(SaveFilePrinter, self).__init__(ppd=ppd) + super(SaveFilePrinter, self).__init__(uri=uri, ppd=ppd) def handle_postscript(self, ipp_request, postscript_file): filename = self.filename(ipp_request) @@ -485,11 +503,11 @@ def leaf_filename(self, _ipp_request): class SaveAndRunPrinter(SaveFilePrinter): - def __init__(self, directory, use_env, filename_ext, command): + def __init__(self, uri, ppdfile, directory, use_env, filename_ext, command): self.command = command self.use_env = use_env super(SaveAndRunPrinter, self).__init__( - directory=directory, filename_ext=filename_ext + uri=uri, ppdfile=ppdfile, directory=directory, filename_ext=filename_ext ) def run_after_saving(self, filename, ipp_request): @@ -506,16 +524,19 @@ def run_after_saving(self, filename, ipp_request): class RunCommandPrinter(StatelessPrinter): - def __init__(self, command, use_env, filename_ext): + def __init__(self, uri, ppdfile, command, use_env, filename_ext): self.command = command self.use_env = use_env - ppd = { - 'ps': BasicPostscriptPPD(), - 'pdf': BasicPdfPPD(), - }[filename_ext] + if ppdfile: + ppd = FilePPD(ppdfile) + else: + ppd = { + 'ps': BasicPostscriptPPD(), + 'pdf': BasicPdfPPD(), + }[filename_ext] - super(RunCommandPrinter, self).__init__(ppd=ppd) + super(RunCommandPrinter, self).__init__(uri=uri, ppd=ppd) def handle_postscript(self, ipp_request, postscript_file): logging.info('Running command for job') @@ -534,16 +555,19 @@ def handle_postscript(self, ipp_request, postscript_file): class PostageServicePrinter(StatelessPrinter): - def __init__(self, service_api, filename_ext): + def __init__(self, uri, ppdfile, service_api, filename_ext): self.service_api = service_api self.filename_ext = filename_ext - ppd = { - 'ps': BasicPostscriptPPD(), - 'pdf': BasicPdfPPD(), - }[filename_ext] + if ppdfile: + ppd = FilePPD(ppdfile) + else: + ppd = { + 'ps': BasicPostscriptPPD(), + 'pdf': BasicPdfPPD(), + }[filename_ext] - super(PostageServicePrinter, self).__init__(ppd=ppd) + super(PostageServicePrinter, self).__init__(uri=uri, ppd=ppd) def handle_postscript(self, _ipp_request, postscript_file): filename = b'ipp-server-{}.{}'.format( diff --git a/ippserver/data/printer-large.png b/ippserver/data/printer-large.png new file mode 100644 index 0000000..8f7e477 Binary files /dev/null and b/ippserver/data/printer-large.png differ diff --git a/ippserver/data/printer-small.png b/ippserver/data/printer-small.png new file mode 100644 index 0000000..3cf62c3 Binary files /dev/null and b/ippserver/data/printer-small.png differ diff --git a/ippserver/data/printer.png b/ippserver/data/printer.png new file mode 100644 index 0000000..6cb34f5 Binary files /dev/null and b/ippserver/data/printer.png differ diff --git a/ippserver/ppd.py b/ippserver/ppd.py index a8e281e..bbe2b88 100644 --- a/ippserver/ppd.py +++ b/ippserver/ppd.py @@ -2,11 +2,53 @@ from __future__ import absolute_import from __future__ import print_function +from .server import local_file_location + class PPD(object): def text(self): raise NotImplementedError() + def document_format_default(self): + """Scan the PPD for *cupsFilter2 and extract the 'best final' MIME-type""" + types = [] + for line in self.text().split(b'\n'): + if line.startswith(b'*cupsFilter2'): + line = line.split(b':', maxsplit=1)[1].strip().lstrip(b'"').rstrip(b'"') + types.append(dict(zip(("src", "dst", "cost", "filter"), line.split()))) + types = sorted(types, key=lambda type: type["cost"]) + destination_types = [type["src"] for type in types] + if destination_types: + return [destination_types[0]] + else: + # assume PostScript + return [b'application/postscript'] + + def document_format_supported(self): + """Scan the PPD for *cupsFilter2 and extract the 'supported' MIME-types""" + types = [] + for line in self.text().split(b'\n'): + if line.startswith(b'*cupsFilter2'): + line = line.split(b':', maxsplit=1)[1].strip().lstrip(b'"').rstrip(b'"') + types.append(dict(zip(("src", "dst", "cost", "filter"), line.split()))) + types = sorted(types, key=lambda type: type["cost"]) + destination_types = [type["src"] for type in types] + if destination_types: + return destination_types + else: + # assume PostScript + return [b'application/postscript', b'application/octet-stream'] + + +class FilePPD(PPD): + def __init__(self, filename): + filepath = local_file_location(filename) + with open(filepath, "r") as ppdfile: + self.ppd = ppdfile.read() + + def text(self): + return self.ppd.encode("ascii") + class BasicPostscriptPPD(PPD): product = 'ipp-server' diff --git a/ippserver/server.py b/ippserver/server.py index 9b330c6..2bd1e0a 100644 --- a/ippserver/server.py +++ b/ippserver/server.py @@ -20,6 +20,9 @@ def local_file_location(filename): + filename = os.path.basename(filename) + if os.environ.get("IPP_DATA_DIR"): + return os.path.join(os.environ.get("IPP_DATA_DIR"), filename) return os.path.join(os.path.dirname(__file__), 'data', filename) @@ -115,6 +118,12 @@ def handle_www(self): status=200, content_type='text/plain' ) self.wfile.write(self.server.behaviour.ppd.text()) + elif self.path.endswith('.png'): + self.send_headers( + status=200, content_type='image/png' + ) + with open(local_file_location(self.path), 'rb') as wwwfile: + self.wfile.write(wwwfile.read()) else: self.send_headers( status=404, content_type='text/plain'