class FilePrintServer(StubServerMixin): def __init__(self): self.lpadmin = "" self.lpinfo = "" self.add_printer_options = [] self.file_transfer = FileTransferAttributes() def init(self, opts): self.file_transfer.init_opts(opts, can_ask=False) self.lpadmin = opts.lpadmin self.lpinfo = opts.lpinfo self.add_printer_options = opts.add_printer_options #server-side printer handling is only for posix via pycups for now: self.postscript_printer = opts.postscript_printer self.pdf_printer = opts.pdf_printer def threaded_setup(self): self.init_printing() def init_sockets(self, sockets): #verify we have a local socket for printing: unixsockets = [ info for socktype, _, info, _ in sockets if socktype == "unix-domain" ] printlog("local unix domain sockets we can use for printing: %s", unixsockets) if not unixsockets and self.file_transfer.printing: if not WIN32: printlog.warn("Warning: no local sockets defined,") printlog.warn(" disabling printer forwarding") printlog("printer forwarding disabled") self.file_transfer.printing = False def get_server_features(self, _source): f = self.file_transfer.get_file_transfer_features() f["printer.attributes"] = ("printer-info", "device-uri") ftf = self.file_transfer.get_file_transfer_features() if self.file_transfer.file_transfer: ftf["request-file"] = True f.update(ftf) return f def get_info(self, _proto): d = {} if POSIX: d.update({ "lpadmin": self.lpadmin, "lpinfo": self.lpinfo, "add-printer-options": self.add_printer_options, }) if self.file_transfer.printing: from xpra.platform.printing import get_info d.update(get_info()) info = {"printing": d} if self.file_transfer.file_transfer: fti = self.file_transfer.get_info() if self.file_transfer.file_transfer: fti["request-file"] = True info["file"] = fti return info def init_printing(self): printing = self.file_transfer.printing if not printing or WIN32: return try: from xpra.platform import pycups_printing pycups_printing.set_lpadmin_command(self.lpadmin) pycups_printing.set_lpinfo_command(self.lpinfo) pycups_printing.set_add_printer_options(self.add_printer_options) if self.postscript_printer: pycups_printing.add_printer_def("application/postscript", self.postscript_printer) if self.pdf_printer: pycups_printing.add_printer_def("application/pdf", self.pdf_printer) printer_definitions = pycups_printing.validate_setup() printing = bool(printer_definitions) if printing: printlog.info( "printer forwarding enabled using %s", " and ".join( x.replace("application/", "") for x in printer_definitions)) else: printlog.warn("Warning: no printer definitions found,") printlog.warn(" cannot enable printer forwarding") except ImportError as e: printlog("printing module is not installed: %s", e) printing = False except Exception: printlog.error("Error: failed to set lpadmin and lpinfo commands", exc_info=True) printing = False #verify that we can talk to the socket: auth_class = self.auth_classes.get("unix-domain") if printing and auth_class: try: #this should be the name of the auth module: auth_name = auth_class[0] except: auth_name = str(auth_class) if auth_name not in ("none", "file"): printlog.warn("Warning: printer forwarding cannot be used,") printlog.warn( " it conflicts with socket authentication module '%r'", auth_name) printing = False #update file transfer attributes since printing nay have been disabled here self.file_transfer.printing = printing printlog("init_printing() printing=%s", printing) def _process_print(self, _proto, packet): #ie: from the xpraforwarder we call this command: #command = ["xpra", "print", "socket:/path/tosocket", # filename, mimetype, source, title, printer, no_copies, print_options] assert self.file_transfer.printing #printlog("_process_print(%s, %s)", proto, packet) if len(packet) < 3: printlog.error("Error: invalid print packet, only %i arguments", len(packet)) printlog.error(" %s", [repr_ellipsized(x) for x in packet]) return def s(b): try: return b.decode("utf-8") except Exception: return bytestostr(b) filename = s(packet[1]) file_data = packet[2] mimetype, source_uuid, title, printer, no_copies, print_options = "", "*", "unnamed document", "", 1, "" if len(packet) >= 4: mimetype = bytestostr(packet[3]) if len(packet) >= 5: source_uuid = bytestostr(packet[4]) if len(packet) >= 6: title = s(packet[5]) if len(packet) >= 7: printer = bytestostr(packet[6]) if len(packet) >= 8: no_copies = int(packet[7]) if len(packet) >= 9: print_options = packet[8] #parse and validate: if len(mimetype) >= 128: printlog.error("Error: invalid mimetype in print packet:") printlog.error(" %s", repr_ellipsized(mimetype)) return if not isinstance(print_options, dict): s = bytestostr(print_options) print_options = {} for x in s.split(" "): parts = x.split("=", 1) if len(parts) == 2: print_options[parts[0]] = parts[1] printlog("process_print: %s", (filename, mimetype, "%s bytes" % len(file_data), source_uuid, title, printer, no_copies, print_options)) printlog("process_print: got %s bytes for file %s", len(file_data), filename) #parse the print options: u = hashlib.sha1() u.update(file_data) printlog("sha1 digest: %s", u.hexdigest()) options = { "printer": printer, "title": title, "copies": no_copies, "options": print_options, "sha1": u.hexdigest(), } printlog("parsed printer options: %s", options) if SAVE_PRINT_JOBS: self._save_print_job(filename, file_data) sent = 0 sources = tuple(self._server_sources.values()) printlog("will try to send to %i clients: %s", len(sources), sources) for ss in sources: if source_uuid not in ("*", ss.uuid): printlog("not sending to %s (uuid=%s, wanted uuid=%s)", ss, ss.uuid, source_uuid) continue if not ss.printing: if source_uuid != '*': printlog.warn("Warning: printing is not enabled for:") printlog.warn(" %s", ss) else: printlog("printing is not enabled for %s", ss) continue if not ss.printers: printlog.warn("Warning: client %s does not have any printers", ss.uuid) continue if printer not in ss.printers: printlog.warn( "Warning: client %s does not have a '%s' printer", ss.uuid, printer) continue printlog("'%s' sent to %s for printing on '%s'", bytestostr(title or filename), ss, printer) if ss.send_file(filename, mimetype, file_data, len(file_data), True, True, options): sent += 1 #warn if not sent: if sent == 0: l = printlog.warn else: l = printlog.info unit_str, v = to_std_unit(len(file_data), unit=1024) l("'%s' (%i%sB) sent to %i client%s for printing", title or filename, v, unit_str, sent, engs(sent)) def _save_print_job(self, filename, file_data): try: save_filename = os.path.join(SAVE_PRINT_JOBS, filename) with open(save_filename, "wb") as f: f.write(file_data) printlog.info("saved print job to: %s", save_filename) except Exception as e: printlog.error("Error: failed to save print job to %s", save_filename) printlog.error(" %s", e) def _process_printers(self, proto, packet): if not self.file_transfer.printing or WIN32: printlog.error("Error: received printer definitions data") printlog.error( " but this server does not support printer forwarding") return ss = self.get_server_source(proto) if ss is None: return printers = packet[1] auth_class = self.auth_classes.get("unix-domain") ss.set_printers(printers, self.password_file, auth_class, self.encryption, self.encryption_keyfile) ###################################################################### # file transfers: def _process_send_file(self, proto, packet): ss = self.get_server_source(proto) if not ss: printlog.warn( "Warning: invalid client source for send-file packet") return ss._process_send_file(packet) def _process_ack_file_chunk(self, proto, packet): ss = self.get_server_source(proto) if not ss: printlog.warn( "Warning: invalid client source for ack-file-chunk packet") return ss._process_ack_file_chunk(packet) def _process_send_file_chunk(self, proto, packet): ss = self.get_server_source(proto) if not ss: printlog.warn( "Warning: invalid client source for send-file-chunk packet") return ss._process_send_file_chunk(packet) def _process_send_data_request(self, proto, packet): ss = self.get_server_source(proto) if not ss: printlog.warn( "Warning: invalid client source for send-file-request packet") return ss._process_send_data_request(packet) def _process_send_data_response(self, proto, packet): ss = self.get_server_source(proto) if not ss: printlog.warn( "Warning: invalid client source for send-data-response packet") return ss._process_send_data_response(packet) def _process_request_file(self, proto, packet): ss = self.get_server_source(proto) if not ss: printlog.warn( "Warning: invalid client source for send-data-response packet") return try: argf = packet[1].decode("utf-8") except UnicodeDecodeError: argf = bytestostr(packet[1]) openit = packet[2] filename = os.path.abspath(osexpand(argf)) if not os.path.exists(filename): filelog.warn("Warning: the file requested does not exist:") filelog.warn(" %s", filename) ss.may_notify(XPRA_FILETRANSFER_NOTIFICATION_ID, "File not found", "The file requested does not exist:\n%s" % filename, icon_name="file") return try: stat = os.stat(filename) filelog("os.stat(%s)=%s", filename, stat) except os.error: filelog("os.stat(%s)", filename, exc_info=True) else: file_size_MB = stat.st_size // 1024 // 1024 if file_size_MB > self.file_transfer.file_size_limit or file_size_MB > ss.file_size_limit: ss.may_notify( XPRA_FILETRANSFER_NOTIFICATION_ID, "File too large", "The file requested is too large to send:\n%s\nis %iMB" % (argf, file_size_MB), icon_name="file") return data = load_binary_file(filename) ss.send_file(filename, "", data, len(data), openit=openit) def init_packet_handlers(self): if self.file_transfer.printing: self.add_packet_handlers( { "printers": self._process_printers, "print": self._process_print, }, False) if self.file_transfer.printing or self.file_transfer.file_transfer: self.add_packet_handlers( { "send-file": self._process_send_file, "ack-file-chunk": self._process_ack_file_chunk, "send-file-chunk": self._process_send_file_chunk, "send-data-request": self._process_send_data_request, "send-data-response": self._process_send_data_response, }, False) if self.file_transfer.file_transfer: self.add_packet_handler("request-file", self._process_request_file, False)
def test_file_transfer_attributes(self): fta = FileTransferAttributes() assert fta.get_file_transfer_features() assert fta.get_info()