Description
In r2029, encrypted downloads using Nginx X-Accel are decrypted to a temporary file, but ProjectSend deletes that file in a PHP shutdown function before Nginx services the X-Accel-Redirect. Nginx consequently returns a missing/empty download even though decryption succeeded.
This is a distinct lifecycle regression following #1518: r2029 now attempts the suggested decrypt-to-temp approach, but cleans up too early.
The same code remains on develop at 0ea849175f4c6c3e7c4a09c112534f62e2f4a8c3.
Steps to reproduce
- Enable ProjectSend file encryption.
- Configure
download_method as nginx_xaccel with a valid internal location for the files and temp directories.
- Upload a small encrypted file.
- Download it through ProjectSend.
Expected behavior
The decrypted temporary file remains available until Nginx has completed the internal redirect and sent the response, then it is cleaned up asynchronously.
Actual behavior
ProjectSend returns X-Accel-Redirect for a path such as /upload/temp/ps_decrypt_*, but Nginx logs:
open() "/.../upload/temp/ps_decrypt_*" failed (2: No such file or directory)
Root cause
includes/Classes/Download.php::serveFile() contains:
$temp_file = tempnam(UPLOADS_TEMP_DIR, 'ps_decrypt_');
$decrypt_result = $encryption->decryptFileToPath($file_location, $temp_file, $file_key);
$xaccel = XACCEL_FILES_URL . '/temp/' . basename($temp_file);
register_shutdown_function(function() use ($temp_file) {
if (file_exists($temp_file)) {
unlink($temp_file);
}
});
PHP shutdown runs while finalizing the upstream FastCGI request. Nginx handles the X-Accel-Redirect only after receiving that response, so the callback removes the file before the Nginx file-open step.
Environment
- ProjectSend r2029
- PHP 8.4.16
- Nginx with X-Accel-Redirect
- LinuxServer.io image
r2029-ls275
Suggested fix
Do not unlink an X-Accel target from a PHP shutdown callback. Use a delayed/periodic cleanup mechanism based on a conservative file age, or delegate cleanup to the web server/runtime after the transfer. Cleanup should also cover abandoned temp files without racing active downloads.
Description
In r2029, encrypted downloads using Nginx X-Accel are decrypted to a temporary file, but ProjectSend deletes that file in a PHP shutdown function before Nginx services the
X-Accel-Redirect. Nginx consequently returns a missing/empty download even though decryption succeeded.This is a distinct lifecycle regression following #1518: r2029 now attempts the suggested decrypt-to-temp approach, but cleans up too early.
The same code remains on
developat0ea849175f4c6c3e7c4a09c112534f62e2f4a8c3.Steps to reproduce
download_methodasnginx_xaccelwith a valid internal location for the files and temp directories.Expected behavior
The decrypted temporary file remains available until Nginx has completed the internal redirect and sent the response, then it is cleaned up asynchronously.
Actual behavior
ProjectSend returns
X-Accel-Redirectfor a path such as/upload/temp/ps_decrypt_*, but Nginx logs:Root cause
includes/Classes/Download.php::serveFile()contains:PHP shutdown runs while finalizing the upstream FastCGI request. Nginx handles the
X-Accel-Redirectonly after receiving that response, so the callback removes the file before the Nginx file-open step.Environment
r2029-ls275Suggested fix
Do not unlink an X-Accel target from a PHP shutdown callback. Use a delayed/periodic cleanup mechanism based on a conservative file age, or delegate cleanup to the web server/runtime after the transfer. Cleanup should also cover abandoned temp files without racing active downloads.