Describe the bug
copyparty ignores suffix byte ranges (Range: bytes=-N). Instead of returning the last N bytes, it treats the absent first-byte-pos as 0 and reuses N as the last-byte-pos, so it returns the first N+1 bytes — with a 206 Partial Content and a Content-Range that claims the range it actually served.
This violates RFC 7233 §2.1 (suffix-byte-range-spec), and it is worse than a plain 416: the client gets a success status and self-consistent headers, so it has no way to notice it received the head of the file instead of the tail. Anything that reads trailing metadata over HTTP is silently fed the wrong bytes — remote zip readers (end-of-central-directory), log tailers, media containers with a trailing index.
To Reproduce
Serve any file (example below is 19381087 bytes) and request a suffix range:
| Request |
Response Content-Range |
Should be |
bytes=-10 |
bytes 0-10/19381087 |
last 10 bytes |
bytes=-100 |
bytes 0-100/19381087 |
last 100 bytes |
bytes=-65536 |
bytes 0-65536/19381087 |
last 65536 bytes |
curl -si -H 'Range: bytes=-10' http://127.0.0.1:3923/somefile.bin | head -n1
curl -si -H 'Range: bytes=-10' http://127.0.0.1:3923/somefile.bin | grep -i content-range
Expected behavior
bytes=-N returns the final N bytes: Content-Range: bytes 102390-102399/102400, Content-Length: 10.
Per RFC 7233 §2.1, if N is larger than the file, the whole file is returned (not 416). bytes=-0 is unsatisfiable and should be 416.
Explicit ranges are unaffected and already correct (bytes=19381077-19381086 works, so does bytes=102390-).
Expected behavior
bytes=-N returns the final N bytes: for the file above, Content-Range: bytes 102390-102399/102400 with Content-Length: 10.
Per RFC 7233 §2.1, a suffix length larger than the file returns the whole file (not 416). bytes=-0 is unsatisfiable and should stay 416.
Server details (if you're NOT using docker/podman)
- server OS / version: Ubuntu 24.04.4 LTS, Linux 7.0.0-29-generic x86_64
- what copyparty did you grab: run from source, 9de0902
- how you're running it: in a terminal
--version:
copyparty v1.20.21 "sftp is fine too" (2026-08-17)
CPython v3.12.3 on Linux64 24.04.2 [GCC 13.3.0]
sqlite 3.45.1*1 | jinja 3.1.2 | pyftpd (None) | tftp (None) | miko (None)
- copyparty arguments and/or config-file:
-p 3923 -v .::r
Client details
curl
The rest of the stack
None
Describe the bug
copyparty ignores suffix byte ranges (
Range: bytes=-N). Instead of returning the last N bytes, it treats the absent first-byte-pos as0and reuses N as the last-byte-pos, so it returns the first N+1 bytes — with a206 Partial Contentand aContent-Rangethat claims the range it actually served.This violates RFC 7233 §2.1 (
suffix-byte-range-spec), and it is worse than a plain 416: the client gets a success status and self-consistent headers, so it has no way to notice it received the head of the file instead of the tail. Anything that reads trailing metadata over HTTP is silently fed the wrong bytes — remote zip readers (end-of-central-directory), log tailers, media containers with a trailing index.To Reproduce
Serve any file (example below is 19381087 bytes) and request a suffix range:
Content-Rangebytes=-10bytes 0-10/19381087bytes=-100bytes 0-100/19381087bytes=-65536bytes 0-65536/19381087Expected behavior
bytes=-Nreturns the final N bytes:Content-Range: bytes 102390-102399/102400,Content-Length: 10.Per RFC 7233 §2.1, if N is larger than the file, the whole file is returned (not 416).
bytes=-0is unsatisfiable and should be 416.Explicit ranges are unaffected and already correct (
bytes=19381077-19381086works, so doesbytes=102390-).Expected behavior
bytes=-Nreturns the final N bytes: for the file above,Content-Range: bytes 102390-102399/102400withContent-Length: 10.Per RFC 7233 §2.1, a suffix length larger than the file returns the whole file (not 416).
bytes=-0is unsatisfiable and should stay 416.Server details (if you're NOT using docker/podman)
--version:-p 3923 -v .::rClient details
curl
The rest of the stack
None