feat: autoindex

This commit is contained in:
Quinten 2025-10-28 14:19:52 +01:00
parent b27b3fe2d2
commit 0534d07948
5 changed files with 137 additions and 8 deletions

View File

@ -1,7 +1,8 @@
#include <webserv/handler/FileHandler.hpp> #include "webserv/utils/AutoIndex.hpp"
#include <webserv/config/AConfig.hpp> // for AConfig #include <webserv/config/AConfig.hpp> // for AConfig
#include <webserv/handler/ErrorHandler.hpp> // for ErrorHandler #include <webserv/handler/ErrorHandler.hpp> // for ErrorHandler
#include <webserv/handler/FileHandler.hpp>
#include <webserv/handler/MIMETypes.hpp> // for MIMETypes #include <webserv/handler/MIMETypes.hpp> // for MIMETypes
#include <webserv/handler/URI.hpp> // for URI #include <webserv/handler/URI.hpp> // for URI
#include <webserv/http/HttpConstants.hpp> // for NOT_FOUND, FORBIDDEN, OK #include <webserv/http/HttpConstants.hpp> // for NOT_FOUND, FORBIDDEN, OK
@ -53,7 +54,6 @@ void FileHandler::handleDirectory(const std::string &dirpath, ResourceType type)
{ {
Log::trace(LOCATION); Log::trace(LOCATION);
Log::debug("Requested path is a directory: " + dirpath); Log::debug("Requested path is a directory: " + dirpath);
if (type == DIRECTORY_INDEX) if (type == DIRECTORY_INDEX)
{ {
auto possible_indexes = config_->get<std::vector<std::string>>("index").value(); auto possible_indexes = config_->get<std::vector<std::string>>("index").value();
@ -71,7 +71,10 @@ void FileHandler::handleDirectory(const std::string &dirpath, ResourceType type)
if (type == DIRECTORY_AUTOINDEX) if (type == DIRECTORY_AUTOINDEX)
{ {
Log::debug("Requested path is a directory: " + dirpath); Log::debug("Requested path is a directory: " + dirpath);
ErrorHandler::createErrorResponse(Http::StatusCode::FORBIDDEN, response_, config_); // ErrorHandler::createErrorResponse(Http::StatusCode::FORBIDDEN, response_, config_);
// TODO: This doesn't trigger for some reason :p
response_.setBody(AutoIndex::generate(dirpath));
response_.setStatus(Http::StatusCode::OK);
return; return;
} }
ErrorHandler::createErrorResponse(Http::StatusCode::NOT_FOUND, response_, config_); ErrorHandler::createErrorResponse(Http::StatusCode::NOT_FOUND, response_, config_);

View File

@ -0,0 +1,83 @@
#include <webserv/utils/AutoIndex.hpp>
#include <webserv/utils/FileUtils.hpp>
#include <format>
#include <string>
#include <vector>
std::string AutoIndex::generate(const std::string &dir)
{
std::ostringstream html;
html << "<!DOCTYPE html>\n"
<< "<html><head>\n"
<< "<title>Index of " << dir << "</title>\n"
<< "<style>\n"
<< "body { font-family: Arial, sans-serif; margin: 40px; }\n"
<< "h1 { border-bottom: 1px solid #ccc; }\n"
<< "table { border-collapse: collapse; width: 100%; }\n"
<< "th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid #ddd; }\n"
<< "th { background-color: #f5f5f5; }\n"
<< "a { text-decoration: none; color: #0066cc; }\n"
<< "a:hover { text-decoration: underline; }\n"
<< ".dir { font-weight: bold; }\n"
<< "</style>\n"
<< "</head><body>\n"
<< "<h1>Index of " << dir << "</h1>\n"
<< "<table>\n"
<< "<tr><th>Name</th><th>Last Modified</th><th>Size</th></tr>\n";
// Add parent directory link if not root
if (dir != "/")
{
html << "<tr><td><a href=\"../\" class=\"dir\">[Parent Directory]</a></td><td>-</td><td>-</td></tr>\n";
}
auto entries = FileUtils::listDirectory(dir);
for (const auto &entry : entries)
{
std::string href = entry.path().filename().string();
if (entry.is_directory())
{
href += "/";
}
html << "<tr><td><a href=\"" << href << "\"";
if (entry.is_directory())
{
html << " class=\"dir\"";
}
html << ">" << entry.path().filename().string();
if (entry.is_directory())
{
html << "/";
}
html << "</a></td>" << "<td></td>"; //<< std::format("{:%Y-%m-%d %H:%M:%S}", entry.last_write_time()) << "</td>"
html << "<td>" << (entry.is_directory() ? "-" : formatFileSize(entry.file_size())) << "</td></tr>\n";
}
html << "</table>\n"
<< "<hr><p><em>webserv</em></p>\n"
<< "</body></html>\n";
return html.str();
}
std::string AutoIndex::formatFileSize(uintmax_t size)
{
static const std::vector<std::string> units = {"B", "KB", "MB", "GB", "TB"};
int unitIndex = 0;
auto displaySize = static_cast<double>(size);
while (displaySize >= 1024 && unitIndex < 4)
{
displaySize /= 1024;
++unitIndex;
}
std::ostringstream oss;
oss.precision(2);
oss << std::fixed << displaySize << " " << units.at(unitIndex);
return oss.str();
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <cstdint>
#include <string>
class AutoIndex
{
public:
AutoIndex() = delete;
AutoIndex(const AutoIndex &) = delete;
AutoIndex &operator=(const AutoIndex &) = delete;
AutoIndex(AutoIndex &&) = delete;
AutoIndex &operator=(AutoIndex &&) = delete;
~AutoIndex() = delete;
static std::string generate(const std::string &dir);
private:
static std::string formatFileSize(uintmax_t size);
};

View File

@ -1,11 +1,12 @@
#include <webserv/log/Log.hpp> // for Log, LOCATION
#include <webserv/utils/FileUtils.hpp> #include <webserv/utils/FileUtils.hpp>
#include <webserv/log/Log.hpp> // for Log, LOCATION
#include <cstring> // for size_t #include <cstring> // for size_t
#include <filesystem>
#include <fstream> // for basic_ifstream, basic_ios, basic_istream, ios, ifstream, operator|, basic_istream::read, basic_istream::seekg, basic_istream::tellg, streamsize #include <fstream> // for basic_ifstream, basic_ios, basic_istream, ios, ifstream, operator|, basic_istream::read, basic_istream::seekg, basic_istream::tellg, streamsize
#include <iterator> // for istreambuf_iterator, operator== #include <iterator> // for istreambuf_iterator, operator==
#include <string> // for basic_string, string, char_traits, operator+ #include <string> // for basic_string, string, char_traits, operator+
#include <vector>
#include <sys/stat.h> // for stat, S_ISDIR, S_ISREG #include <sys/stat.h> // for stat, S_ISDIR, S_ISREG
@ -13,7 +14,10 @@ namespace FileUtils
{ {
bool isDirectory(const std::string &path) bool isDirectory(const std::string &path)
{ {
struct stat pathStat{}; struct stat pathStat
{
};
if (stat(path.c_str(), &pathStat) != 0) if (stat(path.c_str(), &pathStat) != 0)
{ {
return false; // Could not access path return false; // Could not access path
@ -23,7 +27,10 @@ bool isDirectory(const std::string &path)
bool isFile(const std::string &path) bool isFile(const std::string &path)
{ {
struct stat pathStat{}; struct stat pathStat
{
};
if (stat(path.c_str(), &pathStat) != 0) if (stat(path.c_str(), &pathStat) != 0)
{ {
return false; // Could not access path return false; // Could not access path
@ -33,7 +40,10 @@ bool isFile(const std::string &path)
bool isValidPath(const std::string &path) bool isValidPath(const std::string &path)
{ {
struct stat pathStat{}; struct stat pathStat
{
};
return stat(path.c_str(), &pathStat) == 0; return stat(path.c_str(), &pathStat) == 0;
} }
@ -115,4 +125,15 @@ std::string readFileAsString(const std::string &filepath)
return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()}; return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
} }
std::vector<std::filesystem::directory_entry> listDirectory(const std::string &dirpath)
{
std::vector<std::filesystem::directory_entry> entries = {};
for (const auto &entry : std::filesystem::directory_iterator(dirpath))
{
entries.emplace_back(entry);
}
return entries;
}
} // namespace FileUtils } // namespace FileUtils

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <filesystem>
#include <string> #include <string>
#include <vector> #include <vector>
@ -14,4 +15,6 @@ std::string joinPath(const std::string &base, const std::string &addition);
std::vector<char> readBinaryFile(const std::string &filepath); std::vector<char> readBinaryFile(const std::string &filepath);
std::string readFileAsString(const std::string &filepath); std::string readFileAsString(const std::string &filepath);
std::vector<std::filesystem::directory_entry> listDirectory(const std::string &dirpath);
} // namespace FileUtils } // namespace FileUtils