Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/mixins/fileprint_server.py : 35%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>)
4# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org>
5# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com>
6# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
7# later version. See the file COPYING for details.
8#pylint: disable-msg=E1101
10import os.path
11import hashlib
13from xpra.simple_stats import to_std_unit, std_unit
14from xpra.os_util import bytestostr, osexpand, load_binary_file, WIN32, POSIX
15from xpra.util import engs, repr_ellipsized, XPRA_FILETRANSFER_NOTIFICATION_ID
16from xpra.net.file_transfer import FileTransferAttributes
17from xpra.server.mixins.stub_server_mixin import StubServerMixin
18from xpra.log import Logger
20printlog = Logger("printing")
21filelog = Logger("file")
23SAVE_PRINT_JOBS = os.environ.get("XPRA_SAVE_PRINT_JOBS", None)
26"""
27Mixin for servers that can handle file transfers and forwarded printers.
28Printer forwarding is only supported on Posix servers with the cups backend script.
29"""
30class FilePrintServer(StubServerMixin):
32 def __init__(self):
33 self.lpadmin = ""
34 self.lpinfo = ""
35 self.add_printer_options = []
36 self.file_transfer = FileTransferAttributes()
38 def init(self, opts):
39 self.file_transfer.init_opts(opts, can_ask=False)
40 self.lpadmin = opts.lpadmin
41 self.lpinfo = opts.lpinfo
42 self.add_printer_options = opts.add_printer_options
43 #server-side printer handling is only for posix via pycups for now:
44 self.postscript_printer = opts.postscript_printer
45 self.pdf_printer = opts.pdf_printer
47 def threaded_setup(self):
48 self.init_printing()
50 def init_sockets(self, sockets):
51 #verify we have a local socket for printing:
52 unixsockets = [info for socktype, _, info, _ in sockets if socktype=="unix-domain"]
53 printlog("local unix domain sockets we can use for printing: %s", unixsockets)
54 if not unixsockets and self.file_transfer.printing:
55 if not WIN32:
56 printlog.warn("Warning: no local sockets defined,")
57 printlog.warn(" disabling printer forwarding")
58 printlog("printer forwarding disabled")
59 self.file_transfer.printing = False
62 def get_server_features(self, _source):
63 f = self.file_transfer.get_file_transfer_features()
64 f["printer.attributes"] = ("printer-info", "device-uri")
65 ftf = self.file_transfer.get_file_transfer_features()
66 if self.file_transfer.file_transfer:
67 ftf["request-file"] = True
68 f.update(ftf)
69 return f
71 def get_info(self, _proto):
72 d = {}
73 if POSIX:
74 d.update({
75 "lpadmin" : self.lpadmin,
76 "lpinfo" : self.lpinfo,
77 "add-printer-options" : self.add_printer_options,
78 })
79 if self.file_transfer.printing:
80 from xpra.platform.printing import get_info
81 d.update(get_info())
82 info = {"printing" : d}
83 if self.file_transfer.file_transfer:
84 fti = self.file_transfer.get_info()
85 if self.file_transfer.file_transfer:
86 fti["request-file"] = True
87 info["file"] = fti
88 return info
91 def init_printing(self):
92 printing = self.file_transfer.printing
93 if not printing or WIN32:
94 return
95 try:
96 from xpra.platform import pycups_printing
97 pycups_printing.set_lpadmin_command(self.lpadmin)
98 pycups_printing.set_lpinfo_command(self.lpinfo)
99 pycups_printing.set_add_printer_options(self.add_printer_options)
100 if self.postscript_printer:
101 pycups_printing.add_printer_def("application/postscript", self.postscript_printer)
102 if self.pdf_printer:
103 pycups_printing.add_printer_def("application/pdf", self.pdf_printer)
104 printer_definitions = pycups_printing.validate_setup()
105 printing = bool(printer_definitions)
106 if printing:
107 printlog.info("printer forwarding enabled using %s", " and ".join(
108 x.replace("application/", "") for x in printer_definitions))
109 else:
110 printlog.warn("Warning: no printer definitions found,")
111 printlog.warn(" cannot enable printer forwarding")
112 except ImportError as e:
113 printlog("printing module is not installed: %s", e)
114 printing = False
115 except Exception:
116 printlog.error("Error: failed to set lpadmin and lpinfo commands", exc_info=True)
117 printing = False
118 #verify that we can talk to the socket:
119 auth_class = self.auth_classes.get("unix-domain")
120 if printing and auth_class:
121 try:
122 #this should be the name of the auth module:
123 auth_name = auth_class[0]
124 except:
125 auth_name = str(auth_class)
126 if auth_name not in ("none", "file"):
127 printlog.warn("Warning: printer forwarding cannot be used,")
128 printlog.warn(" it conflicts with socket authentication module '%r'", auth_name)
129 printing = False
130 #update file transfer attributes since printing nay have been disabled here
131 self.file_transfer.printing = printing
132 printlog("init_printing() printing=%s", printing)
134 def _process_print(self, _proto, packet):
135 #ie: from the xpraforwarder we call this command:
136 #command = ["xpra", "print", "socket:/path/tosocket",
137 # filename, mimetype, source, title, printer, no_copies, print_options]
138 assert self.file_transfer.printing
139 #printlog("_process_print(%s, %s)", proto, packet)
140 if len(packet)<3:
141 printlog.error("Error: invalid print packet, only %i arguments", len(packet))
142 printlog.error(" %s", [repr_ellipsized(x) for x in packet])
143 return
144 def s(b):
145 try:
146 return b.decode("utf-8")
147 except Exception:
148 return bytestostr(b)
149 filename = s(packet[1])
150 file_data = packet[2]
151 mimetype, source_uuid, title, printer, no_copies, print_options = "", "*", "unnamed document", "", 1, ""
152 if len(packet)>=4:
153 mimetype = bytestostr(packet[3])
154 if len(packet)>=5:
155 source_uuid = bytestostr(packet[4])
156 if len(packet)>=6:
157 title = s(packet[5])
158 if len(packet)>=7:
159 printer = bytestostr(packet[6])
160 if len(packet)>=8:
161 no_copies = int(packet[7])
162 if len(packet)>=9:
163 print_options = packet[8]
164 #parse and validate:
165 if len(mimetype)>=128:
166 printlog.error("Error: invalid mimetype in print packet:")
167 printlog.error(" %s", repr_ellipsized(mimetype))
168 return
169 if not isinstance(print_options, dict):
170 s = bytestostr(print_options)
171 print_options = {}
172 for x in s.split(" "):
173 parts = x.split("=", 1)
174 if len(parts)==2:
175 print_options[parts[0]] = parts[1]
176 printlog("process_print: %s", (filename, mimetype, "%s bytes" % len(file_data),
177 source_uuid, title, printer, no_copies, print_options))
178 printlog("process_print: got %s bytes for file %s", len(file_data), filename)
179 #parse the print options:
180 u = hashlib.sha1()
181 u.update(file_data)
182 printlog("sha1 digest: %s", u.hexdigest())
183 options = {
184 "printer" : printer,
185 "title" : title,
186 "copies" : no_copies,
187 "options" : print_options,
188 "sha1" : u.hexdigest(),
189 }
190 printlog("parsed printer options: %s", options)
191 if SAVE_PRINT_JOBS:
192 self._save_print_job(filename, file_data)
194 sent = 0
195 sources = tuple(self._server_sources.values())
196 printlog("will try to send to %i clients: %s", len(sources), sources)
197 for ss in sources:
198 if source_uuid not in ("*", ss.uuid):
199 printlog("not sending to %s (uuid=%s, wanted uuid=%s)", ss, ss.uuid, source_uuid)
200 continue
201 if not ss.printing:
202 if source_uuid!='*':
203 printlog.warn("Warning: printing is not enabled for:")
204 printlog.warn(" %s", ss)
205 else:
206 printlog("printing is not enabled for %s", ss)
207 continue
208 if not ss.printers:
209 printlog.warn("Warning: client %s does not have any printers", ss.uuid)
210 continue
211 if printer not in ss.printers:
212 printlog.warn("Warning: client %s does not have a '%s' printer", ss.uuid, printer)
213 continue
214 printlog("'%s' sent to %s for printing on '%s'", bytestostr(title or filename), ss, printer)
215 if ss.send_file(filename, mimetype, file_data, len(file_data), True, True, options):
216 sent += 1
217 #warn if not sent:
218 if sent==0:
219 l = printlog.warn
220 else:
221 l = printlog.info
222 unit_str, v = to_std_unit(len(file_data), unit=1024)
223 l("'%s' (%i%sB) sent to %i client%s for printing", title or filename, v, unit_str, sent, engs(sent))
225 def _save_print_job(self, filename, file_data):
226 try:
227 save_filename = os.path.join(SAVE_PRINT_JOBS, filename)
228 with open(save_filename, "wb") as f:
229 f.write(file_data)
230 printlog.info("saved print job to: %s", save_filename)
231 except Exception as e:
232 printlog.error("Error: failed to save print job to %s", save_filename)
233 printlog.error(" %s", e)
235 def _process_printers(self, proto, packet):
236 if not self.file_transfer.printing or WIN32:
237 printlog.error("Error: received printer definitions data")
238 printlog.error(" but this server does not support printer forwarding")
239 return
240 ss = self.get_server_source(proto)
241 if ss is None:
242 return
243 printers = packet[1]
244 auth_class = self.auth_classes.get("unix-domain")
245 ss.set_printers(printers, self.password_file, auth_class, self.encryption, self.encryption_keyfile)
248 ######################################################################
249 # file transfers:
250 def _process_send_file(self, proto, packet):
251 ss = self.get_server_source(proto)
252 if not ss:
253 printlog.warn("Warning: invalid client source for send-file packet")
254 return
255 ss._process_send_file(packet)
257 def _process_ack_file_chunk(self, proto, packet):
258 ss = self.get_server_source(proto)
259 if not ss:
260 printlog.warn("Warning: invalid client source for ack-file-chunk packet")
261 return
262 ss._process_ack_file_chunk(packet)
264 def _process_send_file_chunk(self, proto, packet):
265 ss = self.get_server_source(proto)
266 if not ss:
267 printlog.warn("Warning: invalid client source for send-file-chunk packet")
268 return
269 ss._process_send_file_chunk(packet)
271 def _process_send_data_request(self, proto, packet):
272 ss = self.get_server_source(proto)
273 if not ss:
274 printlog.warn("Warning: invalid client source for send-file-request packet")
275 return
276 ss._process_send_data_request(packet)
278 def _process_send_data_response(self, proto, packet):
279 ss = self.get_server_source(proto)
280 if not ss:
281 printlog.warn("Warning: invalid client source for send-data-response packet")
282 return
283 ss._process_send_data_response(packet)
285 def _process_request_file(self, proto, packet):
286 ss = self.get_server_source(proto)
287 if not ss:
288 printlog.warn("Warning: invalid client source for send-data-response packet")
289 return
290 try:
291 argf = packet[1].decode("utf-8")
292 except UnicodeDecodeError:
293 argf = bytestostr(packet[1])
294 openit = packet[2]
295 filename = os.path.abspath(osexpand(argf))
296 if not os.path.exists(filename):
297 filelog.warn("Warning: the file requested does not exist:")
298 filelog.warn(" %s", filename)
299 ss.may_notify(XPRA_FILETRANSFER_NOTIFICATION_ID,
300 "File not found", "The file requested does not exist:\n%s" % filename,
301 icon_name="file")
302 return
303 try:
304 stat = os.stat(filename)
305 filelog("os.stat(%s)=%s", filename, stat)
306 except os.error:
307 filelog("os.stat(%s)", filename, exc_info=True)
308 else:
309 file_size = stat.st_size
310 if file_size>self.file_transfer.file_size_limit or file_size>ss.file_size_limit:
311 ss.may_notify(XPRA_FILETRANSFER_NOTIFICATION_ID,
312 "File too large",
313 "The file requested is too large to send:\n%s\nis %s" % (argf, std_unit(file_size)),
314 icon_name="file")
315 return
316 data = load_binary_file(filename)
317 ss.send_file(filename, "", data, len(data), openit=openit, options={"request-file" : (argf, openit)})
320 def init_packet_handlers(self):
321 if self.file_transfer.printing:
322 self.add_packet_handlers({
323 "printers": self._process_printers,
324 "print": self._process_print,
325 }, False)
326 if self.file_transfer.printing or self.file_transfer.file_transfer:
327 self.add_packet_handlers({
328 "send-file": self._process_send_file,
329 "ack-file-chunk": self._process_ack_file_chunk,
330 "send-file-chunk": self._process_send_file_chunk,
331 "send-data-request": self._process_send_data_request,
332 "send-data-response": self._process_send_data_response,
333 }, False)
334 if self.file_transfer.file_transfer:
335 self.add_packet_handler("request-file", self._process_request_file, False)