/examples/medialouise-toolkit/media test mode · no data leaves your browserlouise-toolkit/media never trusts a file's claimed MIME type — it reads the real format from the leading magic bytes, pulls intrinsic dimensions out of the header without decoding pixels, and serves resized derivatives from the edge. Drop a file on the left and watch the actual toolkit functions run.
import { cfImage, mediaUrl, putMedia } from "louise-toolkit/media";
interface MediaEnv {
MEDIA: R2Bucket; // the bucket uploads land in
MEDIA_URL: string; // public base URL the bucket is served from
IMAGES?: ImagesBinding; // optional — sizes AVIF/TIFF the header parser can't
}
// POST multipart/form-data with a `file` field — verify, store, return the URLs.
export async function handleUpload(request: Request, env: MediaEnv): Promise<Response> {
const form = await request.formData();
const file = form.get("file");
if (!(file instanceof File)) return Response.json({ error: "No file" }, { status: 400 });
// The whole verification: bytes are sniffed for a real image signature, so a
// file claiming `image/png` in its MIME but carrying JPEG bytes is stored as
// what it actually is — or rejected outright. `putMedia` writes nothing when it
// rejects: oversize is 413, non-image is 415.
const result = await putMedia(env.MEDIA, file, { scope: "web", images: env.IMAGES });
if (!result.ok) return Response.json({ error: result.error }, { status: result.status });
// The stored original, plus an on-the-fly resized derivative. `cfImage` only
// rewrites the path to `/cdn-cgi/image/...` — nothing is re-encoded or stored
// server-side, so a thumbnail costs no extra bytes in the bucket.
const url = mediaUrl(env.MEDIA_URL, result.key);
return Response.json({
url,
thumb: cfImage(url, { width: 480, fit: "cover", gravity: "auto", format: "auto" }),
contentType: result.contentType, // the VERIFIED type, not the client's claim
width: result.width,
height: result.height,
});
}<div class="louise-media-demo grid gap-3">
<label
class="louise-media-drop flex cursor-pointer flex-col items-center gap-2 rounded-xl border-2 border-dashed border-base-300 px-4 py-8 text-center transition hover:border-primary hover:bg-primary/5"
>
<i class="ph ph-image text-3xl text-base-content/40" aria-hidden="true"></i>
<span class="text-sm font-semibold">Drop an image, or click to choose</span>
<span class="text-xs text-base-content/50">JPEG · PNG · GIF · WebP · AVIF · TIFF</span>
<input class="louise-media-input sr-only" type="file" accept="image/*" />
</label>
<output class="louise-media-out hidden grid gap-3" aria-live="polite"></output>
</div>
<script>
// The real thing — the same pure functions the upload route calls. No mirror,
// no reimplementation: these tree-shake out of louise-toolkit/media into the
// client bundle because none of them touch a binding.
import { cfImage, imageDimensions, sniffImageType } from "louise-toolkit/media";
// Mirrors of the two constants `putMedia` applies that aren't exported — the
// size ceiling, and the key-safe filename rule. Display only.
const MAX_BYTES = 10 * 1024 * 1024;
const safeName = (n: string) => n.replace(/[^a-zA-Z0-9._-]/g, "-").toLowerCase();
const esc = (s: string) =>
s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
document.querySelectorAll<HTMLElement>(".louise-media-demo").forEach((root) => {
const input = root.querySelector<HTMLInputElement>(".louise-media-input");
const drop = root.querySelector<HTMLElement>(".louise-media-drop");
const out = root.querySelector<HTMLElement>(".louise-media-out");
if (!input || !drop || !out) return;
input.addEventListener("change", () => {
const file = input.files?.[0];
if (file) void inspect(file);
});
// Drag-and-drop onto the label, with a hover affordance while dragging.
drop.addEventListener("dragover", (e) => {
e.preventDefault();
drop.classList.add("border-primary", "bg-primary/5");
});
drop.addEventListener("dragleave", () =>
drop.classList.remove("border-primary", "bg-primary/5"),
);
drop.addEventListener("drop", (e) => {
e.preventDefault();
drop.classList.remove("border-primary", "bg-primary/5");
const file = e.dataTransfer?.files?.[0];
if (file) void inspect(file);
});
async function inspect(file: File) {
const buffer = await file.arrayBuffer();
// 1. Size cap — the server rejects before buffering, with a 413.
if (buffer.byteLength > MAX_BYTES) {
return reject(413, `File too large (max ${MAX_BYTES / 1024 / 1024} MB)`);
}
// 2. The security-critical step: the real type comes from the leading magic
// bytes, never from `file.type`. A spoofed MIME would otherwise be stored
// AND SERVED as an image from a public media domain.
const head = new Uint8Array(buffer, 0, Math.min(buffer.byteLength, 32));
const verified = sniffImageType(head);
if (!verified) return reject(415, "Unsupported or invalid image file");
// 3. Intrinsic dimensions straight out of the header — no pixel decode, no
// image library. (The server prefers the Images binding when it has one;
// this is the binding-free fallback, which is all a browser needs.)
const dims = imageDimensions(
new Uint8Array(buffer, 0, Math.min(buffer.byteLength, 65536)),
);
// 4. What the server would write, and what it would serve back. `cfImage`
// only rewrites the path — the derivative is generated at the edge on
// request, so it costs nothing in the bucket.
const key = `web/${Date.now()}-${safeName(file.name)}`;
const url = `https://media.example.com/${key}`;
const thumb = cfImage(url, { width: 480, fit: "cover", gravity: "auto" });
const claimed = file.type || "(none)";
const spoofed = claimed !== verified;
out!.className = "louise-media-out grid gap-3";
out!.innerHTML = `
<div class="flex gap-3">
<img src="${URL.createObjectURL(file)}" alt="" class="h-20 w-20 rounded-lg object-cover ring-1 ring-base-300" />
<dl class="grid flex-1 grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-xs">
<dt class="text-base-content/50">Browser claims</dt>
<dd class="font-mono ${spoofed ? "text-error line-through" : ""}">${esc(claimed)}</dd>
<dt class="text-base-content/50">Sniffed from bytes</dt>
<dd class="font-mono font-semibold text-success">${verified}</dd>
<dt class="text-base-content/50">Dimensions</dt>
<dd class="font-mono">${dims ? `${dims.width} × ${dims.height}` : "unknown"}</dd>
<dt class="text-base-content/50">Size</dt>
<dd class="font-mono">${(buffer.byteLength / 1024).toFixed(1)} KB</dd>
</dl>
</div>
${
spoofed
? `<p class="rounded-lg bg-error/10 px-3 py-2 text-xs text-error">
<strong>Caught a mismatch.</strong> The file says <code>${esc(claimed)}</code>;
its bytes say <code>${verified}</code>. The object is stored as what it
<em>is</em> — trusting the claim is how a public media domain ends up
serving something that isn't an image.
</p>`
: ""
}
<div class="grid gap-1 text-[11px]">
<span class="text-base-content/50">R2 key <code class="text-base-content/80">${esc(key)}</code></span>
<span class="text-base-content/50">Derivative <code class="break-all text-base-content/80">${esc(thumb)}</code></span>
</div>
<p class="text-[11px] text-base-content/40">
Verified in your browser by the real toolkit functions. Nothing was uploaded —
the demo stops where <code>bucket.put</code> would run.
</p>`;
}
function reject(status: number, message: string) {
out!.className = "louise-media-out grid gap-3";
out!.innerHTML = `<p class="rounded-lg bg-error/10 px-3 py-2 text-sm text-error">
<strong>${status}</strong> — ${esc(message)}. Rejected before anything is written.
</p>`;
}
});
</script>.png and drop it — the sniffer reads the bytes and catches the lie, exactly as the upload route would. Deep-dive doc View sourceThis page runs the real sniffImageType, imageDimensions andcfImage — they take no bindings, so they behave the same in your browser as on a Worker. The one thing it doesn't do is write to R2. For an upload that actually stores a row and appears in the media library, use the live sandbox.