mirror of
https://github.com/9001/copyparty.git
synced 2026-06-20 13:12:26 -06:00
Merge b668b8d2f3 into d33d11321f
This commit is contained in:
commit
d36216886f
|
|
@ -1572,6 +1572,12 @@ def add_opds(ap):
|
||||||
ap2.add_argument("--opds-exts", metavar="T,T", type=u, default="epub,cbz,pdf", help="file formats to list in OPDS feeds; leave empty to show everything (volflag=opds_exts)")
|
ap2.add_argument("--opds-exts", metavar="T,T", type=u, default="epub,cbz,pdf", help="file formats to list in OPDS feeds; leave empty to show everything (volflag=opds_exts)")
|
||||||
|
|
||||||
|
|
||||||
|
def add_wopi(ap):
|
||||||
|
ap2 = ap.add_argument_group("WOPI options")
|
||||||
|
ap2.add_argument("--wopi", action="store_true", help="allows for integrating with office suites using WOPI (volflag=wopi)")
|
||||||
|
ap2.add_argument("--wopi-client", type=u, default="https://demo.eu.collaboraonline.com", help="where to find your WOPI client, this is what actually hosts e.g. Collabora Online")
|
||||||
|
|
||||||
|
|
||||||
def add_handlers(ap):
|
def add_handlers(ap):
|
||||||
ap2 = ap.add_argument_group("handlers (see --help-handlers)")
|
ap2 = ap.add_argument_group("handlers (see --help-handlers)")
|
||||||
ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="\033[34mREPEATABLE:\033[0m handle 404s by executing \033[33mPY\033[0m file")
|
ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="\033[34mREPEATABLE:\033[0m handle 404s by executing \033[33mPY\033[0m file")
|
||||||
|
|
@ -2080,6 +2086,7 @@ def run_argparse(
|
||||||
add_tftp(ap)
|
add_tftp(ap)
|
||||||
add_smb(ap)
|
add_smb(ap)
|
||||||
add_opds(ap)
|
add_opds(ap)
|
||||||
|
add_wopi(ap)
|
||||||
add_safety(ap)
|
add_safety(ap)
|
||||||
add_salt(ap, fk_salt, dk_salt, ah_salt)
|
add_salt(ap, fk_salt, dk_salt, ah_salt)
|
||||||
add_optouts(ap)
|
add_optouts(ap)
|
||||||
|
|
|
||||||
|
|
@ -394,6 +394,10 @@ flagcats = {
|
||||||
"og_no_head": "you want to add tags manually with og_tpl",
|
"og_no_head": "you want to add tags manually with og_tpl",
|
||||||
"og_ua": "if defined: only send OG html if useragent matches this regex",
|
"og_ua": "if defined: only send OG html if useragent matches this regex",
|
||||||
},
|
},
|
||||||
|
"wopi": {
|
||||||
|
"wopi": "enable WOPI support for integrating with online office suites",
|
||||||
|
"wopi-client": "address of WOPI client, e.g. Collabora Online",
|
||||||
|
},
|
||||||
"opds": {
|
"opds": {
|
||||||
"opds": "enable OPDS",
|
"opds": "enable OPDS",
|
||||||
"opds_exts": "file formats to list in OPDS feeds; leave empty to show everything",
|
"opds_exts": "file formats to list in OPDS feeds; leave empty to show everything",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ import sys
|
||||||
import threading # typechk
|
import threading # typechk
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
|
|
@ -798,6 +801,12 @@ class HttpCli(object):
|
||||||
else:
|
else:
|
||||||
self.log("unknown username: %r" % (idp_usr,), 1)
|
self.log("unknown username: %r" % (idp_usr,), 1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.args.wopi:
|
||||||
|
self.uname = self.conn.hsrv.wopi_files.get(self.uparam.get("access_token")).get("uname")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if self.args.have_ipu_or_ipr:
|
if self.args.have_ipu_or_ipr:
|
||||||
if self.args.ipu and (self.uname == "*" or self.args.ao_ipu_wins):
|
if self.args.ipu and (self.uname == "*" or self.args.ao_ipu_wins):
|
||||||
self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
|
self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
|
||||||
|
|
@ -1525,8 +1534,120 @@ class HttpCli(object):
|
||||||
if "rss" in self.uparam:
|
if "rss" in self.uparam:
|
||||||
return self.tx_rss()
|
return self.tx_rss()
|
||||||
|
|
||||||
|
if self.args.wopi:
|
||||||
|
if "wopi" in self.uparam:
|
||||||
|
return self.tx_wopi()
|
||||||
|
|
||||||
|
if self.vpath.startswith("wopi"):
|
||||||
|
return self.tx_wopi_api()
|
||||||
|
|
||||||
return self.tx_browser()
|
return self.tx_browser()
|
||||||
|
|
||||||
|
def tx_wopi_api(self) -> bool:
|
||||||
|
path = self.vpath.split('/')
|
||||||
|
|
||||||
|
if "files" in path and self.conn.hsrv.wopi_files[self.uparam["access_token"]]["file_key"] in path:
|
||||||
|
real_path = self.conn.hsrv.wopi_files[self.uparam["access_token"]]["path"]
|
||||||
|
vfs, _ = self.asrv.vfs.get(real_path, self.uname, False, True)
|
||||||
|
full_path = vfs.realpath + "/" + real_path
|
||||||
|
|
||||||
|
if "contents" in path:
|
||||||
|
if self.do_log:
|
||||||
|
self.log("WOPI GET 'contents': %s" % (full_path))
|
||||||
|
|
||||||
|
return self.tx_file("oh_f", full_path)
|
||||||
|
else:
|
||||||
|
if self.do_log:
|
||||||
|
self.log("WOPI GET 'file_info': %s" % (full_path))
|
||||||
|
|
||||||
|
file_info = {
|
||||||
|
"BaseFileName": real_path.split("/")[-1],
|
||||||
|
"OwnerId": self.uname,
|
||||||
|
"Size": os.path.getsize(full_path),
|
||||||
|
"UserId": self.uname,
|
||||||
|
"UserFriendlyName": self.uname,
|
||||||
|
"UserCanWrite": True,
|
||||||
|
"UserCanNotWriteRelative": True,
|
||||||
|
"LastModifiedTime":
|
||||||
|
time.strftime(
|
||||||
|
"%Y-%m-%dT%H:%M:%SZ",
|
||||||
|
time.gmtime(os.path.getmtime(full_path))
|
||||||
|
),
|
||||||
|
}
|
||||||
|
ret = json.dumps(file_info).encode("utf-8", "replace")
|
||||||
|
self.reply(ret, 200, "application/json; charset=utf-8")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return self.tx_404()
|
||||||
|
|
||||||
|
def tx_wopi(self) -> bool:
|
||||||
|
path = self.vpath + "/" + str(self.uparam["wopi"])
|
||||||
|
session_salt = ub64enc(os.urandom(64)).decode("utf-8")
|
||||||
|
session_key = self.gen_fk(2, session_salt, self.uname, 0, 0)
|
||||||
|
file_key = self.gen_fk(2, self.args.fk_salt, path, 0, 0)
|
||||||
|
self.conn.hsrv.wopi_files[session_key] = {
|
||||||
|
"uname": self.uname,
|
||||||
|
"file_key": file_key,
|
||||||
|
"path": path,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
discovery = urllib.request.urlopen(self.args.wopi_client + "/hosting/discovery")
|
||||||
|
response = ET.fromstring(discovery.read())
|
||||||
|
ext = path.split('.')[-1]
|
||||||
|
wopi_url = response.find(".//action[@ext='%s'][@urlsrc]" % ext).get("urlsrc")
|
||||||
|
favicon_url = response.find(".//action[@ext='%s'].." % ext).get("favIconUrl")
|
||||||
|
url = wopi_url + urllib.parse.quote("WOPISrc=https://" + self.host + "/wopi/files/" + file_key, safe="=")
|
||||||
|
except Exception as error:
|
||||||
|
self.log("Couldn't get urls from WOPI client: %s" % error)
|
||||||
|
return False
|
||||||
|
|
||||||
|
ret = [
|
||||||
|
"""\
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<link rel="icon" href="%s" />
|
||||||
|
<title>%s</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#viewer {
|
||||||
|
width: 100%%;
|
||||||
|
height: 100%%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="display: none">
|
||||||
|
<form action="%s" enctype="multipart/form-data" method="post" target="viewer" id="submit-form">
|
||||||
|
<input name="access_token" value="%s" type="hidden" id="access-token"/>
|
||||||
|
<input type="submit" value="" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<iframe id="viewer" name="viewer" allow="clipboard-read *; clipboard-write *; fullscreen *"></iframe>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById("submit-form").submit();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
% (favicon_url, self.uparam["wopi"], url, session_key)
|
||||||
|
]
|
||||||
|
|
||||||
|
bret = "".join(ret).encode("utf-8", "replace")
|
||||||
|
self.reply(bret, 200, "text/html; charset=utf-8")
|
||||||
|
return True
|
||||||
|
|
||||||
def tx_rss(self) -> bool:
|
def tx_rss(self) -> bool:
|
||||||
if self.do_log:
|
if self.do_log:
|
||||||
self.log("RSS %s @%s" % (self.req, self.uname))
|
self.log("RSS %s @%s" % (self.req, self.uname))
|
||||||
|
|
@ -3149,6 +3270,9 @@ class HttpCli(object):
|
||||||
except:
|
except:
|
||||||
raise Pebkac(400, "you must supply a content-length for binary POST")
|
raise Pebkac(400, "you must supply a content-length for binary POST")
|
||||||
|
|
||||||
|
if self.args.wopi and self.vpath.startswith("wopi"):
|
||||||
|
return self.rx_wopi()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
chashes = self.headers["x-up2k-hash"].split(",")
|
chashes = self.headers["x-up2k-hash"].split(",")
|
||||||
wark = self.headers["x-up2k-wark"]
|
wark = self.headers["x-up2k-wark"]
|
||||||
|
|
@ -3360,6 +3484,47 @@ class HttpCli(object):
|
||||||
self.reply(b"thank")
|
self.reply(b"thank")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def rx_wopi(self) -> bool:
|
||||||
|
path = self.vpath.split('/')
|
||||||
|
|
||||||
|
if (
|
||||||
|
"files" in path and
|
||||||
|
"contents" in path and
|
||||||
|
self.conn.hsrv.wopi_files[self.uparam["access_token"]]["file_key"] in path
|
||||||
|
):
|
||||||
|
real_path = self.conn.hsrv.wopi_files[self.uparam["access_token"]]["path"]
|
||||||
|
vfs, _ = self.asrv.vfs.get(real_path, self.uname, False, True)
|
||||||
|
full_path = vfs.realpath + "/" + real_path
|
||||||
|
|
||||||
|
if self.do_log:
|
||||||
|
self.log("WOPI POST 'contents': %s" % (full_path))
|
||||||
|
|
||||||
|
last_mod_time = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(os.path.getmtime(full_path)))
|
||||||
|
if self.headers["x-cool-wopi-timestamp"] != last_mod_time:
|
||||||
|
self.reply(json.dumps({"COOLStatusCode": 1010}).encode("utf-8"), 409)
|
||||||
|
return True
|
||||||
|
|
||||||
|
reader, _ = self.get_body_reader()
|
||||||
|
buf = b""
|
||||||
|
for rbuf in reader:
|
||||||
|
buf += rbuf
|
||||||
|
if not rbuf:
|
||||||
|
break
|
||||||
|
|
||||||
|
if buf:
|
||||||
|
with open(full_path, "wb") as file:
|
||||||
|
file.write(buf)
|
||||||
|
|
||||||
|
new_mod_time = {
|
||||||
|
"LastModifiedTime":
|
||||||
|
time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(os.path.getmtime(full_path)))
|
||||||
|
}
|
||||||
|
ret = json.dumps(new_mod_time).encode("utf-8", "replace")
|
||||||
|
self.reply(ret, 200, "application/json; charset=utf-8")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return self.tx_404(True)
|
||||||
|
|
||||||
def handle_chpw(self) -> bool:
|
def handle_chpw(self) -> bool:
|
||||||
assert self.parser # !rm
|
assert self.parser # !rm
|
||||||
if self.args.usernames:
|
if self.args.usernames:
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,9 @@ class HttpSrv(object):
|
||||||
if (HAVE_PIL or HAVE_VIPS or HAVE_FFMPEG) and not self.args.no_thumb:
|
if (HAVE_PIL or HAVE_VIPS or HAVE_FFMPEG) and not self.args.no_thumb:
|
||||||
Daemon(self.post_init, "hsrv-init2")
|
Daemon(self.post_init, "hsrv-init2")
|
||||||
|
|
||||||
|
if self.args.wopi:
|
||||||
|
self.wopi_files: dict[str, dict[str, str]] = {}
|
||||||
|
|
||||||
def post_init(self) -> None:
|
def post_init(self) -> None:
|
||||||
try:
|
try:
|
||||||
x = self.broker.ask("thumbsrv.getcfg")
|
x = self.broker.ask("thumbsrv.getcfg")
|
||||||
|
|
|
||||||
|
|
@ -7665,10 +7665,24 @@ var treectl = (function () {
|
||||||
tn.href = addq(tn.href, 'v');
|
tn.href = addq(tn.href, 'v');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tn.lead == '-')
|
// https://en.wikipedia.org/wiki/OpenDocument
|
||||||
|
// https://help.collaboraoffice.com/latest/en-US/text/shared/guide/ms_user.html
|
||||||
|
var office_formats = [
|
||||||
|
"odt", "fodt", "doc", "docx",
|
||||||
|
"ods", "fods", "xls", "xlsx",
|
||||||
|
"odp", "fopd", "ppt", "pps", "pptx",
|
||||||
|
"odg", "fodg",
|
||||||
|
"odf",
|
||||||
|
];
|
||||||
|
|
||||||
|
if (office_formats.includes(tn.ext)) {
|
||||||
|
tn.lead = '<a href="?wopi=' + bhref + '" id="t' + id +
|
||||||
|
'" rel="nofollow" target="blank" name="' + hname + '">📄</a>';
|
||||||
|
} else if (tn.lead == '-') {
|
||||||
tn.lead = '<a href="?doc=' + bhref + '" id="t' + id +
|
tn.lead = '<a href="?doc=' + bhref + '" id="t' + id +
|
||||||
'" rel="nofollow" class="doc' + (lang ? ' bri' : '') +
|
'" rel="nofollow" class="doc' + (lang ? ' bri' : '') +
|
||||||
'" hl="' + id + '" name="' + hname + '">-txt-</a>';
|
'" hl="' + id + '" name="' + hname + '">-txt-</a>';
|
||||||
|
}
|
||||||
|
|
||||||
var cl = /\.PARTIAL$/.exec(fname) ? ' class="fade"' : '',
|
var cl = /\.PARTIAL$/.exec(fname) ? ' class="fade"' : '',
|
||||||
ln = ['<tr' + cl + '><td>' + tn.lead + '</td><td><a href="' +
|
ln = ['<tr' + cl + '><td>' + tn.lead + '</td><td><a href="' +
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue