append to existing files with PUT

This commit is contained in:
ed 2026-01-01 20:32:33 +00:00
parent ec51d3241c
commit 63d8e5a033
6 changed files with 32 additions and 3 deletions

View file

@ -2684,6 +2684,10 @@ interact with copyparty using non-browser clients
* `chunk(){ curl -H pw:wark -T- http://127.0.0.1:3923/;}`
`chunk <movie.mkv`
* curl: append to existing file with `?apnd`
* `log(){ curl -H pw:wark -T- http://127.0.0.1:3923/logfile.txt?apnd;}`
`echo hey | log`
* bash: when curl and wget is not available or too boring
* `(printf 'PUT /junk?pw=wark HTTP/1.1\r\n\r\n'; cat movie.mkv) | nc 127.0.0.1 3923`
* `(printf 'PUT / HTTP/1.1\r\n\r\n'; cat movie.mkv) >/dev/tcp/127.0.0.1/3923`

View file

@ -1232,6 +1232,7 @@ def add_upload(ap):
ap2.add_argument("--bup-ck", metavar="ALG", type=u, default="sha512", help="default checksum-hasher for bup/basic-uploader: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=bup_ck)")
ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled, default=12h")
ap2.add_argument("--unp-who", metavar="NUM", type=int, default=1, help="clients can undo recent uploads by using the unpost tab (requires \033[33m-e2d\033[0m). [\033[32m0\033[0m] = never allowed (disable feature), [\033[32m1\033[0m] = allow if client has the same IP as the upload AND is using the same account, [\033[32m2\033[0m] = just check the IP, [\033[32m3\033[0m] = just check account-name (volflag=unp_who)")
ap2.add_argument("--apnd-who", metavar="NUM", type=u, default="dw", help="who can append to existing files? [\033[32mno\033[0m]=nobody, [\033[32maw\033[0m]=admin+write, [\033[32mdw\033[0m]=delete+write, [\033[32mw\033[0m]=write (volflag=apnd_who)")
ap2.add_argument("--u2abort", metavar="NUM", type=int, default=1, help="clients can abort incomplete uploads by using the unpost tab (requires \033[33m-e2d\033[0m). [\033[32m0\033[0m] = never allowed (disable feature), [\033[32m1\033[0m] = allow if client has the same IP as the upload AND is using the same account, [\033[32m2\033[0m] = just check the IP, [\033[32m3\033[0m] = just check account-name (volflag=u2abort)")
ap2.add_argument("--blank-wt", metavar="SEC", type=int, default=300, help="file write grace period (any client can write to a blank file last-modified more recently than \033[33mSEC\033[0m seconds ago)")
ap2.add_argument("--reg-cap", metavar="N", type=int, default=38400, help="max number of uploads to keep in memory when running without \033[33m-e2d\033[0m; roughly 1 MiB RAM per 600")

View file

@ -94,6 +94,7 @@ def vf_vmap() -> dict[str, str]:
"th_x3": "th3x",
}
for k in (
"apnd_who",
"bup_ck",
"cachectl",
"casechk",
@ -232,6 +233,7 @@ flagcats = {
"pk": "forces server-side compression, optional arg: xz,9",
},
"upload rules": {
"apnd_who=dw": "who can append? (aw/dw/w/no)",
"maxn=250,600": "max 250 uploads over 15min",
"maxb=1g,300": "max 1 GiB over 5min (suffixes: b, k, m, g, t)",
"vmaxb=1g": "total volume size max 1 GiB (suffixes: b, k, m, g, t)",

View file

@ -2478,7 +2478,12 @@ class HttpCli(object):
self.vpath = vjoin(self.vpath, fn)
params["fdir"] = fdir
if is_put and not (self.args.no_dav or self.args.nw) and bos.path.exists(path):
if (
is_put
and not (self.args.no_dav or self.args.nw)
and "append" not in self.uparam
and bos.path.exists(path)
):
# allow overwrite if...
# * volflag 'daw' is set, or client is definitely webdav
# * and account has delete-access
@ -2526,7 +2531,22 @@ class HttpCli(object):
else:
raise Pebkac(500, "unknown hash alg")
if "apnd" in self.uparam and not self.args.nw and bos.path.exists(path):
zs = vfs.flags["apnd_who"]
if (
zs == "w"
or (zs == "aw" and self.can_admin)
or (zs == "dw" and self.can_delete)
):
pass
else:
raise Pebkac(400, "you do not have permission to append")
zs = os.path.join(params["fdir"], fn)
self.log("upload will append to [%s]" % (zs,))
f = open(zs, "ab")
else:
f, fn = ren_open(fn, *open_a, **params)
try:
path = os.path.join(fdir, fn)
post_sz, sha_hex, sha_b64 = copier(reader, f, hasher, 0, self.args.s_wr_slp)

View file

@ -226,11 +226,13 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
| PUT | `?ck=md5` | (binary data) | return md5 instead of sha512 |
| PUT | `?gz` | (binary data) | compress with gzip and write into file at URL |
| PUT | `?xz` | (binary data) | compress with xz and write into file at URL |
| PUT | `?apnd` | (binary data) | append to existing file |
| mPOST | | `f=FILE` | upload `FILE` into the folder at URL |
| mPOST | `?j` | `f=FILE` | ...and reply with json |
| mPOST | `?ck` | `f=FILE` | ...and disable checksum gen (faster) |
| mPOST | `?ck=md5` | `f=FILE` | ...and return md5 instead of sha512 |
| mPOST | `?replace` | `f=FILE` | ...and overwrite existing files |
| mPOST | `?apnd` | `f=FILE` | ...and append to existing files |
| mPOST | `?media` | `f=FILE` | ...and return medialink (not hotlink) |
| mPOST | | `act=mkdir`, `name=foo` | create directory `foo` at URL |
| POST | `?delete` | | delete URL recursively |

View file

@ -167,7 +167,7 @@ class Cfg(Namespace):
ex = "ah_alg bname chdir chmod_f chpw_db doctitle df epilogues exit favico ipa ipar html_head html_head_d html_head_s idp_login idp_logout lg_sba lg_sbf log_date log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i opds_exts preadmes prologues readmes shr tcolor textfiles txt_eol ufavico ufavico_h unlist vname xff_src zipmaxt R RS SR"
ka.update(**{k: "" for k in ex.split()})
ex = "ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url dont_ban cachectl rss_fmt_d rss_fmt_t spinner"
ex = "apnd_who ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url dont_ban cachectl rss_fmt_d rss_fmt_t spinner"
ka.update(**{k: "no" for k in ex.split()})
ex = "ext_th grp idp_h_usr idp_hm_usr ipr on403 on404 qr_file xac xad xar xau xban xbc xbd xbr xbu xiu xm"