Files
ginxsom/plans/fix-avif-serving.md
T

5.9 KiB

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 drives every place a blob is written to disk:

#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() 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:

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 — 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) forces the browser to download rather than render. This is the observed symptom.

Note: mime.types 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 Add X("image/avif", ".avif") to the map
config/ginxsom-local.conf Add .avif to try_files
config/local-nginx.conf Add .avif to both try_files blocks (HTTP + HTTPS)
remote.nginx.config Add .avif to try_files
README.md Update documented try_files examples
docs/NGINX_CONFIG_UPDATES.md Update documented try_files example

Fix Plan

Step 1 - Backend: map image/avif to .avif

In src/bud08.c, add an entry to GINXSOM_MIME_EXTENSION_ENTRIES:

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.

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 and docs/NGINX_CONFIG_UPDATES.md so future deployments include .avif.

Step 5 - Rebuild static binaries

Rebuild via Dockerfile.alpine-musl / 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)

Why This Is Not a mime.types Problem

mime.types and config/mime.types 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.