Skip to content

Commit e34058f

Browse files
committed
Add support for normalizing paths on FileProperty
Added: - Method `FileProperty::normalizePath()` to resolve relative paths while avoiding issues with the `realpath()` function Changed: - Method `ImageProperty::processEffectsOne()` to normalize paths Example: ``` "thumbnail": { "type": "image", "upload_path": "uploads/thumbnails/src/", "effects": [ { "type": "resize", "mode": "best_fit", "width": 150, "height": 150 }, { "type": "save", "copy": "../small/{{filename}}.{{extension}}" } ] } ```
1 parent 23a388d commit e34058f

File tree

2 files changed

+66
-8
lines changed

2 files changed

+66
-8
lines changed

src/Charcoal/Property/FileProperty.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ class FileProperty extends AbstractProperty
108108
*/
109109
private $filesystem = self::DEFAULT_FILESYSTEM;
110110

111+
/**
112+
* Holds a list of all normalized paths.
113+
*
114+
* @var string[]
115+
*/
116+
protected static $normalizePathCache = [];
117+
111118
/**
112119
* @return string
113120
*/
@@ -1283,4 +1290,55 @@ public static function parseUploadedFiles(array $uploadedFiles, callable $filter
12831290

12841291
return $parsedFiles;
12851292
}
1293+
1294+
/**
1295+
* Normalize a file path string so that it can be checked safely.
1296+
*
1297+
* Attempt to avoid invalid encoding bugs by transcoding the path. Then
1298+
* remove any unnecessary path components including '.', '..' and ''.
1299+
*
1300+
* @link https://gist.github.com/thsutton/772287
1301+
*
1302+
* @param string $path The path to normalise.
1303+
* @param string $encoding The name of the path iconv() encoding.
1304+
* @return string The path, normalised.
1305+
*/
1306+
public static function normalizePath($path, $encoding = 'UTF-8')
1307+
{
1308+
$key = $path;
1309+
1310+
if (isset(static::$normalizePathCache[$key])) {
1311+
return static::$normalizePathCache[$key];
1312+
}
1313+
1314+
// Attempt to avoid path encoding problems.
1315+
$path = iconv($encoding, $encoding.'//IGNORE//TRANSLIT', $path);
1316+
1317+
if (strpos($path, '..') !== false || strpos($path, './') !== false) {
1318+
// Process the components
1319+
$parts = explode('/', $path);
1320+
$safe = [];
1321+
foreach ($parts as $idx => $part) {
1322+
if ((empty($part) && !is_numeric($part)) || ($part === '.')) {
1323+
continue;
1324+
} elseif ($part === '..') {
1325+
array_pop($safe);
1326+
continue;
1327+
} else {
1328+
$safe[] = $part;
1329+
}
1330+
}
1331+
1332+
// Return the "clean" path
1333+
$path = implode(DIRECTORY_SEPARATOR, $safe);
1334+
1335+
if ($key[0] === '/' && $path[0] !== '/') {
1336+
$path = '/'.$path;
1337+
}
1338+
}
1339+
1340+
static::$normalizePathCache[$key] = $path;
1341+
1342+
return static::$normalizePathCache[$key];
1343+
}
12861344
}

src/Charcoal/Property/ImageProperty.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -497,10 +497,10 @@ private function processEffectsOne($value, array $effects = null, ImageInterface
497497

498498
// @todo Save original file here
499499
$valuePath = ($isAbsolute ? '' : $basePath);
500-
$image->open($valuePath.$value);
500+
$image->open(static::normalizePath($valuePath.$value));
501501
$target = null;
502502
if ($isAbsolute) {
503-
$target = $basePath.$this['uploadPath'].pathinfo($value, PATHINFO_BASENAME);
503+
$target = static::normalizePath($basePath.$this['uploadPath'].pathinfo($value, PATHINFO_BASENAME));
504504
}
505505

506506
foreach ($effects as $fxGroup) {
@@ -545,13 +545,13 @@ private function processEffectsOne($value, array $effects = null, ImageInterface
545545
if ($rename || $copy) {
546546
if ($copy) {
547547
$copy = $this->renderFileRenamePattern(($target ?: $value), $copy);
548-
$exists = $this->fileExists($basePath.$copy);
548+
$exists = $this->fileExists(static::normalizePath($basePath.$copy));
549549
$doCopy = ($copy && ($this['overwrite'] || !$exists));
550550
}
551551

552552
if ($rename) {
553553
$value = $this->renderFileRenamePattern(($target ?: $value), $rename);
554-
$exists = $this->fileExists($basePath.$value);
554+
$exists = $this->fileExists(static::normalizePath($basePath.$value));
555555
$doRename = ($value && ($this['overwrite'] || !$exists));
556556
}
557557

@@ -566,20 +566,20 @@ private function processEffectsOne($value, array $effects = null, ImageInterface
566566

567567
if ($rename || $copy) {
568568
if ($doCopy) {
569-
$image->save($valuePath.$copy);
569+
$image->save(static::normalizePath($valuePath.$copy));
570570
}
571571

572572
if ($doRename) {
573-
$image->save($valuePath.$value);
573+
$image->save(static::normalizePath($valuePath.$value));
574574
}
575575
} else {
576-
$image->save($target ?: $valuePath.$value);
576+
$image->save($target ?: static::normalizePath($valuePath.$value));
577577
}
578578
}
579579
}
580580
// reset to default image allow starting effects chains from original image.
581581
if ($fxGroup['reset']) {
582-
$image = $image->open($valuePath.$value);
582+
$image = $image->open(static::normalizePath($valuePath.$value));
583583
}
584584
}
585585
}

0 commit comments

Comments
 (0)