/*
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
|| context.Response.Headers.ContentDisposition.Any(x => x != null && x.StartsWith("attachment"))
) {
// 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) {
await Results.NotFound().ExecuteAsync(context);
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();