Skip to content

r2029 deletes decrypted X-Accel temp file before Nginx can serve it #1600

Description

@Iaotle

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

  1. Enable ProjectSend file encryption.
  2. Configure download_method as nginx_xaccel with a valid internal location for the files and temp directories.
  3. Upload a small encrypted file.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions