Skip to content

Repository files navigation

Bennewitz.Ninja.FileServer

CI Latest release License: MIT NuGet

A lightweight ASP.NET Core web app that serves a local directory over HTTP or HTTPS with directory browsing enabled. Point it at a folder; browse it in any web browser.

The same file browser ships as a NuGet package, so you can mount it on a route of an application you already have — see Use it in your own app.

Security note: The standalone server has no authentication or authorization. Use it only on trusted networks or behind a reverse proxy that handles access control. Mounted in your own application, the component honours whatever authorization you apply to it — downloads included.


Quick start

Prerequisites: .NET 10 SDK

  1. Clone the repository and create a settings.json next to the executable (or in the project directory when running with dotnet run):

    {
      "ServedFilesRoot": "/path/to/your/files",
      "ServedFilesRoute": "files",
      "HttpPort": 5550
    }
  2. Run the app from the repository root:

    dotnet run --project src/Bennewitz.Ninja.FileServer.Cli
  3. Open http://localhost:5550/ in a browser — it redirects automatically to the file browser.


Use it in your own app

The file browser is a component, not just an executable. Install it into any ASP.NET Core application:

dotnet add package Bennewitz.Ninja.FileServer
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFileServer();

var app = builder.Build();

app.MapFileServer("/docs", options => options.RootPath = "/srv/docs");

app.Run();

/docs lists the directory and renders Markdown, styled the same as the standalone server, with no wwwroot/ to copy and nothing to register in your static-file pipeline.

MapFileServer returns the mount's route group, so authorization applied to it covers every route the mount owns — listings and downloads:

app.MapFileServer("/private", o => o.RootPath = "/srv/private")
   .RequireAuthorization("StaffOnly");

Call it once per directory to serve several, each with its own extension filter, layout, and policy. Full options and behaviour: package README.

A runnable host with four mounts — default styling, a host layout, an extension filter, and one behind RequireAuthorization — is in samples/SampleWebApp.


HTTPS

HTTPS is enabled automatically when a PFX certificate is configured. The server will listen on both the HTTP port (redirecting to HTTPS) and the HTTPS port.

Generating a self-signed certificate

Using .NET's built-in tool (simplest):

dotnet dev-certs https -ep ./server.pfx -p yourpassword

Using OpenSSL (cross-platform, production-grade):

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes \
  -subj "/CN=fileserver"
openssl pkcs12 -export -out server.pfx -inkey key.pem -in cert.pem -passout pass:yourpassword

Enabling HTTPS in settings.json

Add CertificatePath and optionally CertificatePassword to your settings.json:

{
  "ServedFilesRoot": "/path/to/your/files",
  "HttpPort": 5550,
  "HttpsPort": 5551,
  "CertificatePath": "/path/to/server.pfx",
  "CertificatePassword": "yourpassword"
}

Note: Browsers will show a security warning for self-signed certificates. You can suppress this permanently by adding the certificate to your OS or browser trust store.


Docker

See docker/README.md for Docker usage, including the standard (volume-mounted) and bundled-content image variants.


Markdown rendering

.md files are rendered as formatted HTML instead of raw text. Every rendered page has three controls in the top-right corner:

  • View raw — opens the raw Markdown source in the browser.
  • Download — downloads the .md file directly.
  • Auto / Light / Dark — cycles the colour theme between system default, forced light, and forced dark.

Directory listings carry the same Auto / Light / Dark control, beside the ↑ Up link. Both pages remember the choice under one key, so a pinned scheme holds while browsing rather than reverting whenever you leave a document.

Fenced code blocks are tokenised. A fence tagged with a language the tokeniser knows — csharp, xml, javascript, typescript, powershell, sql, css, html, json, python, java, php, cpp, fsharp, vb.net, markdown, and a handful more — is coloured using GitHub's own token palette, so the colour scheme above governs code along with the rest of the page. A fence tagged with anything else keeps its text and loses only the colour.

Append ?raw=1 to any .md URL to bypass rendering from any client.

When AllowedExtensions is set and .md is not included, rendering never comes up: .md files are hidden from listings and refused on download like any other excluded extension.


Configuration

Settings are resolved in this order (later sources override earlier ones):

  1. settings.json — located next to the application: beside the executable when published, or in the build output when running with dotnet run, where the copy in the project directory lands
  2. Environment variables
  3. Command-line arguments (highest priority)
