From 94b9f7b049ad2ccbbf94902341a9f68a874ff7e1 Mon Sep 17 00:00:00 2001 From: Peter Johnson <johnson.peter@gmail.com> Date: Mon, 9 Dec 2024 22:41:19 -0800 Subject: [PATCH] Add directory browsing --- wpinet/src/main/native/cpp/WebServer.cpp | 52 +++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/wpinet/src/main/native/cpp/WebServer.cpp b/wpinet/src/main/native/cpp/WebServer.cpp index e101a2e9200..0e94e6ff043 100644 --- a/wpinet/src/main/native/cpp/WebServer.cpp +++ b/wpinet/src/main/native/cpp/WebServer.cpp @@ -14,11 +14,13 @@ #include <wpi/DenseMap.h> #include <wpi/Signal.h> #include <wpi/StringMap.h> +#include <wpi/json.h> #include <wpi/fs.h> #include <wpi/print.h> #include "wpinet/EventLoopRunner.h" #include "wpinet/HttpServerConnection.h" +#include "wpinet/HttpUtil.h" #include "wpinet/UrlParser.h" #include "wpinet/raw_uv_ostream.h" #include "wpinet/uv/GetAddrInfo.h" @@ -39,7 +41,7 @@ class MyHttpConnection : public wpi::HttpServerConnection, protected: void ProcessRequest() override; void SendFileResponse(int code, std::string_view codeText, - std::string_view contentType, std::string_view filename, + std::string_view contentType, fs::path filename, std::string_view extraHeader = {}); std::string m_path; @@ -141,7 +143,7 @@ static std::string_view GetMimeType(std::string_view ext) { void MyHttpConnection::SendFileResponse(int code, std::string_view codeText, std::string_view contentType, - std::string_view filename, + fs::path filename, std::string_view extraHeader) { // open file std::error_code ec; @@ -214,11 +216,51 @@ void MyHttpConnection::ProcessRequest() { query = url.GetQuery(); } // fmt::print(stderr, "query: \"{}\"\n", query); + HttpQueryMap qmap{query}; const bool isGET = m_request.GetMethod() == wpi::HTTP_GET; - if (isGET && wpi::starts_with(path, "/") && !wpi::contains(path, "..")) { - SendFileResponse(200, "OK", GetMimeType(wpi::rsplit(path, '.').second), - fmt::format("{}{}", m_path, path)); + if (isGET && wpi::starts_with(path, '/') && !wpi::contains(path, "..")) { + fs::path fullpath = fmt::format("{}{}", m_path, path); + std::error_code ec; + bool isdir = fs::is_directory(fullpath, ec); + if (isdir) { + if (!wpi::ends_with(path, '/')) { + // redirect to trailing / location + SendResponse(301, "Moved Permanently", "text/plain", "", + fmt::format("Location: {}/\r\n\r\n", path)); + return; + } + // generate directory listing + wpi::SmallString<64> formatBuf; + if (qmap.Get("format", formatBuf).value_or("") == "json") { + wpi::json j = wpi::json::array(); + for (auto&& entry : fs::directory_iterator{fullpath}) { + bool subdir = entry.is_directory(ec); + j.emplace_back( + wpi::json{{"filename", entry.path().filename().string()}, + {"size", subdir ? 0 : entry.file_size(ec)}, + {"directory", subdir}}); + } + SendResponse(200, "OK", "text/json", j.dump()); + } else { + std::string html = fmt::format( + "<html><head><title>{}</title></head><body>" + "<table><tr><th>Name</th><th>Size</th></tr>\n", + path); + for (auto&& entry : fs::directory_iterator{fullpath}) { + bool subdir = entry.is_directory(ec); + html += fmt::format( + "<tr><td><a href=\"{0}{1}\">{0}{1}</a></td><td>{2}</td></tr>", + entry.path().filename().string(), subdir ? "/" : "", + subdir ? "" : fmt::to_string(entry.file_size(ec))); + } + html += "</table></body></html>"; + SendResponse(200, "OK", "text/html", html); + } + } else { + SendFileResponse(200, "OK", GetMimeType(wpi::rsplit(path, '.').second), + fullpath); + } } else { SendError(404, "Resource not found"); }