From 7b4b38d98baa0a9609473688ef78e54ff40cc6bb Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 10 Jun 2019 19:47:01 +0000 Subject: [PATCH] fallback uploader works --- README.md | 7 ++++-- copyparty/httpcli.py | 40 +++++++++++++++++++++++++++------ copyparty/httpconn.py | 1 + copyparty/web/browser.css | 3 +-- copyparty/web/browser.html | 14 ++++++------ copyparty/web/msg.css | 31 ++++++++++++++++++++++++++ copyparty/web/msg.html | 45 ++++++++++++++++++++++++++++++++++++++ docs/notes.sh | 6 +++++ 8 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 copyparty/web/msg.css create mode 100644 copyparty/web/msg.html diff --git a/README.md b/README.md index 12c0dd9c..aa7c2956 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down * [x] sanic multipart parser * [x] load balancer (multiprocessing) -* [ ] upload (plain multipart, ie6 support) +* [x] upload (plain multipart, ie6 support) * [ ] upload (js, resumable, multithreaded) * [x] download * [x] browser @@ -25,6 +25,8 @@ turn your phone or raspi into a portable file server with resumable uploads/down * [x] volumes * [x] accounts +summary: it works + # dependencies @@ -64,9 +66,10 @@ pip install black bandit pylint flake8 # vscode tooling ``` -# TODO +# immediate todo roughly sorted by priority +* http error handling (conn.status or handler-retval) * last-modified header * support pillow-simd diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 5c47a866..b460d519 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -15,8 +15,10 @@ from .util import * # noqa # pylint: disable=unused-wildcard-import if not PY2: unicode = str from urllib.parse import unquote_plus + from urllib.parse import quote_plus else: from urllib import unquote_plus # pylint: disable=no-name-in-module + from urllib import quote_plus class HttpCli(object): @@ -151,7 +153,7 @@ class HttpCli(object): if self.vpath == "" and not self.uparam: nread = len(self.rvol) nwrite = len(self.wvol) - if nread + nwrite == 1: + if nread + nwrite == 1 or (self.rvol == self.wvol and nread == 1): if nread == 1: self.vpath = self.rvol[0] else: @@ -212,19 +214,25 @@ class HttpCli(object): pwd = u"x" # nosec h = ["Set-Cookie: cppwd={}; Path=/".format(pwd)] - html = u'

{}

ack

'.format(msg) - html += '' + html = self.conn.tpl_msg.render(h1=msg, h2='ack', redir="/") self.reply(html.encode("utf-8"), headers=h) def handle_plain_upload(self): nullwrite = self.args.nw + vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True) + + # rem is escaped at this point, + # this is just a sanity check to prevent any disasters + if rem.startswith("/") or rem.startswith("../") or "/../" in rem: + raise Exception("that was close") files = [] t0 = time.time() for nfile, (p_field, p_file, p_data) in enumerate(self.parser.gen): fn = os.devnull if not nullwrite: - fn = sanitize_fn(p_file) + fn = os.path.join(vfs.realpath, rem, sanitize_fn(p_file)) + # TODO broker which avoid this race # and provides a new filename if taken if os.path.exists(fn): @@ -253,7 +261,14 @@ class HttpCli(object): # truncated SHA-512 prevents length extension attacks; # using SHA-512/224, optionally SHA-512/256 = :64 - self.loud_reply(msg) + html = self.conn.tpl_msg.render( + h2='return to /{}'.format( + quote_plus(self.vpath, safe="/"), cgi.escape(self.vpath, quote=True) + ), + pre=msg, + ) + self.log(msg) + self.reply(html.encode("utf-8")) if not nullwrite: # TODO this is bad @@ -310,10 +325,15 @@ class HttpCli(object): vpnodes = [[u"/", u"/"]] for node in self.vpath.split("/"): vpath += u"/" + node - vpnodes.append([cgi.escape(vpath) + "/", cgi.escape(node)]) + vpnodes.append([quote_plus(vpath, safe="/") + "/", cgi.escape(node)]) vn, rem = self.auth.vfs.get(self.vpath, self.uname, True, False) abspath = vn.canonical(rem) + + if not os.path.exists(abspath): + print(abspath) + raise Pebkac("404 not found") + if not os.path.isdir(abspath): return self.tx_file(abspath) @@ -341,7 +361,13 @@ class HttpCli(object): dt = datetime.utcfromtimestamp(inf.st_mtime) dt = dt.strftime("%Y-%m-%d %H:%M:%S") - item = [margin, cgi.escape(href), cgi.escape(fn), sz, dt] + item = [ + margin, + quote_plus(href, safe="/"), + cgi.escape(fn, quote=True), + sz, + dt, + ] if is_dir: dirs.append(item) else: diff --git a/copyparty/httpconn.py b/copyparty/httpconn.py index f7dec680..ab87db70 100644 --- a/copyparty/httpconn.py +++ b/copyparty/httpconn.py @@ -31,6 +31,7 @@ class HttpConn(object): self.tpl_mounts = self.load_tpl("splash.html") self.tpl_browser = self.load_tpl("browser.html") + self.tpl_msg = self.load_tpl("msg.html") def load_tpl(self, fn): with open(self.respath(fn), "rb") as f: diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index 407cf270..140646d4 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -251,9 +251,8 @@ a.play.act { } #bup { padding: .5em .5em .5em .3em; - margin-bottom: 1em; background: #2d2d2d; - border-radius: 0 .5em .5em 0; + border-radius: 0 0 1em 0; border-right: .3em solid #3a3a3a; max-width: 40em; } diff --git a/copyparty/web/browser.html b/copyparty/web/browser.html index a8687092..8107d019 100644 --- a/copyparty/web/browser.html +++ b/copyparty/web/browser.html @@ -10,13 +10,6 @@ -

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

