php download zip folder as attachment, folder is not found

I'm at a bit of a loss as to why this folder is not being found. I have a script that, after searching a database to find the $filename of someone's purchase based on a stored random code, should simply return their file. My code looks like this (including the trailing end of the db query):

            $stmt_2 -> bind_result($filename);
            $stmt_2 -> fetch();
            $stmt_2 -> close();
            
            // For .zip files

            $filepath='/media-files/Label/' . $filename;

            if (headers_sent()) {
                echo 'HTTP header already sent';
            } else {
                if (!is_file($filepath)) {
                    header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
                    echo 'File not found.';
                } else if (!is_readable($filepath)) {
                    header($_SERVER['SERVER_PROTOCOL'].' 403 Forbidden');
                    echo 'File not readable.';
                } else {
                    header('Content-Type: application/zip');
                    header('Content-Disposition: attachment; filename="' . basename($filepath) . '"');
                    header('Content-Length: ' . filesize($filepath));
                    readfile($filepath);
                    exit;   
                }
            }

When I run this code, I receive "File not found." so !is_file($filepath) is where it is getting tripped up -- However, the path is correct and the zip is definitely there, so I'm not sure what is wrong here.

In terms of debugging, I've tried removing the checks, going directly to the headers and readfile, which returns an empty zip folder. What does work is if I navigate directly to the file by URL...

UPDATE The file path issue has been fixed, but I am still not able to download the file. In all attempts I get either ERR_INVALID_RESPONSE or if I try to brute force download the file, it returns an empty file. I tried using these headers with no success:

header_remove(); 
ob_end_clean();
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filepath));
readfile($filepath);
ob_end_flush();
exit;

They are large audio files, which appears to be causing the issue...

Answer

Solution:

You have two types of pathes:

(a) The path of an URL. You have a web-adress which defines the root of your webpage. e.g. https://www.stackoverflow.com is the start of the site. If you adress /questions at this site you always have the path https://www.stackoverflow.com/questions

(b) The path of the drive where the webpage is located. It is the filesystem-root.

e.g. /home/httpd/html/MyWebPage/questions

If you try to use /questions in (b) it will fail because you need the whole path.

So, this said you need to know where '/media-files/Label/'.$filename is located. It seems to me that /media-files is not at root-level of your filesystem (b). Maybe it is at the web-root but this is not enough for your system to find the file. Therefore you need something like this:

'/root/httpd/MyWebPage/media-files/Label/'.$filename

Answer

Solution:

Nico Haase was absolutely correct, this is an issue with misunderstanding of paths. Here is a link to an article that should clear things up: https://phpdelusions.net/articles/paths

Currently your script is trying to find the file in:

/media-files/Label/file.zip

not:

/var/www/myproject/media-files/Label/file.zip

The linked article should provide you with all the neccesary information.

TLDR;

use:

$filepath=$_SERVER['DOCUMENT_ROOT'].'/media-files/Label/' . $filename;

UPDATE

With the file size issue it might be that PHP runs out of allowed memory when trying to load the whole file. We could try something like:

flush();
$file = fopen($filepath, "r");
while(!feof($file)) {
    // send the current file part to the browser
    print fread($file, round(10 * 1024));
    // flush the content to the browser
    flush();
}
fclose($file);

There are some issues with flush() but it's a good shot I think. You can have a read on: https://www.php.net/manual/en/function.flush

Other then that there is always the possibility to split the file into smaller chunks.

Source