dedicated tcp-port for tricky webdav clients;

there are webdav-clients (for example zotero) which fully pretend
to be a graphical webbrowser, going as far as faking the firefox
user-agent, which means they get the graphical login-page
instead of 401 (basic-authentication challenge)

these webdav-clients unfortunately also refuse to send credentials
unless they get 401'd, so until now it was impossible to connect them

the obvious solution of adding a suffix to
links in PROPFIND responses is a nonstarter;

* windows-webdav ignores the <displayname> property and shows the
   <href> as the filename, so this would show up in windows explorer
   and probably make most file operations impossible

* rclone is the opposite; ignores the <href> property (so it wouldn't
   even see the suffix) and builds its own URL from the <displayname>

so we need a new weapon:

gloabl-option dav-port makes copyparty listen on another port which
is dedicated to webdav-clients that otherwise don't look the part

global-option p-nodav is the opposite; tags a listening-port as
only accepting connections from graphical browsers, just in case

closes #1142
This commit is contained in:
ed
2025-12-26 17:21:58 +00:00
parent 2c26aecd87
commit 4642d32366
3 changed files with 20 additions and 13 deletions

View File

@@ -1440,7 +1440,9 @@ def add_webdav(ap):
ap2.add_argument("--dav-rt", action="store_true", help="show symlink-destination's lastmodified instead of the link itself; always enabled for recursive listings (volflag=davrt)") ap2.add_argument("--dav-rt", action="store_true", help="show symlink-destination's lastmodified instead of the link itself; always enabled for recursive listings (volflag=davrt)")
ap2.add_argument("--dav-auth", action="store_true", help="force auth for all folders (required by davfs2 when only some folders are world-readable) (volflag=davauth)") ap2.add_argument("--dav-auth", action="store_true", help="force auth for all folders (required by davfs2 when only some folders are world-readable) (volflag=davauth)")
ap2.add_argument("--dav-ua1", metavar="PTN", type=u, default=r" kioworker/", help="regex of user-agents which ARE webdav-clients, and expect 401 from GET requests; disable with [\033[32mno\033[0m] or blank") ap2.add_argument("--dav-ua1", metavar="PTN", type=u, default=r" kioworker/", help="regex of user-agents which ARE webdav-clients, and expect 401 from GET requests; disable with [\033[32mno\033[0m] or blank")
ap2.add_argument("--dav-port", metavar="P", type=int, default=0, help="additional port to listen on for misbehaving webdav clients which pretend they are graphical browsers; an alternative/supplement to dav-ua1")
ap2.add_argument("--ua-nodav", metavar="PTN", type=u, default=r"^(Mozilla/|NetworkingExtension/|com\.apple\.WebKit)", help="regex of user-agents which are NOT webdav-clients") ap2.add_argument("--ua-nodav", metavar="PTN", type=u, default=r"^(Mozilla/|NetworkingExtension/|com\.apple\.WebKit)", help="regex of user-agents which are NOT webdav-clients")
ap2.add_argument("--p-nodav", metavar="P,P", type=u, default="", help="server-ports (comma-sep.) which are NOT webdav-clients; an alternative/supplement to ua-nodav")
def add_tftp(ap): def add_tftp(ap):
@@ -1558,7 +1560,7 @@ def add_safety(ap):
ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to turn something into a dotfile") ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to turn something into a dotfile")
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings") ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme/preadme.md into directory listings") ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme/preadme.md into directory listings")
ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)") ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise). \033[1;31mWARNING:\033[0m Not compatible with WebDAV")
ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore \033[33m--no-robots\033[0m") ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore \033[33m--no-robots\033[0m")
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)") ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
ap2.add_argument("--logout", metavar="H", type=float, default=8086.0, help="logout clients after \033[33mH\033[0m hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)") ap2.add_argument("--logout", metavar="H", type=float, default=8086.0, help="logout clients after \033[33mH\033[0m hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)")

View File

@@ -5522,18 +5522,19 @@ class HttpCli(object):
# most webdav clients will not send credentials until they # most webdav clients will not send credentials until they
# get 401'd, so send a challenge if we're Absolutely Sure # get 401'd, so send a challenge if we're Absolutely Sure
# that the client is not a graphical browser # that the client is not a graphical browser
if ( if rc == 403 and self.uname == "*":
rc == 403 sport = self.s.getsockname()[1]
and self.uname == "*" if self.args.dav_port == sport or (
and "sec-fetch-site" not in self.headers "sec-fetch-site" not in self.headers
and self.cookies.get("js") != "y" and self.cookies.get("js") != "y"
and ( and sport not in self.args.p_nodav
not self.args.ua_nodav.search(self.ua) and (
or (self.args.dav_ua1 and self.args.dav_ua1.search(self.ua)) not self.args.ua_nodav.search(self.ua)
) or (self.args.dav_ua1 and self.args.dav_ua1.search(self.ua))
): )
rc = 401 ):
self.out_headers["WWW-Authenticate"] = 'Basic realm="a"' rc = 401
self.out_headers["WWW-Authenticate"] = 'Basic realm="a"'
t = t.format(self.args.SR) t = t.format(self.args.SR)
qv = quotep(self.vpaths) + self.ourlq() qv = quotep(self.vpaths) + self.ourlq()

View File

@@ -220,6 +220,10 @@ class SvcHub(object):
self.log("root", t.format(args.j), c=3) self.log("root", t.format(args.j), c=3)
args.no_fpool = True args.no_fpool = True
args.p_nodav = [int(x.strip()) for x in args.p_nodav.split(",") if x]
if args.dav_port and args.dav_port not in args.p:
args.p.append(args.dav_port)
for name, arg in ( for name, arg in (
("iobuf", "iobuf"), ("iobuf", "iobuf"),
("s-rd-sz", "s_rd_sz"), ("s-rd-sz", "s_rd_sz"),