Skip to content
Advertisement

PHP viewing folder content

I am trying to create a file server.

What I am trying to do is -> on the page there will be lots of folders displayed, when you click on one you will be shown its content and if there is a folder inside of this folder you can click on it and it will display the content of that folder.

This is the code I currently have, however I do not know how to proceed further as I am quite new to PHP.

function searchFolders($dir){
        $filesInFolder = array();
        $iterator = new FilesystemIterator($dir);

        foreach($iterator as $file){
            if(is_dir($file)){
                echo "<h1>Folder</h1>";
                searchFolders($file);
            }else{
                $filesInFolder[] = $file;
            }
        }

        foreach($filesInFolder as $file){
            echo "<a href='/$file'> $file </a><br>";
        }
    }
searchFolders('src')

Advertisement

Answer

Below is something I believe will help you out. I’ve create a directory and file listing interface using PHP’s FilesystemIterator, Bootstrap. Written for PHP 8.

Here is the below code in use: enter image description here

This example uses two files, Reader.php and index.php

Reader.php

<?php

class Reader {

    public function __construct(
        public string $root
    ) {}

    /**
     * Remove the root directory from the path value.
     *
     * @param string $path
     * @return string
     */
    public function removeRootFromPath(string $path) {
        $path = preg_replace('/^' . preg_quote($this->root, '/') . '/', '', $path);
        $path = $this->cleanPath($path);
        return DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
    }

    /**
     * Add the root directory to the path value.
     *
     * @param string $path
     * @return string
     */
    public function addRootToPath(string $path) {
        $path = $this->removeRootFromPath($path);
        $path = ltrim($path, DIRECTORY_SEPARATOR);
        $root = rtrim($this->root, DIRECTORY_SEPARATOR);
        return $root . DIRECTORY_SEPARATOR . $path;
    }

    /**
     * Replace dot notation in paths i.e ../ and ./
     *
     * @param string $dir
     * @return string
     */
    public function cleanPath(string $dir) {
        $sep = preg_quote(DIRECTORY_SEPARATOR, '/');
        return preg_replace('/..' . $sep . '|.' . $sep . '/', '', $dir);
    }

    /**
     * @param string $dir
     * @return FilesystemIterator|null
     */
    public function readDirectory(string $dir) {
        $dir = $this->addRootToPath($dir);

        try {
            return new FilesystemIterator($dir, FilesystemIterator::SKIP_DOTS);
        } catch (UnexpectedValueException $exception) {
            return null;
        }
    }

}

index.php

<?php
require_once 'Reader.php';

$root_dir = 'src'; // Relative to current file. Change to your path!
$reader = new Reader(__DIR__ . DIRECTORY_SEPARATOR . $root_dir);
$target = $reader->removeRootFromPath(!empty($_GET['path']) ? $_GET['path'] : '/');
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Directory viewer</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

    <div class="container my-5">

        <h1>Current: <?= $target; ?></h1>

        <table class="table table-bordered table-striped">
            <thead>
                <tr>
                    <th class="w-25">
                        Type
                    </th>
                    <th class="w-75">
                        File
                    </th>
                </tr>
            </thead>
            <tbody>
                <?php if ($target !== $reader->removeRootFromPath('/')): ?>
                    <tr>
                        <td>
                            Parent
                        </td>
                        <td>
                            <a href="?path=<?= urlencode($reader->removeRootFromPath(dirname($target))); ?>">../</a>
                        </td>
                    </tr>
                <?php endif ?>
                <?php if ($results = $reader->readDirectory($target)): ?>
                    <?php foreach($results as $result): ?>
                        <?php
                        // Make the full path user friendly by removing the root directory.
                        $user_friendly = $reader->removeRootFromPath($result->getFileInfo());

                        $type = $result->getType();
                        ?>
                        <tr>
                            <td>
                                <?= ucfirst($type); ?>
                            </td>
                            <td>
                                <?php if ($type === 'dir'): ?>
                                    <a href="?path=<?= urlencode($user_friendly); ?>"><?= $user_friendly; ?></a>
                                <?php else: ?>
                                    <!-- Maybe add a download link to the file -->
                                    <?= $user_friendly; ?>
                                <?php endif ?>
                            </td>
                        </tr>
                    <?php endforeach ?>
                <?php else: ?>
                    <tr>
                        <td colspan="2">
                            Directory does not exist.
                        </td>
                    </tr>
                <?php endif ?>
            </tbody>
        </table>
    </div>

</body>
</html>

The Reader class includes a number of helper methods removeRootFromPath(), addRootToPath() and cleanPath() the combination of these are there to help prevent users from accessing paths outside of your source directory.

For example, if your file system looked something like….

/var/www/Reader.php
/var/www/index.php
/var/www/src/

And you were listing the /var/www/src/ directory, with out the path cleaning methods the the user could modify the input to ../../ and end up at your filesystems root which is something you don’t want!

This is something I’ve put together quickly for the sake of this question and should be used as a guide.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement