Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/imagekit-ar-encoding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"unpic": patch
---

fix(imagekit): stop double-encoding hyphens in aspect ratio (`ar`) values
57 changes: 57 additions & 0 deletions src/providers/imagekit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,33 @@ Deno.test("imagekit extract", async (t) => {
height: 300,
});
});

await t.step("should extract a dashed aspect ratio from a query URL", () => {
const url = `${img}?tr=ar-9-16,w-640`;
const result = extract(url);
assertEquals(result?.src, img);
assertEquals(result?.operations, {
ar: "9-16",
width: 640,
});
});

await t.step(
"should extract a dashed aspect ratio from a path-based URL",
() => {
const url =
`https://ik.imagekit.io/ikmedia/tr:ar-4-3,w-400/docs_images/examples/example_food_3.jpg`;
const result = extract(url);
assertEquals(
result?.src,
"https://ik.imagekit.io/ikmedia/docs_images/examples/example_food_3.jpg",
);
assertEquals(result?.operations, {
ar: "4-3",
width: 400,
});
},
);
});

Deno.test("imagekit transform", async (t) => {
Expand Down Expand Up @@ -93,6 +120,19 @@ Deno.test("imagekit transform", async (t) => {
);
},
);

await t.step(
"should preserve a dashed aspect ratio when transforming an existing URL",
() => {
const result = transform(`${img}?tr=ar-9-16,w-640`, {
quality: 80,
});
assertEquals(
new URL(result).searchParams.get("tr"),
"ar-9-16,w-640,q-80,c-maintain_ratio,fo-auto",
);
},
);
});

Deno.test("imagekit generate", async (t) => {
Expand Down Expand Up @@ -159,4 +199,21 @@ Deno.test("imagekit generate", async (t) => {
);
},
);

await t.step(
"should not double-encode hyphens in aspect ratio values",
() => {
const result = generate(`${img}?updatedAt=1778956861259`, {
ar: "9-16",
width: 640,
});
const url = new URL(result);
assertEquals(
url.searchParams.get("tr"),
"ar-9-16,w-640,c-maintain_ratio,fo-auto",
);
assertEquals(url.searchParams.get("updatedAt"), "1778956861259");
assertEquals(result.includes("%252D"), false);
},
);
});
25 changes: 22 additions & 3 deletions src/providers/imagekit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface ImageKitOperations extends Operations {

/**
* Aspect ratio of the output image.
* Example: `ar=16:9`
* Example: `ar-4-3` or `ar-9-16`
*/
ar?: string;

Expand Down Expand Up @@ -152,13 +152,32 @@ const { operationsGenerator, operationsParser } = createOperationsHandlers<
paramSeparator: ",",
});

/**
* ImageKit values such as aspect ratio `9-16` contain the same `-` used as
* the key/value separator. Encode extra hyphens so the shared parser keeps
* the full value instead of truncating at the second dash.
*/
function encodeHyphensInTrValues(trPart: string): string {
return trPart.split(",").map((pair) => {
const separatorIndex = pair.indexOf("-");
if (separatorIndex === -1) {
return pair;
}
const key = pair.slice(0, separatorIndex);
const value = pair.slice(separatorIndex + 1).replaceAll("-", "%2D");
return `${key}-${value}`;
}).join(",");
}

export const generate: URLGenerator<"imagekit"> = (
src,
operations,
) => {
const modifiers = operationsGenerator(operations);
const url = toUrl(src);
url.searchParams.set("tr", modifiers);
// Decode formatter output so URLSearchParams does not double-encode
// hyphens in values such as aspect ratio `ar-9-16` (#195).
url.searchParams.set("tr", decodeURIComponent(modifiers));
return toCanonicalUrlString(url);
};

Expand Down Expand Up @@ -190,7 +209,7 @@ export const extract: URLExtractor<"imagekit"> = (url) => {

parsedUrl.pathname = path;

const operations = operationsParser(trPart);
const operations = operationsParser(encodeHyphensInTrValues(trPart));

return {
src: toCanonicalUrlString(parsedUrl),
Expand Down
Loading