Skip to content

Instantly share code, notes, and snippets.

@perryflynn
Created May 31, 2025 22:43
Show Gist options
  • Save perryflynn/6fc08c278df6fe0aa83ef275392edca5 to your computer and use it in GitHub Desktop.
Save perryflynn/6fc08c278df6fe0aa83ef275392edca5 to your computer and use it in GitHub Desktop.
Handling securely a PHP file upload
<?php
declare(strict_types=1);
namespace PerrysFramework;
class FileUploadUtils
{
// This is just a list of my personal common mime types,
// this may not suit your requirements
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types
const FileTypeMap = [
// images
'image/png' => '.png',
'image/jpeg' => '.jpg',
'image/gif' => '.gif',
'image/webp' => '.webp',
'image/svg+xml' => '.svg',
'image/svg' => '.svg',
// documents
'application/pdf' => '.pdf',
// text
'text/plain' => '.txt',
'text/css' => '.css',
'text/csv' => '.csv',
'text/html' => '.html',
'text/javascript' => '.js',
'application/json' => '.json',
'text/markdown' => '.md',
// archives
'application/gzip' => '.gz',
'application/x-gzip' => '.gz',
'application/x-tar' => '.tar',
'application/zip' => '.zip',
'application/x-zip-compressed' => '.zip',
];
/**
* Handle file upload
* @param fileItem Element from $_FILE
* @param target Full path to store uploaded file without extension
* @param maxFileSize Maximum file size in bytes
* @param mimeTypePrefix Detected file mime type must start with or equal to this string
* @param mimeMap Map mime type to file extension
* @param ensureInMimeMap Files mime type MUST exist in mimeMap
* @param allowOverride Allows overriding existing files
*/
public static function receiveUploadedFile(array $fileItem, string $target,
int $maxFileSize = 0, ?string $mimeTypePrefix = null,
array $mimeMap = self::FileTypeMap, bool $ensureInMimeMap = true,
bool $allowOverride = false)
{
// https://dev.to/einlinuus/how-to-upload-files-with-php-correctly-and-securely-1kng
// https://www.php.net/manual/en/features.file-upload.post-method.php
// handle error codes from $_FILE item
if (!array_key_exists('error', $fileItem))
{
return [ false, 'php_no_error_info', 'There was no error info in the file array', null, null, null, null, null ];
}
else if ($fileItem['error'] == UPLOAD_ERR_CANT_WRITE)
{
return [ false, 'php_cant_write', 'Failed to write upload to disk', null, null, null, null, null ];
}
else if ($fileItem['error'] == UPLOAD_ERR_EXTENSION)
{
return [ false, 'php_stopped_by_extension', 'Upload was stopped by PHP extension', null, null, null, null, null ];
}
else if ($fileItem['error'] == UPLOAD_ERR_FORM_SIZE)
{
return [ false, 'php_too_large_form', 'File larger than defined in form', null, null, null, null, null ];
}
else if ($fileItem['error'] == UPLOAD_ERR_INI_SIZE)
{
return [ false, 'php_too_large_ini', 'File larger than defined in php.ini', null, null, null, null, null ];
}
else if ($fileItem['error'] == UPLOAD_ERR_NO_FILE)
{
return [ false, 'php_no_file', 'No file was uploaded', null, null, null, null, null ];
}
else if ($fileItem['error'] == UPLOAD_ERR_NO_TMP_DIR)
{
return [ false, 'php_no_tmp_dir', 'Temporary folder was missing', null, null, null, null, null ];
}
else if ($fileItem['error'] == UPLOAD_ERR_PARTIAL)
{
return [ false, 'php_partial', 'File was only uploaded partially', null, null, null, null, null ];
}
if ($fileItem['error'] !== UPLOAD_ERR_OK)
{
return [ false, 'php_unexpected_upload_error', 'Unhandled PHP upload error', null, null, null, null, null ];
}
// file must exist
$tempFile = null;
if (isset($fileItem['tmp_name']) && is_file($fileItem['tmp_name']))
{
$tempFile = $fileItem['tmp_name'];
}
if (empty($tempFile))
{
return [ false, 'no_file', 'File could not be found in temporary folder', null, null, null, null, null ];
}
// file must be an uploaded file
if (!is_uploaded_file($tempFile))
{
return [ false, 'not_uploaded', 'File is not an uploaded file', null, null, null, null, null ];
}
// check file size
$fileSize = filesize($tempFile);
if ($fileSize <= 0)
{
return [ false, 'file_empty', 'File is empty', null, null, null, null, null ];
}
if ($maxFileSize > 0 && $fileSize > $maxFileSize)
{
return [ false, 'file_too_large', 'File larger than '.$maxFileSize.' bytes', null, null, null, null, null ];
}
// get mime type from file
$fileInfo = finfo_open(FILEINFO_MIME_TYPE);
$fileType = finfo_file($fileInfo, $tempFile);
if (!empty($mimeTypePrefix) && empty($fileType))
{
return [ false, 'unknown_filetype', 'Unable to determine file type', null, null, null, null, null ];
}
if (!empty($mimeTypePrefix) && strpos($fileType, $mimeTypePrefix) !== 0)
{
return [ false, 'unexpected_filetype', 'Unexpected file type '.$fileType, null, null, null, null, null ];
}
// find file extension by mime
$fileExtension = null;
if (count($mimeMap) > 0)
{
$mimeResult = array_filter($mimeMap, function($mapMime) use($fileType)
{
return $mapMime == $fileType || strpos($fileType, $mapMime.';') === 0;
},
ARRAY_FILTER_USE_KEY);
if (count($mimeResult) > 0)
{
$fileExtension = reset($mimeResult);
}
}
if ($ensureInMimeMap && empty($fileExtension))
{
return [ false, 'not_in_mimemap', 'Type '.$fileType.' was not found in mime map', null, null, null, null, null ];
}
// use detected extension in filename
$name = 'untitled.ukn';
if (isset($fileItem['name']) && !empty($fileItem['name']))
{
$name = $fileItem['name'];
}
$nameNoExt = pathinfo($name, PATHINFO_FILENAME);
$finalName = $name;
$finalTarget = $target;
if (!empty($nameNoExt) && !empty($fileExtension))
{
$finalName = $nameNoExt.$fileExtension;
$finalTarget = $finalTarget.$fileExtension;
}
// prevent overriding
if (!$allowOverride && file_exists($finalTarget))
{
return [ false, 'file_exists', 'File '.$finalTarget.' already exists', null, null, null, null, null ];
}
// move uploaded file to requested destination
if (!move_uploaded_file($tempFile, $finalTarget))
{
return [ false, 'move_failed', 'Moving uploaded file to '.$finalTarget.' failed', null, null, null, null, null ];
}
return [ true, 'ok', 'Success', $finalName, $finalTarget, $fileExtension, $fileType, $fileSize ];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment