@socialtip/asset-proxy-url-parser 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ ## 0.5.0
2
+
3
+ - fix: use scale_cuda for GPU video resize instead of cuvid -resize
4
+ - docs: documented videoThumbnailAnimation options
5
+ - feat: support fill, extendFrame, trim, and focus point in VTA
6
+ - feat(proxy): add page info option, inline snapshots for validation tests
7
+ - refactor: add zod schema for info control options, add max_src_resolution
8
+ - feat(proxy): add hashsum verification and source limits to info endpoint
9
+ - feat(proxy): add calc_hashsums info option
10
+ - feat(proxy): add blurhash info option
11
+ - feat(proxy): add dominant colours info option
12
+ - feat(proxy): add average colour info option
13
+ - feat(proxy): add palette info option
14
+ - refactor: move info URL option parsing to url-parser package
15
+
16
+ ## 0.4.1
17
+
18
+ - fix: preserve JSDoc on ParsedUrlInput in declaration output
19
+ - feat: add resizingAlgorithm to parsedUrlSchema and URL generator
20
+
21
+ ## 0.4.0
22
+
23
+ - feat: add mute option to strip audio from video (ST-2456)
24
+ - fix: allow image-only options with video thumbnail extraction (ST-2541)
25
+ - feat: add security limit options (ST-2501)
26
+ - feat: add miscellaneous imgproxy options (ST-2500)
27
+
28
+ ## 0.3.0
29
+
30
+ - feat: ensure URL generator produces identical URLs to @imgproxy/imgproxy-node
31
+ - feat: add best format selection for images
32
+
33
+ ## 0.2.0
34
+
35
+ - refactor: standardise tsconfig pattern and add path aliases across all packages
36
+
37
+ ## 0.1.0
38
+
39
+ Initial release with `@socialtip`-scoped packages.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @socialtip/asset-proxy-url-parser
2
+
3
+ Shared URL schema, parsing, signature verification/generation, and source URL encryption for the [asset-proxy](../../README.md) service.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @socialtip/asset-proxy-url-parser
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Parsing a URL
14
+
15
+ ```ts
16
+ import { parseProcessingUrl } from "@socialtip/asset-proxy-url-parser";
17
+
18
+ const parsed = parseProcessingUrl(
19
+ "/resize:fill:480:360/q:80/plain/https://example.com/photo.jpg@webp",
20
+ );
21
+
22
+ parsed.resize; // { type: "fill", width: 480, height: 360 }
23
+ parsed.quality; // 80
24
+ parsed.sourceUrl; // "https://example.com/photo.jpg"
25
+ parsed.outputFormat; // "webp"
26
+ ```
27
+
28
+ With encrypted source URLs:
29
+
30
+ ```ts
31
+ const parsed = parseProcessingUrl("/resize:fill:480:360/enc/dGhpcyBpcy...", {
32
+ encryptionKey: Buffer.from("0123456789abcdef...", "hex"),
33
+ });
34
+ ```
35
+
36
+ ### Signature verification and generation
37
+
38
+ ```ts
39
+ import { verifySignature, sign } from "@socialtip/asset-proxy-url-parser";
40
+
41
+ // Generate a signature
42
+ const key = Buffer.from("...", "hex");
43
+ const salt = Buffer.from("...", "hex");
44
+ const signature = sign(
45
+ "/resize:fill:480:360/plain/https://example.com/photo.jpg",
46
+ key,
47
+ salt,
48
+ );
49
+
50
+ // Verify a signed URL
51
+ const pathAfterSignature = verifySignature(
52
+ `/${signature}/resize:fill:480:360/plain/https://example.com/photo.jpg`,
53
+ { signingKey: key, signingSalt: salt },
54
+ );
55
+ ```
56
+
57
+ ### Source URL encryption
58
+
59
+ ```ts
60
+ import {
61
+ encryptSourceUrl,
62
+ decryptSourceUrl,
63
+ } from "@socialtip/asset-proxy-url-parser";
64
+
65
+ const key = Buffer.from("0123456789abcdef...", "hex"); // 32-byte key
66
+ const encrypted = encryptSourceUrl("https://example.com/photo.jpg", key);
67
+ const decrypted = decryptSourceUrl(encrypted, key);
68
+ ```
69
+
70
+ ### Schema and types
71
+
72
+ ```ts
73
+ import {
74
+ parsedUrlSchema, // Zod schema for validated processing options
75
+ type ParsedUrlInput, // Input type (z.input<typeof parsedUrlSchema>)
76
+ type ParsedUrl, // Output type with resizingAlgorithm
77
+ } from "@socialtip/asset-proxy-url-parser";
78
+ ```
@@ -0,0 +1,8 @@
1
+ /** Decrypts an encrypted source URL. The encrypted payload is URL-safe Base64 encoding of: IV (16 bytes) + AES-256-CBC ciphertext. The plaintext is PKCS#7 padded before encryption. */
2
+ export declare function decryptSourceUrl(encoded: string, key: Buffer): string;
3
+ export interface EncryptOptions {
4
+ /** When true, derives the IV from the SHA-256 hash of the source URL instead of using random bytes. This makes the encrypted output deterministic for the same input, which is useful when URLs need to be stable for caching purposes. */
5
+ deterministic?: boolean;
6
+ }
7
+ /** Encrypts a source URL using AES-256-CBC, returning URL-safe Base64 of IV + ciphertext. */
8
+ export declare function encryptSourceUrl(sourceUrl: string, key: Buffer, options?: EncryptOptions): string;
package/dist/crypto.js ADDED
@@ -0,0 +1,32 @@
1
+ import { createCipheriv, createDecipheriv, createHash, randomBytes, } from "node:crypto";
2
+ import { HTTPError } from "./error.js";
3
+ /** Decrypts an encrypted source URL. The encrypted payload is URL-safe Base64 encoding of: IV (16 bytes) + AES-256-CBC ciphertext. The plaintext is PKCS#7 padded before encryption. */
4
+ export function decryptSourceUrl(encoded, key) {
5
+ const data = Buffer.from(encoded, "base64url");
6
+ if (data.length < 32) {
7
+ throw new HTTPError("Encrypted payload too short", {
8
+ code: "BAD_REQUEST",
9
+ });
10
+ }
11
+ const iv = data.subarray(0, 16);
12
+ const ciphertext = data.subarray(16);
13
+ const decipher = createDecipheriv("aes-256-cbc", key, iv);
14
+ const decrypted = Buffer.concat([
15
+ decipher.update(ciphertext),
16
+ decipher.final(),
17
+ ]);
18
+ return decrypted.toString("utf-8");
19
+ }
20
+ /** Encrypts a source URL using AES-256-CBC, returning URL-safe Base64 of IV + ciphertext. */
21
+ export function encryptSourceUrl(sourceUrl, key, options) {
22
+ const iv = options?.deterministic
23
+ ? Buffer.from(createHash("sha256").update(sourceUrl).digest("hex").slice(0, 16))
24
+ : randomBytes(16);
25
+ const cipher = createCipheriv("aes-256-cbc", key, iv);
26
+ const encrypted = Buffer.concat([
27
+ cipher.update(sourceUrl, "utf-8"),
28
+ cipher.final(),
29
+ ]);
30
+ return Buffer.concat([iv, encrypted]).toString("base64url");
31
+ }
32
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,wLAAwL;AACxL,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,GAAW;IAC3D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAE/C,IAAI,IAAI,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACrB,MAAM,IAAI,SAAS,CAAC,6BAA6B,EAAE;YACjD,IAAI,EAAE,aAAa;SACpB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAErC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;QAC9B,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC;QAC3B,QAAQ,CAAC,KAAK,EAAE;KACjB,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACrC,CAAC;AAOD,6FAA6F;AAC7F,MAAM,UAAU,gBAAgB,CAC9B,SAAiB,EACjB,GAAW,EACX,OAAwB;IAExB,MAAM,EAAE,GAAG,OAAO,EAAE,aAAa;QAC/B,CAAC,CAAC,MAAM,CAAC,IAAI,CACT,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAClE;QACH,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACpB,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;QAC9B,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC;QACjC,MAAM,CAAC,KAAK,EAAE;KACf,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,17 @@
1
+ export declare const ERROR_CODES: {
2
+ readonly BAD_REQUEST: 400;
3
+ readonly FORBIDDEN: 403;
4
+ readonly NOT_FOUND: 404;
5
+ readonly UNPROCESSABLE_ENTITY: 422;
6
+ readonly INTERNAL_SERVER_ERROR: 500;
7
+ readonly NOT_IMPLEMENTED: 501;
8
+ readonly BAD_GATEWAY: 502;
9
+ };
10
+ type ErrorCode = keyof typeof ERROR_CODES;
11
+ export declare class HTTPError extends Error {
12
+ readonly status: number;
13
+ constructor(message: string, { code }?: {
14
+ code?: ErrorCode;
15
+ });
16
+ }
17
+ export {};
package/dist/error.js ADDED
@@ -0,0 +1,17 @@
1
+ export const ERROR_CODES = {
2
+ BAD_REQUEST: 400,
3
+ FORBIDDEN: 403,
4
+ NOT_FOUND: 404,
5
+ UNPROCESSABLE_ENTITY: 422,
6
+ INTERNAL_SERVER_ERROR: 500,
7
+ NOT_IMPLEMENTED: 501,
8
+ BAD_GATEWAY: 502,
9
+ };
10
+ export class HTTPError extends Error {
11
+ status;
12
+ constructor(message, { code = "INTERNAL_SERVER_ERROR" } = {}) {
13
+ super(message);
14
+ this.status = ERROR_CODES[code];
15
+ }
16
+ }
17
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,WAAW,EAAE,GAAG;IAChB,SAAS,EAAE,GAAG;IACd,SAAS,EAAE,GAAG;IACd,oBAAoB,EAAE,GAAG;IACzB,qBAAqB,EAAE,GAAG;IAC1B,eAAe,EAAE,GAAG;IACpB,WAAW,EAAE,GAAG;CACR,CAAC;AAIX,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,MAAM,CAAS;IAExB,YACE,OAAe,EACf,EAAE,IAAI,GAAG,uBAAuB,KAA2B,EAAE;QAE7D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ export { HTTPError, ERROR_CODES } from "./error.js";
2
+ export { parseInfoUrl, type InfoOptions, type ControlOptions, type ParsedInfoUrl, type InfoParseOptions, } from "./info-parse.js";
3
+ export { decryptSourceUrl, encryptSourceUrl, type EncryptOptions, } from "./crypto.js";
4
+ export { verifySignature, sign, type SignatureOptions } from "./signature.js";
5
+ export { parseProcessingUrl, parsedUrlSchema, isImageUrl, isVideoUrl, type ParsedUrl, type ParsedUrlInput, type ImageUrl, type VideoUrl, type ParseOptions, type ResizingType, type ResizingAlgorithm, type OutputFormat, type ImageFormat, type VideoFormat, type MediaType, type CompassGravity, type FocusPointGravity, type Gravity, type ResizeOptions, SHORTHANDS, } from "./parse.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { HTTPError, ERROR_CODES } from "./error.js";
2
+ export { parseInfoUrl, } from "./info-parse.js";
3
+ export { decryptSourceUrl, encryptSourceUrl, } from "./crypto.js";
4
+ export { verifySignature, sign } from "./signature.js";
5
+ export { parseProcessingUrl, parsedUrlSchema, isImageUrl, isVideoUrl, SHORTHANDS, } from "./parse.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EACL,YAAY,GAKb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,gBAAgB,EAChB,gBAAgB,GAEjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,IAAI,EAAyB,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,UAAU,EACV,UAAU,EAgBV,UAAU,GACX,MAAM,YAAY,CAAC"}
@@ -0,0 +1,98 @@
1
+ import { z } from "zod/v4";
2
+ /** Parsed info endpoint options that control which additional metadata is returned. */
3
+ export interface InfoOptions {
4
+ /** Include EXIF metadata in the response. */
5
+ exif?: boolean;
6
+ /** Include IPTC metadata in the response. */
7
+ iptc?: boolean;
8
+ /** Include XMP metadata organised by namespace in the response. */
9
+ xmp?: boolean;
10
+ /** Include the image colour space (e.g. `gbr`, `bt709`). */
11
+ colorspace?: boolean;
12
+ /** Include the number of image bands/channels. */
13
+ bands?: boolean;
14
+ /** Include the sample format (uchar, ushort, float). */
15
+ sampleFormat?: boolean;
16
+ /** Include the page/frame count. */
17
+ pagesNumber?: boolean;
18
+ /** Include alpha channel information. */
19
+ alpha?: boolean;
20
+ /** Return an RGBA colour palette with this many colours (2-256). 0 or undefined to disable. */
21
+ palette?: number;
22
+ /** Return the average image colour. */
23
+ average?: {
24
+ ignoreTransparent: boolean;
25
+ };
26
+ /** Return six dominant colour categories (vibrant, muted, light/dark variants). */
27
+ dominantColors?: boolean;
28
+ /** Return a BlurHash string with the given x and y components. */
29
+ blurhash?: {
30
+ xComponents: number;
31
+ yComponents: number;
32
+ };
33
+ /** Calculate and return hashsums of the source file. List of types: md5, sha1, sha256, sha512. */
34
+ calcHashsums?: Array<"md5" | "sha1" | "sha256" | "sha512">;
35
+ /** Which page to analyse for multi-page images (0-indexed). */
36
+ page?: number;
37
+ }
38
+ /** Zod schema for runtime validation of parsed info options. The `InfoOptions` interface is the authoritative type definition; this schema validates against it at compile time via `satisfies`. */
39
+ export declare const parsedInfoOptionsSchema: z.ZodObject<{
40
+ exif: z.ZodOptional<z.ZodBoolean>;
41
+ iptc: z.ZodOptional<z.ZodBoolean>;
42
+ xmp: z.ZodOptional<z.ZodBoolean>;
43
+ colorspace: z.ZodOptional<z.ZodBoolean>;
44
+ bands: z.ZodOptional<z.ZodBoolean>;
45
+ sampleFormat: z.ZodOptional<z.ZodBoolean>;
46
+ pagesNumber: z.ZodOptional<z.ZodBoolean>;
47
+ alpha: z.ZodOptional<z.ZodBoolean>;
48
+ palette: z.ZodOptional<z.ZodNumber>;
49
+ average: z.ZodOptional<z.ZodObject<{
50
+ ignoreTransparent: z.ZodBoolean;
51
+ }, z.core.$strip>>;
52
+ dominantColors: z.ZodOptional<z.ZodBoolean>;
53
+ blurhash: z.ZodOptional<z.ZodObject<{
54
+ xComponents: z.ZodNumber;
55
+ yComponents: z.ZodNumber;
56
+ }, z.core.$strip>>;
57
+ calcHashsums: z.ZodOptional<z.ZodArray<z.ZodEnum<{
58
+ sha256: "sha256";
59
+ md5: "md5";
60
+ sha1: "sha1";
61
+ sha512: "sha512";
62
+ }>>>;
63
+ page: z.ZodOptional<z.ZodNumber>;
64
+ }, z.core.$strip>;
65
+ /** Control options parsed from an info URL (security, limits). */
66
+ export interface ControlOptions {
67
+ /** Unix timestamp after which the URL returns 404. */
68
+ expires?: number;
69
+ /** Expected checksum of the source file. */
70
+ hashsum?: {
71
+ type: string;
72
+ hash: string;
73
+ };
74
+ /** Max source file size in bytes. */
75
+ maxSrcFileSize?: number;
76
+ /** Max source resolution in megapixels. */
77
+ maxSrcResolution?: number;
78
+ }
79
+ /** Zod schema for runtime validation of parsed control options. The `ControlOptions` interface is the authoritative type definition; this schema validates against it at compile time via `satisfies`. */
80
+ export declare const parsedControlOptionsSchema: z.ZodObject<{
81
+ expires: z.ZodOptional<z.ZodNumber>;
82
+ hashsum: z.ZodOptional<z.ZodObject<{
83
+ type: z.ZodString;
84
+ hash: z.ZodString;
85
+ }, z.core.$strip>>;
86
+ maxSrcFileSize: z.ZodOptional<z.ZodNumber>;
87
+ maxSrcResolution: z.ZodOptional<z.ZodNumber>;
88
+ }, z.core.$strip>;
89
+ /** Parsed result from an info URL. */
90
+ export interface ParsedInfoUrl extends ControlOptions {
91
+ sourceUrl: string;
92
+ infoOptions: InfoOptions;
93
+ }
94
+ export interface InfoParseOptions {
95
+ encryptionKey?: Buffer;
96
+ }
97
+ /** Parses an info URL path (after signature has been stripped). Extracts the source URL, info options, and control options (expires, hashsum, source limits). */
98
+ export declare function parseInfoUrl(path: string, options?: InfoParseOptions): ParsedInfoUrl;
@@ -0,0 +1,187 @@
1
+ import { z } from "zod/v4";
2
+ import { decryptSourceUrl } from "./crypto.js";
3
+ import { HTTPError } from "./error.js";
4
+ const zBool = z
5
+ .string()
6
+ .transform((v) => v === "1" || v === "t" || v === "true");
7
+ const INFO_SHORTHANDS = {
8
+ cs: "colorspace",
9
+ b: "bands",
10
+ sf: "sample_format",
11
+ pn: "pages_number",
12
+ a: "alpha",
13
+ p: "palette",
14
+ avg: "average",
15
+ dc: "dominant_colors",
16
+ bh: "blurhash",
17
+ chs: "calc_hashsums",
18
+ pg: "page",
19
+ };
20
+ const hashsumType = z.enum(["md5", "sha1", "sha256", "sha512"]);
21
+ const rawInfoOptionsSchema = z.object({
22
+ exif: zBool.optional(),
23
+ iptc: zBool.optional(),
24
+ xmp: zBool.optional(),
25
+ colorspace: zBool.optional(),
26
+ bands: zBool.optional(),
27
+ sample_format: zBool.optional(),
28
+ pages_number: zBool.optional(),
29
+ alpha: zBool.optional(),
30
+ palette: z.coerce.number().int().min(0).max(256).optional(),
31
+ average: z
32
+ .string()
33
+ .transform((v) => {
34
+ const parts = v.split(":");
35
+ const enabled = parts[0] === "1" || parts[0] === "t" || parts[0] === "true";
36
+ const ignoreTransparent = parts[1] === "1" || parts[1] === "t" || parts[1] === "true";
37
+ return enabled ? { ignoreTransparent } : undefined;
38
+ })
39
+ .optional(),
40
+ dominant_colors: zBool.optional(),
41
+ blurhash: z
42
+ .string()
43
+ .transform((v) => {
44
+ const [x, y] = v.split(":").map(Number);
45
+ if (!x || !y)
46
+ return undefined;
47
+ return { xComponents: x, yComponents: y };
48
+ })
49
+ .optional(),
50
+ calc_hashsums: z
51
+ .string()
52
+ .transform((v) => v.split(":").filter((t) => hashsumType.safeParse(t).success))
53
+ .optional(),
54
+ page: z.coerce.number().int().min(0).optional(),
55
+ });
56
+ const infoOptionsSchema = rawInfoOptionsSchema.transform((data) => ({
57
+ exif: data.exif,
58
+ iptc: data.iptc,
59
+ xmp: data.xmp,
60
+ colorspace: data.colorspace,
61
+ bands: data.bands,
62
+ sampleFormat: data.sample_format,
63
+ pagesNumber: data.pages_number,
64
+ alpha: data.alpha,
65
+ palette: data.palette,
66
+ average: data.average,
67
+ dominantColors: data.dominant_colors,
68
+ blurhash: data.blurhash,
69
+ calcHashsums: data.calc_hashsums,
70
+ page: data.page,
71
+ }));
72
+ /** Zod schema for runtime validation of parsed info options. The `InfoOptions` interface is the authoritative type definition; this schema validates against it at compile time via `satisfies`. */
73
+ export const parsedInfoOptionsSchema = z.object({
74
+ exif: z.boolean().optional(),
75
+ iptc: z.boolean().optional(),
76
+ xmp: z.boolean().optional(),
77
+ colorspace: z.boolean().optional(),
78
+ bands: z.boolean().optional(),
79
+ sampleFormat: z.boolean().optional(),
80
+ pagesNumber: z.boolean().optional(),
81
+ alpha: z.boolean().optional(),
82
+ palette: z.number().optional(),
83
+ average: z.object({ ignoreTransparent: z.boolean() }).optional(),
84
+ dominantColors: z.boolean().optional(),
85
+ blurhash: z
86
+ .object({ xComponents: z.number(), yComponents: z.number() })
87
+ .optional(),
88
+ calcHashsums: z.array(hashsumType).optional(),
89
+ page: z.number().optional(),
90
+ });
91
+ const CONTROL_SHORTHANDS = {
92
+ exp: "expires",
93
+ hs: "hashsum",
94
+ msfs: "max_src_file_size",
95
+ msr: "max_src_resolution",
96
+ cb: "cache_buster",
97
+ };
98
+ const ALL_SHORTHANDS = {
99
+ ...INFO_SHORTHANDS,
100
+ ...CONTROL_SHORTHANDS,
101
+ };
102
+ const ALL_OPTION_NAMES = new Set([
103
+ ...Object.keys(ALL_SHORTHANDS),
104
+ ...Object.values(ALL_SHORTHANDS),
105
+ "exif",
106
+ "iptc",
107
+ "xmp",
108
+ ]);
109
+ const rawControlOptionsSchema = z.object({
110
+ expires: z.coerce.number().int().optional(),
111
+ hashsum: z
112
+ .string()
113
+ .transform((v) => {
114
+ const idx = v.indexOf(":");
115
+ if (idx === -1)
116
+ return undefined;
117
+ return { type: v.slice(0, idx), hash: v.slice(idx + 1) };
118
+ })
119
+ .optional(),
120
+ max_src_file_size: z.coerce.number().int().positive().optional(),
121
+ max_src_resolution: z.coerce.number().positive().optional(),
122
+ cache_buster: z.string().optional(),
123
+ });
124
+ const controlOptionsSchema = rawControlOptionsSchema.transform((data) => ({
125
+ expires: data.expires,
126
+ hashsum: data.hashsum,
127
+ maxSrcFileSize: data.max_src_file_size,
128
+ maxSrcResolution: data.max_src_resolution,
129
+ }));
130
+ /** Zod schema for runtime validation of parsed control options. The `ControlOptions` interface is the authoritative type definition; this schema validates against it at compile time via `satisfies`. */
131
+ export const parsedControlOptionsSchema = z.object({
132
+ expires: z.number().optional(),
133
+ hashsum: z.object({ type: z.string(), hash: z.string() }).optional(),
134
+ maxSrcFileSize: z.number().optional(),
135
+ maxSrcResolution: z.number().optional(),
136
+ });
137
+ /** Parses an info URL path (after signature has been stripped). Extracts the source URL, info options, and control options (expires, hashsum, source limits). */
138
+ export function parseInfoUrl(path, options) {
139
+ const withoutPrefix = path.replace(/^\//, "");
140
+ let optionsPart;
141
+ let sourceUrl;
142
+ let encrypted = false;
143
+ const plainIdx = withoutPrefix.indexOf("plain/");
144
+ const encIdx = withoutPrefix.indexOf("enc/");
145
+ if (plainIdx !== -1 && (encIdx === -1 || plainIdx <= encIdx)) {
146
+ optionsPart = withoutPrefix.slice(0, plainIdx).replace(/\/$/, "");
147
+ sourceUrl = withoutPrefix.slice(plainIdx + "plain/".length);
148
+ }
149
+ else if (encIdx !== -1) {
150
+ optionsPart = withoutPrefix.slice(0, encIdx).replace(/\/$/, "");
151
+ sourceUrl = withoutPrefix.slice(encIdx + "enc/".length);
152
+ encrypted = true;
153
+ }
154
+ else {
155
+ throw new HTTPError("Unsupported URL format: expected /plain/ or /enc/ source URL", { code: "BAD_REQUEST" });
156
+ }
157
+ if (!sourceUrl) {
158
+ throw new HTTPError("Missing source URL", { code: "BAD_REQUEST" });
159
+ }
160
+ if (encrypted) {
161
+ if (!options?.encryptionKey) {
162
+ throw new HTTPError("Encrypted source URLs are not supported: no encryption key provided", { code: "BAD_REQUEST" });
163
+ }
164
+ sourceUrl = decryptSourceUrl(sourceUrl, options.encryptionKey);
165
+ }
166
+ const controlCanonicals = new Set(Object.values(CONTROL_SHORTHANDS));
167
+ const infoRaw = {};
168
+ const controlRaw = {};
169
+ for (const seg of optionsPart.split("/").filter(Boolean)) {
170
+ const colonIdx = seg.indexOf(":");
171
+ const name = colonIdx === -1 ? seg : seg.slice(0, colonIdx);
172
+ const value = colonIdx === -1 ? "" : seg.slice(colonIdx + 1);
173
+ const canonical = ALL_SHORTHANDS[name] ?? name;
174
+ if (!ALL_OPTION_NAMES.has(name))
175
+ continue;
176
+ if (controlCanonicals.has(canonical)) {
177
+ controlRaw[canonical] = value;
178
+ }
179
+ else {
180
+ infoRaw[canonical] = value;
181
+ }
182
+ }
183
+ const infoOptions = parsedInfoOptionsSchema.parse(infoOptionsSchema.parse(infoRaw));
184
+ const control = parsedControlOptionsSchema.parse(controlOptionsSchema.parse(controlRaw));
185
+ return { sourceUrl, infoOptions, ...control };
186
+ }
187
+ //# sourceMappingURL=info-parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"info-parse.js","sourceRoot":"","sources":["../src/info-parse.ts"],"names":[],"mappings":"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"}