150 lines
5.9 KiB
Markdown
150 lines
5.9 KiB
Markdown
# 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`.
|