From a009ff53f7b928e202d9c3378c7f10951e11d5f7 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 6 Sep 2021 00:23:35 +0200 Subject: [PATCH] show README.md in directory listings --- README.md | 23 +++++++++++++- copyparty/__main__.py | 1 + copyparty/httpcli.py | 11 +++++++ copyparty/web/browser.html | 3 +- copyparty/web/browser.js | 65 ++++++++++++++++++++++++++++++++++++-- copyparty/web/ui.css | 1 + 6 files changed, 100 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 346f82aa..1a3ddcd4 100644 --- a/README.md +++ b/README.md @@ -477,6 +477,10 @@ and there are *two* editors * click the bottom-left `π` to open a javascript prompt for debugging +* files named `.prologue.html` / `.epilogue.html` will be rendered before/after directory listings unless `--no-logues` + +* files named `README.md` / `readme.md` will be rendered after directory listings unless `--no-readme` (but `.epilogue.html` takes precedence) + ## searching @@ -757,6 +761,24 @@ below are some tweaks roughly ordered by usefulness: ...however it adds an overhead to internal communication so it might be a net loss, see if it works 4 u +# security + +some notes on hardening + +on public copyparty instances with anonymous upload enabled: + +* users can upload html/css/js which will evaluate for other visitors in a few ways, + * unless `--no-readme` is set: by uploading/modifying a file named `readme.md` + * if `move` access is granted AND none of `--no-logues`, `--no-dot-mv`, `--no-dot-ren` is set: by uploading some .html file and renaming it to `.epilogue.html` (uploading it directly is blocked) + + +## gotchas + +behavior that might be unexpected + +* users without read-access to a folder can still see the `.prologue.html` / `.epilogue.html` / `README.md` contents, for the purpose of showing a description on how to use the uploader for example + + # dependencies mandatory deps: @@ -887,7 +909,6 @@ in the `scripts` folder: roughly sorted by priority * hls framework for Someone Else to drop code into :^) -* readme.md as epilogue ## discarded ideas diff --git a/copyparty/__main__.py b/copyparty/__main__.py index b7f5c727..d14ecb7f 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -363,6 +363,7 @@ def run_argparse(argv, formatter): ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles") ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to make something a dotfile") ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings") + ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme.md into directory listings") ap2 = ap.add_argument_group('logging options') ap2.add_argument("-q", action="store_true", help="quiet") diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index d445fd0b..8ff0d726 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -1875,6 +1875,15 @@ class HttpCli(object): with open(fsenc(fn), "rb") as f: logues[n] = f.read().decode("utf-8") + readme = "" + if not self.args.no_readme and not logues[1]: + for fn in ["README.md", "readme.md"]: + fn = os.path.join(abspath, fn) + if bos.path.exists(fn): + with open(fsenc(fn), "rb") as f: + readme = f.read().decode("utf-8") + break + ls_ret = { "dirs": [], "files": [], @@ -1883,6 +1892,7 @@ class HttpCli(object): "acct": self.uname, "perms": perms, "logues": logues, + "readme": readme, } j2a = { "vdir": quotep(self.vpath), @@ -1901,6 +1911,7 @@ class HttpCli(object): "have_b_u": (self.can_write and self.uparam.get("b") == "u"), "url_suf": url_suf, "logues": logues, + "readme": readme, "title": html_escape(self.vpath, crlf=True), "srv_info": srv_info, } diff --git a/copyparty/web/browser.html b/copyparty/web/browser.html index 66b649b4..c2b9fbdd 100644 --- a/copyparty/web/browser.html +++ b/copyparty/web/browser.html @@ -133,7 +133,8 @@ have_mv = {{ have_mv|tojson }}, have_del = {{ have_del|tojson }}, have_unpost = {{ have_unpost|tojson }}, - have_zip = {{ have_zip|tojson }}; + have_zip = {{ have_zip|tojson }}, + readme = {{ readme|tojson }}; diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index df8d2a12..e9d25c90 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -140,9 +140,10 @@ ebi('op_cfg').innerHTML = ( '
\n' + ' ℹ️ tooltips\n' + ' ☀️ lightmode\n' + - ' dotfiles\n' + ' 田 the grid\n' + ' 🖼️ thumbs\n' + + ' dotfiles\n' + + ' 📜 readme\n' + '
\n' + '\n' + (have_zip ? ( @@ -2999,7 +3000,8 @@ var treectl = (function () { var treectl = { "hidden": true, "ls_cb": null, - "dir_cb": tree_scrollto + "dir_cb": tree_scrollto, + "ireadme": bcfg_get('ireadme', true) }, entreed = false, fixedpos = false, @@ -3320,6 +3322,12 @@ var treectl = (function () { ebi('pro').innerHTML = res.logues ? res.logues[0] || "" : ""; ebi('epi').innerHTML = res.logues ? res.logues[1] || "" : ""; + clmod(ebi('epi'), 'mdo'); + if (res.readme) + setTimeout(function () { + show_readme(res.readme); + }, 10); + document.title = '⇆🎉 ' + uricom_dec(document.location.pathname.slice(1, -1))[0]; filecols.set_style(); @@ -3372,6 +3380,12 @@ var treectl = (function () { treectl.goto(get_evpath()); } + function treadme(e) { + ev(e); + treectl.ireadme = !treectl.ireadme; + bcfg_set('ireadme', treectl.ireadme); + } + function dyntree(e) { ev(e); dyn = !dyn; @@ -3393,6 +3407,7 @@ var treectl = (function () { ebi('detree').onclick = treectl.detree; ebi('visdir').onclick = tree_scrollto; ebi('dotfiles').onclick = tdots; + ebi('ireadme').onclick = treadme; ebi('dyntree').onclick = dyntree; ebi('twig').onclick = scaletree; ebi('twobytwo').onclick = scaletree; @@ -3823,6 +3838,7 @@ var light; function freshen() { clmod(document.documentElement, "light", light); + clmod(document.documentElement, "dark", !light); pbar.drawbuf(); pbar.drawpos(); vbar.draw(); @@ -4036,6 +4052,51 @@ var msel = (function () { })(); +function show_readme(md, url, depth) { + if (!treectl.ireadme) + return; + + var div = ebi('epi'), + errmsg = 'cannot show README.md:\n\n', + now = window.location.href.replace(/\/?[?#].*/, ""); + + url = url || now; + if (url != now) + return; + + if (!window['marked']) { + if (depth) + return toast.warn(10, errmsg + 'failed to load marked.js') + + return import_js('/.cpr/deps/marked.js', function () { + show_readme(md, url, 1); + }); + } + + try { + clmod(div, 'mdo', 1); + div.innerHTML = marked(md, { + headerPrefix: 'md-', + breaks: true, + gfm: true + }); + var links = QSA('#epi a'); + for (var a = 0, aa = links.length; a < aa; a++) { + var href = links[a].getAttribute('href'); + if (!href.startsWith('#')) + continue; + + links[a].setAttribute('href', '#md-' + href.slice(1)); + } + } + catch (ex) { + toast.warn(10, errmsg + ex); + } +} +if (readme) + show_readme(readme); + + (function () { try { var tr = ebi('files').tBodies[0].rows; diff --git a/copyparty/web/ui.css b/copyparty/web/ui.css index eca18fd8..fab448b2 100644 --- a/copyparty/web/ui.css +++ b/copyparty/web/ui.css @@ -365,6 +365,7 @@ html.light #tt em { overflow-wrap: break-word; word-wrap: break-word; /*ie*/ } + html.light .mdo a, .mdo a { color: #fff; background: #39b;