Compare commits

...

44 Commits

Author SHA1 Message Date
ed
ed69d42005 v1.1.11 2022-01-14 22:25:06 +01:00
ed
0b47ee306b bump marked.js to 4.0.10 2022-01-14 20:42:23 +01:00
ed
e4e63619d4 linkable maintabs 2022-01-14 19:26:07 +01:00
ed
f32cca292a propagate sort-order to thegrid 2022-01-14 18:28:49 +01:00
ed
e87ea19ff1 return file URL in PUT response 2022-01-11 22:59:19 +01:00
ed
0214793740 fix garbage in markdown output 2022-01-05 18:57:05 +01:00
ed
fc9dd5d743 meadup changes 2022-01-03 01:16:27 +01:00
ed
9e6d5dd2b9 vbi: add onscreen qrcode 2021-12-28 20:57:11 +01:00
ed
bdad197e2c make it even worse 2021-12-27 00:04:38 +01:00
ed
7e139288a6 add very bad idea 2021-12-26 23:32:46 +01:00
ed
6e7935abaf repaint cut/paste buttons when permissions change 2021-12-24 00:50:52 +01:00
ed
3ba0cc20f1 v1.1.10 2021-12-17 00:05:17 +01:00
ed
dd28de1796 sendfile: handle eagain 2021-12-17 00:04:19 +01:00
ed
9eecc9e19a v1.1.9 2021-12-16 22:54:44 +01:00
ed
6530cb6b05 shut socket on tx error 2021-12-16 22:51:24 +01:00
ed
41ce613379 add multisearch 2021-12-12 20:11:07 +01:00
ed
5e2785caba more aggressively try ffmpeg when mutagen fails 2021-12-11 20:31:04 +01:00
ed
d7cc000976 v1.1.8 2021-12-10 02:44:48 +01:00
ed
50d8ff95ae good stuff 2021-12-10 02:21:56 +01:00
ed
b2de1459b6 quick backports to the alternative fuse client 2021-12-10 01:59:45 +01:00
ed
f0ffbea0b2 add breadcrumbs to the textfile tree 2021-12-10 00:44:47 +01:00
ed
199ccca0fe v1.1.7 2021-12-07 19:19:35 +01:00
ed
1d9b355743 fix search ui after b265e59 broke it 2021-12-07 19:12:36 +01:00
ed
f0437fbb07 cleanup the windowtitle a bit 2021-12-07 19:09:24 +01:00
ed
abc404a5b7 v1.1.6 2021-12-07 01:17:56 +01:00
ed
04b9e21330 update web-deps 2021-12-07 01:12:32 +01:00
ed
1044aa071b deal with consecutive dupes even without sqlite 2021-12-06 23:51:44 +01:00
ed
4c3192c8cc set window-title to listening ip 2021-12-06 23:08:04 +01:00
ed
689e77a025 option to set a custom servicename 2021-12-06 22:24:25 +01:00
ed
3bd89403d2 apply per-volume index config to ui 2021-12-06 22:04:24 +01:00
ed
b4800d9bcb option to disable onboot-scans per-volume 2021-12-06 20:54:13 +01:00
ed
05485e8539 md: smaller indent on outermost list 2021-12-06 20:17:12 +01:00
ed
0e03dc0868 and fix the markdown breadcrumbs too 2021-12-06 19:51:47 +01:00
ed
352b1ed10a generate correct links when trailing slash missing 2021-12-06 19:49:14 +01:00
ed
0db1244d04 also consider TMPDIR and friends 2021-12-06 09:47:39 +01:00
ed
ece08b8179 create ~/.config if /tmp is readonly 2021-12-06 02:02:44 +01:00
ed
b8945ae233 fix tests and readme 2021-12-04 18:52:14 +01:00
ed
dcaf7b0a20 v1.1.5 2021-12-04 03:33:57 +01:00
ed
f982cdc178 spa gridview 2021-12-04 03:31:12 +01:00
ed
b265e59834 spa filetab 2021-12-04 03:25:28 +01:00
ed
4a843a6624 unflicker navpane + add client state escape hatch 2021-12-04 02:46:00 +01:00
ed
241ef5b99d preserve mtimes when juggling symlinks 2021-12-04 01:58:04 +01:00
ed
f39f575a9c sort-order indicators 2021-12-03 23:53:41 +01:00
ed
1521307f1e use preferred sort on initial render, fixes #8 2021-12-03 02:07:08 +01:00
47 changed files with 1794 additions and 404 deletions

View File

