Compare commits

...

6 Commits

Author SHA1 Message Date
ed
c15ecb6c8e ver 0.3.1 2020-05-07 00:20:22 +02:00
ed
ee96005026 sortable file list 2020-05-07 00:08:06 +02:00
ed
5b55d05a20 ux tweaks 2020-05-06 23:40:36 +02:00
ed
2f09c62c4e indicate version history in the browser 2020-05-06 23:10:30 +02:00
ed
1cc8b873d4 add missing urldecodes in js 2020-05-06 23:07:18 +02:00
ed
15d5859750 deal with illegal filenames on windows 2020-05-06 23:06:26 +02:00
16 changed files with 121 additions and 28 deletions

View File

@@ -104,10 +104,8 @@ in the `scripts` folder:
roughly sorted by priority
* sortable browser columns
* up2k handle filename too long
* up2k fails on empty files? alert then stuck
* unexpected filepath on dupe up2k
* drop onto folders
* look into android thumbnail cache file format
* support pillow-simd

View File

@@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (0, 3, 0)
VERSION = (0, 3, 1)
CODENAME = "docuparty"
BUILD_DT = (2020, 5, 6)
BUILD_DT = (2020, 5, 7)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -4,7 +4,6 @@ from __future__ import print_function, unicode_literals
import traceback
from .__init__ import PY2
from .util import Pebkac, Queue

View File

@@ -420,9 +420,11 @@ class HttpCli(object):
vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
self._assert_safe_rem(rem)
sanitized = sanitize_fn(new_dir)
if not nullwrite:
fdir = os.path.join(vfs.realpath, rem)
fn = os.path.join(fdir, sanitize_fn(new_dir))
fn = os.path.join(fdir, sanitized)
if not os.path.isdir(fsenc(fdir)):
raise Pebkac(500, "parent folder does not exist")
@@ -435,7 +437,7 @@ class HttpCli(object):
except:
raise Pebkac(500, "mkdir failed, check the logs")
vpath = "{}/{}".format(self.vpath, new_dir).lstrip("/")
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
html = self.conn.tpl_msg.render(
h2='<a href="/{}">go to /{}</a>'.format(
quotep(vpath), html_escape(vpath, quote=False)
@@ -457,9 +459,11 @@ class HttpCli(object):
if not new_file.endswith(".md"):
new_file += ".md"
sanitized = sanitize_fn(new_file)
if not nullwrite:
fdir = os.path.join(vfs.realpath, rem)
fn = os.path.join(fdir, sanitize_fn(new_file))
fn = os.path.join(fdir, sanitized)
if os.path.exists(fsenc(fn)):
raise Pebkac(500, "that file exists already")
@@ -467,7 +471,7 @@ class HttpCli(object):
with open(fsenc(fn), "wb") as f:
f.write(b"`GRUNNUR`\n")
vpath = "{}/{}".format(self.vpath, new_file).lstrip("/")
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
html = self.conn.tpl_msg.render(
h2='<a href="/{}?edit">go to /{}?edit</a>'.format(
quotep(vpath), html_escape(vpath, quote=False)
@@ -600,10 +604,10 @@ class HttpCli(object):
self.reply(response.encode("utf-8"))
return True
fn = os.path.join(vfs.realpath, rem)
fp = os.path.join(vfs.realpath, rem)
srv_lastmod = -1
try:
st = os.stat(fsenc(fn))
st = os.stat(fsenc(fp))
srv_lastmod = st.st_mtime
srv_lastmod3 = int(srv_lastmod * 1000)
except OSError as ex:
@@ -631,16 +635,22 @@ class HttpCli(object):
return True
# TODO another hack re: pending permissions rework
os.rename(fn, "{}.bak-{:.3f}.md".format(fn[:-3], srv_lastmod))
mdir, mfile = os.path.split(fp)
mfile2 = "{}.{:.3f}.md".format(mfile[:-3], srv_lastmod)
try:
os.mkdir(os.path.join(mdir, ".hist"))
except:
pass
os.rename(fp, os.path.join(mdir, ".hist", mfile2))
p_field, _, p_data = next(self.parser.gen)
if p_field != "body":
raise Pebkac(400, "expected body, got {}".format(p_field))
with open(fn, "wb") as f:
with open(fp, "wb") as f:
sz, sha512, _ = hashcopy(self.conn, p_data, f)
new_lastmod = os.stat(fsenc(fn)).st_mtime
new_lastmod = os.stat(fsenc(fp)).st_mtime
new_lastmod3 = int(new_lastmod * 1000)
sha512 = sha512[:56]
@@ -909,12 +919,30 @@ class HttpCli(object):
fsroot, vfs_ls, vfs_virt = vn.ls(rem, self.uname)
vfs_ls.extend(vfs_virt.keys())
# check for old versions of files,
hist = {} # [num-backups, most-recent, hist-path]
histdir = os.path.join(fsroot, ".hist")
ptn = re.compile(r"(.*)\.([0-9]+\.[0-9]{3})(\.[^\.]+)$")
try:
for hfn in os.listdir(histdir):
m = ptn.match(hfn)
if not m:
continue
fn = m.group(1) + m.group(3)
n, ts, _ = hist.get(fn, [0, 0, ""])
hist[fn] = [n + 1, max(ts, float(m.group(2))), hfn]
except:
pass
dirs = []
files = []
for fn in exclude_dotfiles(vfs_ls):
base = ""
href = fn
if self.absolute_urls and vpath:
href = "/" + vpath + "/" + fn
base = "/" + vpath + "/"
href = base + fn
if fn in vfs_virt:
fspath = vfs_virt[fn].realpath
@@ -931,6 +959,10 @@ class HttpCli(object):
if is_dir:
margin = "DIR"
href += "/"
elif fn in hist:
margin = '<a href="{}.hist/{}">#{}</a>'.format(
base, html_escape(hist[fn][2], quote=True), hist[fn][0]
)
else:
margin = "-"

View File

@@ -24,7 +24,7 @@ class TcpSrv(object):
ip = "127.0.0.1"
eps = {ip: "local only"}
if self.args.i != ip:
eps = self.detect_interfaces(self.args.i) or eps
eps = self.detect_interfaces(self.args.i) or {self.args.i: "external"}
for ip, desc in sorted(eps.items(), key=lambda x: x[1]):
self.log(

View File

@@ -13,7 +13,7 @@ import threading
from copy import deepcopy
from .__init__ import WINDOWS
from .util import Pebkac, Queue, fsenc
from .util import Pebkac, Queue, fsenc, sanitize_fn
class Up2k(object):
@@ -48,6 +48,7 @@ class Up2k(object):
self.r_hash = re.compile("^[0-9a-zA-Z_-]{43}$")
def handle_json(self, cj):
cj["name"] = sanitize_fn(cj["name"])
wark = self._get_wark(cj)
now = time.time()
with self.mutex:

View File

@@ -356,7 +356,30 @@ def undot(path):
def sanitize_fn(fn):
return fn.replace("\\", "/").split("/")[-1].strip()
fn = fn.replace("\\", "/").split("/")[-1]
if WINDOWS:
for bad, good in [
["<", ""],
[">", ""],
[":", ""],
['"', ""],
["/", ""],
["\\", ""],
["|", ""],
["?", ""],
["*", ""],
]:
fn = fn.replace(bad, good)
bad = ["con", "prn", "aux", "nul"]
for n in range(1, 10):
bad += "com{0} lpt{0}".format(n).split(" ")
if fn.lower() in bad:
fn = "_" + fn
return fn.strip()
def exclude_dotfiles(filepaths):

View File

@@ -68,7 +68,7 @@ a {
}
#files thead th:last-child {
background: #444;
border-radius: .7em 0 0 0;
border-radius: .7em .7em 0 0;
}
#files thead th:first-child {
background: #222;

View File

@@ -34,6 +34,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
}
document.body.style.fontSize = '0.8em';
document.body.style.padding = '0 1em 1em 1em';
hcroak(html.join('\n'));
};
@@ -78,6 +79,39 @@ function ev(e) {
}
function sortTable(table, col) {
var tb = table.tBodies[0], // use `<tbody>` to ignore `<thead>` and `<tfoot>` rows
th = table.tHead.rows[0].cells,
tr = Array.prototype.slice.call(tb.rows, 0),
i, reverse = th[col].className == 'sort1' ? -1 : 1;
for (var a = 0, thl = th.length; a < thl; a++)
th[a].className = '';
th[col].className = 'sort' + reverse;
var stype = th[col].getAttribute('sort');
tr = tr.sort(function (a, b) {
var v1 = a.cells[col].textContent.trim();
var v2 = b.cells[col].textContent.trim();
if (stype == 'int') {
v1 = parseInt(v1.replace(/,/g, ''));
v2 = parseInt(v2.replace(/,/g, ''));
return reverse * (v1 - v2);
}
return reverse * (v1.localeCompare(v2));
});
for (i = 0; i < tr.length; ++i) tb.appendChild(tr[i]);
}
function makeSortable(table) {
var th = table.tHead, i;
th && (th = th.rows[0]) && (th = th.cells);
if (th) i = th.length;
else return; // if no `<thead>` then do nothing
while (--i >= 0) (function (i) {
th[i].addEventListener('click', function () { sortTable(table, i) });
}(i));
}
makeSortable(o('files'));
// extract songs + add play column
var mp = (function () {
var tracks = [];

View File

@@ -239,8 +239,8 @@ blink {
height: 1.05em;
margin: -.2em .3em -.2em -.4em;
display: inline-block;
border: 1px solid rgba(0,0,0,0.3);
border-width: .05em .05em 0 0;
border: 1px solid rgba(0,0,0,0.2);
border-width: .2em .2em 0 0;
transform: rotate(45deg);
}
#mn a:hover {

View File

@@ -22,7 +22,9 @@ var dom_md = document.getElementById('mt');
if (a > 0)
loc.push(n[a]);
nav.push('<a href="/' + loc.join('/') + '">' + n[a] + '</a>');
var dec = decodeURIComponent(n[a]).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>');
}
dom_nav.innerHTML = nav.join('');
})();

View File

@@ -44,8 +44,8 @@ html, body {
height: 1.05em;
margin: -.2em .3em -.2em -.4em;
display: inline-block;
border: 1px solid rgba(0,0,0,0.3);
border-width: .05em .05em 0 0;
border: 1px solid rgba(0,0,0,0.2);
border-width: .2em .2em 0 0;
transform: rotate(45deg);
}
#mn a:hover {

View File

@@ -13,7 +13,9 @@ var dom_md = document.getElementById('mt');
if (a > 0)
loc.push(n[a]);
nav.push('<a href="/' + loc.join('/') + '">' + n[a] + '</a>');
var dec = decodeURIComponent(n[a]).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>');
}
dom_nav.innerHTML = nav.join('');
})();

View File

@@ -34,6 +34,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
}
document.body.style.fontSize = '0.8em';
document.body.style.padding = '0 1em 1em 1em';
hcroak(html.join('\n'));
};
@@ -211,7 +212,8 @@ function up2k_init(have_crypto) {
// handle user intent to use the basic uploader instead
o('u2nope').onclick = function (e) {
e.preventDefault();
un2k();
setmsg('');
goto('bup');
};
if (!String.prototype.format) {

View File

@@ -66,5 +66,5 @@
</table>
<p id="u2foot"></p>
<p>( if you don't need lastmod timestamps, resumable uploads or progress bars just use the <a href="#" id="u2nope" onclick="javascript:goto('bup');">basic uploader</a>)</p>
<p>( if you don't need lastmod timestamps, resumable uploads or progress bars just use the <a href="#" id="u2nope">basic uploader</a>)</p>
</div>

View File

@@ -101,7 +101,7 @@ grep -lE '\.full\.(js|css)' copyparty/web/* |
while IFS= read -r x; do sed -ri 's/\.full\.(js|css)/.\1/g' "$x"; done
[ $no_ogv ] &&
rm -rf copyparty/web/deps/{dynamicaudio,ogv}* copyparty/web/browser.js
rm -rf copyparty/web/deps/{dynamicaudio,ogv}*
echo creating tar
args=(--owner=1000 --group=1000)