-
-
-
-
-
-
-
-
-
-
-
- Drag Files Here To Upload
- or
-
-
-
-
+
+
+
+
+
Drop files here to upload
Maximum = sfm_h(sfm_format_bytes((int) $GLOBALS['max_upload_size_bytes'])) ?> per file
+
+
+
+
+
+
-
-
- | Name |
- Size |
- Modified |
- Permissions |
- Actions |
-
+
+| Name | Size | Modified | Permissions | Actions |
+
+| Parent folder |
+
+
+ |
+= $entry['is_dir'] ? 'β' : sfm_h(sfm_format_bytes($entry['size'])) ?> |
+= sfm_h(date('Y-m-d H:i', $entry['mtime'])) ?> |
+= sfm_h($entry['perms']) ?> |
+
+
+
+
+
+ |
+
+| This folder is empty. |
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
= sfm_h($title) ?> β Login
+
+
Configuration required= sfm_h($title) ?> is not configured
Authentication is enabled but no usable password hash exists.
Edit the CONFIGURATION section at the top of index.php, then add a password hash to $auth_users.
php -r "echo password_hash('your-strong-password', PASSWORD_DEFAULT), PHP_EOL;"Example: 'admin' => '$2y$...'
+|]#', $name)) return null; return $name; }
+function sfm_matches_patterns(string $name, array $patterns): bool { foreach ($patterns as $pattern) if (fnmatch($pattern, $name, FNM_CASEFOLD)) return true; return false; }
+
+function sfm_list_entries(string $root, string $directory, string $requested_path, bool $show_hidden, array $hidden_patterns): array
+{
+ if (!is_dir($directory) || !is_readable($directory)) return [];
+ $entries = [];
+ foreach (new DirectoryIterator($directory) as $item) {
+ if ($item->isDot() || $item->isLink()) continue; // Never expose symlinks through the manager.
+ $name = $item->getFilename();
+ if ((!$show_hidden && str_starts_with($name, '.')) || sfm_matches_patterns($name, $hidden_patterns)) continue;
+ $real = $item->getRealPath();
+ if ($real === false || !sfm_path_is_within($root, $real)) continue;
+ $is_dir = $item->isDir();
+ $relative = sfm_relative_path($root, $real);
+ $entries[] = [
+ 'name' => $name, 'is_dir' => $is_dir, 'size' => $is_dir ? 0 : $item->getSize(), 'mtime' => $item->getMTime(),
+ 'perms' => substr(sprintf('%o', $item->getPerms()), -4), 'relative' => $relative,
+ 'href' => $is_dir ? sfm_url($relative) : ($GLOBALS['allow_direct_link'] ? sfm_url($relative) : sfm_action_url('download', $relative)),
+ ];
+ }
+ usort($entries, static fn(array $a, array $b): int => ($a['is_dir'] === $b['is_dir']) ? strnatcasecmp($a['name'], $b['name']) : ($a['is_dir'] ? -1 : 1));
+ return $entries;
+}
+
+function sfm_delete_tree(string $path): bool
+{
+ if (is_link($path)) return unlink($path);
+ if (is_file($path)) return unlink($path);
+ if (!is_dir($path)) return false;
+ foreach (new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS) as $item) {
+ if ($item->isLink() || !sfm_delete_tree($item->getPathname())) return false;
+ }
+ return rmdir($path);
+}
+function sfm_copy_tree(string $source, string $destination): bool
+{
+ if (is_link($source)) return false;
+ if (is_file($source)) return copy($source, $destination);
+ if (!mkdir($destination, 0750)) return false;
+ foreach (new FilesystemIterator($source, FilesystemIterator::SKIP_DOTS) as $item) {
+ if (!sfm_copy_tree($item->getPathname(), $destination . DIRECTORY_SEPARATOR . $item->getFilename())) return false;
+ }
+ return true;
+}
+function sfm_upload_error_message(int $code): string { return [UPLOAD_ERR_INI_SIZE => 'server upload limit exceeded', UPLOAD_ERR_FORM_SIZE => 'form upload limit exceeded', UPLOAD_ERR_PARTIAL => 'partial upload', UPLOAD_ERR_NO_FILE => 'no file supplied', UPLOAD_ERR_NO_TMP_DIR => 'temporary directory missing', UPLOAD_ERR_CANT_WRITE => 'failed writing file', UPLOAD_ERR_EXTENSION => 'blocked by PHP extension'][$code] ?? 'unknown error'; }
+function sfm_format_bytes(int $bytes): string { $units = ['B','KiB','MiB','GiB','TiB']; $i = 0; $value = (float) $bytes; while ($value >= 1024 && $i < count($units) - 1) { $value /= 1024; $i++; } return ($i === 0 ? (string) (int) $value : number_format($value, 1)) . ' ' . $units[$i]; }
+function sfm_icon_for_name(string $name): string { $ext = strtolower((string) pathinfo($name, PATHINFO_EXTENSION)); return match ($ext) { 'jpg','jpeg','png','gif','webp','svg' => 'file-image-o', 'pdf' => 'file-pdf-o', 'zip','gz','rar','7z','tar' => 'file-archive-o', 'mp3','wav','ogg','m4a' => 'file-audio-o', 'mp4','mkv','webm','avi' => 'file-video-o', 'php','js','ts','css','html','htm','json','xml','yml','yaml','md','txt','log' => 'file-code-o', default => 'file-o' }; }
+
+function sfm_url(string $path = '', array $extra = []): string { $query = array_merge(['path' => $path], $extra); if ($path === '') unset($query['path']); return sfm_self_url() . ($query ? '?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986) : ''); }
+function sfm_action_url(string $action, string $path): string { return sfm_self_url() . '?' . http_build_query(['action' => $action, 'path' => $path], '', '&', PHP_QUERY_RFC3986); }
+function sfm_redirect(string $path, string $message): void { $_SESSION['flash'] = $message; header('Location: ' . sfm_url($path)); exit; }
+function sfm_breadcrumbs(string $path): string { if ($path === '') return ''; $html = ''; $build = []; foreach (explode('/', $path) as $part) { $build[] = $part; $html .= '
/ ' . sfm_h($part) . ''; } return $html; }
+function sfm_fail(int $status, string $message): void { http_response_code($status); header('Content-Type: text/plain; charset=utf-8'); echo $message; exit; }
+
+function sfm_client_ip(): string { return (string) ($_SERVER['REMOTE_ADDR'] ?? ''); }
+function sfm_ip_is_allowed(string $ip, string $mode, array $rules): bool { if ($mode === 'off') return true; $matched = false; foreach ($rules as $rule) { if (sfm_ip_matches_rule($ip, $rule)) { $matched = true; break; } } return $mode === 'allow' ? $matched : !$matched; }
+function sfm_ip_matches_rule(string $ip, string $rule): bool { if (!str_contains($rule, '/')) return hash_equals($rule, $ip); [$network, $prefix] = explode('/', $rule, 2); if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false || filter_var($network, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false || !ctype_digit($prefix) || (int) $prefix > 32) return false; $mask = -1 << (32 - (int) $prefix); return ((ip2long($ip) & $mask) === (ip2long($network) & $mask)); }
+?>