mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
fallback uploader works
This commit is contained in:
parent
1ea9b26a89
commit
7b4b38d98b
|
@ -15,7 +15,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
||||||
|
|
||||||
* [x] sanic multipart parser
|
* [x] sanic multipart parser
|
||||||
* [x] load balancer (multiprocessing)
|
* [x] load balancer (multiprocessing)
|
||||||
* [ ] upload (plain multipart, ie6 support)
|
* [x] upload (plain multipart, ie6 support)
|
||||||
* [ ] upload (js, resumable, multithreaded)
|
* [ ] upload (js, resumable, multithreaded)
|
||||||
* [x] download
|
* [x] download
|
||||||
* [x] browser
|
* [x] browser
|
||||||
|
@ -25,6 +25,8 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
||||||
* [x] volumes
|
* [x] volumes
|
||||||
* [x] accounts
|
* [x] accounts
|
||||||
|
|
||||||
|
summary: it works
|
||||||
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
|
|
||||||
|
@ -64,9 +66,10 @@ pip install black bandit pylint flake8 # vscode tooling
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
# TODO
|
# immediate todo
|
||||||
|
|
||||||
roughly sorted by priority
|
roughly sorted by priority
|
||||||
|
|
||||||
|
* http error handling (conn.status or handler-retval)
|
||||||
* last-modified header
|
* last-modified header
|
||||||
* support pillow-simd
|
* support pillow-simd
|
||||||
|
|
|
@ -15,8 +15,10 @@ from .util import * # noqa # pylint: disable=unused-wildcard-import
|
||||||
if not PY2:
|
if not PY2:
|
||||||
unicode = str
|
unicode = str
|
||||||
from urllib.parse import unquote_plus
|
from urllib.parse import unquote_plus
|
||||||
|
from urllib.parse import quote_plus
|
||||||
else:
|
else:
|
||||||
from urllib import unquote_plus # pylint: disable=no-name-in-module
|
from urllib import unquote_plus # pylint: disable=no-name-in-module
|
||||||
|
from urllib import quote_plus
|
||||||
|
|
||||||
|
|
||||||
class HttpCli(object):
|
class HttpCli(object):
|
||||||
|
@ -151,7 +153,7 @@ class HttpCli(object):
|
||||||
if self.vpath == "" and not self.uparam:
|
if self.vpath == "" and not self.uparam:
|
||||||
nread = len(self.rvol)
|
nread = len(self.rvol)
|
||||||
nwrite = len(self.wvol)
|
nwrite = len(self.wvol)
|
||||||
if nread + nwrite == 1:
|
if nread + nwrite == 1 or (self.rvol == self.wvol and nread == 1):
|
||||||
if nread == 1:
|
if nread == 1:
|
||||||
self.vpath = self.rvol[0]
|
self.vpath = self.rvol[0]
|
||||||
else:
|
else:
|
||||||
|
@ -212,19 +214,25 @@ class HttpCli(object):
|
||||||
pwd = u"x" # nosec
|
pwd = u"x" # nosec
|
||||||
|
|
||||||
h = ["Set-Cookie: cppwd={}; Path=/".format(pwd)]
|
h = ["Set-Cookie: cppwd={}; Path=/".format(pwd)]
|
||||||
html = u'<h1>{}</h1><h2><a href="/">ack</a></h2>'.format(msg)
|
html = self.conn.tpl_msg.render(h1=msg, h2='<a href="/">ack</a>', redir="/")
|
||||||
html += '<script>setTimeout(function(){window.location.replace("/");},500);</script>'
|
|
||||||
self.reply(html.encode("utf-8"), headers=h)
|
self.reply(html.encode("utf-8"), headers=h)
|
||||||
|
|
||||||
def handle_plain_upload(self):
|
def handle_plain_upload(self):
|
||||||
nullwrite = self.args.nw
|
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 = []
|
files = []
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
for nfile, (p_field, p_file, p_data) in enumerate(self.parser.gen):
|
for nfile, (p_field, p_file, p_data) in enumerate(self.parser.gen):
|
||||||
fn = os.devnull
|
fn = os.devnull
|
||||||
if not nullwrite:
|
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
|
# TODO broker which avoid this race
|
||||||
# and provides a new filename if taken
|
# and provides a new filename if taken
|
||||||
if os.path.exists(fn):
|
if os.path.exists(fn):
|
||||||
|
@ -253,7 +261,14 @@ class HttpCli(object):
|
||||||
# truncated SHA-512 prevents length extension attacks;
|
# truncated SHA-512 prevents length extension attacks;
|
||||||
# using SHA-512/224, optionally SHA-512/256 = :64
|
# using SHA-512/224, optionally SHA-512/256 = :64
|
||||||
|
|
||||||
self.loud_reply(msg)
|
html = self.conn.tpl_msg.render(
|
||||||
|
h2='<a href="/{}">return to /{}</a>'.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:
|
if not nullwrite:
|
||||||
# TODO this is bad
|
# TODO this is bad
|
||||||
|
@ -310,10 +325,15 @@ class HttpCli(object):
|
||||||
vpnodes = [[u"/", u"/"]]
|
vpnodes = [[u"/", u"/"]]
|
||||||
for node in self.vpath.split("/"):
|
for node in self.vpath.split("/"):
|
||||||
vpath += u"/" + node
|
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)
|
vn, rem = self.auth.vfs.get(self.vpath, self.uname, True, False)
|
||||||
abspath = vn.canonical(rem)
|
abspath = vn.canonical(rem)
|
||||||
|
|
||||||
|
if not os.path.exists(abspath):
|
||||||
|
print(abspath)
|
||||||
|
raise Pebkac("404 not found")
|
||||||
|
|
||||||
if not os.path.isdir(abspath):
|
if not os.path.isdir(abspath):
|
||||||
return self.tx_file(abspath)
|
return self.tx_file(abspath)
|
||||||
|
|
||||||
|
@ -341,7 +361,13 @@ class HttpCli(object):
|
||||||
dt = datetime.utcfromtimestamp(inf.st_mtime)
|
dt = datetime.utcfromtimestamp(inf.st_mtime)
|
||||||
dt = dt.strftime("%Y-%m-%d %H:%M:%S")
|
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:
|
if is_dir:
|
||||||
dirs.append(item)
|
dirs.append(item)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -31,6 +31,7 @@ class HttpConn(object):
|
||||||
|
|
||||||
self.tpl_mounts = self.load_tpl("splash.html")
|
self.tpl_mounts = self.load_tpl("splash.html")
|
||||||
self.tpl_browser = self.load_tpl("browser.html")
|
self.tpl_browser = self.load_tpl("browser.html")
|
||||||
|
self.tpl_msg = self.load_tpl("msg.html")
|
||||||
|
|
||||||
def load_tpl(self, fn):
|
def load_tpl(self, fn):
|
||||||
with open(self.respath(fn), "rb") as f:
|
with open(self.respath(fn), "rb") as f:
|
||||||
|
|
|
@ -251,9 +251,8 @@ a.play.act {
|
||||||
}
|
}
|
||||||
#bup {
|
#bup {
|
||||||
padding: .5em .5em .5em .3em;
|
padding: .5em .5em .5em .3em;
|
||||||
margin-bottom: 1em;
|
|
||||||
background: #2d2d2d;
|
background: #2d2d2d;
|
||||||
border-radius: 0 .5em .5em 0;
|
border-radius: 0 0 1em 0;
|
||||||
border-right: .3em solid #3a3a3a;
|
border-right: .3em solid #3a3a3a;
|
||||||
max-width: 40em;
|
max-width: 40em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,6 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1 id="path">
|
|
||||||
{%- for n in vpnodes[:-1] %}
|
|
||||||
<a href="{{ n[0] }}">{{ n[1] }}</a>
|
|
||||||
{%- endfor %}
|
|
||||||
<span>{{ vpnodes[-1][1] }}</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{%- if can_upload %}
|
{%- if can_upload %}
|
||||||
<div id="bup">
|
<div id="bup">
|
||||||
<form method="post" enctype="multipart/form-data" accept-charset="utf-8">
|
<form method="post" enctype="multipart/form-data" accept-charset="utf-8">
|
||||||
|
@ -27,6 +20,13 @@
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
<h1 id="path">
|
||||||
|
{%- for n in vpnodes[:-1] %}
|
||||||
|
<a href="{{ n[0] }}">{{ n[1] }}</a>
|
||||||
|
{%- endfor %}
|
||||||
|
<span>{{ vpnodes[-1][1] }}</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
<table id="files">
|
<table id="files">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
31
copyparty/web/msg.css
Normal file
31
copyparty/web/msg.css
Normal file
|
@ -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;
|
||||||
|
}
|
45
copyparty/web/msg.html
Normal file
45
copyparty/web/msg.html
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>copyparty</title>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
|
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/msg.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="box">
|
||||||
|
|
||||||
|
{%- if h1 %}
|
||||||
|
<h1>{{ h1 }}</h1>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- if h2 %}
|
||||||
|
<h2>{{ h2 }}</h2>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- if p %}
|
||||||
|
<p>{{ p }}</p>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- if pre %}
|
||||||
|
<pre>{{ pre }}</pre>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- if html %}
|
||||||
|
{{ html }}
|
||||||
|
{%- endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{%- if redir %}
|
||||||
|
<script>
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.replace("{{ redir }}");
|
||||||
|
}, 500);
|
||||||
|
</script>
|
||||||
|
{%- endif %}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -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"; }
|
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 <qwe>"rty'"'"'uio&asd fgh'.html
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## vscode
|
## vscode
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue