PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.0
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
← All changes | back/Helpers/global/file.php +227 -242 10.0.1 → 11.1.0 View file →
@@ -1,9 +1,13 @@
1 1 <?php
2 +defined('ABSPATH') || die('Restricted Access');
2 3
3 4 use AcyMailing\Helpers\ImageHelper;
4 5
5 -function acym_bytes($val)
6 +/**
7 + * Function to return the number of bytes of a val like 2M
8 + */
9 +function acym_bytes(string $val): int
6 10 {
7 11 $val = trim($val);
8 12 if (empty($val)) {
9 13 return 0;
@@ -23,14 +27,18 @@
23 27
24 28 return (int)$val;
25 29 }
26 30
27 -function acym_createDir($dir, $report = true, $secured = false)
31 +//Create a dir and an index.html file inside
32 +function acym_createDir(string $dir, bool $report = true, bool $secured = false): bool
28 33 {
29 - if (is_dir($dir)) return true;
34 + if (is_dir($dir)) {
35 + return true;
36 + }
30 37
31 38 $indexhtml = '<html><body bgcolor="#FFFFFF"></body></html>';
32 39
40 + //Create the directory with an index file inside
33 41 try {
34 42 $status = acym_createFolder($dir);
35 43 } catch (Exception $e) {
36 44 $status = false;
@@ -49,12 +57,10 @@
49 57 } catch (Exception $e) {
50 58 $status = false;
51 59 }
52 60
53 - if (!$status) {
54 - if ($report) {
55 - acym_display('Could not create the file '.$dir.DS.'index.html', 'error');
56 - }
61 + if (!$status && $report) {
62 + acym_display('Could not create the file '.$dir.DS.'index.html', 'error');
57 63 }
58 64
59 65 if ($secured) {
60 66 try {
@@ -63,12 +69,10 @@
63 69 } catch (Exception $e) {
64 70 $status = false;
65 71 }
66 72
67 - if (!$status) {
68 - if ($report) {
69 - acym_display('Could not create the file '.$dir.DS.'.htaccess', 'error');
70 - }
73 + if (!$status && $report) {
74 + acym_display('Could not create the file '.$dir.DS.'.htaccess', 'error');
71 75 }
72 76 }
73 77
74 78 return $status;
@@ -73,12 +77,19 @@
73 77
74 78 return $status;
75 79 }
76 80
77 -function acym_importFile($file, $uploadPath, $onlyPict, $maxwidth = '')
81 +function acym_isForbiddenFileExtension(string $filename): bool
78 82 {
79 - acym_checkToken();
83 + // Always blocked regardless of the "allowed files" configuration, for security reasons
84 + return (bool)preg_match('#\.(php.?|.?htm.?|pl|py|jsp|asp|sh|cgi)#Ui', $filename);
85 +}
80 86
87 +function acym_importFile(array $file, string $uploadPath, bool $onlyPict): ?string
88 +{
89 + // Check the token... no import without token!
90 + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token');
91 +
81 92 $config = acym_config();
82 93 $additionalMsg = '';
83 94
84 95 if ($file['error'] > 0) {
@@ -99,19 +110,19 @@
99 110 ];
100 111
101 112 acym_enqueueMessage(acym_translationSprintf('ACYM_ERROR_UPLOADING_FILE_X', $phpFileUploadErrors[$file['error']]), 'error');
102 113
103 - return false;
114 + return null;
104 115 }
105 116
106 117 acym_createDir($uploadPath, true);
107 118
108 - if (!is_writable($uploadPath)) {
109 - @chmod($uploadPath, '0755');
110 - if (!is_writable($uploadPath)) {
119 + if (!acym_isWritable($uploadPath)) {
120 + acym_chmod($uploadPath, 0755);
121 + if (!acym_isWritable($uploadPath)) {
111 122 acym_display(acym_translationSprintf('ACYM_WRITABLE_FOLDER', $uploadPath), 'error');
112 123
113 - return false;
124 + return null;
114 125 }
115 126 }
116 127
117 128 if ($onlyPict) {
@@ -119,31 +130,35 @@
119 130 } else {
120 131 $allowedExtensions = explode(',', $config->get('allowed_files'));
121 132 }
122 133
134 + // We don't allow to upload anything else than a picture extension
123 135 if (!preg_match('#\.('.implode('|', $allowedExtensions).')$#Ui', $file['name'], $extension)) {
124 136 $ext = substr($file['name'], strrpos($file['name'], '.') + 1);
125 137 acym_display(
126 138 acym_translationSprintf(
127 139 'ACYM_ACCEPTED_TYPE',
128 - acym_escape($ext),
140 + esc_html($ext),
129 141 implode(', ', $allowedExtensions)
130 142 ),
131 143 'error'
132 144 );
133 145
134 - return false;
146 + return null;
135 147 }
136 148
137 - if (preg_match('#\.(php.?|.?htm.?|pl|py|jsp|asp|sh|cgi)#Ui', $file['name'])) {
149 + // We will never allow some files to be uploaded...
150 + // This should never happen... only if there is an hack tentative so no need to translate this error message
151 + if (acym_isForbiddenFileExtension($file['name'])) {
138 152 acym_display(
139 153 'This extension name is blocked by the system regardless your configuration for security reasons',
140 154 'error'
141 155 );
142 156
143 - return false;
157 + return null;
144 158 }
145 159
160 + // We remove all dots or space from the file name to avoid the double extension security issue and the fact some mail clients don't like spaces
146 161 $file['name'] = str_replace(
147 162 ['-', '.', ' ', '\''],
148 163 '_',
149 164 substr($file['name'], 0, strrpos($file['name'], '.'))
@@ -149,13 +164,14 @@
149 164 substr($file['name'], 0, strrpos($file['name'], '.'))
150 165 ).'.'.$extension[1];
151 166
152 167 if ($onlyPict) {
168 + // Extra security: we make sure we only have a valid picture format
153 169 $imageSize = @getimagesize($file['tmp_name']);
154 170 if (empty($imageSize)) {
155 171 acym_display(acym_translation('ACYM_INVALID_IMAGE'), 'error');
156 172
157 - return false;
173 + return null;
158 174 }
159 175 }
160 176
161 177 if (file_exists($uploadPath.DS.$file['name'])) {
@@ -175,36 +191,30 @@
175 191 }
176 192 }
177 193
178 194 if (!acym_uploadFile($file['tmp_name'], rtrim($uploadPath, DS).DS.$file['name'])) {
179 - if (!move_uploaded_file($file['tmp_name'], rtrim($uploadPath, DS).DS.$file['name'])) {
180 - acym_display(
181 - acym_translationSprintf(
182 - 'ACYM_FAIL_UPLOAD',
183 - '<b><i>'.acym_escape($file['tmp_name']).'</i></b>',
184 - '<b><i>'.acym_escape(rtrim($uploadPath, DS).DS.$file['name']).'</i></b>'
185 - ),
186 - 'error'
187 - );
195 + acym_display(
196 + acym_translationSprintf(
197 + 'ACYM_FAIL_UPLOAD',
198 + '<b><i>'.esc_html($file['tmp_name']).'</i></b>',
199 + '<b><i>'.esc_html(rtrim($uploadPath, DS).DS.$file['name']).'</i></b>'
200 + ),
201 + 'error'
202 + );
188 203
189 - return false;
190 - }
204 + return null;
191 205 }
192 206
193 - if (!empty($maxwidth) || ($onlyPict && $imageSize[0] > 1000)) {
207 + if ($onlyPict && $imageSize[0] > 1000) {
194 208 $imageHelper = new ImageHelper();
195 209 if ($imageHelper->available()) {
196 210 $imageHelper->maxHeight = 9999;
197 - if (empty($maxwidth)) {
198 - $imageHelper->maxWidth = 700;
199 - } else {
200 - $imageHelper->maxWidth = $maxwidth;
201 - }
211 + $imageHelper->maxWidth = 700;
202 212 $message = 'ACYM_IMAGE_RESIZED';
203 213 $imageHelper->destination = $uploadPath;
204 214 $thumb = $imageHelper->generateThumbnail(rtrim($uploadPath, DS).DS.$file['name']);
205 - $resize = acym_moveFile($thumb['file'], $uploadPath.DS.$file['name']);
206 - if ($thumb) {
215 + if (!empty($thumb)) {
216 + acym_moveFile($thumb['file'], $uploadPath.DS.$file['name']);
207 217 $additionalMsg .= '<br />'.acym_translation($message);
208 218 }
209 219 }
210 220 }
@@ -212,35 +222,74 @@
212 222
213 223 return $file['name'];
214 224 }
215 225
216 -function acym_inputFile($name, $value = '', $id = '', $class = '', $attributes = '')
217 -{
218 - $return = '<div class="cell '.$class.' grid-x"><input '.$attributes.' style="display: none" id="'.$id.'" type="file" name="'.$name.'"><button type="button" class=" acym__button__file button button-secondary cell shrink">'.acym_translation(
219 - 'ACYM_CHOOSE_FILE'
220 - ).'</button><span class="cell shrink margin-left-2">';
221 - $return .= empty($value) ? acym_translation('ACYM_NO_FILE_CHOSEN') : $value;
222 - $return .= '</span></div>';
226 +function acym_inputFile(
227 + string $name,
228 + string $value = '',
229 + string $class = '',
230 + array $attributes = [],
231 + string $downloadUrl = ''
232 +): void {
233 + $hasValue = !empty($value);
234 + $noFileText = acym_translation('ACYM_NO_FILE_CHOSEN');
235 + $baseName = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name;
236 + ?>
223 237
224 - return $return;
238 + <div class="cell <?php echo esc_attr($class); ?> grid-x acym__input__file__container">
239 + <input type="hidden" name="<?php echo esc_attr($baseName); ?>" value="<?php echo esc_attr($value); ?>" class="acym__input__file__clear">
240 + <input <?php
241 + foreach ($attributes as $oneAttribute => $oneValue) {
242 + if (is_array($oneValue) || is_object($oneValue)) {
243 + $oneValue = json_encode($oneValue);
244 + } elseif ($oneValue === true) {
245 + $oneValue = $oneAttribute;
246 + }
247 +
248 + echo ' '.esc_attr($oneAttribute).'="'.esc_attr($oneValue).'"';
249 + }
250 + ?> style="display: none" type="file" name="<?php echo esc_attr($name); ?>">
251 + <button type="button" class="acym__button__file button button-secondary cell shrink">
252 + <?php echo esc_html(acym_translation('ACYM_CHOOSE_FILE')); ?>
253 + </button>
254 + <span class="cell shrink margin-left-2 margin-right-2 acym__input__file__name" data-no-file="<?php echo esc_attr($noFileText); ?>">
255 + <?php echo esc_html($hasValue ? $value : $noFileText); ?>
256 + </span>
257 + <i class="acym__input__file__download cell shrink acym__color__blue cursor-pointer acymicon-download margin-right-1"
258 + <?php echo $hasValue ? '' : ' style="display: none;"'; ?>
259 + <?php echo !empty($downloadUrl) ? ' data-download-url="'.esc_url($downloadUrl).'"' : ''; ?>
260 + title="<?php echo esc_attr(acym_translation('ACYM_DOWNLOAD')); ?>" aria-label="<?php echo esc_attr(acym_translation('ACYM_DOWNLOAD')); ?>"></i>
261 + <i class="acymicon-close acym__color__red acym__input__file__delete cursor-pointer cell shrink margin-left-1"
262 + <?php echo $hasValue ? '' : ' style="display: none;"'; ?>
263 + title="<?php echo esc_attr(acym_translation('ACYM_DELETE')); ?>" aria-label="<?php echo esc_attr(acym_translation('ACYM_DELETE')); ?>"></i>
264 + </div>
265 + <?php
225 266 }
226 267
227 -function acym_getFilesFolder($ignoreVariables = false)
268 +function acym_getFilesFolder(bool $ignoreVariables = false): string
228 269 {
229 270 $config = acym_config();
230 271 $uploadFolder = $config->get('uploadfolder', ACYM_UPLOAD_FOLDER);
231 - if ($ignoreVariables) $uploadFolder = str_replace(['{userid}', '{groupname}'], '', $uploadFolder);
272 + if ($ignoreVariables) {
273 + $uploadFolder = str_replace(['{userid}', '{groupname}', '{year}', '{month}', '{day}'], '', $uploadFolder);
274 + }
232 275 $uploadFolder = trim($uploadFolder, '/');
233 276
234 - if (strpos($uploadFolder, '{userid}') !== false) {
235 - $uploadFolder = str_replace('{userid}', acym_currentUserId(), $uploadFolder);
236 - }
277 + $uploadFolder = str_replace('{userid}', acym_currentUserId(), $uploadFolder);
278 + $uploadFolder = str_replace('{year}', gmdate('Y'), $uploadFolder);
279 + $uploadFolder = str_replace('{month}', gmdate('m'), $uploadFolder);
280 + $uploadFolder = str_replace('{day}', gmdate('d'), $uploadFolder);
237 281
238 282 return acym_replaceGroupTags($uploadFolder);
239 283 }
240 284
241 -function acym_generateArborescence($folders)
285 +/**
286 + * Find all sub folders...
287 + * Return an array of all folders ordered properly
288 + */
289 +function acym_generateArborescence(array $folders): array
242 290 {
291 + //Recursive algorythm to catch subfolders
243 292 $folderList = [];
244 293 foreach ($folders as $folder) {
245 294 $folderPath = acym_cleanPath(ACYM_ROOT.trim(str_replace('/', DS, trim($folder)), DS));
246 295 if (!file_exists($folderPath)) {
@@ -258,9 +307,9 @@
258 307
259 308 return $folderList;
260 309 }
261 310
262 -function acym_makeSafeFile($file)
311 +function acym_makeSafeFile(string $file): string
263 312 {
264 313 $file = rtrim($file, '.');
265 314 $regex = ['#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#', '#^\.#'];
266 315
@@ -266,63 +315,18 @@
266 315
267 316 return trim(preg_replace($regex, '', $file));
268 317 }
269 318
270 -function acym_deleteFolder($path, $report = true)
271 -{
319 +function acym_getFolders(
320 + string $path,
321 + string $filter = '.',
322 + bool $recurse = false,
323 + bool $full = false,
324 + array $exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'],
325 + array $excludefilter = ['^\..*']
326 +): array {
272 327 $path = acym_cleanPath($path);
273 - if (!is_dir($path)) {
274 - if ($report) acym_enqueueMessage(acym_translationSprintf('ACYM_IS_NOT_A_FOLDER', $path), 'error');
275 328
276 - return false;
277 - }
278 -
279 - $files = acym_getFiles($path, '.', false, false, [], []);
280 - if (!empty($files)) {
281 - foreach ($files as $oneFile) {
282 - if (!acym_deleteFile($path.DS.$oneFile, $report)) {
283 - return false;
284 - }
285 - }
286 - }
287 -
288 - $folders = acym_getFolders($path, '.', false, false, []);
289 - if (!empty($folders)) {
290 - foreach ($folders as $oneFolder) {
291 - if (!acym_deleteFolder($path.DS.$oneFolder, $report)) {
292 - return false;
293 - }
294 - }
295 - }
296 -
297 - if (@rmdir($path)) {
298 - $ret = true;
299 - } else {
300 - if ($report) acym_enqueueMessage(acym_translationSprintf('ACYM_COULD_NOT_DELETE_FOLDER', $path), 'error');
301 - $ret = false;
302 - }
303 -
304 - return $ret;
305 -}
306 -
307 -function acym_createFolder($path = '', $mode = 0755)
308 -{
309 - $path = acym_cleanPath($path);
310 - if (file_exists($path)) {
311 - return true;
312 - }
313 -
314 - $origmask = @umask(0);
315 - $ret = @mkdir($path, $mode, true);
316 - @umask($origmask);
317 -
318 - return $ret;
319 -}
320 -
321 -function acym_getFolders($path, $filter = '.', $recurse = false, $full = false, $exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'], $excludefilter = ['^\..*'])
322 -{
323 - $path = acym_cleanPath($path);
324 -
325 329 if (!is_dir($path)) {
326 330 acym_enqueueMessage(acym_translationSprintf('ACYM_IS_NOT_A_FOLDER', $path), 'error');
327 331
328 332 return [];
@@ -340,25 +344,25 @@
340 344 return array_values($arr);
341 345 }
342 346
343 347 function acym_getFiles(
344 - $path,
345 - $filter = '.',
346 - $recurse = false,
347 - $full = false,
348 - $exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'],
349 - $excludefilter = [
348 + string $path,
349 + string $filter = '.',
350 + bool $recurse = false,
351 + bool $full = false,
352 + array $exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'],
353 + array $excludefilter = [
350 354 '^\..*',
351 355 '.*~',
352 356 ],
353 - $naturalSort = false
354 -) {
357 + bool $naturalSort = false
358 +): array {
355 359 $path = acym_cleanPath($path);
356 360
357 361 if (!is_dir($path)) {
358 362 acym_enqueueMessage(acym_translationSprintf('ACYM_IS_NOT_A_FOLDER', $path), 'error');
359 363
360 - return false;
364 + return [];
361 365 }
362 366
363 367 if (count($excludefilter)) {
364 368 $excludefilter_string = '/('.implode('|', $excludefilter).')/';
@@ -376,10 +380,17 @@
376 380
377 381 return array_values($arr);
378 382 }
379 383
380 -function acym_getItems($path, $filter, $recurse, $full, $exclude, $excludefilter_string, $findfiles)
381 -{
384 +function acym_getItems(
385 + string $path,
386 + string $filter,
387 + bool $recurse,
388 + bool $full,
389 + array $exclude,
390 + string $excludefilter_string,
391 + bool $findfiles
392 +): array {
382 393 $arr = [];
383 394
384 395 if (!($handle = @opendir($path))) {
385 396 return $arr;
@@ -385,12 +396,14 @@
385 396 return $arr;
386 397 }
387 398
388 399 while (($file = readdir($handle)) !== false) {
389 - if ($file == '.' || $file == '..' || in_array($file, $exclude) || (!empty($excludefilter_string) && preg_match(
390 - $excludefilter_string,
391 - $file
392 - ))) {
400 + if (
401 + $file == '.'
402 + || $file == '..'
403 + || in_array($file, $exclude)
404 + || (!empty($excludefilter_string) && preg_match($excludefilter_string, $file))
405 + ) {
393 406 continue;
394 407 }
395 408 $fullpath = rtrim($path, '/').'/'.$file;
396 409
@@ -404,35 +417,20 @@
404 417 }
405 418 }
406 419
407 420 if ($isDir && $recurse) {
408 - if (is_int($recurse)) {
409 - $arr = array_merge(
410 - $arr,
411 - acym_getItems(
412 - $fullpath,
413 - $filter,
414 - $recurse - 1,
415 - $full,
416 - $exclude,
417 - $excludefilter_string,
418 - $findfiles
419 - )
420 - );
421 - } else {
422 - $arr = array_merge(
423 - $arr,
424 - acym_getItems(
425 - $fullpath,
426 - $filter,
427 - $recurse,
428 - $full,
429 - $exclude,
430 - $excludefilter_string,
431 - $findfiles
432 - )
433 - );
434 - }
421 + $arr = array_merge(
422 + $arr,
423 + acym_getItems(
424 + $fullpath,
425 + $filter,
426 + true,
427 + $full,
428 + $exclude,
429 + $excludefilter_string,
430 + $findfiles
431 + )
432 + );
435 433 }
436 434 }
437 435
438 436 closedir($handle);
@@ -439,12 +437,11 @@
439 437
440 438 return $arr;
441 439 }
442 440
443 -function acym_copyFolder($src, $dest, $path = '', $force = false, $use_streams = false)
441 +function acym_copyFolder(string $src, string $dest, ?string $path = null, bool $force = false, bool $use_streams = false): bool
444 442 {
445 -
446 - if ($path) {
443 + if (!empty($path)) {
447 444 $src = acym_cleanPath($path.'/'.$src);
448 445 $dest = acym_cleanPath($path.'/'.$dest);
449 446 }
450 447
@@ -502,13 +499,13 @@
502 499
503 500 return true;
504 501 }
505 502
506 -function acym_listFolderTree($path, $filter, $maxLevel = 3, $level = 0, $parent = 0)
503 +function acym_listFolderTree(string $path, string $filter, int $maxLevel = 3, int $level = 0, int $parent = 0): array
507 504 {
508 505 $dirs = [];
509 506
510 - if ($level == 0) {
507 + if ($level === 0) {
511 508 $GLOBALS['acym_folder_tree_index'] = 0;
512 509 }
513 510
514 511 if ($level < $maxLevel) {
@@ -531,86 +528,25 @@
531 528
532 529 return $dirs;
533 530 }
534 531
535 -function acym_deleteFile($file, $report = true)
532 +/**
533 + * @param mixed $buffer
534 + */
535 +function acym_writeFile(string $file, $buffer, int $flags = 0): bool
536 536 {
537 - $file = acym_cleanPath($file);
538 - if (!is_file($file)) {
539 - if ($report) acym_enqueueMessage(acym_translationSprintf('ACYM_IS_NOT_A_FILE', $file), 'error');
540 -
537 + if (!file_exists(dirname($file)) && !acym_createFolder(dirname($file))) {
541 538 return false;
542 539 }
543 540
544 - @chmod($file, 0777);
545 -
546 - if (!@unlink($file)) {
547 - $filename = basename($file);
548 - if ($report) acym_enqueueMessage(acym_translationSprintf('ACYM_FAILED_DELETE', $filename), 'error');
549 -
550 - return false;
551 - }
552 -
553 - return true;
554 -}
555 -
556 -function acym_writeFile($file, $buffer, $flags = 0)
557 -{
558 - if (!file_exists(dirname($file)) && acym_createFolder(dirname($file)) == false) {
559 - return false;
560 - }
561 541 $file = acym_cleanPath($file);
562 542
563 543 return is_int(file_put_contents($file, $buffer, $flags));
564 544 }
565 545
566 -function acym_moveFile($src, $dest, $path = '', $use_streams = false)
546 +function acym_copyFile(string $src, string $dest, string $path = ''): bool
567 547 {
568 548 if (!empty($path)) {
569 - $src = acym_cleanPath($path.'/'.$src);
570 - $dest = acym_cleanPath($path.'/'.$dest);
571 - }
572 -
573 - if (!is_readable($src)) {
574 - acym_enqueueMessage(acym_translationSprintf('ACYM_COULD_NOT_FIND_FILE_SOURCE_PERMISSION', $src), 'error');
575 -
576 - return false;
577 - }
578 -
579 - if (!@rename($src, $dest)) {
580 - acym_enqueueMessage(acym_translation('ACYM_COULD_NOT_MOVE_FILE'), 'error');
581 -
582 - return false;
583 - }
584 -
585 - return true;
586 -}
587 -
588 -function acym_uploadFile($src, $dest)
589 -{
590 - $dest = acym_cleanPath($dest);
591 -
592 - $baseDir = dirname($dest);
593 - if (!file_exists($baseDir)) {
594 - acym_createFolder($baseDir);
595 - }
596 -
597 - if (is_writeable($baseDir) && move_uploaded_file($src, $dest)) {
598 - if (@chmod($dest, octdec('0644'))) {
599 - return true;
600 - } else {
601 - acym_enqueueMessage(acym_translation('ACYM_FILE_REJECTED_SAFETY_REASON'), 'error');
602 - }
603 - } else {
604 - acym_enqueueMessage(acym_translationSprintf('ACYM_COULD_NOT_UPLOAD_FILE_PERMISSION', $baseDir), 'error');
605 - }
606 -
607 - return false;
608 -}
609 -
610 -function acym_copyFile($src, $dest, $path = null, $use_streams = false)
611 -{
612 - if ($path) {
613 549 $src = acym_cleanPath($path.DS.$src);
614 550 $dest = acym_cleanPath($path.DS.$dest);
615 551 }
616 552
@@ -628,9 +564,9 @@
628 564
629 565 return true;
630 566 }
631 567
632 -function acym_fileGetExt($file)
568 +function acym_fileGetExt(string $file): string
633 569 {
634 570 $pathInfo = pathinfo($file);
635 571
636 572 return empty($pathInfo['extension']) ? '' : strtolower($pathInfo['extension']);
@@ -635,15 +571,15 @@
635 571
636 572 return empty($pathInfo['extension']) ? '' : strtolower($pathInfo['extension']);
637 573 }
638 574
639 -function acym_cleanPath($path, $ds = DIRECTORY_SEPARATOR)
575 +function acym_cleanPath(string $path, string $ds = DIRECTORY_SEPARATOR): string
640 576 {
641 577 $path = trim($path);
642 578
643 579 if (empty($path)) {
644 580 $path = ACYM_ROOT;
645 - } elseif (($ds == '\\') && substr($path, 0, 2) == '\\\\') {
581 + } elseif (($ds === '\\') && substr($path, 0, 2) == '\\\\') {
646 582 $path = "\\".preg_replace('#[/\\\\]+#', $ds, $path);
647 583 } else {
648 584 $path = preg_replace('#[/\\\\]+#', $ds, $path);
649 585 }
@@ -650,9 +586,9 @@
650 586
651 587 return $path;
652 588 }
653 589
654 -function acym_createArchive($name, $files)
590 +function acym_createArchive(string $name, array $files): bool
655 591 {
656 592 $contents = [];
657 593 $ctrldir = [];
658 594
@@ -701,31 +637,80 @@
701 637
702 638 return acym_writeFile($name.'.zip', $buffer);
703 639 }
704 640
705 -function acym_loaderLogo($wrap = true)
641 +function acym_loaderLogo(bool $withContainer = true): void
706 642 {
707 - $loader = '';
708 - if ($wrap) $loader .= '<div class="cell shrink acym_loader_logo">';
643 + if ($withContainer) {
644 + echo '<div class="cell shrink acym_loader_logo">';
645 + }
646 + ?>
647 + <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 1024 1024">
648 + <path class="acym__svg__loader"
649 + fill="#a6a9ab"
650 + d="M553.074 174.168c-12.201 7.319-26.84 10.573-40.668 10.573s-27.655-3.253-40.668-10.573l-242.376-139.081-229.362 132.575v732.011l254.576 143.963v-430.26l219.602 124.442c11.388 6.507 24.401 9.76 37.415 9.76 0 0 0 0 0 0s0 0 0 0c12.201 0 25.214-3.253 36.6-9.76l221.23-124.442v430.26l254.576-144.775v-732.011l-229.362-131.762-241.563 139.081zM491.261 701.215l-217.164-122.815-61.001-34.161v430.26l-173.243-97.601v-662.063l422.94 245.629c0 0 0 0 0 0 8.947 4.881 17.895 8.135 27.655 10.573v230.178zM983.334 876.086l-173.243 98.416v-431.073l-61.001 34.161-217.164 122.815v-229.362c9.76-2.441 18.707-5.694 27.655-10.573l423.753-246.444v662.063zM539.246 425.493c-17.080 9.76-38.227 9.76-55.307 0 0 0 0 0 0 0l-422.94-245.629 168.362-97.601 222.043 127.696c18.707 10.573 39.853 16.267 61.001 16.267s42.294-5.694 61.001-16.267l222.043-127.696 168.362 97.601-424.566 245.629z"></path>
651 + </svg>
652 + <?php
653 + if ($withContainer) {
654 + echo '</div>';
655 + }
656 +}
709 657
710 - $loader .= '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 1024 1024">
711 - <path class="acym__svg__loader" fill="#a6a9ab" d="M553.074 174.168c-12.201 7.319-26.84 10.573-40.668 10.573s-27.655-3.253-40.668-10.573l-242.376-139.081-229.362 132.575v732.011l254.576 143.963v-430.26l219.602 124.442c11.388 6.507 24.401 9.76 37.415 9.76 0 0 0 0 0 0s0 0 0 0c12.201 0 25.214-3.253 36.6-9.76l221.23-124.442v430.26l254.576-144.775v-732.011l-229.362-131.762-241.563 139.081zM491.261 701.215l-217.164-122.815-61.001-34.161v430.26l-173.243-97.601v-662.063l422.94 245.629c0 0 0 0 0 0 8.947 4.881 17.895 8.135 27.655 10.573v230.178zM983.334 876.086l-173.243 98.416v-431.073l-61.001 34.161-217.164 122.815v-229.362c9.76-2.441 18.707-5.694 27.655-10.573l423.753-246.444v662.063zM539.246 425.493c-17.080 9.76-38.227 9.76-55.307 0 0 0 0 0 0 0l-422.94-245.629 168.362-97.601 222.043 127.696c18.707 10.573 39.853 16.267 61.001 16.267s42.294-5.694 61.001-16.267l222.043-127.696 168.362 97.601-424.566 245.629z"></path>
712 - </svg>';
658 +function acym_fileNameValid(string $filename): bool
659 +{
660 + if (empty($filename)) return false;
713 661
714 - if ($wrap) $loader .= '</div>';
662 + if (strpos($filename, '..') !== false || strpos($filename, '/') !== false || strpos($filename, '\\') !== false) return false;
715 663
716 - return $loader;
664 + return true;
717 665 }
718 666
719 -function acym_fileNameValid($filename)
667 +function acym_isPathWithinDirectory(string $baseDir, string $path): bool
720 668 {
721 - if (empty($filename)) return false;
669 + $baseDir = rtrim($baseDir, DS).DS;
722 670
723 - if (strpos($filename, '..') !== false || strpos($filename, '/') !== false || strpos($filename, '\\') !== false) return false;
671 + if (strpos($path, $baseDir) !== 0) return false;
724 672
725 - return true;
673 + return preg_match('#(^|[/\\\\])\.\.([/\\\\]|$)#', substr($path, strlen($baseDir))) !== 1;
726 674 }
727 675
728 -function acym_getImageFileExtensions()
676 +function acym_getImageFileExtensions(bool $allowSvg = false): array
729 677 {
730 - return ['jpg', 'jpeg', 'png', 'gif', 'ico', 'bmp', 'svg'];
678 + $imageFormats = ['jpg', 'jpeg', 'png', 'gif', 'ico', 'bmp'];
679 + if ($allowSvg) {
680 + $imageFormats[] = 'svg';
681 + }
682 +
683 + return $imageFormats;
684 +}
685 +
686 +function acym_isSvgFileSafe(string $path): bool
687 +{
688 + if (!is_file($path)) {
689 + return false;
690 + }
691 +
692 + $content = @file_get_contents($path);
693 + if ($content === false || trim($content) === '') {
694 + return false;
695 + }
696 +
697 + // Decode HTML entities so obfuscated payloads (e.g. "javascript&#58;") are caught, and drop NULL bytes.
698 + $content = str_replace("\0", '', html_entity_decode($content, ENT_QUOTES | ENT_HTML5));
699 +
700 + $dangerousPatterns = [
701 + '#<\s*script#i',
702 + '#<\s*(foreignObject|iframe|embed|object|audio|video|handler|listener)#i',
703 + '#\son[a-z]+\s*=#i',
704 + '#javascript\s*:#i',
705 + '#data\s*:\s*text/html#i',
706 + '#<!\s*(DOCTYPE|ENTITY)#i',
707 + ];
708 +
709 + foreach ($dangerousPatterns as $pattern) {
710 + if (preg_match($pattern, $content)) {
711 + return false;
712 + }
713 + }
714 +
715 + return true;
731 716 }