Skip to content

Commit 7d7806d

Browse files
authored
Passthrough SVG images in image resizing (#3224)
1 parent cb5598d commit 7d7806d

File tree

5 files changed

+40
-23
lines changed

5 files changed

+40
-23
lines changed

.changeset/two-lions-tickle.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"gitbook-v2": minor
3+
"gitbook": minor
4+
---
5+
6+
Pass SVG images through image resizing without resizing them to serve them from optimal host.
Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
1-
import { checkIsHttpURL } from '@/lib/urls';
1+
export enum SizableImageAction {
2+
Resize = 'resize',
3+
Skip = 'skip',
4+
Passthrough = 'passthrough',
5+
}
26

37
/**
48
* Check if an image URL is resizable.
59
* Skip it for non-http(s) URLs (data, etc).
610
* Skip it for SVGs.
711
* Skip it for GitBook images (to avoid recursion).
812
*/
9-
export function checkIsSizableImageURL(input: string): boolean {
13+
export function checkIsSizableImageURL(input: string): SizableImageAction {
1014
if (!URL.canParse(input)) {
11-
return false;
12-
}
13-
14-
if (input.includes('/~gitbook/image')) {
15-
return false;
15+
return SizableImageAction.Skip;
1616
}
1717

1818
const parsed = new URL(input);
19-
if (parsed.pathname.endsWith('.svg') || parsed.pathname.endsWith('.avif')) {
20-
return false;
19+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
20+
return SizableImageAction.Skip;
2121
}
22-
if (!checkIsHttpURL(parsed)) {
23-
return false;
22+
if (parsed.hostname === 'localhost') {
23+
return SizableImageAction.Skip;
24+
}
25+
if (parsed.pathname.includes('/~gitbook/image')) {
26+
return SizableImageAction.Skip;
27+
}
28+
if (parsed.pathname.endsWith('.svg') || parsed.pathname.endsWith('.avif')) {
29+
return SizableImageAction.Passthrough;
2430
}
2531

26-
return true;
32+
return SizableImageAction.Resize;
2733
}

packages/gitbook-v2/src/lib/images/createImageResizer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'server-only';
22
import { GITBOOK_IMAGE_RESIZE_SIGNING_KEY, GITBOOK_IMAGE_RESIZE_URL } from '../env';
33
import type { GitBookLinker } from '../links';
4-
import { checkIsSizableImageURL } from './checkIsSizableImageURL';
4+
import { SizableImageAction, checkIsSizableImageURL } from './checkIsSizableImageURL';
55
import { getImageSize } from './resizer';
66
import { type SignatureVersion, generateImageSignature } from './signatures';
77
import type { ImageResizer } from './types';
@@ -24,7 +24,7 @@ export function createImageResizer({
2424

2525
return {
2626
getResizedImageURL: (urlInput) => {
27-
if (!checkIsSizableImageURL(urlInput)) {
27+
if (checkIsSizableImageURL(urlInput) === SizableImageAction.Skip) {
2828
return null;
2929
}
3030

@@ -64,7 +64,7 @@ export function createImageResizer({
6464
},
6565

6666
getImageSize: async (input, options) => {
67-
if (!checkIsSizableImageURL(input)) {
67+
if (checkIsSizableImageURL(input) !== SizableImageAction.Resize) {
6868
return null;
6969
}
7070

packages/gitbook-v2/src/lib/images/resizer/resizeImage.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'server-only';
22
import assertNever from 'assert-never';
33
import { GITBOOK_IMAGE_RESIZE_MODE } from '../../env';
4-
import { checkIsSizableImageURL } from '../checkIsSizableImageURL';
4+
import { SizableImageAction, checkIsSizableImageURL } from '../checkIsSizableImageURL';
55
import { resizeImageWithCDNCgi } from './cdn-cgi';
66
import { resizeImageWithCFFetch } from './cf-fetch';
77
import type { CloudflareImageJsonFormat, CloudflareImageOptions } from './types';
@@ -13,7 +13,7 @@ export async function getImageSize(
1313
input: string,
1414
defaultSize: Partial<CloudflareImageOptions> = {}
1515
): Promise<{ width: number; height: number } | null> {
16-
if (!checkIsSizableImageURL(input)) {
16+
if (checkIsSizableImageURL(input) !== SizableImageAction.Resize) {
1717
return null;
1818
}
1919

@@ -48,13 +48,17 @@ export async function resizeImage(
4848
signal?: AbortSignal;
4949
}
5050
): Promise<Response> {
51-
const parsed = new URL(input);
52-
if (parsed.protocol === 'data:') {
53-
throw new Error('Cannot resize data: URLs');
51+
const action = checkIsSizableImageURL(input);
52+
if (action === SizableImageAction.Skip) {
53+
throw new Error(
54+
'Cannot resize this image, this function should have never been called on this url'
55+
);
5456
}
5557

56-
if (parsed.hostname === 'localhost') {
57-
throw new Error('Cannot resize localhost URLs');
58+
if (action === SizableImageAction.Passthrough) {
59+
return fetch(input, {
60+
signal: options.signal,
61+
});
5862
}
5963

6064
switch (GITBOOK_IMAGE_RESIZE_MODE) {

packages/gitbook/src/routes/image.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
CURRENT_SIGNATURE_VERSION,
33
type CloudflareImageOptions,
44
type SignatureVersion,
5+
SizableImageAction,
56
checkIsSizableImageURL,
67
isSignatureVersion,
78
parseImageAPIURL,
@@ -40,7 +41,7 @@ export async function serveResizedImage(
4041
// Check again if the image can be sized, even though we checked when rendering the Image component
4142
// Otherwise, it's possible to pass just any link to this endpoint and trigger HTML injection on the domain
4243
// Also prevent infinite redirects.
43-
if (!checkIsSizableImageURL(url)) {
44+
if (checkIsSizableImageURL(url) === SizableImageAction.Skip) {
4445
return new Response('Invalid url parameter', { status: 400 });
4546
}
4647

0 commit comments

Comments
 (0)