Compare commits

...

63 Commits

Author SHA1 Message Date
ed
96ceccd12a v1.2.3 2022-03-24 02:35:53 +01:00
ed
87994fe006 retry failed uploads with backoff 2022-03-24 02:29:59 +01:00
ed
fa12c81a03 zip-download files older than 1980-01-01 2022-03-24 01:31:50 +01:00
ed
344ce63455 basic-browser is implicitly not js 2022-03-21 01:20:47 +01:00
ed
ec4daacf9e v1.2.2 2022-03-20 06:15:57 +01:00
ed
f3e8308718 eh, better as volflags 2022-03-20 05:45:07 +01:00
ed
515ac5d941 show textfile name in document title 2022-03-20 03:40:21 +01:00
ed
954c7e7e50 add option to request noindex from crawlers 2022-03-20 03:23:42 +01:00
ed
67ff57f3a3 add option to disable html folder listings 2022-03-20 02:45:53 +01:00
ed
c10c70c1e5 misc 2022-03-04 21:30:31 +01:00
ed
04592a98d2 include all IPs + link status in server url listing 2022-03-04 21:29:28 +01:00
ed
c9c4aac6cf v1.2.1 2022-03-03 01:26:29 +01:00
ed
8b2c7586ce minimal py2 support for ftpd 2022-03-03 01:18:01 +01:00
ed
32e22dfe84 vendor asynchat for pyftpdlib 2022-03-03 01:16:52 +01:00
ed
d70b885722 failed attempt at upgrading scp 2022-03-03 00:17:03 +01:00
ed
ac6c4b13f5 add plaintext volume listing 2022-03-02 21:20:19 +01:00
ed
ececdad22d and increase debounce a bit 2022-03-02 01:56:05 +01:00
ed
bf659781b0 try some more spacing 2022-03-02 01:49:15 +01:00
ed
2c6bb195a4 search: get rid of inner-joins to fix -tags 2022-03-02 00:35:04 +01:00
ed
c032cd08b3 prisonparty: clean exit on sigterm/int 2022-02-27 20:07:28 +01:00
ed
39e7a7a231 sfx: prefer system pyftpdlib if available 2022-02-13 21:00:13 +01:00
ed
6e14cd2c39 graduate copyparty-sfx.sh 2022-02-13 20:44:03 +01:00
ed
aab3baaea7 v1.2.0 2022-02-13 16:58:54 +01:00
ed
b8453c3b4f ftpd: support rootless filesystems 2022-02-13 16:38:24 +01:00
ed
6ce0e2cd5b ftpd: add ftps 2022-02-13 15:46:33 +01:00
ed
76beaae7f2 ftpd: add move/rename 2022-02-13 14:26:16 +01:00
ed
c1a7f9edbe ftpd: add indexing, delete, windows support 2022-02-13 13:58:16 +01:00
ed
b5f2fe2f0a add ftpd 2022-02-13 03:10:53 +01:00
ed
98a90d49cb ctrl-click document links to open in new tab 2022-02-12 20:26:44 +01:00
ed
f55e982cb5 configurable max-hits 2022-02-12 16:22:35 +01:00
ed
686c7defeb fix path-search in nontop volumes 2022-02-12 16:00:14 +01:00
ed
0b1e483c53 bump webdeps 2022-02-09 23:45:09 +01:00
ed
457d7df129 fix ie11 hotkey crash 2022-02-06 02:08:18 +01:00
ed
ce776a547c add rate throttling to uploads too 2022-02-06 02:06:59 +01:00
ed
ded0567cbf v1.1.12 2022-01-18 22:28:33 +01:00
ed
c9cac83d09 fix PUT response in write-only folders 2022-01-18 21:37:11 +01:00
ed
4fbe6b01a8 clarify what the app does 2022-01-17 00:31:23 +00:00
ed
ee9585264e deal with github api change + build vamp if necessary 2022-01-17 00:27:23 +00:00
ed
c9ffead7bf prisonparty: support running from src 2022-01-17 00:24:40 +00:00
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
57 changed files with 2266 additions and 417 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) 📷 **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>
(the app is **NOT** the full copyparty server! just a basic upload client, nothing fancy yet)
## readme toc ## readme toc
* top * top
@@ -47,6 +54,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
* [other tricks](#other-tricks) * [other tricks](#other-tricks)
* [searching](#searching) - search by size, date, path/name, mp3-tags, ... * [searching](#searching) - search by size, date, path/name, mp3-tags, ...
* [server config](#server-config) - using arguments or config files, or a mix of both * [server config](#server-config) - using arguments or config files, or a mix of both
* [ftp-server](#ftp-server) - an FTP server can be started using `--ftp 3921`
* [file indexing](#file-indexing) * [file indexing](#file-indexing)
* [upload rules](#upload-rules) - set upload rules using volume flags * [upload rules](#upload-rules) - set upload rules using volume flags
* [compress uploads](#compress-uploads) - files can be autocompressed on upload * [compress uploads](#compress-uploads) - files can be autocompressed on upload
@@ -54,6 +62,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload * [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
* [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md) * [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md)
* [upload events](#upload-events) - trigger a script/program on each upload * [upload events](#upload-events) - trigger a script/program on each upload
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
* [complete examples](#complete-examples) * [complete examples](#complete-examples)
* [browser support](#browser-support) - TLDR: yes * [browser support](#browser-support) - TLDR: yes
* [client examples](#client-examples) - interact with copyparty using non-browser clients * [client examples](#client-examples) - interact with copyparty using non-browser clients
@@ -75,7 +84,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
* [optional dependencies](#optional-dependencies) - install these to enable bonus features * [optional dependencies](#optional-dependencies) - install these to enable bonus features
* [install recommended deps](#install-recommended-deps) * [install recommended deps](#install-recommended-deps)
* [optional gpl stuff](#optional-gpl-stuff) * [optional gpl stuff](#optional-gpl-stuff)
* [sfx](#sfx) - there are two self-contained "binaries" * [sfx](#sfx) - the self-contained "binary"
* [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features * [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features
* [install on android](#install-on-android) * [install on android](#install-on-android)
* [reporting bugs](#reporting-bugs) - ideas for context to include in bug reports * [reporting bugs](#reporting-bugs) - ideas for context to include in bug reports
@@ -147,6 +156,7 @@ feature summary
* ☑ multiprocessing (actual multithreading) * ☑ multiprocessing (actual multithreading)
* ☑ volumes (mountpoints) * ☑ volumes (mountpoints)
* ☑ [accounts](#accounts-and-volumes) * ☑ [accounts](#accounts-and-volumes)
* ☑ [ftp-server](#ftp-server)
* upload * upload
* ☑ basic: plain multipart, ie6 support * ☑ basic: plain multipart, ie6 support
* ☑ [up2k](#uploading): js, resumable, multithreaded * ☑ [up2k](#uploading): js, resumable, multithreaded
@@ -230,6 +240,8 @@ some improvement ideas
## general bugs ## general bugs
* Windows: if the up2k db is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
* use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise * all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
* probably more, pls let me know * probably more, pls let me know
@@ -449,7 +461,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) ![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 **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
@@ -614,6 +626,18 @@ using arguments or config files, or a mix of both:
* or click the `[reload cfg]` button in the control-panel when logged in as admin * or click the `[reload cfg]` button in the control-panel when logged in as admin
## ftp-server
an FTP server can be started using `--ftp 3921`, and/or `--ftps` for explicit TLS (ftpes)
* based on [pyftpdlib](https://github.com/giampaolo/pyftpdlib)
* needs a dedicated port (cannot share with the HTTP/HTTPS API)
* uploads are not resumable -- delete and restart if necessary
* runs in active mode by default, you probably want `--ftp-pr 12000-13000`
* if you enable both `ftp` and `ftps`, the port-range will be divided in half
* some older software (filezilla on debian-stable) cannot passive-mode with TLS
## file indexing ## file indexing
file indexing relies on two database tables, the up2k filetree (`-e2d`) and the metadata tags (`-e2t`), stored in `.hist/up2k.db`. Configuration can be done through arguments, volume flags, or a mix of both. file indexing relies on two database tables, the up2k filetree (`-e2d`) and the metadata tags (`-e2t`), stored in `.hist/up2k.db`. Configuration can be done through arguments, volume flags, or a mix of both.
@@ -769,6 +793,17 @@ and it will occupy the parsing threads, so fork anything expensive, or if you wa
if this becomes popular maybe there should be a less janky way to do it actually if this becomes popular maybe there should be a less janky way to do it actually
## hiding from google
tell search engines you dont wanna be indexed, either using the good old [robots.txt](https://www.robotstxt.org/robotstxt.html) or through copyparty settings:
* `--no-robots` adds HTTP (`X-Robots-Tag`) and HTML (`<meta>`) headers with `noindex, nofollow` globally
* volume-flag `[...]:c,norobots` does the same thing for that single volume
* volume-flag `[...]:c,robots` ALLOWS search-engine crawling for that volume, even if `--no-robots` is set globally
also, `--force-js` disables the plain HTML folder listing, making things harder to parse for search engines
## complete examples ## complete examples
* read-only music server with bpm and key scanning * read-only music server with bpm and key scanning
@@ -1052,6 +1087,10 @@ mandatory deps:
install these to enable bonus features install these to enable bonus features
enable ftp-server:
* for just plaintext FTP, `pyftpdlib` (is built into the SFX)
* with TLS encryption, `pyftpdlib pyopenssl`
enable music tags: enable music tags:
* either `mutagen` (fast, pure-python, skips a few tags, makes copyparty GPL? idk) * either `mutagen` (fast, pure-python, skips a few tags, makes copyparty GPL? idk)
* or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users) * or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
@@ -1078,13 +1117,7 @@ these are standalone programs and will never be imported / evaluated by copypart
# sfx # sfx
there are two self-contained "binaries": the self-contained "binary" [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) will unpack itself and run copyparty, assuming you have python installed of course
* [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) -- pure python, works everywhere, **recommended**
* [copyparty-sfx.sh](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.sh) -- smaller, but only for linux and macos, kinda deprecated
launch either of them (**use sfx.py on systemd**) and it'll unpack and run copyparty, assuming you have python installed of course
pls note that `copyparty-sfx.sh` will fail if you rename `copyparty-sfx.py` to `copyparty.py` and keep it in the same folder because `sys.path` is funky
## sfx repack ## sfx repack
@@ -1159,8 +1192,8 @@ mv /tmp/pe-copyparty/copyparty/web/deps/ copyparty/web/deps/
then build the sfx using any of the following examples: then build the sfx using any of the following examples:
```sh ```sh
./scripts/make-sfx.sh # both python and sh editions ./scripts/make-sfx.sh # regular edition
./scripts/make-sfx.sh no-sh gz # just python with gzip ./scripts/make-sfx.sh gz no-cm # gzip-compressed + no fancy markdown editor
``` ```

View File

@@ -5,6 +5,11 @@
* if something breaks just restart it * 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) # [`copyparty-fuse.py`](copyparty-fuse.py)
* mount a copyparty server as a local filesystem (read-only) * mount a copyparty server as a local filesystem (read-only)

View File

@@ -11,14 +11,18 @@ import re
import os import os
import sys import sys
import time import time
import json
import stat import stat
import errno import errno
import struct import struct
import codecs
import platform
import threading import threading
import http.client # py2: httplib import http.client # py2: httplib
import urllib.parse import urllib.parse
from datetime import datetime from datetime import datetime
from urllib.parse import quote_from_bytes as quote from urllib.parse import quote_from_bytes as quote
from urllib.parse import unquote_to_bytes as unquote
try: try:
import fuse import fuse
@@ -38,7 +42,7 @@ except:
mount a copyparty server (local or remote) as a filesystem mount a copyparty server (local or remote) as a filesystem
usage: 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: dependencies:
sudo apk add fuse-dev python3-dev 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): def threadless_log(msg):
print(msg + "\n", end="") 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): class CacheNode(object):
def __init__(self, tag, data): def __init__(self, tag, data):
self.tag = tag self.tag = tag
@@ -115,8 +158,9 @@ class Stat(fuse.Stat):
class Gateway(object): class Gateway(object):
def __init__(self, base_url): def __init__(self, base_url, pw):
self.base_url = base_url self.base_url = base_url
self.pw = pw
ui = urllib.parse.urlparse(base_url) ui = urllib.parse.urlparse(base_url)
self.web_root = ui.path.strip("/") self.web_root = ui.path.strip("/")
@@ -135,8 +179,7 @@ class Gateway(object):
self.conns = {} self.conns = {}
def quotep(self, path): def quotep(self, path):
# TODO: mojibake support path = path.encode("wtf-8")
path = path.encode("utf-8", "ignore")
return quote(path, safe="/") return quote(path, safe="/")
def getconn(self, tid=None): def getconn(self, tid=None):
@@ -159,20 +202,29 @@ class Gateway(object):
except: except:
pass pass
def sendreq(self, *args, **kwargs): def sendreq(self, *args, **ka):
tid = get_tid() tid = get_tid()
if self.pw:
ck = "cppwd=" + self.pw
try:
ka["headers"]["Cookie"] = ck
except:
ka["headers"] = {"Cookie": ck}
try: try:
c = self.getconn(tid) c = self.getconn(tid)
c.request(*list(args), **kwargs) c.request(*list(args), **ka)
return c.getresponse() return c.getresponse()
except: except:
self.closeconn(tid) self.closeconn(tid)
c = self.getconn(tid) c = self.getconn(tid)
c.request(*list(args), **kwargs) c.request(*list(args), **ka)
return c.getresponse() return c.getresponse()
def listdir(self, path): 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) r = self.sendreq("GET", web_path)
if r.status != 200: if r.status != 200:
self.closeconn() 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): def download_file_range(self, path, ofs1, ofs2):
if bad_good:
path = dewin(path)
web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?raw" web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?raw"
hdr_range = "bytes={}-{}".format(ofs1, ofs2 - 1) hdr_range = "bytes={}-{}".format(ofs1, ofs2 - 1)
log("downloading {}".format(hdr_range)) log("downloading {}".format(hdr_range))
@@ -200,40 +255,27 @@ class Gateway(object):
return r.read() return r.read()
def parse_html(self, datasrc): def parse_jls(self, datasrc):
ret = [] rsp = b""
remainder = b""
ptn = re.compile(
r"^<tr><td>(-|DIR)</td><td><a [^>]+>([^<]+)</a></td><td>([^<]+)</td><td>([^<]+)</td></tr>$"
)
while True: while True:
buf = remainder + datasrc.read(4096) buf = datasrc.read(1024 * 32)
# print('[{}]'.format(buf.decode('utf-8')))
if not buf: if not buf:
break break
remainder = b"" rsp += buf
endpos = buf.rfind(b"\n")
if endpos >= 0:
remainder = buf[endpos + 1 :]
buf = buf[:endpos]
lines = buf.decode("utf-8").split("\n") rsp = json.loads(rsp.decode("utf-8"))
for line in lines: ret = []
m = ptn.match(line) for statfun, nodes in [
if not m: [self.stat_dir, rsp["dirs"]],
# print(line) [self.stat_file, rsp["files"]],
continue ]:
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() ret.append([fname, statfun(n["ts"], n["sz"]), 0])
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])
return ret return ret
@@ -262,6 +304,7 @@ class CPPF(Fuse):
Fuse.__init__(self, *args, **kwargs) Fuse.__init__(self, *args, **kwargs)
self.url = None self.url = None
self.pw = None
self.dircache = [] self.dircache = []
self.dircache_mtx = threading.Lock() self.dircache_mtx = threading.Lock()
@@ -271,7 +314,7 @@ class CPPF(Fuse):
def init2(self): def init2(self):
# TODO figure out how python-fuse wanted this to go # 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") info("up")
def clean_dircache(self): def clean_dircache(self):
@@ -536,6 +579,8 @@ class CPPF(Fuse):
def getattr(self, path): def getattr(self, path):
log("getattr [{}]".format(path)) log("getattr [{}]".format(path))
if WINDOWS:
path = enwin(path) # windows occasionally decodes f0xx to xx
path = path.strip("/") path = path.strip("/")
try: try:
@@ -568,9 +613,25 @@ class CPPF(Fuse):
def main(): def main():
time.strptime("19970815", "%Y%m%d") # python#7980 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 = CPPF()
server.parser.add_option(mountopt="url", metavar="BASE_URL", default=None) 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) server.parse(values=server, errex=1)
if not server.url or not str(server.url).startswith("http"): if not server.url or not str(server.url).startswith("http"):
print("\nerror:") print("\nerror:")
@@ -578,7 +639,7 @@ def main():
print(" need argument: mount-path") print(" need argument: mount-path")
print("example:") print("example:")
print( 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) sys.exit(1)

View File

@@ -4,8 +4,8 @@ set -e
# install dependencies for audio-*.py # install dependencies for audio-*.py
# #
# linux/alpine: requires {python3,ffmpeg,fftw}-dev py3-{wheel,pip} py3-numpy{,-dev} vamp-sdk-dev patchelf cmake # linux/alpine: requires gcc g++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-dev py3-{wheel,pip} py3-numpy{,-dev}
# linux/debian: requires libav{codec,device,filter,format,resample,util}-dev {libfftw3,python3}-dev python3-{numpy,pip} vamp-{plugin-sdk,examples} patchelf cmake # linux/debian: requires libav{codec,device,filter,format,resample,util}-dev {libfftw3,python3,libsndfile1}-dev python3-{numpy,pip} vamp-{plugin-sdk,examples} patchelf cmake
# win64: requires msys2-mingw64 environment # win64: requires msys2-mingw64 environment
# macos: requires macports # macos: requires macports
# #
@@ -101,8 +101,11 @@ export -f dl_files
github_tarball() { github_tarball() {
rm -rf g
mkdir g
cd g
dl_text "$1" | dl_text "$1" |
tee json | tee ../json |
( (
# prefer jq if available # prefer jq if available
jq -r '.tarball_url' || jq -r '.tarball_url' ||
@@ -111,8 +114,11 @@ github_tarball() {
awk -F\" '/"tarball_url": "/ {print$4}' awk -F\" '/"tarball_url": "/ {print$4}'
) | ) |
tee /dev/stderr | tee /dev/stderr |
head -n 1 |
tr -d '\r' | tr '\n' '\0' | tr -d '\r' | tr '\n' '\0' |
xargs -0 bash -c 'dl_files "$@"' _ xargs -0 bash -c 'dl_files "$@"' _
mv * ../tgz
cd ..
} }
@@ -127,6 +133,7 @@ gitlab_tarball() {
tr \" '\n' | grep -E '\.tar\.gz$' | head -n 1 tr \" '\n' | grep -E '\.tar\.gz$' | head -n 1
) | ) |
tee /dev/stderr | tee /dev/stderr |
head -n 1 |
tr -d '\r' | tr '\n' '\0' | tr -d '\r' | tr '\n' '\0' |
tee links | tee links |
xargs -0 bash -c 'dl_files "$@"' _ xargs -0 bash -c 'dl_files "$@"' _
@@ -138,10 +145,17 @@ install_keyfinder() {
# use msys2 in mingw-w64 mode # use msys2 in mingw-w64 mode
# pacman -S --needed mingw-w64-x86_64-{ffmpeg,python} # pacman -S --needed mingw-w64-x86_64-{ffmpeg,python}
github_tarball https://api.github.com/repos/mixxxdj/libkeyfinder/releases/latest [ -e $HOME/pe/keyfinder ] && {
echo found a keyfinder build in ~/pe, skipping
return
}
tar -xf mixxxdj-libkeyfinder-* cd "$td"
rm -- *.tar.gz github_tarball https://api.github.com/repos/mixxxdj/libkeyfinder/releases/latest
ls -al
tar -xf tgz
rm tgz
cd mixxxdj-libkeyfinder* cd mixxxdj-libkeyfinder*
h="$HOME" h="$HOME"
@@ -208,6 +222,22 @@ install_vamp() {
$pybin -m pip install --user vamp $pybin -m pip install --user vamp
cd "$td"
echo '#include <vamp-sdk/Plugin.h>' | gcc -x c -c -o /dev/null - || [ -e ~/pe/vamp-sdk ] || {
printf '\033[33mcould not find the vamp-sdk, building from source\033[0m\n'
(dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/2588/vamp-plugin-sdk-2.9.0.tar.gz)
sha512sum -c <(
echo "7ef7f837d19a08048b059e0da408373a7964ced452b290fae40b85d6d70ca9000bcfb3302cd0b4dc76cf2a848528456f78c1ce1ee0c402228d812bd347b6983b -"
) <vamp-plugin-sdk-2.9.0.tar.gz
tar -xf vamp-plugin-sdk-2.9.0.tar.gz
rm -- *.tar.gz
ls -al
cd vamp-plugin-sdk-*
./configure --prefix=$HOME/pe/vamp-sdk
make -j1 install
}
cd "$td"
have_beatroot || { have_beatroot || {
printf '\033[33mcould not find the vamp beatroot plugin, building from source\033[0m\n' printf '\033[33mcould not find the vamp beatroot plugin, building from source\033[0m\n'
(dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/885/beatroot-vamp-v1.0.tar.gz) (dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/885/beatroot-vamp-v1.0.tar.gz)
@@ -215,8 +245,11 @@ install_vamp() {
echo "1f444d1d58ccf565c0adfe99f1a1aa62789e19f5071e46857e2adfbc9d453037bc1c4dcb039b02c16240e9b97f444aaff3afb625c86aa2470233e711f55b6874 -" echo "1f444d1d58ccf565c0adfe99f1a1aa62789e19f5071e46857e2adfbc9d453037bc1c4dcb039b02c16240e9b97f444aaff3afb625c86aa2470233e711f55b6874 -"
) <beatroot-vamp-v1.0.tar.gz ) <beatroot-vamp-v1.0.tar.gz
tar -xf beatroot-vamp-v1.0.tar.gz tar -xf beatroot-vamp-v1.0.tar.gz
rm -- *.tar.gz
cd beatroot-vamp-v1.0 cd beatroot-vamp-v1.0
make -f Makefile.linux -j4 [ -e ~/pe/vamp-sdk ] &&
sed -ri 's`^(CFLAGS :=.*)`\1 -I'$HOME'/pe/vamp-sdk/include`' Makefile.linux
make -f Makefile.linux -j4 LDFLAGS=-L$HOME/pe/vamp-sdk/lib
# /home/ed/vamp /home/ed/.vamp /usr/local/lib/vamp # /home/ed/vamp /home/ed/.vamp /usr/local/lib/vamp
mkdir ~/vamp mkdir ~/vamp
cp -pv beatroot-vamp.* ~/vamp/ cp -pv beatroot-vamp.* ~/vamp/
@@ -230,6 +263,7 @@ install_vamp() {
# not in use because it kinda segfaults, also no windows support # not in use because it kinda segfaults, also no windows support
install_soundtouch() { install_soundtouch() {
cd "$td"
gitlab_tarball https://gitlab.com/api/v4/projects/soundtouch%2Fsoundtouch/releases gitlab_tarball https://gitlab.com/api/v4/projects/soundtouch%2Fsoundtouch/releases
tar -xvf soundtouch-* tar -xvf soundtouch-*

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()

63
bin/prisonparty.sh Normal file → Executable file
View File

@@ -11,10 +11,16 @@ sysdirs=( /bin /lib /lib32 /lib64 /sbin /usr )
help() { cat <<'EOF' help() { cat <<'EOF'
usage: usage:
./prisonparty.sh <ROOTDIR> <UID> <GID> [VOLDIR [VOLDIR...]] -- copyparty-sfx.py [...]" ./prisonparty.sh <ROOTDIR> <UID> <GID> [VOLDIR [VOLDIR...]] -- python3 copyparty-sfx.py [...]"
example: example:
./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- copyparty-sfx.py -v /mnt/nas/music::rwmd" ./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- python3 copyparty-sfx.py -v /mnt/nas/music::rwmd"
example for running straight from source (instead of using an sfx):
PYTHONPATH=$PWD ./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- python3 -um copyparty -v /mnt/nas/music::rwmd"
note that if you have python modules installed as --user (such as bpm/key detectors),
you should add /home/foo/.local as a VOLDIR
EOF EOF
exit 1 exit 1
@@ -35,10 +41,20 @@ while true; do
vols+=( "$(realpath "$v")" ) vols+=( "$(realpath "$v")" )
done done
pybin="$1"; shift pybin="$1"; shift
pybin="$(realpath "$pybin")" pybin="$(command -v "$pybin")"
pyarg=
while true; do
v="$1"
[ "${v:0:1}" = - ] || break
pyarg="$pyarg $v"
shift
done
cpp="$1"; shift cpp="$1"; shift
[ -d "$cpp" ] && cppdir="$PWD" || {
# sfx, not module
cpp="$(realpath "$cpp")" cpp="$(realpath "$cpp")"
cppdir="$(dirname "$cpp")" cppdir="$(dirname "$cpp")"
}
trap - EXIT trap - EXIT
@@ -60,11 +76,10 @@ echo
# remove any trailing slashes # remove any trailing slashes
jail="${jail%/}" jail="${jail%/}"
cppdir="${cppdir%/}"
# bind-mount system directories and volumes # bind-mount system directories and volumes
printf '%s\n' "${sysdirs[@]}" "${vols[@]}" | LC_ALL=C sort | printf '%s\n' "${sysdirs[@]}" "${vols[@]}" | sed -r 's`/$``' | LC_ALL=C sort | uniq |
while IFS= read -r v; do while IFS= read -r v; do
[ -e "$v" ] || { [ -e "$v" ] || {
# printf '\033[1;31mfolder does not exist:\033[0m %s\n' "/$v" # printf '\033[1;31mfolder does not exist:\033[0m %s\n' "/$v"
@@ -72,6 +87,7 @@ while IFS= read -r v; do
} }
i1=$(stat -c%D.%i "$v" 2>/dev/null || echo a) i1=$(stat -c%D.%i "$v" 2>/dev/null || echo a)
i2=$(stat -c%D.%i "$jail$v" 2>/dev/null || echo b) i2=$(stat -c%D.%i "$jail$v" 2>/dev/null || echo b)
# echo "v [$v] i1 [$i1] i2 [$i2]"
[ $i1 = $i2 ] && continue [ $i1 = $i2 ] && continue
mkdir -p "$jail$v" mkdir -p "$jail$v"
@@ -79,21 +95,34 @@ while IFS= read -r v; do
done done
cln() {
rv=$?
# cleanup if not in use
lsof "$jail" | grep -qF "$jail" &&
echo "chroot is in use, will not cleanup" ||
{
mount | grep -F " on $jail" |
awk '{sub(/ type .*/,"");sub(/.* on /,"");print}' |
LC_ALL=C sort -r | tee /dev/stderr | tr '\n' '\0' | xargs -r0 umount
}
exit $rv
}
trap cln EXIT
# create a tmp # create a tmp
mkdir -p "$jail/tmp" mkdir -p "$jail/tmp"
chmod 777 "$jail/tmp" chmod 777 "$jail/tmp"
# run copyparty # run copyparty
/sbin/chroot --userspec=$uid:$gid "$jail" "$pybin" "$cpp" "$@" && rv=0 || rv=$? export HOME=$(getent passwd $uid | cut -d: -f6)
export USER=$(getent passwd $uid | cut -d: -f1)
export LOGNAME="$USER"
# cleanup if not in use #echo "pybin [$pybin]"
lsof "$jail" | grep -qF "$jail" && #echo "pyarg [$pyarg]"
echo "chroot is in use, will not cleanup" || #echo "cpp [$cpp]"
{ chroot --userspec=$uid:$gid "$jail" "$pybin" $pyarg "$cpp" "$@" &
mount | grep -qF " on $jail" | p=$!
awk '{sub(/ type .*/,"");sub(/.* on /,"");print}' | trap 'kill $p' INT TERM
LC_ALL=C sort -r | tee /dev/stderr | tr '\n' '\0' | xargs -r0 umount wait
}
exit $rv

View File

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

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

@@ -445,6 +445,13 @@ def run_argparse(argv, formatter):
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info") ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
ap2.add_argument("--ssl-log", metavar="PATH", type=u, help="log master secrets") ap2.add_argument("--ssl-log", metavar="PATH", type=u, help="log master secrets")
ap2 = ap.add_argument_group('FTP options')
ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on PORT, for example 3921")
ap2.add_argument("--ftps", metavar="PORT", type=int, help="enable FTPS server on PORT, for example 3990")
ap2.add_argument("--ftp-dbg", action="store_true", help="enable debug logging")
ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, help="the NAT address to use for passive connections")
ap2.add_argument("--ftp-pr", metavar="P-P", type=u, help="the range of TCP ports to use for passive connections, for example 12000-13000")
ap2 = ap.add_argument_group('opt-outs') ap2 = ap.add_argument_group('opt-outs')
ap2.add_argument("-nw", action="store_true", help="disable writes (benchmark)") ap2.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows") ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows")
@@ -464,6 +471,8 @@ def run_argparse(argv, formatter):
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings") ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme.md into directory listings") ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme.md into directory listings")
ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)") ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)")
ap2.add_argument("--force-js", action="store_true", help="don't send HTML folder listings, force clients to use the embedded json instead")
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything")
ap2 = ap.add_argument_group('yolo options') ap2 = ap.add_argument_group('yolo options')
ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints") ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints")
@@ -513,6 +522,7 @@ def run_argparse(argv, formatter):
ap2.add_argument("--no-idx", metavar="PTN", type=u, help="regex: disable indexing of matching paths during e2ds folder scans") ap2.add_argument("--no-idx", metavar="PTN", type=u, help="regex: disable indexing of matching paths during e2ds folder scans")
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval, 0=off, can be set per-volume with the 'scan' volflag") ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval, 0=off, can be set per-volume with the 'scan' volflag")
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline") ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
ap2.add_argument("--srch-hits", metavar="N", type=int, default=1000, help="max search results")
ap2 = ap.add_argument_group('metadata db options') ap2 = ap.add_argument_group('metadata db options')
ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing") ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing")
@@ -531,6 +541,7 @@ def run_argparse(argv, formatter):
ap2 = ap.add_argument_group('ui options') ap2 = ap.add_argument_group('ui options')
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include") 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("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages")
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext") 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.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty", help="title / service-name to show in html documents")

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (1, 1, 6) VERSION = (1, 2, 3)
CODENAME = "opus" CODENAME = "ftp btw"
BUILD_DT = (2021, 12, 7) BUILD_DT = (2022, 3, 24)
S_VERSION = ".".join(map(str, VERSION)) S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -14,6 +14,7 @@ from datetime import datetime
from .__init__ import WINDOWS from .__init__ import WINDOWS
from .util import ( from .util import (
IMPLICATIONS, IMPLICATIONS,
META_NOBOTS,
uncyg, uncyg,
undot, undot,
unhumanize, unhumanize,
@@ -394,6 +395,13 @@ class VFS(object):
if ok: if ok:
virt_vis[name] = vn2 virt_vis[name] = vn2
if ".hist" in abspath:
p = abspath.replace("\\", "/") if WINDOWS else abspath
if p.endswith("/.hist"):
real = [x for x in real if not x[0].startswith("up2k.")]
elif "/.hist/th/" in p:
real = [x for x in real if not x[0].endswith("dir.txt")]
return [abspath, real, virt_vis] return [abspath, real, virt_vis]
def walk(self, rel, rem, seen, uname, permsets, dots, scandir, lstat): def walk(self, rel, rem, seen, uname, permsets, dots, scandir, lstat):
@@ -444,10 +452,6 @@ class VFS(object):
if flt: if flt:
flt = {k: True for k in flt} flt = {k: True for k in flt}
f1 = "{0}.hist{0}up2k.".format(os.sep)
f2a = os.sep + "dir.txt"
f2b = "{0}.hist{0}".format(os.sep)
# if multiselect: add all items to archive root # if multiselect: add all items to archive root
# if single folder: the folder itself is the top-level item # if single folder: the folder itself is the top-level item
folder = "" if flt else (vrem.split("/")[-1] or "top") folder = "" if flt else (vrem.split("/")[-1] or "top")
@@ -483,13 +487,6 @@ class VFS(object):
for x in rm: for x in rm:
del vd[x] del vd[x]
# up2k filetring based on actual abspath
files = [
x
for x in files
if f1 not in x[1] and (not x[1].endswith(f2a) or f2b not in x[1])
]
for f in [{"vp": v, "ap": a, "st": n[1]} for v, a, n in files]: for f in [{"vp": v, "ap": a, "st": n[1]} for v, a, n in files]:
yield f yield f
@@ -865,6 +862,19 @@ class AuthSrv(object):
if use: if use:
vol.lim = lim vol.lim = lim
if self.args.no_robots:
for vol in vfs.all_vols.values():
# volflag "robots" overrides global "norobots", allowing indexing by search engines for this vol
if not vol.flags.get("robots"):
vol.flags["norobots"] = True
for vol in vfs.all_vols.values():
h = [vol.flags.get("html_head", self.args.html_head)]
if vol.flags.get("norobots"):
h.insert(0, META_NOBOTS)
vol.flags["html_head"] = "\n".join([x for x in h if x])
for vol in vfs.all_vols.values(): for vol in vfs.all_vols.values():
fk = vol.flags.get("fk") fk = vol.flags.get("fk")
if fk: if fk:

View File

@@ -18,10 +18,6 @@ def listdir(p="."):
return [fsdec(x) for x in os.listdir(fsenc(p))] return [fsdec(x) for x in os.listdir(fsenc(p))]
def lstat(p):
return os.lstat(fsenc(p))
def makedirs(name, mode=0o755, exist_ok=True): def makedirs(name, mode=0o755, exist_ok=True):
bname = fsenc(name) bname = fsenc(name)
try: try:
@@ -60,3 +56,12 @@ def utime(p, times=None, follow_symlinks=True):
return os.utime(fsenc(p), times, follow_symlinks=follow_symlinks) return os.utime(fsenc(p), times, follow_symlinks=follow_symlinks)
else: else:
return os.utime(fsenc(p), times) return os.utime(fsenc(p), times)
if hasattr(os, "lstat"):
def lstat(p):
return os.lstat(fsenc(p))
else:
lstat = stat

View File

@@ -36,5 +36,9 @@ def islink(p):
return os.path.islink(fsenc(p)) return os.path.islink(fsenc(p))
def lexists(p):
return os.path.lexists(fsenc(p))
def realpath(p): def realpath(p):
return fsdec(os.path.realpath(fsenc(p))) return fsdec(os.path.realpath(fsenc(p)))

374
copyparty/ftpd.py Normal file
View File

@@ -0,0 +1,374 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
import os
import sys
import stat
import time
import logging
import threading
from .__init__ import E, PY2
from .util import Pebkac, fsenc, exclude_dotfiles
from .bos import bos
try:
from pyftpdlib.ioloop import IOLoop
except ImportError:
p = os.path.join(E.mod, "vend")
print("loading asynchat from " + p)
sys.path.append(p)
from pyftpdlib.ioloop import IOLoop
from pyftpdlib.authorizers import DummyAuthorizer, AuthenticationFailed
from pyftpdlib.filesystems import AbstractedFS, FilesystemError
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
from pyftpdlib.log import config_logging
try:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .svchub import SvcHub
except ImportError:
pass
class FtpAuth(DummyAuthorizer):
def __init__(self):
super(FtpAuth, self).__init__()
self.hub = None # type: SvcHub
def validate_authentication(self, username, password, handler):
asrv = self.hub.asrv
if username == "anonymous":
password = ""
uname = "*"
if password:
uname = asrv.iacct.get(password, None)
handler.username = uname
if password and not uname:
raise AuthenticationFailed("Authentication failed.")
def get_home_dir(self, username):
return "/"
def has_user(self, username):
asrv = self.hub.asrv
return username in asrv.acct
def has_perm(self, username, perm, path=None):
return True # handled at filesystem layer
def get_perms(self, username):
return "elradfmwMT"
def get_msg_login(self, username):
return "sup {}".format(username)
def get_msg_quit(self, username):
return "cya"
class FtpFs(AbstractedFS):
def __init__(self, root, cmd_channel):
self.h = self.cmd_channel = cmd_channel # type: FTPHandler
self.hub = cmd_channel.hub # type: SvcHub
self.args = cmd_channel.args
self.uname = self.hub.asrv.iacct.get(cmd_channel.password, "*")
self.cwd = "/" # pyftpdlib convention of leading slash
self.root = "/var/lib/empty"
self.listdirinfo = self.listdir
self.chdir(".")
def v2a(self, vpath, r=False, w=False, m=False, d=False):
try:
vpath = vpath.replace("\\", "/").lstrip("/")
vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
if not vfs.realpath:
raise FilesystemError("no filesystem mounted at this path")
return os.path.join(vfs.realpath, rem)
except Pebkac as ex:
raise FilesystemError(str(ex))
def rv2a(self, vpath, r=False, w=False, m=False, d=False):
return self.v2a(os.path.join(self.cwd, vpath), r, w, m, d)
def ftp2fs(self, ftppath):
# return self.v2a(ftppath)
return ftppath # self.cwd must be vpath
def fs2ftp(self, fspath):
# raise NotImplementedError()
return fspath
def validpath(self, path):
if "/.hist/" in path:
if "/up2k." in path or path.endswith("/dir.txt"):
raise FilesystemError("access to this file is forbidden")
return True
def open(self, filename, mode):
r = "r" in mode
w = "w" in mode or "a" in mode or "+" in mode
ap = self.rv2a(filename, r, w)
if w and bos.path.exists(ap):
raise FilesystemError("cannot open existing file for writing")
self.validpath(ap)
return open(fsenc(ap), mode)
def chdir(self, path):
self.cwd = join(self.cwd, path)
x = self.hub.asrv.vfs.can_access(self.cwd.lstrip("/"), self.h.username)
self.can_read, self.can_write, self.can_move, self.can_delete, self.can_get = x
def mkdir(self, path):
ap = self.rv2a(path, w=True)
bos.mkdir(ap)
def listdir(self, path):
vpath = join(self.cwd, path).lstrip("/")
try:
vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, True, False)
fsroot, vfs_ls, vfs_virt = vfs.ls(
rem, self.uname, not self.args.no_scandir, [[True], [False, True]]
)
vfs_ls = [x[0] for x in vfs_ls]
vfs_ls.extend(vfs_virt.keys())
if not self.args.ed:
vfs_ls = exclude_dotfiles(vfs_ls)
vfs_ls.sort()
return vfs_ls
except Exception as ex:
if vpath:
# display write-only folders as empty
return []
# return list of volumes
r = {x.split("/")[0]: 1 for x in self.hub.asrv.vfs.all_vols.keys()}
return list(sorted(list(r.keys())))
def rmdir(self, path):
ap = self.rv2a(path, d=True)
bos.rmdir(ap)
def remove(self, path):
if self.args.no_del:
raise FilesystemError("the delete feature is disabled in server config")
vp = join(self.cwd, path).lstrip("/")
x = self.hub.broker.put(
True, "up2k.handle_rm", self.uname, self.h.remote_ip, [vp]
)
try:
x.get()
except Exception as ex:
raise FilesystemError(str(ex))
def rename(self, src, dst):
if not self.can_move:
raise FilesystemError("not allowed for user " + self.h.username)
if self.args.no_mv:
m = "the rename/move feature is disabled in server config"
raise FilesystemError(m)
svp = join(self.cwd, src).lstrip("/")
dvp = join(self.cwd, dst).lstrip("/")
x = self.hub.broker.put(True, "up2k.handle_mv", self.uname, svp, dvp)
try:
x.get()
except Exception as ex:
raise FilesystemError(str(ex))
def chmod(self, path, mode):
pass
def stat(self, path):
try:
ap = self.rv2a(path, r=True)
return bos.stat(ap)
except:
ap = self.rv2a(path)
st = bos.stat(ap)
if not stat.S_ISDIR(st.st_mode):
raise
return st
def utime(self, path, timeval):
ap = self.rv2a(path, w=True)
return bos.utime(ap, (timeval, timeval))
def lstat(self, path):
ap = self.rv2a(path)
return bos.lstat(ap)
def isfile(self, path):
st = self.stat(path)
return stat.S_ISREG(st.st_mode)
def islink(self, path):
ap = self.rv2a(path)
return bos.path.islink(ap)
def isdir(self, path):
try:
st = self.stat(path)
return stat.S_ISDIR(st.st_mode)
except:
return True
def getsize(self, path):
ap = self.rv2a(path)
return bos.path.getsize(ap)
def getmtime(self, path):
ap = self.rv2a(path)
return bos.path.getmtime(ap)
def realpath(self, path):
return path
def lexists(self, path):
ap = self.rv2a(path)
return bos.path.lexists(ap)
def get_user_by_uid(self, uid):
return "root"
def get_group_by_uid(self, gid):
return "root"
class FtpHandler(FTPHandler):
abstracted_fs = FtpFs
def __init__(self, conn, server, ioloop=None):
if PY2:
FTPHandler.__init__(self, conn, server, ioloop)
else:
super(FtpHandler, self).__init__(conn, server, ioloop)
# abspath->vpath mapping to resolve log_transfer paths
self.vfs_map = {}
def ftp_STOR(self, file, mode="w"):
vp = join(self.fs.cwd, file).lstrip("/")
ap = self.fs.v2a(vp)
self.vfs_map[ap] = vp
# print("ftp_STOR: {} {} => {}".format(vp, mode, ap))
ret = FTPHandler.ftp_STOR(self, file, mode)
# print("ftp_STOR: {} {} OK".format(vp, mode))
return ret
def log_transfer(self, cmd, filename, receive, completed, elapsed, bytes):
ap = filename.decode("utf-8", "replace")
vp = self.vfs_map.pop(ap, None)
# print("xfer_end: {} => {}".format(ap, vp))
if vp:
vp, fn = os.path.split(vp)
vfs, rem = self.hub.asrv.vfs.get(vp, self.username, False, True)
vfs, rem = vfs.get_dbv(rem)
self.hub.broker.put(
False,
"up2k.hash_file",
vfs.realpath,
vfs.flags,
rem,
fn,
self.remote_ip,
time.time(),
)
return FTPHandler.log_transfer(
self, cmd, filename, receive, completed, elapsed, bytes
)
try:
from pyftpdlib.handlers import TLS_FTPHandler
class SftpHandler(FtpHandler, TLS_FTPHandler):
pass
except:
pass
class Ftpd(object):
def __init__(self, hub):
self.hub = hub
self.args = hub.args
hs = []
if self.args.ftp:
hs.append([FtpHandler, self.args.ftp])
if self.args.ftps:
try:
h = SftpHandler
except:
m = "\nftps requires pyopenssl;\nplease run the following:\n\n {} -m pip install --user pyopenssl\n"
print(m.format(sys.executable))
sys.exit(1)
h.certfile = os.path.join(E.cfg, "cert.pem")
h.tls_control_required = True
h.tls_data_required = True
hs.append([h, self.args.ftps])
for h in hs:
h, lp = h
h.hub = hub
h.args = hub.args
h.authorizer = FtpAuth()
h.authorizer.hub = hub
if self.args.ftp_pr:
p1, p2 = [int(x) for x in self.args.ftp_pr.split("-")]
if self.args.ftp and self.args.ftps:
# divide port range in half
d = int((p2 - p1) / 2)
if lp == self.args.ftp:
p2 = p1 + d
else:
p1 += d + 1
h.passive_ports = list(range(p1, p2 + 1))
if self.args.ftp_nat:
h.masquerade_address = self.args.ftp_nat
if self.args.ftp_dbg:
config_logging(level=logging.DEBUG)
ioloop = IOLoop()
for ip in self.args.i:
for h, lp in hs:
FTPServer((ip, int(lp)), h, ioloop)
t = threading.Thread(target=ioloop.loop)
t.daemon = True
t.start()
def join(p1, p2):
w = os.path.join(p1, p2.replace("\\", "/"))
return os.path.normpath(w).replace("\\", "/")

View File

@@ -65,6 +65,11 @@ class HttpCli(object):
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Origin": "*",
"Cache-Control": "no-store; max-age=0", "Cache-Control": "no-store; max-age=0",
} }
h = self.args.html_head
if self.args.no_robots:
h = META_NOBOTS + (("\n" + h) if h else "")
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
self.html_head = h
def log(self, msg, c=0): def log(self, msg, c=0):
ptn = self.asrv.re_pwd ptn = self.asrv.re_pwd
@@ -93,6 +98,7 @@ class HttpCli(object):
if ka: if ka:
ka["ts"] = self.conn.hsrv.cachebuster() ka["ts"] = self.conn.hsrv.cachebuster()
ka["svcname"] = self.args.doctitle ka["svcname"] = self.args.doctitle
ka["html_head"] = self.html_head
return tpl.render(**ka) return tpl.render(**ka)
return tpl return tpl
@@ -524,7 +530,7 @@ class HttpCli(object):
return self.handle_stash() return self.handle_stash()
if "save" in opt: 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)) self.log("urlform: {} bytes, {}".format(post_sz, path))
elif "print" in opt: elif "print" in opt:
reader, _ = self.get_body_reader() reader, _ = self.get_body_reader()
@@ -640,7 +646,7 @@ class HttpCli(object):
with ren_open(fn, *open_a, **params) as f: with ren_open(fn, *open_a, **params) as f:
f, fn = f["orz"] f, fn = f["orz"]
path = os.path.join(fdir, fn) path = os.path.join(fdir, fn)
post_sz, sha_hex, sha_b64 = hashcopy(reader, f) post_sz, sha_hex, sha_b64 = hashcopy(reader, f, self.args.s_wr_slp)
if lim: if lim:
lim.nup(self.ip) lim.nup(self.ip)
@@ -651,26 +657,47 @@ class HttpCli(object):
bos.unlink(path) bos.unlink(path)
raise raise
if not self.args.nw: if self.args.nw:
vfs, vrem = vfs.get_dbv(rem) return post_sz, sha_hex, sha_b64, remains, path, ""
vfs, rem = vfs.get_dbv(rem)
self.conn.hsrv.broker.put( self.conn.hsrv.broker.put(
False, False,
"up2k.hash_file", "up2k.hash_file",
vfs.realpath, vfs.realpath,
vfs.flags, vfs.flags,
vrem, rem,
fn, fn,
self.ip, self.ip,
time.time(), time.time(),
) )
return post_sz, sha_hex, sha_b64, remains, path vsuf = ""
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): 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) spd = self._spd(post_sz)
self.log("{} wrote {}/{} bytes to {}".format(spd, post_sz, remains, path)) m = "{} wrote {}/{} bytes to {} # {}"
m = "{}\n{}\n{}\n".format(post_sz, sha_b64, sha_hex[:56]) self.log(m.format(spd, post_sz, remains, path, sha_b64[:28])) # 21
m = "{}\n{}\n{}\n{}\n".format(post_sz, sha_b64, sha_hex[:56], url)
self.reply(m.encode("utf-8")) self.reply(m.encode("utf-8"))
return True return True
@@ -901,7 +928,7 @@ class HttpCli(object):
try: try:
f.seek(cstart[0]) f.seek(cstart[0])
post_sz, _, sha_b64 = hashcopy(reader, f) post_sz, _, sha_b64 = hashcopy(reader, f, self.args.s_wr_slp)
if sha_b64 != chash: if sha_b64 != chash:
m = "your chunk got corrupted somehow (received {} bytes); expected vs received hash:\n{}\n{}" m = "your chunk got corrupted somehow (received {} bytes); expected vs received hash:\n{}\n{}"
@@ -1094,7 +1121,7 @@ class HttpCli(object):
f, fname = f["orz"] f, fname = f["orz"]
abspath = os.path.join(fdir, fname) abspath = os.path.join(fdir, fname)
self.log("writing to {}".format(abspath)) self.log("writing to {}".format(abspath))
sz, sha_hex, sha_b64 = hashcopy(p_data, f) sz, sha_hex, sha_b64 = hashcopy(p_data, f, self.args.s_wr_slp)
if sz == 0: if sz == 0:
raise Pebkac(400, "empty files in post") raise Pebkac(400, "empty files in post")
@@ -1170,11 +1197,12 @@ class HttpCli(object):
)[: vfs.flags["fk"]] )[: vfs.flags["fk"]]
vpath = "{}/{}".format(upload_vpath, lfn).strip("/") vpath = "{}/{}".format(upload_vpath, lfn).strip("/")
rel_url = quotep(vpath) + vsuf
msg += 'sha512: {} // {} // {} bytes // <a href="/{}">{}</a> {}\n'.format( msg += 'sha512: {} // {} // {} bytes // <a href="/{}">{}</a> {}\n'.format(
sha_hex[:56], sha_hex[:56],
sha_b64, sha_b64,
sz, sz,
quotep(vpath) + vsuf, rel_url,
html_escape(ofn, crlf=True), html_escape(ofn, crlf=True),
vsuf, vsuf,
) )
@@ -1183,15 +1211,16 @@ class HttpCli(object):
jpart = { jpart = {
"url": "{}://{}/{}".format( "url": "{}://{}/{}".format(
"https" if self.is_https else "http", "https" if self.is_https else "http",
self.headers.get("host", "copyparty"), self.headers.get("host")
vpath + vsuf, or "{}:{}".format(*list(self.s.getsockname())),
rel_url,
), ),
"sha512": sha_hex[:56], "sha512": sha_hex[:56],
"sha_b64": sha_b64, "sha_b64": sha_b64,
"sz": sz, "sz": sz,
"fn": lfn, "fn": lfn,
"fn_orig": ofn, "fn_orig": ofn,
"path": vpath + vsuf, "path": rel_url,
} }
jmsg["files"].append(jpart) jmsg["files"].append(jpart)
@@ -1310,7 +1339,7 @@ class HttpCli(object):
raise Pebkac(400, "expected body, got {}".format(p_field)) raise Pebkac(400, "expected body, got {}".format(p_field))
with open(fsenc(fp), "wb", 512 * 1024) as f: with open(fsenc(fp), "wb", 512 * 1024) as f:
sz, sha512, _ = hashcopy(p_data, f) sz, sha512, _ = hashcopy(p_data, f, self.args.s_wr_slp)
if lim: if lim:
lim.nup(self.ip) lim.nup(self.ip)
@@ -1367,6 +1396,9 @@ class HttpCli(object):
try: try:
fs_path = req_path + ext fs_path = req_path + ext
st = bos.stat(fs_path) st = bos.stat(fs_path)
if stat.S_ISDIR(st.st_mode):
continue
file_ts = max(file_ts, st.st_mtime) file_ts = max(file_ts, st.st_mtime)
editions[ext or "plain"] = [fs_path, st.st_size] editions[ext or "plain"] = [fs_path, st.st_size]
except: except:
@@ -1512,11 +1544,12 @@ class HttpCli(object):
with open_func(*open_args) as f: with open_func(*open_args) as f:
sendfun = sendfile_kern if use_sendfile else sendfile_py sendfun = sendfile_kern if use_sendfile else sendfile_py
remains = sendfun( 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: if remains > 0:
logmsg += " \033[31m" + unicode(upper - remains) + "\033[0m" logmsg += " \033[31m" + unicode(upper - remains) + "\033[0m"
self.keepalive = False
spd = self._spd((upper - lower) - remains) spd = self._spd((upper - lower) - remains)
if self.do_log: if self.do_log:
@@ -1650,13 +1683,15 @@ class HttpCli(object):
boundary = "\roll\tide" boundary = "\roll\tide"
targs = { targs = {
"ts": self.conn.hsrv.cachebuster(),
"svcname": self.args.doctitle,
"html_head": self.html_head,
"edit": "edit" in self.uparam, "edit": "edit" in self.uparam,
"title": html_escape(self.vpath, crlf=True), "title": html_escape(self.vpath, crlf=True),
"lastmod": int(ts_md * 1000), "lastmod": int(ts_md * 1000),
"md_plug": "true" if self.args.emp else "false", "md_plug": "true" if self.args.emp else "false",
"md_chk_rate": self.args.mcr, "md_chk_rate": self.args.mcr,
"md": boundary, "md": boundary,
"ts": self.conn.hsrv.cachebuster(),
"arg_base": arg_base, "arg_base": arg_base,
} }
html = template.render(**targs).encode("utf-8", "replace") html = template.render(**targs).encode("utf-8", "replace")
@@ -1705,6 +1740,31 @@ class HttpCli(object):
vstate = {} vstate = {}
vs = {"scanning": None, "hashq": None, "tagq": None, "mtpq": None} vs = {"scanning": None, "hashq": None, "tagq": None, "mtpq": None}
if self.uparam.get("ls") in ["v", "t", "txt"]:
if self.uname == "*":
txt = "howdy stranger (you're not logged in)"
else:
txt = "welcome back {}".format(self.uname)
if vstate:
txt += "\nstatus:"
for k in ["scanning", "hashq", "tagq", "mtpq"]:
txt += " {}({})".format(k, vs[k])
if rvol:
txt += "\nyou can browse:"
for v in rvol:
txt += "\n " + v
if wvol:
txt += "\nyou can upload to:"
for v in wvol:
txt += "\n " + v
txt = txt.encode("utf-8", "replace") + b"\n"
self.reply(txt, mime="text/plain; charset=utf-8")
return True
html = self.j2( html = self.j2(
"splash", "splash",
this=self, this=self,
@@ -1959,6 +2019,13 @@ class HttpCli(object):
fmt = "{{}} {{:{},}} {{}}" fmt = "{{}} {{:{},}} {{}}"
nfmt = "{:,}" 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))) fmt = fmt.format(len(nfmt.format(biggest)))
ret = [ ret = [
"# {}: {}".format(x, ls[x]) "# {}: {}".format(x, ls[x])
@@ -2007,6 +2074,12 @@ class HttpCli(object):
): ):
raise Pebkac(403) raise Pebkac(403)
self.html_head = vn.flags.get("html_head", "")
if vn.flags.get("norobots"):
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
else:
self.out_headers.pop("X-Robots-Tag", None)
is_dir = stat.S_ISDIR(st.st_mode) is_dir = stat.S_ISDIR(st.st_mode)
if self.can_read: if self.can_read:
th_fmt = self.uparam.get("th") th_fmt = self.uparam.get("th")
@@ -2097,11 +2170,12 @@ class HttpCli(object):
url_suf = self.urlq({}, []) url_suf = self.urlq({}, [])
is_ls = "ls" in self.uparam is_ls = "ls" in self.uparam
is_js = self.cookies.get("js") == "y" is_js = self.args.force_js or self.cookies.get("js") == "y"
tpl = "browser" tpl = "browser"
if "b" in self.uparam: if "b" in self.uparam:
tpl = "browser2" tpl = "browser2"
is_js = False
logues = ["", ""] logues = ["", ""]
if not self.args.no_logues: if not self.args.no_logues:
@@ -2200,10 +2274,6 @@ class HttpCli(object):
if not self.args.ed or "dots" not in self.uparam: if not self.args.ed or "dots" not in self.uparam:
vfs_ls = exclude_dotfiles(vfs_ls) vfs_ls = exclude_dotfiles(vfs_ls)
hidden = []
if rem == ".hist":
hidden = ["up2k."]
icur = None icur = None
if "e2t" in vn.flags: if "e2t" in vn.flags:
idx = self.conn.get_u2idx() idx = self.conn.get_u2idx()
@@ -2222,8 +2292,6 @@ class HttpCli(object):
if fn in vfs_virt: if fn in vfs_virt:
fspath = vfs_virt[fn].realpath fspath = vfs_virt[fn].realpath
elif hidden and any(fn.startswith(x) for x in hidden):
continue
else: else:
fspath = fsroot + "/" + fn fspath = fsroot + "/" + fn
@@ -2340,7 +2408,7 @@ class HttpCli(object):
doc = self.uparam.get("doc") if self.can_read else None doc = self.uparam.get("doc") if self.can_read else None
if doc: if doc:
doc = unquotep(doc.replace("+", " ")) doc = unquotep(doc.replace("+", " ").split("?")[0])
j2a["docname"] = doc j2a["docname"] = doc
if next((x for x in files if x["name"] == doc), None): if next((x for x in files if x["name"] == doc), None):
with open(os.path.join(abspath, doc), "rb") as f: with open(os.path.join(abspath, doc), "rb") as f:

View File

@@ -418,7 +418,8 @@ class MTag(object):
try: try:
md = mutagen.File(fsenc(abspath), easy=True) 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: except Exception as ex:
return self.get_ffprobe(abspath) if self.can_ffprobe else {} return self.get_ffprobe(abspath) if self.can_ffprobe else {}

View File

@@ -5,7 +5,7 @@ import tarfile
import threading import threading
from .sutil import errdesc from .sutil import errdesc
from .util import Queue, fsenc from .util import Queue, fsenc, min_ex
from .bos import bos from .bos import bos
@@ -88,8 +88,9 @@ class StreamTar(object):
try: try:
self.ser(f) self.ser(f)
except Exception as ex: except Exception:
errors.append([f["vp"], repr(ex)]) ex = min_ex(5, True).replace("\n", "\n-- ")
errors.append([f["vp"], ex])
if errors: if errors:
self.errf, txt = errdesc(errors) self.errf, txt = errdesc(errors)

View File

@@ -105,6 +105,11 @@ class SvcHub(object):
args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage) args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
if args.ftp or args.ftps:
from .ftpd import Ftpd
self.ftpd = Ftpd(self)
# decide which worker impl to use # decide which worker impl to use
if self.check_mp_enable(): if self.check_mp_enable():
from .broker_mp import BrokerMp as Broker from .broker_mp import BrokerMp as Broker
@@ -357,7 +362,7 @@ class SvcHub(object):
src = ansi_re.sub("", src) src = ansi_re.sub("", src)
elif c: elif c:
if isinstance(c, int): if isinstance(c, int):
msg = "\033[3{}m{}".format(c, msg) msg = "\033[3{}m{}\033[0m".format(c, msg)
elif "\033" not in c: elif "\033" not in c:
msg = "\033[{}m{}\033[0m".format(c, msg) msg = "\033[{}m{}\033[0m".format(c, msg)
else: else:

View File

@@ -1,13 +1,12 @@
# coding: utf-8 # coding: utf-8
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import os
import time import time
import zlib import zlib
from datetime import datetime from datetime import datetime
from .sutil import errdesc from .sutil import errdesc
from .util import yieldfile, sanitize_fn, spack, sunpack from .util import yieldfile, sanitize_fn, spack, sunpack, min_ex
from .bos import bos from .bos import bos
@@ -36,7 +35,10 @@ def unixtime2dos(ts):
bd = ((dy - 1980) << 9) + (dm << 5) + dd bd = ((dy - 1980) << 9) + (dm << 5) + dd
bt = (th << 11) + (tm << 5) + ts // 2 bt = (th << 11) + (tm << 5) + ts // 2
try:
return spack(b"<HH", bt, bd) return spack(b"<HH", bt, bd)
except:
return b"\x00\x00\x21\x00"
def gen_fdesc(sz, crc32, z64): def gen_fdesc(sz, crc32, z64):
@@ -244,8 +246,9 @@ class StreamZip(object):
try: try:
for x in self.ser(f): for x in self.ser(f):
yield x yield x
except Exception as ex: except Exception:
errors.append([f["vp"], repr(ex)]) ex = min_ex(5, True).replace("\n", "\n-- ")
errors.append([f["vp"], ex])
if errors: if errors:
errf, txt = errdesc(errors) errf, txt = errdesc(errors)

View File

@@ -57,13 +57,19 @@ class TcpSrv(object):
msgs = [] msgs = []
title_tab = {} title_tab = {}
title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")] title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")]
m = "available @ http://{}:{}/ (\033[33m{}\033[0m)" m = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
for ip, desc in sorted(eps.items(), key=lambda x: x[1]): for ip, desc in sorted(eps.items(), key=lambda x: x[1]):
for port in sorted(self.args.p): for port in sorted(self.args.p):
if port not in ok.get(ip, ok.get("0.0.0.0", [])): if port not in ok.get(ip, ok.get("0.0.0.0", [])):
continue continue
msgs.append(m.format(ip, port, desc)) proto = " http"
if self.args.http_only:
pass
elif self.args.https_only or port == 443:
proto = "https"
msgs.append(m.format(proto, ip, port, desc))
if not self.args.wintitle: if not self.args.wintitle:
continue continue
@@ -77,15 +83,18 @@ class TcpSrv(object):
if "pub" in title_vars and "external" in unicode(desc): if "pub" in title_vars and "external" in unicode(desc):
hits.append(("pub", ep)) hits.append(("pub", ep))
if "pub" in title_vars or "all" in title_vars:
hits.append(("all", ep))
for var in title_vars: for var in title_vars:
if var.startswith("ip-") and ep.startswith(var[3:]): if var.startswith("ip-") and ep.startswith(var[3:]):
hits.append((var, ep)) hits.append((var, ep))
for tk, tv in hits: for tk, tv in hits:
try: try:
title_tab[tk] += " and {}".format(tv) title_tab[tk][tv] = 1
except: except:
title_tab[tk] = tv title_tab[tk] = {tv: 1}
if msgs: if msgs:
msgs[-1] += "\n" msgs[-1] += "\n"
@@ -141,10 +150,15 @@ class TcpSrv(object):
return eps return eps
r = re.compile(r"^\s+inet ([^ ]+)/.* (.*)") r = re.compile(r"^\s+inet ([^ ]+)/.* (.*)")
ri = re.compile(r"^\s*[0-9]+\s*:.*")
up = False
for ln in txt.split("\n"): for ln in txt.split("\n"):
if ri.match(ln):
up = "UP" in re.split("[>,< ]", ln)
try: try:
ip, dev = r.match(ln.rstrip()).groups() ip, dev = r.match(ln.rstrip()).groups()
eps[ip] = dev eps[ip] = dev + ("" if up else ", \033[31mLINK-DOWN")
except: except:
pass pass
@@ -174,6 +188,7 @@ class TcpSrv(object):
def ips_windows_ipconfig(self): def ips_windows_ipconfig(self):
eps = {} eps = {}
offs = {}
try: try:
txt, _ = chkcmd(["ipconfig"]) txt, _ = chkcmd(["ipconfig"])
except: except:
@@ -181,18 +196,29 @@ class TcpSrv(object):
rdev = re.compile(r"(^[^ ].*):$") rdev = re.compile(r"(^[^ ].*):$")
rip = re.compile(r"^ +IPv?4? [^:]+: *([0-9\.]{7,15})$") rip = re.compile(r"^ +IPv?4? [^:]+: *([0-9\.]{7,15})$")
roff = re.compile(r".*: Media disconnected$")
dev = None dev = None
for ln in txt.replace("\r", "").split("\n"): for ln in txt.replace("\r", "").split("\n"):
m = rdev.match(ln) m = rdev.match(ln)
if m: if m:
if dev and dev not in eps.values():
offs[dev] = 1
dev = m.group(1).split(" adapter ", 1)[-1] dev = m.group(1).split(" adapter ", 1)[-1]
if dev and roff.match(ln):
offs[dev] = 1
dev = None
m = rip.match(ln) m = rip.match(ln)
if m and dev: if m and dev:
eps[m.group(1)] = dev eps[m.group(1)] = dev
dev = None dev = None
return eps if dev and dev not in eps.values():
offs[dev] = 1
return eps, offs
def ips_windows_netsh(self): def ips_windows_netsh(self):
eps = {} eps = {}
@@ -212,7 +238,6 @@ class TcpSrv(object):
m = rip.match(ln) m = rip.match(ln)
if m and dev: if m and dev:
eps[m.group(1)] = dev eps[m.group(1)] = dev
dev = None
return eps return eps
@@ -220,8 +245,11 @@ class TcpSrv(object):
if MACOS: if MACOS:
eps = self.ips_macos() eps = self.ips_macos()
elif ANYWIN: elif ANYWIN:
eps = self.ips_windows_ipconfig() # sees more interfaces eps, off = self.ips_windows_ipconfig() # sees more interfaces + link state
eps.update(self.ips_windows_netsh()) # has better names eps.update(self.ips_windows_netsh()) # has better names
for k, v in eps.items():
if v in off:
eps[k] += ", \033[31mLINK-DOWN"
else: else:
eps = self.ips_linux() eps = self.ips_linux()
@@ -262,13 +290,22 @@ class TcpSrv(object):
return eps return eps
def _set_wintitle(self, vars): def _set_wintitle(self, vars):
if "pub" not in vars: vars["all"] = vars.get("all", {"Local-Only": 1})
vars["pub"] = "Local-Only" 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 = "" title = ""
vars = vars2
for p in self.args.wintitle.split(" "): for p in self.args.wintitle.split(" "):
if p.startswith("$"): if p.startswith("$"):
p = vars.get(p[1:], "(None)") p = " and ".join(sorted(vars.get(p[1:], {"(None)": 1}).keys()))
title += "{} ".format(p) title += "{} ".format(p)

View File

@@ -51,11 +51,11 @@ class U2idx(object):
fhash = body["hash"] fhash = body["hash"]
wark = up2k_wark_from_hashlist(self.args.salt, fsize, fhash) wark = up2k_wark_from_hashlist(self.args.salt, fsize, fhash)
uq = "where substr(w,1,16) = ? and w = ?" uq = "substr(w,1,16) = ? and w = ?"
uv = [wark[:16], wark] uv = [wark[:16], wark]
try: try:
return self.run_query(vols, uq, uv)[0] return self.run_query(vols, uq, uv, True, False)[0]
except: except:
raise Pebkac(500, min_ex()) raise Pebkac(500, min_ex())
@@ -87,17 +87,16 @@ class U2idx(object):
q = "" q = ""
va = [] va = []
joins = "" have_up = False # query has up.* operands
have_mt = False
is_key = True is_key = True
is_size = False is_size = False
is_date = False is_date = False
field_end = "" # closing parenthesis or whatever
kw_key = ["(", ")", "and ", "or ", "not "] kw_key = ["(", ")", "and ", "or ", "not "]
kw_val = ["==", "=", "!=", ">", ">=", "<", "<=", "like "] kw_val = ["==", "=", "!=", ">", ">=", "<", "<=", "like "]
ptn_mt = re.compile(r"^\.?[a-z_-]+$") ptn_mt = re.compile(r"^\.?[a-z_-]+$")
mt_ctr = 0 ptn_lc = re.compile(r" (mt\.v) ([=<!>]+) \? \) $")
mt_keycmp = "substr(up.w,1,16)"
mt_keycmp2 = None
ptn_lc = re.compile(r" (mt[0-9]+\.v) ([=<!>]+) \? $")
ptn_lcv = re.compile(r"[a-zA-Z]") ptn_lcv = re.compile(r"[a-zA-Z]")
while True: while True:
@@ -133,28 +132,31 @@ class U2idx(object):
if v == "size": if v == "size":
v = "up.sz" v = "up.sz"
is_size = True is_size = True
have_up = True
elif v == "date": elif v == "date":
v = "up.mt" v = "up.mt"
is_date = True is_date = True
have_up = True
elif v == "path": elif v == "path":
v = "up.rd" v = "trim(?||up.rd,'/')"
va.append("\nrd")
have_up = True
elif v == "name": elif v == "name":
v = "up.fn" v = "up.fn"
have_up = True
elif v == "tags" or ptn_mt.match(v): elif v == "tags" or ptn_mt.match(v):
mt_ctr += 1 have_mt = True
mt_keycmp2 = "mt{}.w".format(mt_ctr) field_end = ") "
joins += "inner join mt mt{} on {} = {} ".format(
mt_ctr, mt_keycmp, mt_keycmp2
)
mt_keycmp = mt_keycmp2
if v == "tags": if v == "tags":
v = "mt{0}.v".format(mt_ctr) vq = "mt.v"
else: else:
v = "+mt{0}.k = '{1}' and mt{0}.v".format(mt_ctr, v) vq = "+mt.k = '{}' and mt.v".format(v)
v = "exists(select 1 from mt where mt.w = mtw and " + vq
else: else:
raise Pebkac(400, "invalid key [" + v + "]") raise Pebkac(400, "invalid key [" + v + "]")
@@ -200,6 +202,10 @@ class U2idx(object):
va.append(v) va.append(v)
is_key = True is_key = True
if field_end:
q += field_end
field_end = ""
# lowercase tag searches # lowercase tag searches
m = ptn_lc.search(q) m = ptn_lc.search(q)
if not m or not ptn_lcv.search(unicode(v)): if not m or not ptn_lcv.search(unicode(v)):
@@ -211,16 +217,16 @@ class U2idx(object):
field, oper = m.groups() field, oper = m.groups()
if oper in ["=", "=="]: if oper in ["=", "=="]:
q += " {} like ? ".format(field) q += " {} like ? ) ".format(field)
else: else:
q += " lower({}) {} ? ".format(field, oper) q += " lower({}) {} ? ) ".format(field, oper)
try: try:
return self.run_query(vols, joins + "where " + q, va) return self.run_query(vols, q, va, have_up, have_mt)
except Exception as ex: except Exception as ex:
raise Pebkac(500, repr(ex)) raise Pebkac(500, repr(ex))
def run_query(self, vols, uq, uv): def run_query(self, vols, uq, uv, have_up, have_mt):
done_flag = [] done_flag = []
self.active_id = "{:.6f}_{}".format( self.active_id = "{:.6f}_{}".format(
time.time(), threading.current_thread().ident time.time(), threading.current_thread().ident
@@ -237,16 +243,19 @@ class U2idx(object):
thr.start() thr.start()
if not uq or not uv: if not uq or not uv:
q = "select * from up" uq = "select * from up"
v = () uv = ()
elif have_mt:
uq = "select up.*, substr(up.w,1,16) mtw from up where " + uq
uv = tuple(uv)
else: else:
q = "select up.* from up " + uq uq = "select up.* from up where " + uq
v = tuple(uv) uv = tuple(uv)
self.log("qs: {!r} {!r}".format(q, v)) self.log("qs: {!r} {!r}".format(uq, uv))
ret = [] ret = []
lim = 1000 lim = int(self.args.srch_hits)
taglist = {} taglist = {}
for (vtop, ptop, flags) in vols: for (vtop, ptop, flags) in vols:
cur = self.get_cur(ptop) cur = self.get_cur(ptop)
@@ -255,11 +264,19 @@ class U2idx(object):
self.active_cur = cur self.active_cur = cur
vuv = []
for v in uv:
if v == "\nrd":
v = vtop + "/"
vuv.append(v)
vuv = tuple(vuv)
sret = [] sret = []
fk = flags.get("fk") fk = flags.get("fk")
c = cur.execute(q, v) c = cur.execute(uq, vuv)
for hit in c: for hit in c:
w, ts, sz, rd, fn, ip, at = hit w, ts, sz, rd, fn, ip, at = hit[:7]
lim -= 1 lim -= 1
if lim <= 0: if lim <= 0:
break break

View File

@@ -1137,9 +1137,9 @@ class Up2k(object):
m = "database is version {}, this copyparty only supports versions <= {}" m = "database is version {}, this copyparty only supports versions <= {}"
raise Exception(m.format(ver, DB_VER)) raise Exception(m.format(ver, DB_VER))
msg = "creating new DB (old is bad); backup: {}" msg = "creating new DB (old is bad); backup: "
if ver: if ver:
msg = "creating new DB (too old to upgrade); backup: {}" msg = "creating new DB (too old to upgrade); backup: "
cur = self._backup_db(db_path, cur, ver, msg) cur = self._backup_db(db_path, cur, ver, msg)
db = cur.connection db = cur.connection

View File

@@ -71,6 +71,8 @@ SYMTIME = sys.version_info >= (3, 6) and os.supports_follow_symlinks
HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT" HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT"
META_NOBOTS = '<meta name="robots" content="noindex, nofollow">'
HTTPCODE = { HTTPCODE = {
200: "OK", 200: "OK",
204: "No Content", 204: "No Content",
@@ -483,13 +485,13 @@ def vol_san(vols, txt):
return txt return txt
def min_ex(): def min_ex(max_lines=8, reverse=False):
et, ev, tb = sys.exc_info() et, ev, tb = sys.exc_info()
tb = traceback.extract_tb(tb) tb = traceback.extract_tb(tb)
fmt = "{} @ {} <{}>: {}" fmt = "{} @ {} <{}>: {}"
ex = [fmt.format(fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in tb] ex = [fmt.format(fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in tb]
ex.append("[{}] {}".format(et.__name__, ev)) ex.append("[{}] {}".format(et.__name__, ev))
return "\n".join(ex[-8:]) return "\n".join(ex[-max_lines:][:: -1 if reverse else 1])
@contextlib.contextmanager @contextlib.contextmanager
@@ -1097,7 +1099,8 @@ def read_socket(sr, total_size):
buf = sr.recv(bufsz) buf = sr.recv(bufsz)
if not buf: 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) remains -= len(buf)
yield buf yield buf
@@ -1163,13 +1166,15 @@ def yieldfile(fn):
yield buf yield buf
def hashcopy(fin, fout): def hashcopy(fin, fout, slp=0):
hashobj = hashlib.sha512() hashobj = hashlib.sha512()
tlen = 0 tlen = 0
for buf in fin: for buf in fin:
tlen += len(buf) tlen += len(buf)
hashobj.update(buf) hashobj.update(buf)
fout.write(buf) fout.write(buf)
if slp:
time.sleep(slp)
digest = hashobj.digest()[:33] digest = hashobj.digest()[:33]
digest_b64 = base64.urlsafe_b64encode(digest).decode("utf-8") digest_b64 = base64.urlsafe_b64encode(digest).decode("utf-8")
@@ -1177,7 +1182,7 @@ def hashcopy(fin, fout):
return tlen, hashobj.hexdigest(), digest_b64 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 remains = upper - lower
f.seek(lower) f.seek(lower)
while remains > 0: while remains > 0:
@@ -1197,17 +1202,24 @@ def sendfile_py(lower, upper, f, s, bufsz, slp):
return 0 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() out_fd = s.fileno()
in_fd = f.fileno() in_fd = f.fileno()
ofs = lower ofs = lower
stuck = None
while ofs < upper: while ofs < upper:
stuck = stuck or time.time()
try: try:
req = min(2 ** 30, upper - ofs) req = min(2 ** 30, upper - ofs)
select.select([], [out_fd], [], 10) select.select([], [out_fd], [], 10)
n = os.sendfile(out_fd, in_fd, ofs, req) n = os.sendfile(out_fd, in_fd, ofs, req)
stuck = None
except Exception as ex: 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 n = 0
if n <= 0: if n <= 0:

View File

@@ -37,7 +37,7 @@ pre, code, tt, #doc, #doc>code {
display: inline-block; display: inline-block;
padding: .35em .5em .2em .5em; padding: .35em .5em .2em .5em;
border-radius: 0 .3em .3em 0; border-radius: 0 .3em .3em 0;
margin: 1.3em 0 0 0; margin: 1.3em 0 -.2em 0;
font-size: 1.4em; font-size: 1.4em;
} }
#path #entree { #path #entree {
@@ -51,7 +51,8 @@ pre, code, tt, #doc, #doc>code {
} }
#files tbody a { #files tbody a {
display: block; display: block;
padding: .3em 0; padding: .5em 0;
margin: -.3em 0;
scroll-margin-top: 45vh; scroll-margin-top: 45vh;
} }
#files tr { #files tr {
@@ -110,7 +111,7 @@ a, #files tbody div a:last-child {
} }
#files td { #files td {
margin: 0; margin: 0;
padding: .1em .5em; padding: .3em .5em;
border-left: 1px solid #3c3c3c; border-left: 1px solid #3c3c3c;
} }
#files td+td+td { #files td+td+td {
@@ -234,8 +235,8 @@ a, #files tbody div a:last-child {
} }
#files tbody a.play { #files tbody a.play {
color: #e70; color: #e70;
padding: .2em; padding: .3em;
margin: -.2em; margin: -.3em;
} }
#files tbody a.play.act { #files tbody a.play.act {
color: #720; color: #720;
@@ -426,6 +427,9 @@ html.light #ggrid>a.sel {
opacity: .3; opacity: .3;
color: #f6c; color: #f6c;
} }
#wfm a.hide {
display: none;
}
html.light #wfm a:not(.en) { html.light #wfm a:not(.en) {
color: #c4a; color: #c4a;
} }
@@ -487,7 +491,7 @@ html.light #wfm a:not(.en) {
width: calc(100% - 10.5em); width: calc(100% - 10.5em);
background: rgba(0,0,0,0.2); background: rgba(0,0,0,0.2);
} }
@media (min-width: 80em) { @media (min-width: 70em) {
#barpos, #barpos,
#barbuf { #barbuf {
width: calc(100% - 21em); width: calc(100% - 21em);
@@ -679,7 +683,7 @@ input.eq_gain {
#wrap { #wrap {
margin: 1.8em 1.5em 0 1.5em; margin: 1.8em 1.5em 0 1.5em;
min-height: 70vh; min-height: 70vh;
padding-bottom: 5em; padding-bottom: 7em;
} }
#tree { #tree {
display: none; display: none;
@@ -1101,7 +1105,7 @@ html.light #doc .line-highlight {
#docul li { #docul li {
margin: 0; margin: 0;
} }
#tree #docul a { #tree #docul li+li a {
display: block; display: block;
} }
#seldoc.sel { #seldoc.sel {
@@ -1424,6 +1428,7 @@ html.light .opview input[type="text"] {
border-color: #38d; border-color: #38d;
} }
html.light #u2tab a>span, html.light #u2tab a>span,
html.light #docul .bn a>span,
html.light #files td div span { html.light #files td div span {
color: #000; color: #000;
} }
@@ -2149,6 +2154,7 @@ html.light #u2foot .warn span {
border-color: #d06; border-color: #d06;
} }
#u2tab a>span, #u2tab a>span,
#docul .bn a>span,
#unpost a>span { #unpost a>span {
font-weight: bold; font-weight: bold;
font-style: italic; font-style: italic;

View File

@@ -6,6 +6,7 @@
<title>⇆🎉 {{ title }}</title> <title>⇆🎉 {{ title }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8"> <meta name="viewport" content="width=device-width, initial-scale=0.8">
{{ html_head }}
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}"> <link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
<link rel="stylesheet" media="screen" href="/.cpr/browser.css?_={{ ts }}"> <link rel="stylesheet" media="screen" href="/.cpr/browser.css?_={{ ts }}">
{%- if css %} {%- if css %}

View File

@@ -204,6 +204,9 @@ ebi('tree').innerHTML = (
var ops = QSA('#ops>a'); var ops = QSA('#ops>a');
for (var a = 0; a < ops.length; a++) { for (var a = 0; a < ops.length; a++) {
ops[a].onclick = opclick; ops[a].onclick = opclick;
var v = ops[a].getAttribute('data-dest');
if (v)
ops[a].href = '#v=' + v;
} }
})(); })();
@@ -1481,7 +1484,7 @@ function play(tid, is_ev, seek, call_depth) {
seek_au_sec(seek); seek_au_sec(seek);
} }
if (!seek) { if (!seek && !ebi('unsearch')) {
var o = ebi(oid); var o = ebi(oid);
o.setAttribute('id', 'thx_js'); o.setAttribute('id', 'thx_js');
sethash(oid); sethash(oid);
@@ -1591,6 +1594,11 @@ function eval_hash() {
i.value = uricom_dec(v.slice(3))[0]; i.value = uricom_dec(v.slice(3))[0];
return i.oninput(); return i.oninput();
} }
if (v.indexOf('#v=') === 0) {
goto(v.slice(3));
return;
}
} }
@@ -1802,17 +1810,19 @@ var fileman = (function () {
clmod(bdel, 'en', nsel); clmod(bdel, 'en', nsel);
clmod(bcut, 'en', nsel); clmod(bcut, 'en', nsel);
clmod(bpst, 'en', r.clip && r.clip.length); 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'; clmod(bren, 'hide', !(have_mv && has(perms, 'write') && has(perms, 'move')));
bcut.style.display = have_mv && has(perms, 'move') ? '' : 'none'; clmod(bdel, 'hide', !(have_del && has(perms, 'delete')));
bpst.style.display = have_mv && has(perms, 'write') ? '' : 'none'; 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'); 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) { r.rename = function (e) {
ev(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'); return toast.err(3, 'cannot rename:\nyou do not have “move” permission in this folder');
var sel = msel.getsel(); var sel = msel.getsel();
@@ -2099,7 +2109,7 @@ var fileman = (function () {
r.delete = function (e) { r.delete = function (e) {
ev(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'); return toast.err(3, 'cannot delete:\nyou do not have “delete” permission in this folder');
var sel = msel.getsel(), var sel = msel.getsel(),
@@ -2145,7 +2155,7 @@ var fileman = (function () {
r.cut = function (e) { r.cut = function (e) {
ev(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'); return toast.err(3, 'cannot cut:\nyou do not have “move” permission in this folder');
var sel = msel.getsel(), var sel = msel.getsel(),
@@ -2186,7 +2196,7 @@ var fileman = (function () {
r.paste = function (e) { r.paste = function (e) {
ev(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'); return toast.err(3, 'cannot paste:\nyou do not have “write” permission in this folder');
if (!r.clip.length) if (!r.clip.length)
@@ -2379,6 +2389,7 @@ var showfile = (function () {
continue; continue;
td.innerHTML = '<a href="#" class="doc bri" hl="' + link.id + '">-txt-</a>'; td.innerHTML = '<a href="#" class="doc bri" hl="' + link.id + '">-txt-</a>';
td.getElementsByTagName('a')[0].setAttribute('href', '?doc=' + fn);
} }
r.mktree(); r.mktree();
if (em) { if (em) {
@@ -2415,6 +2426,7 @@ var showfile = (function () {
lnh = doc[1], lnh = doc[1],
txt = doc[2], txt = doc[2],
name = url.split('/').pop(), name = url.split('/').pop(),
tname = uricom_dec(name)[0],
lang = r.getlang(name), lang = r.getlang(name),
is_md = lang == 'md'; is_md = lang == 'md';
@@ -2461,13 +2473,14 @@ var showfile = (function () {
wr.style.display = ''; wr.style.display = '';
set_tabindex(); set_tabindex();
wintitle(tname + ' \u2014 ');
document.documentElement.scrollTop = 0; document.documentElement.scrollTop = 0;
var hfun = no_push ? hist_replace : hist_push; var hfun = no_push ? hist_replace : hist_push;
hfun(get_evpath() + '?doc=' + url.split('/').pop()); hfun(get_evpath() + '?doc=' + url.split('/').pop());
qsr('#docname'); qsr('#docname');
el = mknod('span'); el = mknod('span');
el.textContent = uricom_dec(name)[0]; el.textContent = tname;
el.setAttribute('id', 'docname'); el.setAttribute('id', 'docname');
ebi('path').appendChild(el); ebi('path').appendChild(el);
@@ -2477,7 +2490,7 @@ var showfile = (function () {
} }
r.mktree = 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++) { for (var a = 0; a < r.files.length; a++) {
var file = r.files[a]; var file = r.files[a];
html.push('<li><a href="#" hl="' + file.id + html.push('<li><a href="#" hl="' + file.id +
@@ -2602,6 +2615,7 @@ var thegrid = (function () {
return; return;
hist_push(get_evpath()); hist_push(get_evpath());
wintitle();
} }
var vis = has(perms, "read"); var vis = has(perms, "read");
@@ -2966,9 +2980,6 @@ document.onkeydown = function (e) {
if (QS('.opview.act')) if (QS('.opview.act'))
return QS('#ops>a').click(); return QS('#ops>a').click();
if (QS('#unsearch'))
return QS('#unsearch').click();
if (widget.is_open) if (widget.is_open)
return widget.close(); return widget.close();
@@ -2978,6 +2989,9 @@ document.onkeydown = function (e) {
if (!treectl.hidden) if (!treectl.hidden)
return treectl.detree(); return treectl.detree();
if (QS('#unsearch'))
return QS('#unsearch').click();
if (thegrid.en) if (thegrid.en)
return ebi('griden').click(); return ebi('griden').click();
} }
@@ -3015,7 +3029,7 @@ document.onkeydown = function (e) {
} }
} }
if (ae.closest('pre')) { if (ae && ae.closest('pre')) {
if (k == 'KeyA' && ctrl(e)) { if (k == 'KeyA' && ctrl(e)) {
var sel = document.getSelection(), var sel = document.getSelection(),
ran = document.createRange(); ran = document.createRange();
@@ -3209,7 +3223,7 @@ document.onkeydown = function (e) {
clearTimeout(defer_timeout); clearTimeout(defer_timeout);
clearTimeout(search_timeout); clearTimeout(search_timeout);
search_timeout = setTimeout(do_search, search_timeout = setTimeout(do_search,
v && v.length < (is_touch ? 4 : 3) ? 600 : 200); v && v.length < (is_touch ? 4 : 3) ? 1000 : 500);
} }
} }
@@ -3303,7 +3317,7 @@ document.onkeydown = function (e) {
tv = '"' + tv + '"'; tv = '"' + tv + '"';
} }
q += k + not + 'like ' + tv; q += not + k + ' like ' + tv;
} }
} }
} }
@@ -3351,8 +3365,9 @@ document.onkeydown = function (e) {
return; return;
treectl.hide(); treectl.hide();
thegrid.setvis(true);
var html = mk_files_header(tagord); var html = mk_files_header(tagord), seen = {};
html.push('<tbody>'); 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>'); 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++) { for (var a = 0; a < res.hits.length; a++) {
@@ -3361,13 +3376,18 @@ document.onkeydown = function (e) {
sz = esc(r.sz + ''), sz = esc(r.sz + ''),
rp = esc(uricom_dec(r.rp + '')[0]), rp = esc(uricom_dec(r.rp + '')[0]),
ext = rp.lastIndexOf('.') > 0 ? rp.split('.').pop().split('?')[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) if (ext.length > 8)
ext = '%'; ext = '%';
links = links.join(''); var links = linksplit(r.rp + '', id).join(''),
var nodes = ['<tr><td>-</td><td><div>' + links + '</div>', sz]; nodes = ['<tr><td>-</td><td><div>' + links + '</div>', sz];
for (var b = 0; b < tagord.length; b++) { for (var b = 0; b < tagord.length; b++) {
var k = tagord[b], var k = tagord[b],
v = r.tags[k] || ""; v = r.tags[k] || "";
@@ -3437,7 +3457,7 @@ var treectl = (function () {
}); });
setwrap(bcfg_bind(r, 'wtree', 'wraptree', true, setwrap)); setwrap(bcfg_bind(r, 'wtree', 'wraptree', true, setwrap));
setwrap(bcfg_bind(r, 'parpane', 'parpane', true, onscroll)); 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) { function setwrap(v) {
clmod(ebi('tree'), 'nowrap', !v); clmod(ebi('tree'), 'nowrap', !v);
@@ -3866,10 +3886,6 @@ var treectl = (function () {
hist_push(this.top); hist_push(this.top);
r.gentab(this.top, res); r.gentab(this.top, res);
acct = res.acct;
have_up2k_idx = res.idx;
apply_perms(res.perms);
despin('#files'); despin('#files');
despin('#gfiles'); despin('#gfiles');
@@ -3914,7 +3930,7 @@ var treectl = (function () {
showfile.files.push({ 'id': id, 'name': fname }); showfile.files.push({ 'id': id, 'name': fname });
if (tn.lead == '-') if (tn.lead == '-')
tn.lead = '<a href="#" class="doc' + (lang ? ' bri' : '') + tn.lead = '<a href="?doc=' + tn.href + '" class="doc' + (lang ? ' bri' : '') +
'" hl="' + id + '" name="' + hname + '">-txt-</a>'; '" hl="' + id + '" name="' + hname + '">-txt-</a>';
var ln = ['<tr><td>' + tn.lead + '</td><td sortv="' + sortv + var ln = ['<tr><td>' + tn.lead + '</td><td sortv="' + sortv +
@@ -3944,6 +3960,12 @@ var treectl = (function () {
reload_tree(); reload_tree();
reload_browser(); reload_browser();
tree_scrollto(); tree_scrollto();
if (res.acct) {
acct = res.acct;
have_up2k_idx = res.idx;
apply_perms(res.perms);
fileman.render();
}
} }
r.hydrate = function () { r.hydrate = function () {
@@ -5005,12 +5027,16 @@ ebi('path').onclick = function (e) {
if (!treectl.spa || !a || !(a = a.getAttribute('href') + '') || !a.endsWith('/')) if (!treectl.spa || !a || !(a = a.getAttribute('href') + '') || !a.endsWith('/'))
return; return;
thegrid.setvis(true);
treectl.reqls(a, true, true); treectl.reqls(a, true, true);
return ev(e); return ev(e);
}; };
ebi('files').onclick = ebi('docul').onclick = function (e) { ebi('files').onclick = ebi('docul').onclick = function (e) {
if (ctrl(e))
return true;
var tgt = e.target.closest('a[id]'); var tgt = e.target.closest('a[id]');
if (tgt && tgt.getAttribute('id').indexOf('f-') === 0 && tgt.textContent.endsWith('/')) { if (tgt && tgt.getAttribute('id').indexOf('f-') === 0 && tgt.textContent.endsWith('/')) {
var el = treectl.find(tgt.textContent.slice(0, -1)); var el = treectl.find(tgt.textContent.slice(0, -1));
@@ -5030,6 +5056,13 @@ ebi('files').onclick = ebi('docul').onclick = function (e) {
showfile.show(noq_href(ebi(tgt.getAttribute('hl'))), tgt.getAttribute('lang')); showfile.show(noq_href(ebi(tgt.getAttribute('hl'))), tgt.getAttribute('lang'));
return ev(e); 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);
}
}; };
@@ -5080,7 +5113,10 @@ function reload_browser() {
reload_mp(); reload_mp();
try { showsort(ftab); } catch (ex) { } try { showsort(ftab); } catch (ex) { }
makeSortable(ftab, mp.read_order.bind(mp)); makeSortable(ftab, function () {
thegrid.setdirty();
mp.read_order();
});
for (var a = 0; a < 2; a++) for (var a = 0; a < 2; a++)
clmod(ebi(a ? 'pro' : 'epi'), 'hidden', ebi('unsearch')); clmod(ebi(a ? 'pro' : 'epi'), 'hidden', ebi('unsearch'));

View File

@@ -6,6 +6,7 @@
<title>{{ title }}</title> <title>{{ title }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8"> <meta name="viewport" content="width=device-width, initial-scale=0.8">
{{ html_head }}
<style> <style>
html{font-family:sans-serif} html{font-family:sans-serif}
td{border:1px solid #999;border-width:1px 1px 0 0;padding:0 5px} td{border:1px solid #999;border-width:1px 1px 0 0;padding:0 5px}

View File

@@ -3,6 +3,7 @@
<title>📝🎉 {{ title }}</title> <title>📝🎉 {{ title }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.7"> <meta name="viewport" content="width=device-width, initial-scale=0.7">
{{ html_head }}
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}"> <link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
<link rel="stylesheet" href="/.cpr/md.css?_={{ ts }}"> <link rel="stylesheet" href="/.cpr/md.css?_={{ ts }}">
{%- if edit %} {%- if edit %}

View File

@@ -85,13 +85,13 @@ function copydom(src, dst, lv) {
var rpl = []; var rpl = [];
for (var a = sc.length - 1; a >= 0; a--) { for (var a = sc.length - 1; a >= 0; a--) {
var st = sc[a].tagName, var st = sc[a].tagName || sc[a].nodeType,
dt = dc[a].tagName; dt = dc[a].tagName || dc[a].nodeType;
if (st !== dt) { if (st !== dt) {
dbg("replace L%d (%d/%d) type %s/%s", lv, a, sc.length, st, dt); dbg("replace L%d (%d/%d) type %s/%s", lv, a, sc.length, st, dt);
rpl.push(a); dst.innerHTML = src.innerHTML;
continue; return;
} }
var sa = sc[a].attributes || [], var sa = sc[a].attributes || [],
@@ -140,8 +140,11 @@ function copydom(src, dst, lv) {
// repl is reversed; build top-down // repl is reversed; build top-down
var nbytes = 0; var nbytes = 0;
for (var a = rpl.length - 1; a >= 0; a--) { for (var a = rpl.length - 1; a >= 0; a--) {
var html = sc[rpl[a]].outerHTML; var i = rpl[a],
dc[rpl[a]].outerHTML = html; prop = sc[i].nodeType == 1 ? 'outerHTML' : 'nodeValue';
var html = sc[i][prop];
dc[i][prop] = html;
nbytes += html.length; nbytes += html.length;
} }
if (nbytes > 0) if (nbytes > 0)

View File

@@ -3,6 +3,7 @@
<title>📝🎉 {{ title }}</title> <title>📝🎉 {{ title }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.7"> <meta name="viewport" content="width=device-width, initial-scale=0.7">
{{ html_head }}
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}"> <link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
<link rel="stylesheet" href="/.cpr/mde.css?_={{ ts }}"> <link rel="stylesheet" href="/.cpr/mde.css?_={{ ts }}">
<link rel="stylesheet" href="/.cpr/deps/mini-fa.css?_={{ ts }}"> <link rel="stylesheet" href="/.cpr/deps/mini-fa.css?_={{ ts }}">

View File

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

View File

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

View File

@@ -1173,7 +1173,7 @@ function up2k_init(subtle) {
var t = st.todo.handshake[0], var t = st.todo.handshake[0],
cd = t.cooldown; cd = t.cooldown;
if (cd && cd - Date.now() > 0) if (cd && cd > Date.now())
return false; return false;
// keepalive or verify // keepalive or verify
@@ -1370,6 +1370,14 @@ function up2k_init(subtle) {
return taskerd; return taskerd;
})(); })();
function chill(t) {
var now = Date.now();
if ((t.coolmul || 0) < 2 || now - t.cooldown < t.coolmul * 700)
t.coolmul = Math.min((t.coolmul || 0.5) * 2, 32);
t.cooldown = Math.max(t.cooldown || 1, Date.now() + t.coolmul * 1000);
}
///// /////
//// ////
/// hashing /// hashing
@@ -1485,7 +1493,8 @@ function up2k_init(subtle) {
err.indexOf('NotFoundError') !== -1 // macos-firefox permissions err.indexOf('NotFoundError') !== -1 // macos-firefox permissions
) { ) {
pvis.seth(t.n, 1, 'OS-error'); 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; handled = true;
} }
@@ -1755,8 +1764,12 @@ function up2k_init(subtle) {
pvis.move(t.n, 'ok'); pvis.move(t.n, 'ok');
} }
else t.t_uploaded = undefined; else {
if (t.t_uploaded)
chill(t);
t.t_uploaded = undefined;
}
tasker(); tasker();
} }
else { else {
@@ -1868,7 +1881,8 @@ function up2k_init(subtle) {
else { else {
toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format( toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format(
xhr.status, t.name) + (txt || "no further information")); xhr.status, t.name) + (txt || "no further information"));
return;
chill(t);
} }
orz2(xhr); orz2(xhr);
} }
@@ -2113,7 +2127,7 @@ function up2k_init(subtle) {
if (parallel_uploads < 1) if (parallel_uploads < 1)
bumpthread(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) if ((msg + '').indexOf('ResizeObserver') !== -1)
return; // chrome issue 809574 (benign, from <video>) 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; var ekey = url + '\n' + lineNo + '\n' + msg;
if (ignexd[ekey] || crashed) if (ignexd[ekey] || crashed)
return; return;
@@ -219,15 +222,15 @@ if (!String.prototype.endsWith)
return this.substring(this_len - search.length, this_len) === search; return this.substring(this_len - search.length, this_len) === search;
}; };
if (!String.startsWith) if (!String.prototype.startsWith)
String.prototype.startsWith = function (s, i) { String.prototype.startsWith = function (s, i) {
i = i > 0 ? i | 0 : 0; i = i > 0 ? i | 0 : 0;
return this.substring(i, i + s.length) === s; return this.substring(i, i + s.length) === s;
}; };
if (!String.trimEnd) if (!String.prototype.trimEnd)
String.prototype.trimEnd = String.prototype.trimRight = function () { String.prototype.trimEnd = String.prototype.trimRight = function () {
return this.replace(/[ \t\r\n]+$/m, ''); return this.replace(/[ \t\r\n]+$/, '');
}; };
if (!Element.prototype.matches) if (!Element.prototype.matches)
@@ -434,7 +437,7 @@ function makeSortable(table, cb) {
} }
function linksplit(rp) { function linksplit(rp, id) {
var ret = [], var ret = [],
apath = '/', apath = '/',
q = null; q = null;
@@ -464,9 +467,14 @@ function linksplit(rp) {
vlink = vlink.slice(0, -1) + '<span>/</span>'; vlink = vlink.slice(0, -1) + '<span>/</span>';
} }
if (!rp && q) if (!rp) {
if (q)
link += q; link += q;
if (id)
link += '" id="' + id;
}
ret.push('<a href="' + apath + link + '">' + vlink + '</a>'); ret.push('<a href="' + apath + link + '">' + vlink + '</a>');
apath += link; apath += link;
} }

View File

@@ -2,24 +2,12 @@
# example resource files # utilities
can be provided to copyparty to tweak things ## [`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
## 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

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

@@ -3,6 +3,12 @@ echo not a script
exit 1 exit 1
##
## add index.html banners
find -name index.html | sed -r 's/index.html$//' | while IFS= read -r dir; do f="$dir/.prologue.html"; [ -e "$f" ] || echo '<h1><a href="index.html">open index.html</a></h1>' >"$f"; done
## ##
## delete all partial uploads ## delete all partial uploads
## (supports linux/macos, probably windows+msys2) ## (supports linux/macos, probably windows+msys2)
@@ -95,6 +101,7 @@ var t=[]; var b=document.location.href.split('#')[0].slice(0, -1); document.quer
# debug md-editor line tracking # debug md-editor line tracking
var s=mknod('style');s.innerHTML='*[data-ln]:before {content:attr(data-ln)!important;color:#f0c;background:#000;position:absolute;left:-1.5em;font-size:1rem}';document.head.appendChild(s); var s=mknod('style');s.innerHTML='*[data-ln]:before {content:attr(data-ln)!important;color:#f0c;background:#000;position:absolute;left:-1.5em;font-size:1rem}';document.head.appendChild(s);
## ##
## bash oneliners ## bash oneliners
@@ -199,6 +206,7 @@ git remote add all git@github.com:9001/copyparty.git
git remote set-url --add --push all git@gitlab.com:9001/copyparty.git git remote set-url --add --push all git@gitlab.com:9001/copyparty.git
git remote set-url --add --push all git@github.com:9001/copyparty.git git remote set-url --add --push all git@github.com:9001/copyparty.git
## ##
## http 206 ## http 206

View File

@@ -12,21 +12,18 @@ set -e
# #
# output summary (filesizes and contents): # output summary (filesizes and contents):
# #
# 535672 copyparty-extras/sfx-full/copyparty-sfx.sh
# 550760 copyparty-extras/sfx-full/copyparty-sfx.py # 550760 copyparty-extras/sfx-full/copyparty-sfx.py
# `- original unmodified sfx from github # `- original unmodified sfx from github
# #
# 572923 copyparty-extras/sfx-full/copyparty-sfx-gz.py # 572923 copyparty-extras/sfx-full/copyparty-sfx-gz.py
# `- unmodified but recompressed from bzip2 to gzip # `- unmodified but recompressed from bzip2 to gzip
# #
# 341792 copyparty-extras/sfx-ent/copyparty-sfx.sh
# 353975 copyparty-extras/sfx-ent/copyparty-sfx.py # 353975 copyparty-extras/sfx-ent/copyparty-sfx.py
# 376934 copyparty-extras/sfx-ent/copyparty-sfx-gz.py # 376934 copyparty-extras/sfx-ent/copyparty-sfx-gz.py
# `- removed iOS ogg/opus/vorbis audio decoder, # `- removed iOS ogg/opus/vorbis audio decoder,
# removed the audio tray mouse cursor, # removed the audio tray mouse cursor,
# "enterprise edition" # "enterprise edition"
# #
# 259288 copyparty-extras/sfx-lite/copyparty-sfx.sh
# 270004 copyparty-extras/sfx-lite/copyparty-sfx.py # 270004 copyparty-extras/sfx-lite/copyparty-sfx.py
# 293159 copyparty-extras/sfx-lite/copyparty-sfx-gz.py # 293159 copyparty-extras/sfx-lite/copyparty-sfx-gz.py
# `- also removed the codemirror markdown editor # `- also removed the codemirror markdown editor
@@ -81,7 +78,7 @@ cache="$od/.copyparty-repack.cache"
# fallback to awk (sorry) # fallback to awk (sorry)
awk -F\" '/"browser_download_url".*(\.tar\.gz|-sfx\.)/ {print$4}' awk -F\" '/"browser_download_url".*(\.tar\.gz|-sfx\.)/ {print$4}'
) | ) |
grep -E '(sfx\.(sh|py)|tar\.gz)$' | grep -E '(sfx\.py|tar\.gz)$' |
tee /dev/stderr | tee /dev/stderr |
tr -d '\r' | tr '\n' '\0' | tr -d '\r' | tr '\n' '\0' |
xargs -0 bash -c 'dl_files "$@"' _ xargs -0 bash -c 'dl_files "$@"' _
@@ -139,11 +136,11 @@ repack() {
) )
} }
repack sfx-full "re gz no-sh" repack sfx-full "re gz"
repack sfx-ent "re no-dd" repack sfx-ent "re no-dd"
repack sfx-ent "re no-dd gz no-sh" repack sfx-ent "re no-dd gz"
repack sfx-lite "re no-dd no-cm no-hl" repack sfx-lite "re no-dd no-cm no-hl"
repack sfx-lite "re no-dd no-cm no-hl gz no-sh" repack sfx-lite "re no-dd no-cm no-hl gz"
# move fuse and up2k clients into copyparty-extras/, # move fuse and up2k clients into copyparty-extras/,

View File

@@ -2,15 +2,15 @@ FROM alpine:3.15
WORKDIR /z WORKDIR /z
ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \ ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
ver_hashwasm=4.9.0 \ ver_hashwasm=4.9.0 \
ver_marked=4.0.6 \ ver_marked=4.0.12 \
ver_mde=2.15.0 \ ver_mde=2.16.1 \
ver_codemirror=5.64.0 \ ver_codemirror=5.65.2 \
ver_fontawesome=5.13.0 \ ver_fontawesome=5.13.0 \
ver_zopfli=1.0.3 ver_zopfli=1.0.3
# download; # download;
# the scp url is latin from https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap # the scp url is regular latin from https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap
RUN mkdir -p /z/dist/no-pk \ RUN mkdir -p /z/dist/no-pk \
&& wget https://fonts.gstatic.com/s/sourcecodepro/v11/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2 -O scp.woff2 \ && wget https://fonts.gstatic.com/s/sourcecodepro/v11/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2 -O scp.woff2 \
&& apk add cmake make g++ git bash npm patch wget tar pigz brotli gzip unzip python3 python3-dev brotli py3-brotli \ && apk add cmake make g++ git bash npm patch wget tar pigz brotli gzip unzip python3 python3-dev brotli py3-brotli \
@@ -97,15 +97,14 @@ RUN cd CodeMirror-$ver_codemirror \
# build easymde # build easymde
COPY easymde-marked6.patch /z/
COPY easymde.patch /z/ COPY easymde.patch /z/
RUN cd easy-markdown-editor-$ver_mde \ RUN cd easy-markdown-editor-$ver_mde \
&& patch -p1 < /z/easymde-marked6.patch \
&& patch -p1 < /z/easymde.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`https://registry.npmjs.org/marked/-/marked-[0-9\.]+.tgz`file:/z/nodepkgs/marked`' package-lock.json \
&& sed -ri 's`https://registry.npmjs.org/codemirror/-/codemirror-[0-9\.]+.tgz`file:/z/nodepkgs/codemirror`' package-lock.json \
&& sed -ri 's`("marked": ")[^"]+`\1file:/z/nodepkgs/marked`' ./package.json \ && sed -ri 's`("marked": ")[^"]+`\1file:/z/nodepkgs/marked`' ./package.json \
&& sed -ri 's`("codemirror": ")[^"]+`\1file:/z/nodepkgs/codemirror`' ./package.json \ && sed -ri 's`("codemirror": ")[^"]+`\1file:/z/nodepkgs/codemirror`' ./package.json \
&& sed -ri 's`^var marked = require\(.marked/lib/marked.\);$`var marked = window.marked;`' src/js/easymde.js \ && sed -ri 's`^var marked = require\(.marked.\).marked;$`var marked = window.marked;`' src/js/easymde.js \
&& npm install && npm install
COPY easymde-ln.patch /z/ COPY easymde-ln.patch /z/
@@ -119,6 +118,7 @@ RUN cd easy-markdown-editor-$ver_mde \
# build fontawesome and scp # build fontawesome and scp
COPY mini-fa.sh /z COPY mini-fa.sh /z
COPY mini-fa.css /z COPY mini-fa.css /z
COPY shiftbase.py /z
RUN /bin/ash /z/mini-fa.sh RUN /bin/ash /z/mini-fa.sh

View File

@@ -1,6 +1,6 @@
diff -NarU2 codemirror-5.59.3-orig/mode/gfm/gfm.js codemirror-5.59.3/mode/gfm/gfm.js diff -wNarU2 codemirror-5.65.1-orig/mode/gfm/gfm.js codemirror-5.65.1/mode/gfm/gfm.js
--- codemirror-5.59.3-orig/mode/gfm/gfm.js 2021-02-20 21:24:57.000000000 +0000 --- codemirror-5.65.1-orig/mode/gfm/gfm.js 2022-01-20 13:06:23.000000000 +0100
+++ codemirror-5.59.3/mode/gfm/gfm.js 2021-02-21 20:42:02.166174775 +0000 +++ codemirror-5.65.1/mode/gfm/gfm.js 2022-02-09 22:50:18.145862052 +0100
@@ -97,5 +97,5 @@ @@ -97,5 +97,5 @@
} }
} }
@@ -15,9 +15,9 @@ diff -NarU2 codemirror-5.59.3-orig/mode/gfm/gfm.js codemirror-5.59.3/mode/gfm/gf
+ }*/ + }*/
stream.next(); stream.next();
return null; return null;
diff -NarU2 codemirror-5.59.3-orig/mode/meta.js codemirror-5.59.3/mode/meta.js diff -wNarU2 codemirror-5.65.1-orig/mode/meta.js codemirror-5.65.1/mode/meta.js
--- codemirror-5.59.3-orig/mode/meta.js 2021-02-20 21:24:57.000000000 +0000 --- codemirror-5.65.1-orig/mode/meta.js 2022-01-20 13:06:23.000000000 +0100
+++ codemirror-5.59.3/mode/meta.js 2021-02-21 20:42:54.798742821 +0000 +++ codemirror-5.65.1/mode/meta.js 2022-02-09 22:50:18.145862052 +0100
@@ -13,4 +13,5 @@ @@ -13,4 +13,5 @@
CodeMirror.modeInfo = [ CodeMirror.modeInfo = [
@@ -62,10 +62,10 @@ diff -NarU2 codemirror-5.59.3-orig/mode/meta.js codemirror-5.59.3/mode/meta.js
+ */ + */
]; ];
// Ensure all modes have a mime property for backwards compatibility // Ensure all modes have a mime property for backwards compatibility
diff -NarU2 codemirror-5.59.3-orig/src/display/selection.js codemirror-5.59.3/src/display/selection.js diff -wNarU2 codemirror-5.65.1-orig/src/display/selection.js codemirror-5.65.1/src/display/selection.js
--- codemirror-5.59.3-orig/src/display/selection.js 2021-02-20 21:24:57.000000000 +0000 --- codemirror-5.65.1-orig/src/display/selection.js 2022-01-20 13:06:23.000000000 +0100
+++ codemirror-5.59.3/src/display/selection.js 2021-02-21 20:44:14.860894328 +0000 +++ codemirror-5.65.1/src/display/selection.js 2022-02-09 22:50:18.145862052 +0100
@@ -84,29 +84,21 @@ @@ -96,29 +96,21 @@
let order = getOrder(lineObj, doc.direction) let order = getOrder(lineObj, doc.direction)
iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, (from, to, dir, i) => { iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, (from, to, dir, i) => {
- let ltr = dir == "ltr" - let ltr = dir == "ltr"
@@ -105,24 +105,24 @@ diff -NarU2 codemirror-5.59.3-orig/src/display/selection.js codemirror-5.59.3/sr
+ botRight = openEnd && last ? rightSide : toPos.right + botRight = openEnd && last ? rightSide : toPos.right
add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom) add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom)
if (fromPos.bottom < toPos.top) add(leftSide, fromPos.bottom, null, toPos.top) if (fromPos.bottom < toPos.top) add(leftSide, fromPos.bottom, null, toPos.top)
diff -NarU2 codemirror-5.59.3-orig/src/input/ContentEditableInput.js codemirror-5.59.3/src/input/ContentEditableInput.js diff -wNarU2 codemirror-5.65.1-orig/src/input/ContentEditableInput.js codemirror-5.65.1/src/input/ContentEditableInput.js
--- codemirror-5.59.3-orig/src/input/ContentEditableInput.js 2021-02-20 21:24:57.000000000 +0000 --- codemirror-5.65.1-orig/src/input/ContentEditableInput.js 2022-01-20 13:06:23.000000000 +0100
+++ codemirror-5.59.3/src/input/ContentEditableInput.js 2021-02-21 20:44:33.273953867 +0000 +++ codemirror-5.65.1/src/input/ContentEditableInput.js 2022-02-09 22:50:18.145862052 +0100
@@ -399,4 +399,5 @@ @@ -400,4 +400,5 @@
let info = mapFromLineView(view, line, pos.line) let info = mapFromLineView(view, line, pos.line)
+ /* + /*
let order = getOrder(line, cm.doc.direction), side = "left" let order = getOrder(line, cm.doc.direction), side = "left"
if (order) { if (order) {
@@ -404,4 +405,5 @@ @@ -405,4 +406,5 @@
side = partPos % 2 ? "right" : "left" side = partPos % 2 ? "right" : "left"
} }
+ */ + */
let result = nodeAndOffsetInLineMap(info.map, pos.ch, side) let result = nodeAndOffsetInLineMap(info.map, pos.ch, side)
result.offset = result.collapse == "right" ? result.end : result.start result.offset = result.collapse == "right" ? result.end : result.start
diff -NarU2 codemirror-5.59.3-orig/src/input/movement.js codemirror-5.59.3/src/input/movement.js diff -wNarU2 codemirror-5.65.1-orig/src/input/movement.js codemirror-5.65.1/src/input/movement.js
--- codemirror-5.59.3-orig/src/input/movement.js 2021-02-20 21:24:57.000000000 +0000 --- codemirror-5.65.1-orig/src/input/movement.js 2022-01-20 13:06:23.000000000 +0100
+++ codemirror-5.59.3/src/input/movement.js 2021-02-21 20:45:12.763093671 +0000 +++ codemirror-5.65.1/src/input/movement.js 2022-02-09 22:50:18.145862052 +0100
@@ -15,4 +15,5 @@ @@ -15,4 +15,5 @@
export function endOfLine(visually, cm, lineObj, lineNo, dir) { export function endOfLine(visually, cm, lineObj, lineNo, dir) {
@@ -146,9 +146,16 @@ diff -NarU2 codemirror-5.59.3-orig/src/input/movement.js codemirror-5.59.3/src/i
return null return null
+ */ + */
} }
diff -NarU2 codemirror-5.59.3-orig/src/line/line_data.js codemirror-5.59.3/src/line/line_data.js diff -wNarU2 codemirror-5.65.1-orig/src/line/line_data.js codemirror-5.65.1/src/line/line_data.js
--- codemirror-5.59.3-orig/src/line/line_data.js 2021-02-20 21:24:57.000000000 +0000 --- codemirror-5.65.1-orig/src/line/line_data.js 2022-01-20 13:06:23.000000000 +0100
+++ codemirror-5.59.3/src/line/line_data.js 2021-02-21 20:45:36.472549599 +0000 +++ codemirror-5.65.1/src/line/line_data.js 2022-02-09 22:54:11.542722046 +0100
@@ -3,5 +3,5 @@
import { elt, eltP, joinClasses } from "../util/dom.js"
import { eventMixin, signal } from "../util/event.js"
-import { hasBadBidiRects, zeroWidthElement } from "../util/feature_detection.js"
+import { zeroWidthElement } from "../util/feature_detection.js"
import { lst, spaceStr } from "../util/misc.js"
@@ -79,6 +79,6 @@ @@ -79,6 +79,6 @@
// Optionally wire in some hacks into the token-rendering // Optionally wire in some hacks into the token-rendering
// algorithm, to deal with browser quirks. // algorithm, to deal with browser quirks.
@@ -158,10 +165,10 @@ diff -NarU2 codemirror-5.59.3-orig/src/line/line_data.js codemirror-5.59.3/src/l
+ // builder.addToken = buildTokenBadBidi(builder.addToken, order) + // builder.addToken = buildTokenBadBidi(builder.addToken, order)
builder.map = [] builder.map = []
let allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line) let allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line)
diff -NarU2 codemirror-5.59.3-orig/src/measurement/position_measurement.js codemirror-5.59.3/src/measurement/position_measurement.js diff -wNarU2 codemirror-5.65.1-orig/src/measurement/position_measurement.js codemirror-5.65.1/src/measurement/position_measurement.js
--- codemirror-5.59.3-orig/src/measurement/position_measurement.js 2021-02-20 21:24:57.000000000 +0000 --- codemirror-5.65.1-orig/src/measurement/position_measurement.js 2022-01-20 13:06:23.000000000 +0100
+++ codemirror-5.59.3/src/measurement/position_measurement.js 2021-02-21 20:50:52.372945293 +0000 +++ codemirror-5.65.1/src/measurement/position_measurement.js 2022-02-09 22:50:18.145862052 +0100
@@ -380,5 +380,6 @@ @@ -382,5 +382,6 @@
sticky = "after" sticky = "after"
} }
- if (!order) return get(sticky == "before" ? ch - 1 : ch, sticky == "before") - if (!order) return get(sticky == "before" ? ch - 1 : ch, sticky == "before")
@@ -169,39 +176,39 @@ diff -NarU2 codemirror-5.59.3-orig/src/measurement/position_measurement.js codem
+ /* + /*
function getBidi(ch, partPos, invert) { function getBidi(ch, partPos, invert) {
@@ -391,4 +392,5 @@ @@ -393,4 +394,5 @@
if (other != null) val.other = getBidi(ch, other, sticky != "before") if (other != null) val.other = getBidi(ch, other, sticky != "before")
return val return val
+ */ + */
} }
@@ -468,4 +470,5 @@ @@ -470,4 +472,5 @@
let begin = 0, end = lineObj.text.length, ltr = true let begin = 0, end = lineObj.text.length, ltr = true
+ /* + /*
let order = getOrder(lineObj, cm.doc.direction) let order = getOrder(lineObj, cm.doc.direction)
// If the line isn't plain left-to-right text, first figure out // If the line isn't plain left-to-right text, first figure out
@@ -482,4 +485,5 @@ @@ -484,4 +487,5 @@
end = ltr ? part.to : part.from - 1 end = ltr ? part.to : part.from - 1
} }
+ */ + */
// A binary search to find the first character whose bounding box // A binary search to find the first character whose bounding box
@@ -526,4 +530,5 @@ @@ -528,4 +532,5 @@
} }
+/* +/*
function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) { function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) {
// Bidi parts are sorted left-to-right, and in a non-line-wrapping // Bidi parts are sorted left-to-right, and in a non-line-wrapping
@@ -580,4 +585,5 @@ @@ -582,4 +587,5 @@
return part return part
} }
+*/ +*/
let measureText let measureText
diff -NarU2 codemirror-5.59.3-orig/src/util/bidi.js codemirror-5.59.3/src/util/bidi.js diff -wNarU2 codemirror-5.65.1-orig/src/util/bidi.js codemirror-5.65.1/src/util/bidi.js
--- codemirror-5.59.3-orig/src/util/bidi.js 2021-02-20 21:24:57.000000000 +0000 --- codemirror-5.65.1-orig/src/util/bidi.js 2022-01-20 13:06:23.000000000 +0100
+++ codemirror-5.59.3/src/util/bidi.js 2021-02-21 20:52:18.168092225 +0000 +++ codemirror-5.65.1/src/util/bidi.js 2022-02-09 22:50:18.145862052 +0100
@@ -4,5 +4,5 @@ @@ -4,5 +4,5 @@
export function iterateBidiSections(order, from, to, f) { export function iterateBidiSections(order, from, to, f) {
@@ -259,9 +266,9 @@ diff -NarU2 codemirror-5.59.3-orig/src/util/bidi.js codemirror-5.59.3/src/util/b
- return order - return order
+ return false; + return false;
} }
diff -NarU2 codemirror-5.59.3-orig/src/util/feature_detection.js codemirror-5.59.3/src/util/feature_detection.js diff -wNarU2 codemirror-5.65.1-orig/src/util/feature_detection.js codemirror-5.65.1/src/util/feature_detection.js
--- codemirror-5.59.3-orig/src/util/feature_detection.js 2021-02-20 21:24:57.000000000 +0000 --- codemirror-5.65.1-orig/src/util/feature_detection.js 2022-01-20 13:06:23.000000000 +0100
+++ codemirror-5.59.3/src/util/feature_detection.js 2021-02-21 20:49:22.191269270 +0000 +++ codemirror-5.65.1/src/util/feature_detection.js 2022-02-09 22:50:18.145862052 +0100
@@ -25,4 +25,5 @@ @@ -25,4 +25,5 @@
} }

View File

@@ -1,31 +1,31 @@
diff -NarU2 easy-markdown-editor-2.14.0-orig/gulpfile.js easy-markdown-editor-2.14.0/gulpfile.js diff -wNarU2 easy-markdown-editor-2.16.1-orig/gulpfile.js easy-markdown-editor-2.16.1/gulpfile.js
--- easy-markdown-editor-2.14.0-orig/gulpfile.js 2021-02-14 12:11:48.000000000 +0000 --- easy-markdown-editor-2.16.1-orig/gulpfile.js 2022-01-14 23:27:44.000000000 +0100
+++ easy-markdown-editor-2.14.0/gulpfile.js 2021-02-21 20:55:37.134701007 +0000 +++ easy-markdown-editor-2.16.1/gulpfile.js 2022-02-09 23:06:01.694592535 +0100
@@ -25,5 +25,4 @@ @@ -25,5 +25,4 @@
'./node_modules/codemirror/lib/codemirror.css', './node_modules/codemirror/lib/codemirror.css',
'./src/css/*.css', './src/css/*.css',
- './node_modules/codemirror-spell-checker/src/css/spell-checker.css', - './node_modules/codemirror-spell-checker/src/css/spell-checker.css',
]; ];
diff -NarU2 easy-markdown-editor-2.14.0-orig/package.json easy-markdown-editor-2.14.0/package.json diff -wNarU2 easy-markdown-editor-2.16.1-orig/package.json easy-markdown-editor-2.16.1/package.json
--- easy-markdown-editor-2.14.0-orig/package.json 2021-02-14 12:11:48.000000000 +0000 --- easy-markdown-editor-2.16.1-orig/package.json 2022-01-14 23:27:44.000000000 +0100
+++ easy-markdown-editor-2.14.0/package.json 2021-02-21 20:55:47.761190082 +0000 +++ easy-markdown-editor-2.16.1/package.json 2022-02-09 23:06:24.778501888 +0100
@@ -21,5 +21,4 @@ @@ -23,5 +23,4 @@
"dependencies": { "@types/marked": "^4.0.1",
"codemirror": "^5.59.2", "codemirror": "^5.63.1",
- "codemirror-spell-checker": "1.1.2", - "codemirror-spell-checker": "1.1.2",
"marked": "^2.0.0" "marked": "^4.0.10"
}, },
diff -NarU2 easy-markdown-editor-2.14.0-orig/src/js/easymde.js easy-markdown-editor-2.14.0/src/js/easymde.js diff -wNarU2 easy-markdown-editor-2.16.1-orig/src/js/easymde.js easy-markdown-editor-2.16.1/src/js/easymde.js
--- easy-markdown-editor-2.14.0-orig/src/js/easymde.js 2021-02-14 12:11:48.000000000 +0000 --- easy-markdown-editor-2.16.1-orig/src/js/easymde.js 2022-01-14 23:27:44.000000000 +0100
+++ easy-markdown-editor-2.14.0/src/js/easymde.js 2021-02-21 20:57:09.143171536 +0000 +++ easy-markdown-editor-2.16.1/src/js/easymde.js 2022-02-09 23:07:21.203131415 +0100
@@ -12,5 +12,4 @@ @@ -12,5 +12,4 @@
require('codemirror/mode/gfm/gfm.js'); require('codemirror/mode/gfm/gfm.js');
require('codemirror/mode/xml/xml.js'); require('codemirror/mode/xml/xml.js');
-var CodeMirrorSpellChecker = require('codemirror-spell-checker'); -var CodeMirrorSpellChecker = require('codemirror-spell-checker');
var marked = require('marked/lib/marked'); var marked = require('marked').marked;
@@ -1762,9 +1761,4 @@ @@ -1816,9 +1815,4 @@
options.autosave.uniqueId = options.autosave.unique_id; options.autosave.uniqueId = options.autosave.unique_id;
- // If overlay mode is specified and combine is not provided, default it to true - // If overlay mode is specified and combine is not provided, default it to true
@@ -35,7 +35,7 @@ diff -NarU2 easy-markdown-editor-2.14.0-orig/src/js/easymde.js easy-markdown-edi
- -
// Update this options // Update this options
this.options = options; this.options = options;
@@ -2003,28 +1997,7 @@ @@ -2057,34 +2051,7 @@
var mode, backdrop; var mode, backdrop;
- // CodeMirror overlay mode - // CodeMirror overlay mode
@@ -58,31 +58,35 @@ diff -NarU2 easy-markdown-editor-2.14.0-orig/src/js/easymde.js easy-markdown-edi
- backdrop.name = 'gfm'; - backdrop.name = 'gfm';
- backdrop.gitHubSpice = false; - backdrop.gitHubSpice = false;
- -
- if (typeof options.spellChecker === 'function') {
- options.spellChecker({
- codeMirrorInstance: CodeMirror,
- });
- } else {
- CodeMirrorSpellChecker({ - CodeMirrorSpellChecker({
- codeMirrorInstance: CodeMirror, - codeMirrorInstance: CodeMirror,
- }); - });
- }
- } - }
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
diff -NarU2 easy-markdown-editor-2.14.0-orig/types/easymde.d.ts easy-markdown-editor-2.14.0/types/easymde.d.ts diff -wNarU2 easy-markdown-editor-2.16.1-orig/types/easymde.d.ts easy-markdown-editor-2.16.1/types/easymde.d.ts
--- easy-markdown-editor-2.14.0-orig/types/easymde.d.ts 2021-02-14 12:11:48.000000000 +0000 --- easy-markdown-editor-2.16.1-orig/types/easymde.d.ts 2022-01-14 23:27:44.000000000 +0100
+++ easy-markdown-editor-2.14.0/types/easymde.d.ts 2021-02-21 20:57:42.492620979 +0000 +++ easy-markdown-editor-2.16.1/types/easymde.d.ts 2022-02-09 23:07:55.427605243 +0100
@@ -160,9 +160,4 @@ @@ -167,9 +167,4 @@
} }
- interface OverlayModeOptions { - interface OverlayModeOptions {
- mode: CodeMirror.Mode<any> - mode: CodeMirror.Mode<any>;
- combine?: boolean - combine?: boolean;
- } - }
- -
interface Options { interface SpellCheckerOptions {
autoDownloadFontAwesome?: boolean; codeMirrorInstance: CodeMirror.Editor;
@@ -214,7 +209,5 @@ @@ -229,6 +224,4 @@
syncSideBySidePreviewScroll?: boolean;
promptTexts?: PromptTexts; - overlayMode?: OverlayModeOptions;
- syncSideBySidePreviewScroll?: boolean;
- -
- overlayMode?: OverlayModeOptions direction?: 'ltr' | 'rtl';
+ syncSideBySidePreviewScroll?: boolean
}
} }

View File

@@ -29,3 +29,10 @@ pyftsubset "$orig_woff" --unicodes-file=/z/icon.list --no-ignore-missing-unicode
# scp is easier, just want basic latin # scp is easier, just want basic latin
pyftsubset /z/scp.woff2 --unicodes="20-7e,ab,b7,bb,2022" --no-ignore-missing-unicodes --flavor=woff2 --output-file=/z/dist/no-pk/scp.woff2 --verbose pyftsubset /z/scp.woff2 --unicodes="20-7e,ab,b7,bb,2022" --no-ignore-missing-unicodes --flavor=woff2 --output-file=/z/dist/no-pk/scp.woff2 --verbose
exit 0
# kinda works but ruins hinting on windows, just use the old version of the font which has correct baseline
python3 shiftbase.py /z/dist/no-pk/scp.woff2
cd /z/dist/no-pk/
mv scp.woff2.woff2 scp.woff2

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import sys
from fontTools.ttLib import TTFont, newTable
def main():
woff = sys.argv[1]
font = TTFont(woff)
print(repr(font["hhea"].__dict__))
print(repr(font["OS/2"].__dict__))
# font["hhea"].ascent = round(base_asc * mul)
# font["hhea"].descent = round(base_desc * mul)
# font["OS/2"].usWinAscent = round(base_asc * mul)
font["OS/2"].usWinDescent = round(font["OS/2"].usWinDescent * 1.1)
font["OS/2"].sTypoDescender = round(font["OS/2"].sTypoDescender * 1.1)
try:
del font["post"].mapping["Delta#1"]
except:
pass
font.save(woff + ".woff2")
if __name__ == "__main__":
main()

