Sharing and improvement requests of file management system based on apache+ngin environment

Dear developers! Hello everyone, this is the file management system I built based on apache+ngin environment, with a total of 3 folders! If there is an improvement or a more perfect solution, can you share it with me? Thank you!

index.php

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>File Browser</title>
    <style>  
        body {  
            font-family: Arial, sans-serif;  
            margin: 0;  
            padding: 20px;  
        }  
        h2 {  
            margin-top: 0;  
        }  
        table {  
            width: 80%;  
            border-collapse: collapse;  
            margin-top: 20px;  
        }  
        th, td {  
            padding: 10px;  
            text-align: left;  
            border-bottom: 1px solid #ddd;  
        }  
        th {  
            background-color: #f2f2f2;  
            font-weight: bold;  
        }  
        a {  
            text-decoration: none;  
            color: #0645ad;  
        }  
        a:hover {  
            text-decoration: underline;  
        }  
        tr:nth-child(even) {  
            background-color: #f9f9f9;  
        }  
        .highlight {  
            background-color: yellowgreen !important;  
        }  
    </style>  
</head>
<body>
    <h2>Root Directory:</h2>
<?php  
// Get the current directory  
$baseDir = __DIR__;  
$currentDir = isset($_GET['dir']) && is_dir($baseDir . '/' . $_GET['dir'])  
    ? realpath($baseDir . '/' . $_GET['dir'])  
    : $baseDir;  
  
// Current path (as part of the URL)  
$currentPath = isset($_GET['dir']) ? $_GET['dir'] : ''; // Not urlencoded for now  
  
// Parent directory path  
$parentPath = ''; // Initialize as an empty string  
if ($currentPath !== '') {  
    $parentPath = dirname($currentPath);  
    // Remove any trailing slashes and ensure it does not return '.' as the parent path  
    if ($parentPath === '.') {  
        $parentPath = '';  
    } else {  
        $parentPath = urlencode($parentPath); // Now encode the parent path  
    }  
}  
  
// If the current directory is not the root directory, display the link to go back up  
if ($currentDir !== $baseDir) {  
    // Use a relative path to build the link to go back up  
    echo '<a href="?dir=' . $parentPath . '">Go Back</a>';  
}
// Function to format file size  
function formatSize($bytes) {  
    $units = array('B', 'KB', 'MB', 'GB', 'TB');  
    $bytes = max($bytes, 0);  
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));  
    $pow = min($pow, count($units) - 1);  
    $bytes /= pow(1024, $pow);  
    return round($bytes, 2) . ' ' . $units[$pow];  
}  
  
// Function to return emoji based on file extension  
function getIcon($extension, $isFile) {  
    if ($isFile) {  
        switch ($extension) {  
            case 'jpg':
            case 'jpeg':
            case 'png':
            case 'gif':
                return 'πŸ–ΌοΈ'; // Image  
            case 'zip':
            case 'tar':
            case 'gz':
            case 'tgz':
                return 'πŸ“¦'; // Compressed file  
            case 'txt':
            case 'md':
            case 'php':
            case 'html':
            case 'css':
            case 'js':
                return 'πŸ“„'; // Text file  
            default:
                return 'πŸ“'; // Other files  
        }  
    } else {  
        return 'πŸ“'; // Folder  
    }  
}  
// Display the file list  
echo '<table>';  
echo '<tr><th>Icon</th><th>Name</th><th>Last Modified Time</th><th>Size</th><th>Actions</th></tr>';  
  
$files = new DirectoryIterator($currentDir);  
foreach ($files as $file) {  
    // Check and skip hidden filenames  
    if ($file->isDot()) continue;  
  
    $isFile = $file->isFile();  
    $filename = $file->getFilename();  
    $encodedFilename = urlencode($filename); // Encode the filename once, for later use  
    $relativePath = $currentPath . ($currentPath !== '' ? '/' : '') . $filename;  
    $encodedRelativePath = urlencode($relativePath); // Encode the relative path once  
    $extension = $isFile ? strtolower($file->getExtension()) : '';  
    $size = $isFile ? formatSize($file->getSize()) : '-';  
    $lastModified = date('Y-m-d H:i:s', $file->getMTime());  
    $icon = getIcon($extension, $isFile);  
  
// Set the base URL    
$baseUrl = $isFile ? 'view.php?file=' : '?dir=';    
$url = $baseUrl . $encodedRelativePath;    
  
echo "<tr>";  
echo "<td>" . $icon . "</td>";  
echo "<td>";  
if (!$isFile || !in_array($extension, ['zip', 'tar.gz'])) {    
    // For non-ZIP/tar.gz files, display the filename as a link  
    echo "<a href='" . htmlspecialchars($url) . "' " . ($isFile && in_array($extension, ['jpg', 'jpeg', 'png', 'gif']) ? 'target="_blank"' : '') . ">" . htmlspecialchars($filename) . "</a>";  
} else {  
    // For ZIP/tar.gz files, display the filename and hint text, but do not link the filename  
    echo htmlspecialchars($filename) . " <span style='color:gray;'>(Please click Download on the right)</span>";  
}  
echo "</td>";  
echo "<td>" . htmlspecialchars($lastModified) . "</td>";  
echo "<td>" . htmlspecialchars($size) . "</td>";  
echo "<td>";  
if ($isFile) {  
    if (in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) {  
        echo "<a href='view.php?file=" . $encodedFilename . "' target='_blank'>View</a>";  
    } elseif (in_array($extension, ['zip', 'tar.gz'])) {  
        echo "<a href='download.php?file=" . $encodedFilename . "' target='_blank'>Download</a>";  
    }  
} else {  
    echo "<a href='?dir=" . $encodedFilename . "'>OPEN</a>";  
}  
echo "</td>";  
echo "</tr>";
}  
?>
</table>
    <script>  
        // Highlight the selected table row  
        document.querySelectorAll('table tr').forEach(function(row) {  
            row.addEventListener('click', function() {  
                // Remove the highlight from previously highlighted rows  
                document.querySelectorAll('table tr.highlight').forEach(function(highlightedRow) {  
                    highlightedRow.classList.remove('highlight');  
                });  
                // Add the highlight class to the current row  
                this.classList.add('highlight');  
            });  
        });  
    </script>  
</body>
</html>

download.php

download.php handles file download requests. It verifies whether the requested file exists in the allowed directory and provides the file to the user for download.

<?php
// download.php
$allowedDir = 'uploads/'; // Root directory allowed for downloads

if (isset($_GET['file'])) {
    $requestedFile = $_GET['file'];
    // Remove any path traversal characters
    $requestedFile = str_replace(['..', '/', '\\'], '', $requestedFile);

    // Check if the file exists within the uploads directory or its subdirectories
    $found = false;
    $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($allowedDir));
    foreach ($iterator as $file) {
        if ($file->getFilename() === $requestedFile) {
            $filePath = $file->getPathname();
            $found = true;
            break;
        }
    }

    if ($found) {
        if ($file->isFile() && $file->isReadable()) {
            header('Content-Description: File Transfer');
            header('Content-Type: application/octet-stream');
            header('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
            header('Expires: 0');
            header('Cache-Control: must-revalidate');
            header('Pragma: public');
            header('Content-Length: ' . $file->getSize());
            ob_clean();
            flush();
            readfile($filePath);
            exit;
        } else {
            header('HTTP/1.0 403 Forbidden');
            echo "File is not readable or is not a file.";
        }
    } else {
        header('HTTP/1.0 404 Not Found');
        echo "File not found.";
    }
} else {
    header('HTTP/1.0 400 Bad Request');
    echo "Error: No file specified.";
}
?>

view.php

view.php is responsible for displaying the contents of viewable files such as images and documents in common formats. It sets the appropriate headers to ensure that the file is displayed inline in the browser.

<?php
// Set the allowed file directory for access (relative to the current script directory)
$allowedDir = 'uploads/';

// Check if the file has been sent via the GET parameter 'file'
if (isset($_GET['file'])) {
    // Get and validate the filename
    $file = basename(urldecode($_GET['file']));
    $filePath = __DIR__ . DIRECTORY_SEPARATOR . $allowedDir . DIRECTORY_SEPARATOR . $file;

    // Ensure the file is within the allowed directory and exists
    if (file_exists($filePath) && strpos($filePath, __DIR__ . DIRECTORY_SEPARATOR . $allowedDir) === 0) {
        // Get the file's MIME type
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mimeType = finfo_file($finfo, $filePath);
        finfo_close($finfo);

        // Decide to display the content or redirect to the download page based on the MIME type
        switch ($mimeType) {
            case 'image/jpeg':
            case 'image/png':
            case 'image/gif':
            case 'application/pdf':
                // Other document types that are supported for direct viewing can be added here
                // Set the Content-Type header and output the file content
                header('Content-Type: ' . $mimeType);
                header('Content-Disposition: inline; filename="' . $file . '"');
                readfile($filePath);
                exit;
            default:
                // For unsupported file types that cannot be viewed directly, provide a download link
                header('Location: download.php?file=' . urlencode($allowedDir . $file));
                exit;
        }
    } else {
        header('HTTP/1.0 404 Not Found');
        echo "The file does not exist or the path is incorrect.";
    }
} else {
    header('HTTP/1.0 400 Bad Request');
    echo "Error: No file specified.";
}
?>

Here’s how it works

Save the three files in the root directory of the website. Create a new uploads folder. The function of this folder is that all your downloadable and viewable files are in this directory. Files in other root directories are not affected! . No .htaccess support is required

The files are located in the /public_html directory

  • uploads
  • Description: The directory that stores all downloadable and viewable files.
  • download.php
  • Function: Provides an interface for processing file download requests in the uploads folder.
  • Description: When a user clicks a download link, this file will handle file retrieval and send HTTP responses.
  • index.php
  • Function: Serves as the main interface of the file browser.
  • view.php
  • Function: Used to view viewable files in the uploads folder. Supports file previews in formats such as images and common documents.