Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
import {
CADDY_IMGPROXY_CODES,
CaddyImgproxyError,
} from "../error-codes/error-codes.js";
import { recordParseOutcome } from "../telemetry/telemetry.js";
import type {
GravityCode,
HintType,
ImgproxyOperation,
ParsedImgproxyUrl,
} from "../types/types.js";
/**
* Matches the compact imgproxy URL grammar. Capture groups:
* 1: signed/unsigned (`s`|`u`)
* 2: operation (`v`|`c`|`f`|`s`|`r`) — optional
* 3: gravity (`tl`|`tr`|`bl`|`br`|`t`|`b`|`l`|`r`|`c`) — optional
* 4: aspect W (>=1)
* 5: aspect H (>=1)
* 6: pixel W (>=1, when followed by xH)
* 7: pixel H (>=0)
* 8: pixel W (=0)
* 9: pixel H (>=1, when W=0)
* 10: square dim
* 11: modifiers
* 12: path
* 13: format
*/
const URL_PATTERN =
/^\/([su])([vcfsr])?(tl|tr|bl|br|[tblrc])?(?:([1-9]\d*):([1-9]\d*)|([1-9]\d*)x(\d+)|(0)x([1-9]\d*)|([1-9]\d*))?((?:[wh][1-9]\d*|q(?:100|[1-9]\d?))*)\/(.+)\.([a-z]+)$/;
/** Extracts individual `w<int>`, `h<int>`, `q<int>` modifiers. */
const MODIFIER_PATTERN = /([whq])(\d+)/g;
const OP_MAP: Record<string, ImgproxyOperation> = {
v: "cover",
c: "crop",
f: "fit",
s: "stretch",
r: "resize",
};
/**
* Parses an imgproxy URL into its typed components.
*
* Verbatim port of the upstream Go parseURL. Returns a frozen
* ParsedImgproxyUrl on success and throws CaddyImgproxyError on
* every failure. Increments caddy_imgproxy_parse_total once per call.
*/
export function parseImgproxyUrl(path: string): ParsedImgproxyUrl {
const matches = URL_PATTERN.exec(path);
if (matches === null) {
recordParseOutcome("invalid");
throw new CaddyImgproxyError(
CADDY_IMGPROXY_CODES.INVALID_URL,
"imgproxy URL does not match the expected grammar",
{ context: { path } }
);
}
const opToken = matches[2] ?? "";
const operation =
opToken === "" ? "" : (OP_MAP[opToken] as ImgproxyOperation);
const gravity = resolveGravity(matches[3]);
const { aspectW, aspectH, pixelW, pixelH, isAspect, hasDims } =
resolveDimensions(matches);
const { hintType, hintValue, quality } = parseModifiers(
matches[11] as string
);
if (operation !== "" && !hasDims) {
recordParseOutcome("missing_dims");
throw new CaddyImgproxyError(
CADDY_IMGPROXY_CODES.MISSING_DIMENSIONS,
`dimensions required for operation: ${operation}`,
{ context: { path, operation } }
);
}
recordParseOutcome("ok");
return Object.freeze({
signed: matches[1] === "s",
operation,
gravity,
aspectW,
aspectH,
pixelW,
pixelH,
hintType,
hintValue,
quality,
path: matches[12] as string,
format: matches[13] as string,
isAspect,
hasDims,
});
}
function resolveGravity(token: string | undefined): GravityCode {
if (token === undefined || token === "") return "c";
return token as GravityCode;
}
interface DimensionParts {
readonly aspectW: number;
readonly aspectH: number;
readonly pixelW: number;
readonly pixelH: number;
readonly isAspect: boolean;
readonly hasDims: boolean;
}
function resolveDimensions(matches: RegExpExecArray): DimensionParts {
if (matches[4] !== undefined && matches[5] !== undefined) {
return {
aspectW: Number.parseInt(matches[4], 10),
aspectH: Number.parseInt(matches[5], 10),
pixelW: 0,
pixelH: 0,
isAspect: true,
hasDims: true,
};
}
if (matches[6] !== undefined) {
return {
aspectW: 0,
aspectH: 0,
pixelW: Number.parseInt(matches[6], 10),
pixelH: Number.parseInt(matches[7] as string, 10),
isAspect: false,
hasDims: true,
};
}
if (matches[8] !== undefined) {
return {
aspectW: 0,
aspectH: 0,
pixelW: 0,
pixelH: Number.parseInt(matches[9] as string, 10),
isAspect: false,
hasDims: true,
};
}
if (matches[10] !== undefined) {
const dim = Number.parseInt(matches[10], 10);
return {
aspectW: 0,
aspectH: 0,
pixelW: dim,
pixelH: dim,
isAspect: false,
hasDims: true,
};
}
return {
aspectW: 0,
aspectH: 0,
pixelW: 0,
pixelH: 0,
isAspect: false,
hasDims: false,
};
}
interface ModifierParts {
readonly hintType: HintType;
readonly hintValue: number;
readonly quality: number;
}
function parseModifiers(token: string): ModifierParts {
let hintType: HintType = "";
let hintValue = 0;
let quality = 0;
if (token === "") {
return { hintType, hintValue, quality };
}
MODIFIER_PATTERN.lastIndex = 0;
let match: RegExpExecArray | null = MODIFIER_PATTERN.exec(token);
while (match !== null) {
const kind = match[1];
const value = Number.parseInt(match[2] as string, 10);
if (kind === "w") {
hintType = "w";
hintValue = value;
} else if (kind === "h") {
hintType = "h";
hintValue = value;
} else {
quality = value;
}
match = MODIFIER_PATTERN.exec(token);
}
return { hintType, hintValue, quality };
}