View File

@@ -14,8 +14,6 @@ help() { exec cat <<'EOF'
# #
# `gz` creates a gzip-compressed python sfx instead of bzip2 # `gz` creates a gzip-compressed python sfx instead of bzip2
# #
# `no-sh` makes just the python sfx, skips the sh/unix sfx
#
# `no-cm` saves ~82k by removing easymde/codemirror # `no-cm` saves ~82k by removing easymde/codemirror
# (the fancy markdown editor) # (the fancy markdown editor)
# #
@@ -64,8 +62,6 @@ pybin=$(command -v python3 || command -v python) || {
} }
use_gz= use_gz=
do_sh=1
do_py=1
zopf=2560 zopf=2560
while [ ! -z "$1" ]; do while [ ! -z "$1" ]; do
case $1 in case $1 in
@@ -76,8 +72,6 @@ while [ ! -z "$1" ]; do
no-hl) no_hl=1 ; ;; no-hl) no_hl=1 ; ;;
no-dd) no_dd=1 ; ;; no-dd) no_dd=1 ; ;;
no-cm) no_cm=1 ; ;; no-cm) no_cm=1 ; ;;
no-sh) do_sh= ; ;;
no-py) do_py= ; ;;
fast) zopf=100 ; ;; fast) zopf=100 ; ;;
*) help ; ;; *) help ; ;;
esac esac
@@ -107,7 +101,7 @@ tmpdir="$(
[ $repack ] && { [ $repack ] && {
old="$tmpdir/pe-copyparty" old="$tmpdir/pe-copyparty"
echo "repack of files in $old" echo "repack of files in $old"
cp -pR "$old/"*{dep-j2,copyparty} . cp -pR "$old/"*{dep-j2,dep-ftp,copyparty} .
} }
[ $repack ] || { [ $repack ] || {
@@ -134,6 +128,27 @@ tmpdir="$(
mkdir dep-j2/ mkdir dep-j2/
mv {markupsafe,jinja2} dep-j2/ mv {markupsafe,jinja2} dep-j2/
echo collecting pyftpdlib
f="../build/pyftpdlib-1.5.6.tar.gz"
[ -e "$f" ] ||
(url=https://github.com/giampaolo/pyftpdlib/archive/refs/tags/release-1.5.6.tar.gz;
wget -O$f "$url" || curl -L "$url" >$f)
tar -zxf $f
mv pyftpdlib-release-*/pyftpdlib .
rm -rf pyftpdlib-release-* pyftpdlib/test
mkdir dep-ftp/
mv pyftpdlib dep-ftp/
echo collecting asyncore, asynchat
for n in asyncore.py asynchat.py; do
f=../build/$n
[ -e "$f" ] ||
(url=https://raw.githubusercontent.com/python/cpython/c4d45ee670c09d4f6da709df072ec80cb7dfad22/Lib/$n;
wget -O$f "$url" || curl -L "$url" >$f)
done
# msys2 tar is bad, make the best of it # msys2 tar is bad, make the best of it
echo collecting source echo collecting source
[ $clean ] && { [ $clean ] && {
@@ -144,6 +159,12 @@ tmpdir="$(
(cd .. && tar -cf tar copyparty) && tar -xf ../tar (cd .. && tar -cf tar copyparty) && tar -xf ../tar
} }
rm -f ../tar rm -f ../tar
# insert asynchat
mkdir copyparty/vend
for n in asyncore.py asynchat.py; do
awk 'NR<4||NR>27;NR==4{print"# license: https://opensource.org/licenses/ISC\n"}' ../build/$n >copyparty/vend/$n
done
} }
ver= ver=
@@ -245,7 +266,7 @@ rm have
find | grep -E '\.py$' | find | grep -E '\.py$' |
grep -vE '__version__' | grep -vE '__version__' |
tr '\n' '\0' | tr '\n' '\0' |
xargs -0 $pybin ../scripts/uncomment.py xargs -0 "$pybin" ../scripts/uncomment.py
f=dep-j2/jinja2/constants.py f=dep-j2/jinja2/constants.py
awk '/^LOREM_IPSUM_WORDS/{o=1;print "LOREM_IPSUM_WORDS = u\"a\"";next} !o; /"""/{o=0}' <$f >t awk '/^LOREM_IPSUM_WORDS/{o=1;print "LOREM_IPSUM_WORDS = u\"a\"";next} !o; /"""/{o=0}' <$f >t
@@ -331,11 +352,18 @@ nf=$(ls -1 "$zdir"/arc.* | wc -l)
echo gen tarlist echo gen tarlist
for d in copyparty dep-j2; do find $d -type f; done | for d in copyparty dep-j2 dep-ftp; do find $d -type f; done |
sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort | sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort |
sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1 sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1
for n in {1..50}; do
(grep -vE '\.(gz|br)$' list1; grep -E '\.(gz|br)$' list1 | shuf) >list || true (grep -vE '\.(gz|br)$' list1; grep -E '\.(gz|br)$' list1 | shuf) >list || true
s=$(md5sum list | cut -c-16)
grep -q $s "$zdir/h" && continue
echo $s >> "$zdir/h"
break
done
[ $n -eq 50 ] && exit
echo creating tar echo creating tar
args=(--owner=1000 --group=1000) args=(--owner=1000 --group=1000)
@@ -350,25 +378,12 @@ pe=bz2
echo compressing tar echo compressing tar
# detect best level; bzip2 -7 is usually better than -9 # detect best level; bzip2 -7 is usually better than -9
[ $do_py ] && { for n in {2..9}; do cp tar t.$n; $pc -$n t.$n & done; wait; mv -v $(ls -1S t.*.$pe | tail -n 1) tar.bz2; } for n in {2..9}; do cp tar t.$n; $pc -$n t.$n & done; wait; mv -v $(ls -1S t.*.$pe | tail -n 1) tar.bz2
[ $do_sh ] && { for n in {2..9}; do cp tar t.$n; xz -ze$n t.$n & done; wait; mv -v $(ls -1S t.*.xz | tail -n 1) tar.xz; }
rm t.* || true rm t.* || true
exts=() exts=()
[ $do_sh ] && { echo creating sfx
exts+=(.sh)
echo creating unix sfx
(
sed "s/PACK_TS/$ts/; s/PACK_HTS/$hts/; s/CPP_VER/$ver/" <../scripts/sfx.sh |
grep -E '^sfx_eof$' -B 9001;
cat tar.xz
) >$sfx_out.sh
}
[ $do_py ] && {
echo creating generic sfx
py=../scripts/sfx.py py=../scripts/sfx.py
suf= suf=
@@ -378,13 +393,12 @@ echo creating unix sfx
suf=-gz suf=-gz
} }
$pybin $py --sfx-make tar.bz2 $ver $ts "$pybin" $py --sfx-make tar.bz2 $ver $ts
mv sfx.out $sfx_out$suf.py mv sfx.out $sfx_out$suf.py
exts+=($suf.py) exts+=($suf.py)
[ $use_gz ] && [ $use_gz ] &&
rm $py rm $py
}
chmod 755 $sfx_out* chmod 755 $sfx_out*
@@ -395,4 +409,4 @@ for ext in ${exts[@]}; do
done done
# apk add bash python3 tar xz bzip2 # apk add bash python3 tar xz bzip2
# while true; do ./make-sfx.sh; for f in ..//dist/copyparty-sfx.{sh,py}; do mv $f $f.$(wc -c <$f | awk '{print$1}'); done; done # while true; do ./make-sfx.sh; f=../dist/copyparty-sfx.py; mv $f $f.$(wc -c <$f | awk '{print$1}'); done

