/* This file is part of Utatane. Utatane is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Utatane is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Utatane. If not, see . */ using System.IO.Compression; using System.Text; using Utatane.Components; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.Rewrite; using Utatane; var builder = WebApplication.CreateBuilder(new WebApplicationOptions() { Args = args, WebRootPath = "public" }); // Add services to the container. // Add services to the container. builder.Services.AddRazorComponents(); builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; options.Providers.Add(); options.Providers.Add(); options.MimeTypes = ResponseCompressionDefaults.MimeTypes; } ); builder.Services.Configure(options => { options.Level = CompressionLevel.SmallestSize; }); builder.Services.Configure(options => { options.Level = CompressionLevel.SmallestSize; }); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); app.UseHttpsRedirection(); app.UseAntiforgery(); app.UseResponseCompression(); // HTML compression app.Use(async (context, next) => { // context.Response.Body is a direct line to the client, so // swap it out for our own in-memory stream for now Stream responseStream = context.Response.Body; using var memoryStream = new MemoryStream(); context.Response.Body = memoryStream; // let downstream render the response & write to our stream await next(context); if ( context.Response.ContentType?.StartsWith("text/html") != true /* || TODO: don't compress when serving HTML files found by Utatane!! */ ) { // oops my bad gangalang // ok now put it back memoryStream.Position = 0; await memoryStream.CopyToAsync(responseStream); context.Response.Body = responseStream; return; } memoryStream.Position = 0; String html = await new StreamReader(memoryStream).ReadToEndAsync(); String minified = Utils.OptimizeHtml(html); context.Response.ContentLength = Encoding.UTF8.GetByteCount(minified); await responseStream.WriteAsync(Encoding.UTF8.GetBytes(minified)); context.Response.Body = responseStream; }); // check paths exist app.Use(async (context, next) => { if ( context.Request.Path.StartsWithSegments("/api/files") || context.Request.Path.StartsWithSegments("/.nhnd") ) { await next(context); return; } var resolved = Utils.VerifyPath(context.Request.Path); if (resolved.IsFailed) { context.Response.StatusCode = StatusCodes.Status404NotFound; return; } // if we're a file AND we aren't a link to a directory if (resolved.Value.IsT2 && resolved.Value.AsT2().UnravelLink() is not DirectoryInfo) { FileInfo file = new FileInfo(resolved.Value.AsT2().UnravelLink().FullName); await Results.File( file.FullName, MimeMapping.MimeUtility.GetMimeMapping(file.Name), file.Name, lastModified: file.LastWriteTimeUtc, enableRangeProcessing: true ).ExecuteAsync(context); return; } // Console.WriteLine($"dir={resolved.Value.IsT1} file={resolved.Value.IsT2}"); // we are either a directory or a link to one await next(context); }); app.UseRewriter(new RewriteOptions().AddRedirect(@"^favicon\.ico$", "/.nhnd/favicon.ico")); app.UseStaticFiles(new StaticFileOptions { RequestPath = "/.nhnd" }); app.MapRazorComponents(); app.Run();