My PHP
script has difficulty downloading PNG
and JPG
images from a folder (outside of the root), resulting in corrupted files. PDF
files, on the other hand, can be downloaded without issues. I have confirmed that the files exist, have the correct MIME
types (image/png
and image/jpeg
), and are not empty (file size not empty).
The files also exist in the uploads folder, and I can open them with preview tools, etc., so the file in the server works as intended, and I suspect I set my headers wrong.
The file download gets triggered when the file is clicked that starts a page load with GET parameters (I’m using a front controller)
container.addEventListener('click', () => {
// ... Get the user id and file name ...
// Construct the URL to the PHP script that gets the correct file
const open = `/view/?user=${user}&file=${file}&open=true`;
// Open in a new page (used for inline, not attachments)
window.open(open, '_blank');
});
My view file:
// Make sure the user is authenticated...
// If download, user, and file parameters are set, download the file:
if(isset($_GET['open']) && isset($_GET['user']) && isset($_GET['file'])){
if(!Files::open($_GET['file'], $_GET['user'])){
Report::error("Error");
}
Report::success("Success");
exit();
}
My Files::open()
function:
/**
* Open a requested file.
*
* @param string $file
* The file to open.
*
* @param string $folder
* The folder where the file is located.
*
* @return bool
* True if the file was opened successfully, false otherwise.
*/
public static function open(string $file, string $folder = ''): bool {
// Update: 16.05.2023 - Quickfix for vulnerability:
$file = str_replace(['../', '..\'], '', $file);
// Path to the file, this currently only picks
// files from the uploads folder:
$path = dirname(__DIR__, 2) . "/uploads/$folder/$file";
Report::notice("Opening the file '$file' from the '$folder' folder.");
// If the file does not exist, return false:
if (!file_exists($path)) {
Report::warning("The file '$file' does not exist in the '$folder' folder.");
return false;
}
// Create a new Fileinfo object:
$finfo = new finfo(FILEINFO_MIME_TYPE);
// Get the mime type:
$mime = $finfo->file($path);
// Get the mime type:
Report::notice("The mime type of the file '$file' is '$mime'.");
// Set the headers (usually inline, attachment for testing):
header("Content-Type: $mime");
header("Content-Disposition: attachment; filename=$file");
header("Content-Length: " . filesize($path));
// Read the file
readfile($path);
// Return true:
return true;
}
UPDATE 16.05.2023
Thanks to the assistance provided in the comment section, we have made progress in identifying the issue. It has been determined that when the file is downloaded, additional information is added to the file. Specifically, a leading whitespace is consistently included in all downloaded files. Interestingly, the PDF format appears to be able to disregard this extra information, as mentioned in the comments.
2
Answers
As SalmanA noted, the issue was in each file's added newline character, that in fact, came from a newline in my
view.php
script, which had an accidental newline before opening tags for php<php
.I also managed to fix the issue by cleaning the output buffer.
Seems like your PHP script is adding a LF character (line feed, Unix line ending character,
0x0A
). This could be caused by having a blank line somewhere in your script for example:Locate the blank line and remove it.
FFD8FFE0
are the first four bytes of a JPEG image (ref); you seem to have0AFFD8FFE0
in the beginning where the 0A is the LF character.