copyparty 1.16.2__py3-none-any.whl → 1.16.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- copyparty/__main__.py +5 -1
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +19 -0
- copyparty/cfg.py +2 -0
- copyparty/httpcli.py +96 -19
- copyparty/mdns.py +7 -0
- copyparty/res/COPYING.txt +59 -2
- copyparty/stolen/dnslib/dns.py +2 -2
- copyparty/stolen/dnslib/label.py +21 -1
- copyparty/svchub.py +1 -1
- copyparty/u2idx.py +23 -20
- copyparty/up2k.py +38 -14
- copyparty/util.py +20 -1
- copyparty/web/a/u2c.py +53 -7
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/deps/easymde.js.gz +0 -0
- copyparty/web/deps/marked.js.gz +0 -0
- copyparty/web/deps/sha512.hw.js.gz +0 -0
- copyparty/web/md.js.gz +0 -0
- copyparty/web/md2.js.gz +0 -0
- copyparty/web/up2k.js.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.16.2.dist-info → copyparty-1.16.4.dist-info}/METADATA +6 -3
- {copyparty-1.16.2.dist-info → copyparty-1.16.4.dist-info}/RECORD +29 -29
- {copyparty-1.16.2.dist-info → copyparty-1.16.4.dist-info}/LICENSE +0 -0
- {copyparty-1.16.2.dist-info → copyparty-1.16.4.dist-info}/WHEEL +0 -0
- {copyparty-1.16.2.dist-info → copyparty-1.16.4.dist-info}/entry_points.txt +0 -0
- {copyparty-1.16.2.dist-info → copyparty-1.16.4.dist-info}/top_level.txt +0 -0
    
        copyparty/__main__.py
    CHANGED
    
    | @@ -1116,6 +1116,8 @@ def add_zc_mdns(ap): | |
| 1116 1116 | 
             
                ap2.add_argument("--zm6", action="store_true", help="IPv6 only")
         | 
| 1117 1117 | 
             
                ap2.add_argument("--zmv", action="store_true", help="verbose mdns")
         | 
| 1118 1118 | 
             
                ap2.add_argument("--zmvv", action="store_true", help="verboser mdns")
         | 
| 1119 | 
            +
                ap2.add_argument("--zm-no-pe", action="store_true", help="mute parser errors (invalid incoming MDNS packets)")
         | 
| 1120 | 
            +
                ap2.add_argument("--zm-nwa-1", action="store_true", help="disable workaround for avahi-bug #379 (corruption in Avahi's mDNS reflection feature)")
         | 
| 1119 1121 | 
             
                ap2.add_argument("--zms", metavar="dhf", type=u, default="", help="list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (\033[32mDdfs\033[0m if \033[33m--ftp\033[0m and \033[33m--smb\033[0m is set, \033[32mDd\033[0m otherwise)")
         | 
| 1120 1122 | 
             
                ap2.add_argument("--zm-ld", metavar="PATH", type=u, default="", help="link a specific folder for webdav shares")
         | 
| 1121 1123 | 
             
                ap2.add_argument("--zm-lh", metavar="PATH", type=u, default="", help="link a specific folder for http shares")
         | 
| @@ -1308,7 +1310,7 @@ def add_logging(ap): | |
| 1308 1310 | 
             
                ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
         | 
| 1309 1311 | 
             
                ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
         | 
| 1310 1312 | 
             
                ap2.add_argument("--ohead", metavar="HEADER", type=u, action='append', help="print response \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
         | 
| 1311 | 
            -
                ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr | 
| 1313 | 
            +
                ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|[?&]th=[wjp]|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching regex \033[33mRE\033[0m")
         | 
| 1312 1314 |  | 
| 1313 1315 |  | 
| 1314 1316 | 
             
            def add_admin(ap):
         | 
| @@ -1393,6 +1395,7 @@ def add_db_general(ap, hcores): | |
| 1393 1395 | 
             
                ap2.add_argument("--db-act", metavar="SEC", type=float, default=10.0, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
         | 
| 1394 1396 | 
             
                ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than \033[33mSEC\033[0m seconds")
         | 
| 1395 1397 | 
             
                ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
         | 
| 1398 | 
            +
                ap2.add_argument("--srch-excl", metavar="PTN", type=u, default="", help="regex: exclude files from search results if the file-URL matches \033[33mPTN\033[0m (case-sensitive). Example: [\033[32mpassword|logs/[0-9]\033[0m] any URL containing 'password' or 'logs/DIGIT' (volflag=srch_excl)")
         | 
| 1396 1399 | 
             
                ap2.add_argument("--dotsrch", action="store_true", help="show dotfiles in search results (volflags: dotsrch | nodotsrch)")
         | 
| 1397 1400 |  | 
| 1398 1401 |  | 
| @@ -1450,6 +1453,7 @@ def add_ui(ap, retry): | |
| 1450 1453 | 
             
                ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
         | 
| 1451 1454 | 
             
                ap2.add_argument("--sort", metavar="C,C,C", type=u, default="href", help="default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: \033[32mhref -href ext sz ts tags/Album tags/.tn\033[0m (volflag=sort)")
         | 
| 1452 1455 | 
             
                ap2.add_argument("--nsort", action="store_true", help="default-enable natural sort of filenames with leading numbers (volflag=nsort)")
         | 
| 1456 | 
            +
                ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
         | 
| 1453 1457 | 
             
                ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
         | 
| 1454 1458 | 
             
                ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
         | 
| 1455 1459 | 
             
                ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])")
         | 
    
        copyparty/__version__.py
    CHANGED
    
    
    
        copyparty/authsrv.py
    CHANGED
    
    | @@ -1873,6 +1873,7 @@ class AuthSrv(object): | |
| 1873 1873 | 
             
                            ["no_hash", "nohash"],
         | 
| 1874 1874 | 
             
                            ["no_idx", "noidx"],
         | 
| 1875 1875 | 
             
                            ["og_ua", "og_ua"],
         | 
| 1876 | 
            +
                            ["srch_excl", "srch_excl"],
         | 
| 1876 1877 | 
             
                        ]:
         | 
| 1877 1878 | 
             
                            if vf in vol.flags:
         | 
| 1878 1879 | 
             
                                ptn = re.compile(vol.flags.pop(vf))
         | 
| @@ -2079,6 +2080,22 @@ class AuthSrv(object): | |
| 2079 2080 | 
             
                            self.log(t.format(mtp), 1)
         | 
| 2080 2081 | 
             
                            errors = True
         | 
| 2081 2082 |  | 
| 2083 | 
            +
                    for vol in vfs.all_vols.values():
         | 
| 2084 | 
            +
                        re1  = vol.flags.get("srch_excl")
         | 
| 2085 | 
            +
                        excl = [re1.pattern] if re1 else []
         | 
| 2086 | 
            +
             | 
| 2087 | 
            +
                        vpaths = []
         | 
| 2088 | 
            +
                        vtop = vol.vpath
         | 
| 2089 | 
            +
                        for vp2 in vfs.all_vols.keys():
         | 
| 2090 | 
            +
                            if vp2.startswith((vtop + "/").lstrip("/")) and vtop != vp2:
         | 
| 2091 | 
            +
                                vpaths.append(re.escape(vp2[len(vtop) :].lstrip("/")))
         | 
| 2092 | 
            +
                        if vpaths:
         | 
| 2093 | 
            +
                            excl.append("^(%s)/" % ("|".join(vpaths),))
         | 
| 2094 | 
            +
             | 
| 2095 | 
            +
                        vol.flags["srch_re_dots"] = re.compile("|".join(excl or ["^$"]))
         | 
| 2096 | 
            +
                        excl.extend([r"^\.", r"/\."])
         | 
| 2097 | 
            +
                        vol.flags["srch_re_nodot"] = re.compile("|".join(excl))
         | 
| 2098 | 
            +
             | 
| 2082 2099 | 
             
                    have_daw = False
         | 
| 2083 2100 | 
             
                    for vol in vfs.all_nodes.values():
         | 
| 2084 2101 | 
             
                        daw = vol.flags.get("daw") or self.args.daw
         | 
| @@ -2307,6 +2324,7 @@ class AuthSrv(object): | |
| 2307 2324 | 
             
                            "idx": "e2d" in vf,
         | 
| 2308 2325 | 
             
                            "itag": "e2t" in vf,
         | 
| 2309 2326 | 
             
                            "dnsort": "nsort" in vf,
         | 
| 2327 | 
            +
                            "dhsortn": vf["hsortn"],
         | 
| 2310 2328 | 
             
                            "dsort": vf["sort"],
         | 
| 2311 2329 | 
             
                            "dcrop": vf["crop"],
         | 
| 2312 2330 | 
             
                            "dth3x": vf["th3x"],
         | 
| @@ -2332,6 +2350,7 @@ class AuthSrv(object): | |
| 2332 2350 | 
             
                            "dgrid": "grid" in vf,
         | 
| 2333 2351 | 
             
                            "dgsel": "gsel" in vf,
         | 
| 2334 2352 | 
             
                            "dnsort": "nsort" in vf,
         | 
| 2353 | 
            +
                            "dhsortn": vf["hsortn"],
         | 
| 2335 2354 | 
             
                            "dsort": vf["sort"],
         | 
| 2336 2355 | 
             
                            "dcrop": vf["crop"],
         | 
| 2337 2356 | 
             
                            "dth3x": vf["th3x"],
         | 
    
        copyparty/cfg.py
    CHANGED
    
    | @@ -70,6 +70,7 @@ def vf_vmap()   : | |
| 70 70 | 
             
                }
         | 
| 71 71 | 
             
                for k in (
         | 
| 72 72 | 
             
                    "dbd",
         | 
| 73 | 
            +
                    "hsortn",
         | 
| 73 74 | 
             
                    "html_head",
         | 
| 74 75 | 
             
                    "lg_sbf",
         | 
| 75 76 | 
             
                    "md_sbf",
         | 
| @@ -191,6 +192,7 @@ flagcats = { | |
| 191 192 | 
             
                    "xvol": "do not follow symlinks leaving the volume root",
         | 
| 192 193 | 
             
                    "dotsrch": "show dotfiles in search results",
         | 
| 193 194 | 
             
                    "nodotsrch": "hide dotfiles in search results (default)",
         | 
| 195 | 
            +
                    "srch_excl": "exclude search results with URL matching this regex",
         | 
| 194 196 | 
             
                },
         | 
| 195 197 | 
             
                'database, audio tags\n"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...': {
         | 
| 196 198 | 
             
                    "mtp=.bpm=f,audio-bpm.py": 'uses the "audio-bpm.py" program to\ngenerate ".bpm" tags from uploads (f = overwrite tags)',
         | 
    
        copyparty/httpcli.py
    CHANGED
    
    | @@ -14,6 +14,7 @@ import re | |
| 14 14 | 
             
            import socket
         | 
| 15 15 | 
             
            import stat
         | 
| 16 16 | 
             
            import string
         | 
| 17 | 
            +
            import sys
         | 
| 17 18 | 
             
            import threading  # typechk
         | 
| 18 19 | 
             
            import time
         | 
| 19 20 | 
             
            import uuid
         | 
| @@ -76,6 +77,7 @@ from .util import ( | |
| 76 77 | 
             
                html_escape,
         | 
| 77 78 | 
             
                humansize,
         | 
| 78 79 | 
             
                ipnorm,
         | 
| 80 | 
            +
                justcopy,
         | 
| 79 81 | 
             
                load_resource,
         | 
| 80 82 | 
             
                loadpy,
         | 
| 81 83 | 
             
                log_reloc,
         | 
| @@ -120,6 +122,8 @@ if not hasattr(socket, "AF_UNIX"): | |
| 120 122 |  | 
| 121 123 | 
             
            _ = (argparse, threading)
         | 
| 122 124 |  | 
| 125 | 
            +
            USED4SEC = {"usedforsecurity": False} if sys.version_info > (3, 9) else {}
         | 
| 126 | 
            +
             | 
| 123 127 | 
             
            NO_CACHE = {"Cache-Control": "no-cache"}
         | 
| 124 128 |  | 
| 125 129 | 
             
            ALL_COOKIES = "k304 no304 js idxh dots cppwd cppws".split()
         | 
| @@ -133,6 +137,10 @@ READMES = [[0, ["preadme.md", "PREADME.md"]], [1, ["readme.md", "README.md"]]] | |
| 133 137 |  | 
| 134 138 | 
             
            RSS_SORT = {"m": "mt", "u": "at", "n": "fn", "s": "sz"}
         | 
| 135 139 |  | 
| 140 | 
            +
            A_FILE = os.stat_result(
         | 
| 141 | 
            +
                (0o644, -1, -1, 1, 1000, 1000, 8, 0x39230101, 0x39230101, 0x39230101)
         | 
| 142 | 
            +
            )
         | 
| 143 | 
            +
             | 
| 136 144 |  | 
| 137 145 | 
             
            class HttpCli(object):
         | 
| 138 146 | 
             
                """
         | 
| @@ -1238,7 +1246,7 @@ class HttpCli(object): | |
| 1238 1246 | 
             
                        self.log("RSS  %s @%s" % (self.req, self.uname))
         | 
| 1239 1247 |  | 
| 1240 1248 | 
             
                    if not self.can_read:
         | 
| 1241 | 
            -
                        return self.tx_404()
         | 
| 1249 | 
            +
                        return self.tx_404(True)
         | 
| 1242 1250 |  | 
| 1243 1251 | 
             
                    vn = self.vn
         | 
| 1244 1252 | 
             
                    if not vn.flags.get("rss"):
         | 
| @@ -1419,7 +1427,8 @@ class HttpCli(object): | |
| 1419 1427 |  | 
| 1420 1428 | 
             
                    depth = self.headers.get("depth", "infinity").lower()
         | 
| 1421 1429 | 
             
                    if depth == "infinity":
         | 
| 1422 | 
            -
                         | 
| 1430 | 
            +
                        # allow depth:0 from unmapped root, but require read-axs otherwise
         | 
| 1431 | 
            +
                        if not self.can_read and (self.vpath or self.asrv.vfs.realpath):
         | 
| 1423 1432 | 
             
                            t = "depth:infinity requires read-access in /%s"
         | 
| 1424 1433 | 
             
                            t = t % (self.vpath,)
         | 
| 1425 1434 | 
             
                            self.log(t, 3)
         | 
| @@ -1479,7 +1488,7 @@ class HttpCli(object): | |
| 1479 1488 | 
             
                        t2 = " or 'infinity'" if self.args.dav_inf else ""
         | 
| 1480 1489 | 
             
                        raise Pebkac(412, t.format(depth, t2))
         | 
| 1481 1490 |  | 
| 1482 | 
            -
                    if not self.can_read and not self.can_write and not  | 
| 1491 | 
            +
                    if not self.can_read and not self.can_write and not fgen:
         | 
| 1483 1492 | 
             
                        self.log("inaccessible: [%s]" % (self.vpath,))
         | 
| 1484 1493 | 
             
                        raise Pebkac(401, "authenticate")
         | 
| 1485 1494 |  | 
| @@ -1758,7 +1767,7 @@ class HttpCli(object): | |
| 1758 1767 |  | 
| 1759 1768 | 
             
                    if not self.can_write:
         | 
| 1760 1769 | 
             
                        t = "user %s does not have write-access under /%s"
         | 
| 1761 | 
            -
                        raise Pebkac(403, t % (self.uname, self.vn.vpath))
         | 
| 1770 | 
            +
                        raise Pebkac(403 if self.pw else 401, t % (self.uname, self.vn.vpath))
         | 
| 1762 1771 |  | 
| 1763 1772 | 
             
                    if not self.args.no_dav and self._applesan():
         | 
| 1764 1773 | 
             
                        return self.headers.get("content-length") == "0"
         | 
| @@ -2049,10 +2058,31 @@ class HttpCli(object): | |
| 2049 2058 | 
             
                            # small toctou, but better than clobbering a hardlink
         | 
| 2050 2059 | 
             
                            wunlink(self.log, path, vfs.flags)
         | 
| 2051 2060 |  | 
| 2061 | 
            +
                    hasher = None
         | 
| 2062 | 
            +
                    copier = hashcopy
         | 
| 2063 | 
            +
                    if "ck" in self.ouparam or "ck" in self.headers:
         | 
| 2064 | 
            +
                        zs = self.ouparam.get("ck") or self.headers.get("ck") or ""
         | 
| 2065 | 
            +
                        if not zs or zs == "no":
         | 
| 2066 | 
            +
                            copier = justcopy
         | 
| 2067 | 
            +
                        elif zs == "md5":
         | 
| 2068 | 
            +
                            hasher = hashlib.md5(**USED4SEC)
         | 
| 2069 | 
            +
                        elif zs == "sha1":
         | 
| 2070 | 
            +
                            hasher = hashlib.sha1(**USED4SEC)
         | 
| 2071 | 
            +
                        elif zs == "sha256":
         | 
| 2072 | 
            +
                            hasher = hashlib.sha256(**USED4SEC)
         | 
| 2073 | 
            +
                        elif zs in ("blake2", "b2"):
         | 
| 2074 | 
            +
                            hasher = hashlib.blake2b(**USED4SEC)
         | 
| 2075 | 
            +
                        elif zs in ("blake2s", "b2s"):
         | 
| 2076 | 
            +
                            hasher = hashlib.blake2s(**USED4SEC)
         | 
| 2077 | 
            +
                        elif zs == "sha512":
         | 
| 2078 | 
            +
                            pass
         | 
| 2079 | 
            +
                        else:
         | 
| 2080 | 
            +
                            raise Pebkac(500, "unknown hash alg")
         | 
| 2081 | 
            +
             | 
| 2052 2082 | 
             
                    f, fn = ren_open(fn, *open_a, **params)
         | 
| 2053 2083 | 
             
                    try:
         | 
| 2054 2084 | 
             
                        path = os.path.join(fdir, fn)
         | 
| 2055 | 
            -
                        post_sz, sha_hex, sha_b64 =  | 
| 2085 | 
            +
                        post_sz, sha_hex, sha_b64 = copier(reader, f, hasher, 0, self.args.s_wr_slp)
         | 
| 2056 2086 | 
             
                    finally:
         | 
| 2057 2087 | 
             
                        f.close()
         | 
| 2058 2088 |  | 
| @@ -2289,8 +2319,8 @@ class HttpCli(object): | |
| 2289 2319 | 
             
                        # kinda silly but has the least side effects
         | 
| 2290 2320 | 
             
                        return self.handle_new_md()
         | 
| 2291 2321 |  | 
| 2292 | 
            -
                    if act  | 
| 2293 | 
            -
                        return self.handle_plain_upload(file0)
         | 
| 2322 | 
            +
                    if act in ("bput", "uput"):
         | 
| 2323 | 
            +
                        return self.handle_plain_upload(file0, act == "uput")
         | 
| 2294 2324 |  | 
| 2295 2325 | 
             
                    if act == "tput":
         | 
| 2296 2326 | 
             
                        return self.handle_text_upload()
         | 
| @@ -2901,13 +2931,41 @@ class HttpCli(object): | |
| 2901 2931 | 
             
                    )
         | 
| 2902 2932 |  | 
| 2903 2933 | 
             
                def handle_plain_upload(
         | 
| 2904 | 
            -
                    self, | 
| 2934 | 
            +
                    self,
         | 
| 2935 | 
            +
                    file0     ,
         | 
| 2936 | 
            +
                    nohash ,
         | 
| 2905 2937 | 
             
                )  :
         | 
| 2906 2938 | 
             
                    assert self.parser
         | 
| 2907 2939 | 
             
                    nullwrite = self.args.nw
         | 
| 2908 2940 | 
             
                    vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
         | 
| 2909 2941 | 
             
                    self._assert_safe_rem(rem)
         | 
| 2910 2942 |  | 
| 2943 | 
            +
                    halg = "sha512"
         | 
| 2944 | 
            +
                    hasher = None
         | 
| 2945 | 
            +
                    copier = hashcopy
         | 
| 2946 | 
            +
                    if nohash:
         | 
| 2947 | 
            +
                        halg = ""
         | 
| 2948 | 
            +
                        copier = justcopy
         | 
| 2949 | 
            +
                    elif "ck" in self.ouparam or "ck" in self.headers:
         | 
| 2950 | 
            +
                        halg = self.ouparam.get("ck") or self.headers.get("ck") or ""
         | 
| 2951 | 
            +
                        if not halg or halg == "no":
         | 
| 2952 | 
            +
                            copier = justcopy
         | 
| 2953 | 
            +
                            halg = ""
         | 
| 2954 | 
            +
                        elif halg == "md5":
         | 
| 2955 | 
            +
                            hasher = hashlib.md5(**USED4SEC)
         | 
| 2956 | 
            +
                        elif halg == "sha1":
         | 
| 2957 | 
            +
                            hasher = hashlib.sha1(**USED4SEC)
         | 
| 2958 | 
            +
                        elif halg == "sha256":
         | 
| 2959 | 
            +
                            hasher = hashlib.sha256(**USED4SEC)
         | 
| 2960 | 
            +
                        elif halg in ("blake2", "b2"):
         | 
| 2961 | 
            +
                            hasher = hashlib.blake2b(**USED4SEC)
         | 
| 2962 | 
            +
                        elif halg in ("blake2s", "b2s"):
         | 
| 2963 | 
            +
                            hasher = hashlib.blake2s(**USED4SEC)
         | 
| 2964 | 
            +
                        elif halg == "sha512":
         | 
| 2965 | 
            +
                            pass
         | 
| 2966 | 
            +
                        else:
         | 
| 2967 | 
            +
                            raise Pebkac(500, "unknown hash alg")
         | 
| 2968 | 
            +
             | 
| 2911 2969 | 
             
                    upload_vpath = self.vpath
         | 
| 2912 2970 | 
             
                    lim = vfs.get_dbv(rem)[0].lim
         | 
| 2913 2971 | 
             
                    fdir_base = vfs.canonical(rem)
         | 
| @@ -3037,8 +3095,8 @@ class HttpCli(object): | |
| 3037 3095 | 
             
                                try:
         | 
| 3038 3096 | 
             
                                    tabspath = os.path.join(fdir, tnam)
         | 
| 3039 3097 | 
             
                                    self.log("writing to {}".format(tabspath))
         | 
| 3040 | 
            -
                                    sz, sha_hex, sha_b64 =  | 
| 3041 | 
            -
                                        p_data, f,  | 
| 3098 | 
            +
                                    sz, sha_hex, sha_b64 = copier(
         | 
| 3099 | 
            +
                                        p_data, f, hasher, max_sz, self.args.s_wr_slp
         | 
| 3042 3100 | 
             
                                    )
         | 
| 3043 3101 | 
             
                                    if sz == 0:
         | 
| 3044 3102 | 
             
                                        raise Pebkac(400, "empty files in post")
         | 
| @@ -3170,10 +3228,15 @@ class HttpCli(object): | |
| 3170 3228 | 
             
                        jmsg["error"] = errmsg
         | 
| 3171 3229 | 
             
                        errmsg = "ERROR: " + errmsg
         | 
| 3172 3230 |  | 
| 3231 | 
            +
                    if halg:
         | 
| 3232 | 
            +
                        file_fmt = '{0}: {1} // {2} // {3} bytes // <a href="/{4}">{5}</a> {6}\n'
         | 
| 3233 | 
            +
                    else:
         | 
| 3234 | 
            +
                        file_fmt = '{3} bytes // <a href="/{4}">{5}</a> {6}\n'
         | 
| 3235 | 
            +
             | 
| 3173 3236 | 
             
                    for sz, sha_hex, sha_b64, ofn, lfn, ap in files:
         | 
| 3174 3237 | 
             
                        vsuf = ""
         | 
| 3175 3238 | 
             
                        if (self.can_read or self.can_upget) and "fk" in vfs.flags:
         | 
| 3176 | 
            -
                            st = bos.stat(ap)
         | 
| 3239 | 
            +
                            st = A_FILE if nullwrite else bos.stat(ap)
         | 
| 3177 3240 | 
             
                            alg = 2 if "fka" in vfs.flags else 1
         | 
| 3178 3241 | 
             
                            vsuf = "?k=" + self.gen_fk(
         | 
| 3179 3242 | 
             
                                alg,
         | 
| @@ -3188,7 +3251,8 @@ class HttpCli(object): | |
| 3188 3251 |  | 
| 3189 3252 | 
             
                        vpath = "{}/{}".format(upload_vpath, lfn).strip("/")
         | 
| 3190 3253 | 
             
                        rel_url = quotep(self.args.RS + vpath) + vsuf
         | 
| 3191 | 
            -
                        msg +=  | 
| 3254 | 
            +
                        msg += file_fmt.format(
         | 
| 3255 | 
            +
                            halg,
         | 
| 3192 3256 | 
             
                            sha_hex[:56],
         | 
| 3193 3257 | 
             
                            sha_b64,
         | 
| 3194 3258 | 
             
                            sz,
         | 
| @@ -3204,13 +3268,14 @@ class HttpCli(object): | |
| 3204 3268 | 
             
                                self.host,
         | 
| 3205 3269 | 
             
                                rel_url,
         | 
| 3206 3270 | 
             
                            ),
         | 
| 3207 | 
            -
                            "sha512": sha_hex[:56],
         | 
| 3208 | 
            -
                            "sha_b64": sha_b64,
         | 
| 3209 3271 | 
             
                            "sz": sz,
         | 
| 3210 3272 | 
             
                            "fn": lfn,
         | 
| 3211 3273 | 
             
                            "fn_orig": ofn,
         | 
| 3212 3274 | 
             
                            "path": rel_url,
         | 
| 3213 3275 | 
             
                        }
         | 
| 3276 | 
            +
                        if halg:
         | 
| 3277 | 
            +
                            jpart[halg] = sha_hex[:56]
         | 
| 3278 | 
            +
                            jpart["sha_b64"] = sha_b64
         | 
| 3214 3279 | 
             
                        jmsg["files"].append(jpart)
         | 
| 3215 3280 |  | 
| 3216 3281 | 
             
                    vspd = self._spd(sz_total, False)
         | 
| @@ -4603,6 +4668,18 @@ class HttpCli(object): | |
| 4603 4668 | 
             
                    if "th" in self.ouparam:
         | 
| 4604 4669 | 
             
                        return self.tx_svg("e" + pt[:3])
         | 
| 4605 4670 |  | 
| 4671 | 
            +
                    # most webdav clients will not send credentials until they
         | 
| 4672 | 
            +
                    # get 401'd, so send a challenge if we're Absolutely Sure
         | 
| 4673 | 
            +
                    # that the client is not a graphical browser
         | 
| 4674 | 
            +
                    if (
         | 
| 4675 | 
            +
                        rc == 403
         | 
| 4676 | 
            +
                        and not self.pw
         | 
| 4677 | 
            +
                        and not self.ua.startswith("Mozilla/")
         | 
| 4678 | 
            +
                        and "sec-fetch-site" not in self.headers
         | 
| 4679 | 
            +
                    ):
         | 
| 4680 | 
            +
                        rc = 401
         | 
| 4681 | 
            +
                        self.out_headers["WWW-Authenticate"] = 'Basic realm="a"'
         | 
| 4682 | 
            +
             | 
| 4606 4683 | 
             
                    t = t.format(self.args.SR)
         | 
| 4607 4684 | 
             
                    qv = quotep(self.vpaths) + self.ourlq()
         | 
| 4608 4685 | 
             
                    html = self.j2s(
         | 
| @@ -5243,7 +5320,7 @@ class HttpCli(object): | |
| 5243 5320 | 
             
                        st = bos.stat(abspath)
         | 
| 5244 5321 | 
             
                    except:
         | 
| 5245 5322 | 
             
                        if "on404" not in vn.flags:
         | 
| 5246 | 
            -
                            return self.tx_404()
         | 
| 5323 | 
            +
                            return self.tx_404(not self.can_read)
         | 
| 5247 5324 |  | 
| 5248 5325 | 
             
                        ret = self.on40x(vn.flags["on404"], vn, rem)
         | 
| 5249 5326 | 
             
                        if ret == "true":
         | 
| @@ -5254,9 +5331,9 @@ class HttpCli(object): | |
| 5254 5331 | 
             
                            try:
         | 
| 5255 5332 | 
             
                                st = bos.stat(abspath)
         | 
| 5256 5333 | 
             
                            except:
         | 
| 5257 | 
            -
                                return self.tx_404()
         | 
| 5334 | 
            +
                                return self.tx_404(not self.can_read)
         | 
| 5258 5335 | 
             
                        else:
         | 
| 5259 | 
            -
                            return self.tx_404()
         | 
| 5336 | 
            +
                            return self.tx_404(not self.can_read)
         | 
| 5260 5337 |  | 
| 5261 5338 | 
             
                    if rem.startswith(".hist/up2k.") or (
         | 
| 5262 5339 | 
             
                        rem.endswith("/dir.txt") and rem.startswith(".hist/th/")
         | 
| @@ -5383,13 +5460,13 @@ class HttpCli(object): | |
| 5383 5460 | 
             
                                vrem = vjoin(vrem, fn)
         | 
| 5384 5461 | 
             
                                abspath = ap2
         | 
| 5385 5462 | 
             
                                break
         | 
| 5386 | 
            -
                        elif self.vpath.rsplit("/", 1)[1] in ("index.htm", "index.html"):
         | 
| 5463 | 
            +
                        elif self.vpath.rsplit("/", 1)[-1] in ("index.htm", "index.html"):
         | 
| 5387 5464 | 
             
                            fk_pass = True
         | 
| 5388 5465 |  | 
| 5389 5466 | 
             
                    if not is_dir and (self.can_read or self.can_get):
         | 
| 5390 5467 | 
             
                        if not self.can_read and not fk_pass and "fk" in vn.flags:
         | 
| 5391 5468 | 
             
                            if not use_filekey:
         | 
| 5392 | 
            -
                                return self.tx_404()
         | 
| 5469 | 
            +
                                return self.tx_404(True)
         | 
| 5393 5470 |  | 
| 5394 5471 | 
             
                        if add_og and not abspath.lower().endswith(".md"):
         | 
| 5395 5472 | 
             
                            if og_ua or self.host not in self.headers.get("referer", ""):
         | 
    
        copyparty/mdns.py
    CHANGED
    
    | @@ -25,6 +25,7 @@ from .stolen.dnslib import ( | |
| 25 25 | 
             
                DNSHeader,
         | 
| 26 26 | 
             
                DNSQuestion,
         | 
| 27 27 | 
             
                DNSRecord,
         | 
| 28 | 
            +
                set_avahi_379,
         | 
| 28 29 | 
             
            )
         | 
| 29 30 | 
             
            from .util import CachedSet, Daemon, Netdev, list_ips, min_ex
         | 
| 30 31 |  | 
| @@ -68,6 +69,9 @@ class MDNS(MCast): | |
| 68 69 | 
             
                    self.ngen = ngen
         | 
| 69 70 | 
             
                    self.ttl = 300
         | 
| 70 71 |  | 
| 72 | 
            +
                    if not self.args.zm_nwa_1:
         | 
| 73 | 
            +
                        set_avahi_379()
         | 
| 74 | 
            +
             | 
| 71 75 | 
             
                    zs = self.args.name + ".local."
         | 
| 72 76 | 
             
                    zs = zs.encode("ascii", "replace").decode("ascii", "replace")
         | 
| 73 77 | 
             
                    self.hn = "-".join(x for x in zs.split("?") if x) or (
         | 
| @@ -332,6 +336,9 @@ class MDNS(MCast): | |
| 332 336 | 
             
                                    self.log("stopped", 2)
         | 
| 333 337 | 
             
                                    return
         | 
| 334 338 |  | 
| 339 | 
            +
                                if self.args.zm_no_pe:
         | 
| 340 | 
            +
                                    continue
         | 
| 341 | 
            +
             | 
| 335 342 | 
             
                                t = "{} {} \033[33m|{}| {}\n{}".format(
         | 
| 336 343 | 
             
                                    self.srv[sck].name, addr, len(buf), repr(buf)[2:-1], min_ex()
         | 
| 337 344 | 
             
                                )
         | 
    
        copyparty/res/COPYING.txt
    CHANGED
    
    | @@ -75,21 +75,78 @@ Copyright (c) 2022 Fonticons, Inc. | |
| 75 75 | 
             
            License: SIL OFL 1.1
         | 
| 76 76 |  | 
| 77 77 |  | 
| 78 | 
            -
                                          [07m--- MIT License ---[0m
         | 
| 79 78 |  | 
| 80 79 |  | 
| 81 80 |  | 
| 82 | 
            -
                                          [07m---  | 
| 81 | 
            +
                                          [07m--- MIT License ---[0m
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
         | 
| 83 84 |  | 
| 85 | 
            +
            The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
         | 
| 84 86 |  | 
| 87 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
| 85 88 |  | 
| 86 89 | 
             
                                      [07m--- BSD 2-Clause License ---[0m
         | 
| 87 90 |  | 
| 91 | 
            +
            Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
         | 
| 88 94 |  | 
| 95 | 
            +
            2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
         | 
| 89 98 |  | 
| 90 99 | 
             
                                      [07m--- BSD 3-Clause License ---[0m
         | 
| 91 100 |  | 
| 101 | 
            +
            Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
         | 
| 92 106 |  | 
| 107 | 
            +
            3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
         | 
| 93 110 |  | 
| 94 111 | 
             
                                   [07m--- SIL Open Font License v1.1 ---[0m
         | 
| 95 112 |  | 
| 113 | 
            +
            PREAMBLE
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
         | 
| 118 | 
            +
             | 
| 119 | 
            +
            DEFINITIONS
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            "Reserved Font Name" refers to any names specified as such after the copyright statement(s).
         | 
| 124 | 
            +
             | 
| 125 | 
            +
            "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
         | 
| 126 | 
            +
             | 
| 127 | 
            +
            "Modified Version" refers to any derivative made by adding to, deleting, or substituting - in part or in whole - any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
         | 
| 130 | 
            +
             | 
| 131 | 
            +
            PERMISSION & CONDITIONS
         | 
| 132 | 
            +
             | 
| 133 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
         | 
| 134 | 
            +
             | 
| 135 | 
            +
            1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
         | 
| 136 | 
            +
             | 
| 137 | 
            +
            2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
         | 
| 138 | 
            +
             | 
| 139 | 
            +
            3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
         | 
| 140 | 
            +
             | 
| 141 | 
            +
            4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
         | 
| 142 | 
            +
             | 
| 143 | 
            +
            5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
         | 
| 144 | 
            +
             | 
| 145 | 
            +
            TERMINATION
         | 
| 146 | 
            +
             | 
| 147 | 
            +
            This license becomes null and void if any of the above conditions are not met.
         | 
| 148 | 
            +
             | 
| 149 | 
            +
            DISCLAIMER
         | 
| 150 | 
            +
             | 
| 151 | 
            +
            THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
         | 
| 152 | 
            +
             | 
    
        copyparty/stolen/dnslib/dns.py
    CHANGED
    
    | @@ -8,7 +8,7 @@ from itertools import chain | |
| 8 8 | 
             
            from .bimap import Bimap, BimapError
         | 
| 9 9 | 
             
            from .bit import get_bits, set_bits
         | 
| 10 10 | 
             
            from .buffer import BufferError
         | 
| 11 | 
            -
            from .label import DNSBuffer, DNSLabel
         | 
| 11 | 
            +
            from .label import DNSBuffer, DNSLabel, set_avahi_379
         | 
| 12 12 | 
             
            from .ranges import IP4, IP6, H, I, check_bytes
         | 
| 13 13 |  | 
| 14 14 |  | 
| @@ -426,7 +426,7 @@ class RR(object): | |
| 426 426 | 
             
                        if rdlength:
         | 
| 427 427 | 
             
                            rdata = RDMAP.get(QTYPE.get(rtype), RD).parse(buffer, rdlength)
         | 
| 428 428 | 
             
                        else:
         | 
| 429 | 
            -
                            rdata = ""
         | 
| 429 | 
            +
                            rdata = RD(b"a")
         | 
| 430 430 | 
             
                        return cls(rname, rtype, rclass, ttl, rdata)
         | 
| 431 431 | 
             
                    except (BufferError, BimapError) as e:
         | 
| 432 432 | 
             
                        raise DNSError("Error unpacking RR [offset=%d]: %s" % (buffer.offset, e))
         | 
    
        copyparty/stolen/dnslib/label.py
    CHANGED
    
    | @@ -11,6 +11,23 @@ LDH = set(range(33, 127)) | |
| 11 11 | 
             
            ESCAPE = re.compile(r"\\([0-9][0-9][0-9])")
         | 
| 12 12 |  | 
| 13 13 |  | 
| 14 | 
            +
            avahi_379 = 0
         | 
| 15 | 
            +
             | 
| 16 | 
            +
             | 
| 17 | 
            +
            def set_avahi_379():
         | 
| 18 | 
            +
                global avahi_379
         | 
| 19 | 
            +
                avahi_379 = 1
         | 
| 20 | 
            +
             | 
| 21 | 
            +
             | 
| 22 | 
            +
            def log_avahi_379(args):
         | 
| 23 | 
            +
                global avahi_379
         | 
| 24 | 
            +
                if avahi_379 == 2:
         | 
| 25 | 
            +
                    return
         | 
| 26 | 
            +
                avahi_379 = 2
         | 
| 27 | 
            +
                t = "Invalid pointer in DNSLabel [offset=%d,pointer=%d,length=%d];\n\033[35m  NOTE: this is probably avahi-bug #379, packet corruption in Avahi's mDNS-reflection feature. Copyparty has a workaround and is OK, but other devices need either --zm4 or --zm6"
         | 
| 28 | 
            +
                raise BufferError(t % args)
         | 
| 29 | 
            +
             | 
| 30 | 
            +
             | 
| 14 31 | 
             
            class DNSLabelError(Exception):
         | 
| 15 32 | 
             
                pass
         | 
| 16 33 |  | 
| @@ -96,8 +113,11 @@ class DNSBuffer(Buffer): | |
| 96 113 | 
             
                                )
         | 
| 97 114 | 
             
                            if pointer < self.offset:
         | 
| 98 115 | 
             
                                self.offset = pointer
         | 
| 116 | 
            +
                            elif avahi_379:
         | 
| 117 | 
            +
                                log_avahi_379((self.offset, pointer, len(self.data)))
         | 
| 118 | 
            +
                                label.extend(b"a")
         | 
| 119 | 
            +
                                break
         | 
| 99 120 | 
             
                            else:
         | 
| 100 | 
            -
             | 
| 101 121 | 
             
                                raise BufferError(
         | 
| 102 122 | 
             
                                    "Invalid pointer in DNSLabel [offset=%d,pointer=%d,length=%d]"
         | 
| 103 123 | 
             
                                    % (self.offset, pointer, len(self.data))
         | 
    
        copyparty/svchub.py
    CHANGED
    
    | @@ -783,7 +783,7 @@ class SvcHub(object): | |
| 783 783 | 
             
                    al.exp_md = odfusion(exp, al.exp_md.replace(" ", ","))
         | 
| 784 784 | 
             
                    al.exp_lg = odfusion(exp, al.exp_lg.replace(" ", ","))
         | 
| 785 785 |  | 
| 786 | 
            -
                    for k in ["no_hash", "no_idx", "og_ua"]:
         | 
| 786 | 
            +
                    for k in ["no_hash", "no_idx", "og_ua", "srch_excl"]:
         | 
| 787 787 | 
             
                        ptn = getattr(self.args, k)
         | 
| 788 788 | 
             
                        if ptn:
         | 
| 789 789 | 
             
                            setattr(self.args, k, re.compile(ptn))
         | 
    
        copyparty/u2idx.py
    CHANGED
    
    | @@ -318,7 +318,8 @@ class U2idx(object): | |
| 318 318 | 
             
                    sort ,
         | 
| 319 319 | 
             
                    lim ,
         | 
| 320 320 | 
             
                )     :
         | 
| 321 | 
            -
                     | 
| 321 | 
            +
                    dbg = self.args.srch_dbg
         | 
| 322 | 
            +
                    if dbg:
         | 
| 322 323 | 
             
                        t = "searching across all %s volumes in which the user has 'r' (full read access):\n  %s"
         | 
| 323 324 | 
             
                        zs = "\n  ".join(["/%s = %s" % (x.vpath, x.realpath) for x in vols])
         | 
| 324 325 | 
             
                        self.log(t % (len(vols), zs), 5)
         | 
| @@ -361,14 +362,14 @@ class U2idx(object): | |
| 361 362 | 
             
                        if not cur:
         | 
| 362 363 | 
             
                            continue
         | 
| 363 364 |  | 
| 364 | 
            -
                         | 
| 365 | 
            -
                         | 
| 366 | 
            -
             | 
| 367 | 
            -
                                excl.append(vp2[len(vtop) :].lstrip("/"))
         | 
| 365 | 
            +
                        dots = flags.get("dotsrch") and uname in vol.axs.udot
         | 
| 366 | 
            +
                        zs = "srch_re_dots" if dots else "srch_re_nodot"
         | 
| 367 | 
            +
                        rex  = flags.get(zs)  # type: ignore
         | 
| 368 368 |  | 
| 369 | 
            -
                        if  | 
| 370 | 
            -
                            t = "searching in volume /%s (%s),  | 
| 371 | 
            -
                            self.log(t % (vtop, ptop,  | 
| 369 | 
            +
                        if dbg:
         | 
| 370 | 
            +
                            t = "searching in volume /%s (%s), excluding %s"
         | 
| 371 | 
            +
                            self.log(t % (vtop, ptop, rex.pattern), 5)
         | 
| 372 | 
            +
                            rex_cfg  = flags.get("srch_excl")
         | 
| 372 373 |  | 
| 373 374 | 
             
                        self.active_cur = cur
         | 
| 374 375 |  | 
| @@ -381,7 +382,6 @@ class U2idx(object): | |
| 381 382 |  | 
| 382 383 | 
             
                        sret = []
         | 
| 383 384 | 
             
                        fk = flags.get("fk")
         | 
| 384 | 
            -
                        dots = flags.get("dotsrch") and uname in vol.axs.udot
         | 
| 385 385 | 
             
                        fk_alg = 2 if "fka" in flags else 1
         | 
| 386 386 | 
             
                        c = cur.execute(uq, tuple(vuv))
         | 
| 387 387 | 
             
                        for hit in c:
         | 
| @@ -390,20 +390,23 @@ class U2idx(object): | |
| 390 390 | 
             
                            if rd.startswith("//") or fn.startswith("//"):
         | 
| 391 391 | 
             
                                rd, fn = s3dec(rd, fn)
         | 
| 392 392 |  | 
| 393 | 
            -
                             | 
| 394 | 
            -
                                if self.args.srch_dbg:
         | 
| 395 | 
            -
                                    zs = vjoin(vjoin(vtop, rd), fn)
         | 
| 396 | 
            -
                                    t = "database inconsistency in volume '/%s'; ignoring: %s"
         | 
| 397 | 
            -
                                    self.log(t % (vtop, zs), 1)
         | 
| 398 | 
            -
                                continue
         | 
| 393 | 
            +
                            vp = vjoin(vjoin(vtop, rd), fn)
         | 
| 399 394 |  | 
| 400 | 
            -
                             | 
| 401 | 
            -
                            if not dots and "/." in ("/" + rp):
         | 
| 395 | 
            +
                            if vp in seen_rps:
         | 
| 402 396 | 
             
                                continue
         | 
| 403 397 |  | 
| 404 | 
            -
                            if  | 
| 398 | 
            +
                            if rex.search(vp):
         | 
| 399 | 
            +
                                if dbg:
         | 
| 400 | 
            +
                                    if rex_cfg and rex_cfg.search(vp):  # type: ignore
         | 
| 401 | 
            +
                                        self.log("filtered by srch_excl: %s" % (vp,), 6)
         | 
| 402 | 
            +
                                    elif not dots and "/." in ("/" + vp):
         | 
| 403 | 
            +
                                        pass
         | 
| 404 | 
            +
                                    else:
         | 
| 405 | 
            +
                                        t = "database inconsistency in volume '/%s'; ignoring: %s"
         | 
| 406 | 
            +
                                        self.log(t % (vtop, vp), 1)
         | 
| 405 407 | 
             
                                continue
         | 
| 406 408 |  | 
| 409 | 
            +
                            rp = quotep(vp)
         | 
| 407 410 | 
             
                            if not fk:
         | 
| 408 411 | 
             
                                suf = ""
         | 
| 409 412 | 
             
                            else:
         | 
| @@ -425,7 +428,7 @@ class U2idx(object): | |
| 425 428 | 
             
                            if lim < 0:
         | 
| 426 429 | 
             
                                break
         | 
| 427 430 |  | 
| 428 | 
            -
                            if  | 
| 431 | 
            +
                            if dbg:
         | 
| 429 432 | 
             
                                t = "in volume '/%s': hit: %s"
         | 
| 430 433 | 
             
                                self.log(t % (vtop, rp), 5)
         | 
| 431 434 |  | 
| @@ -455,7 +458,7 @@ class U2idx(object): | |
| 455 458 | 
             
                        ret.extend(sret)
         | 
| 456 459 | 
             
                        # print("[{}] {}".format(ptop, sret))
         | 
| 457 460 |  | 
| 458 | 
            -
                        if  | 
| 461 | 
            +
                        if dbg:
         | 
| 459 462 | 
             
                            t = "in volume '/%s': got %d hits, %d total so far"
         | 
| 460 463 | 
             
                            self.log(t % (vtop, len(sret), len(ret)), 5)
         | 
| 461 464 |  | 
    
        copyparty/up2k.py
    CHANGED
    
    | @@ -85,16 +85,23 @@ if TYPE_CHECKING: | |
| 85 85 | 
             
            zsg = "avif,avifs,bmp,gif,heic,heics,heif,heifs,ico,j2p,j2k,jp2,jpeg,jpg,jpx,png,tga,tif,tiff,webp"
         | 
| 86 86 | 
             
            CV_EXTS = set(zsg.split(","))
         | 
| 87 87 |  | 
| 88 | 
            +
            zsg = "nohash noidx xdev xvol"
         | 
| 89 | 
            +
            VF_AFFECTS_INDEXING = set(zsg.split(" "))
         | 
| 90 | 
            +
             | 
| 88 91 |  | 
| 89 92 | 
             
            SBUSY = "cannot receive uploads right now;\nserver busy with %s.\nPlease wait; the client will retry..."
         | 
| 90 93 |  | 
| 91 94 | 
             
            HINT_HISTPATH = "you could try moving the database to another location (preferably an SSD or NVME drive) using either the --hist argument (global option for all volumes), or the hist volflag (just for this volume)"
         | 
| 92 95 |  | 
| 93 96 |  | 
| 97 | 
            +
            NULLSTAT = os.stat_result((0, -1, -1, 0, 0, 0, 0, 0, 0, 0))
         | 
| 98 | 
            +
             | 
| 99 | 
            +
             | 
| 94 100 | 
             
            class Dbw(object):
         | 
| 95 | 
            -
                def __init__(self, c , n , t )  :
         | 
| 101 | 
            +
                def __init__(self, c , n , nf , t )  :
         | 
| 96 102 | 
             
                    self.c = c
         | 
| 97 103 | 
             
                    self.n = n
         | 
| 104 | 
            +
                    self.nf = nf
         | 
| 98 105 | 
             
                    self.t = t
         | 
| 99 106 |  | 
| 100 107 |  | 
| @@ -1067,7 +1074,8 @@ class Up2k(object): | |
| 1067 1074 | 
             
                    ft = "\033[0;32m{}{:.0}"
         | 
| 1068 1075 | 
             
                    ff = "\033[0;35m{}{:.0}"
         | 
| 1069 1076 | 
             
                    fv = "\033[0;36m{}:\033[90m{}"
         | 
| 1070 | 
            -
                     | 
| 1077 | 
            +
                    zs = "html_head mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot"
         | 
| 1078 | 
            +
                    fx = set(zs.split())
         | 
| 1071 1079 | 
             
                    fd = vf_bmap()
         | 
| 1072 1080 | 
             
                    fd.update(vf_cmap())
         | 
| 1073 1081 | 
             
                    fd.update(vf_vmap())
         | 
| @@ -1224,10 +1232,17 @@ class Up2k(object): | |
| 1224 1232 | 
             
                def _verify_db_cache(self, cur , vpath )  :
         | 
| 1225 1233 | 
             
                    # check if list of intersecting volumes changed since last use; drop caches if so
         | 
| 1226 1234 | 
             
                    prefix = (vpath + "/").lstrip("/")
         | 
| 1227 | 
            -
                     | 
| 1228 | 
            -
                     | 
| 1229 | 
            -
                     | 
| 1230 | 
            -
             | 
| 1235 | 
            +
                    vps = [x for x in self.vfs.all_vols if x.startswith(prefix)]
         | 
| 1236 | 
            +
                    vps.sort()
         | 
| 1237 | 
            +
                    seed = [x[len(prefix) :] for x in vps]
         | 
| 1238 | 
            +
             | 
| 1239 | 
            +
                    # also consider volflags which affect indexing
         | 
| 1240 | 
            +
                    for vp in vps:
         | 
| 1241 | 
            +
                        vf = self.vfs.all_vols[vp].flags
         | 
| 1242 | 
            +
                        vf = {k: v for k, v in vf.items() if k in VF_AFFECTS_INDEXING}
         | 
| 1243 | 
            +
                        seed.append(str(sorted(vf.items())))
         | 
| 1244 | 
            +
             | 
| 1245 | 
            +
                    zb = hashlib.sha1("\n".join(seed).encode("utf-8", "replace")).digest()
         | 
| 1231 1246 | 
             
                    vcfg = ub64enc(zb[:18]).decode("ascii")
         | 
| 1232 1247 |  | 
| 1233 1248 | 
             
                    c = cur.execute("select v from kv where k = 'volcfg'")
         | 
| @@ -1259,7 +1274,7 @@ class Up2k(object): | |
| 1259 1274 |  | 
| 1260 1275 | 
             
                        cur, db_path = reg
         | 
| 1261 1276 |  | 
| 1262 | 
            -
                        db = Dbw(cur, 0, time.time())
         | 
| 1277 | 
            +
                        db = Dbw(cur, 0, 0, time.time())
         | 
| 1263 1278 | 
             
                        self.pp.n = next(db.c.execute("select count(w) from up"))[0]
         | 
| 1264 1279 |  | 
| 1265 1280 | 
             
                        excl = [
         | 
| @@ -1311,7 +1326,7 @@ class Up2k(object): | |
| 1311 1326 | 
             
                                self.hub.log_stacks()
         | 
| 1312 1327 |  | 
| 1313 1328 | 
             
                        if db.n:
         | 
| 1314 | 
            -
                            self.log("commit  | 
| 1329 | 
            +
                            self.log("commit %d new files; %d updates" % (db.nf, db.n))
         | 
| 1315 1330 |  | 
| 1316 1331 | 
             
                        if self.args.no_dhash:
         | 
| 1317 1332 | 
             
                            if db.c.execute("select d from dh").fetchone():
         | 
| @@ -1612,12 +1627,13 @@ class Up2k(object): | |
| 1612 1627 | 
             
                        # skip upload hooks by not providing vflags
         | 
| 1613 1628 | 
             
                        self.db_add(db.c, {}, rd, fn, lmod, sz, "", "", wark, wark, "", "", ip, at)
         | 
| 1614 1629 | 
             
                        db.n += 1
         | 
| 1630 | 
            +
                        db.nf += 1
         | 
| 1615 1631 | 
             
                        tfa += 1
         | 
| 1616 1632 | 
             
                        td = time.time() - db.t
         | 
| 1617 1633 | 
             
                        if db.n >= 4096 or td >= 60:
         | 
| 1618 | 
            -
                            self.log("commit  | 
| 1634 | 
            +
                            self.log("commit %d new files; %d updates" % (db.nf, db.n))
         | 
| 1619 1635 | 
             
                            db.c.connection.commit()
         | 
| 1620 | 
            -
                            db.n = 0
         | 
| 1636 | 
            +
                            db.n = db.nf = 0
         | 
| 1621 1637 | 
             
                            db.t = time.time()
         | 
| 1622 1638 |  | 
| 1623 1639 | 
             
                    if not self.args.no_dhash:
         | 
| @@ -1630,7 +1646,7 @@ class Up2k(object): | |
| 1630 1646 | 
             
                    # drop shadowed folders
         | 
| 1631 1647 | 
             
                    for sh_rd in unreg:
         | 
| 1632 1648 | 
             
                        n = 0
         | 
| 1633 | 
            -
                        q = "select count(w) from up where (rd=? or rd like ?||'/%') | 
| 1649 | 
            +
                        q = "select count(w) from up where (rd=? or rd like ?||'/%')"
         | 
| 1634 1650 | 
             
                        for sh_erd in [sh_rd, "//" + w8b64enc(sh_rd)]:
         | 
| 1635 1651 | 
             
                            try:
         | 
| 1636 1652 | 
             
                                erd_erd = (sh_erd, sh_erd)
         | 
| @@ -1647,7 +1663,7 @@ class Up2k(object): | |
| 1647 1663 | 
             
                            q = "delete from dh where (d = ? or d like ?||'/%')"
         | 
| 1648 1664 | 
             
                            db.c.execute(q, erd_erd)
         | 
| 1649 1665 |  | 
| 1650 | 
            -
                            q = "delete from up where (rd=? or rd like ?||'/%') | 
| 1666 | 
            +
                            q = "delete from up where (rd=? or rd like ?||'/%')"
         | 
| 1651 1667 | 
             
                            db.c.execute(q, erd_erd)
         | 
| 1652 1668 | 
             
                            tfa += n
         | 
| 1653 1669 |  | 
| @@ -2914,7 +2930,7 @@ class Up2k(object): | |
| 2914 2930 | 
             
                                        raise Exception()
         | 
| 2915 2931 | 
             
                                except Exception as ex:
         | 
| 2916 2932 | 
             
                                    if n4g:
         | 
| 2917 | 
            -
                                        st =  | 
| 2933 | 
            +
                                        st = NULLSTAT
         | 
| 2918 2934 | 
             
                                    else:
         | 
| 2919 2935 | 
             
                                        lost.append((cur, dp_dir, dp_fn))
         | 
| 2920 2936 | 
             
                                        continue
         | 
| @@ -3061,7 +3077,8 @@ class Up2k(object): | |
| 3061 3077 | 
             
                                    if cur:
         | 
| 3062 3078 | 
             
                                        dupe = (cj["prel"], cj["name"], cj["lmod"])
         | 
| 3063 3079 | 
             
                                        try:
         | 
| 3064 | 
            -
                                            self.dupesched[src] | 
| 3080 | 
            +
                                            if dupe not in self.dupesched[src]:
         | 
| 3081 | 
            +
                                                self.dupesched[src].append(dupe)
         | 
| 3065 3082 | 
             
                                        except:
         | 
| 3066 3083 | 
             
                                            self.dupesched[src] = [dupe]
         | 
| 3067 3084 |  | 
| @@ -4636,6 +4653,13 @@ class Up2k(object): | |
| 4636 4653 | 
             
                                t = "forgetting partial upload {} ({})"
         | 
| 4637 4654 | 
             
                                p = self._vis_job_progress(job)
         | 
| 4638 4655 | 
             
                                self.log(t.format(wark, p))
         | 
| 4656 | 
            +
             | 
| 4657 | 
            +
                            src = djoin(ptop, vrem)
         | 
| 4658 | 
            +
                            zi = len(self.dupesched.pop(src, []))
         | 
| 4659 | 
            +
                            if zi:
         | 
| 4660 | 
            +
                                t = "...and forgetting %d links in dupesched"
         | 
| 4661 | 
            +
                                self.log(t % (zi,))
         | 
| 4662 | 
            +
             | 
| 4639 4663 | 
             
                            assert wark
         | 
| 4640 4664 | 
             
                            del reg[wark]
         | 
| 4641 4665 |  | 
    
        copyparty/util.py
    CHANGED
    
    | @@ -2709,6 +2709,26 @@ def yieldfile(fn , bufsz )    : | |
| 2709 2709 | 
             
                        yield buf
         | 
| 2710 2710 |  | 
| 2711 2711 |  | 
| 2712 | 
            +
            def justcopy(
         | 
| 2713 | 
            +
                fin   ,
         | 
| 2714 | 
            +
                fout  ,
         | 
| 2715 | 
            +
                hashobj ,
         | 
| 2716 | 
            +
                max_sz ,
         | 
| 2717 | 
            +
                slp ,
         | 
| 2718 | 
            +
            )    :
         | 
| 2719 | 
            +
                tlen = 0
         | 
| 2720 | 
            +
                for buf in fin:
         | 
| 2721 | 
            +
                    tlen += len(buf)
         | 
| 2722 | 
            +
                    if max_sz and tlen > max_sz:
         | 
| 2723 | 
            +
                        continue
         | 
| 2724 | 
            +
             | 
| 2725 | 
            +
                    fout.write(buf)
         | 
| 2726 | 
            +
                    if slp:
         | 
| 2727 | 
            +
                        time.sleep(slp)
         | 
| 2728 | 
            +
             | 
| 2729 | 
            +
                return tlen, "checksum-disabled", "checksum-disabled"
         | 
| 2730 | 
            +
             | 
| 2731 | 
            +
             | 
| 2712 2732 | 
             
            def hashcopy(
         | 
| 2713 2733 | 
             
                fin   ,
         | 
| 2714 2734 | 
             
                fout  ,
         | 
| @@ -3416,7 +3436,6 @@ def runhook( | |
| 3416 3436 | 
             
                at ,
         | 
| 3417 3437 | 
             
                txt ,
         | 
| 3418 3438 | 
             
            )   :
         | 
| 3419 | 
            -
                asrv = (broker or up2k).asrv
         | 
| 3420 3439 | 
             
                args = (broker or up2k).args
         | 
| 3421 3440 | 
             
                vp = vp.replace("\\", "/")
         | 
| 3422 3441 | 
             
                ret = {"rc": 0}
         | 
    
        copyparty/web/a/u2c.py
    CHANGED
    
    | @@ -1,8 +1,8 @@ | |
| 1 1 | 
             
            #!/usr/bin/env python3
         | 
| 2 2 | 
             
            from __future__ import print_function, unicode_literals
         | 
| 3 3 |  | 
| 4 | 
            -
            S_VERSION = "2. | 
| 5 | 
            -
            S_BUILD_DT = "2024- | 
| 4 | 
            +
            S_VERSION = "2.7"
         | 
| 5 | 
            +
            S_BUILD_DT = "2024-12-06"
         | 
| 6 6 |  | 
| 7 7 | 
             
            """
         | 
| 8 8 | 
             
            u2c.py: upload to copyparty
         | 
| @@ -1031,8 +1031,8 @@ class Ctl(object): | |
| 1031 1031 | 
             
                        handshake(self.ar, file, False)
         | 
| 1032 1032 |  | 
| 1033 1033 | 
             
                def _fancy(self):
         | 
| 1034 | 
            +
                    atexit.register(self.cleanup_vt100)
         | 
| 1034 1035 | 
             
                    if VT100 and not self.ar.ns:
         | 
| 1035 | 
            -
                        atexit.register(self.cleanup_vt100)
         | 
| 1036 1036 | 
             
                        ss.scroll_region(3)
         | 
| 1037 1037 |  | 
| 1038 1038 | 
             
                    Daemon(self.hasher)
         | 
| @@ -1040,6 +1040,7 @@ class Ctl(object): | |
| 1040 1040 | 
             
                        Daemon(self.handshaker)
         | 
| 1041 1041 | 
             
                        Daemon(self.uploader)
         | 
| 1042 1042 |  | 
| 1043 | 
            +
                    last_sp = -1
         | 
| 1043 1044 | 
             
                    while True:
         | 
| 1044 1045 | 
             
                        with self.exit_cond:
         | 
| 1045 1046 | 
             
                            self.exit_cond.wait(0.07)
         | 
| @@ -1078,6 +1079,12 @@ class Ctl(object): | |
| 1078 1079 | 
             
                        else:
         | 
| 1079 1080 | 
             
                            txt = " "
         | 
| 1080 1081 |  | 
| 1082 | 
            +
                        if not VT100:  # OSC9;4 (taskbar-progress)
         | 
| 1083 | 
            +
                            sp = int(self.up_b * 100 / self.nbytes) or 1
         | 
| 1084 | 
            +
                            if last_sp != sp:
         | 
| 1085 | 
            +
                                last_sp = sp
         | 
| 1086 | 
            +
                                txt += "\033]9;4;1;%d\033\\" % (sp,)
         | 
| 1087 | 
            +
             | 
| 1081 1088 | 
             
                        if not self.up_br:
         | 
| 1082 1089 | 
             
                            spd = self.hash_b / ((time.time() - self.t0) or 1)
         | 
| 1083 1090 | 
             
                            eta = (self.nbytes - self.hash_b) / (spd or 1)
         | 
| @@ -1095,6 +1102,8 @@ class Ctl(object): | |
| 1095 1102 | 
             
                        tail = "\033[K\033[u" if VT100 and not self.ar.ns else "\r"
         | 
| 1096 1103 |  | 
| 1097 1104 | 
             
                        t = "%s eta @ %s/s, %s, %d# left\033[K" % (self.eta, spd, sleft, nleft)
         | 
| 1105 | 
            +
                        if not self.hash_b:
         | 
| 1106 | 
            +
                            t = " now hashing..."
         | 
| 1098 1107 | 
             
                        eprint(txt + "\033]0;{0}\033\\\r{0}{1}".format(t, tail))
         | 
| 1099 1108 |  | 
| 1100 1109 | 
             
                    if self.ar.wlist:
         | 
| @@ -1115,7 +1124,10 @@ class Ctl(object): | |
| 1115 1124 | 
             
                        handshake(self.ar, file, False)
         | 
| 1116 1125 |  | 
| 1117 1126 | 
             
                def cleanup_vt100(self):
         | 
| 1118 | 
            -
                     | 
| 1127 | 
            +
                    if VT100:
         | 
| 1128 | 
            +
                        ss.scroll_region(None)
         | 
| 1129 | 
            +
                    else:
         | 
| 1130 | 
            +
                        eprint("\033]9;4;0\033\\")
         | 
| 1119 1131 | 
             
                    eprint("\033[J\033]0;\033\\")
         | 
| 1120 1132 |  | 
| 1121 1133 | 
             
                def cb_hasher(self, file, ofs):
         | 
| @@ -1130,7 +1142,9 @@ class Ctl(object): | |
| 1130 1142 | 
             
                        isdir = stat.S_ISDIR(inf.st_mode)
         | 
| 1131 1143 | 
             
                        if self.ar.z or self.ar.drd:
         | 
| 1132 1144 | 
             
                            rd = rel if isdir else os.path.dirname(rel)
         | 
| 1133 | 
            -
                            srd = rd.decode("utf-8", "replace").replace("\\", "/")
         | 
| 1145 | 
            +
                            srd = rd.decode("utf-8", "replace").replace("\\", "/").rstrip("/")
         | 
| 1146 | 
            +
                            if srd:
         | 
| 1147 | 
            +
                                srd += "/"
         | 
| 1134 1148 | 
             
                            if prd != rd:
         | 
| 1135 1149 | 
             
                                prd = rd
         | 
| 1136 1150 | 
             
                                ls = {}
         | 
| @@ -1165,11 +1179,11 @@ class Ctl(object): | |
| 1165 1179 | 
             
                                    bnames = [x for x in ls if x not in lnodes and x != b".hist"]
         | 
| 1166 1180 | 
             
                                    vpath = self.ar.url.split("://")[-1].split("/", 1)[-1]
         | 
| 1167 1181 | 
             
                                    names = [x.decode("utf-8", WTF8) for x in bnames]
         | 
| 1168 | 
            -
                                    locs = [vpath + srd +  | 
| 1182 | 
            +
                                    locs = [vpath + srd + x for x in names]
         | 
| 1169 1183 | 
             
                                    while locs:
         | 
| 1170 1184 | 
             
                                        req = locs
         | 
| 1171 1185 | 
             
                                        while req:
         | 
| 1172 | 
            -
                                            print("DELETING ~%s | 
| 1186 | 
            +
                                            print("DELETING ~%s#%s" % (srd, len(req)))
         | 
| 1173 1187 | 
             
                                            body = json.dumps(req).encode("utf-8")
         | 
| 1174 1188 | 
             
                                            sc, txt = web.req(
         | 
| 1175 1189 | 
             
                                                "POST", self.ar.url + "?delete", {}, body, MJ
         | 
| @@ -1534,6 +1548,38 @@ source file/folder selection uses rsync syntax, meaning that: | |
| 1534 1548 | 
             
                        except:
         | 
| 1535 1549 | 
             
                            pass
         | 
| 1536 1550 |  | 
| 1551 | 
            +
                # msys2 doesn't uncygpath absolute paths with whitespace
         | 
| 1552 | 
            +
                if not VT100:
         | 
| 1553 | 
            +
                    zsl = []
         | 
| 1554 | 
            +
                    for fn in ar.files:
         | 
| 1555 | 
            +
                        if re.search("^/[a-z]/", fn):
         | 
| 1556 | 
            +
                            fn = r"%s:\%s" % (fn[1:2], fn[3:])
         | 
| 1557 | 
            +
                        zsl.append(fn.replace("/", "\\"))
         | 
| 1558 | 
            +
                    ar.files = zsl
         | 
| 1559 | 
            +
             | 
| 1560 | 
            +
                fok = []
         | 
| 1561 | 
            +
                fng = []
         | 
| 1562 | 
            +
                for fn in ar.files:
         | 
| 1563 | 
            +
                    if os.path.exists(fn):
         | 
| 1564 | 
            +
                        fok.append(fn)
         | 
| 1565 | 
            +
                    elif VT100:
         | 
| 1566 | 
            +
                        fng.append(fn)
         | 
| 1567 | 
            +
                    else:
         | 
| 1568 | 
            +
                        # windows leaves glob-expansion to the invoked process... okayyy let's get to work
         | 
| 1569 | 
            +
                        from glob import glob
         | 
| 1570 | 
            +
             | 
| 1571 | 
            +
                        fns = glob(fn)
         | 
| 1572 | 
            +
                        if fns:
         | 
| 1573 | 
            +
                            fok.extend(fns)
         | 
| 1574 | 
            +
                        else:
         | 
| 1575 | 
            +
                            fng.append(fn)
         | 
| 1576 | 
            +
             | 
| 1577 | 
            +
                if fng:
         | 
| 1578 | 
            +
                    t = "some files/folders were not found:\n  %s"
         | 
| 1579 | 
            +
                    raise Exception(t % ("\n  ".join(fng),))
         | 
| 1580 | 
            +
             | 
| 1581 | 
            +
                ar.files = fok
         | 
| 1582 | 
            +
             | 
| 1537 1583 | 
             
                if ar.drd:
         | 
| 1538 1584 | 
             
                    ar.dr = True
         | 
| 1539 1585 |  | 
    
        copyparty/web/browser.css.gz
    CHANGED
    
    | Binary file | 
    
        copyparty/web/browser.js.gz
    CHANGED
    
    | Binary file | 
    
        copyparty/web/deps/easymde.js.gz
    CHANGED
    
    | Binary file | 
    
        copyparty/web/deps/marked.js.gz
    CHANGED
    
    | Binary file | 
| Binary file | 
    
        copyparty/web/md.js.gz
    CHANGED
    
    | Binary file | 
    
        copyparty/web/md2.js.gz
    CHANGED
    
    | Binary file | 
    
        copyparty/web/up2k.js.gz
    CHANGED
    
    | Binary file | 
    
        copyparty/web/util.js.gz
    CHANGED
    
    | Binary file | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            Metadata-Version: 2.1
         | 
| 2 2 | 
             
            Name: copyparty
         | 
| 3 | 
            -
            Version: 1.16. | 
| 3 | 
            +
            Version: 1.16.4
         | 
| 4 4 | 
             
            Summary:   Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
         | 
| 5 5 | 
             
            Author-email: ed <copyparty@ocv.me>
         | 
| 6 6 | 
             
            License: MIT
         | 
| @@ -1152,11 +1152,12 @@ using the GUI  (winXP or later): | |
| 1152 1152 | 
             
              * on winXP only, click the `Sign up for online storage` hyperlink instead and put the URL there
         | 
| 1153 1153 | 
             
              * providing your password as the username is recommended; the password field can be anything or empty
         | 
| 1154 1154 |  | 
| 1155 | 
            -
             | 
| 1155 | 
            +
            the webdav client that's built into windows has the following list of bugs; you can avoid all of these by connecting with rclone instead:
         | 
| 1156 1156 | 
             
            * win7+ doesn't actually send the password to the server when reauthenticating after a reboot unless you first try to login with an incorrect password and then switch to the correct password
         | 
| 1157 1157 | 
             
              * or just type your password into the username field instead to get around it entirely
         | 
| 1158 1158 | 
             
            * connecting to a folder which allows anonymous read will make writing impossible, as windows has decided it doesn't need to login
         | 
| 1159 1159 | 
             
              * workaround: connect twice; first to a folder which requires auth, then to the folder you actually want, and leave both of those mounted
         | 
| 1160 | 
            +
              * or set the server-option `--dav-auth` to force password-auth for all webdav clients
         | 
| 1160 1161 | 
             
            * win7+ may open a new tcp connection for every file and sometimes forgets to close them, eventually needing a reboot
         | 
| 1161 1162 | 
             
              * maybe NIC-related (??), happens with win10-ltsc on e1000e but not virtio
         | 
| 1162 1163 | 
             
            * windows cannot access folders which contain filenames with invalid unicode or forbidden characters (`<>:"/\|?*`), or names ending with `.`
         | 
| @@ -1323,7 +1324,7 @@ note: | |
| 1323 1324 |  | 
| 1324 1325 | 
             
            ### exclude-patterns
         | 
| 1325 1326 |  | 
| 1326 | 
            -
            to save some time,  you can provide a regex pattern for filepaths to only index by filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash \.iso | 
| 1327 | 
            +
            to save some time,  you can provide a regex pattern for filepaths to only index by filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash '\.iso$'` or the volflag `:c,nohash=\.iso$`, this has the following consequences:
         | 
| 1327 1328 | 
             
            * initial indexing is way faster, especially when the volume is on a network disk
         | 
| 1328 1329 | 
             
            * makes it impossible to [file-search](#file-search)
         | 
| 1329 1330 | 
             
            * if someone uploads the same file contents, the upload will not be detected as a dupe, so it will not get symlinked or rejected
         | 
| @@ -1334,6 +1335,8 @@ similarly, you can fully ignore files/folders using `--no-idx [...]` and `:c,noi | |
| 1334 1335 |  | 
| 1335 1336 | 
             
            if you set `--no-hash [...]` globally, you can enable hashing for specific volumes using flag `:c,nohash=`
         | 
| 1336 1337 |  | 
| 1338 | 
            +
            to exclude certain filepaths from search-results, use `--srch-excl` or volflag `srch_excl` instead of `--no-idx`, for example `--srch-excl 'password|logs/[0-9]'`
         | 
| 1339 | 
            +
             | 
| 1337 1340 | 
             
            ### filesystem guards
         | 
| 1338 1341 |  | 
| 1339 1342 | 
             
            avoid traversing into other filesystems  using `--xdev` / volflag `:c,xdev`, skipping any symlinks or bind-mounts to another HDD for example
         | 
| @@ -1,21 +1,21 @@ | |
| 1 1 | 
             
            copyparty/__init__.py,sha256=iRCNvMPg6k9WG_O2uCtlkD4vWogH8EgP9elp9XwSIJs,2610
         | 
| 2 | 
            -
            copyparty/__main__.py,sha256= | 
| 3 | 
            -
            copyparty/__version__.py,sha256= | 
| 4 | 
            -
            copyparty/authsrv.py,sha256= | 
| 2 | 
            +
            copyparty/__main__.py,sha256=fA4D0yAs6twyBcawN9lM7CtHuFagTsQloljWX9MZ1j0,113240
         | 
| 3 | 
            +
            copyparty/__version__.py,sha256=PSHPFmB4WReQ90GVjo2tS3ZE1I6F3Z2igIEoAOtf4ZQ,251
         | 
| 4 | 
            +
            copyparty/authsrv.py,sha256=CIK5OKl38LehdAt4hidCMpsATdJhPABJPkJStpQpp9U,103405
         | 
| 5 5 | 
             
            copyparty/broker_mp.py,sha256=QdOXXvV2Xn6J0CysEqyY3GZbqxQMyWnTpnba-a5lMc0,4987
         | 
| 6 6 | 
             
            copyparty/broker_mpw.py,sha256=PpSS4SK3pItlpfD8OwVr3QmJEPKlUgaf2nuMOozixgU,3347
         | 
| 7 7 | 
             
            copyparty/broker_thr.py,sha256=fjoYtpSscUA7-nMl4r1n2R7UK3J9lrvLS3rUZ-iJzKQ,1721
         | 
| 8 8 | 
             
            copyparty/broker_util.py,sha256=76mfnFOpX1gUUvtjm8UQI7jpTIaVINX10QonM-B7ggc,1680
         | 
| 9 9 | 
             
            copyparty/cert.py,sha256=0ZAPeXeMR164vWn9GQU3JDKooYXEq_NOQkDeg543ivg,8009
         | 
| 10 | 
            -
            copyparty/cfg.py,sha256= | 
| 10 | 
            +
            copyparty/cfg.py,sha256=UUmFpFbTm750Nv9RnofS80-FTpWT37EggEtmkE1wbxE,10374
         | 
| 11 11 | 
             
            copyparty/dxml.py,sha256=lZpg-kn-kQsXRtNY1n6fRaS-b7uXzMCyv8ovKnhZcZc,1548
         | 
| 12 12 | 
             
            copyparty/fsutil.py,sha256=5CshJWO7CflfaRRNOb3JxghUH7W5rmS_HWNmKfx42MM,4538
         | 
| 13 13 | 
             
            copyparty/ftpd.py,sha256=T97SFS7JFtvRLbJX8C4fJSYwe13vhN3-E6emtlVmqLA,17608
         | 
| 14 | 
            -
            copyparty/httpcli.py,sha256= | 
| 14 | 
            +
            copyparty/httpcli.py,sha256=hwHJ8EwV30MHZ6hGPGfmCuow5Nu2daMV2EN7Ups5jb8,207704
         | 
| 15 15 | 
             
            copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
         | 
| 16 16 | 
             
            copyparty/httpsrv.py,sha256=PXLZlT6iuJZYG9ajNsAaNgRK3UtS4CrOGFGXOpkRtOk,18235
         | 
| 17 17 | 
             
            copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
         | 
| 18 | 
            -
            copyparty/mdns.py,sha256= | 
| 18 | 
            +
            copyparty/mdns.py,sha256=G73OWWg1copda47LgayCRK7qjVrk6cnUGpMR5ugmi7o,18315
         | 
| 19 19 | 
             
            copyparty/metrics.py,sha256=EOIiPOItEQmdK9YgNb75l0kCzanWb6RtJGwMI7ufifY,8966
         | 
| 20 20 | 
             
            copyparty/mtag.py,sha256=o-rxu4LajWF4H3HFmfCbXnKfs0GCQh1FQ0XywuW6wOc,19934
         | 
| 21 21 | 
             
            copyparty/multicast.py,sha256=Ha27l2oATEa-Qo2WOzkeRgjAm6G_YDCfbVJWR-ao2UE,12319
         | 
| @@ -24,19 +24,19 @@ copyparty/smbd.py,sha256=Or7RF13cl1r3ncnpVh8BqyAGqH2Oa04O9iPZWCoB0Bo,14609 | |
| 24 24 | 
             
            copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
         | 
| 25 25 | 
             
            copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
         | 
| 26 26 | 
             
            copyparty/sutil.py,sha256=JTMrQwcWH85hXB_cKG206eDZ967WZDGaP00AWvl_gB0,3214
         | 
| 27 | 
            -
            copyparty/svchub.py,sha256= | 
| 27 | 
            +
            copyparty/svchub.py,sha256=sAHkiPGzzKACLqKlem2V-bps9Xh-wHlcfwaNywxcd5A,40877
         | 
| 28 28 | 
             
            copyparty/szip.py,sha256=HFtnwOiBgx0HMLUf-h_T84zSlRijPxmhRo5PM613kRA,8602
         | 
| 29 29 | 
             
            copyparty/tcpsrv.py,sha256=VuW_aVDcyXIhIMZ8I-wpIouX8MI9TGp7KKSRohrMTtk,19897
         | 
| 30 30 | 
             
            copyparty/tftpd.py,sha256=FRCALO3PigoBlwUrqxoKHM_xk7wT2O0GPG1TvNRtjOY,13606
         | 
| 31 31 | 
             
            copyparty/th_cli.py,sha256=o6FMkerYvAXS455z3DUossVztu_nzFlYSQhs6qN6Jt8,4636
         | 
| 32 32 | 
             
            copyparty/th_srv.py,sha256=LBcB4LpsF-H7L52Z0Dhn9LogRjJVPp1GKa8MeIMIBnw,29596
         | 
| 33 | 
            -
            copyparty/u2idx.py,sha256= | 
| 34 | 
            -
            copyparty/up2k.py,sha256= | 
| 35 | 
            -
            copyparty/util.py,sha256= | 
| 33 | 
            +
            copyparty/u2idx.py,sha256=kF4TE-j78Faq_f4vGh5So5raYDYLt2UDpza6yBA3f54,13687
         | 
| 34 | 
            +
            copyparty/up2k.py,sha256=KQZcpqWRwjvmJLFEo3ejVWDroQi10jQdvgxuASDpUlY,174476
         | 
| 35 | 
            +
            copyparty/util.py,sha256=UyaYXfVO4nOkqhSMi9SZn2zK2W9texb9CL9rsqMak8I,95135
         | 
| 36 36 | 
             
            copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 37 37 | 
             
            copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
         | 
| 38 38 | 
             
            copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
         | 
| 39 | 
            -
            copyparty/res/COPYING.txt,sha256= | 
| 39 | 
            +
            copyparty/res/COPYING.txt,sha256=1LnBxkwJuC8mRBxuoMF2UIcpCjcteIzFHotSUE1Xte0,9776
         | 
| 40 40 | 
             
            copyparty/res/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 41 41 | 
             
            copyparty/res/insecure.pem,sha256=FEt7jgrn6ZHTlFopq_LFAN027YIoaHi0HQFBbzYnEwc,2876
         | 
| 42 42 | 
             
            copyparty/stolen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| @@ -46,8 +46,8 @@ copyparty/stolen/dnslib/__init__.py,sha256=SmYdaHdL5czxKxrOFGvzbfmojOthRky1GXKgm | |
| 46 46 | 
             
            copyparty/stolen/dnslib/bimap.py,sha256=j_ah40P3kOw0AQT5vvMcv7XbMtS0jV6Oe9YGCh7J57s,1182
         | 
| 47 47 | 
             
            copyparty/stolen/dnslib/bit.py,sha256=uDECHT5wM-QObxxRgc4lJq9CUcd7hglRLgs0K1X8mnU,348
         | 
| 48 48 | 
             
            copyparty/stolen/dnslib/buffer.py,sha256=bvj9rTn4EcccVuMWe1b5j57ex6elba3NCDBdevsnoSc,1407
         | 
| 49 | 
            -
            copyparty/stolen/dnslib/dns.py,sha256= | 
| 50 | 
            -
            copyparty/stolen/dnslib/label.py,sha256= | 
| 49 | 
            +
            copyparty/stolen/dnslib/dns.py,sha256=wEaStjfDhT2RI6lS6StwDrkS40afGnbHPue1duOT3vs,21020
         | 
| 50 | 
            +
            copyparty/stolen/dnslib/label.py,sha256=GKN4JkmZPdsHIyt70oFQYb4KB4Muh2zvGvla-6HiMQ0,5536
         | 
| 51 51 | 
             
            copyparty/stolen/dnslib/lex.py,sha256=XuXvz4PHzSaEdRRl8xnYaM8cAupzns0QW3oeFn-4isM,2615
         | 
| 52 52 | 
             
            copyparty/stolen/dnslib/ranges.py,sha256=XqR6NaRMm0xiEht590KhOjKjIuUFgW4I9_zPmPsGV0c,1810
         | 
| 53 53 | 
             
            copyparty/stolen/ifaddr/__init__.py,sha256=vpREjAyPubr5s1NJi91icXV3q1o4DrKAvHABwkoeJB0,706
         | 
| @@ -55,17 +55,17 @@ copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUu | |
| 55 55 | 
             
            copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
         | 
| 56 56 | 
             
            copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
         | 
| 57 57 | 
             
            copyparty/web/baguettebox.js.gz,sha256=_amC3ipOrXKEFz8DsVP-JEl49VjMQYiKyF78eWfG-uk,7965
         | 
| 58 | 
            -
            copyparty/web/browser.css.gz,sha256= | 
| 58 | 
            +
            copyparty/web/browser.css.gz,sha256=kurx_iA-KxLYx8PqJsn0bJVjkAxP-0YTOHSV9l_oouo,11645
         | 
| 59 59 | 
             
            copyparty/web/browser.html,sha256=KCkZ_LwzQnj9xhXKYptAxp6W3nVGiDoSJ4ioZDo7rQ0,4827
         | 
| 60 | 
            -
            copyparty/web/browser.js.gz,sha256= | 
| 60 | 
            +
            copyparty/web/browser.js.gz,sha256=7YbcHLByMAc71j3nKKUrCEExAzfenYnHPBSrnJ8iJFA,89833
         | 
| 61 61 | 
             
            copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
         | 
| 62 62 | 
             
            copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
         | 
| 63 63 | 
             
            copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
         | 
| 64 64 | 
             
            copyparty/web/md.css.gz,sha256=UZpN0J7ubVM05CZkbZYkQRJeGgJt_GNDEzKTGSQd8h4,2032
         | 
| 65 65 | 
             
            copyparty/web/md.html,sha256=isNbIzWbp_oqkhQSXm0uf3st3h4uB3LLTXFcHHcvgPQ,4189
         | 
| 66 | 
            -
            copyparty/web/md.js.gz,sha256= | 
| 66 | 
            +
            copyparty/web/md.js.gz,sha256=tw9vS9yZzyVW4FNOTv0MMHBdBEedfahZRZ8WhVJDu4A,4180
         | 
| 67 67 | 
             
            copyparty/web/md2.css.gz,sha256=uIVHKScThdbcfhXNSHgKZnALYpxbnXC-WuEzOJ20Lpc,699
         | 
| 68 | 
            -
            copyparty/web/md2.js.gz,sha256= | 
| 68 | 
            +
            copyparty/web/md2.js.gz,sha256=w0Ve06BUcyZfcWR8DAPpCj-kI-uL_wTL2aX_0PsNc-I,8363
         | 
| 69 69 | 
             
            copyparty/web/mde.css.gz,sha256=2SkAEDKIRPqywNJ8t_heQaeBQ_R73Rf-pQI_bDoKF6o,942
         | 
| 70 70 | 
             
            copyparty/web/mde.html,sha256=ImBhQAaEUCke2M85QU_fl4X2XQExRLcEzgCEN8RNe9o,1759
         | 
| 71 71 | 
             
            copyparty/web/mde.js.gz,sha256=kN2eUSvr4mFuksfK4-4LimJmWdwsao39Sea2lWtu8L0,2224
         | 
| @@ -80,12 +80,12 @@ copyparty/web/splash.js.gz,sha256=EEfsi9YGtPTYRB6MPX8Dfg4YyfqncI9ldJS7_MGVOhs,27 | |
| 80 80 | 
             
            copyparty/web/svcs.html,sha256=P5YZimYLeQMT0uz6u3clQSNZRc5Zs0Ok-ffcbcGSYuc,11762
         | 
| 81 81 | 
             
            copyparty/web/svcs.js.gz,sha256=k81ZvZ3I-f4fMHKrNGGOgOlvXnCBz0mVjD-8mieoWCA,520
         | 
| 82 82 | 
             
            copyparty/web/ui.css.gz,sha256=v8U-1tetZzuzTpITjq8NWj1gg3jEiYDIIE8aE0dx63k,2800
         | 
| 83 | 
            -
            copyparty/web/up2k.js.gz,sha256= | 
| 84 | 
            -
            copyparty/web/util.js.gz,sha256= | 
| 83 | 
            +
            copyparty/web/up2k.js.gz,sha256=vB3hLpRZzVStcN2-NAGjFdfroxFPbFvKss4vxVYyy1c,23371
         | 
| 84 | 
            +
            copyparty/web/util.js.gz,sha256=MMDIZbxzBwBYE5eYLzhGGpC7h257oEFZaBKZwJ4uhc8,15077
         | 
| 85 85 | 
             
            copyparty/web/w.hash.js.gz,sha256=l3GpSJD6mcU-1CRWkIj7PybgbjlfSr8oeO3vortIrQk,1105
         | 
| 86 86 | 
             
            copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 87 87 | 
             
            copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
         | 
| 88 | 
            -
            copyparty/web/a/u2c.py,sha256= | 
| 88 | 
            +
            copyparty/web/a/u2c.py,sha256=1XcH5XfC2UuGrpTnLVHCe4KFU0Vl6uC02CZz04rvKOc,51612
         | 
| 89 89 | 
             
            copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
         | 
| 90 90 | 
             
            copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
         | 
| 91 91 | 
             
            copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
         | 
| @@ -95,9 +95,9 @@ copyparty/web/dd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU, | |
| 95 95 | 
             
            copyparty/web/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 96 96 | 
             
            copyparty/web/deps/busy.mp3.gz,sha256=EVphk1_HYyRKJmtpeK99vbAstF7ub1f9ndu020H8PQ8,106
         | 
| 97 97 | 
             
            copyparty/web/deps/easymde.css.gz,sha256=vWxfueI64rPikuqFj69wJBtGisqf93AheQtOZqgUI_c,3041
         | 
| 98 | 
            -
            copyparty/web/deps/easymde.js.gz,sha256= | 
| 98 | 
            +
            copyparty/web/deps/easymde.js.gz,sha256=rHBs4XWQe2bmv7ZzDIk43oxnTwrwpq5laYHhV5sKQQo,77014
         | 
| 99 99 | 
             
            copyparty/web/deps/fuse.py,sha256=6j4Zy3VpQg629pwwIW77v2LJ1hy-qlyrxwhXfKl9B7I,33426
         | 
| 100 | 
            -
            copyparty/web/deps/marked.js.gz,sha256= | 
| 100 | 
            +
            copyparty/web/deps/marked.js.gz,sha256=3sviQ05gVpZF2s43evtFQZZSMSHV6K3OsS15OoBqb10,22610
         | 
| 101 101 | 
             
            copyparty/web/deps/mini-fa.css.gz,sha256=CTPrNaH8OTVmxajrGP88E2MkjadY9_81TBVnd9sw9Y8,572
         | 
| 102 102 | 
             
            copyparty/web/deps/mini-fa.woff,sha256=L9DNncV2TIyvsrspMbJouvnnt7F068Hbn7YZYvN76AU,2784
         | 
| 103 103 | 
             
            copyparty/web/deps/prism.css.gz,sha256=Z_A6rJ3MN5KWnjvXaV787aTW_5DT-xjFd0YZ7_W-Krk,1468
         | 
| @@ -105,10 +105,10 @@ copyparty/web/deps/prism.js.gz,sha256=DR0OAfTUb6PkzSPgxjnjj7jqlpvuCHdMDJKbsQP7s5 | |
| 105 105 | 
             
            copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cVCO0,1637
         | 
| 106 106 | 
             
            copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
         | 
| 107 107 | 
             
            copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
         | 
| 108 | 
            -
            copyparty/web/deps/sha512.hw.js.gz,sha256= | 
| 109 | 
            -
            copyparty-1.16. | 
| 110 | 
            -
            copyparty-1.16. | 
| 111 | 
            -
            copyparty-1.16. | 
| 112 | 
            -
            copyparty-1.16. | 
| 113 | 
            -
            copyparty-1.16. | 
| 114 | 
            -
            copyparty-1.16. | 
| 108 | 
            +
            copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
         | 
| 109 | 
            +
            copyparty-1.16.4.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
         | 
| 110 | 
            +
            copyparty-1.16.4.dist-info/METADATA,sha256=yo6iBPRZrSl4VBK6h32czwBccoFewYTRoSFVaOOYr5s,140656
         | 
| 111 | 
            +
            copyparty-1.16.4.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
         | 
| 112 | 
            +
            copyparty-1.16.4.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
         | 
| 113 | 
            +
            copyparty-1.16.4.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
         | 
| 114 | 
            +
            copyparty-1.16.4.dist-info/RECORD,,
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         |