Nice little directory browser :D
1/*
2 This file is part of Utatane.
3
4 Utatane is free software: you can redistribute it and/or modify it under
5 the terms of the GNU Affero General Public License as published by the Free
6 Software Foundation, either version 3 of the License, or (at your option)
7 any later version.
8
9 Utatane is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
12 more details.
13
14 You should have received a copy of the GNU Affero General Public License
15 along with Utatane. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18using System.IO.Compression;
19using System.Text;
20using Utatane.Components;
21using Microsoft.AspNetCore.ResponseCompression;
22using Microsoft.AspNetCore.Rewrite;
23using Utatane;
24
25var builder = WebApplication.CreateBuilder(new WebApplicationOptions() {
26 Args = args,
27 WebRootPath = "public"
28});
29
30// Add services to the container.
31// Add services to the container.
32builder.Services.AddRazorComponents();
33builder.Services.AddResponseCompression(options => {
34 options.EnableForHttps = true;
35 options.Providers.Add<BrotliCompressionProvider>();
36 options.Providers.Add<GzipCompressionProvider>();
37 options.MimeTypes = ResponseCompressionDefaults.MimeTypes;
38} );
39builder.Services.Configure<BrotliCompressionProviderOptions>(options => {
40 options.Level = CompressionLevel.SmallestSize;
41});
42
43builder.Services.Configure<GzipCompressionProviderOptions>(options => {
44 options.Level = CompressionLevel.SmallestSize;
45});
46
47var app = builder.Build();
48
49// Configure the HTTP request pipeline.
50if (!app.Environment.IsDevelopment()) {
51 app.UseExceptionHandler("/Error", createScopeForErrors: true);
52 // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
53 app.UseHsts();
54}
55
56app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
57app.UseHttpsRedirection();
58
59app.UseAntiforgery();
60app.UseResponseCompression();
61
62// HTML compression
63app.Use(async (context, next) => {
64 // context.Response.Body is a direct line to the client, so
65 // swap it out for our own in-memory stream for now
66 Stream responseStream = context.Response.Body;
67 using var memoryStream = new MemoryStream();
68 context.Response.Body = memoryStream;
69
70 // let downstream render the response & write to our stream
71 await next(context);
72
73 if (
74 context.Response.ContentType?.StartsWith("text/html") != true
75 /* || TODO: don't compress when serving HTML files found by Utatane!! */ ) {
76 // oops my bad gangalang
77 // ok now put it back
78 memoryStream.Position = 0;
79 await memoryStream.CopyToAsync(responseStream);
80 context.Response.Body = responseStream;
81
82 return;
83 }
84
85 memoryStream.Position = 0;
86 String html = await new StreamReader(memoryStream).ReadToEndAsync();
87 String minified = Utils.OptimizeHtml(html);
88
89 context.Response.ContentLength = Encoding.UTF8.GetByteCount(minified);
90 await responseStream.WriteAsync(Encoding.UTF8.GetBytes(minified));
91 context.Response.Body = responseStream;
92});
93
94// check paths exist
95app.Use(async (context, next) => {
96 if (
97 context.Request.Path.StartsWithSegments("/api/files")
98 || context.Request.Path.StartsWithSegments("/.nhnd")
99 ) {
100 await next(context);
101 return;
102 }
103
104 var resolved = Utils.VerifyPath(context.Request.Path);
105
106 if (resolved.IsFailed) {
107 context.Response.StatusCode = StatusCodes.Status404NotFound;
108 return;
109 }
110
111 // if we're a file AND we aren't a link to a directory
112 if (resolved.Value.IsT2 && resolved.Value.AsT2().UnravelLink() is not DirectoryInfo) {
113 FileInfo file = new FileInfo(resolved.Value.AsT2().UnravelLink().FullName);
114
115 await Results.File(
116 file.FullName,
117 MimeMapping.MimeUtility.GetMimeMapping(file.Name),
118 file.Name,
119 lastModified: file.LastWriteTimeUtc,
120 enableRangeProcessing: true
121 ).ExecuteAsync(context);
122
123 return;
124 }
125
126 // Console.WriteLine($"dir={resolved.Value.IsT1} file={resolved.Value.IsT2}");
127
128 // we are either a directory or a link to one
129
130 await next(context);
131});
132
133app.UseRewriter(new RewriteOptions().AddRedirect(@"^favicon\.ico$", "/.nhnd/favicon.ico"));
134
135app.UseStaticFiles(new StaticFileOptions {
136 RequestPath = "/.nhnd"
137});
138app.MapRazorComponents<App>();
139
140app.Run();