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