View File

@@ -4,6 +4,8 @@ set -e
cd ~/dev/copyparty/scripts cd ~/dev/copyparty/scripts
v=$1 v=$1
[ "$v" = sfx ] || {
printf '%s\n' "$v" | grep -qE '^[0-9\.]+$' || exit 1 printf '%s\n' "$v" | grep -qE '^[0-9\.]+$' || exit 1
grep -E "(${v//./, })" ../copyparty/__version__.py || exit 1 grep -E "(${v//./, })" ../copyparty/__version__.py || exit 1
@@ -17,21 +19,16 @@ rm -rf ../dist
(cd .. && python3 ./setup.py clean2) (cd .. && python3 ./setup.py clean2)
./make-tgz-release.sh $v ./make-tgz-release.sh $v
}
rm -f ../dist/copyparty-sfx.* rm -f ../dist/copyparty-sfx.*
./make-sfx.sh no-sh f=../dist/copyparty-sfx.py
../dist/copyparty-sfx.py -h ./make-sfx.sh
$f -h
ar=
while true; do while true; do
for ((a=0; a<100; a++)); do
for f in ../dist/copyparty-sfx.{py,sh}; do
[ -e $f ] || continue;
mv $f $f.$(wc -c <$f | awk '{print$1}') mv $f $f.$(wc -c <$f | awk '{print$1}')
done
./make-sfx.sh re $ar ./make-sfx.sh re $ar
done done
ar=no-sh
done
# git tag -d v$v; git push --delete origin v$v # git tag -d v$v; git push --delete origin v$v

