Преглед на файлове

LibWeb: Add directory entries page when visiting a local directory

Bastiaan van der Plaat преди 2 години
родител
ревизия
eafdb06d87

+ 51 - 0
Base/res/html/directory.html

@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <title>Index of @path@</title>
+        <style>
+            header {
+                margin-bottom: 10px;
+            }
+            h1 {
+                display: inline;
+            }
+            a {
+                text-decoration: none;
+            }
+            a:focus, a:hover {
+                text-decoration: underline;
+            }
+            table {
+                font-family: monospace;
+            }
+
+            .folder, .file, .open-parent {
+                display: inline-block;
+                width: 16px;
+                height: 16px;
+                margin-right: 5px;
+                background-size: contain;
+            }
+            .folder {
+                background-image: url('@resource_directory_url@/icons/32x32/filetype-folder.png');
+            }
+            .file {
+                background-image: url('@resource_directory_url@/icons/32x32/filetype-unknown.png');
+            }
+            .open-parent {
+                background-image: url('@resource_directory_url@/icons/16x16/open-parent-directory.png');
+            }
+        </style>
+    </head>
+    <body>
+        <header>
+            <span class="folder" style="width: 24px; height: 24px;"></span>
+            <h1>Index of @path@</h1>
+        </header>
+        <p><a href="file://@parent_path@"><span class="open-parent"></span>Open Parent Directory</a></p>
+        <hr>
+        @contents@
+        <hr>
+    </body>
+</html>

+ 1 - 0
Ladybird/WebContent/main.cpp

@@ -100,6 +100,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
 
     Web::FrameLoader::set_resource_directory_url(DeprecatedString::formatted("file://{}/res", s_serenity_resource_root));
     Web::FrameLoader::set_error_page_url(DeprecatedString::formatted("file://{}/res/html/error.html", s_serenity_resource_root));
+    Web::FrameLoader::set_directory_page_url(DeprecatedString::formatted("file://{}/res/html/directory.html", s_serenity_resource_root));
 
     TRY(Web::Bindings::initialize_main_thread_vm());
 

+ 1 - 0
Meta/gn/secondary/Userland/Libraries/LibWeb/Loader/BUILD.gn

