From 8cec3cc3058adc348218ef9617d43f9e11af3f85 Mon Sep 17 00:00:00 2001 From: plegall Date: Tue, 21 Apr 2026 16:39:04 +0200 Subject: [PATCH] fixes #2550 checks MIME type against each uploaded file --- admin/include/functions.php | 25 ++++++ admin/include/functions_upload.inc.php | 101 +++++++++++++++++-------- admin/intro.php | 1 + admin/photos_add_direct.php | 2 + include/config_default.inc.php | 57 ++++++++++++++ 5 files changed, 153 insertions(+), 33 deletions(-) diff --git a/admin/include/functions.php b/admin/include/functions.php index c5a26ebca..d0db9576e 100644 --- a/admin/include/functions.php +++ b/admin/include/functions.php @@ -3662,6 +3662,31 @@ SELECT } } +/** + * Displays a page warning if no MIME type is defined for an upload-authorized file extension + * + * @since 17.0.0 + */ +function check_authorized_file_extension_mime_types() +{ + global $conf, $page; + + if (!is_webmaster()) + { + return; + } + + $authorized_file_extensions = $conf['upload_form_all_types'] ? $conf['file_ext'] : $conf['picture_ext']; + + foreach ($authorized_file_extensions as $ext) + { + if (!isset($conf['mime_types_for_ext'][$ext])) + { + $page['warnings'][] = 'File extension "'.$ext.'" is authorized for upload but there is no $conf[\'mime_types_for_ext\'][\''.$ext.'\'] defined. Fix this.'; + } + } +} + /** * Return latest news from piwigo.org. * diff --git a/admin/include/functions_upload.inc.php b/admin/include/functions_upload.inc.php index 474062bd3..780bab0ab 100644 --- a/admin/include/functions_upload.inc.php +++ b/admin/include/functions_upload.inc.php @@ -231,44 +231,34 @@ SELECT $filename_wo_ext = $date_string.'-'.$random_string; $file_path = $upload_dir.'/'.$filename_wo_ext.'.'; - list($width, $height, $type) = getimagesize($source_filepath); - - if (IMAGETYPE_PNG == $type) - { - $file_path.= 'png'; - } - elseif (IMAGETYPE_GIF == $type) - { - $file_path.= 'gif'; - } - elseif (IMAGETYPE_JPEG == $type) - { - $file_path.= 'jpg'; - } - elseif (IMAGETYPE_WEBP == $type) - { - $file_path.= 'webp'; - } - elseif (isset($conf['upload_form_all_types']) and $conf['upload_form_all_types']) - { - $original_extension = strtolower(get_extension($original_filename)); + $authorized_file_extensions = $conf['upload_form_all_types'] ? $conf['file_ext'] : $conf['picture_ext']; - if (in_array($original_extension, $conf['file_ext'])) - { - $file_path.= $original_extension; - } - else - { - unlink($source_filepath); - die('unexpected file type'); - } - } - else + $original_extension = strtolower(get_extension($original_filename)); + + if (!in_array($original_extension, $authorized_file_extensions)) { unlink($source_filepath); - die('forbidden file type'); + + $error_msg = 'forbidden file type'; + + if (defined('IN_WS')) + { + global $service; + $service->sendResponse(new PwgError(415, $error_msg)); + exit; + } + + die($error_msg); } + pwg_check_real_extension($source_filepath, $original_filename, true); + + $file_extension_replace_by = array( + 'jpeg' => 'jpg', + ); + + $file_path .= $file_extension_replace_by[$original_extension] ?? $original_extension; + prepare_directory($upload_dir); $file_path_pattern = $file_path; @@ -979,6 +969,51 @@ function pwg_image_infos($path) ); } +function pwg_check_real_extension($source_filepath, $original_filename, $die_on_error=true) +{ + global $conf, $logger; + + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $finfo_type = finfo_file($finfo, $source_filepath); + finfo_close($finfo); + + $original_extension = strtolower(get_extension($original_filename)); + + if (!isset($conf['mime_types_for_ext'][$original_extension])) + { + // not a situation we like: the extension is authorized for upload but + // not listed for MIME type check. See function check_authorized_file_extension_mime_types + return true; + } + + if (!in_array($finfo_type, $conf['mime_types_for_ext'][$original_extension])) + { + $error_msg = 'File extension "'.$original_extension.'" for file "'.$original_filename.'" does not match file MIME type "'.$finfo_type.'"'; + + $logger->info(__FUNCTION__.' '.$error_msg); + + if ($die_on_error) + { + unlink($source_filepath); + + if (defined('IN_WS')) + { + global $service; + $service->sendResponse(new PwgError(415, $error_msg)); + exit; + } + + die($error_msg); + } + + return false; + } + + $logger->info(__FUNCTION__.' file_ext='.$original_extension.' Vs finfo_type='.$finfo_type); + + return true; +} + function is_valid_image_extension($extension) { global $conf; diff --git a/admin/intro.php b/admin/intro.php index 1cbf28507..30e7d92da 100644 --- a/admin/intro.php +++ b/admin/intro.php @@ -92,6 +92,7 @@ if ($locked_album > 0) } fs_quick_check(); +check_authorized_file_extension_mime_types(); // +-----------------------------------------------------------------------+ // | template init | diff --git a/admin/photos_add_direct.php b/admin/photos_add_direct.php index 21fc3e372..f38d51c55 100644 --- a/admin/photos_add_direct.php +++ b/admin/photos_add_direct.php @@ -160,5 +160,7 @@ $template->assign(array( 'str_format_ext' => implode(', ', $conf['format_ext']), )); +check_authorized_file_extension_mime_types(); + $template->assign_var_from_handle('ADMIN_CONTENT', 'photos_add'); ?> diff --git a/include/config_default.inc.php b/include/config_default.inc.php index 4dc7fec47..908e5fbd7 100644 --- a/include/config_default.inc.php +++ b/include/config_default.inc.php @@ -56,6 +56,63 @@ $conf['file_ext'] = array_merge( array('tiff', 'tif', 'mpg','zip','avi','mp3','ogg','pdf','svg', 'heic') ); +// mime_types_for_ext : list of valid/expected MIME types for each file extension. +// +// Every permitted file extension authorized for upload should be listed. +// Otherwise Piwigo won't be able to check. +$conf['mime_types_for_ext'] = array( + '3gp' => ['video/3gpp', 'audio/3gpp'], + 'ai' => ['application/postscript'], + 'avi' => ['video/x-msvideo'], + 'avif' => ['image/avif'], + 'bmp' => ['image/bmp'], + 'cr2' => ['image/x-canon-cr2'], + 'dng' => ['image/x-adobe-dng'], + 'doc' => ['application/msword'], + 'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'], + 'eps' => ['application/postscript'], + 'flv' => ['video/x-flv'], + 'gif' => ['image/gif'], + 'gp3' => ['video/3gpp'], + 'gp4' => ['video/3gpp'], + 'gpx' => ['application/gpx+xml'], + 'heic' => ['image/heic', 'image/heif'], + 'ico' => ['image/x-icon'], + 'indd' => ['application/x-indesign'], + 'jpeg' => ['image/jpeg'], + 'jpg' => ['image/jpeg'], + 'm4a' => ['audio/mp4', 'audio/x-m4a'], + 'm4v' => ['video/x-m4v'], + 'mkv' => ['video/x-matroska'], + 'mov' => ['video/quicktime'], + 'mp3' => ['audio/mpeg'], + 'mp4' => ['video/mp4'], + 'mpeg' => ['video/mpeg'], + 'mpg' => ['video/mpeg'], + 'nef' => ['image/x-nikon-nef'], + 'odt' => ['application/vnd.oasis.opendocument.text'], + 'ogg' => ['audio/ogg', 'application/ogg'], + 'ogv' => ['video/ogg'], + 'pdf' => ['application/pdf'], + 'png' => ['image/png'], + 'ppt' => ['application/vnd.ms-powerpoint'], + 'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation'], + 'psd' => ['image/vnd.adobe.photoshop'], + 'rar' => ['application/x-rar-compressed', 'application/vnd.rar'], + 'strm' => ['application/x-ms-wmp'], + 'svg' => ['image/svg', 'image/svg+xml'], + 'tif' => ['image/tiff'], + 'tiff' => ['image/tiff'], + 'txt' => ['text/plain'], + 'wav' => ['audio/wav', 'audio/x-wav'], + 'webm' => ['video/webm'], + 'webp' => ['image/webp'], + 'wmv' => ['video/x-ms-wmv'], + 'xls' => ['application/vnd.ms-excel'], + 'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], + 'zip' => ['application/zip'], +); + // enable_formats: should Piwigo search for multiple formats? $conf['enable_formats'] = false;