@@ -16,6 +16,13 @@ turn your phone or raspi into a portable file server with resumable uploads/down
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [unpost](#unpost) // [thumbnails](#thumbnails) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [md-viewer](#markdown-viewer) // [ie4](#browser-support)
## get the app
<a href="https://f-droid.org/packages/me.ocv.partyup/"><img src="https://ocv.me/fdroid.png" alt="Get it on F-Droid" height="50" /> '' <img src="https://img.shields.io/f-droid/v/me.ocv.partyup.svg" alt="f-droid version info" /></a> '' <a href="https://github.com/9001/party-up"><img src="https://img.shields.io/github/release/9001/party-up.svg?logo=github" alt="github version info" /></a>
(basic upload client, nothing fancy yet)
## readme toc
* top
@@ -169,7 +176,6 @@ feature summary
* ☑ ...of audio (spectrograms) using FFmpeg
* ☑ cache eviction (max-age; maybe max-size eventually)
* ☑ SPA (browse while uploading)
* if you use the navpane to navigate, not folders in the file list
* server indexing
* ☑ [locate files by contents](#file-search)
* ☑ search by name/path/date/size
@@ -325,6 +331,7 @@ the browser has the following hotkeys (always qwerty)
* `V` toggle folders / textfiles in the navpane
* `G` toggle list / [grid view](#thumbnails)
* `T` toggle thumbnails / icons
* `ESC` close various things
* `ctrl-X` cut selected files/folders
* `ctrl-V` paste
* `F2` [rename](#batch-rename) selected file/folder
@@ -376,9 +383,13 @@ switching between breadcrumbs or navpane
click the `🌲` or pressing the `B` hotkey to toggle between breadcrumbs path (default), or a navpane (tree-browser sidebar thing)
* `[-]` and `[+]` (or hotkeys `A`/`D`) adjust the size
* `[v]` jumps to the currently open folder
* `[+]` and `[-]` (or hotkeys `A`/`D`) adjust the size
* `[🎯]` jumps to the currently open folder
* `[📃]` toggles between showing folders and textfiles
* `[📌]` shows the name of all parent folders in a docked panel
* `[a]` toggles automatic widening as you go deeper
* `[↵]` toggles wordwrap
* `[👀]` show full name on hover (if wordwrap is off)
## thumbnails
@@ -394,6 +405,7 @@ audio files are covnerted into spectrograms using FFmpeg unless you `--no-athumb
images with the following names (see `--th-covers`) become the thumbnail of the folder they're in: `folder.png`, `folder.jpg`, `cover.png`, `cover.jpg`
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
* indicated by the audio files having the ▶ icon instead of 💾
## zip downloads
@@ -444,7 +456,7 @@ see [up2k](#up2k) for details on how it works
![copyparty-upload-fs8](https://user-images.githubusercontent.com/241032/129635371-48fc54ca-fa91-48e3-9b1d-ba413e4b68cb.png)
**protip:** you can avoid scaring away users with [docs/minimal-up2k.html](docs/minimal-up2k.html) which makes it look [much simpler](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
**protip:** you can avoid scaring away users with [contrib/plugins/minimal-up2k.html](contrib/plugins/minimal-up2k.html) which makes it look [much simpler](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
**protip:** if you enable `favicon` in the `[⚙️] settings` tab (by typing something into the textbox), the icon in the browser tab will indicate upload progress
@@ -621,10 +633,12 @@ through arguments:
* `-e2ts` also scans for tags in all files that don't have tags yet
* `-e2tsr` also deletes all existing tags, doing a full reindex
the same arguments can be set as volume flags, in addition to `d2d` and `d2t` for disabling:
the same arguments can be set as volume flags, in addition to `d2d`, `d2ds`, `d2t`, `d2ts` for disabling:
* `-v ~/music::r:c,e2dsa,e2tsr` does a full reindex of everything on startup
* `-v ~/music::r:c,d2d` disables **all** indexing, even if any `-e2*` are on
* `-v ~/music::r:c,d2t` disables all `-e2t*` (tags), does not affect `-e2d*`
* `-v ~/music::r:c,d2ds` disables on-boot scans; only index new uploads
* `-v ~/music::r:c,d2ts` same except only affecting tags
note:
* the parser can finally handle `c,e2dsa,e2tsr` so you no longer have to `c,e2dsa:c,e2tsr`
@@ -850,7 +864,7 @@ copyparty returns a truncated sha512sum of your PUT/POST as base64; you can gene
b512(){ printf "$((sha512sum||shasum -a512)|sed -E 's/ .*//;s/(..)/\\x\1/g')"|base64|tr '+/' '-_'|head -c44;}
b512 <movie.mkv
you can provide passwords using cookie 'cppwd=hunter2', as a url query `?pw=hunter2`, or with basic-authentication (either as the username or password)
you can provide passwords using cookie `cppwd=hunter2`, as a url query `?pw=hunter2`, or with basic-authentication (either as the username or password)
# up2k

View File

@@ -5,6 +5,11 @@
* if something breaks just restart it
# [`partyjournal.py`](partyjournal.py)
produces a chronological list of all uploads by collecting info from up2k databases and the filesystem
* outputs a standalone html file
* optional mapping from IP-addresses to nicknames
# [`copyparty-fuse.py`](copyparty-fuse.py)
* mount a copyparty server as a local filesystem (read-only)

View File

@@ -11,14 +11,18 @@ import re
import os
import sys
import time
import json
import stat
import errno
import struct
import codecs
import platform
import threading
import http.client # py2: httplib
import urllib.parse
from datetime import datetime
from urllib.parse import quote_from_bytes as quote
from urllib.parse import unquote_to_bytes as unquote
try:
import fuse
@@ -38,7 +42,7 @@ except:
mount a copyparty server (local or remote) as a filesystem
usage:
python ./copyparty-fuseb.py -f -o allow_other,auto_unmount,nonempty,url=http://192.168.1.69:3923 /mnt/nas
python ./copyparty-fuseb.py -f -o allow_other,auto_unmount,nonempty,pw=wark,url=http://192.168.1.69:3923 /mnt/nas
dependencies:
sudo apk add fuse-dev python3-dev
@@ -50,6 +54,10 @@ fork of copyparty-fuse.py based on fuse-python which
"""
WINDOWS = sys.platform == "win32"
MACOS = platform.system() == "Darwin"
def threadless_log(msg):
print(msg + "\n", end="")
@@ -93,6 +101,41 @@ def html_dec(txt):
)
def register_wtf8():
def wtf8_enc(text):
return str(text).encode("utf-8", "surrogateescape"), len(text)
def wtf8_dec(binary):
return bytes(binary).decode("utf-8", "surrogateescape"), len(binary)
def wtf8_search(encoding_name):
return codecs.CodecInfo(wtf8_enc, wtf8_dec, name="wtf-8")
codecs.register(wtf8_search)
bad_good = {}
good_bad = {}
def enwin(txt):
return "".join([bad_good.get(x, x) for x in txt])
for bad, good in bad_good.items():
txt = txt.replace(bad, good)
return txt
def dewin(txt):
return "".join([good_bad.get(x, x) for x in txt])
for bad, good in bad_good.items():
txt = txt.replace(good, bad)
return txt
class CacheNode(object):
def __init__(self, tag, data):
self.tag = tag
@@ -115,8 +158,9 @@ class Stat(fuse.Stat):
class Gateway(object):
def __init__(self, base_url):
def __init__(self, base_url, pw):
self.base_url = base_url
self.pw = pw
ui = urllib.parse.urlparse(base_url)
self.web_root = ui.path.strip("/")
@@ -135,8 +179,7 @@ class Gateway(object):
self.conns = {}
def quotep(self, path):
# TODO: mojibake support
path = path.encode("utf-8", "ignore")
path = path.encode("wtf-8")
return quote(path, safe="/")
def getconn(self, tid=None):
@@ -159,20 +202,29 @@ class Gateway(object):
except:
pass
def sendreq(self, *args, **kwargs):
def sendreq(self, *args, **ka):
tid = get_tid()
if self.pw:
ck = "cppwd=" + self.pw
try:
ka["headers"]["Cookie"] = ck
except:
ka["headers"] = {"Cookie": ck}
try:
c = self.getconn(tid)
c.request(*list(args), **kwargs)
c.request(*list(args), **ka)
return c.getresponse()
except:
self.closeconn(tid)
c = self.getconn(tid)
c.request(*list(args), **kwargs)
c.request(*list(args), **ka)
return c.getresponse()
def listdir(self, path):
web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots"
if bad_good:
path = dewin(path)
web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots&ls"
r = self.sendreq("GET", web_path)
if r.status != 200:
self.closeconn()
@@ -182,9 +234,12 @@ class Gateway(object):
)
)
return self.parse_html(r)
return self.parse_jls(r)
def download_file_range(self, path, ofs1, ofs2):
if bad_good:
path = dewin(path)
web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?raw"
hdr_range = "bytes={}-{}".format(ofs1, ofs2 - 1)
log("downloading {}".format(hdr_range))
@@ -200,40 +255,27 @@ class Gateway(object):
return r.read()
def parse_html(self, datasrc):
ret = []
remainder = b""
ptn = re.compile(
r"^<tr><td>(-|DIR)</td><td><a [^>]+>([^<]+)</a></td><td>([^<]+)</td><td>([^<]+)</td></tr>$"
)
def parse_jls(self, datasrc):
rsp = b""
while True:
buf = remainder + datasrc.read(4096)
# print('[{}]'.format(buf.decode('utf-8')))
buf = datasrc.read(1024 * 32)
if not buf:
break
remainder = b""
endpos = buf.rfind(b"\n")
if endpos >= 0:
remainder = buf[endpos + 1 :]
buf = buf[:endpos]
rsp += buf
lines = buf.decode("utf-8").split("\n")
for line in lines:
m = ptn.match(line)
if not m:
# print(line)
continue
rsp = json.loads(rsp.decode("utf-8"))
ret = []
for statfun, nodes in [
[self.stat_dir, rsp["dirs"]],
[self.stat_file, rsp["files"]],
]:
for n in nodes:
fname = unquote(n["href"].split("?")[0]).rstrip(b"/").decode("wtf-8")
if bad_good:
fname = enwin(fname)
ftype, fname, fsize, fdate = m.groups()
fname = html_dec(fname)
ts = datetime.strptime(fdate, "%Y-%m-%d %H:%M:%S").timestamp()
sz = int(fsize)
if ftype == "-":
ret.append([fname, self.stat_file(ts, sz), 0])
else:
ret.append([fname, self.stat_dir(ts, sz), 0])
ret.append([fname, statfun(n["ts"], n["sz"]), 0])
return ret
@@ -262,6 +304,7 @@ class CPPF(Fuse):
Fuse.__init__(self, *args, **kwargs)
self.url = None
self.pw = None
self.dircache = []
self.dircache_mtx = threading.Lock()
@@ -271,7 +314,7 @@ class CPPF(Fuse):
def init2(self):
# TODO figure out how python-fuse wanted this to go
self.gw = Gateway(self.url) # .decode('utf-8'))
self.gw = Gateway(self.url, self.pw) # .decode('utf-8'))
info("up")
def clean_dircache(self):
@@ -536,6 +579,8 @@ class CPPF(Fuse):
def getattr(self, path):
log("getattr [{}]".format(path))
if WINDOWS:
path = enwin(path) # windows occasionally decodes f0xx to xx
path = path.strip("/")
try:
@@ -568,9 +613,25 @@ class CPPF(Fuse):
def main():
time.strptime("19970815", "%Y%m%d") # python#7980
register_wtf8()
if WINDOWS:
os.system("rem")
for ch in '<>:"\\|?*':
# microsoft maps illegal characters to f0xx
# (e000 to f8ff is basic-plane private-use)
bad_good[ch] = chr(ord(ch) + 0xF000)
for n in range(0, 0x100):
# map surrogateescape to another private-use area
bad_good[chr(n + 0xDC00)] = chr(n + 0xF100)
for k, v in bad_good.items():
good_bad[v] = k
server = CPPF()
server.parser.add_option(mountopt="url", metavar="BASE_URL", default=None)
server.parser.add_option(mountopt="pw", metavar="PASSWORD", default=None)
server.parse(values=server, errex=1)
if not server.url or not str(server.url).startswith("http"):
print("\nerror:")
@@ -578,7 +639,7 @@ def main():
print(" need argument: mount-path")
print("example:")
print(
" ./copyparty-fuseb.py -f -o allow_other,auto_unmount,nonempty,url=http://192.168.1.69:3923 /mnt/nas"
" ./copyparty-fuseb.py -f -o allow_other,auto_unmount,nonempty,pw=wark,url=http://192.168.1.69:3923 /mnt/nas"
)
sys.exit(1)

View File

@@ -0,0 +1,21 @@
// ==UserScript==
// @name twitter-unmute
// @namespace http://ocv.me/
// @version 0.1
// @description memes
// @author ed <irc.rizon.net>
// @match https://twitter.com/*
// @icon https://www.google.com/s2/favicons?domain=twitter.com
// @grant GM_addStyle
// ==/UserScript==
function grunnur() {
setInterval(function () {
//document.querySelector('div[aria-label="Unmute"]').click();
document.querySelector('video').muted = false;
}, 200);
}
var scr = document.createElement('script');
scr.textContent = '(' + grunnur.toString() + ')();';
(document.head || document.getElementsByTagName('head')[0]).appendChild(scr);

139
bin/mtag/very-bad-idea.py Executable file
View File

@@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""
use copyparty as a chromecast replacement:
* post a URL and it will open in the default browser
* upload a file and it will open in the default application
* the `key` command simulates keyboard input
* the `x` command executes other xdotool commands
* the `c` command executes arbitrary unix commands
the android app makes it a breeze to post pics and links:
https://github.com/9001/party-up/releases
(iOS devices have to rely on the web-UI)
goes without saying, but this is HELLA DANGEROUS,
GIVES RCE TO ANYONE WHO HAVE UPLOAD PERMISSIONS
example copyparty config to use this:
--urlform save,get -v.::w:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,bin/mtag/very-bad-idea.py
recommended deps:
apt install xdotool libnotify-bin
https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/meadup.js
and you probably want `twitter-unmute.user.js` from the res folder
-----------------------------------------------------------------------
-- startup script:
-----------------------------------------------------------------------
#!/bin/bash
set -e
# create qr code
ip=$(ip r | awk '/^default/{print$(NF-2)}'); echo http://$ip:3923/ | qrencode -o - -s 4 >/dev/shm/cpp-qr.png
/usr/bin/feh -x /dev/shm/cpp-qr.png &
# reposition and make topmost (with janky raspbian support)
( sleep 0.5
xdotool search --name cpp-qr.png windowactivate --sync windowmove 1780 0
wmctrl -r :ACTIVE: -b toggle,above || true
ps aux | grep -E 'sleep[ ]7\.27' ||
while true; do
w=$(xdotool getactivewindow)
xdotool search --name cpp-qr.png windowactivate windowraise windowfocus
xdotool windowactivate $w
xdotool windowfocus $w
sleep 7.27 || break
done &
xeyes # distraction window to prevent ^w from closing the qr-code
) &
# bail if copyparty is already running
ps aux | grep -E '[3] copy[p]arty' && exit 0
# dumb chrome wrapper to allow autoplay
cat >/usr/local/bin/chromium-browser <<'EOF'
#!/bin/bash
set -e
/usr/bin/chromium-browser --autoplay-policy=no-user-gesture-required "$@"
EOF
chmod 755 /usr/local/bin/chromium-browser
# start the server (note: replace `-v.::rw:` with `-v.::r:` to disallow retrieving uploaded stuff)
cd ~/Downloads; python3 copyparty-sfx.py --urlform save,get -v.::rw:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,very-bad-idea.py
"""
import os
import sys
import time
import subprocess as sp
from urllib.parse import unquote_to_bytes as unquote
def main():
fp = os.path.abspath(sys.argv[1])
with open(fp, "rb") as f:
txt = f.read(4096)
if txt.startswith(b"msg="):
open_post(txt)
else:
open_url(fp)
def open_post(txt):
txt = unquote(txt.replace(b"+", b" ")).decode("utf-8")[4:]
try:
k, v = txt.split(" ", 1)
except:
open_url(txt)
if k == "key":
sp.call(["xdotool", "key"] + v.split(" "))
elif k == "x":
sp.call(["xdotool"] + v.split(" "))
elif k == "c":
env = os.environ.copy()
while " " in v:
v1, v2 = v.split(" ", 1)
if "=" not in v1:
break
ek, ev = v1.split("=", 1)
env[ek] = ev
v = v2
sp.call(v.split(" "), env=env)
else:
open_url(txt)
def open_url(txt):
ext = txt.rsplit(".")[-1].lower()
sp.call(["notify-send", "--", txt])
if ext not in ["jpg", "jpeg", "png", "gif", "webp"]:
# sp.call(["wmctrl", "-c", ":ACTIVE:"]) # closes the active window correctly
sp.call(["killall", "vlc"])
sp.call(["killall", "mpv"])
sp.call(["killall", "feh"])
time.sleep(0.5)
for _ in range(20):
sp.call(["xdotool", "key", "ctrl+w"]) # closes the open tab correctly
# else:
# sp.call(["xdotool", "getactivewindow", "windowminimize"]) # minimizes the focused windo
# close any error messages:
sp.call(["xdotool", "search", "--name", "Error", "windowclose"])
# sp.call(["xdotool", "key", "ctrl+alt+d"]) # doesnt work at all
# sp.call(["xdotool", "keydown", "--delay", "100", "ctrl+alt+d"])
# sp.call(["xdotool", "keyup", "ctrl+alt+d"])
sp.call(["xdg-open", txt])
main()

177
bin/partyjournal.py Executable file
View File

@@ -0,0 +1,177 @@
#!/usr/bin/env python3
"""
partyjournal.py: chronological history of uploads
2021-12-31, v0.1, ed <irc.rizon.net>, MIT-Licensed
https://github.com/9001/copyparty/blob/hovudstraum/bin/partyjournal.py
produces a chronological list of all uploads,
by collecting info from up2k databases and the filesystem
specify subnet `192.168.1.*` with argument `.=192.168.1.`,
affecting all successive mappings
usage:
./partyjournal.py > partyjournal.html .=192.168.1. cart=125 steen=114 steen=131 sleepy=121 fscarlet=144 ed=101 ed=123
"""
import sys
import base64
import sqlite3
import argparse
from datetime import datetime
from urllib.parse import quote_from_bytes as quote
from urllib.parse import unquote_to_bytes as unquote
FS_ENCODING = sys.getfilesystemencoding()
class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
pass
##
## snibbed from copyparty
def s3dec(v):
if not v.startswith("//"):
return v
v = base64.urlsafe_b64decode(v.encode("ascii")[2:])
return v.decode(FS_ENCODING, "replace")
def quotep(txt):
btxt = txt.encode("utf-8", "replace")
quot1 = quote(btxt, safe=b"/")
quot1 = quot1.encode("ascii")
quot2 = quot1.replace(b" ", b"+")
return quot2.decode("utf-8", "replace")
def html_escape(s, quote=False, crlf=False):
"""html.escape but also newlines"""
s = s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
if quote:
s = s.replace('"', "&quot;").replace("'", "&#x27;")
if crlf:
s = s.replace("\r", "&#13;").replace("\n", "&#10;")
return s
## end snibs
##
def main():
ap = argparse.ArgumentParser(formatter_class=APF)
ap.add_argument("who", nargs="*")
ar = ap.parse_args()
imap = {}
subnet = ""
for v in ar.who:
if "=" not in v:
raise Exception("bad who: " + v)
k, v = v.split("=")
if k == ".":
subnet = v
continue
imap["{}{}".format(subnet, v)] = k
print(repr(imap), file=sys.stderr)
print(
"""\
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><style>
html, body {
color: #ccc;
background: #222;
font-family: sans-serif;
}
a {
color: #fc5;
}
td, th {
padding: .2em .5em;
border: 1px solid #999;
border-width: 0 1px 1px 0;
white-space: nowrap;
}
td:nth-child(1),
td:nth-child(2),
td:nth-child(3) {
font-family: monospace, monospace;
text-align: right;
}
tr:first-child {
position: sticky;
top: -1px;
}
th {
background: #222;
text-align: left;
}
</style></head><body><table><tr>
<th>wark</th>
<th>time</th>
<th>size</th>
<th>who</th>
<th>link</th>
</tr>"""
)
db_path = ".hist/up2k.db"
conn = sqlite3.connect(db_path)
q = r"pragma table_info(up)"
inf = conn.execute(q).fetchall()
cols = [x[1] for x in inf]
print("<!-- " + str(cols) + " -->")
# ['w', 'mt', 'sz', 'rd', 'fn', 'ip', 'at']
q = r"select * from up order by case when at > 0 then at else mt end"
for w, mt, sz, rd, fn, ip, at in conn.execute(q):
link = "/".join([s3dec(x) for x in [rd, fn] if x])
if fn.startswith("put-") and sz < 4096:
try:
with open(link, "rb") as f:
txt = f.read().decode("utf-8", "replace")
except:
continue
if txt.startswith("msg="):
txt = txt.encode("utf-8", "replace")
txt = unquote(txt.replace(b"+", b" "))
link = txt.decode("utf-8")[4:]
sz = "{:,}".format(sz)
v = [
w[:16],
datetime.utcfromtimestamp(at if at > 0 else mt).strftime(
"%Y-%m-%d %H:%M:%S"
),
sz,
imap.get(ip, ip),
]
row = "<tr>\n "
row += "\n ".join(["<td>{}</th>".format(x) for x in v])
row += '\n <td><a href="{}">{}</a></td>'.format(link, html_escape(link))
row += "\n</tr>"
print(row)
print("</table></body></html>")
if __name__ == "__main__":
main()

View File

@@ -1,3 +1,6 @@
### [`plugins/`](plugins/)
* example extensions
### [`copyparty.bat`](copyparty.bat)
* launches copyparty with no arguments (anon read+write within same folder)
* intended for windows machines with no python.exe in PATH

View File

@@ -13,7 +13,7 @@
upstream cpp {
server 127.0.0.1:3923;
keepalive 120;
keepalive 1;
}
server {
listen 443 ssl;

25
contrib/plugins/README.md Normal file
View File

@@ -0,0 +1,25 @@
# example resource files
can be provided to copyparty to tweak things
## example `.epilogue.html`
save one of these as `.epilogue.html` inside a folder to customize it:
* [`minimal-up2k.html`](minimal-up2k.html) will [simplify the upload ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
## example browser-css
point `--css-browser` to one of these by URL:
* [`browser.css`](browser.css) changes the background
* [`browser-icons.css`](browser-icons.css) adds filetype icons
## meadup.js
* turns copyparty into chromecast just more flexible (and probably way more buggy)
* usage: put the js somewhere in the webroot and `--js-browser /memes/meadup.js`

506
contrib/plugins/meadup.js Normal file
View File

@@ -0,0 +1,506 @@
// USAGE:
// place this file somewhere in the webroot and then
// python3 -m copyparty --js-browser /memes/meadup.js
//
// FEATURES:
// * adds an onscreen keyboard for operating a media center remotely,
// relies on https://github.com/9001/copyparty/blob/hovudstraum/bin/mtag/very-bad-idea.py
// * adds an interactive anime girl (if you can find the dependencies)
var hambagas = [
"https://www.youtube.com/watch?v=pFA3KGp4GuU"
];
// keybaord,
// onscreen keyboard by @steinuil
function initKeybaord(BASE_URL, HAMBAGA, consoleLog, consoleError) {
document.querySelector('.keybaord-container').innerHTML = `
<div class="keybaord-body">
<div class="keybaord-row keybaord-row-1">
<div class="keybaord-key" data-keybaord-key="Escape">
esc
</div>
<div class="keybaord-key" data-keybaord-key="F1">
F1
</div>
<div class="keybaord-key" data-keybaord-key="F2">
F2
</div>
<div class="keybaord-key" data-keybaord-key="F3">
F3
</div>
<div class="keybaord-key" data-keybaord-key="F4">
F4
</div>
<div class="keybaord-key" data-keybaord-key="F5">
F5
</div>
<div class="keybaord-key" data-keybaord-key="F6">
F6
</div>
<div class="keybaord-key" data-keybaord-key="F7">
F7
</div>
<div class="keybaord-key" data-keybaord-key="F8">
F8
</div>
<div class="keybaord-key" data-keybaord-key="F9">
F9
</div>
<div class="keybaord-key" data-keybaord-key="F10">
F10
</div>
<div class="keybaord-key" data-keybaord-key="F11">
F11
</div>
<div class="keybaord-key" data-keybaord-key="F12">
F12
</div>
<div class="keybaord-key" data-keybaord-key="Insert">
ins
</div>
<div class="keybaord-key" data-keybaord-key="Delete">
del
</div>
</div>
<div class="keybaord-row keybaord-row-2">
<div class="keybaord-key" data-keybaord-key="\`">
\`
</div>
<div class="keybaord-key" data-keybaord-key="1">
1
</div>
<div class="keybaord-key" data-keybaord-key="2">
2
</div>
<div class="keybaord-key" data-keybaord-key="3">
3
</div>
<div class="keybaord-key" data-keybaord-key="4">
4
</div>
<div class="keybaord-key" data-keybaord-key="5">
5
</div>
<div class="keybaord-key" data-keybaord-key="6">
6
</div>
<div class="keybaord-key" data-keybaord-key="7">
7
</div>
<div class="keybaord-key" data-keybaord-key="8">
8
</div>
<div class="keybaord-key" data-keybaord-key="9">
9
</div>
<div class="keybaord-key" data-keybaord-key="0">
0
</div>
<div class="keybaord-key" data-keybaord-key="-">
-
</div>
<div class="keybaord-key" data-keybaord-key="=">
=
</div>
<div class="keybaord-key keybaord-backspace" data-keybaord-key="BackSpace">
backspace
</div>
</div>
<div class="keybaord-row keybaord-row-3">
<div class="keybaord-key keybaord-tab" data-keybaord-key="Tab">
tab
</div>
<div class="keybaord-key" data-keybaord-key="q">
q
</div>
<div class="keybaord-key" data-keybaord-key="w">
w
</div>
<div class="keybaord-key" data-keybaord-key="e">
e
</div>
<div class="keybaord-key" data-keybaord-key="r">
r
</div>
<div class="keybaord-key" data-keybaord-key="t">
t
</div>
<div class="keybaord-key" data-keybaord-key="y">
y
</div>
<div class="keybaord-key" data-keybaord-key="u">
u
</div>
<div class="keybaord-key" data-keybaord-key="i">
i
</div>
<div class="keybaord-key" data-keybaord-key="o">
o
</div>
<div class="keybaord-key" data-keybaord-key="p">
p
</div>
<div class="keybaord-key" data-keybaord-key="[">
[
</div>
<div class="keybaord-key" data-keybaord-key="]">
]
</div>
<div class="keybaord-key keybaord-enter" data-keybaord-key="Return">
enter
</div>
</div>
<div class="keybaord-row keybaord-row-4">
<div class="keybaord-key keybaord-capslock" data-keybaord-key="HAMBAGA">
🍔
</div>
<div class="keybaord-key" data-keybaord-key="a">
a
</div>
<div class="keybaord-key" data-keybaord-key="s">
s
</div>
<div class="keybaord-key" data-keybaord-key="d">
d
</div>
<div class="keybaord-key" data-keybaord-key="f">
f
</div>
<div class="keybaord-key" data-keybaord-key="g">
g
</div>
<div class="keybaord-key" data-keybaord-key="h">
h
</div>
<div class="keybaord-key" data-keybaord-key="j">
j
</div>
<div class="keybaord-key" data-keybaord-key="k">
k
</div>
<div class="keybaord-key" data-keybaord-key="l">
l
</div>
<div class="keybaord-key" data-keybaord-key=";">
;
</div>
<div class="keybaord-key" data-keybaord-key="'">
'
</div>
<div class="keybaord-key keybaord-backslash" data-keybaord-key="\\">
\\
</div>
</div>
<div class="keybaord-row keybaord-row-5">
<div class="keybaord-key keybaord-lshift" data-keybaord-key="Shift_L">
shift
</div>
<div class="keybaord-key" data-keybaord-key="\\">
\\
</div>
<div class="keybaord-key" data-keybaord-key="z">
z
</div>
<div class="keybaord-key" data-keybaord-key="x">
x
</div>
<div class="keybaord-key" data-keybaord-key="c">
c
</div>
<div class="keybaord-key" data-keybaord-key="v">
v
</div>
<div class="keybaord-key" data-keybaord-key="b">
b
</div>
<div class="keybaord-key" data-keybaord-key="n">
n
</div>
<div class="keybaord-key" data-keybaord-key="m">
m
</div>
<div class="keybaord-key" data-keybaord-key=",">
,
</div>
<div class="keybaord-key" data-keybaord-key=".">
.
</div>
<div class="keybaord-key" data-keybaord-key="/">
/
</div>
<div class="keybaord-key keybaord-rshift" data-keybaord-key="Shift_R">
shift
</div>
</div>
<div class="keybaord-row keybaord-row-6">
<div class="keybaord-key keybaord-lctrl" data-keybaord-key="Control_L">
ctrl
</div>
<div class="keybaord-key keybaord-super" data-keybaord-key="Meta_L">
win
</div>
<div class="keybaord-key keybaord-alt" data-keybaord-key="Alt_L">
alt
</div>
<div class="keybaord-key keybaord-spacebar" data-keybaord-key="space">
space
</div>
<div class="keybaord-key keybaord-altgr" data-keybaord-key="Alt_R">
altgr
</div>
<div class="keybaord-key keybaord-what" data-keybaord-key="Menu">
menu
</div>
<div class="keybaord-key keybaord-rctrl" data-keybaord-key="Control_R">
ctrl
</div>
</div>
<div class="keybaord-row">
<div class="keybaord-key" data-keybaord-key="XF86AudioLowerVolume">
🔉
</div>
<div class="keybaord-key" data-keybaord-key="XF86AudioRaiseVolume">
🔊
</div>
<div class="keybaord-key" data-keybaord-key="Left">
⬅️
</div>
<div class="keybaord-key" data-keybaord-key="Down">
⬇️
</div>
<div class="keybaord-key" data-keybaord-key="Up">
⬆️
</div>
<div class="keybaord-key" data-keybaord-key="Right">
➡️
</div>
<div class="keybaord-key" data-keybaord-key="Page_Up">
PgUp
</div>
<div class="keybaord-key" data-keybaord-key="Page_Down">
PgDn
</div>
<div class="keybaord-key" data-keybaord-key="Home">
🏠
</div>
<div class="keybaord-key" data-keybaord-key="End">
End
</div>
</div>
<div>
`;
function arraySample(array) {
return array[Math.floor(Math.random() * array.length)];
}
function sendMessage(msg) {
return fetch(BASE_URL, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
},
body: "msg=" + encodeURIComponent(msg),
}).then(
(r) => r.text(), // so the response body shows up in network tab
(err) => consoleError(err)
);
}
const MODIFIER_ON_CLASS = "keybaord-modifier-on";
const KEY_DATASET = "data-keybaord-key";
const KEY_CLASS = "keybaord-key";
const modifiers = new Set()
function toggleModifier(button, key) {
button.classList.toggle(MODIFIER_ON_CLASS);
if (modifiers.has(key)) {
modifiers.delete(key);
} else {
modifiers.add(key);
}
}
function popModifiers() {
let modifierString = "";
modifiers.forEach((mod) => {
document.querySelector("[" + KEY_DATASET + "='" + mod + "']")
.classList.remove(MODIFIER_ON_CLASS);
modifierString += mod + "+";
});
modifiers.clear();
return modifierString;
}
Array.from(document.querySelectorAll("." + KEY_CLASS)).forEach((button) => {
const key = button.dataset.keybaordKey;
button.addEventListener("click", (ev) => {
switch (key) {
case "HAMBAGA":
sendMessage(arraySample(HAMBAGA));
break;
case "Shift_L":
case "Shift_R":
case "Control_L":
case "Control_R":
case "Meta_L":
case "Alt_L":
case "Alt_R":
toggleModifier(button, key);
break;
default: {
const keyWithModifiers = popModifiers() + key;
consoleLog(keyWithModifiers);
sendMessage("key " + keyWithModifiers)
.then(() => consoleLog(keyWithModifiers + " OK"));
}
}
});
});
}
// keybaord integration
(function () {
var o = mknod('div');
clmod(o, 'keybaord-container', 1);
ebi('op_msg').appendChild(o);
o = mknod('style');
o.innerHTML = `
.keybaord-body {
display: flex;
flex-flow: column nowrap;
margin: .6em 0;
}
.keybaord-row {
display: flex;
}
.keybaord-key {
border: 1px solid rgba(128,128,128,0.2);
width: 41px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
}
.keybaord-key:active {
background-color: lightgrey;
}
.keybaord-key.keybaord-modifier-on {
background-color: lightblue;
}
.keybaord-key.keybaord-backspace {
width: 82px;
}
.keybaord-key.keybaord-tab {
width: 55px;
}
.keybaord-key.keybaord-enter {
width: 69px;
}
.keybaord-key.keybaord-capslock {
width: 80px;
}
.keybaord-key.keybaord-backslash {
width: 88px;
}
.keybaord-key.keybaord-lshift {
width: 65px;
}
.keybaord-key.keybaord-rshift {
width: 103px;
}
.keybaord-key.keybaord-lctrl {
width: 55px;
}
.keybaord-key.keybaord-super {
width: 55px;
}
.keybaord-key.keybaord-alt {
width: 55px;
}
.keybaord-key.keybaord-altgr {
width: 55px;
}
.keybaord-key.keybaord-what {
width: 55px;
}
.keybaord-key.keybaord-rctrl {
width: 55px;
}
.keybaord-key.keybaord-spacebar {
width: 302px;
}
`;
document.head.appendChild(o);
initKeybaord('/', hambagas,
(msg) => { toast.inf(2, msg.toString()) },
(msg) => { toast.err(30, msg.toString()) });
})();
// live2d (dumb pointless meme)
// dependencies for this part are not tracked in git
// so delete this section if you wanna use this file
// (or supply your own l2d model and js)
(function () {
var o = mknod('link');
o.setAttribute('rel', 'stylesheet');
o.setAttribute('href', "/bad-memes/pio.css");
document.head.appendChild(o);
o = mknod('style');
o.innerHTML = '.pio-container{text-shadow:none;z-index:1}';
document.head.appendChild(o);
o = mknod('div');
clmod(o, 'pio-container', 1);
o.innerHTML = '<div class="pio-action"></div><canvas id="pio" width="280" height="500"></canvas>';
document.body.appendChild(o);
var remaining = 3;
for (var a of ['pio', 'l2d', 'fireworks']) {
import_js(`/bad-memes/${a}.js`, function () {
if (remaining --> 1)
return;
o = mknod('script');
o.innerHTML = 'var pio = new Paul_Pio({"selector":[],"mode":"fixed","hidden":false,"content":{"close":"ok bye"},"model":["/bad-memes/sagiri/model.json"]});';
document.body.appendChild(o);
});
}
})();

View File

@@ -25,26 +25,34 @@ ANYWIN = WINDOWS or sys.platform in ["msys"]
MACOS = platform.system() == "Darwin"
def get_unix_home():
try:
v = os.environ["XDG_CONFIG_HOME"]
if not v:
raise Exception()
ret = os.path.normpath(v)
os.listdir(ret)
return ret
except:
pass
def get_unixdir():
paths = [
(os.environ.get, "XDG_CONFIG_HOME"),
(os.path.expanduser, "~/.config"),
(os.environ.get, "TMPDIR"),
(os.environ.get, "TEMP"),
(os.environ.get, "TMP"),
(unicode, "/tmp"),
]
for chk in [os.listdir, os.mkdir]:
for pf, pa in paths:
try:
p = pf(pa)
# print(chk.__name__, p, pa)
if not p or p.startswith("~"):
continue
try:
v = os.path.expanduser("~/.config")
if v.startswith("~"):
raise Exception()
ret = os.path.normpath(v)
os.listdir(ret)
return ret
except:
return "/tmp"
p = os.path.normpath(p)
chk(p)
p = os.path.join(p, "copyparty")
if not os.path.isdir(p):
os.mkdir(p)
return p
except:
pass
raise Exception("could not find a writable path for config")
class EnvParams(object):
@@ -59,7 +67,7 @@ class EnvParams(object):
elif sys.platform == "darwin":
self.cfg = os.path.expanduser("~/Library/Preferences/copyparty")
else:
self.cfg = get_unix_home() + "/copyparty"
self.cfg = get_unixdir()
self.cfg = self.cfg.replace("\\", "/")
try:

View File

@@ -350,6 +350,8 @@ def run_argparse(argv, formatter):
\033[0mdatabase, general:
\033[36me2d\033[35m sets -e2d (all -e2* args can be set using ce2* volflags)
\033[36md2ts\033[35m disables metadata collection for existing files
\033[36md2ds\033[35m disables onboot indexing, overrides -e2ds*
\033[36md2t\033[35m disables metadata collection, overrides -e2t*
\033[36md2d\033[35m disables all database stuff, overrides -e2*
\033[36mnohash=\\.iso$\033[35m skips hashing file contents if path matches *.iso
@@ -416,6 +418,7 @@ def run_argparse(argv, formatter):
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins")
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-forms; examples: [stash], [save,get]")
ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="window title, for example '$ip-10.1.2.' or '$ip-'")
ap2 = ap.add_argument_group('upload options')
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads")
@@ -529,6 +532,7 @@ def run_argparse(argv, formatter):
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty", help="title / service-name to show in html documents")
ap2 = ap.add_argument_group('debug options')
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
@@ -605,6 +609,9 @@ def main(argv=None):
except:
print("\nfailed to disable quick-edit-mode:\n" + min_ex() + "\n")
if not VT100:
al.wintitle = ""
nstrs = []
anymod = False
for ostr in al.v or []:

View File

@@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (1, 1, 4)
VERSION = (1, 1, 11)
CODENAME = "opus"
BUILD_DT = (2021, 11, 28)
BUILD_DT = (2022, 1, 14)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -926,6 +926,14 @@ class AuthSrv(object):
vol.flags["d2t"] = True
vol.flags = {k: v for k, v in vol.flags.items() if not k.startswith(rm)}
# d2ds drops all onboot scans for a volume
for grp, rm in [["d2ds", "e2ds"], ["d2ts", "e2ts"]]:
if not vol.flags.get(grp, False):
continue
vol.flags["d2ts"] = True
vol.flags = {k: v for k, v in vol.flags.items() if not k.startswith(rm)}
# mt* needs e2t so drop those too
for grp, rm in [["e2t", "mt"]]:
if vol.flags.get(grp, False):

View File

@@ -2,7 +2,7 @@
from __future__ import print_function, unicode_literals
import os
from ..util import fsenc, fsdec
from ..util import fsenc, fsdec, SYMTIME
from . import path
@@ -55,5 +55,8 @@ def unlink(p):
return os.unlink(fsenc(p))
def utime(p, times=None):
return os.utime(fsenc(p), times)
def utime(p, times=None, follow_symlinks=True):
if SYMTIME:
return os.utime(fsenc(p), times, follow_symlinks=follow_symlinks)
else:
return os.utime(fsenc(p), times)

View File

@@ -2,7 +2,7 @@
from __future__ import print_function, unicode_literals
import os
from ..util import fsenc, fsdec
from ..util import fsenc, fsdec, SYMTIME
def abspath(p):
@@ -13,8 +13,11 @@ def exists(p):
return os.path.exists(fsenc(p))
def getmtime(p):
return os.path.getmtime(fsenc(p))
def getmtime(p, follow_symlinks=True):
if not follow_symlinks and SYMTIME:
return os.lstat(fsenc(p)).st_mtime
else:
return os.path.getmtime(fsenc(p))
def getsize(p):

View File

@@ -60,6 +60,7 @@ class HttpCli(object):
self.bufsz = 1024 * 32
self.hint = None
self.trailing_slash = True
self.out_headerlist = []
self.out_headers = {
"Access-Control-Allow-Origin": "*",
"Cache-Control": "no-store; max-age=0",
@@ -91,6 +92,7 @@ class HttpCli(object):
tpl = self.conn.hsrv.j2[name]
if ka:
ka["ts"] = self.conn.hsrv.cachebuster()
ka["svcname"] = self.args.doctitle
return tpl.render(**ka)
return tpl
@@ -226,7 +228,7 @@ class HttpCli(object):
self.gvol = self.asrv.vfs.aget[self.uname]
if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
self.out_headers["Set-Cookie"] = self.get_pwd_cookie(pwd)[0]
self.out_headerlist.append(("Set-Cookie", self.get_pwd_cookie(pwd)[0]))
self.ua = self.headers.get("user-agent", "")
self.is_rclone = self.ua.startswith("rclone/")
@@ -285,7 +287,7 @@ class HttpCli(object):
self.out_headers["Cache-Control"] = "max-age=" + n
def k304(self):
k304 = self.cookies.get("k304", "")
k304 = self.cookies.get("k304")
return k304 == "y" or ("; Trident/" in self.ua and not k304)
def send_headers(self, length, status=200, mime=None, headers=None):
@@ -310,7 +312,7 @@ class HttpCli(object):
self.out_headers["Content-Type"] = mime
for k, v in self.out_headers.items():
for k, v in list(self.out_headers.items()) + self.out_headerlist:
response.append("{}: {}".format(k, v))
try:
@@ -439,6 +441,12 @@ class HttpCli(object):
if "k304" in self.uparam:
return self.set_k304()
if "am_js" in self.uparam:
return self.set_am_js()
if "reset" in self.uparam:
return self.set_cfg_reset()
if "h" in self.uparam:
return self.tx_mounts()
@@ -516,7 +524,7 @@ class HttpCli(object):
return self.handle_stash()
if "save" in opt:
post_sz, _, _, _, path = self.dump_to_file()
post_sz, _, _, _, path, _ = self.dump_to_file()
self.log("urlform: {} bytes, {}".format(post_sz, path))
elif "print" in opt:
reader, _ = self.get_body_reader()
@@ -643,26 +651,45 @@ class HttpCli(object):
bos.unlink(path)
raise
if not self.args.nw:
vfs, vrem = vfs.get_dbv(rem)
self.conn.hsrv.broker.put(
False,
"up2k.hash_file",
vfs.realpath,
vfs.flags,
vrem,
fn,
self.ip,
time.time(),
)
if self.args.nw:
return post_sz, sha_hex, sha_b64, remains, path, ""
return post_sz, sha_hex, sha_b64, remains, path
vfs, rem = vfs.get_dbv(rem)
self.conn.hsrv.broker.put(
False,
"up2k.hash_file",
vfs.realpath,
vfs.flags,
rem,
fn,
self.ip,
time.time(),
)
if self.can_read and "fk" in vfs.flags:
vsuf = "?k=" + gen_filekey(
self.args.fk_salt,
path,
post_sz,
0 if ANYWIN else bos.stat(path).st_ino,
)[: vfs.flags["fk"]]
vpath = "/".join([x for x in [vfs.vpath, rem, fn] if x])
vpath = quotep(vpath)
url = "{}://{}/{}".format(
"https" if self.is_https else "http",
self.headers.get("host") or "{}:{}".format(*list(self.s.getsockname())),
vpath + vsuf,
)
return post_sz, sha_hex, sha_b64, remains, path, url
def handle_stash(self):
post_sz, sha_hex, sha_b64, remains, path = self.dump_to_file()
post_sz, sha_hex, sha_b64, remains, path, url = self.dump_to_file()
spd = self._spd(post_sz)
self.log("{} wrote {}/{} bytes to {}".format(spd, post_sz, remains, path))
m = "{}\n{}\n{}\n".format(post_sz, sha_b64, sha_hex[:56])
m = "{}\n{}\n{}\n{}\n".format(post_sz, sha_b64, sha_hex[:56], url)
self.reply(m.encode("utf-8"))
return True
@@ -1162,11 +1189,12 @@ class HttpCli(object):
)[: vfs.flags["fk"]]
vpath = "{}/{}".format(upload_vpath, lfn).strip("/")
rel_url = quotep(vpath) + vsuf
msg += 'sha512: {} // {} // {} bytes // <a href="/{}">{}</a> {}\n'.format(
sha_hex[:56],
sha_b64,
sz,
quotep(vpath) + vsuf,
rel_url,
html_escape(ofn, crlf=True),
vsuf,
)
@@ -1175,15 +1203,16 @@ class HttpCli(object):
jpart = {
"url": "{}://{}/{}".format(
"https" if self.is_https else "http",
self.headers.get("host", "copyparty"),
vpath + vsuf,
self.headers.get("host")
or "{}:{}".format(*list(self.s.getsockname())),
rel_url,
),
"sha512": sha_hex[:56],
"sha_b64": sha_b64,
"sz": sz,
"fn": lfn,
"fn_orig": ofn,
"path": vpath + vsuf,
"path": rel_url,
}
jmsg["files"].append(jpart)
@@ -1359,6 +1388,9 @@ class HttpCli(object):
try:
fs_path = req_path + ext
st = bos.stat(fs_path)
if stat.S_ISDIR(st.st_mode):
continue
file_ts = max(file_ts, st.st_mtime)
editions[ext or "plain"] = [fs_path, st.st_size]
except:
@@ -1504,11 +1536,12 @@ class HttpCli(object):
with open_func(*open_args) as f:
sendfun = sendfile_kern if use_sendfile else sendfile_py
remains = sendfun(
lower, upper, f, self.s, self.args.s_wr_sz, self.args.s_wr_slp
self.log, lower, upper, f, self.s, self.args.s_wr_sz, self.args.s_wr_slp
)
if remains > 0:
logmsg += " \033[31m" + unicode(upper - remains) + "\033[0m"
self.keepalive = False
spd = self._spd((upper - lower) - remains)
if self.do_log:
@@ -1717,7 +1750,19 @@ class HttpCli(object):
def set_k304(self):
ck = gencookie("k304", self.uparam["k304"], 60 * 60 * 24 * 365)
self.out_headers["Set-Cookie"] = ck
self.out_headerlist.append(("Set-Cookie", ck))
self.redirect("", "?h#cc")
def set_am_js(self):
v = "n" if self.uparam["am_js"] == "n" else "y"
ck = gencookie("js", v, 60 * 60 * 24 * 365)
self.out_headerlist.append(("Set-Cookie", ck))
self.reply(b"promoted\n")
def set_cfg_reset(self):
for k in ("k304", "js", "cppwd"):
self.out_headerlist.append(("Set-Cookie", gencookie(k, "x", None)))
self.redirect("", "?h#cc")
def tx_404(self, is_403=False):
@@ -1939,6 +1984,13 @@ class HttpCli(object):
fmt = "{{}} {{:{},}} {{}}"
nfmt = "{:,}"
for x in dirs:
n = x["name"] + "/"
if arg == "v":
n = "\033[94m" + n
x["name"] = n
fmt = fmt.format(len(nfmt.format(biggest)))
ret = [
"# {}: {}".format(x, ls[x])
@@ -2077,6 +2129,7 @@ class HttpCli(object):
url_suf = self.urlq({}, [])
is_ls = "ls" in self.uparam
is_js = self.cookies.get("js") == "y"
tpl = "browser"
if "b" in self.uparam:
@@ -2105,6 +2158,7 @@ class HttpCli(object):
"taglist": [],
"srvinf": srv_info,
"acct": self.uname,
"idx": ("e2d" in vn.flags),
"perms": perms,
"logues": logues,
"readme": readme,
@@ -2113,6 +2167,7 @@ class HttpCli(object):
"vdir": quotep(self.vpath),
"vpnodes": vpnodes,
"files": [],
"ls0": None,
"acct": self.uname,
"perms": json.dumps(perms),
"taglist": [],
@@ -2193,7 +2248,7 @@ class HttpCli(object):
for fn in vfs_ls:
base = ""
href = fn
if not is_ls and not self.trailing_slash and vpath:
if not is_ls and not is_js and not self.trailing_slash and vpath:
base = "/" + vpath + "/"
href = base + fn
@@ -2336,7 +2391,12 @@ class HttpCli(object):
dirs.sort(key=itemgetter("name"))
j2a["files"] = dirs + files
if is_js:
j2a["ls0"] = {"dirs": dirs, "files": files, "taglist": taglist}
j2a["files"] = []
else:
j2a["files"] = dirs + files
j2a["logues"] = logues
j2a["taglist"] = taglist
j2a["txt_ext"] = self.args.textfiles.replace(",", " ")

View File

@@ -418,7 +418,8 @@ class MTag(object):
try:
md = mutagen.File(fsenc(abspath), easy=True)
x = md.info.length
if not md.info.length and not md.info.codec:
raise Exception()
except Exception as ex:
return self.get_ffprobe(abspath) if self.can_ffprobe else {}

View File

@@ -302,6 +302,10 @@ class SvcHub(object):
print("nailed it", end="")
ret = self.retcode
finally:
if self.args.wintitle:
print("\033]0;\033\\", file=sys.stderr, end="")
sys.stderr.flush()
print("\033[0m")
if self.logf:
self.logf.close()

View File

@@ -2,9 +2,10 @@
from __future__ import print_function, unicode_literals
import re
import sys
import socket
from .__init__ import MACOS, ANYWIN
from .__init__ import MACOS, ANYWIN, unicode
from .util import chkcmd
@@ -54,6 +55,8 @@ class TcpSrv(object):
eps[x] = "external"
msgs = []
title_tab = {}
title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")]
m = "available @ http://{}:{}/ (\033[33m{}\033[0m)"
for ip, desc in sorted(eps.items(), key=lambda x: x[1]):
for port in sorted(self.args.p):
@@ -62,11 +65,39 @@ class TcpSrv(object):
msgs.append(m.format(ip, port, desc))
if not self.args.wintitle:
continue
if port in [80, 443]:
ep = ip
else:
ep = "{}:{}".format(ip, port)
hits = []
if "pub" in title_vars and "external" in unicode(desc):
hits.append(("pub", ep))
if "pub" in title_vars or "all" in title_vars:
hits.append(("all", ep))
for var in title_vars:
if var.startswith("ip-") and ep.startswith(var[3:]):
hits.append((var, ep))
for tk, tv in hits:
try:
title_tab[tk][tv] = 1
except:
title_tab[tk] = {tv: 1}
if msgs:
msgs[-1] += "\n"
for m in msgs:
self.log("tcpsrv", m)
if self.args.wintitle:
self._set_wintitle(title_tab)
def _listen(self, ip, port):
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -232,3 +263,26 @@ class TcpSrv(object):
eps[default_route] = desc
return eps
def _set_wintitle(self, vars):
vars["all"] = vars.get("all", {"Local-Only": 1})
vars["pub"] = vars.get("pub", vars["all"])
vars2 = {}
for k, eps in vars.items():
vars2[k] = {
ep: 1
for ep in eps.keys()
if ":" not in ep or ep.split(":")[0] not in eps
}
title = ""
vars = vars2
for p in self.args.wintitle.split(" "):
if p.startswith("$"):
p = " and ".join(sorted(vars.get(p[1:], {"(None)": 1}).keys()))
title += "{} ".format(p)
print("\033]0;{}\033\\".format(title), file=sys.stderr, end="")
sys.stderr.flush()

View File

@@ -21,6 +21,7 @@ from .util import (
Pebkac,
Queue,
ProgressPrinter,
SYMTIME,
fsdec,
fsenc,
absreal,
@@ -1307,11 +1308,14 @@ class Up2k(object):
err = "partial upload exists at a different location; please resume uploading here instead:\n"
err += "/" + quotep(vsrc) + " "
dupe = [cj["prel"], cj["name"]]
try:
self.dupesched[src].append(dupe)
except:
self.dupesched[src] = [dupe]
# registry is size-constrained + can only contain one unique wark;
# let want_recheck trigger symlink (if still in reg) or reupload
if cur:
dupe = [cj["prel"], cj["name"], cj["lmod"]]
try:
self.dupesched[src].append(dupe)
except:
self.dupesched[src] = [dupe]
raise Pebkac(400, err)
@@ -1332,7 +1336,7 @@ class Up2k(object):
dst = os.path.join(job["ptop"], job["prel"], job["name"])
if not self.args.nw:
bos.unlink(dst) # TODO ed pls
self._symlink(src, dst)
self._symlink(src, dst, lmod=cj["lmod"])
if cur:
a = [cj[x] for x in "prel name lmod size addr".split()]
@@ -1404,13 +1408,14 @@ class Up2k(object):
with ren_open(fname, "wb", fdir=fdir, suffix=suffix) as f:
return f["orz"][1]
def _symlink(self, src, dst, verbose=True):
def _symlink(self, src, dst, verbose=True, lmod=None):
if verbose:
self.log("linking dupe:\n {0}\n {1}".format(src, dst))
if self.args.nw:
return
linked = False
try:
if self.args.no_symlink:
raise Exception("disabled in config")
@@ -1441,10 +1446,18 @@ class Up2k(object):
hops = len(ndst[nc:]) - 1
lsrc = "../" * hops + "/".join(lsrc)
os.symlink(fsenc(lsrc), fsenc(ldst))
linked = True
except Exception as ex:
self.log("cannot symlink; creating copy: " + repr(ex))
shutil.copy2(fsenc(src), fsenc(dst))
if lmod and (not linked or SYMTIME):
times = (int(time.time()), int(lmod))
if ANYWIN:
self.lastmod_q.put([dst, 0, times])
else:
bos.utime(dst, times, False)
def handle_chunk(self, ptop, wark, chash):
with self.mutex:
job = self.registry[ptop].get(wark)
@@ -1551,12 +1564,12 @@ class Up2k(object):
return
cur = self.cur.get(ptop)
for rd, fn in dupes:
for rd, fn, lmod in dupes:
d2 = os.path.join(ptop, rd, fn)
if os.path.exists(d2):
continue
self._symlink(dst, d2)
self._symlink(dst, d2, lmod=lmod)
if cur:
self.db_rm(cur, rd, fn)
self.db_add(cur, wark, rd, fn, *a[-4:])
@@ -1773,8 +1786,9 @@ class Up2k(object):
dlabs = absreal(sabs)
m = "moving symlink from [{}] to [{}], target [{}]"
self.log(m.format(sabs, dabs, dlabs))
os.unlink(sabs)
self._symlink(dlabs, dabs, False)
mt = bos.path.getmtime(sabs, False)
bos.unlink(sabs)
self._symlink(dlabs, dabs, False, lmod=mt)
# folders are too scary, schedule rescan of both vols
self.need_rescan[svn.vpath] = 1
@@ -1904,25 +1918,30 @@ class Up2k(object):
slabs = list(sorted(links.keys()))[0]
ptop, rem = links.pop(slabs)
self.log("linkswap [{}] and [{}]".format(sabs, slabs))
mt = bos.path.getmtime(slabs, False)
bos.unlink(slabs)
bos.rename(sabs, slabs)
bos.utime(slabs, (int(time.time()), int(mt)), False)
self._symlink(slabs, sabs, False)
full[slabs] = [ptop, rem]
sabs = slabs
if not dabs:
dabs = list(sorted(full.keys()))[0]
for alink in links.keys():
lmod = None
try:
if alink != sabs and absreal(alink) != sabs:
continue
self.log("relinking [{}] to [{}]".format(alink, dabs))
lmod = bos.path.getmtime(alink, False)
bos.unlink(alink)
except:
pass
self._symlink(dabs, alink, False)
self._symlink(dabs, alink, False, lmod=lmod)
return len(full) + len(links)
@@ -2028,7 +2047,7 @@ class Up2k(object):
for path, sz, times in ready:
self.log("lmod: setting times {} on {}".format(times, path))
try:
bos.utime(path, times)
bos.utime(path, times, False)
except:
self.log("lmod: failed to utime ({}, {})".format(path, times))

View File

@@ -67,8 +67,9 @@ if WINDOWS and PY2:
FS_ENCODING = "utf-8"
HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT"
SYMTIME = sys.version_info >= (3, 6) and os.supports_follow_symlinks
HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT"
HTTPCODE = {
200: "OK",
@@ -1096,7 +1097,8 @@ def read_socket(sr, total_size):
buf = sr.recv(bufsz)
if not buf:
raise Pebkac(400, "client d/c during binary post")
m = "client d/c during binary post after {} bytes, {} bytes remaining"
raise Pebkac(400, m.format(total_size - remains, remains))
remains -= len(buf)
yield buf
@@ -1176,7 +1178,7 @@ def hashcopy(fin, fout):
return tlen, hashobj.hexdigest(), digest_b64
def sendfile_py(lower, upper, f, s, bufsz, slp):
def sendfile_py(log, lower, upper, f, s, bufsz, slp):
remains = upper - lower
f.seek(lower)
while remains > 0:
@@ -1196,17 +1198,24 @@ def sendfile_py(lower, upper, f, s, bufsz, slp):
return 0
def sendfile_kern(lower, upper, f, s, bufsz, slp):
def sendfile_kern(log, lower, upper, f, s, bufsz, slp):
out_fd = s.fileno()
in_fd = f.fileno()
ofs = lower
stuck = None
while ofs < upper:
stuck = stuck or time.time()
try:
req = min(2 ** 30, upper - ofs)
select.select([], [out_fd], [], 10)
n = os.sendfile(out_fd, in_fd, ofs, req)
stuck = None
except Exception as ex:
# print("sendfile: " + repr(ex))
d = time.time() - stuck
log("sendfile stuck for {:.3f} sec: {!r}".format(d, ex))
if d < 3600 and ex.errno == 11: # eagain
continue
n = 0
if n <= 0:

View File

@@ -79,6 +79,27 @@ a, #files tbody div a:last-child {
color: #999;
font-weight: normal;
}
.s0:after,
.s1:after {
content: '⌄';
margin-left: -.1em;
}
.s0r:after,
.s1r:after {
content: '⌃';
margin-left: -.1em;
}
.s0:after,
.s0r:after {
color: #fb0;
}
.s1:after,
.s1r:after {
color: #d09;
}
#files thead th:after {
margin-right: -.7em;
}
#files tbody tr:hover td {
background: #1c1c1c;
}
@@ -405,6 +426,9 @@ html.light #ggrid>a.sel {
opacity: .3;
color: #f6c;
}
#wfm a.hide {
display: none;
}
html.light #wfm a:not(.en) {
color: #c4a;
}
@@ -466,7 +490,7 @@ html.light #wfm a:not(.en) {
width: calc(100% - 10.5em);
background: rgba(0,0,0,0.2);
}
@media (min-width: 80em) {
@media (min-width: 70em) {
#barpos,
#barbuf {
width: calc(100% - 21em);
@@ -658,7 +682,7 @@ input.eq_gain {
#wrap {
margin: 1.8em 1.5em 0 1.5em;
min-height: 70vh;
padding-bottom: 5em;
padding-bottom: 7em;
}
#tree {
display: none;
@@ -1042,7 +1066,6 @@ html.light #rui {
font-size: 1.5em;
}
#doc {
background: none;
overflow: visible;
margin: -1em 0 .5em 0;
padding: 1em 0 1em 0;
@@ -1081,7 +1104,7 @@ html.light #doc .line-highlight {
#docul li {
margin: 0;
}
#tree #docul a {
#tree #docul li+li a {
display: block;
}
#seldoc.sel {
@@ -1130,6 +1153,7 @@ a.btn,
html,
#doc,
#rui,
#files td,
#files thead th,
@@ -1211,6 +1235,7 @@ html.light {
html.light #ops,
html.light .opbox,
html.light #path,
html.light #doc,
html.light #srch_form,
html.light .ghead,
html.light #u2etas {
@@ -1288,6 +1313,14 @@ html.light #ops a,
html.light #files tbody div a:last-child {
color: #06a;
}
html.light .s0:after,
html.light .s0r:after {
color: #059;
}
html.light .s1:after,
html.light .s1r:after {
color: #f5d;
}
html.light #files thead th {
background: #eaeaea;
border-color: #ccc;
@@ -1394,6 +1427,7 @@ html.light .opview input[type="text"] {
border-color: #38d;
}
html.light #u2tab a>span,
html.light #docul .bn a>span,
html.light #files td div span {
color: #000;
}
@@ -2119,6 +2153,7 @@ html.light #u2foot .warn span {
border-color: #d06;
}
#u2tab a>span,
#docul .bn a>span,
#unpost a>span {
font-weight: bold;
font-style: italic;

View File

@@ -143,7 +143,8 @@
have_zip = {{ have_zip|tojson }},
txt_ext = "{{ txt_ext }}",
{% if no_prism %}no_prism = 1,{% endif %}
readme = {{ readme|tojson }};
readme = {{ readme|tojson }},
ls0 = {{ ls0|tojson }};
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark");
</script>

View File

@@ -8,13 +8,9 @@ function dbg(msg) {
// toolbar
ebi('ops').innerHTML = (
'<a href="#" data-dest="" tt="close submenu">--</a>\n' +
(have_up2k_idx ? (
'<a href="#" data-perm="read" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those$N$N&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,$N&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,$N&lt;code&gt;^yana .opus$&lt;/code&gt; = start with yana and be an opus file$N&lt;code&gt;&quot;try unite&quot;&lt;/code&gt; = contain exactly «try unite»">🔎</a>\n' +
(have_del && have_unpost ? '<a href="#" data-dest="unpost" tt="unpost: delete your recent uploads">🧯</a>\n' : '') +
'<a href="#" data-dest="up2k" tt="up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server">🚀</a>\n'
) : (
'<a href="#" data-perm="write" data-dest="up2k" tt="up2k: upload files with resume support (close your browser and drop the same files in later)">🚀</a>\n'
)) +
'<a href="#" data-perm="read" data-dep="idx" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those$N$N&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,$N&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,$N&lt;code&gt;^yana .opus$&lt;/code&gt; = start with yana and be an opus file$N&lt;code&gt;&quot;try unite&quot;&lt;/code&gt; = contain exactly «try unite»">🔎</a>\n' +
(have_del && have_unpost ? '<a href="#" data-dest="unpost" data-dep="idx" tt="unpost: delete your recent uploads">🧯</a>\n' : '') +
'<a href="#" data-dest="up2k">🚀</a>\n' +
'<a href="#" data-perm="write" data-dest="bup" tt="bup: basic uploader, even supports netscape 4.0">🎈</a>\n' +
'<a href="#" data-perm="write" data-dest="mkdir" tt="mkdir: create a new directory">📂</a>\n' +
'<a href="#" data-perm="read write" data-dest="new_md" tt="new-md: create a new markdown document">📝</a>\n' +
@@ -68,12 +64,10 @@ ebi('op_up2k').innerHTML = (
' <input type="checkbox" id="ask_up" />\n' +
' <label for="ask_up" tt="ask for confirmation before upload starts">💭</label>\n' +
' </td>\n' +
(have_up2k_idx ? (
' <td class="c" data-perm="read" rowspan="2">\n' +
' <input type="checkbox" id="fsearch" />\n' +
' <label for="fsearch" tt="don\'t actually upload, instead check if the files already $N exist on the server (will scan all folders you can read)">🔎</label>\n' +
' </td>\n'
) : '') +
' <td class="c" data-perm="read" data-dep="idx" rowspan="2">\n' +
' <input type="checkbox" id="fsearch" />\n' +
' <label for="fsearch" tt="don\'t actually upload, instead check if the files already $N exist on the server (will scan all folders you can read)">🔎</label>\n' +
' </td>\n' +
' <td data-perm="read" rowspan="2" id="u2btn_cw"></td>\n' +
' <td data-perm="read" rowspan="2" id="u2c3w"></td>\n' +
' </tr>\n' +
@@ -157,6 +151,7 @@ ebi('op_cfg').innerHTML = (
' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' +
' <a id="dotfiles" class="tgl btn" href="#" tt="show hidden files (if server permits)">dotfiles</a>\n' +
' <a id="ireadme" class="tgl btn" href="#" tt="show README.md in folder listings">📜 readme</a>\n' +
' <a id="spafiles" class="tgl btn" href="#" tt="speedboost when not using the navpane;$Nturn it off if things arent loading somehow">spa</a>\n' +
' </div>\n' +
'</div>\n' +
(have_zip ? (
@@ -209,6 +204,9 @@ ebi('tree').innerHTML = (
var ops = QSA('#ops>a');
for (var a = 0; a < ops.length; a++) {
ops[a].onclick = opclick;
var v = ops[a].getAttribute('data-dest');
if (v)
ops[a].href = '#v=' + v;
}
})();
@@ -306,7 +304,9 @@ function set_files_html(html) {
var ACtx = window.AudioContext || window.webkitAudioContext,
actx = ACtx && new ACtx();
actx = ACtx && new ACtx(),
hash0 = location.hash,
mp;
var mpl = (function () {
@@ -652,14 +652,11 @@ function MPlayer() {
};
}
addcrc();
var mp = new MPlayer();
makeSortable(ebi('files'), mp.read_order.bind(mp));
function ft2dict(tr) {
var th = ebi('files').tHead.rows[0].cells,
rv = [],
rh = [],
ra = [],
rt = {};
@@ -671,10 +668,11 @@ function ft2dict(tr) {
if (!tv)
continue;
(vis ? rv : ra).push(tk);
(vis ? rv : rh).push(tk);
ra.push(tk);
rt[tk] = tv;
}
return [rt, rv, ra];
return [rt, rv, rh, ra];
}
@@ -808,7 +806,7 @@ var pbar = (function () {
bctx.clearRect(0, 0, bc.w, bc.h);
if (!mp.au)
if (!mp || !mp.au)
return;
var sm = bc.w * 1.0 / mp.au.duration,
@@ -835,7 +833,7 @@ var pbar = (function () {
pctx.clearRect(0, 0, pc.w, pc.h);
if (!mp.au || isNaN(adur = mp.au.duration) || isNaN(apos = mp.au.currentTime) || apos < 0 || adur < apos)
if (!mp || !mp.au || isNaN(adur = mp.au.duration) || isNaN(apos = mp.au.currentTime) || apos < 0 || adur < apos)
return; // not-init || unsupp-codec
var sm = bc.w * 1.0 / adur;
@@ -906,6 +904,9 @@ var vbar = (function () {
}
r.draw = function () {
if (!mp)
return;
var gh = h + '' + light;
if (gradh != gh) {
gradh = gh;
@@ -1132,7 +1133,6 @@ var mpui = (function () {
if (mp.au.paused)
timer.rm(updater_impl);
}
r.progress_updater();
return r;
})();
@@ -1484,7 +1484,7 @@ function play(tid, is_ev, seek, call_depth) {
seek_au_sec(seek);
}
if (!seek) {
if (!seek && !ebi('unsearch')) {
var o = ebi(oid);
o.setAttribute('id', 'thx_js');
sethash(oid);
@@ -1492,7 +1492,8 @@ function play(tid, is_ev, seek, call_depth) {
}
mpui.progress_updater();
pbar.drawbuf();
pbar.onresize();
vbar.onresize();
mpl.announce();
return true;
}
@@ -1567,7 +1568,8 @@ function autoplay_blocked(seek) {
function eval_hash() {
var v = location.hash;
var v = hash0;
hash0 = null;
if (!v)
return;
@@ -1592,6 +1594,11 @@ function eval_hash() {
i.value = uricom_dec(v.slice(3))[0];
return i.oninput();
}
if (v.indexOf('#v=') === 0) {
goto(v.slice(3));
return;
}
}
@@ -1803,17 +1810,19 @@ var fileman = (function () {
clmod(bdel, 'en', nsel);
clmod(bcut, 'en', nsel);
clmod(bpst, 'en', r.clip && r.clip.length);
bren.style.display = have_mv && has(perms, 'write') && has(perms, 'move') ? '' : 'none';
bdel.style.display = have_del && has(perms, 'delete') ? '' : 'none';
bcut.style.display = have_mv && has(perms, 'move') ? '' : 'none';
bpst.style.display = have_mv && has(perms, 'write') ? '' : 'none';
clmod(bren, 'hide', !(have_mv && has(perms, 'write') && has(perms, 'move')));
clmod(bdel, 'hide', !(have_del && has(perms, 'delete')));
clmod(bcut, 'hide', !(have_mv && has(perms, 'move')));
clmod(bpst, 'hide', !(have_mv && has(perms, 'write')));
clmod(ebi('wfm'), 'act', QS('#wfm a.en:not(.hide)'));
bpst.setAttribute('tt', 'paste ' + r.clip.length + ' items$NHotkey: ctrl-V');
clmod(ebi('wfm'), 'act', QS('#wfm a.en:not([style])'));
};
r.rename = function (e) {
ev(e);
if (bren.style.display)
if (clgot(bren, 'hide'))
return toast.err(3, 'cannot rename:\nyou do not have “move” permission in this folder');
var sel = msel.getsel();
@@ -1929,10 +1938,6 @@ var fileman = (function () {
(function (a) {
f[a].inew.onkeydown = function (e) {
rn_ok(a, true);
if (e.key == 'Escape')
return rn_cancel();
if (e.key == 'Enter')
return rn_apply();
};
@@ -2104,7 +2109,7 @@ var fileman = (function () {
r.delete = function (e) {
ev(e);
if (bdel.style.display)
if (clgot(bdel, 'hide'))
return toast.err(3, 'cannot delete:\nyou do not have “delete” permission in this folder');
var sel = msel.getsel(),
@@ -2150,7 +2155,7 @@ var fileman = (function () {
r.cut = function (e) {
ev(e);
if (bcut.style.display)
if (clgot(bcut, 'hide'))
return toast.err(3, 'cannot cut:\nyou do not have “move” permission in this folder');
var sel = msel.getsel(),
@@ -2191,7 +2196,7 @@ var fileman = (function () {
r.paste = function (e) {
ev(e);
if (bpst.style.display)
if (clgot(bpst, 'hide'))
return toast.err(3, 'cannot paste:\nyou do not have “write” permission in this folder');
if (!r.clip.length)
@@ -2482,7 +2487,7 @@ var showfile = (function () {
}
r.mktree = function () {
var html = ['<li class="bn">list of textfiles in<br />' + esc(get_vpath()) + '</li>'];
var html = ['<li class="bn">list of textfiles in<br />' + linksplit(get_vpath()).join('') + '</li>'];
for (var a = 0; a < r.files.length; a++) {
var file = r.files[a];
html.push('<li><a href="#" hl="' + file.id +
@@ -2559,9 +2564,9 @@ var thegrid = (function () {
'<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a></span> <span>chop: ' +
'<a href="#" class="btn" l="-1" tt="truncate filenames more (show less)">&ndash;</a> ' +
'<a href="#" class="btn" l="1" tt="truncate filenames less (show more)">+</a></span> <span>sort by: ' +
'<a href="#" s="href">name</a>, ' +
'<a href="#" s="sz">size</a>, ' +
'<a href="#" s="ts">date</a>, ' +
'<a href="#" s="href">name</a> ' +
'<a href="#" s="sz">size</a> ' +
'<a href="#" s="ts">date</a> ' +
'<a href="#" s="ext">type</a>' +
'</span></div>' +
'<div id="ggrid"></div>'
@@ -2675,14 +2680,12 @@ var thegrid = (function () {
href = noq_href(this),
aplay = ebi('a' + oth.getAttribute('id')),
is_img = /\.(gif|jpe?g|png|webp|webm|mp4)(\?|$)/i.test(href),
in_tree = null,
is_dir = href.endsWith('/'),
in_tree = is_dir && treectl.find(oth.textContent.slice(0, -1)),
have_sel = QS('#files tr.sel'),
td = oth.closest('td').nextSibling,
tr = td.parentNode;
if (href.endsWith('/'))
in_tree = treectl.find(oth.textContent.slice(0, -1));
if (r.sel && !dbl) {
td.click();
clmod(this, 'sel', clgot(tr, 'sel'));
@@ -2693,6 +2696,9 @@ var thegrid = (function () {
else if (in_tree && !have_sel)
in_tree.click();
else if (is_dir && !have_sel && treectl.spa)
treectl.reqls(href, true, true);
else if (!is_img && have_sel)
window.open(href, '_blank');
@@ -2867,10 +2873,6 @@ var thegrid = (function () {
import_js('/.cpr/baguettebox.js', r.bagit);
}, 1);
if (r.en) {
loadgrid();
}
return r;
})();
@@ -2968,12 +2970,12 @@ document.onkeydown = function (e) {
if (k == 'Escape') {
ae && ae.blur();
if (ebi('rn_cancel'))
return ebi('rn_cancel').click();
if (QS('.opview.act'))
return QS('#ops>a').click();
if (QS('#unsearch'))
return QS('#unsearch').click();
if (widget.is_open)
return widget.close();
@@ -2983,6 +2985,9 @@ document.onkeydown = function (e) {
if (!treectl.hidden)
return treectl.detree();
if (QS('#unsearch'))
return QS('#unsearch').click();
if (thegrid.en)
return ebi('griden').click();
}
@@ -3357,7 +3362,7 @@ document.onkeydown = function (e) {
treectl.hide();
var html = mk_files_header(tagord);
var html = mk_files_header(tagord), seen = {};
html.push('<tbody>');
html.push('<tr><td>-</td><td colspan="42"><a href="#" id="unsearch"><big style="font-weight:bold">[❌] close search results</big></a></td></tr>');
for (var a = 0; a < res.hits.length; a++) {
@@ -3366,13 +3371,18 @@ document.onkeydown = function (e) {
sz = esc(r.sz + ''),
rp = esc(uricom_dec(r.rp + '')[0]),
ext = rp.lastIndexOf('.') > 0 ? rp.split('.').pop().split('?')[0] : '%',
links = linksplit(r.rp + '');
id = 'f-' + ('00000000' + crc32(rp)).slice(-8);
while (seen[id])
id += 'a';
seen[id] = 1;
if (ext.length > 8)
ext = '%';
links = links.join('');
var nodes = ['<tr><td>-</td><td><div>' + links + '</div>', sz];
var links = linksplit(r.rp + '', id).join(''),
nodes = ['<tr><td>-</td><td><div>' + links + '</div>', sz];
for (var b = 0; b < tagord.length; b++) {
var k = tagord[b],
v = r.tags[k] || "";
@@ -3434,6 +3444,7 @@ var treectl = (function () {
mentered = null,
treesz = clamp(icfg_get('treesz', 16), 10, 50);
bcfg_bind(r, 'spa', 'spafiles', true);
bcfg_bind(r, 'ireadme', 'ireadme', true);
bcfg_bind(r, 'dyn', 'dyntree', true, onresize);
bcfg_bind(r, 'dots', 'dotfiles', false, function (v) {
@@ -3441,7 +3452,7 @@ var treectl = (function () {
});
setwrap(bcfg_bind(r, 'wtree', 'wraptree', true, setwrap));
setwrap(bcfg_bind(r, 'parpane', 'parpane', true, onscroll));
bcfg_bind(r, 'htree', 'hovertree', true, reload_tree);
bcfg_bind(r, 'htree', 'hovertree', false, reload_tree);
function setwrap(v) {
clmod(ebi('tree'), 'nowrap', !v);
@@ -3614,6 +3625,7 @@ var treectl = (function () {
if (!QS(q))
break;
}
nq = Math.max(nq, get_evpath().split('/').length - 2);
var iw = (treesz + Math.max(0, nq)),
w = iw + 'em',
w2 = (iw + 2) + 'em';
@@ -3636,7 +3648,7 @@ var treectl = (function () {
r.goto = function (url, push) {
get_tree("", url, true);
reqls(url, push, true);
r.reqls(url, push, true);
};
function get_tree(top, dst, rst) {
@@ -3805,12 +3817,12 @@ var treectl = (function () {
treegrow.call(this.previousSibling, e);
return;
}
reqls(this.getAttribute('href'), true);
r.reqls(this.getAttribute('href'), true);
r.dir_cb = tree_scrollto;
thegrid.setvis(true);
}
function reqls(url, hpush, no_tree) {
r.reqls = function (url, hpush, no_tree) {
var xhr = new XMLHttpRequest();
xhr.top = url;
xhr.hpush = hpush;
@@ -3865,8 +3877,31 @@ var treectl = (function () {
ebi('srv_info').innerHTML = '<span>' + res.srvinf + '</span>';
var top = this.top,
nodes = res.dirs.concat(res.files),
if (this.hpush && !showfile.active())
hist_push(this.top);
r.gentab(this.top, res);
despin('#files');
despin('#gfiles');
ebi('pro').innerHTML = res.logues ? res.logues[0] || "" : "";
ebi('epi').innerHTML = res.logues ? res.logues[1] || "" : "";
clmod(ebi('epi'), 'mdo');
if (res.readme)
show_readme(res.readme);
wintitle();
var fun = r.ls_cb;
if (fun) {
r.ls_cb = null;
fun();
}
eval_hash();
}
r.gentab = function (top, res) {
var nodes = res.dirs.concat(res.files),
html = mk_files_header(res.taglist),
seen = {};
@@ -3882,7 +3917,7 @@ var treectl = (function () {
id = 'f-' + ('00000000' + crc32(fname)).slice(-8),
lang = showfile.getlang(fname);
while (seen[id])
while (seen[id]) // ejyefs ev69gg y9j8sg .opus
id += 'a';
seen[id] = 1;
@@ -3914,36 +3949,39 @@ var treectl = (function () {
html = html.join('\n');
set_files_html(html);
if (this.hpush && !showfile.active())
hist_push(this.top);
acct = res.acct;
apply_perms(res.perms);
despin('#files');
despin('#gfiles');
ebi('pro').innerHTML = res.logues ? res.logues[0] || "" : "";
ebi('epi').innerHTML = res.logues ? res.logues[1] || "" : "";
clmod(ebi('epi'), 'mdo');
if (res.readme)
show_readme(res.readme);
wintitle();
filecols.set_style();
showfile.mktree();
mukey.render();
reload_tree();
reload_browser();
tree_scrollto();
var fun = r.ls_cb;
if (fun) {
r.ls_cb = null;
fun();
if (res.acct) {
acct = res.acct;
have_up2k_idx = res.idx;
apply_perms(res.perms);
fileman.render();
}
}
r.hydrate = function () {
if (ls0 === null) {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/?am_js', true);
xhr.send();
return r.reqls(get_evpath(), false, true);
}
r.gentab(get_evpath(), ls0);
reload_browser();
pbar.onresize();
vbar.onresize();
mukey.render();
showfile.addlinks();
thegrid.setdirty();
setTimeout(eval_hash, 1);
};
function parsetree(res, top) {
var ret = '';
for (var a = 0; a < res.a.length; a++) {
@@ -4038,6 +4076,17 @@ function despin(sel) {
function apply_perms(newperms) {
perms = newperms || [];
var a = QS('#ops a[data-dest="up2k"]');
if (have_up2k_idx) {
a.removeAttribute('data-perm');
a.setAttribute('tt', 'up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server');
}
else {
a.setAttribute('data-perm', 'write');
a.setAttribute('tt', 'up2k: upload files with resume support (close your browser and drop the same files in later)');
}
tt.att(QS('#ops'));
var axs = [],
aclass = '>',
chk = ['read', 'write', 'move', 'delete', 'get'];
@@ -4067,6 +4116,12 @@ function apply_perms(newperms) {
o[a].style.display = display;
}
var o = QSA('#ops>a[data-dep], #u2conf td[data-dep]');
for (var a = 0; a < o.length; a++)
o[a].style.display = (
o[a].getAttribute('data-dep') != 'idx' || have_up2k_idx
) ? '' : 'none';
var act = QS('#ops>a.act');
if (act && act.style.display === 'none')
goto();
@@ -4404,27 +4459,6 @@ var mukey = (function () {
})();
function addcrc() {
var links = QSA(
'#files>tbody>tr>td:first-child+td>' + (
ebi('unsearch') ? 'div>a:last-child' : 'a'));
var seen = {}; // ejyefs ev69gg y9j8sg .opus
for (var a = 0, aa = links.length; a < aa; a++) {
var id = links[a].getAttribute('id');
if (!id) {
var crc = crc32(links[a].textContent || links[a].innerText);
id = 'f-' + ('00000000' + crc).slice(-8);
while (seen[id])
id += 'a';
links[a].setAttribute('id', id);
}
seen[id] = 1;
}
}
var light;
(function () {
function freshen() {
@@ -4756,7 +4790,7 @@ function show_md(md, name, div, url, depth) {
try {
clmod(div, 'mdo', 1);
div.innerHTML = marked(md, {
div.innerHTML = marked.parse(md, {
headerPrefix: 'md-',
breaks: true,
gfm: true
@@ -4983,15 +5017,30 @@ function wintitle(txt) {
}
ebi('path').onclick = function (e) {
var a = e.target.closest('a[href]');
if (!treectl.spa || !a || !(a = a.getAttribute('href') + '') || !a.endsWith('/'))
return;
thegrid.setvis(true);
treectl.reqls(a, true, true);
return ev(e);
};
ebi('files').onclick = ebi('docul').onclick = function (e) {
var tgt = e.target.closest('a[id]');
if (tgt && tgt.getAttribute('id').indexOf('f-') === 0 && tgt.textContent.endsWith('/')) {
var el = treectl.find(tgt.textContent.slice(0, -1));
if (!el)
return;
el.click();
return ev(e);
if (el) {
el.click();
return ev(e);
}
if (treectl.spa) {
treectl.reqls(tgt.getAttribute('href'), true, true);
return ev(e);
}
return;
}
tgt = e.target.closest('a[hl]');
@@ -4999,7 +5048,14 @@ ebi('files').onclick = ebi('docul').onclick = function (e) {
showfile.show(noq_href(ebi(tgt.getAttribute('hl'))), tgt.getAttribute('lang'));
return ev(e);
}
}
tgt = e.target.closest('a');
if (tgt && tgt.closest('li.bn')) {
thegrid.setvis(true);
treectl.goto(tgt.getAttribute('href'), true);
return ev(e);
}
};
function reload_mp() {
@@ -5007,32 +5063,33 @@ function reload_mp() {
audio_eq.stop();
mp.au.pause();
mp.au = null;
mpl.unbuffer();
}
mpl.stop();
var plays = QSA('tr>td:first-child>a.play');
for (var a = plays.length - 1; a >= 0; a--)
plays[a].parentNode.innerHTML = '-';
mpl.unbuffer();
mp = new MPlayer();
audio_eq.acst = {};
setTimeout(pbar.onresize, 1);
}
function reload_browser(not_mp) {
function reload_browser() {
filecols.set_style();
var parts = get_evpath().split('/'),
rm = QSA('#path>a+a+a');
rm = QSA('#path>a+a+a'),
ftab = ebi('files'),
link = '/', o;
for (a = rm.length - 1; a >= 0; a--)
rm[a].parentNode.removeChild(rm[a]);
var link = '/';
for (var a = 1; a < parts.length - 1; a++) {
link += parts[a] + '/';
var o = mknod('a');
o = mknod('a');
o.setAttribute('href', link);
o.textContent = uricom_dec(parts[a])[0];
ebi('path').appendChild(o);
@@ -5046,11 +5103,12 @@ function reload_browser(not_mp) {
oo[a].textContent = hsz;
}
if (!not_mp) {
addcrc();
reload_mp();
makeSortable(ebi('files'), mp.read_order.bind(mp));
}
reload_mp();
try { showsort(ftab); } catch (ex) { }
makeSortable(ftab, function () {
thegrid.setdirty();
mp.read_order();
});
for (var a = 0; a < 2; a++)
clmod(ebi(a ? 'pro' : 'epi'), 'hidden', ebi('unsearch'));
@@ -5061,7 +5119,4 @@ function reload_browser(not_mp) {
thegrid.setdirty();
msel.render();
}
reload_browser(true);
showfile.addlinks();
mukey.render();
setTimeout(eval_hash, 1);
treectl.hydrate();

View File

@@ -10,7 +10,7 @@
{%- endif %}
</head>
<body>
<div id="mn">navbar</div>
<div id="mn"></div>
<div id="mh">
<a id="lightswitch" href="#">go dark</a>
<a id="navtoggle" href="#">hide nav</a>

View File

@@ -39,20 +39,14 @@ var md_plug = {};
// add navbar
(function () {
var n = document.location + '';
n = n.substr(n.indexOf('//') + 2).split('?')[0].split('/');
n[0] = 'top';
var loc = [];
var nav = [];
for (var a = 0; a < n.length; a++) {
if (a > 0)
loc.push(n[a]);
var dec = esc(uricom_dec(n[a])[0]);
nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>');
var parts = get_evpath().split('/'), link = '', o;
for (var a = 0, aa = parts.length - 2; a <= aa; a++) {
link += parts[a] + (a < aa ? '/' : '');
o = mknod('a');
o.setAttribute('href', link);
o.textContent = uricom_dec(parts[a])[0] || 'top';
dom_nav.appendChild(o);
}
dom_nav.innerHTML = nav.join('');
})();
@@ -91,13 +85,13 @@ function copydom(src, dst, lv) {
var rpl = [];
for (var a = sc.length - 1; a >= 0; a--) {
var st = sc[a].tagName,
dt = dc[a].tagName;
var st = sc[a].tagName || sc[a].nodeType,
dt = dc[a].tagName || dc[a].nodeType;
if (st !== dt) {
dbg("replace L%d (%d/%d) type %s/%s", lv, a, sc.length, st, dt);
rpl.push(a);
continue;
dst.innerHTML = src.innerHTML;
return;
}
var sa = sc[a].attributes || [],
@@ -146,8 +140,11 @@ function copydom(src, dst, lv) {
// repl is reversed; build top-down
var nbytes = 0;
for (var a = rpl.length - 1; a >= 0; a--) {
var html = sc[rpl[a]].outerHTML;
dc[rpl[a]].outerHTML = html;
var i = rpl[a],
prop = sc[i].nodeType == 1 ? 'outerHTML' : 'nodeValue';
var html = sc[i][prop];
dc[i][prop] = html;
nbytes += html.length;
}
if (nbytes > 0)
@@ -256,7 +253,7 @@ function convert_markdown(md_text, dest_dom) {
Object.assign(marked_opts, ext[0]);
try {
var md_html = marked(md_text, marked_opts);
var md_html = marked.parse(md_text, marked_opts);
}
catch (ex) {
if (ext)

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<title>copyparty</title>
<title>{{ svcname }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8">
<link rel="stylesheet" media="screen" href="/.cpr/msg.css?_={{ ts }}">

View File

@@ -81,9 +81,10 @@ table {
text-align: right;
}
blockquote {
margin: 0 0 0 .6em;
padding: .7em 1em;
margin: 0 0 1.6em .6em;
padding: .7em 1em 0 1em;
border-left: .3em solid rgba(128,128,128,0.5);
border-radius: 0 0 0 .25em;
}

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<title>copyparty</title>
<title>{{ svcname }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8">
<link rel="stylesheet" media="screen" href="/.cpr/splash.css?_={{ ts }}">
@@ -75,11 +75,13 @@
<h1 id="cc">client config:</h1>
<ul>
{% if k304 %}
<li><a href="/?k304=n" class="r">disable k304</a> (currently enabled)
<li><a href="/?k304=n">disable k304</a> (currently enabled)
{%- else %}
<li><a href="/?k304=y">enable k304</a> (currently disabled)
<li><a href="/?k304=y" class="r">enable k304</a> (currently disabled)
{% endif %}
<blockquote>enabling this will disconnect your client on every HTTP 304, which can prevent some buggy browsers/proxies from getting stuck (suddenly not being able to load pages), <em>but</em> it will also make things slower in general</blockquote></li>
<li><a href="/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
</ul>
<h1>login for more:</h1>

View File

@@ -354,6 +354,13 @@ html.light textarea:focus {
}
.mdo ul,
.mdo ol {
padding-left: 1em;
}
.mdo ul ul,
.mdo ul ol,
.mdo ol ul,
.mdo ol ol {
padding-left: 2em;
border-left: .3em solid #ddd;
}
.mdo ul>li,

View File

@@ -1485,7 +1485,8 @@ function up2k_init(subtle) {
err.indexOf('NotFoundError') !== -1 // macos-firefox permissions
) {
pvis.seth(t.n, 1, 'OS-error');
pvis.seth(t.n, 2, err);
pvis.seth(t.n, 2, err + ' @ ' + car);
console.log('OS-error', reader.error, '@', car);
handled = true;
}
@@ -2038,7 +2039,7 @@ function up2k_init(subtle) {
new_state = true;
fixed = true;
}
if (!has(perms, 'read')) {
if (!has(perms, 'read') || !have_up2k_idx) {
new_state = false;
fixed = true;
}
@@ -2113,7 +2114,7 @@ function up2k_init(subtle) {
if (parallel_uploads < 1)
bumpthread(1);
return { "init_deps": init_deps, "set_fsearch": set_fsearch, "ui": pvis }
return { "init_deps": init_deps, "set_fsearch": set_fsearch, "ui": pvis, "st": st, "uc": uc }
}

View File

@@ -86,6 +86,9 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
if ((msg + '').indexOf('ResizeObserver') !== -1)
return; // chrome issue 809574 (benign, from <video>)
if ((msg + '').indexOf('l2d.js') !== -1)
return; // `t` undefined in tapEvent -> hitTestSimpleCustom
var ekey = url + '\n' + lineNo + '\n' + msg;
if (ignexd[ekey] || crashed)
return;
@@ -219,15 +222,15 @@ if (!String.prototype.endsWith)
return this.substring(this_len - search.length, this_len) === search;
};
if (!String.startsWith)
if (!String.prototype.startsWith)
String.prototype.startsWith = function (s, i) {
i = i > 0 ? i | 0 : 0;
return this.substring(i, i + s.length) === s;
};
if (!String.trimEnd)
if (!String.prototype.trimEnd)
String.prototype.trimEnd = String.prototype.trimRight = function () {
return this.replace(/[ \t\r\n]+$/m, '');
return this.replace(/[ \t\r\n]+$/, '');
};
if (!Element.prototype.matches)
@@ -329,14 +332,45 @@ function clgot(el, cls) {
}
function showsort(tab) {
var v, vn, v1, v2, th = tab.tHead,
sopts = jread('fsort', [["href", 1, ""]]);
th && (th = th.rows[0]) && (th = th.cells);
for (var a = sopts.length - 1; a >= 0; a--) {
if (!sopts[a][0])
continue;
v2 = v1;
v1 = sopts[a];
}
v = [v1, v2];
vn = [v1 ? v1[0] : '', v2 ? v2[0] : ''];
var ga = QSA('#ghead a[s]');
for (var a = 0; a < ga.length; a++)
ga[a].className = '';
for (var a = 0; a < th.length; a++) {
var n = vn.indexOf(th[a].getAttribute('name')),
cl = n < 0 ? ' ' : ' s' + n + (v[n][1] > 0 ? ' ' : 'r ');
th[a].className = th[a].className.replace(/ *s[01]r? */, ' ') + cl;
if (n + 1) {
ga = QS('#ghead a[s="' + vn[n] + '"]');
if (ga)
ga.className = cl;
}
}
}
function sortTable(table, col, cb) {
var tb = table.tBodies[0],
th = table.tHead.rows[0].cells,
tr = Array.prototype.slice.call(tb.rows, 0),
i, reverse = th[col].className.indexOf('sort1') !== -1 ? -1 : 1;
for (var a = 0, thl = th.length; a < thl; a++)
th[a].className = th[a].className.replace(/ *sort-?1 */, " ");
th[col].className += ' sort' + reverse;
i, reverse = /s0[^r]/.exec(th[col].className + ' ') ? -1 : 1;
var stype = th[col].getAttribute('sort');
try {
var nrules = [], rules = jread("fsort", []);
@@ -354,6 +388,7 @@ function sortTable(table, col, cb) {
break;
}
jwrite("fsort", nrules);
try { showsort(table); } catch (ex) { }
}
catch (ex) {
console.log("failed to persist sort rules, resetting: " + ex);
@@ -402,7 +437,7 @@ function makeSortable(table, cb) {
}
function linksplit(rp) {
function linksplit(rp, id) {
var ret = [],
apath = '/',
q = null;
@@ -432,8 +467,13 @@ function linksplit(rp) {
vlink = vlink.slice(0, -1) + '<span>/</span>';
}
if (!rp && q)
link += q;
if (!rp) {
if (q)
link += q;
if (id)
link += '" id="' + id;
}
ret.push('<a href="' + apath + link + '">' + vlink + '</a>');
apath += link;

View File

@@ -2,24 +2,12 @@
# example resource files
# utilities
can be provided to copyparty to tweak things
## example `.epilogue.html`
save one of these as `.epilogue.html` inside a folder to customize it:
* [`minimal-up2k.html`](minimal-up2k.html) will [simplify the upload ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
## example browser-css
point `--css-browser` to one of these by URL:
* [`browser.css`](browser.css) changes the background
* [`browser-icons.css`](browser-icons.css) adds filetype icons
## [`multisearch.html`](multisearch.html)
* takes a list of filenames of youtube rips, grabs the youtube-id of each file, and does a search on the server for those
* use it by putting it somewhere on the server and opening it as an html page
* also serves as an extendable template for other specific search behaviors

124
docs/multisearch.html Normal file
View File

@@ -0,0 +1,124 @@
<!DOCTYPE html><html lang="en"><head>
<meta charset="utf-8">
<title>multisearch</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<style>
html, body {
margin: 0;
padding: 0;
color: #ddd;
background: #222;
font-family: sans-serif;
}
body {
padding: 1em;
}
a {
color: #fc5;
}
ul {
line-height: 1.5em;
}
code {
color: #fc5;
border: 1px solid #444;
padding: .1em .2em;
font-family: sans-serif, sans-serif;
}
#src {
display: block;
width: calc(100% - 1em);
padding: .5em;
margin: 0;
}
td {
padding-left: 1em;
}
.hit,
.miss {
font-weight: bold;
padding-left: 0;
padding-top: 1em;
}
.hit {color: #af0;}
.miss {color: #f0c;}
.hit:before {content: '✅';}
.miss:before {content: '❌';}
</style></head><body>
<ul>
<li>paste a list of filenames (youtube rips) below and hit search</li>
<li>it will grab the youtube-id from the filenames and search for each id</li>
<li>filenames must be like <code>-YTID.webm</code> (youtube-dl style) or <code>[YTID].webm</code> (ytdlp style)</li>
</ul>
<textarea id="src"></textarea>
<button id="go">search</button>
<div id="res"></div>
<script>
var ebi = document.getElementById.bind(document);
function esc(txt) {
return txt.replace(/[&"<>]/g, function (c) {
return {
'&': '&amp;',
'"': '&quot;',
'<': '&lt;',
'>': '&gt;'
}[c];
});
}
ebi('go').onclick = async function() {
var queries = [];
for (var ln of ebi('src').value.split(/\n/g)) {
// filter the list of input files,
// only keeping youtube videos,
// meaning the filename ends with either
// [YOUTUBEID].EXTENSION or
// -YOUTUBEID.EXTENSION
var m = /[[-]([0-9a-zA-Z_-]{11})\]?\.(mp4|webm|mkv)$/.exec(ln);
if (!m || !(m = m[1]))
continue;
// create a search query for each line: name like *youtubeid*
queries.push([ln, `name like *${m}*`]);
}
var a = 0, html = ['<table>'], hits = [], misses = [];
for (var [fn, q] of queries) {
var r = await fetch('/?srch', {
method: 'POST',
body: JSON.stringify({'q': q})
});
r = await r.json();
var cl, tab2;
if (r.hits.length) {
tab2 = hits;
cl = 'hit';
}
else {
tab2 = misses;
cl = 'miss';
}
var h = `<tr><td class="${cl}" colspan="9">${esc(fn)}</td></tr>`;
tab2.push(h);
html.push(h);
for (var h of r.hits) {
var link = `<a href="/${h.rp}">${esc(decodeURIComponent(h.rp))}</a>`;
html.push(`<tr><td>${h.sz}</td><td>${link}</td></tr>`);
}
ebi('res').innerHTML = `searching, ${++a} / ${queries.length} done, ${hits.length} hits, ${misses.length} miss`;
}
html.push('<tr><td><h1>hits:</h1></td></tr>');
html = html.concat(hits);
html.push('<tr><td><h1>miss:</h1></td></tr>');
html = html.concat(misses);
html.push('</table>');
ebi('res').innerHTML = html.join('\n');
};
</script></body></html>

View File

@@ -1,10 +1,10 @@
FROM alpine:3.14
FROM alpine:3.15
WORKDIR /z
ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
ver_hashwasm=4.9.0 \
ver_marked=3.0.4 \
ver_marked=4.0.10 \
ver_mde=2.15.0 \
ver_codemirror=5.62.3 \
ver_codemirror=5.64.0 \
ver_fontawesome=5.13.0 \
ver_zopfli=1.0.3
@@ -82,7 +82,6 @@ RUN cd marked-$ver_marked \
&& patch -p1 < /z/marked.patch \
&& npm run build \
&& cp -pv marked.min.js /z/dist/marked.js \
&& cp -pv lib/marked.js /z/dist/marked.full.js \
&& mkdir -p /z/nodepkgs \
&& ln -s $(pwd) /z/nodepkgs/marked
# && npm run test \
@@ -98,8 +97,10 @@ RUN cd CodeMirror-$ver_codemirror \
# build easymde
COPY easymde-marked6.patch /z/
COPY easymde.patch /z/
RUN cd easy-markdown-editor-$ver_mde \
&& patch -p1 < /z/easymde-marked6.patch \
&& patch -p1 < /z/easymde.patch \
&& sed -ri 's`https://registry.npmjs.org/marked/-/marked-[0-9\.]+.tgz`file:/z/nodepkgs/marked`' package-lock.json \
&& sed -ri 's`("marked": ")[^"]+`\1file:/z/nodepkgs/marked`' ./package.json \

View File

@@ -0,0 +1,12 @@
diff --git a/src/js/easymde.js b/src/js/easymde.js
--- a/src/js/easymde.js
+++ b/src/js/easymde.js
@@ -1962,7 +1962,7 @@ EasyMDE.prototype.markdown = function (text) {
marked.setOptions(markedOptions);
// Convert the markdown to HTML
- var htmlText = marked(text);
+ var htmlText = marked.parse(text);
// Sanitize HTML
if (this.options.renderingConfig && typeof this.options.renderingConfig.sanitizerFunction === 'function') {

View File

@@ -1,15 +1,15 @@
diff --git a/src/Lexer.js b/src/Lexer.js
adds linetracking to marked.js v3.0.4;
adds linetracking to marked.js v4.0.6;
add data-ln="%d" to most tags, %d is the source markdown line
--- a/src/Lexer.js
+++ b/src/Lexer.js
@@ -50,4 +50,5 @@ function mangle(text) {
module.exports = class Lexer {
export class Lexer {
constructor(options) {
+ this.ln = 1; // like most editors, start couting from 1
this.tokens = [];
this.tokens.links = Object.create(null);
@@ -127,4 +128,15 @@ module.exports = class Lexer {
@@ -127,4 +128,15 @@ export class Lexer {
}
+ set_ln(token, ln = this.ln) {
@@ -25,7 +25,7 @@ add data-ln="%d" to most tags, %d is the source markdown line
+
/**
* Lexing
@@ -134,7 +146,11 @@ module.exports = class Lexer {
@@ -134,7 +146,11 @@ export class Lexer {
src = src.replace(/^ +$/gm, '');
}
- let token, lastToken, cutSrc, lastParagraphClipped;
@@ -38,105 +38,105 @@ add data-ln="%d" to most tags, %d is the source markdown line
+
if (this.options.extensions
&& this.options.extensions.block
@@ -142,4 +158,5 @@ module.exports = class Lexer {
@@ -142,4 +158,5 @@ export class Lexer {
if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
tokens.push(token);
return true;
@@ -153,4 +170,5 @@ module.exports = class Lexer {
@@ -153,4 +170,5 @@ export class Lexer {
if (token = this.tokenizer.space(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln); // is \n if not type
if (token.type) {
tokens.push(token);
@@ -162,4 +180,5 @@ module.exports = class Lexer {
@@ -162,4 +180,5 @@ export class Lexer {
if (token = this.tokenizer.code(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
lastToken = tokens[tokens.length - 1];
// An indented code block cannot interrupt a paragraph.
@@ -177,4 +196,5 @@ module.exports = class Lexer {
@@ -177,4 +196,5 @@ export class Lexer {
if (token = this.tokenizer.fences(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
tokens.push(token);
continue;
@@ -184,4 +204,5 @@ module.exports = class Lexer {
@@ -184,4 +204,5 @@ export class Lexer {
if (token = this.tokenizer.heading(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
tokens.push(token);
continue;
@@ -191,4 +212,5 @@ module.exports = class Lexer {
@@ -191,4 +212,5 @@ export class Lexer {
if (token = this.tokenizer.hr(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
tokens.push(token);
continue;
@@ -198,4 +220,5 @@ module.exports = class Lexer {
@@ -198,4 +220,5 @@ export class Lexer {
if (token = this.tokenizer.blockquote(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
tokens.push(token);
continue;
@@ -205,4 +228,5 @@ module.exports = class Lexer {
@@ -205,4 +228,5 @@ export class Lexer {
if (token = this.tokenizer.list(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
tokens.push(token);
continue;
@@ -212,4 +236,5 @@ module.exports = class Lexer {
@@ -212,4 +236,5 @@ export class Lexer {
if (token = this.tokenizer.html(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
tokens.push(token);
continue;
@@ -219,4 +244,5 @@ module.exports = class Lexer {
@@ -219,4 +244,5 @@ export class Lexer {
if (token = this.tokenizer.def(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
lastToken = tokens[tokens.length - 1];
if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) {
@@ -236,4 +262,5 @@ module.exports = class Lexer {
@@ -236,4 +262,5 @@ export class Lexer {
if (token = this.tokenizer.table(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
tokens.push(token);
continue;
@@ -243,4 +270,5 @@ module.exports = class Lexer {
@@ -243,4 +270,5 @@ export class Lexer {
if (token = this.tokenizer.lheading(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
tokens.push(token);
continue;
@@ -263,4 +291,5 @@ module.exports = class Lexer {
@@ -263,4 +291,5 @@ export class Lexer {
}
if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {
+ this.set_ln(token, ln);
lastToken = tokens[tokens.length - 1];
if (lastParagraphClipped && lastToken.type === 'paragraph') {
@@ -280,4 +309,6 @@ module.exports = class Lexer {
@@ -280,4 +309,6 @@ export class Lexer {
if (token = this.tokenizer.text(src)) {
src = src.substring(token.raw.length);
+ this.set_ln(token, ln);
+ this.ln++;
lastToken = tokens[tokens.length - 1];
if (lastToken && lastToken.type === 'text') {
@@ -355,4 +386,5 @@ module.exports = class Lexer {
@@ -355,4 +386,5 @@ export class Lexer {
if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
src = src.substring(token.raw.length);
+ this.ln = token.ln || this.ln;
tokens.push(token);
return true;
@@ -420,4 +452,6 @@ module.exports = class Lexer {
@@ -420,4 +452,6 @@ export class Lexer {
if (token = this.tokenizer.br(src)) {
src = src.substring(token.raw.length);
+ // no need to reset (no more blockTokens anyways)
+ token.ln = this.ln++;
tokens.push(token);
continue;
@@ -462,4 +496,5 @@ module.exports = class Lexer {
@@ -462,4 +496,5 @@ export class Lexer {
if (token = this.tokenizer.inlineText(cutSrc, smartypants)) {
src = src.substring(token.raw.length);
+ this.ln = token.ln || this.ln;
@@ -145,13 +145,13 @@ add data-ln="%d" to most tags, %d is the source markdown line
diff --git a/src/Parser.js b/src/Parser.js
--- a/src/Parser.js
+++ b/src/Parser.js
@@ -18,4 +18,5 @@ module.exports = class Parser {
@@ -18,4 +18,5 @@ export class Parser {
this.textRenderer = new TextRenderer();
this.slugger = new Slugger();
+ this.ln = 0; // error indicator; should always be set >=1 from tokens
}
@@ -64,4 +65,8 @@ module.exports = class Parser {
@@ -64,4 +65,8 @@ export class Parser {
for (i = 0; i < l; i++) {
token = tokens[i];
+ // take line-numbers from tokens whenever possible
@@ -160,7 +160,7 @@ diff --git a/src/Parser.js b/src/Parser.js
+ this.renderer.tag_ln(this.ln);
// Run any renderer extensions
@@ -124,7 +129,10 @@ module.exports = class Parser {
@@ -124,7 +129,10 @@ export class Parser {
}
- body += this.renderer.tablerow(cell);
@@ -173,7 +173,7 @@ diff --git a/src/Parser.js b/src/Parser.js
+ out += this.renderer.tag_ln(token.ln).table(header, body);
continue;
}
@@ -167,8 +175,12 @@ module.exports = class Parser {
@@ -167,8 +175,12 @@ export class Parser {
itemBody += this.parse(item.tokens, loose);
- body += this.renderer.listitem(itemBody, task, checked);
@@ -188,7 +188,7 @@ diff --git a/src/Parser.js b/src/Parser.js
+ out += this.renderer.tag_ln(token.ln).list(body, ordered, start);
continue;
}
@@ -179,5 +191,6 @@ module.exports = class Parser {
@@ -179,5 +191,6 @@ export class Parser {
}
case 'paragraph': {
- out += this.renderer.paragraph(this.parseInline(token.tokens));
@@ -196,7 +196,7 @@ diff --git a/src/Parser.js b/src/Parser.js
+ out += this.renderer.tag_ln(token.ln).paragraph(t);
continue;
}
@@ -221,4 +234,7 @@ module.exports = class Parser {
@@ -221,4 +234,7 @@ export class Parser {
token = tokens[i];
+ // another thing that only affects <br/> and other inlines
@@ -207,7 +207,7 @@ diff --git a/src/Parser.js b/src/Parser.js
diff --git a/src/Renderer.js b/src/Renderer.js
--- a/src/Renderer.js
+++ b/src/Renderer.js
@@ -11,6 +11,12 @@ module.exports = class Renderer {
@@ -11,6 +11,12 @@ export class Renderer {
constructor(options) {
this.options = options || defaults;
+ this.ln = "";
@@ -220,7 +220,7 @@ diff --git a/src/Renderer.js b/src/Renderer.js
+
code(code, infostring, escaped) {
const lang = (infostring || '').match(/\S*/)[0];
@@ -26,10 +32,10 @@ module.exports = class Renderer {
@@ -26,10 +32,10 @@ export class Renderer {
if (!lang) {
- return '<pre><code>'
@@ -233,55 +233,55 @@ diff --git a/src/Renderer.js b/src/Renderer.js
+ return '<pre' + this.ln + '><code class="'
+ this.options.langPrefix
+ escape(lang, true)
@@ -40,5 +46,5 @@ module.exports = class Renderer {
@@ -40,5 +46,5 @@ export class Renderer {
blockquote(quote) {
- return '<blockquote>\n' + quote + '</blockquote>\n';
+ return '<blockquote' + this.ln + '>\n' + quote + '</blockquote>\n';
}
@@ -51,4 +57,5 @@ module.exports = class Renderer {
@@ -51,4 +57,5 @@ export class Renderer {
return '<h'
+ level
+ + this.ln
+ ' id="'
+ this.options.headerPrefix
@@ -61,5 +68,5 @@ module.exports = class Renderer {
@@ -61,5 +68,5 @@ export class Renderer {
}
// ignore IDs
- return '<h' + level + '>' + text + '</h' + level + '>\n';
+ return '<h' + level + this.ln + '>' + text + '</h' + level + '>\n';
}
@@ -75,5 +82,5 @@ module.exports = class Renderer {
@@ -75,5 +82,5 @@ export class Renderer {
listitem(text) {
- return '<li>' + text + '</li>\n';
+ return '<li' + this.ln + '>' + text + '</li>\n';
}
@@ -87,5 +94,5 @@ module.exports = class Renderer {
@@ -87,5 +94,5 @@ export class Renderer {
paragraph(text) {
- return '<p>' + text + '</p>\n';
+ return '<p' + this.ln + '>' + text + '</p>\n';
}
@@ -102,5 +109,5 @@ module.exports = class Renderer {
@@ -102,5 +109,5 @@ export class Renderer {
tablerow(content) {
- return '<tr>\n' + content + '</tr>\n';
+ return '<tr' + this.ln + '>\n' + content + '</tr>\n';
}
@@ -127,5 +134,5 @@ module.exports = class Renderer {
@@ -127,5 +134,5 @@ export class Renderer {
br() {
- return this.options.xhtml ? '<br/>' : '<br>';
+ return this.options.xhtml ? '<br' + this.ln + '/>' : '<br' + this.ln + '>';
}
@@ -153,5 +160,5 @@ module.exports = class Renderer {
@@ -153,5 +160,5 @@ export class Renderer {
}
- let out = '<img src="' + href + '" alt="' + text + '"';
@@ -291,7 +291,7 @@ diff --git a/src/Renderer.js b/src/Renderer.js
diff --git a/src/Tokenizer.js b/src/Tokenizer.js
--- a/src/Tokenizer.js
+++ b/src/Tokenizer.js
@@ -301,4 +301,7 @@ module.exports = class Tokenizer {
@@ -297,4 +297,7 @@ export class Tokenizer {
const l = list.items.length;
+ // each nested list gets +1 ahead; this hack makes every listgroup -1 but atleast it doesn't get infinitely bad

View File

@@ -1,7 +1,7 @@
diff --git a/src/Lexer.js b/src/Lexer.js
--- a/src/Lexer.js
+++ b/src/Lexer.js
@@ -6,5 +6,5 @@ const { repeatString } = require('./helpers.js');
@@ -6,5 +6,5 @@ import { repeatString } from './helpers.js';
/**
* smartypants text replacement
- */
@@ -15,21 +15,21 @@ diff --git a/src/Lexer.js b/src/Lexer.js
+ *
function mangle(text) {
let out = '',
@@ -465,5 +465,5 @@ module.exports = class Lexer {
@@ -466,5 +466,5 @@ export class Lexer {
// autolink
- if (token = this.tokenizer.autolink(src, mangle)) {
+ if (token = this.tokenizer.autolink(src)) {
src = src.substring(token.raw.length);
tokens.push(token);
@@ -472,5 +472,5 @@ module.exports = class Lexer {
@@ -473,5 +473,5 @@ export class Lexer {
// url (gfm)
- if (!this.state.inLink && (token = this.tokenizer.url(src, mangle))) {
+ if (!this.state.inLink && (token = this.tokenizer.url(src))) {
src = src.substring(token.raw.length);
tokens.push(token);
@@ -493,5 +493,5 @@ module.exports = class Lexer {
@@ -494,5 +494,5 @@ export class Lexer {
}
}
- if (token = this.tokenizer.inlineText(cutSrc, smartypants)) {
@@ -39,14 +39,14 @@ diff --git a/src/Lexer.js b/src/Lexer.js
diff --git a/src/Renderer.js b/src/Renderer.js
--- a/src/Renderer.js
+++ b/src/Renderer.js
@@ -142,5 +142,5 @@ module.exports = class Renderer {
@@ -142,5 +142,5 @@ export class Renderer {
link(href, title, text) {
- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
+ href = cleanUrl(this.options.baseUrl, href);
if (href === null) {
return text;
@@ -155,5 +155,5 @@ module.exports = class Renderer {
@@ -155,5 +155,5 @@ export class Renderer {
image(href, title, text) {
- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
@@ -56,7 +56,7 @@ diff --git a/src/Renderer.js b/src/Renderer.js
diff --git a/src/Tokenizer.js b/src/Tokenizer.js
--- a/src/Tokenizer.js
+++ b/src/Tokenizer.js
@@ -321,14 +321,7 @@ module.exports = class Tokenizer {
@@ -320,14 +320,7 @@ export class Tokenizer {
type: 'html',
raw: cap[0],
- pre: !this.options.sanitizer
@@ -72,7 +72,7 @@ diff --git a/src/Tokenizer.js b/src/Tokenizer.js
- }
return token;
}
@@ -477,15 +470,9 @@ module.exports = class Tokenizer {
@@ -476,15 +469,9 @@ export class Tokenizer {
return {
- type: this.options.sanitize
@@ -90,7 +90,7 @@ diff --git a/src/Tokenizer.js b/src/Tokenizer.js
+ text: cap[0]
};
}
@@ -672,10 +659,10 @@ module.exports = class Tokenizer {
@@ -671,10 +658,10 @@ export class Tokenizer {
}
- autolink(src, mangle) {
@@ -103,7 +103,7 @@ diff --git a/src/Tokenizer.js b/src/Tokenizer.js
+ text = escape(cap[1]);
href = 'mailto:' + text;
} else {
@@ -700,10 +687,10 @@ module.exports = class Tokenizer {
@@ -699,10 +686,10 @@ export class Tokenizer {
}
- url(src, mangle) {
@@ -116,7 +116,7 @@ diff --git a/src/Tokenizer.js b/src/Tokenizer.js
+ text = escape(cap[0]);
href = 'mailto:' + text;
} else {
@@ -737,12 +724,12 @@ module.exports = class Tokenizer {
@@ -736,12 +723,12 @@ export class Tokenizer {
}
- inlineText(src, smartypants) {
@@ -135,7 +135,7 @@ diff --git a/src/Tokenizer.js b/src/Tokenizer.js
diff --git a/src/defaults.js b/src/defaults.js
--- a/src/defaults.js
+++ b/src/defaults.js
@@ -9,12 +9,8 @@ function getDefaults() {
@@ -9,12 +9,8 @@ export function getDefaults() {
highlight: null,
langPrefix: 'language-',
- mangle: true,
@@ -151,10 +151,10 @@ diff --git a/src/defaults.js b/src/defaults.js
diff --git a/src/helpers.js b/src/helpers.js
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -64,18 +64,5 @@ function edit(regex, opt) {
@@ -64,18 +64,5 @@ export function edit(regex, opt) {
const nonWordAndColonTest = /[^\w:]/g;
const originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
-function cleanUrl(sanitize, base, href) {
-export function cleanUrl(sanitize, base, href) {
- if (sanitize) {
- let prot;
- try {
@@ -168,36 +168,30 @@ diff --git a/src/helpers.js b/src/helpers.js
- return null;
- }
- }
+function cleanUrl(base, href) {
+export function cleanUrl(base, href) {
if (base && !originIndependentUrl.test(href)) {
href = resolveUrl(base, href);
@@ -227,10 +214,4 @@ function findClosingBracket(str, b) {
@@ -227,10 +214,4 @@ export function findClosingBracket(str, b) {
}
-function checkSanitizeDeprecation(opt) {
-export function checkSanitizeDeprecation(opt) {
- if (opt && opt.sanitize && !opt.silent) {
- console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options');
- }
-}
-
// copied from https://stackoverflow.com/a/5450113/806777
function repeatString(pattern, count) {
@@ -260,5 +241,4 @@ module.exports = {
rtrim,
findClosingBracket,
- checkSanitizeDeprecation,
repeatString
};
export function repeatString(pattern, count) {
diff --git a/src/marked.js b/src/marked.js
--- a/src/marked.js
+++ b/src/marked.js
@@ -7,5 +7,4 @@ const Slugger = require('./Slugger.js');
const {
@@ -7,5 +7,4 @@ import { Slugger } from './Slugger.js';
import {
merge,
- checkSanitizeDeprecation,
escape
} = require('./helpers.js');
@@ -35,5 +34,4 @@ function marked(src, opt, callback) {
} from './helpers.js';
@@ -35,5 +34,4 @@ export function marked(src, opt, callback) {
opt = merge({}, marked.defaults, opt || {});
- checkSanitizeDeprecation(opt);
@@ -219,37 +213,37 @@ diff --git a/src/marked.js b/src/marked.js
diff --git a/test/bench.js b/test/bench.js
--- a/test/bench.js
+++ b/test/bench.js
@@ -33,5 +33,4 @@ async function runBench(options) {
@@ -37,5 +37,4 @@ export async function runBench(options) {
breaks: false,
pedantic: false,
- sanitize: false,
smartLists: false
});
@@ -45,5 +44,4 @@ async function runBench(options) {
@@ -49,5 +48,4 @@ export async function runBench(options) {
breaks: false,
pedantic: false,
- sanitize: false,
smartLists: false
});
@@ -58,5 +56,4 @@ async function runBench(options) {
@@ -62,5 +60,4 @@ export async function runBench(options) {
breaks: false,
pedantic: false,
- sanitize: false,
smartLists: false
});
@@ -70,5 +67,4 @@ async function runBench(options) {
@@ -74,5 +71,4 @@ export async function runBench(options) {
breaks: false,
pedantic: false,
- sanitize: false,
smartLists: false
});
@@ -83,5 +79,4 @@ async function runBench(options) {
@@ -87,5 +83,4 @@ export async function runBench(options) {
breaks: false,
pedantic: true,
- sanitize: false,
smartLists: false
});
@@ -95,5 +90,4 @@ async function runBench(options) {
@@ -99,5 +94,4 @@ export async function runBench(options) {
breaks: false,
pedantic: true,
- sanitize: false,
@@ -258,7 +252,7 @@ diff --git a/test/bench.js b/test/bench.js
diff --git a/test/specs/run-spec.js b/test/specs/run-spec.js
--- a/test/specs/run-spec.js
+++ b/test/specs/run-spec.js
@@ -22,9 +22,4 @@ function runSpecs(title, dir, showCompletionTable, options) {
@@ -25,9 +25,4 @@ function runSpecs(title, dir, showCompletionTable, options) {
}
- if (spec.options.sanitizer) {
@@ -268,77 +262,77 @@ diff --git a/test/specs/run-spec.js b/test/specs/run-spec.js
-
(spec.only ? fit : (spec.skip ? xit : it))('should ' + passFail + example, async() => {
const before = process.hrtime();
@@ -53,3 +48,2 @@ runSpecs('Original', './original', false, { gfm: false, pedantic: true });
@@ -56,3 +51,2 @@ runSpecs('Original', './original', false, { gfm: false, pedantic: true });
runSpecs('New', './new');
runSpecs('ReDOS', './redos');
-runSpecs('Security', './security', false, { silent: true }); // silent - do not show deprecation warning
diff --git a/test/unit/Lexer-spec.js b/test/unit/Lexer-spec.js
--- a/test/unit/Lexer-spec.js
+++ b/test/unit/Lexer-spec.js
@@ -589,5 +589,5 @@ paragraph
@@ -635,5 +635,5 @@ paragraph
});
- it('sanitize', () => {
+ /*it('sanitize', () => {
expectTokens({
md: '<div>html</div>',
@@ -607,5 +607,5 @@ paragraph
@@ -653,5 +653,5 @@ paragraph
]
});
- });
+ });*/
});
@@ -652,5 +652,5 @@ paragraph
@@ -698,5 +698,5 @@ paragraph
});
- it('html sanitize', () => {
+ /*it('html sanitize', () => {
expectInlineTokens({
md: '<div>html</div>',
@@ -660,5 +660,5 @@ paragraph
@@ -706,5 +706,5 @@ paragraph
]
});
- });
+ });*/
it('link', () => {
@@ -971,5 +971,5 @@ paragraph
@@ -1017,5 +1017,5 @@ paragraph
});
- it('autolink mangle email', () => {
+ /*it('autolink mangle email', () => {
expectInlineTokens({
md: '<test@example.com>',
@@ -991,5 +991,5 @@ paragraph
@@ -1037,5 +1037,5 @@ paragraph
]
});
- });
+ });*/
it('url', () => {
@@ -1028,5 +1028,5 @@ paragraph
@@ -1074,5 +1074,5 @@ paragraph
});
- it('url mangle email', () => {
+ /*it('url mangle email', () => {
expectInlineTokens({
md: 'test@example.com',
@@ -1048,5 +1048,5 @@ paragraph
@@ -1094,5 +1094,5 @@ paragraph
]
});
- });
+ });*/
});
@@ -1064,5 +1064,5 @@ paragraph
@@ -1110,5 +1110,5 @@ paragraph
});
- describe('smartypants', () => {
+ /*describe('smartypants', () => {
it('single quotes', () => {
expectInlineTokens({
@@ -1134,5 +1134,5 @@ paragraph
@@ -1180,5 +1180,5 @@ paragraph
});
});
- });

View File

@@ -86,8 +86,6 @@ function have() {
python -c "import $1; $1; $1.__version__"
}
mv copyparty/web/deps/marked.full.js.gz srv/ || true
. buildenv/bin/activate
have setuptools
have wheel

View File

@@ -35,8 +35,6 @@ ver="$1"
exit 1
}
mv copyparty/web/deps/marked.full.js.gz srv/ || true
mkdir -p dist
zip_path="$(pwd)/dist/copyparty-$ver.zip"
tgz_path="$(pwd)/dist/copyparty-$ver.tar.gz"

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env python3
# coding: latin-1
from __future__ import print_function, unicode_literals
import re, os, sys, time, shutil, signal, threading, tarfile, hashlib, platform, tempfile, traceback
import subprocess as sp
"""
to edit this file, use HxD or "vim -b"
(there is compressed stuff at the end)
@@ -20,6 +20,7 @@ the archive data is attached after the b"\n# eof\n" archive marker,
b"\n# " decodes to b""
"""
# set by make-sfx.sh
VER = None
SIZE = None

View File

@@ -29,6 +29,9 @@ class Cfg(Namespace):
v=v or [],
c=c,
rproxy=0,
rsp_slp=0,
s_wr_slp=0,
s_wr_sz=512 * 1024,
ed=False,
nw=False,
unpost=600,
@@ -48,6 +51,7 @@ class Cfg(Namespace):
mte="a",
mth="",
textfiles="",
doctitle="",
hist=None,
no_idx=None,
no_hash=None,

View File

@@ -23,6 +23,7 @@ class Cfg(Namespace):
"mtp": [],
"mte": "a",
"mth": "",
"doctitle": "",
"hist": None,
"no_idx": None,
"no_hash": None,
@@ -31,6 +32,9 @@ class Cfg(Namespace):
"no_voldump": True,
"re_maxage": 0,
"rproxy": 0,
"rsp_slp": 0,
"s_wr_slp": 0,
"s_wr_sz": 512 * 1024,
}
ex.update(ex2)
super(Cfg, self).__init__(a=a or [], v=v or [], c=c, **ex)