@@ -3,6 +3,7 @@ source_set("Loader") {
   deps = [ "//Userland/Libraries/LibWeb:all_generated" ]
   sources = [
     "ContentFilter.cpp",
+    "FileDirectoryLoader.cpp",
     "FileRequest.cpp",
     "FrameLoader.cpp",
     "LoadRequest.cpp",

+ 1 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -444,6 +444,7 @@ set(SOURCES
     Layout/TreeBuilder.cpp
     Layout/VideoBox.cpp
     Loader/ContentFilter.cpp
+    Loader/FileDirectoryLoader.cpp
     Loader/FileRequest.cpp
     Loader/FrameLoader.cpp
     Loader/LoadRequest.cpp

+ 59 - 0
Userland/Libraries/LibWeb/Loader/FileDirectoryLoader.cpp

@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/QuickSort.h>
+#include <AK/SourceGenerator.h>
+#include <LibCore/DateTime.h>
+#include <LibCore/Directory.h>
+#include <LibCore/System.h>
+#include <LibWeb/Loader/FileDirectoryLoader.h>
+#include <LibWeb/Loader/FrameLoader.h>
+
+namespace Web {
+
+ErrorOr<DeprecatedString> load_file_directory_page(LoadRequest const& request)
+{
+    // Generate HTML contents entries table
+    auto lexical_path = LexicalPath(request.url().serialize_path());
+    Core::DirIterator dt(lexical_path.string(), Core::DirIterator::Flags::SkipParentAndBaseDir);
+    Vector<DeprecatedString> names;
+    while (dt.has_next())
+        names.append(dt.next_path());
+    quick_sort(names);
+
+    StringBuilder contents;
+    contents.append("<table>"sv);
+    for (auto& name : names) {
+        auto path = lexical_path.append(name);
+        auto maybe_st = Core::System::stat(path.string());
+        if (!maybe_st.is_error()) {
+            auto st = maybe_st.release_value();
+            contents.append("<tr>"sv);
+            contents.appendff("<td><span class=\"{}\"></span></td>", S_ISDIR(st.st_mode) ? "folder" : "file");
+            contents.appendff("<td><a href=\"file://{}\">{}</a></td><td>&nbsp;</td>"sv, path, name);
+            contents.appendff("<td>{:10}</td><td>&nbsp;</td>", st.st_size);
+            contents.appendff("<td>{}</td>"sv, Core::DateTime::from_timestamp(st.st_mtime).to_deprecated_string());
+            contents.append("</tr>\n"sv);
+        }
+    }
+    contents.append("</table>"sv);
+
+    // Generate HTML directory page from directory template file
+    // FIXME: Use an actual templating engine (our own one when it's built, preferably with a way to check these usages at compile time)
+    auto template_path = AK::URL::create_with_url_or_path(FrameLoader::directory_page_url()).serialize_path();
+    auto template_file = TRY(Core::File::open(template_path, Core::File::OpenMode::Read));
+    auto template_contents = TRY(template_file->read_until_eof());
+    StringBuilder builder;
+    SourceGenerator generator { builder };
+    generator.set("resource_directory_url", FrameLoader::resource_directory_url());
+    generator.set("path", escape_html_entities(lexical_path.string()));
+    generator.set("parent_path", escape_html_entities(lexical_path.parent().string()));
+    generator.set("contents", contents.to_deprecated_string());
+    generator.append(template_contents);
+    return generator.as_string_view().to_deprecated_string();
+}
+
+}

+ 15 - 0
Userland/Libraries/LibWeb/Loader/FileDirectoryLoader.h

@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/Loader/Resource.h>
+
+namespace Web {
+
+ErrorOr<DeprecatedString> load_file_directory_page(LoadRequest const&);
+
+}

+ 12 - 0
Userland/Libraries/LibWeb/Loader/FrameLoader.cpp

@@ -193,6 +193,18 @@ void FrameLoader::set_error_page_url(DeprecatedString error_page_url)
     s_error_page_url = error_page_url;
 }
 
+static DeprecatedString s_directory_page_url = "file:///res/html/directory.html";
+
+DeprecatedString FrameLoader::directory_page_url()
+{
+    return s_directory_page_url;
+}
+
+void FrameLoader::set_directory_page_url(DeprecatedString directory_page_url)
+{
+    s_directory_page_url = directory_page_url;
+}
+
 // FIXME: Use an actual templating engine (our own one when it's built, preferably
 // with a way to check these usages at compile time)
 

+ 2 - 0
Userland/Libraries/LibWeb/Loader/FrameLoader.h

@@ -29,6 +29,8 @@ public:
     static void set_resource_directory_url(DeprecatedString);
     static DeprecatedString error_page_url();
     static void set_error_page_url(DeprecatedString);
+    static DeprecatedString directory_page_url();
+    static void set_directory_page_url(DeprecatedString);
 
     explicit FrameLoader(HTML::BrowsingContext&);
     ~FrameLoader();

+ 21 - 0
Userland/Libraries/LibWeb/Loader/ResourceLoader.cpp

@@ -7,11 +7,13 @@
 
 #include <AK/Debug.h>
 #include <AK/JsonObject.h>
+#include <LibCore/Directory.h>
 #include <LibCore/ElapsedTimer.h>
 #include <LibCore/MimeData.h>
 #include <LibWeb/Cookie/Cookie.h>
 #include <LibWeb/Cookie/ParsedCookie.h>
 #include <LibWeb/Loader/ContentFilter.h>
+#include <LibWeb/Loader/FileDirectoryLoader.h>
 #include <LibWeb/Loader/LoadRequest.h>
 #include <LibWeb/Loader/ProxyMappings.h>
 #include <LibWeb/Loader/Resource.h>
@@ -251,6 +253,25 @@ void ResourceLoader::load(LoadRequest& request, Function<void(ReadonlyBytes, Has
 
             auto const fd = file_or_error.value();
 
+            // When local file is a directory use file directory loader to generate response
+            auto maybe_is_valid_directory = Core::Directory::is_valid_directory(fd);
+            if (!maybe_is_valid_directory.is_error() && maybe_is_valid_directory.value()) {
+                auto maybe_response = load_file_directory_page(request);
+                if (maybe_response.is_error()) {
+                    log_failure(request, maybe_response.error());
+                    if (error_callback)
+                        error_callback(DeprecatedString::formatted("{}", maybe_response.error()), 500u);
+                    return;
+                }
+
+                log_success(request);
+                HashMap<DeprecatedString, DeprecatedString, CaseInsensitiveStringTraits> response_headers;
+                response_headers.set("Content-Type"sv, "text/html"sv);
+                success_callback(maybe_response.release_value().bytes(), response_headers, {});
+                return;
+            }
+
+            // Try to read file normally
             auto maybe_file = Core::File::adopt_fd(fd, Core::File::OpenMode::Read);
             if (maybe_file.is_error()) {
                 log_failure(request, maybe_file.error());