Media: uploads stripped of metadata, attachments both ways, a remote media proxy
- /api/v1/media and /api/v2/media (and GET/PUT /api/v1/media/:id):
images go through libvips (NetVips, its native build bundled):
autorotated, every kind of metadata dropped (EXIF, GPS, XMP, IPTC,
comments), capped at 4096 px, with a 640 px preview and a blurhash
(own encoder, the reference algorithm); animated GIFs are re-encoded;
video and audio are remuxed by ffmpeg with -map_metadata -1, never
re-encoded, and a video gets a still preview. Files get random names
under /var/lib/privapub/media, outside the web root deploys replace, and
are served at /media/files with nosniff and a sandbox CSP.
- media_ids on create and edit (four at most, the persona's own, each used
once); notes carry them as Document attachments with alt text, blurhash,
focalPoint and size; inbound attachments were already kept.
- avatar and header uploads in update_credentials, cropped to 400x400 and
1500x500, federated with Update{Person}.
- Remote media reaches clients only through /media/proxy/{hmac}/{url},
fetched by the guarded client (no SVG, 40 MB cap) and cached outside the
served root, trimmed to 5 GB; foreign avatars and headers use it too, so
a client never contacts another server.
- MediaJanitor deletes uploads left unattached for a day.
- nginx accepts 100 MB bodies on the upload endpoints only (applied on Max).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
8f75317050
commit
4a713f3fb6
22 files changed
+1030
-26
No files matched your search
@@ -58,6 +58,10 @@ try
|
||||
.PrivaPubOAuth(builder.Environment)
|
||||
.AddScoped<PrivaPub.Api.Mastodon.Mappers.MastodonMapper>()
|
||||
.AddScoped<PrivaPub.Api.Mastodon.Mappers.AccountSearch>()
|
||||
.Configure<PrivaPub.Domain.Media.MediaOptions>(builder.Configuration.GetSection("Media"))
|
||||
.AddSingleton<PrivaPub.Domain.Media.IMediaService, PrivaPub.Domain.Media.MediaService>()
|
||||
.AddSingleton<PrivaPub.Domain.Media.IMediaProxy, PrivaPub.Domain.Media.MediaProxy>()
|
||||
.AddHostedService<PrivaPub.Domain.Media.MediaJanitor>()
|
||||
.PrivaPubMiddlewareConfiguration();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -124,6 +128,19 @@ try
|
||||
app.UseCors("DefaultCORS");
|
||||
|
||||
app.UseStaticFiles();
|
||||
var mediaRoot = app.Services.GetRequiredService<PrivaPub.Domain.Media.IMediaService>().Root;
|
||||
Directory.CreateDirectory(mediaRoot);
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(mediaRoot),
|
||||
RequestPath = "/media/files",
|
||||
OnPrepareResponse = context =>
|
||||
{
|
||||
context.Context.Response.Headers["X-Content-Type-Options"] = "nosniff";
|
||||
context.Context.Response.Headers["Content-Security-Policy"] = "default-src 'none'; sandbox";
|
||||
context.Context.Response.Headers["Cache-Control"] = "public, max-age=31536000, immutable";
|
||||
}
|
||||
});
|
||||
|
||||
app.UseRequestLocalization(await localizationService.Get());
|
||||
|
||||
|
||||
Reference in new issue
Block a user