using System.Text.RegularExpressions; namespace Uwaa.HTTP.Routing; /// /// Special router which serves static files from the filesystem. /// public partial class FileEndpoint : RouterBase { static MIMEType GuessMIMEType(string extension) { return extension switch { ".txt" => new("text", "plain"), ".htm" or "html" => new("text", "html"), ".js" => new("text", "javascript"), ".css" => new("text", "css"), ".csv" => new("text", "csv"), ".bin" => new("application", "octet-stream"), ".zip" => new("application", "zip"), ".7z" => new("application", "x-7z-compressed"), ".gz" => new("application", "gzip"), ".xml" => new("application", "xml"), ".pdf" => new("application", "pdf"), ".json" => new("application", "json"), ".bmp" => new("image", "bmp"), ".png" => new("image", "png"), ".jpg" or "jpeg" => new("image", "jpeg"), ".gif" => new("image", "gif"), ".webp" => new("image", "webp"), ".svg" => new("image", "svg+xml"), ".ico" => new("image", "vnd.microsoft.icon"), ".mid" or ".midi" => new("audio", "midi"), ".mp3" => new("audio", "mpeg"), ".ogg" or ".oga" or ".opus" => new("audio", "ogg"), ".wav" => new("audio", "wav"), ".weba" => new("audio", "webm"), ".webm" => new("video", "webm"), ".mp4" => new("video", "mp4"), ".mpeg" => new("video", "mpeg"), ".ogv" => new("video", "ogg"), ".otf" => new("font", "otf"), ".ttf" => new("font", "ttf"), ".woff" => new("font", "woff"), ".woff2" => new("font", "woff2"), _ => new("application", "octet-stream"), //Unknown }; } /// /// The source directory from which assets should be served. /// public string Directory; public override HttpMethod Method => HttpMethod.GET; public override int Arguments => 1; public FileEndpoint(string directory) { Directory = directory; } protected override async Task GetResponseInner(HttpRequest req, HttpClientInfo info, ArraySegment path) { string? asset = path[0]; if (string.IsNullOrWhiteSpace(asset)) return null; if (FilenameChecker().IsMatch(asset)) return HttpResponse.BadRequest("Illegal chars in asset path"); string assetPath = $"{Directory}/{asset}"; FileInfo fileInfo = new FileInfo(assetPath); MIMEType mime; if (string.IsNullOrEmpty(fileInfo.Extension)) { mime = new MIMEType("text", "html"); assetPath += ".htm"; } else { mime = GuessMIMEType(fileInfo.Extension); } if (!File.Exists(assetPath)) return null; return new HttpContent(mime, await File.ReadAllBytesAsync(assetPath)); } /// /// Ensures filenames are legal. /// /// /// Enforcing a set of legal characters in filenames reduces the potential attack surface against the server. /// /// Returns a regular expression which checks for invalid characters. [GeneratedRegex(@"[^a-zA-Z0-9_\-.()\[\] ]")] private static partial Regex FilenameChecker(); }