| @@ -7,8 +7,9 @@ | ||
| 7 | 7 | use FluentBoards\Framework\Support\Arr; |
| 8 | 8 | |
| 9 | 9 | class UploadService |
| 10 | 10 | { |
| 11 | + const MAX_FILE_UPLOAD_BYTES = 104857600; | |
| 11 | 12 | |
| 12 | 13 | /** |
| 13 | 14 | * @throws \Exception |
| 14 | 15 | */ |
| @@ -25,32 +26,86 @@ | ||
| 25 | 26 | |
| 26 | 27 | public function validateFile($file) |
| 27 | 28 | { |
| 28 | 29 | if (!$file) { |
| 29 | - throw new \Exception('File is empty.'); | |
| 30 | + throw new \Exception(esc_html__('File is empty.', 'fluent-boards')); | |
| 30 | 31 | } |
| 31 | 32 | if (!$this->isFileTypeSupported($file)) { |
| 32 | - throw new \Exception('File type not supported'); | |
| 33 | + throw new \Exception(esc_html__('File type not supported', 'fluent-boards')); | |
| 33 | 34 | } |
| 34 | - if ($file['size'] > $this->getFileUploadLimit()) { | |
| 35 | - throw new \Exception('File size is too large'); | |
| 35 | + if ($file['size_in_bytes'] > $this->getFileUploadLimit()) { | |
| 36 | + throw new \Exception(esc_html__('File size is too large', 'fluent-boards')); | |
| 36 | 37 | } |
| 37 | 38 | } |
| 38 | 39 | |
| 39 | 40 | public function getFileUploadLimit() { |
| 40 | - // Logic for calculating file upload limit as in your original code | |
| 41 | - return min( | |
| 42 | - wp_convert_hr_to_bytes(ini_get('upload_max_filesize')), | |
| 43 | - wp_convert_hr_to_bytes(ini_get('post_max_size')), | |
| 44 | - wp_max_upload_size() | |
| 45 | - ); | |
| 41 | + return (int) apply_filters('fluent_boards/upload_file_size_limit', self::MAX_FILE_UPLOAD_BYTES); | |
| 46 | 42 | } |
| 47 | 43 | |
| 48 | 44 | public function isFileTypeSupported($file) |
| 49 | 45 | { |
| 50 | - // Define supported file types that are generally allowed by user | |
| 51 | - $allowedMimeTypes = get_allowed_mime_types(); | |
| 52 | - // Check if the file type is supported | |
| 53 | - return in_array(strtolower($file['type']), $allowedMimeTypes); | |
| 46 | + // Validate by extension against our allow-list. This is more reliable than the | |
| 47 | + // browser-provided mime, which is empty/inconsistent for types like .json and .md. | |
| 48 | + $extension = strtolower(pathinfo(Arr::get($file, 'name', ''), PATHINFO_EXTENSION)); | |
| 49 | + if (!$extension) { | |
| 50 | + return false; | |
| 51 | + } | |
| 52 | + | |
| 53 | + foreach (array_keys(self::getAllowedMimeMap()) as $extensionPattern) { | |
| 54 | + if (in_array($extension, explode('|', $extensionPattern), true)) { | |
| 55 | + return true; | |
| 56 | + } | |
| 57 | + } | |
| 58 | + | |
| 59 | + return false; | |
| 54 | 60 | } |
| 55 | 61 | |
| 56 | -} | |
| 62 | + /** | |
| 63 | + * Allow-list of upload types as an extension => mime map (WordPress defaults plus | |
| 64 | + * common developer/document formats). Shared by validateFile() and wp_handle_upload() | |
| 65 | + * so both agree. Executable/script types are intentionally excluded. | |
| 66 | + * | |
| 67 | + * @return array | |
| 68 | + */ | |
| 69 | + public static function getAllowedMimeMap() | |
| 70 | + { | |
| 71 | + $extraMimes = [ | |
| 72 | + 'json' => 'application/json', | |
| 73 | + 'md' => 'text/markdown', | |
| 74 | + 'markdown' => 'text/markdown', | |
| 75 | + 'csv' => 'text/csv', | |
| 76 | + 'txt' => 'text/plain', | |
| 77 | + 'log' => 'text/plain', | |
| 78 | + 'xml' => 'text/xml', | |
| 79 | + 'yaml|yml' => 'text/yaml', | |
| 80 | + 'webp' => 'image/webp', | |
| 81 | + 'avif' => 'image/avif', | |
| 82 | + 'heic' => 'image/heic', | |
| 83 | + 'zip' => 'application/zip', | |
| 84 | + 'rar' => 'application/vnd.rar', | |
| 85 | + '7z' => 'application/x-7z-compressed', | |
| 86 | + 'tar' => 'application/x-tar', | |
| 87 | + 'gz|gzip' => 'application/gzip', | |
| 88 | + ]; | |
| 89 | + | |
| 90 | + $map = array_merge(get_allowed_mime_types(), $extraMimes); | |
| 91 | + $map = apply_filters('fluent_boards/upload_allowed_mimes', $map); | |
| 92 | + | |
| 93 | + // Never allow executable or browser-active formats through plugin filters. | |
| 94 | + $blockedExtensions = [ | |
| 95 | + 'php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phtml', 'phar', | |
| 96 | + 'html', 'htm', 'shtml', 'xhtml', 'xht', | |
| 97 | + 'js', 'mjs', 'svg', 'svgz', 'xml', 'xsl', 'xslt', 'swf', 'htaccess', | |
| 98 | + ]; | |
| 99 | + $safeMap = []; | |
| 100 | + | |
| 101 | + foreach ($map as $extensionPattern => $mimeType) { | |
| 102 | + $extensions = array_diff(explode('|', strtolower($extensionPattern)), $blockedExtensions); | |
| 103 | + if ($extensions) { | |
| 104 | + $safeMap[implode('|', $extensions)] = $mimeType; | |
| 105 | + } | |
| 106 | + } | |
| 107 | + | |
| 108 | + return $safeMap; | |
| 109 | + } | |
| 110 | + | |
| 111 | +} | |