From 28b93d796191d51765df1a43ae3241d7453f6c16 Mon Sep 17 00:00:00 2001 From: ed Date: Wed, 27 Aug 2025 19:55:21 +0000 Subject: [PATCH] option to log invalid xml from clients (#695); windows webdav can send invalid xml in LOCK requests --- copyparty/__main__.py | 1 + copyparty/httpcli.py | 15 ++++++++++++++- tests/util.py | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 783797af..944fa665 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1546,6 +1546,7 @@ def add_logging(ap): ap2.add_argument("--log-utc", action="store_true", help="do not use local timezone; assume the TZ env-var is UTC (tiny bit faster)") ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals") ap2.add_argument("--log-badpwd", metavar="N", type=int, default=2, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed") + ap2.add_argument("--log-badxml", action="store_true", help="log any invalid XML received from a client") ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs") ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling") ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all") diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 21676f33..171791a7 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -819,6 +819,15 @@ class HttpCli(object): 6 if em.startswith("client d/c ") else 3, ) + if self.hint and self.hint.startswith(" "): + if self.args.log_badxml: + t = "invalid XML received from client: %r" + self.log(t % (self.hint[6:],), 6) + else: + t = "received invalid XML from client; enable --log-badxml to see the whole XML in the log" + self.log(t, 6) + self.hint = "" + msg = "%s\r\nURL: %s\r\n" % (em, self.vpath) if self.hint: msg += "hint: %s\r\n" % (self.hint,) @@ -1535,7 +1544,9 @@ class HttpCli(object): if not rbuf or len(buf) >= 32768: break - xroot = parse_xml(buf.decode(enc, "replace")) + sbuf = buf.decode(enc, "replace") + self.hint = " " + sbuf + xroot = parse_xml(sbuf) xtag = next((x for x in xroot if x.tag.split("}")[-1] == "prop"), None) if xtag is not None: props = set([y.tag.split("}")[-1] for y in xtag]) @@ -1741,6 +1752,7 @@ class HttpCli(object): uenc = enc.upper() txt = buf.decode(enc, "replace") + self.hint = " " + txt ET.register_namespace("D", "DAV:") xroot = mkenod("D:orz") xroot.insert(0, parse_xml(txt)) @@ -1801,6 +1813,7 @@ class HttpCli(object): uenc = enc.upper() txt = buf.decode(enc, "replace") + self.hint = " " + txt ET.register_namespace("D", "DAV:") lk = parse_xml(txt) assert lk.tag == "{DAV:}lockinfo" diff --git a/tests/util.py b/tests/util.py index 06f5fd50..a5674808 100644 --- a/tests/util.py +++ b/tests/util.py @@ -143,7 +143,7 @@ class Cfg(Namespace): def __init__(self, a=None, v=None, c=None, **ka0): ka = {} - ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead localtime magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_fnugg no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_u2abrt no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl stats uqe usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" + ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_fnugg no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_u2abrt no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl stats uqe usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" ka.update(**{k: False for k in ex.split()}) ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash see_dots plain_ip"