From db194ab51953fbef89fedaae1fb9c5dd345cff33 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 10 Dec 2022 23:43:31 +0000 Subject: [PATCH] support location-based rproxy --- copyparty/__main__.py | 1 + copyparty/httpcli.py | 44 ++++++++++++++++++++++++++++++--- copyparty/httpsrv.py | 5 ---- copyparty/star.py | 4 +++ copyparty/svchub.py | 11 +++++++++ copyparty/szip.py | 4 +++ copyparty/tcpsrv.py | 4 --- copyparty/web/browser.css | 12 ++++----- copyparty/web/browser.html | 19 +++++++------- copyparty/web/browser.js | 27 +++++++++++--------- copyparty/web/browser2.html | 2 +- copyparty/web/md.html | 17 +++++++------ copyparty/web/mde.html | 19 +++++++------- copyparty/web/msg.html | 2 +- copyparty/web/splash.html | 29 +++++++++++----------- copyparty/web/svcs.html | 19 +++++++------- copyparty/web/ui.css | 2 +- copyparty/web/up2k.js | 6 ++--- copyparty/web/util.js | 2 ++ copyparty/web/w.hash.js | 2 +- scripts/deps-docker/mini-fa.css | 2 +- 21 files changed, 147 insertions(+), 86 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 7bfaa7a6..479d569d 100755 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -673,6 +673,7 @@ def run_argparse( ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)") ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 even if the NIC has routable IPs (breaks some mdns clients)") ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd), [\033[32m2\033[0m]=cloudflare, [\033[32m3\033[0m]=nginx, [\033[32m-1\033[0m]=closest proxy") + ap2.add_argument("--webroot", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated subdomain, provide the location here") if ANYWIN: ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances") ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes") diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 40da9a2c..9083277e 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -119,6 +119,8 @@ class HttpCli(object): # placeholders; assigned by run() self.keepalive = False self.is_https = False + self.is_proxied = False + self.is_vproxied = False self.in_hdr_recv = True self.headers: dict[str, str] = {} self.mode = " " @@ -191,6 +193,7 @@ class HttpCli(object): def j2s(self, name: str, **ka: Any) -> str: tpl = self.conn.hsrv.j2[name] + ka["r"] = self.args.SR if self.is_vproxied else "" ka["ts"] = self.conn.hsrv.cachebuster() ka["lang"] = self.args.lang ka["favico"] = self.args.favico @@ -276,6 +279,8 @@ class HttpCli(object): self.log(t.format(self.args.rproxy, zso), c=3) self.log_src = self.conn.set_rproxy(self.ip) + self.is_vproxied = bool(self.args.R) + self.is_proxied = True if self.is_banned(): return False @@ -320,6 +325,13 @@ class HttpCli(object): else: uparam[k.lower()] = "" + if self.is_vproxied: + if vpath.startswith(self.args.R): + vpath = vpath[len(self.args.R) + 1 :] + else: + t = "incorrect --webroot or webserver config; expected vpath starting with [{}] but got [{}]" + self.log(t.format(self.args.R, vpath), 1) + self.ouparam = {k: zs for k, zs in uparam.items()} if self.args.rsp_slp: @@ -604,10 +616,11 @@ class HttpCli(object): status: int = 200, use302: bool = False, ) -> bool: + vp = self.args.RS + vpath html = self.j2s( "msg", h2='{} /{}'.format( - quotep(vpath) + suf, flavor, html_escape(vpath, crlf=True) + suf + quotep(vp) + suf, flavor, html_escape(vp, crlf=True) + suf ), pre=msg, click=click, @@ -1375,7 +1388,7 @@ class HttpCli(object): url = "{}://{}/{}".format( "https" if self.is_https else "http", self.headers.get("host") or "{}:{}".format(*list(self.s.getsockname()[:2])), - vpath + vsuf, + self.args.RS + vpath + vsuf, ) return post_sz, sha_hex, sha_b64, remains, path, url @@ -1576,6 +1589,10 @@ class HttpCli(object): x = self.conn.hsrv.broker.ask("up2k.handle_json", body) ret = x.get() + if self.is_vproxied: + if "purl" in ret: + ret["purl"] = self.args.SR + ret["purl"] + ret = json.dumps(ret) self.log(ret) self.reply(ret.encode("utf-8"), mime="application/json") @@ -1634,6 +1651,10 @@ class HttpCli(object): if t not in order: order.append(t) + if self.is_vproxied: + for hit in hits: + hit["rp"] = self.args.RS + hit["rp"] + r = json.dumps({"hits": hits, "tag_order": order}).encode("utf-8") self.reply(r, mime="application/json") return True @@ -2032,7 +2053,7 @@ class HttpCli(object): )[: vfs.flags["fk"]] vpath = "{}/{}".format(upload_vpath, lfn).strip("/") - rel_url = quotep(vpath) + vsuf + rel_url = quotep(self.args.RS + vpath) + vsuf msg += 'sha512: {} // {} // {} bytes // {} {}\n'.format( sha_hex[:56], sha_b64, @@ -2537,6 +2558,7 @@ class HttpCli(object): boundary = "\roll\tide" targs = { + "r": self.args.SR if self.is_vproxied else "", "ts": self.conn.hsrv.cachebuster(), "svcname": self.args.doctitle, "html_head": self.html_head, @@ -2765,6 +2787,11 @@ class HttpCli(object): dst = dst[len(top) + 1 :] ret = self.gen_tree(top, dst) + if self.is_vproxied: + parents = self.args.R.split("/") + for parent in parents[::-1]: + ret = {"k{}".format(parent): ret, "a": []} + zs = json.dumps(ret) self.reply(zs.encode("utf-8"), mime="application/json") return True @@ -2875,6 +2902,11 @@ class HttpCli(object): break ret = ret[:2000] + + if self.is_vproxied: + for v in ret: + v["vp"] = self.args.SR + v["vp"] + jtxt = json.dumps(ret, indent=2, sort_keys=True).encode("utf-8", "replace") self.log("{} #{} {:.2f}sec".format(lm, len(ret), time.time() - t0)) self.reply(jtxt, mime="application/json") @@ -2889,6 +2921,8 @@ class HttpCli(object): if not req: req = [self.vpath] + elif self.is_vproxied: + req = [x[len(self.args.SR) :] for x in req] nlim = int(self.uparam.get("lim") or 0) lim = [nlim, nlim] if nlim else [] @@ -2900,6 +2934,10 @@ class HttpCli(object): def handle_mv(self) -> bool: # full path of new loc (incl filename) dst = self.uparam.get("move") + + if self.is_vproxied and dst and dst.startswith(self.args.SR): + dst = dst[len(self.args.RS) :] + if not dst: raise Pebkac(400, "need dst vpath") diff --git a/copyparty/httpsrv.py b/copyparty/httpsrv.py index 75a8b9af..9106b532 100644 --- a/copyparty/httpsrv.py +++ b/copyparty/httpsrv.py @@ -178,11 +178,6 @@ class HttpSrv(object): def listen(self, sck: socket.socket, nlisteners: int) -> None: if self.args.j != 1: # lost in the pickle; redefine - try: - sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - except: - pass - if not ANYWIN or self.args.reuseaddr: sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) diff --git a/copyparty/star.py b/copyparty/star.py index 1bb4618b..dc4fc120 100644 --- a/copyparty/star.py +++ b/copyparty/star.py @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import print_function, unicode_literals +import stat import tarfile from queue import Queue @@ -79,6 +80,9 @@ class StreamTar(StreamArc): src = f["ap"] fsi = f["st"] + if stat.S_ISDIR(fsi.st_mode): + return + inf = tarfile.TarInfo(name=name) inf.mode = fsi.st_mode inf.size = fsi.st_size diff --git a/copyparty/svchub.py b/copyparty/svchub.py index a031e7a3..b2663925 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -301,6 +301,17 @@ class SvcHub(object): vs = [x for x in vs if x] setattr(al, n, vs) + R = al.webroot + if "//" in R or ":" in R: + t = "found URL in --webroot; it should be just the location, for example /foo/bar" + raise Exception(t) + + R = R.strip("/") + if R: + al.R = R + al.SR = "/" + R + al.RS = R + "/" + return True def _setlimits(self) -> None: diff --git a/copyparty/szip.py b/copyparty/szip.py index 4207d682..86615cf7 100644 --- a/copyparty/szip.py +++ b/copyparty/szip.py @@ -3,6 +3,7 @@ from __future__ import print_function, unicode_literals import calendar import time +import stat import zlib from .bos import bos @@ -238,6 +239,9 @@ class StreamZip(StreamArc): src = f["ap"] st = f["st"] + if stat.S_ISDIR(st.st_mode): + return + sz = st.st_size ts = st.st_mtime diff --git a/copyparty/tcpsrv.py b/copyparty/tcpsrv.py index f813eb0b..122ee6a8 100644 --- a/copyparty/tcpsrv.py +++ b/copyparty/tcpsrv.py @@ -209,10 +209,6 @@ class TcpSrv(object): def _listen(self, ip: str, port: int) -> None: ipv = socket.AF_INET6 if ":" in ip else socket.AF_INET srv = socket.socket(ipv, socket.SOCK_STREAM) - try: - srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - except: - pass if not ANYWIN or self.args.reuseaddr: srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index 029ce581..b77395a2 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -1075,18 +1075,18 @@ html.y #widget.open { top: -.12em; } #wtico { - cursor: url(/.cpr/dd/4.png), pointer; + cursor: url(dd/4.png), pointer; animation: cursor 500ms; } #wtico:hover { animation: cursor 500ms infinite; } @keyframes cursor { - 0% {cursor: url(/.cpr/dd/2.png), pointer} - 30% {cursor: url(/.cpr/dd/3.png), pointer} - 50% {cursor: url(/.cpr/dd/4.png), pointer} - 75% {cursor: url(/.cpr/dd/5.png), pointer} - 85% {cursor: url(/.cpr/dd/4.png), pointer} + 0% {cursor: url(dd/2.png), pointer} + 30% {cursor: url(dd/3.png), pointer} + 50% {cursor: url(dd/4.png), pointer} + 75% {cursor: url(dd/5.png), pointer} + 85% {cursor: url(dd/4.png), pointer} } @keyframes spin { 100% {transform: rotate(360deg)} diff --git a/copyparty/web/browser.html b/copyparty/web/browser.html index b5662e45..7c2148c6 100644 --- a/copyparty/web/browser.html +++ b/copyparty/web/browser.html @@ -8,8 +8,8 @@ {{ html_head }} - - + + {%- if css %} {%- endif %} @@ -71,7 +71,7 @@

🌲 {%- for n in vpnodes %} - {{ n[1] }} + {{ n[1] }} {%- endfor %}

@@ -121,7 +121,7 @@
{{ logues[1] }}
-

control-panel

+

control-panel

π @@ -134,7 +134,8 @@
- - - - + + + + {%- if js %} {%- endif %} diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 7acb30ec..a7ca5351 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -3647,7 +3647,7 @@ var showfile = (function () { qsr('#prism_css'); var el = mknod('link', 'prism_css'); el.rel = 'stylesheet'; - el.href = '/.cpr/deps/prism' + (light ? '' : 'd') + '.css'; + el.href = SR + '/.cpr/deps/prism' + (light ? '' : 'd') + '.css'; document.head.appendChild(el); }; @@ -3775,7 +3775,7 @@ var showfile = (function () { if (!defer) fun(el.firstChild); else - import_js('/.cpr/deps/prism.js', function () { fun(); }); + import_js(SR + '/.cpr/deps/prism.js', function () { fun(); }); } } @@ -4181,10 +4181,10 @@ var thegrid = (function () { if (r.thumbs) { ihref += '?th=' + (have_webp ? 'w' : 'j'); if (href == "#") - ihref = '/.cpr/ico/⏏️'; + ihref = SR + '/.cpr/ico/⏏️'; } else if (isdir) { - ihref = '/.cpr/ico/folder'; + ihref = SR + '/.cpr/ico/folder'; } else { var ar = href.split('.'); @@ -4209,7 +4209,7 @@ var thegrid = (function () { else ext = "unk"; } - ihref = '/.cpr/ico/' + ext; + ihref = SR + '/.cpr/ico/' + ext; } ihref += (ihref.indexOf('?') > 0 ? '&' : '?') + 'cache=i'; @@ -4784,7 +4784,7 @@ document.onkeydown = function (e) { clearTimeout(search_timeout); var xhr = new XHR(); - xhr.open('POST', '/?srch', true); + xhr.open('POST', SR + '/?srch', true); xhr.setRequestHeader('Content-Type', 'text/plain'); xhr.onload = xhr.onerror = xhr_search_results; xhr.ts = Date.now(); @@ -5323,7 +5323,12 @@ var treectl = (function () { treegrow.call(this.previousSibling, e); return; } - r.reqls(this.getAttribute('href'), true); + var href = this.getAttribute('href'); + if (R && !href.startsWith(SR)) { + window.location = href; + return; + } + r.reqls(href, true); r.dir_cb = tree_scrollto; thegrid.setvis(true); } @@ -5547,7 +5552,7 @@ var treectl = (function () { qsr('#bbsw'); if (ls0 === null) { var xhr = new XHR(); - xhr.open('GET', '/?am_js', true); + xhr.open('GET', SR + '/?am_js', true); xhr.send(); r.ls_cb = showfile.addlinks; @@ -6552,7 +6557,7 @@ function show_md(md, name, div, url, depth) { if (depth) return toast.warn(10, errmsg + 'failed to load marked.js') - return import_js('/.cpr/deps/marked.js', function () { + return import_js(SR + '/.cpr/deps/marked.js', function () { show_md(md, name, div, url, 1); }); } @@ -6705,7 +6710,7 @@ var unpost = (function () { r.me = me; } - var q = '/?ups'; + var q = SR + '/?ups'; if (filt.value) q += '&filter=' + uricom_enc(filt.value, true); @@ -6771,7 +6776,7 @@ var unpost = (function () { var xhr = new XHR(); xhr.n = n; xhr.n2 = n2; - xhr.open('POST', '/?delete&lim=' + req.length, true); + xhr.open('POST', SR + '/?delete&lim=' + req.length, true); xhr.onload = xhr.onerror = unpost_delete_cb; xhr.send(JSON.stringify(req)); }; diff --git a/copyparty/web/browser2.html b/copyparty/web/browser2.html index 0da8d313..2d0b12b8 100644 --- a/copyparty/web/browser2.html +++ b/copyparty/web/browser2.html @@ -57,7 +57,7 @@
{{ logues[1] }}

{%- endif %} -

control-panel

+

control-panel

diff --git a/copyparty/web/md.html b/copyparty/web/md.html index 16d955ba..d9da972a 100644 --- a/copyparty/web/md.html +++ b/copyparty/web/md.html @@ -5,10 +5,10 @@ {{ html_head }} - - + + {%- if edit %} - + {%- endif %} @@ -128,7 +128,8 @@ write markdown (most html is 🙆 too) - - - + + + {%- if edit %} - + {%- endif %} diff --git a/copyparty/web/mde.html b/copyparty/web/mde.html index 47ce8424..bba78514 100644 --- a/copyparty/web/mde.html +++ b/copyparty/web/mde.html @@ -5,10 +5,10 @@ {{ html_head }} - - - - + + + +
@@ -26,7 +26,8 @@ π - - - - + + + + diff --git a/copyparty/web/msg.html b/copyparty/web/msg.html index a43e5e21..17ac26be 100644 --- a/copyparty/web/msg.html +++ b/copyparty/web/msg.html @@ -8,7 +8,7 @@ {{ html_head }} - + diff --git a/copyparty/web/splash.html b/copyparty/web/splash.html index b1b1c235..b8de295d 100644 --- a/copyparty/web/splash.html +++ b/copyparty/web/splash.html @@ -8,19 +8,19 @@ {{ html_head }} - - + +
- refresh - connect + refresh + connect {%- if this.uname == '*' %}

howdy stranger   (you're not logged in)

{%- else %} - logout + logout

welcome back, {{ this.uname }}

{%- endif %} @@ -53,8 +53,8 @@ {%- endif %} @@ -79,18 +79,18 @@

client config:

login for more:

    -
    + @@ -100,13 +100,14 @@ π - - + + diff --git a/copyparty/web/svcs.html b/copyparty/web/svcs.html index 70cc5ff4..a0399ba8 100644 --- a/copyparty/web/svcs.html +++ b/copyparty/web/svcs.html @@ -8,14 +8,14 @@ {{ html_head }} - - + +
    -

    browse files // control panel

    +

    browse files // control panel

    or choose your OS for cooler alternatives:

    Windows @@ -53,7 +53,7 @@

    note: if you are on LAN (or just dont have valid certificates), add --no-check-certificate to the mount command
    ---

    {% endif %} -

    if you want to use the native WebDAV client in windows instead (slow and buggy), first run webdav-cfg.bat to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:

    +

    if you want to use the native WebDAV client in windows instead (slow and buggy), first run webdav-cfg.bat to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:

                     net use w: http{{ s }}://{{ ep }}/{{ vp }}{% if accs %} k /user:{{ pw }}{% endif %}
                 
    @@ -144,7 +144,7 @@

    partyfuse

    - partyfuse.py -- fast, read-only, + partyfuse.py -- fast, read-only, needs winfsp doesn't need root

    @@ -155,7 +155,7 @@

    note: if you are on LAN (or just dont have valid certificates), add -td

    {% endif %}

    - you can use up2k.py to upload (sometimes faster than web-browsers) + you can use up2k.py to upload (sometimes faster than web-browsers)

    @@ -188,13 +188,14 @@ π - - + + diff --git a/copyparty/web/ui.css b/copyparty/web/ui.css index 4665655d..87f0c0cf 100644 --- a/copyparty/web/ui.css +++ b/copyparty/web/ui.css @@ -1,7 +1,7 @@ @font-face { font-family: 'scp'; font-display: swap; - src: local('Source Code Pro Regular'), local('SourceCodePro-Regular'), url(/.cpr/deps/scp.woff2) format('woff2'); + src: local('Source Code Pro Regular'), local('SourceCodePro-Regular'), url(deps/scp.woff2) format('woff2'); } html { touch-action: manipulation; diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index 542937e1..10b9cc46 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -779,7 +779,7 @@ function up2k_init(subtle) { setTimeout(function () { if (window.WebAssembly && !hws.length) - fetch('/.cpr/w.hash.js' + CB); + fetch(SR + '/.cpr/w.hash.js' + CB); }, 1000); function showmodal(msg) { @@ -809,7 +809,7 @@ function up2k_init(subtle) { m = L.u_https1 + ' ' + L.u_https2 + ' ' + L.u_https3; showmodal('

    loading ' + fn + '

    '); - import_js('/.cpr/deps/' + fn, unmodal); + import_js(SR + '/.cpr/deps/' + fn, unmodal); if (HTTPS) { // chrome<37 firefox<34 edge<12 opera<24 safari<7 @@ -1312,7 +1312,7 @@ function up2k_init(subtle) { if (window.WebAssembly && !hws.length) { for (var a = 0; a < Math.min(navigator.hardwareConcurrency || 4, 16); a++) - hws.push(new Worker('/.cpr/w.hash.js' + CB)); + hws.push(new Worker(SR + '/.cpr/w.hash.js' + CB)); console.log(hws.length + " hashers"); } diff --git a/copyparty/web/util.js b/copyparty/web/util.js index 772497da..ea22f802 100644 --- a/copyparty/web/util.js +++ b/copyparty/web/util.js @@ -9,6 +9,8 @@ if (!window.console || !console.log) var wah = '', L, tt, treectl, thegrid, up2k, asmCrypto, hashwasm, vbar, marked, CB = '?_=' + Date.now(), + R = SR.slice(1), + RS = R ? "/" + R : "", HALFMAX = 8192 * 8192 * 8192 * 8192, HTTPS = (window.location + '').indexOf('https:') === 0, TOUCH = 'ontouchstart' in window, diff --git a/copyparty/web/w.hash.js b/copyparty/web/w.hash.js index 6af20fce..98b8f01e 100644 --- a/copyparty/web/w.hash.js +++ b/copyparty/web/w.hash.js @@ -19,7 +19,7 @@ catch (ex) { } function load_fb() { subtle = null; - importScripts('/.cpr/deps/sha512.hw.js'); + importScripts('deps/sha512.hw.js'); } diff --git a/scripts/deps-docker/mini-fa.css b/scripts/deps-docker/mini-fa.css index fa2b8b29..d5c83ecb 100644 --- a/scripts/deps-docker/mini-fa.css +++ b/scripts/deps-docker/mini-fa.css @@ -9,7 +9,7 @@ font-family: 'fa'; font-style: normal; font-weight: 400; font-display: block; -src: url("/.cpr/deps/mini-fa.woff") format("woff"); +src: url("mini-fa.woff") format("woff"); } .fa,