v0.2.5 - Fix AVIF blobs downloading instead of displaying: add image/avif -> .avif to centralized MIME map in bud08.c and add .avif/.bin to nginx try_files lists (remote.nginx.config, config/ginxsom-local.conf, config/local-nginx.conf); update README and NGINX_CONFIG_UPDATES docs; rebuild static binary

This commit is contained in:
Laan Tungir
2026-09-16 20:42:29 -04:00
parent 311c5c9e41
commit fb81ea7a23
9 changed files with 158 additions and 8 deletions
+1 -1
View File
@@ -200,7 +200,7 @@ server {
}
# GET requests served directly with extension fallback
try_files /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.txt /$1.md =404;
try_files /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.avif /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.txt /$1.md /$1.bin =404;
}
# Upload endpoint
Binary file not shown.
+1 -1
View File
@@ -113,7 +113,7 @@ server {
return 405;
}
try_files /blobs/$1.html /blobs/$1.js /blobs/$1.mjs /blobs/$1.css /blobs/$1.txt /blobs/$1.jpg /blobs/$1.jpeg /blobs/$1.png /blobs/$1.webp /blobs/$1.gif /blobs/$1.pdf /blobs/$1.mp4 /blobs/$1.mp3 /blobs/$1.md =404;
try_files /blobs/$1.html /blobs/$1.js /blobs/$1.mjs /blobs/$1.css /blobs/$1.txt /blobs/$1.jpg /blobs/$1.jpeg /blobs/$1.png /blobs/$1.webp /blobs/$1.avif /blobs/$1.gif /blobs/$1.pdf /blobs/$1.mp4 /blobs/$1.mp3 /blobs/$1.md /blobs/$1.bin =404;
# Declare UTF-8 on text responses: nginx derives Content-Type from
# the file extension (.txt -> text/plain) with no charset, so
+2 -2
View File
@@ -298,7 +298,7 @@ http {
}
# GET requests - serve files directly with extension fallback
try_files /$1.html /$1.js /$1.mjs /$1.css /$1.txt /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md =404;
try_files /$1.html /$1.js /$1.mjs /$1.css /$1.txt /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.avif /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md /$1.bin =404;
# Declare UTF-8 on text responses: nginx derives Content-Type from
# the file extension (.txt -> text/plain) with no charset, so
@@ -684,7 +684,7 @@ http {
}
# GET requests - serve files directly with extension fallback
try_files /$1.html /$1.js /$1.mjs /$1.css /$1.txt /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md =404;
try_files /$1.html /$1.js /$1.mjs /$1.css /$1.txt /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.avif /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md /$1.bin =404;
# Declare UTF-8 on text responses: nginx derives Content-Type from
# the file extension (.txt -> text/plain) with no charset, so
+1 -1
View File
@@ -223,7 +223,7 @@ server {
return 405;
}
try_files /$1.txt /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md =404;
try_files /$1.txt /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.avif /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md /$1.bin =404;
# Cache headers
add_header Cache-Control "public, max-age=31536000, immutable";
+149
View File
@@ -0,0 +1,149 @@
# Fix: `.avif` files download instead of displaying
## Symptom
Browsers download `.avif` blobs instead of rendering them inline. Other image
types (`.jpg`, `.png`, `.webp`, `.gif`) display correctly.
## Root Cause
There are **two independent gaps** in the AVIF path, and they compound.
### 1. Backend MIME to extension map is missing `image/avif`
The centralized map in [`src/bud08.c`](src/bud08.c:98) drives every place a blob
is written to disk:
```c
#define GINXSOM_MIME_EXTENSION_ENTRIES(X) \
X("image/jpeg", ".jpg") \
X("image/webp", ".webp") \
X("image/png", ".png") \
X("image/gif", ".gif") \
... (no image/avif) ...
```
[`mime_to_extension()`](src/bud08.c:139) falls through to `".bin"` for any
unmapped type. So an AVIF upload is stored on disk as `<sha256>.bin`, while the
database records `type = "image/avif"`. The stored extension and the recorded
MIME type disagree.
This map is used by:
- [`handle_upload_request_with_validation()`](src/main.c:1866) (BUD-02 upload)
- [`handle_mirror_request()`](src/bud04.c:427) (BUD-04 mirror)
- [`handle_get_request()`](src/main.c:1195) (FastCGI GET fallback)
- [`handle_delete_request_with_validation()`](src/main.c:1654)
- [`admin_commands.c`](src/admin_commands.c:667)
### 2. nginx `try_files` extension lists are missing `.avif`
GET requests for blobs are served **directly by nginx from disk** (not FastCGI).
nginx resolves the file by trying a hardcoded list of extensions:
```
try_files /$1.html /$1.js /$1.mjs /$1.css /$1.txt /$1.jpg /$1.jpeg
/$1.png /$1.webp /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md =404;
```
`.avif` is not in the list, and neither is `.bin`. Consequences:
- If the blob was stored as `<sha>.avif`, nginx never finds it (404).
- If the blob was stored as `<sha>.bin` (the current AVIF behavior), nginx
either 404s, or — in deployments using the wildcard form
`try_files /$1* =404` documented in [`README.md`](README.md:580) — finds the
`.bin` file and serves it with `default_type application/octet-stream`.
`application/octet-stream` plus the `X-Content-Type-Options: nosniff` header
(see [`config/ginxsom-local.conf`](config/ginxsom-local.conf:20)) forces the
browser to download rather than render. This is the observed symptom.
Note: [`mime.types`](mime.types:19) **already** contains
`image/avif avif;`, so once the file is stored with a `.avif` extension and
nginx is allowed to find it, the correct `Content-Type: image/avif` is emitted
automatically. The MIME table is not the problem.
## Affected Files
| File | Change |
|------|--------|
| [`src/bud08.c`](src/bud08.c:98) | Add `X("image/avif", ".avif")` to the map |
| [`config/ginxsom-local.conf`](config/ginxsom-local.conf:116) | Add `.avif` to `try_files` |
| [`config/local-nginx.conf`](config/local-nginx.conf:301) | Add `.avif` to both `try_files` blocks (HTTP + HTTPS) |
| [`remote.nginx.config`](remote.nginx.config:187) | Add `.avif` to `try_files` |
| [`README.md`](README.md:203) | Update documented `try_files` examples |
| [`docs/NGINX_CONFIG_UPDATES.md`](docs/NGINX_CONFIG_UPDATES.md:226) | Update documented `try_files` example |
## Fix Plan
### Step 1 - Backend: map `image/avif` to `.avif`
In [`src/bud08.c`](src/bud08.c:98), add an entry to
`GINXSOM_MIME_EXTENSION_ENTRIES`:
```c
X("image/avif", ".avif") \
```
This automatically:
- stores new AVIF uploads as `<sha256>.avif`
- adds `image/avif` to the advertised `supported_mime_types` list in the
NIP-11 root response
- makes NIP-94 canonical URLs end in `.avif`
### Step 2 - nginx: add `.avif` to every `try_files` list
Add `/$1.avif` (or `/blobs/$1.avif` in the local conf) to each list. Place it
next to the other image extensions for readability.
### Step 3 - nginx: add `.bin` to `try_files` (related robustness fix)
Blobs whose MIME type is unknown are stored as `.bin`. They are currently
unreachable through the explicit `try_files` lists. Adding `/$1.bin` lets them
be served (as `application/octet-stream`, which is correct for unknown types).
This is optional but closes the same class of bug.
### Step 4 - Update documentation
Update the `try_files` examples in [`README.md`](README.md:203) and
[`docs/NGINX_CONFIG_UPDATES.md`](docs/NGINX_CONFIG_UPDATES.md:226) so future
deployments include `.avif`.
### Step 5 - Rebuild static binaries
Rebuild via [`Dockerfile.alpine-musl`](Dockerfile.alpine-musl:1) /
[`build_static.sh`](build_static.sh) so the updated `mime_to_extension()` map is
compiled into `build/ginxsom-fcgi_static_*`.
### Step 6 - Verify
1. Upload an AVIF with `Content-Type: image/avif`.
2. Confirm the file on disk is `<sha256>.avif` (not `.bin`).
3. Confirm the DB `type` column is `image/avif`.
4. `curl -I https://<host>/<sha256>.avif` and confirm
`Content-Type: image/avif` and no `Content-Disposition: attachment`.
5. Load the URL in a browser and confirm it renders inline.
## Important Caveat: Existing AVIF Blobs
Config and code changes only affect **newly uploaded** AVIF blobs. Blobs already
stored as `<sha256>.bin` will not be found by the new `.avif` `try_files` entry.
Options for existing data:
- **Re-upload** the affected AVIF blobs (simplest, content-addressed so the
hash/URL is unchanged).
- **One-off migration**: rename `<sha>.bin` to `<sha>.avif` for rows where
`blobs.type = 'image/avif'`. Requires care because the DB is authoritative
for blob existence.
## Optional Enhancement (out of scope unless requested)
- Add AVIF dimension parsing to [`nip94_get_dimensions()`](src/bud08.c:284) so
NIP-94 `dim` tags are emitted for AVIF (currently only PNG/JPEG/WebP).
- Add `avif` to the URL-extension detection in
[`determine_blob_content_type()`](src/bud04.c:143) for BUD-04 mirroring.
## Why This Is Not a `mime.types` Problem
[`mime.types`](mime.types:19) and [`config/mime.types`](config/mime.types:19)
both already map `avif` to `image/avif`. The failure is upstream: the file is
stored with the wrong extension and/or nginx is not told to look for `.avif`.
+1 -1
View File
@@ -184,7 +184,7 @@ server {
return 405;
}
try_files /$1.html /$1.js /$1.mjs /$1.css /$1.txt /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md =404;
try_files /$1.html /$1.js /$1.mjs /$1.css /$1.txt /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.avif /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md /$1.bin =404;
# Declare UTF-8 on text responses. nginx derives Content-Type from the
# file extension here (e.g. .txt -> text/plain) with no charset, so
+1
View File
@@ -98,6 +98,7 @@ int nip94_get_origin(char* out, size_t out_size) {
#define GINXSOM_MIME_EXTENSION_ENTRIES(X) \
X("image/jpeg", ".jpg") \
X("image/webp", ".webp") \
X("image/avif", ".avif") \
X("image/png", ".png") \
X("image/gif", ".gif") \
X("video/mp4", ".mp4") \
+2 -2
View File
@@ -10,8 +10,8 @@
// Version information (auto-updated by build system)
#define VERSION_MAJOR 0
#define VERSION_MINOR 2
#define VERSION_PATCH 4
#define VERSION "v0.2.4"
#define VERSION_PATCH 5
#define VERSION "v0.2.5"
#include <stddef.h>
#include <stdint.h>