View File

@@ -11,6 +11,7 @@ copyparty/broker_mp.py,
copyparty/broker_mpw.py, copyparty/broker_mpw.py,
copyparty/broker_thr.py, copyparty/broker_thr.py,
copyparty/broker_util.py, copyparty/broker_util.py,
copyparty/ftpd.py,
copyparty/httpcli.py, copyparty/httpcli.py,
copyparty/httpconn.py, copyparty/httpconn.py,
copyparty/httpsrv.py, copyparty/httpsrv.py,
@@ -31,6 +32,9 @@ copyparty/th_srv.py,
copyparty/u2idx.py, copyparty/u2idx.py,
copyparty/up2k.py, copyparty/up2k.py,
copyparty/util.py, copyparty/util.py,
copyparty/vend,
copyparty/vend/asynchat.py,
copyparty/vend/asyncore.py,
copyparty/web, copyparty/web,
copyparty/web/baguettebox.js, copyparty/web/baguettebox.js,
copyparty/web/browser.css, copyparty/web/browser.css,

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# coding: latin-1 # coding: latin-1
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import re, os, sys, time, shutil, signal, threading, tarfile, hashlib, platform, tempfile, traceback import re, os, sys, time, shutil, signal, threading, tarfile, hashlib, platform, tempfile, traceback
import subprocess as sp import subprocess as sp
""" """
to edit this file, use HxD or "vim -b" to edit this file, use HxD or "vim -b"
(there is compressed stuff at the end) (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"" b"\n# " decodes to b""
""" """
# set by make-sfx.sh # set by make-sfx.sh
VER = None VER = None
SIZE = None SIZE = None
@@ -367,8 +368,9 @@ def confirm(rv):
sys.exit(rv or 1) sys.exit(rv or 1)
def run(tmp, j2): def run(tmp, j2, ftp):
msg("jinja2:", j2 or "bundled") msg("jinja2:", j2 or "bundled")
msg("pyftpd:", ftp or "bundled")
msg("sfxdir:", tmp) msg("sfxdir:", tmp)
msg() msg()
@@ -386,9 +388,8 @@ def run(tmp, j2):
t.daemon = True t.daemon = True
t.start() t.start()
ld = [tmp, os.path.join(tmp, "dep-j2")] ld = (("", ""), (j2, "dep-j2"), (ftp, "dep-ftp"))
if j2: ld = [os.path.join(tmp, b) for a, b in ld if not a]
del ld[-1]
if any([re.match(r"^-.*j[0-9]", x) for x in sys.argv]): if any([re.match(r"^-.*j[0-9]", x) for x in sys.argv]):
run_s(ld) run_s(ld)
@@ -461,7 +462,12 @@ def main():
j2 = None j2 = None
try: try:
run(tmp, j2) from pyftpdlib.__init__ import __ver__ as ftp
except:
ftp = None
try:
run(tmp, j2, ftp)
except SystemExit as ex: except SystemExit as ex:
c = ex.code c = ex.code
if c not in [0, -15]: if c not in [0, -15]:

View File

@@ -112,7 +112,12 @@ args = {
"data_files": data_files, "data_files": data_files,
"packages": find_packages(), "packages": find_packages(),
"install_requires": ["jinja2"], "install_requires": ["jinja2"],
"extras_require": {"thumbnails": ["Pillow"], "audiotags": ["mutagen"]}, "extras_require": {
"thumbnails": ["Pillow"],
"audiotags": ["mutagen"],
"ftpd": ["pyftpdlib"],
"ftps": ["pyopenssl"],
},
"entry_points": {"console_scripts": ["copyparty = copyparty.__main__:main"]}, "entry_points": {"console_scripts": ["copyparty = copyparty.__main__:main"]},
"scripts": ["bin/copyparty-fuse.py", "bin/up2k.py"], "scripts": ["bin/copyparty-fuse.py", "bin/up2k.py"],
"cmdclass": {"clean2": clean2}, "cmdclass": {"clean2": clean2},

View File

@@ -52,9 +52,12 @@ class Cfg(Namespace):
mth="", mth="",
textfiles="", textfiles="",
doctitle="", doctitle="",
html_head="",
hist=None, hist=None,
no_idx=None, no_idx=None,
no_hash=None, no_hash=None,
force_js=False,
no_robots=False,
js_browser=None, js_browser=None,
css_browser=None, css_browser=None,
**{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr no_acode".split()} **{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr no_acode".split()}

View File

@@ -17,13 +17,14 @@ from copyparty import util
class Cfg(Namespace): class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None): def __init__(self, a=None, v=None, c=None):
ex = "nw e2d e2ds e2dsa e2t e2ts e2tsr no_logues no_readme no_acode" ex = "nw e2d e2ds e2dsa e2t e2ts e2tsr no_logues no_readme no_acode force_js no_robots"
ex = {k: False for k in ex.split()} ex = {k: False for k in ex.split()}
ex2 = { ex2 = {
"mtp": [], "mtp": [],
"mte": "a", "mte": "a",
"mth": "", "mth": "",
"doctitle": "", "doctitle": "",
"html_head": "",
"hist": None, "hist": None,
"no_idx": None, "no_idx": None,
"no_hash": None, "no_hash": None,

View File

@@ -109,6 +109,9 @@ class VSock(object):
self._reply += buf self._reply += buf
return len(buf) return len(buf)
def getsockname(self):
return ("a", 1)
class VHttpSrv(object): class VHttpSrv(object):
def __init__(self): def __init__(self):