PHP 如何在能够下载内容的同时显示文件夹及其内容
PHP how to display folder and its content while also being able to download the content
所以我正在尝试创建一个文件服务器,
基本上它应该工作的方式是你有很多包含文件的文件夹,当你点击一个文件时你会下载它,如果它是一个文件夹你可以打开它并看到里面的内容。
我的问题是如何创建这样的系统?我一直在尝试使用 FilesystemIterator,但我不知道如何继续。
这是我正在使用的 PHP 代码,此代码设置在 div 元素内部
<?php
$filesInFolder = array();
$directory = "src";
$iterator = new FilesystemIterator($directory);
foreach($iterator as $entry){
$filesInFolder[] = $entry->getFilename();
}
foreach($filesInFolder as $file){
echo "<a href='/$file'> $file </a>";
}
?>
这是我的文件结构
file structure
我知道可以创建它,我只是不知道如何创建它。
我稍微重构了你的代码:
$filesInFolder = array();
$baseDir = "/var/www/html/test";
$currentDir = !empty($_GET['dir']) ? $_GET['dir'] : $baseDir;
$currentDir = rtrim($currentDir, '/');
if (isset($_GET['download'])) {
//you could provide another logic to present requested file
readfile($_GET['download']);
exit;
}
$iterator = new FilesystemIterator($currentDir);
echo "<h3>" . $iterator->getPath() . "</h3>";
foreach ($iterator as $entry) {
$name = $entry->getBasename();
if (is_dir($currentDir . '/' . $name)) {
echo "D: <a href='?dir=" . $currentDir . "/" . $name . "'>" . $name . "</a><br />";
} elseif (is_file($currentDir . '/' . $name)) {
echo "F: <a href='?download=" . $currentDir . '/' . $name . "' download='" . $name . "'> " . $name . " </a><br />";
}
}
您必须非常小心,因为攻击者可以轻松地将查询更改为 ?dir=../../
并访问您的文件系统。
所以你必须自己防止这种情况。
编辑:它不是 100% 有效,但我正在努力尽快提供正确答案
编辑 2:代码重构后可以正常工作
这就是您的操作方式。
首先你像这样创建一个 FileReader.php 文件(我还添加了图标和文件大小,你需要创建图标文件夹,所有图标都将位于其中)
<?php
/**
* File reader, reads directory and outputs it in an array
*/
class FileReader {
public function __construct(
public string $root
) {}
/**
* @param string $path
*/
public function removeRootFromPath( $path) {
$path = preg_replace('/' . preg_quote($this->root, '/') . '/', '', $path);
$path = $this->cleanPath($path);
return DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
}
/**
* @param string $path
*/
public function addRootToPath( $path) {
$path = $this->removeRootFromPath($path);
$path = ltrim($path, DIRECTORY_SEPARATOR);
$root = rtrim($this->root, DIRECTORY_SEPARATOR);
return $root . DIRECTORY_SEPARATOR . $path;
}
/**
* @param string $dir Directory to load
*/
public function cleanPath( $dir) {
$sep = preg_quote(DIRECTORY_SEPARATOR, '/');
return preg_replace('/\.\.' . $sep . '|\.' . $sep . '/', '', $dir);
}
/**
* @param string $dir Directory to load
* @return FilesystemIterator|null
*/
public function readDirectory( $dir) {
$dir = $this->addRootToPath($dir);
try {
return new FilesystemIterator($dir, FilesystemIterator::SKIP_DOTS);
} catch (UnexpectedValueException $exception) {
return null;
}
}
/**
* @param string $size File size in bytes
* @param int $precision File size conversion precision
* @return string round($size, $precision).$units[$i]
*/
public function humanFilesize($size, $precision = 1) {
$units = array(' B',' kB',' MB',' GB',' TB',' PB',' EB',' ZB',' YB');
$step = 1024;
$i = 0;
while (($size / $step) > 0.9) {
$size = $size / $step;
$i++;
}
return round($size, $precision).$units[$i];
}
/**
* @param string $file File to load
* @return string $type <- File type
* @return string $image <- File image
*/
public function returnFileExtensionAndImage($file) {
$val = strtolower($file);
switch($val) {
case "avi":
$type = "Video file";
$image = "avi.png";
break;
case "bat":
$type = "Batch file";
$image = "bat.png";
break;
case "css":
$type = "Cascading Style Sheet";
$image = "txt.png";
break;
case "exe":
$type = "Executable file";
$image = "exe.png";
break;
case "fla":
$type = "Flash file";
$image = "fla.png";
break;
case "gif":
$type = "GIF Image";
$image = "gif.png";
break;
case "html":
$type = "HTML file";
$image = "html.png";
break;
case "htm":
$type = "HTM file";
$image = "html.png";
break;
case "jpg":
$type = "JPEG Image";
$image = "jpg.png";
break;
case "mp3":
$type = "Music File";
$image = "mp3.png";
break;
case "msg":
$type = "Email message";
$image = "msg.png";
break;
case "pdf":
$type = "PDF file";
$image = "pdf.png";
break;
case "psd":
$type = "Photoshop file";
$image = "psd.png";
break;
case "php":
$type = "PHP file";
$image = "php.png";
break;
case "ppt":
$type = "PowerPoint presentation";
$image = "ppt.png";
break;
case "pptx":
$type = "PowerPoint presentation";
$image = "ppt.png";
break;
case "swf":
$type = "SWF Flash file";
$image = "swf.png";
break;
case "txt":
$type = "Text file";
$image = "txt.png";
break;
case "csv":
$type = "CSV file";
$image = "txt.png";
break;
case "wma":
$type = "Windows Media Audio";
$image = "wma.png";
break;
case "xls":
$type = "Excel file";
$image = "xls.jpg";
break;
case "xlsx":
$type = "Excel file";
$image = "xls.jpg";
break;
case "zip":
$type = "Zip file";
$image = "zip.png";
break;
case "7zip":
$type = "Zip file";
$image = "zip.png";
break;
case "zip":
$type = "Zip file";
$image = "zip.png";
case "7z":
$type = "7Zip file";
$image = "rar.png";
break;
case "doc":
$type = "Word document";
$image = "doc.png";
break;
case "docx":
$type = "Word document";
$image = "doc.png";
break;
case "docs":
$type = "Word document";
$image = "doc.png";
case "rar":
$type = "Rar file";
$image = "rar.png";
//--- New Here---//
default:
$type = "Unknown file";
$image = "unknown.jpg";
}
return $type . "?" . $image;
}
}
?>
然后您创建 FileTable.php,其中将显示您的所有文件和文件夹,您将能够访问它们并下载选定的文件(仅文件而不是文件夹)+(我添加了简单的过滤)
<?php
$cleanPath = $target;
/**
* CURRENT DIRECTORY LOCATION DISPLAY FIX
*/
switch (strlen($cleanPath)) {
case 1:
$cleanPath[0] = " ";
break;
case 2:
if($cleanPath[0] == "\" && $cleanPath[1] == "/"){
$cleanPath[0] = " ";
$cleanPath[1] = " ";
$cleanPath[2] = " ";
break;
}else {
$cleanPath[0] = " ";
break;
}
default:
$cleanPath[0] = " ";
break;
}
/**
* HERE WRITE ALL FILES YOU WANT FileReader TO IGNORE
* - WRITE file + its extension
* - FOR DIRECTORY WRITE THE NAME OF THE DIRECTORY TO IGNORE
*/
$filesToIgnore = [
'index.php',
'index.css',
'index.html',
'Restricted',
'PUT YOUR FILES HERE.txt'
];
?>
<?php if(strlen($target) != 0): ?>
<p class="CurrentFolderLocation"><?= $cleanPath; ?></p>
<?php else: ?>
<p class="CurrentFolderLocation"></p>
<?php endif ?>
<table class="FileTable">
<div class="InputArea">
<input type="text" id="filterInput" onkeyup="filterTable()" placeholder="Name of the file to search for...">
</div>
<thead class="TableHead">
<tr>
<th>
<a class="ReturnImg" href="?path=<?= urlencode($reader->removeRootFromPath(dirname($target))); ?>">
<img src= "../Components/icons/levelup.png" alt="level up"/>
</a>
</th>
<th>File name</th>
<th>File size</th>
<th>File type</th>
<th>Last file modification date</th>
</tr>
</thead>
<tbody id="tableBody" class="TableBody">
<?php if ($results = $reader->readDirectory($target)): ?>
<?php foreach($results as $result): ?>
<?php
$currentFileToCheck = explode("\",$result);
$currentFileToCheck = $currentFileToCheck[array_key_last($currentFileToCheck)];
?>
<?php if(!in_array($currentFileToCheck,$filesToIgnore)): ?>
<tr>
<?php
// Make the full path user friendly by removing the root directory.
$user_friendly = $reader->removeRootFromPath($result->getFileInfo());
//File information
$fileName = pathinfo($result,PATHINFO_BASENAME);
$fileInfo = explode("?",($reader->returnFileExtensionAndImage(pathinfo($result,PATHINFO_EXTENSION))),2);
$fileExtension = $fileInfo[0];
$fileIcon = $iconsPath . $fileInfo[1];
$fileDateModified = explode(" ",date("F d.Y - H:i:s",filemtime($result)),4);
$fileDateModified = implode(" ",$fileDateModified);
$type = $result->getType();
if($type !== 'dir'){
$fileSize = $reader->humanFilesize(filesize($result));
}
?>
<?php if($type === 'dir'): ?>
<td><img class="FileImage" src="../Components/icons/folder.jpg"></td>
<td>
<a href="?path=<?= urlencode($user_friendly); ?>"><?= $fileName; ?></a>
</td>
<td></td>
<td></td>
<td></td>
<?php else: ?>
<td><img class="FileImage" src=<?= $fileIcon; ?> alt="Ikonka souboru"></td>
<?php if(pathinfo($result,PATHINFO_EXTENSION) == "pdf"): ?>
<td><a target="_blank" href="./<?= $user_friendly; ?>"><?= $fileName; ?></a></td>
<?php else: ?>
<td><a download href="./<?= $directoryToScan . "/" . $user_friendly; ?>"><?= $fileName; ?></a></td>
<?php endif ?>
<td><?= $fileSize; ?></td>
<td><?= $fileExtension; ?></td>
<td><?= $fileDateModified; ?></td>
<?php endif ?>
</tr>
<?php endif ?>
<?php endforeach ?>
<?php else: ?>
<tr>
<td></td>
<td>Directory/File doesn't exist</td>
<td></td>
<td></td>
<td></td>
</tr>
<?php endif ?>
</tbody>
</table>
<script>
function filterTable() {
// Declare variables
let input = document.getElementById("filterInput"),
filter = input.value.toUpperCase(),
table = document.getElementById("tableBody"),
tr = table.getElementsByTagName("tr"),
td,
i,
txtValue;
// Loop through all table rows, and hide those who don't match the search query
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[1];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
</script>
最后您将创建 index.php 并将其连接在一起
<?php
$path = $_SERVER['DOCUMENT_ROOT']; // Get Root path of your file system
$componentPath = $path . "/Components/";
$componentFileReader = $componentPath . "FileReader.php";
$componentFileTable = $componentPath . "FileTable.php";
$iconsPath = "/Components/icons/";
$cssStyle = "./Components/index.css";
include($componentFileReader);
$directoryToScan = 'src'; // Relative to current file. Change to your path!
$reader = new FileReader(__DIR__ . DIRECTORY_SEPARATOR . $directoryToScan);
$target = $reader->removeRootFromPath(!empty($_GET['path']) ? $_GET['path'] : '/');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href=<?=$cssStyle; ?>>
<title>File browser</title>
</head>
<body>
<main class="PageBody flex-column">
<?php include($componentFileTable) ?>
</main>
</body>
</html>
如果您需要它 -> CODE HERE <- 这是我在 Github 上的代码,带有一些 CSS 样式和一些图标 -> 只需下载它并将其放入您的 htdocs文件夹,如果您使用的是 Apache
所以我正在尝试创建一个文件服务器,
基本上它应该工作的方式是你有很多包含文件的文件夹,当你点击一个文件时你会下载它,如果它是一个文件夹你可以打开它并看到里面的内容。
我的问题是如何创建这样的系统?我一直在尝试使用 FilesystemIterator,但我不知道如何继续。
这是我正在使用的 PHP 代码,此代码设置在 div 元素内部
<?php
$filesInFolder = array();
$directory = "src";
$iterator = new FilesystemIterator($directory);
foreach($iterator as $entry){
$filesInFolder[] = $entry->getFilename();
}
foreach($filesInFolder as $file){
echo "<a href='/$file'> $file </a>";
}
?>
这是我的文件结构 file structure
我知道可以创建它,我只是不知道如何创建它。
我稍微重构了你的代码:
$filesInFolder = array();
$baseDir = "/var/www/html/test";
$currentDir = !empty($_GET['dir']) ? $_GET['dir'] : $baseDir;
$currentDir = rtrim($currentDir, '/');
if (isset($_GET['download'])) {
//you could provide another logic to present requested file
readfile($_GET['download']);
exit;
}
$iterator = new FilesystemIterator($currentDir);
echo "<h3>" . $iterator->getPath() . "</h3>";
foreach ($iterator as $entry) {
$name = $entry->getBasename();
if (is_dir($currentDir . '/' . $name)) {
echo "D: <a href='?dir=" . $currentDir . "/" . $name . "'>" . $name . "</a><br />";
} elseif (is_file($currentDir . '/' . $name)) {
echo "F: <a href='?download=" . $currentDir . '/' . $name . "' download='" . $name . "'> " . $name . " </a><br />";
}
}
您必须非常小心,因为攻击者可以轻松地将查询更改为 ?dir=../../
并访问您的文件系统。
所以你必须自己防止这种情况。
编辑:它不是 100% 有效,但我正在努力尽快提供正确答案
编辑 2:代码重构后可以正常工作
这就是您的操作方式。 首先你像这样创建一个 FileReader.php 文件(我还添加了图标和文件大小,你需要创建图标文件夹,所有图标都将位于其中)
<?php
/**
* File reader, reads directory and outputs it in an array
*/
class FileReader {
public function __construct(
public string $root
) {}
/**
* @param string $path
*/
public function removeRootFromPath( $path) {
$path = preg_replace('/' . preg_quote($this->root, '/') . '/', '', $path);
$path = $this->cleanPath($path);
return DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
}
/**
* @param string $path
*/
public function addRootToPath( $path) {
$path = $this->removeRootFromPath($path);
$path = ltrim($path, DIRECTORY_SEPARATOR);
$root = rtrim($this->root, DIRECTORY_SEPARATOR);
return $root . DIRECTORY_SEPARATOR . $path;
}
/**
* @param string $dir Directory to load
*/
public function cleanPath( $dir) {
$sep = preg_quote(DIRECTORY_SEPARATOR, '/');
return preg_replace('/\.\.' . $sep . '|\.' . $sep . '/', '', $dir);
}
/**
* @param string $dir Directory to load
* @return FilesystemIterator|null
*/
public function readDirectory( $dir) {
$dir = $this->addRootToPath($dir);
try {
return new FilesystemIterator($dir, FilesystemIterator::SKIP_DOTS);
} catch (UnexpectedValueException $exception) {
return null;
}
}
/**
* @param string $size File size in bytes
* @param int $precision File size conversion precision
* @return string round($size, $precision).$units[$i]
*/
public function humanFilesize($size, $precision = 1) {
$units = array(' B',' kB',' MB',' GB',' TB',' PB',' EB',' ZB',' YB');
$step = 1024;
$i = 0;
while (($size / $step) > 0.9) {
$size = $size / $step;
$i++;
}
return round($size, $precision).$units[$i];
}
/**
* @param string $file File to load
* @return string $type <- File type
* @return string $image <- File image
*/
public function returnFileExtensionAndImage($file) {
$val = strtolower($file);
switch($val) {
case "avi":
$type = "Video file";
$image = "avi.png";
break;
case "bat":
$type = "Batch file";
$image = "bat.png";
break;
case "css":
$type = "Cascading Style Sheet";
$image = "txt.png";
break;
case "exe":
$type = "Executable file";
$image = "exe.png";
break;
case "fla":
$type = "Flash file";
$image = "fla.png";
break;
case "gif":
$type = "GIF Image";
$image = "gif.png";
break;
case "html":
$type = "HTML file";
$image = "html.png";
break;
case "htm":
$type = "HTM file";
$image = "html.png";
break;
case "jpg":
$type = "JPEG Image";
$image = "jpg.png";
break;
case "mp3":
$type = "Music File";
$image = "mp3.png";
break;
case "msg":
$type = "Email message";
$image = "msg.png";
break;
case "pdf":
$type = "PDF file";
$image = "pdf.png";
break;
case "psd":
$type = "Photoshop file";
$image = "psd.png";
break;
case "php":
$type = "PHP file";
$image = "php.png";
break;
case "ppt":
$type = "PowerPoint presentation";
$image = "ppt.png";
break;
case "pptx":
$type = "PowerPoint presentation";
$image = "ppt.png";
break;
case "swf":
$type = "SWF Flash file";
$image = "swf.png";
break;
case "txt":
$type = "Text file";
$image = "txt.png";
break;
case "csv":
$type = "CSV file";
$image = "txt.png";
break;
case "wma":
$type = "Windows Media Audio";
$image = "wma.png";
break;
case "xls":
$type = "Excel file";
$image = "xls.jpg";
break;
case "xlsx":
$type = "Excel file";
$image = "xls.jpg";
break;
case "zip":
$type = "Zip file";
$image = "zip.png";
break;
case "7zip":
$type = "Zip file";
$image = "zip.png";
break;
case "zip":
$type = "Zip file";
$image = "zip.png";
case "7z":
$type = "7Zip file";
$image = "rar.png";
break;
case "doc":
$type = "Word document";
$image = "doc.png";
break;
case "docx":
$type = "Word document";
$image = "doc.png";
break;
case "docs":
$type = "Word document";
$image = "doc.png";
case "rar":
$type = "Rar file";
$image = "rar.png";
//--- New Here---//
default:
$type = "Unknown file";
$image = "unknown.jpg";
}
return $type . "?" . $image;
}
}
?>
然后您创建 FileTable.php,其中将显示您的所有文件和文件夹,您将能够访问它们并下载选定的文件(仅文件而不是文件夹)+(我添加了简单的过滤)
<?php
$cleanPath = $target;
/**
* CURRENT DIRECTORY LOCATION DISPLAY FIX
*/
switch (strlen($cleanPath)) {
case 1:
$cleanPath[0] = " ";
break;
case 2:
if($cleanPath[0] == "\" && $cleanPath[1] == "/"){
$cleanPath[0] = " ";
$cleanPath[1] = " ";
$cleanPath[2] = " ";
break;
}else {
$cleanPath[0] = " ";
break;
}
default:
$cleanPath[0] = " ";
break;
}
/**
* HERE WRITE ALL FILES YOU WANT FileReader TO IGNORE
* - WRITE file + its extension
* - FOR DIRECTORY WRITE THE NAME OF THE DIRECTORY TO IGNORE
*/
$filesToIgnore = [
'index.php',
'index.css',
'index.html',
'Restricted',
'PUT YOUR FILES HERE.txt'
];
?>
<?php if(strlen($target) != 0): ?>
<p class="CurrentFolderLocation"><?= $cleanPath; ?></p>
<?php else: ?>
<p class="CurrentFolderLocation"></p>
<?php endif ?>
<table class="FileTable">
<div class="InputArea">
<input type="text" id="filterInput" onkeyup="filterTable()" placeholder="Name of the file to search for...">
</div>
<thead class="TableHead">
<tr>
<th>
<a class="ReturnImg" href="?path=<?= urlencode($reader->removeRootFromPath(dirname($target))); ?>">
<img src= "../Components/icons/levelup.png" alt="level up"/>
</a>
</th>
<th>File name</th>
<th>File size</th>
<th>File type</th>
<th>Last file modification date</th>
</tr>
</thead>
<tbody id="tableBody" class="TableBody">
<?php if ($results = $reader->readDirectory($target)): ?>
<?php foreach($results as $result): ?>
<?php
$currentFileToCheck = explode("\",$result);
$currentFileToCheck = $currentFileToCheck[array_key_last($currentFileToCheck)];
?>
<?php if(!in_array($currentFileToCheck,$filesToIgnore)): ?>
<tr>
<?php
// Make the full path user friendly by removing the root directory.
$user_friendly = $reader->removeRootFromPath($result->getFileInfo());
//File information
$fileName = pathinfo($result,PATHINFO_BASENAME);
$fileInfo = explode("?",($reader->returnFileExtensionAndImage(pathinfo($result,PATHINFO_EXTENSION))),2);
$fileExtension = $fileInfo[0];
$fileIcon = $iconsPath . $fileInfo[1];
$fileDateModified = explode(" ",date("F d.Y - H:i:s",filemtime($result)),4);
$fileDateModified = implode(" ",$fileDateModified);
$type = $result->getType();
if($type !== 'dir'){
$fileSize = $reader->humanFilesize(filesize($result));
}
?>
<?php if($type === 'dir'): ?>
<td><img class="FileImage" src="../Components/icons/folder.jpg"></td>
<td>
<a href="?path=<?= urlencode($user_friendly); ?>"><?= $fileName; ?></a>
</td>
<td></td>
<td></td>
<td></td>
<?php else: ?>
<td><img class="FileImage" src=<?= $fileIcon; ?> alt="Ikonka souboru"></td>
<?php if(pathinfo($result,PATHINFO_EXTENSION) == "pdf"): ?>
<td><a target="_blank" href="./<?= $user_friendly; ?>"><?= $fileName; ?></a></td>
<?php else: ?>
<td><a download href="./<?= $directoryToScan . "/" . $user_friendly; ?>"><?= $fileName; ?></a></td>
<?php endif ?>
<td><?= $fileSize; ?></td>
<td><?= $fileExtension; ?></td>
<td><?= $fileDateModified; ?></td>
<?php endif ?>
</tr>
<?php endif ?>
<?php endforeach ?>
<?php else: ?>
<tr>
<td></td>
<td>Directory/File doesn't exist</td>
<td></td>
<td></td>
<td></td>
</tr>
<?php endif ?>
</tbody>
</table>
<script>
function filterTable() {
// Declare variables
let input = document.getElementById("filterInput"),
filter = input.value.toUpperCase(),
table = document.getElementById("tableBody"),
tr = table.getElementsByTagName("tr"),
td,
i,
txtValue;
// Loop through all table rows, and hide those who don't match the search query
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[1];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
</script>
最后您将创建 index.php 并将其连接在一起
<?php
$path = $_SERVER['DOCUMENT_ROOT']; // Get Root path of your file system
$componentPath = $path . "/Components/";
$componentFileReader = $componentPath . "FileReader.php";
$componentFileTable = $componentPath . "FileTable.php";
$iconsPath = "/Components/icons/";
$cssStyle = "./Components/index.css";
include($componentFileReader);
$directoryToScan = 'src'; // Relative to current file. Change to your path!
$reader = new FileReader(__DIR__ . DIRECTORY_SEPARATOR . $directoryToScan);
$target = $reader->removeRootFromPath(!empty($_GET['path']) ? $_GET['path'] : '/');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href=<?=$cssStyle; ?>>
<title>File browser</title>
</head>
<body>
<main class="PageBody flex-column">
<?php include($componentFileTable) ?>
</main>
</body>
</html>
如果您需要它 -> CODE HERE <- 这是我在 Github 上的代码,带有一些 CSS 样式和一些图标 -> 只需下载它并将其放入您的 htdocs文件夹,如果您使用的是 Apache