- {%- if can_upload %}
@@ -27,6 +20,13 @@
{%- endif %} +

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

+ diff --git a/copyparty/web/msg.css b/copyparty/web/msg.css new file mode 100644 index 00000000..47eb5218 --- /dev/null +++ b/copyparty/web/msg.css @@ -0,0 +1,31 @@ +html,body,tr,th,td,#files,a { + color: inherit; + background: none; + font-weight: inherit; + font-size: inherit; + padding: none; + border: none; +} +html { + color: #ccc; + background: #333; + font-family: sans-serif; + text-shadow: 1px 1px 0px #000; +} +html, body { + margin: 0; + padding: 0; +} +body { + padding-bottom: 5em; +} +#box { + padding: .5em 1em; + background: #2c2c2c; +} +pre { + font-family: monospace, monospace; +} +a { + color: #fc5; +} \ No newline at end of file diff --git a/copyparty/web/msg.html b/copyparty/web/msg.html new file mode 100644 index 00000000..3bb8edeb --- /dev/null +++ b/copyparty/web/msg.html @@ -0,0 +1,45 @@ + + + + + + copyparty + + + + + + +
+ + {%- if h1 %} +

{{ h1 }}

+ {%- endif %} + + {%- if h2 %} +

{{ h2 }}

+ {%- endif %} + + {%- if p %} +

{{ p }}

+ {%- endif %} + + {%- if pre %} +
{{ pre }}
+ {%- endif %} + + {%- if html %} + {{ html }} + {%- endif %} +
+ + {%- if redir %} + + {%- endif %} + + + \ No newline at end of file diff --git a/docs/notes.sh b/docs/notes.sh index 1f821af3..94e03b89 100644 --- a/docs/notes.sh +++ b/docs/notes.sh @@ -35,6 +35,12 @@ para() { for s in 1 2 3 4 5 6 7 8 12 16 24 32 48 64; do echo $s; for r in {1..4} avg() { awk 'function pr(ncsz) {if (nsmp>0) {printf "%3s %s\n", csz, sum/nsmp} csz=$1;sum=0;nsmp=0} {sub(/\r$/,"")} /^[0-9]+$/ {pr($1);next} / MiB/ {sub(/ MiB.*/,"");sub(/.* /,"");sum+=$1;nsmp++} END {pr(0)}' "$1"; } +## +## bad filenames + +echo hi > 'qwe,rty;asd fgh+jkl%zxc&vbn "rty'"'"'uio&asd fgh'.html + + ## ## vscode