PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / services / ImportUploadValidator.php

ImportUploadValidator.php in 404 Solution trunk, at includes/services/ImportUploadValidator.php

78 lines 2.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Validates uploaded import files before the importer reads CSV content.
9 */
10 class ABJ_404_Solution_ImportUploadValidator {
11
12 /**
13 * @param array<mixed> $file
14 * @return string Empty on success, error message on failure.
15 */
16 function validate(array $file): string {
17 $allowed_extensions = array('csv', 'txt');
18 $file_ext = strtolower(pathinfo($this->stringField($file, 'name'), PATHINFO_EXTENSION));
19 if (!in_array($file_ext, $allowed_extensions)) {
20 return __('Error: Invalid file type. Only CSV/TXT files are allowed.', '404-solution');
21 }
22
23 $max_file_size = 5 * 1024 * 1024;
24 if ($this->intField($file, 'size') > $max_file_size) {
25 return __('Error: File too large. Maximum size is 5MB.', '404-solution');
26 }
27
28 $allowed_mime_types = array('text/csv', 'text/plain', 'application/csv', 'text/comma-separated-values', 'application/vnd.ms-excel');
29 // DESIGN-AUDIT-OK: the missing finfo_close() is deliberate, not an
30 // oversight. Measured on PHP 8.5.9: 300 validate() calls that never
31 // close the handle grow the process descriptor count by 0, because PHP
32 // refcounts the handle and frees it when $finfo leaves scope. (Positive
33 // control for that measurement: 50 handles held live in an array grew
34 // the count by 50, so the probe can detect growth.) finfo_close() is
35 // deprecated as of PHP 8.5 -- "finfo objects are freed automatically"
36 // -- so calling it would emit a deprecation notice on supported
37 // installs in exchange for fixing nothing.
38 $finfo = finfo_open(FILEINFO_MIME_TYPE);
39 if ($finfo === false) {
40 return __('Error: Unable to determine file type.', '404-solution');
41 }
42 $mime_type = finfo_file($finfo, $this->tmpName($file));
43 if (!in_array($mime_type, $allowed_mime_types)) {
44 return __('Error: Invalid file type. Only CSV files are allowed.', '404-solution');
45 }
46
47 return '';
48 }
49
50 /**
51 * @param array<mixed> $file
52 * @return string
53 */
54 function tmpName(array $file): string {
55 return $this->stringField($file, 'tmp_name');
56 }
57
58 /**
59 * @param array<mixed> $file
60 * @param string $key
61 * @return string
62 */
63 private function stringField(array $file, string $key): string {
64 $value = $file[$key] ?? '';
65 return is_scalar($value) ? (string)$value : '';
66 }
67
68 /**
69 * @param array<mixed> $file
70 * @param string $key
71 * @return int
72 */
73 private function intField(array $file, string $key): int {
74 $value = $file[$key] ?? 0;
75 return is_numeric($value) ? (int)$value : 0;
76 }
77 }
78