settings.json key Environment variable CLI argument Default Description
ServedFilesRoot FILE_SERVER_ROOT --root (required) Absolute path to the directory to serve. The app fails to start if absent or not absolute.
ServedFilesRoute FILE_SERVER_ROUTE --route files URL path segment under which files appear (e.g. files → /files/…).
HttpPort FILE_SERVER_HTTP_PORT --http-port 5550 TCP port Kestrel listens on for HTTP. Redirects to HTTPS when a certificate is configured.
HttpsPort FILE_SERVER_HTTPS_PORT --https-port 5551 TCP port Kestrel listens on for HTTPS. Has no effect when no certificate is configured.
CertificatePath FILE_SERVER_CERT_PATH --cert (none — HTTP only) Absolute path to a PFX certificate file. When set, HTTPS is enabled on HttpsPort.
CertificatePassword FILE_SERVER_CERT_PASSWORD --cert-password (empty) Password for the PFX file. May be omitted for password-less PFX files.
AllowedExtensions FILE_SERVER_ALLOWED_EXTENSIONS --allowed-extensions (empty — all files) JSON string array of permitted file extensions (e.g. [".pdf", ".txt"]). Env var and CLI: semicolon-delimited (e.g. .pdf;.txt;.zip). When non-empty, only matching files appear in listings and can be downloaded. Directories are always visible. Leading dot is optional.
UnlistedPatterns FILE_SERVER_UNLISTED_PATTERNS --unlisted-patterns (empty) JSON string array of globs (env var and CLI: semicolon-delimited) for files and directories left out of listings but still served at their exact URL. See Unlisted files.
ExposedSensitivePatterns FILE_SERVER_EXPOSED_SENSITIVE_PATTERNS --exposed-sensitive-patterns (empty) Globs, same formats, for dot-prefixed or Hidden paths to serve anyway, e.g. .well-known/**. See Hidden and dot-prefixed files.

Unlisted files

FileServer --root /srv/files --unlisted-patterns "**/*.key;private"

A matching entry is left out of its directory's listing and still downloads at its exact URL. Patterns are globs anchored at the served root and compared case-insensitively: *.key matches a.key but not sub/a.key, which takes **/*.key. private hides the directory entry, private/** hides what is inside it. An unlisted directory still lists its own contents at its URL, and unlisting never makes a file downloadable that would otherwise be refused.

Unlisted is not access control. Anyone holding the URL gets the file, and URLs leak through browser history, referrers and logs.

Hidden and dot-prefixed files

Any path with a segment that starts with a dot — or, on Windows, a file or directory with the Hidden or System attribute — is never listed and never served: .env, .git/config and anything beneath .private/ return 404. To serve one anyway, name it:

FileServer --root /srv/files --exposed-sensitive-patterns ".well-known/**"

These patterns match the whole path relative to the served root, case-sensitively. .well-known/** serves everything beneath .well-known but not the directory itself; add .well-known too to make it listable.

CLI argument syntax

Both space-separated and equals forms are accepted:

FileServer --root /srv/files --http-port 8080
FileServer --root=/srv/files --http-port=8080

Run FileServer --help (or -h / -?) to print all options and exit.

Cross-platform path notes

  • Windows: use backslashes or forward slashes — both work (C:\Share or C:/Share).
  • Linux / macOS / Docker: use POSIX paths (/srv/files).
  • Relative paths are rejected at startup with a descriptive error.

Building

# Debug build
dotnet build -c Debug

# Release build (also produces XML documentation)
dotnet build -c Release

# Tests
dotnet test

# Publish the self-contained single-file binary for Linux x64.
# The CLI project is the executable; the other is the component library it references.
dotnet publish src/Bennewitz.Ninja.FileServer.Cli -c Release -r linux-x64 --self-contained \
  -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true

# Pack the component as a NuGet package
dotnet pack src/Bennewitz.Ninja.FileServer -c Release

The publish output contains only the executable and settings.json.example. All UI assets are embedded into the component assembly at build time and served from its own endpoint — no separate folder is needed at runtime, and no static-file middleware either. Rename settings.json.example to settings.json (or use environment variables / CLI arguments) to configure the server.


Development

Set ASPNETCORE_ENVIRONMENT=Development to enable detailed error pages. Configuration comes from settings.json (and environment variables / CLI arguments) — appsettings.json is not used by this app.


License

MIT — see LICENSE.

The rendered-Markdown styling is github-markdown-css by Sindre Sorhus, vendored and embedded in the binary under its MIT licence. Its notice, and any other third-party material shipped inside this software, is reproduced in THIRD-PARTY-NOTICES.md.

About

A lightweight ASP.NET Core web app that serves a local directory over HTTP or HTTPS with directory browsing enabled. Point it at a folder; browse it in any web browser.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages