-
-
Notifications
You must be signed in to change notification settings - Fork 215
Description
Hello.
I'm witnessing a weird behavior of tus-php.
I'm trying upload files via LTE, but the uploaded files sometimes get broken and also may contain extra data.
For debugging purposes, I created a test file data.bin which contains 1259520 bytes of 8-byte integers (little endian) starting with 1 (0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00) β this way we can easily see how the data gets misplaced in the uploaded file.
I tried to upload my test file data.bin from a PC connected to an LTE modem, and this file ended up having size of 1269760 bytes (+10240 extra bytes). Besides, this file at the offset 294912 contains data from the very beginning of the original file (from the offset 0), also at the offset 1261568 (which is already beyond the original file) it starts all over from the beginning of the original.
Here's the uploaded file stats (file size is 1269760 instead of 1259520):
$ ll a0081539-b8e9-47f8-b5ee-83e4f8a449f8_6894ab9b48a27
-rw-r--r-- 1 user user 1269760 aug 7 16:36 a0081539-b8e9-47f8-b5ee-83e4f8a449f8_6894ab9b48a27
And here's the Redis record taken from the server after uploading has finished:
{
"offset": 294912,
"name": "a0081539-b8e9-47f8-b5ee-83e4f8a449f8",
"size": 1259520,
"checksum": "",
"location": "https://example.com/tus/af81d203-3cd4-4eeb-9162-99091c38f63a",
"file_path": "/var/www/uploads/a0081539-b8e9-47f8-b5ee-83e4f8a449f8_6894ab9b48a27",
"metadata": {
"filename": "a0081539-b8e9-47f8-b5ee-83e4f8a449f8"
},
"created_at": "Thu, 07 Aug 2025 13:35:23 GMT",
"expires_at": "Fri, 08 Aug 2025 13:35:23 GMT",
"upload_type": "normal"
}Please note that the offset is somehow not equal to either of 1259520 or 1269760, which is weird. If I understand correctly, successful uploads have both offset and size fields equal.
I searched through issues and found this #391 issue which is closed due to inactivity.
The author is correct though: you open output file with the a flag (append mode) and the fseek function you call on the file later has no effect if the file is opened in this mode.
As per the PHP documentation (https://www.php.net/manual/en/function.fopen.php):
'a': In this mode, fseek() has no effect, writes are always appended.
To make fseek really work you would probably need to use the c mode (or maybe another one that doesn't render fseek useless), from the same source:
'c': Open the file for writing only. If the file does not exist, it is created.
If it exists, it is neither truncated (as opposed to 'w'), nor the call to this
function fails (as is the case with 'x'). The file pointer is positioned on the
beginning of the file.
To Reproduce
Hard to reproduce, as it happens like 1-2 times per 1000 uploads (or even more rarely), maybe due to LTE connectivity issues. See the Additional context below for more info.
Expected behavior
Uploaded file should be a bit-by-bit copy of the original.
Additional context
Here's an excerpt from the log of my client application that does the file uploads with some comments on what's going on (sorry, I can't share the code):
// ...
// Upload created
[2025-08-07T16:35:22] debug File location on server: "https://example.com/tus/af81d203-3cd4-4eeb-9162-99091c38f63a"
// Scheduler calls the UploadFile() function
[2025-08-07T16:35:22] debug Offering offset for TUS upload: 0
[2025-08-07T16:35:23] debug Sending 1048576 bytes at offset 0
[2025-08-07T16:36:23] debug cURL runtime error (PATCH): 28 Operation timed out after 60001 milliseconds with 0 bytes received
[2025-08-07T16:36:23] debug Error while sending chunk: "Operation timed out after 60001 milliseconds with 0 bytes received"
[2025-08-07T16:36:23] debug Warning: failed to upload chunk at offset 0 (attempt 1/3)
// Waiting for 2 secs before retrying
[2025-08-07T16:36:25] debug Sending 1048576 bytes at offset 0
[2025-08-07T16:36:26] debug Unknown error, will retry; HTTP status: 416
[2025-08-07T16:36:26] debug Warning: failed to upload chunk at offset 0 (attempt 2/3)
// Waiting for 2 secs before retrying
[2025-08-07T16:36:28] debug Sending 1048576 bytes at offset 0
[2025-08-07T16:36:29] debug Unknown error, will retry; HTTP status: 416
[2025-08-07T16:36:29] debug Warning: failed to upload chunk at offset 0 (attempt 3/3)
// UploadFile() method returns false, saving the current offset (0) and allowing the scheduler to resume the upload using the saved offset
// Scheduler calls the UploadFile() function again
[2025-08-07T16:36:32] debug Offering offset for TUS upload: 0
[2025-08-07T16:36:32] debug Server demands new offset: 1269760 instead of offered (0). Obeying
// ...
The Unknown error, will retry is written when all the other conditions I expect in the code, were not met (like 'no response', or HTTP codes 200..399, 409, 408, 403, 404, etc.). Error 416 is triggered here due to appending data beyond the original file size:
Lines 333 to 335 in a466a9b
| if ($this->offset > $totalBytes) { | |
| throw new OutOfRangeException('The uploaded file is corrupt.'); | |
| } |
Also, please note the
Server demands new offset: 1269760 instead of offered (0). Obeying line in the log: it means that my client software detected the 409 response code in which server replied with Upload-Offset: 1269760, which is beyond the original file size. Imo, it should respond with 416 in this case.
What I think is happening, is that tus-php somehow ignores the offset (or maybe loses its value due to connection issues) offered by my client in the PATCH requests and appends the data to the file because of using a flag instead of c. So all in all, looks like the real issue here is not using the append mode (which is still incorrect if you intend fseek to really move the write pointer), but incorrect handling of the offset. Otherwise, tus php would reject my offering with the 409 status code. While I understand that I should treat 416 as a fatal error and I will, the upload gets wrecked on the server before I receive this code, so I can't do anything about it.

