Nice little directory browser :D
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 174 lines 5.3 kB view raw
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 Microsoft.AspNetCore.Diagnostics; 21using Microsoft.AspNetCore.Http.Features; 22using Utatane.Components; 23using Microsoft.AspNetCore.ResponseCompression; 24using Microsoft.AspNetCore.Rewrite; 25using Utatane; 26 27var builder = WebApplication.CreateBuilder(new WebApplicationOptions() { 28 Args = args, 29 WebRootPath = "public" 30}); 31 32// Add services to the container. 33// Add services to the container. 34builder.Services.AddRazorComponents(); 35builder.Services.AddResponseCompression(options => { 36 options.EnableForHttps = true; 37 options.Providers.Add<BrotliCompressionProvider>(); 38 options.Providers.Add<GzipCompressionProvider>(); 39 options.MimeTypes = ResponseCompressionDefaults.MimeTypes; 40} ); 41builder.Services.Configure<BrotliCompressionProviderOptions>(options => { 42 options.Level = CompressionLevel.SmallestSize; 43}); 44 45builder.Services.Configure<GzipCompressionProviderOptions>(options => { 46 options.Level = CompressionLevel.SmallestSize; 47}); 48 49builder.Services.AddHttpContextAccessor(); 50 51var app = builder.Build(); 52 53// Configure the HTTP request pipeline. 54if (!app.Environment.IsDevelopment()) { 55 app.UseExceptionHandler("/Error", createScopeForErrors: true); 56} 57 58app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); 59 60app.UseAntiforgery(); 61app.UseResponseCompression(); 62 63// check paths exist 64app.Use(async (context, next) => { 65 context.Request.Headers.Append("X-Nhnd-Compress-Me", "false"); 66 if (context.Request.Path.StartsWithSegments("/api/files")) { 67 // TODO: for future JSON response at this endpoint, set false! 68 context.Request.Headers["X-Nhnd-Compress-Me"] = "true"; 69 await next(context); 70 return; 71 } else if ( 72 // if reexecuting for an error, let someone else handle that 73 context.Features.Get<IStatusCodeReExecuteFeature>() != null 74 // let our static file error handler do that 75 || context.Request.Path.StartsWithSegments("/.nhnd") 76 ) { 77 await next(context); 78 return; 79 } 80 81 var resolved = Utils.VerifyPath(Uri.UnescapeDataString(context.Request.Path)); 82 83 if (resolved.IsFailed) { 84 context.Response.StatusCode = StatusCodes.Status404NotFound; 85 86 var original = context.Request.Path; 87 88 var routeFeature = context.Features.Get<IRouteValuesFeature>(); 89 routeFeature?.RouteValues = new RouteValueDictionary(); 90 context.SetEndpoint(null); 91 92 await next(context); 93 94 context.Request.Path = original; 95 return; 96 } 97 98 // if we're a file AND we aren't a link to a directory 99 if (resolved.Value.IsT2 && resolved.Value.AsT2().UnravelLink() is not DirectoryInfo) { 100 FileInfo file = new FileInfo(resolved.Value.AsT2().UnravelLink().FullName); 101 102 await Results.File( 103 file.FullName, 104 MimeMapping.MimeUtility.GetMimeMapping(file.Name), 105 file.Name, 106 lastModified: file.LastWriteTimeUtc, 107 enableRangeProcessing: true 108 ).ExecuteAsync(context); 109 110 return; 111 } 112 113 // we are either a directory or a link to one 114 context.Request.Headers["X-Nhnd-Compress-Me"] = "true"; 115 await next(context); 116}); 117 118// HTML compression 119app.Use(async (context, next) => { 120 if (context.Request.Headers["X-Nhnd-Compress-Me"] == "false") { 121 await next(context); 122 return; 123 } 124 125 // context.Response.Body is a direct line to the client, so 126 // swap it out for our own in-memory stream for now 127 Stream responseStream = context.Response.Body; 128 using var memoryStream = new MemoryStream(); 129 context.Response.Body = memoryStream; 130 131 // let downstream render the response & write to our stream 132 await next(context); 133 134 if ( 135 context.Response.ContentType?.StartsWith("text/html") != true 136 || context.Response.Headers.ContentDisposition.Any(x => x != null && x.StartsWith("attachment")) 137 ) { 138 // oops my bad gangalang 139 // ok now put it back 140 memoryStream.Position = 0; 141 await memoryStream.CopyToAsync(responseStream); 142 context.Response.Body = responseStream; 143 144 return; 145 } 146 147 memoryStream.Position = 0; 148 String html = await new StreamReader(memoryStream).ReadToEndAsync(); 149 String minified = Utils.OptimizeHtml(html); 150 151 context.Response.ContentLength = Encoding.UTF8.GetByteCount(minified); 152 await responseStream.WriteAsync(Encoding.UTF8.GetBytes(minified)); 153 context.Response.Body = responseStream; 154}); 155 156app.UseRewriter(new RewriteOptions().AddRedirect(@"^favicon\.ico$", "/.nhnd/favicon.ico")); 157 158app.UseStaticFiles(new StaticFileOptions { 159 RequestPath = "/.nhnd" 160}); 161 162// if the static handler didn't pick this up, then 404 163app.Use(async (context, next) => { 164 if (context.Request.Path.StartsWithSegments("/.nhnd")) { 165 context.Response.StatusCode = StatusCodes.Status404NotFound; 166 return; 167 } 168 169 await next(context); 170}); 171 172app.MapRazorComponents<App>(); 173 174app.Run();