Skip to content

Commit 941eb3e

Browse files
committed
Fixes, improvements
- file paths are canonical - system dependant DIRECTORY_SEPARATOR is used - glob in list action is replaced to prevent issues with special chars like *[]! etc - uploaded file names are normalized so they contain only ASCII characters - support for nested directories for compression
1 parent 70313cd commit 941eb3e

File tree

1 file changed

+106
-12
lines changed

1 file changed

+106
-12
lines changed

bridges/php-local/LocalBridge/FileManagerApi.php

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ public function getHandler($queries)
207207
private function downloadAction($path)
208208
{
209209
$file_name = basename($path);
210-
$path = $this->basePath . $path;
210+
$path = $this->canonicalizePath($this->basePath . $path);
211211

212212
if (!file_exists($path)) {
213213
return false;
@@ -261,12 +261,12 @@ private function downloadMultipleAction($items, $archiveName)
261261

262262
private function uploadAction($path, $files)
263263
{
264-
$path = $this->basePath . $path;
264+
$path = $this->canonicalizePath($this->basePath . $path);
265265

266266
foreach ($_FILES as $file) {
267267
$uploaded = move_uploaded_file(
268268
$file['tmp_name'],
269-
rtrim($path, '/') . '/' . $file['name']
269+
$path . DIRECTORY_SEPARATOR . $this->normalizeName($file['name'])
270270
);
271271
if ($uploaded === false) {
272272
return false;
@@ -278,10 +278,19 @@ private function uploadAction($path, $files)
278278

279279
private function listAction($path)
280280
{
281-
$files = glob($this->basePath . rtrim($path, '/') . '/*');
281+
$files = array_values(array_filter(
282+
scandir($this->basePath . $path),
283+
function ($path) {
284+
return !($path === '.' || $path === '..');
285+
}
286+
));
282287

283-
$files = array_map(function ($file) {
288+
$files = array_map(function ($file) use ($path) {
289+
$file = $this->canonicalizePath(
290+
$this->basePath . $path . DIRECTORY_SEPARATOR . $file
291+
);
284292
$date = new \DateTime('@' . filemtime($file));
293+
285294
return [
286295
'name' => basename($file),
287296
'rights' => $this->parsePerms(fileperms($file)),
@@ -308,10 +317,10 @@ private function renameAction($oldPath, $newPath)
308317

309318
private function moveAction($oldPaths, $newPath)
310319
{
311-
$newPath = $this->basePath . rtrim($newPath, '/') . '/';
320+
$newPath = $this->basePath . $this->canonicalizePath($newPath) . DIRECTORY_SEPARATOR;
312321

313322
foreach ($oldPaths as $oldPath) {
314-
if (! file_exists($this->basePath . $oldPath)) {
323+
if (!file_exists($this->basePath . $oldPath)) {
315324
return false;
316325
}
317326

@@ -326,10 +335,10 @@ private function moveAction($oldPaths, $newPath)
326335

327336
private function copyAction($oldPaths, $newPath)
328337
{
329-
$newPath = $this->basePath . rtrim($newPath, '/') . '/';
338+
$newPath = $this->basePath . $this->canonicalizePath($newPath) . DIRECTORY_SEPARATOR;
330339

331340
foreach ($oldPaths as $oldPath) {
332-
if (! file_exists($this->basePath . $oldPath)) {
341+
if (!file_exists($this->basePath . $oldPath)) {
333342
return false;
334343
}
335344

@@ -348,7 +357,7 @@ private function copyAction($oldPaths, $newPath)
348357
private function removeAction($paths)
349358
{
350359
foreach ($paths as $path) {
351-
$path = $this->basePath . $path;
360+
$path = $this->canonicalizePath($this->basePath . $path);
352361

353362
if (is_dir($path)) {
354363
$dirEmpty = (new \FilesystemIterator($path))->valid();
@@ -434,7 +443,43 @@ private function compressAction($paths, $destination, $archiveName)
434443
}
435444

436445
foreach ($paths as $path) {
437-
$zip->addFile($this->basePath . $path, basename($path));
446+
$fullPath = $this->basePath . $path;
447+
448+
if (is_dir($fullPath)) {
449+
$dirs = [
450+
[
451+
'dir' => basename($path),
452+
'path' => $this->canonicalizePath($this->basePath . $path),
453+
]
454+
];
455+
456+
while (count($dirs)) {
457+
$dir = current($dirs);
458+
$zip->addEmptyDir($dir['dir']);
459+
460+
$dh = opendir($dir['path']);
461+
while ($file = readdir($dh)) {
462+
if ($file != '.' && $file != '..') {
463+
$filePath = $dir['path'] . DIRECTORY_SEPARATOR . $file;
464+
if (is_file($filePath)) {
465+
$zip->addFile(
466+
$dir['path'] . DIRECTORY_SEPARATOR . $file,
467+
$dir['dir'] . '/' . basename($file)
468+
);
469+
} elseif (is_dir($filePath)) {
470+
$dirs[] = [
471+
'dir' => $dir['dir'] . '/' . $file,
472+
'path' => $dir['path'] . DIRECTORY_SEPARATOR . $file
473+
];
474+
}
475+
}
476+
}
477+
closedir($dh);
478+
array_shift($dirs);
479+
}
480+
} else {
481+
$zip->addFile($path, basename($path));
482+
}
438483
}
439484

440485
return $zip->close();
@@ -443,7 +488,7 @@ private function compressAction($paths, $destination, $archiveName)
443488
private function extractAction($destination, $archivePath, $folderName)
444489
{
445490
$archivePath = $this->basePath . $archivePath;
446-
$folderPath = $this->basePath . rtrim($destination, '/') . '/' . $folderName;
491+
$folderPath = $this->basePath . $this->canonicalizePath($destination) . DIRECTORY_SEPARATOR . $folderName;
447492

448493
$zip = new \ZipArchive;
449494
if ($zip->open($archivePath) === false) {
@@ -533,4 +578,53 @@ private function parsePerms($perms)
533578

534579
return $info;
535580
}
581+
582+
private function canonicalizePath($path)
583+
{
584+
$dirSep = DIRECTORY_SEPARATOR;
585+
$wrongDirSep = DIRECTORY_SEPARATOR === '/' ? '\\' : '/';
586+
587+
// Replace incorrect dir separators
588+
$path = str_replace($wrongDirSep, $dirSep, $path);
589+
590+
$path = explode($dirSep, $path);
591+
$stack = array();
592+
foreach ($path as $seg) {
593+
if ($seg == '..') {
594+
// Ignore this segment, remove last segment from stack
595+
array_pop($stack);
596+
continue;
597+
}
598+
599+
if ($seg == '.') {
600+
// Ignore this segment
601+
continue;
602+
}
603+
604+
$stack[] = $seg;
605+
}
606+
607+
// Remove last /
608+
if (empty($stack[count($stack) - 1])) {
609+
array_pop($stack);
610+
}
611+
612+
return implode($dirSep, $stack);
613+
}
614+
615+
/**
616+
* Creates ASCII name
617+
*
618+
* @param string name encoded in UTF-8
619+
* @return string name containing only numbers, chars without diacritics, underscore and dash
620+
* @copyright Jakub Vrána, https://php.vrana.cz/
621+
*/
622+
private function normalizeName($name)
623+
{
624+
$name = preg_replace('~[^\\pL0-9_]+~u', '-', $name);
625+
$name = trim($name, "-");
626+
$name = iconv("utf-8", "us-ascii//TRANSLIT", $name);
627+
$name = preg_replace('~[^-a-z0-9_]+~', '', $name);
628+
return $name;
629+
}
536630
}

0 commit comments

Comments
 (0)