| File: HtmlDirectoryFormatter.cs | Web Access |
| Project: src\aspnetcore\src\Middleware\StaticFiles\src\Microsoft.AspNetCore.StaticFiles.csproj (Microsoft.AspNetCore.StaticFiles) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; using System.Linq; using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.StaticFiles; /// <summary> /// Generates an HTML view for a directory. /// </summary> public class HtmlDirectoryFormatter : IDirectoryFormatter { private const string TextHtmlUtf8 = "text/html; charset=utf-8"; private readonly HtmlEncoder _htmlEncoder; /// <summary> /// Constructs the <see cref="HtmlDirectoryFormatter"/>. /// </summary> /// <param name="encoder">The character encoding representation to use.</param> public HtmlDirectoryFormatter(HtmlEncoder encoder) { ArgumentNullException.ThrowIfNull(encoder); _htmlEncoder = encoder; } /// <summary> /// Generates an HTML view for a directory. /// </summary> public virtual Task GenerateContentAsync(HttpContext context, IEnumerable<IFileInfo> contents) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(contents); context.Response.ContentType = TextHtmlUtf8; if (HttpMethods.IsHead(context.Request.Method)) { // HEAD, no response body return Task.CompletedTask; } PathString requestPath = context.Request.PathBase + context.Request.Path; var builder = new StringBuilder(); builder.AppendFormat( CultureInfo.InvariantCulture, @"<!DOCTYPE html> <html lang=""{0}"">", CultureInfo.CurrentUICulture.TwoLetterISOLanguageName); builder.AppendFormat( CultureInfo.InvariantCulture, @" <head> <title>{0} {1}</title>", HtmlEncode(Resources.HtmlDir_IndexOf), HtmlEncode(requestPath.Value!)); builder.Append(@" <style> body { font-family: ""Segoe UI"", ""Segoe WP"", ""Helvetica Neue"", 'RobotoRegular', sans-serif; font-size: 14px;} header h1 { font-family: ""Segoe UI Light"", ""Helvetica Neue"", 'RobotoLight', ""Segoe UI"", ""Segoe WP"", sans-serif; font-size: 28px; font-weight: 100; margin-top: 5px; margin-bottom: 0px;} #index { border-collapse: separate; border-spacing: 0; margin: 0 0 20px; } #index th { vertical-align: bottom; padding: 10px 5px 5px 5px; font-weight: 400; color: #737373; text-align: center; } #index td { padding: 3px 10px; } #index th, #index td { border-right: 1px #ddd solid; border-bottom: 1px #ddd solid; border-left: 1px transparent solid; border-top: 1px transparent solid; box-sizing: border-box; } #index th:last-child, #index td:last-child { border-right: 1px transparent solid; } #index td.length, td.modified { text-align:right; } a { color:#127aac;text-decoration:none; } a:hover { color:#13709e;text-decoration:underline; } </style> </head> <body> <section id=""main"">"); builder.AppendFormat( CultureInfo.InvariantCulture, @" <header><h1>{0} <a href=""/"">/</a>", HtmlEncode(Resources.HtmlDir_IndexOf)); string cumulativePath = "/"; foreach (var segment in requestPath.Value!.Split('/', StringSplitOptions.RemoveEmptyEntries)) { cumulativePath = cumulativePath + segment + "/"; builder.AppendFormat( CultureInfo.InvariantCulture, @"<a href=""{0}"">{1}/</a>", HtmlEncode(cumulativePath), HtmlEncode(segment)); } builder.AppendFormat( CultureInfo.InvariantCulture, @"</h1></header> <table id=""index"" summary=""{0}""> <thead> <tr><th abbr=""{1}"">{1}</th><th abbr=""{2}"">{2}</th><th abbr=""{3}"">{4}</th></tr> </thead> <tbody>", HtmlEncode(Resources.HtmlDir_TableSummary), HtmlEncode(Resources.HtmlDir_Name), HtmlEncode(Resources.HtmlDir_Size), HtmlEncode(Resources.HtmlDir_Modified), HtmlEncode(Resources.HtmlDir_LastModified)); foreach (var subdir in contents.Where(info => info.IsDirectory)) { // Collect directory metadata in a try...catch in case the file is deleted while we're getting the data. // The metadata is retrieved prior to calling AppendFormat so if it throws, we won't have written a row // to the table. try { builder.AppendFormat( CultureInfo.InvariantCulture, @" <tr class=""directory""> <td class=""name""><a href=""./{0}/"">{0}/</a></td> <td></td> <td class=""modified"">{1}</td> </tr>", HtmlEncode(subdir.Name), HtmlEncode(subdir.LastModified.ToString(CultureInfo.CurrentCulture))); } catch (DirectoryNotFoundException) { // The physical DirectoryInfo class doesn't appear to throw for either // of Name or LastWriteTimeUtc (which backs LastModified in the physical provider) // if the directory doesn't exist. However, we don't know what other providers might do. // Just skip this directory. It was deleted while we were enumerating. } catch (FileNotFoundException) { // The physical DirectoryInfo class doesn't appear to throw for either // of Name or LastWriteTimeUtc (which backs LastModified in the physical provider) // if the directory doesn't exist. However, we don't know what other providers might do. // Just skip this directory. It was deleted while we were enumerating. } } foreach (var file in contents.Where(info => !info.IsDirectory)) { // Collect file metadata in a try...catch in case the file is deleted while we're getting the data. // The metadata is retrieved prior to calling AppendFormat so if it throws, we won't have written a row // to the table. try { builder.AppendFormat( CultureInfo.InvariantCulture, @" <tr class=""file""> <td class=""name""><a href=""./{0}"">{0}</a></td> <td class=""length"">{1}</td> <td class=""modified"">{2}</td> </tr>", HtmlEncode(file.Name), HtmlEncode(file.Length.ToString("n0", CultureInfo.CurrentCulture)), HtmlEncode(file.LastModified.ToString(CultureInfo.CurrentCulture))); } catch (DirectoryNotFoundException) { // There doesn't appear to be a case where DirectoryNotFound is thrown in the physical provider, // but we don't know what other providers might do. // Just skip this file. It was deleted while we were enumerating. } catch (FileNotFoundException) { // Just skip this file. It was deleted while we were enumerating. } } builder.Append(@" </tbody> </table> </section> </body> </html>"); string data = builder.ToString(); byte[] bytes = Encoding.UTF8.GetBytes(data); context.Response.ContentLength = bytes.Length; return context.Response.Body.WriteAsync(bytes, 0, bytes.Length); } private string HtmlEncode(string body) { return _htmlEncoder.Encode(body); } }