fix(imaging): support .hab assets, cache avatars/badges, harden routes
The hotel moved from .nitro to .hab bundles, so the imager must treat .hab like .nitro and the public avatar endpoints must accept the figure strings the client sends (including extra trailing segments such as `.0`, which the old FIGURE_RE rejected with a 400). - imager-upstream: relax FIGURE_RE, extend parseAvatarQuery test coverage - api/imaging/avatar + /imaging: shared validation, placeholder and ETag/304 - api/imaging/badge: single-flight upstream fetch and conditional responses - imager-http/imager-png: strong ETag helpers and APNG passthrough guard - csp + next/image: unoptimized external images, tightened CSP - deps: override postcss-selector-parser to ^7.1.6 - also fold in the pending drizzle draft snapshot and stray test cleanup
This commit is contained in:
1 parent
56676effc3
commit
f3407ebfdf
29 files changed
+15134
-93
No files matched your search
@@ -0,0 +1,43 @@
|
||||
import "server-only";
|
||||
|
||||
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
||||
|
||||
/**
|
||||
* Whether a buffer is an animated PNG (APNG).
|
||||
*
|
||||
* APNG is a PNG carrying an `acTL` control chunk before `IDAT`; a plain PNG has
|
||||
* none. `sharp`/libvips in this build cannot decode APNG *animation* — it reads
|
||||
* only the first frame and re-encodes that — so a WebP/AVIF transcode of an
|
||||
* APNG silently turns a moving avatar into a still one. Callers that care about
|
||||
* quality use this to leave animated renders untouched and only re-encode the
|
||||
* static ones. Chunk walking stops at `IDAT`/`IEND` because the spec requires
|
||||
* `acTL` to precede them.
|
||||
*/
|
||||
export function isAnimatedPng(bytes: Uint8Array): boolean {
|
||||
if (bytes.length < 8) return false;
|
||||
for (let i = 0; i < 8; i++) {
|
||||
if (bytes[i] !== PNG_SIGNATURE[i]) return false;
|
||||
}
|
||||
|
||||
let offset = 8;
|
||||
while (offset + 8 <= bytes.length) {
|
||||
const length =
|
||||
((bytes[offset] << 24) |
|
||||
(bytes[offset + 1] << 16) |
|
||||
(bytes[offset + 2] << 8) |
|
||||
bytes[offset + 3]) >>>
|
||||
0;
|
||||
const type = String.fromCharCode(
|
||||
bytes[offset + 4],
|
||||
bytes[offset + 5],
|
||||
bytes[offset + 6],
|
||||
bytes[offset + 7],
|
||||
);
|
||||
if (type === "acTL") return true;
|
||||
if (type === "IDAT" || type === "IEND") return false;
|
||||
// A corrupt length would loop forever; bail instead.
|
||||
if (length > bytes.length) return false;
|
||||
offset += 12 + length;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in new issue
Block a user