File Manager: add UD shares to user shares

This commit is contained in:
bergware
2025-04-14 12:53:22 +02:00
parent 6486409ace
commit 7f2c262d14
3 changed files with 69 additions and 62 deletions
+8 -8
View File
@@ -488,7 +488,7 @@ function doAction(action, title, id) {
case 4: // move folder
case 9: // move file
// disallow mixing of disk and user shares
var valid = ud ? /^\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|rootshare)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)).+$|^\/boot\/.+/);
var valid = user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).*$|^\/boot\/.+/;
// check if 'mv' can be used
if (path.length > 2) {
if (user) {
@@ -520,7 +520,7 @@ function doAction(action, title, id) {
break;
default:
// disallow mixing of disk and user shares
var valid = ud ? /^\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|rootshare)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)).+$|^\/boot\/.+/);
var valid = user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).*$|^\/boot\/.+/;
break;
}
if (!target || !valid.test(target)) {errorTarget(); return;}
@@ -558,7 +558,7 @@ function doAction(action, title, id) {
case 4: // move folder
case 9: // move file
// disallow mixing of disk and user shares
var valid = ud ? /^\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|rootshare)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)).+$|^\/boot\/.+/);
var valid = user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).*$|^\/boot\/.+/;
// check if 'mv' can be used
if (path.length > 2) {
if (user) {
@@ -583,7 +583,7 @@ function doAction(action, title, id) {
break;
default:
// disallow mixing of disk and user shares
var valid = ud ? /^\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|rootshare)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)).+$|^\/boot\/.+/);
var valid = user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).*$|^\/boot\/.+/;
break;
}
if (!target || !valid.test(target)) {errorTarget(); return;}
@@ -767,7 +767,7 @@ function doActions(action, title) {
break;
case 4: // move object
// disallow mixing of disk and user shares
var valid = ud ? /\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|rootshare)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)).+$|^\/boot\/.+/);
var valid = user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).*$|^\/boot\/.+/;
// check if 'mv' can be used
if (path.length > 2) {
if (user) {
@@ -801,7 +801,7 @@ function doActions(action, title) {
break;
default:
// disallow mixing of disk and user shares
var valid = ud ? /\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|rootshare)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)).+$|^\/boot\/.+/);
var valid = user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).*$|^\/boot\/.+/;
break;
}
if (!target || !valid.test(target)) {errorTarget(); return;}
@@ -839,7 +839,7 @@ function doActions(action, title) {
break;
case 4: // move object
// disallow mixing of disk and user shares
var valid = ud ? /\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|rootshare)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)).+$|^\/boot\/.+/);
var valid = user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).*$|^\/boot\/.+/;
// check if 'mv' can be used
if (path.length > 2) {
if (user) {
@@ -866,7 +866,7 @@ function doActions(action, title) {
break;
default:
// disallow mixing of disk and user shares
var valid = ud ? /\/mnt\/.+/ : (user ? /^\/mnt\/(user0?|rootshare)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)).+$|^\/boot\/.+/);
var valid = user ? /^\/mnt\/(user0?|disks|remotes)\/.+/ : /^\/mnt\/(?!.*(user0?|rootshare)\/).*$|^\/boot\/.+/;
break;
}
if (!target || !valid.test(target)) {errorTarget(); return;}
+24 -22
View File
@@ -25,18 +25,18 @@ function write(&$rows) {
function validdir($dir) {
$path = realpath($dir);
return in_array(explode('/',$path)[1]??'',['mnt','boot']) ? $path : '';
return in_array(explode('/', $path)[1] ?? '', ['mnt','boot']) ? $path : '';
}
function escapeQuote($data) {
return str_replace('"','"',$data);
return str_replace('"','"', $data);
}
function add($number, $name, $single='', $plural='s') {
return $number.' '._($name.($number==1 ? $single : $plural));
}
function age($number,$time) {
function age($number, $time) {
return sprintf(_('%s '.($number==1 ? $time : $time.'s').' ago'),$number);
}
@@ -44,25 +44,25 @@ function my_age($time) {
if (!is_numeric($time)) $time = time();
$age = new DateTime('@'.$time);
$age = date_create('now')->diff($age);
if ($age->y > 0) return age($age->y,'year');
if ($age->m > 0) return age($age->m,'month');
if ($age->d > 0) return age($age->d,'day');
if ($age->h > 0) return age($age->h,'hour');
if ($age->i > 0) return age($age->i,'minute');
return age($age->s,'second');
if ($age->y > 0) return age($age->y, 'year');
if ($age->m > 0) return age($age->m, 'month');
if ($age->d > 0) return age($age->d, 'day');
if ($age->h > 0) return age($age->h, 'hour');
if ($age->i > 0) return age($age->i, 'minute');
return age($age->s, 'second');
}
function parent_link() {
global $dir,$path;
global $dir, $path;
$parent = dirname($dir);
return $parent=='/' ? false : '<a href="/'.$path.'?dir='.rawurlencode(htmlspecialchars($parent)).'">'._('Parent Directory').'</a>';
return $parent == '/' ? false : '<a href="/'.$path.'?dir='.rawurlencode(htmlspecialchars($parent)).'">'._('Parent Directory').'</a>';
}
function my_devs(&$devs,$name,$menu) {
global $disks,$lock;
global $disks, $lock;
$text = []; $i = 0;
foreach ($devs as $dev) {
if ($lock=='---') {
if ($lock == '---') {
$text[$i] = '<a class="info" onclick="return false"><i class="lock fa fa-fw fa-hdd-o grey-text"></i></a>&nbsp;---';
} else {
switch ($disks[$dev]['luksState']??0) {
@@ -72,7 +72,7 @@ function my_devs(&$devs,$name,$menu) {
case 3: $text[$i] = '<span class="dfm_device"><a class="info" onclick="return false"><i class="lock fa fa-fw fa-lock red-text"></i><span>'._('Locked: wrong encryption key').'</span></a>'; break;
default: $text[$i] = '<span class="dfm_device"><a class="info" onclick="return false"><i class="lock fa fa-fw fa-lock red-text"></i><span>'._('Locked: unknown error').'</span></a>'; break;
}
$root = $dev=='flash' ? "/boot/$name" : "/mnt/$dev/$name";
$root = ($dev == 'flash' ? "/boot/$name" : "/mnt/$dev/$name");
$text[$i] .= '<span id="device_'.$i.'" class="hand" onclick="'.$menu.'(\''.$root.'\','.$i.')" oncontextmenu="'.$menu.'(\''.$root.'\','.$i.');return false">'.compress($dev,11,0).'</span></span>';
}
$i++;
@@ -141,8 +141,8 @@ $path = unscript($_GET['path']);
$fmt = "%F {$display['time']}";
$dirs = $files = [];
$total = $objs = 0;
[$null,$root,$main,$next,$rest] = my_explode('/',$dir,5);
$user = $root=='mnt' && in_array($main,['user','user0']);
[$null,$root,$main,$next,$rest] = my_explode('/', $dir, 5);
$user = $root=='mnt' && in_array($main, ['user','user0']);
$lock = $root=='mnt' ? ($main ?: '---') : ($root=='boot' ? _('flash') : '---');
$ishare = $root=='mnt' && (!$main || !$next || ($main=='rootshare' && !$rest));
$folder = $lock=='---' ? _('DEVICE') : ($ishare ? _('SHARE') : _('FOLDER'));
@@ -154,13 +154,14 @@ if ($user ) {
}
$stat = popen("shopt -s dotglob;stat -L -c'%F|%U|%A|%s|%Y|%n' ".escapeshellarg($dir)."/* 2>/dev/null",'r');
while (($row = fgets($stat))!==false) {
while (($row = fgets($stat)) !== false) {
[$type,$owner,$perm,$size,$time,$name] = explode('|',rtrim($row,"\n"),6);
$dev = explode('/',$name,5);
$devs = explode(',',$user ? $set[basename($name)]??$shares[$dev[3]]['cachePool']??'' : $lock);
$dev = explode('/', $name, 5);
$devs = explode(',', $user ? $set[basename($name)] ?? $shares[$dev[3]]['cachePool'] ?? '' : $lock);
$objs++;
$text = [];
if ($type[0]=='d') {
if ($type[0] == 'd') {
$text[] = '<tr><td><i id="check_'.$objs.'" class="fa fa-fw fa-square-o" onclick="selectOne(this.id)"></i></td>';
$text[] = '<td data=""><i class="fa fa-folder-o"></i></td>';
$text[] = '<td><a id="name_'.$objs.'" oncontextmenu="folderContextMenu(this.id,\'right\');return false" href="/'.$path.'?dir='.rawurlencode(htmlspecialchars($name)).'">'.htmlspecialchars(basename($name)).'</a></td>';
@@ -172,8 +173,8 @@ while (($row = fgets($stat))!==false) {
$text[] = '<td><i id="row_'.$objs.'" data="'.escapeQuote($name).'" type="d" class="fa fa-plus-square-o" onclick="folderContextMenu(this.id,\'both\')" oncontextmenu="folderContextMenu(this.id,\'both\');return false">...</i></td></tr>';
$dirs[] = gzdeflate(implode($text));
} else {
$ext = strtolower(pathinfo($name,PATHINFO_EXTENSION));
$tag = count($devs)>1 ? 'warning' : '';
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$tag = count($devs) > 1 ? 'warning' : '';
$text[] = '<tr><td><i id="check_'.$objs.'" class="fa fa-fw fa-square-o" onclick="selectOne(this.id)"></i></td>';
$text[] = '<td class="ext" data="'.$ext.'"><i class="'.icon_class($ext).'"></i></td>';
$text[] = '<td id="name_'.$objs.'" class="'.$tag.'" onclick="fileEdit(this.id)" oncontextmenu="fileContextMenu(this.id,\'right\');return false">'.htmlspecialchars(basename($name)).'</td>';
@@ -189,6 +190,7 @@ while (($row = fgets($stat))!==false) {
}
pclose($stat);
if ($link = parent_link()) echo '<tbody class="tablesorter-infoOnly"><tr><td></td><td><i class="fa fa-folder-open-o"></i></td><td>',$link,'</td><td colspan="6"></td></tr></tbody>';
echo write($dirs),write($files),'<tfoot><tr><td></td><td></td><td colspan="7">',add($objs,'object'),': ',add($dirs,'director','y','ies'),', ',add($files,'file'),' (',my_scale($total,$unit),' ',$unit,' ',_('total'),')</td></tr></tfoot>';
?>
+37 -32
View File
@@ -9,6 +9,7 @@
*
* History:
*
* 1.2.2 - allow user shares to UD shares
* 1.2.1 - exclude folders from the /mnt/ root folder
* 1.2.0 - adapted by Bergware for use in Unraid - support UTF-8 encoding & hardening
* 1.1.1 - SECURITY: forcing root to prevent users from determining system's file structure (per DaveBrad)
@@ -29,13 +30,19 @@
function path($dir) {
return mb_substr($dir,-1) == '/' ? $dir : $dir.'/';
}
function is_top($dir) {
global $root;
return mb_strlen($dir) > mb_strlen($root);
}
function is_low($dir) {
global $root;
return mb_substr($dir,0,mb_strlen($root)) == $root;
function no_dots($name) {
return !in_array($name, ['.','..']);
}
function my_dir($name) {
global $rootdir, $userdir, $topdir, $UDincluded;
return ($rootdir === $userdir && in_array($name, $UDincluded)) ? $topdir : $rootdir;
}
$root = path(realpath($_POST['root']));
@@ -44,62 +51,60 @@ if (!$root) exit("ERROR: Root filesystem directory not set in jqueryFileTree.php
$docroot = '/usr/local/emhttp';
require_once "$docroot/webGui/include/Secure.php";
$rootdir = path(realpath($_POST['dir']));
$topdir = '/mnt/';
$mntdir = '/mnt/';
$userdir = '/mnt/user/';
$rootdir = path(realpath($_POST['dir']));
$topdir = str_replace($userdir, $mntdir, $rootdir);
$filters = (array)$_POST['filter'];
$match = $_POST['match'];
$checkbox = $_POST['multiSelect'] == 'true' ? "<input type='checkbox'>" : "";
/* Excluded folders to not show in the dropdown in the '/mnt/' directory only. */
$excludedFolders = ['RecycleBin', 'addons', 'rootshare'];
$udShares = ['disks','remotes'];
// Excluded UD shares to hide under '/mnt'
$UDexcluded = ['RecycleBin', 'addons', 'rootshare'];
// Included UD shares to show under '/mnt/user'
$UDincluded = ['disks','remotes'];
echo "<ul class='jqueryFileTree'>";
if ($_POST['show_parent'] == 'true' && is_top($rootdir)) {
echo "<li class='directory collapsed'>$checkbox<a href='#' rel=\"".htmlspecialchars(dirname($rootdir))."\">..</a></li>";
}
if (is_low($rootdir) && is_dir($rootdir)) {
$dirs = $files = [];
$names = array_filter(scandir($rootdir, SCANDIR_SORT_NONE), function($n){return $n != '.' && $n != '..';});
if (is_dir($rootdir)) {
$dirs = $files = [];
$names = array_filter(scandir($rootdir, SCANDIR_SORT_NONE), 'no_dots');
// add UD shares under /mnt/user
foreach ($UDincluded as $name) {
if (!is_dir($topdir.$name)) continue;
if ($rootdir === $userdir) {
if (!in_array($name, $names)) $names[] = $name;
} else {
if (explode('/', $topdir)[2] === $name) $names = array_merge($names, array_filter(scandir($topdir, SCANDIR_SORT_NONE), 'no_dots'));
}
}
natcasesort($names);
foreach ($names as $name) {
if (is_dir($rootdir.$name)) {
if (is_dir(my_dir($name).$name)) {
$dirs[] = $name;
} else {
$files[] = $name;
}
}
foreach ($dirs as $name) {
$htmlRel = htmlspecialchars($rootdir.$name);
// Exclude '.Recycle.Bin' from all shares and UD folders from '/mnt'
if ($name === '.Recycle.Bin' || ($rootdir === $mntdir && in_array($name, $UDexcluded))) continue;
$htmlRel = htmlspecialchars(my_dir($name).$name);
$htmlName = htmlspecialchars(mb_strlen($name) <= 33 ? $name : mb_substr($name, 0, 30).'...');
/* Exclude '.Recycle.Bin' from all directories */
if ($name === '.Recycle.Bin') continue;
/* Exclude folders only when directory is '/mnt/' */
if (in_array($name, $excludedFolders) && $rootdir === $topdir) continue;
if (empty($match) || preg_match("/$match/", $rootdir.$name.'/')) {
echo "<li class='directory collapsed'>$checkbox<a href='#' rel=\"$htmlRel/\">$htmlName</a></li>";
}
}
if ($rootdir === $userdir) {
// add unassigned devices top level shares
foreach ($udShares as $name) if (is_dir($topdir.$name)) {
echo "<li class='directory collapsed'>$checkbox<a href='#' rel=\"{$topdir}{$name}/\">$name</a></li>";
}
}
foreach ($files as $name) {
$htmlRel = htmlspecialchars($rootdir . $name);
$htmlRel = htmlspecialchars(my_dir($name).$name);
$htmlName = htmlspecialchars($name);
$ext = mb_strtolower(pathinfo($name, PATHINFO_EXTENSION));
foreach ($filters as $filter) {
if (empty($filter) || $ext == $filter) {
if (empty($match) || preg_match("/$match/", $name)) {
echo "<li class='file ext_$ext'>$checkbox<a href='#' rel=\"$htmlRel\">$htmlName</a></li>";
}
foreach ($filters as $filter) if (empty($filter) || $ext === $filter) {
if (empty($match) || preg_match("/$match/", $name)) {
echo "<li class='file ext_$ext'>$checkbox<a href='#' rel=\"$htmlRel\">$htmlName</a></li>";
}
}
}