diff --git a/README.md b/README.md index 48196459..bb387bc7 100644 --- a/README.md +++ b/README.md @@ -1569,10 +1569,12 @@ interact with copyparty using non-browser clients * `var xhr = new XMLHttpRequest(); xhr.open('POST', '//127.0.0.1:3923/msgs?raw'); xhr.send('foo');` * curl/wget: upload some files (post=file, chunk=stdin) - * `post(){ curl -F act=bput -F f=@"$1" http://127.0.0.1:3923/?pw=wark;}` - `post movie.mkv` + * `post(){ curl -F f=@"$1" http://127.0.0.1:3923/?pw=wark;}` + `post movie.mkv` (gives HTML in return) + * `post(){ curl -F f=@"$1" 'http://127.0.0.1:3923/?want=url&pw=wark';}` + `post movie.mkv` (gives hotlink in return) * `post(){ curl -H pw:wark -H rand:8 -T "$1" http://127.0.0.1:3923/;}` - `post movie.mkv` + `post movie.mkv` (randomized filename) * `post(){ wget --header='pw: wark' --post-file="$1" -O- http://127.0.0.1:3923/?raw;}` `post movie.mkv` * `chunk(){ curl -H pw:wark -T- http://127.0.0.1:3923/;}` diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 287c64ca..816ac69b 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -45,6 +45,7 @@ from .util import ( ODict, Pebkac, UnrecvEOF, + WrongPostKey, absreal, alltrace, atomic_move, @@ -1862,7 +1863,16 @@ class HttpCli(object): self.parser = MultipartParser(self.log, self.sr, self.headers) self.parser.parse() - act = self.parser.require("act", 64) + file0: list[tuple[str, Optional[str], Generator[bytes, None, None]]] = [] + try: + act = self.parser.require("act", 64) + except WrongPostKey as ex: + if ex.got == "f" and ex.fname: + self.log("missing 'act', but looks like an upload so assuming that") + file0 = [(ex.got, ex.fname, ex.datagen)] + act = "bput" + else: + raise if act == "login": return self.handle_login() @@ -1875,7 +1885,7 @@ class HttpCli(object): return self.handle_new_md() if act == "bput": - return self.handle_plain_upload() + return self.handle_plain_upload(file0) if act == "tput": return self.handle_text_upload() @@ -2314,7 +2324,9 @@ class HttpCli(object): vfs.flags.get("xau") or [], ) - def handle_plain_upload(self) -> bool: + def handle_plain_upload( + self, file0: list[tuple[str, Optional[str], Generator[bytes, None, None]]] + ) -> bool: assert self.parser nullwrite = self.args.nw vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True) @@ -2340,7 +2352,8 @@ class HttpCli(object): t0 = time.time() try: assert self.parser.gen - for nfile, (p_field, p_file, p_data) in enumerate(self.parser.gen): + gens = itertools.chain(file0, self.parser.gen) + for nfile, (p_field, p_file, p_data) in enumerate(gens): if not p_file: self.log("discarding incoming file without filename") # fallthrough diff --git a/copyparty/util.py b/copyparty/util.py index b97f514c..8a7e5beb 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -1537,11 +1537,9 @@ class MultipartParser(object): raises if the field name is not as expected """ assert self.gen - p_field, _, p_data = next(self.gen) + p_field, p_fname, p_data = next(self.gen) if p_field != field_name: - raise Pebkac( - 422, 'expected field "{}", got "{}"'.format(field_name, p_field) - ) + raise WrongPostKey(field_name, p_field, p_fname, p_data) return self._read_value(p_data, max_len).decode("utf-8", "surrogateescape") @@ -3058,3 +3056,20 @@ class Pebkac(Exception): def __repr__(self) -> str: return "Pebkac({}, {})".format(self.code, repr(self.args)) + + +class WrongPostKey(Pebkac): + def __init__( + self, + expected: str, + got: str, + fname: Optional[str], + datagen: Generator[bytes, None, None], + ) -> None: + msg = 'expected field "{}", got "{}"'.format(expected, got) + super(WrongPostKey, self).__init__(422, msg) + + self.expected = expected + self.got = got + self.fname = fname + self.datagen = datagen diff --git a/docs/devnotes.md b/docs/devnotes.md index 3eeb6027..10ac2a86 100644 --- a/docs/devnotes.md +++ b/docs/devnotes.md @@ -162,8 +162,8 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo` | PUT | | (binary data) | upload into file at URL | | 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 | -| mPOST | | `act=bput`, `f=FILE` | upload `FILE` into the folder at URL | -| mPOST | `?j` | `act=bput`, `f=FILE` | ...and reply with json | +| mPOST | | `f=FILE` | upload `FILE` into the folder at URL | +| mPOST | `?j` | `f=FILE` | ...and reply with json | | mPOST | | `act=mkdir`, `name=foo` | create directory `foo` at URL | | POST | `?delete` | | delete URL recursively | | jPOST | `?delete` | `["/foo","/bar"]` | delete `/foo` and `/bar` recursively |