mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-21 01:53:26 +00:00
Only resize images with supported extensions (#3229)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook-v2": patch
|
||||
---
|
||||
|
||||
Only resize images with supported extensions.
|
||||
@@ -0,0 +1,81 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { SizableImageAction, checkIsSizableImageURL } from './checkIsSizableImageURL';
|
||||
|
||||
describe('checkIsSizableImageURL', () => {
|
||||
it('should return Skip for non-parsable URLs', () => {
|
||||
expect(checkIsSizableImageURL('not a url')).toBe(SizableImageAction.Skip);
|
||||
});
|
||||
|
||||
it('should return Skip for non-http(s) URLs', () => {
|
||||
expect(checkIsSizableImageURL('data:image/png;base64,abc')).toBe(SizableImageAction.Skip);
|
||||
expect(checkIsSizableImageURL('file:///path/to/image.jpg')).toBe(SizableImageAction.Skip);
|
||||
});
|
||||
|
||||
it('should return Skip for localhost URLs', () => {
|
||||
expect(checkIsSizableImageURL('http://localhost:3000/image.jpg')).toBe(
|
||||
SizableImageAction.Skip
|
||||
);
|
||||
expect(checkIsSizableImageURL('https://localhost/image.png')).toBe(SizableImageAction.Skip);
|
||||
});
|
||||
|
||||
it('should return Skip for GitBook image URLs', () => {
|
||||
expect(checkIsSizableImageURL('https://example.com/~gitbook/image/test.jpg')).toBe(
|
||||
SizableImageAction.Skip
|
||||
);
|
||||
});
|
||||
|
||||
it('should return Resize for supported image extensions', () => {
|
||||
expect(checkIsSizableImageURL('https://example.com/image.jpg')).toBe(
|
||||
SizableImageAction.Resize
|
||||
);
|
||||
expect(checkIsSizableImageURL('https://example.com/image.jpeg')).toBe(
|
||||
SizableImageAction.Resize
|
||||
);
|
||||
expect(checkIsSizableImageURL('https://example.com/image.png')).toBe(
|
||||
SizableImageAction.Resize
|
||||
);
|
||||
expect(checkIsSizableImageURL('https://example.com/image.gif')).toBe(
|
||||
SizableImageAction.Resize
|
||||
);
|
||||
expect(checkIsSizableImageURL('https://example.com/image.webp')).toBe(
|
||||
SizableImageAction.Resize
|
||||
);
|
||||
});
|
||||
|
||||
it('should return Resize for URLs without extensions', () => {
|
||||
expect(checkIsSizableImageURL('https://example.com/image')).toBe(SizableImageAction.Resize);
|
||||
});
|
||||
|
||||
it('should return Passthrough for unsupported image extensions', () => {
|
||||
expect(checkIsSizableImageURL('https://example.com/image.svg')).toBe(
|
||||
SizableImageAction.Passthrough
|
||||
);
|
||||
expect(checkIsSizableImageURL('https://example.com/image.bmp')).toBe(
|
||||
SizableImageAction.Passthrough
|
||||
);
|
||||
expect(checkIsSizableImageURL('https://example.com/image.tiff')).toBe(
|
||||
SizableImageAction.Passthrough
|
||||
);
|
||||
expect(checkIsSizableImageURL('https://example.com/image.ico')).toBe(
|
||||
SizableImageAction.Passthrough
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle URLs with query parameters correctly', () => {
|
||||
expect(checkIsSizableImageURL('https://example.com/image.jpg?width=100')).toBe(
|
||||
SizableImageAction.Resize
|
||||
);
|
||||
expect(checkIsSizableImageURL('https://example.com/image.svg?height=200')).toBe(
|
||||
SizableImageAction.Passthrough
|
||||
);
|
||||
});
|
||||
|
||||
it('should be case-insensitive for extensions', () => {
|
||||
expect(checkIsSizableImageURL('https://example.com/image.JPG')).toBe(
|
||||
SizableImageAction.Resize
|
||||
);
|
||||
expect(checkIsSizableImageURL('https://example.com/image.PNG')).toBe(
|
||||
SizableImageAction.Resize
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,16 @@
|
||||
import { getExtension } from '@/lib/paths';
|
||||
|
||||
export enum SizableImageAction {
|
||||
Resize = 'resize',
|
||||
Skip = 'skip',
|
||||
Passthrough = 'passthrough',
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developers.cloudflare.com/images/transform-images/#supported-input-formats
|
||||
*/
|
||||
const SUPPORTED_IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
|
||||
|
||||
/**
|
||||
* Check if an image URL is resizable.
|
||||
* Skip it for non-http(s) URLs (data, etc).
|
||||
@@ -25,9 +32,12 @@ export function checkIsSizableImageURL(input: string): SizableImageAction {
|
||||
if (parsed.pathname.includes('/~gitbook/image')) {
|
||||
return SizableImageAction.Skip;
|
||||
}
|
||||
if (parsed.pathname.endsWith('.svg') || parsed.pathname.endsWith('.avif')) {
|
||||
return SizableImageAction.Passthrough;
|
||||
|
||||
const extension = getExtension(parsed.pathname).toLowerCase();
|
||||
if (!extension || SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
|
||||
// If no extension, we consider it resizable.
|
||||
return SizableImageAction.Resize;
|
||||
}
|
||||
|
||||
return SizableImageAction.Resize;
|
||||
return SizableImageAction.Passthrough;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Card } from '@/components/primitives';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { getDataOrNull } from '@v2/lib/data';
|
||||
import { Image } from '../utils';
|
||||
import type { BlockProps } from './Block';
|
||||
import { Caption } from './Caption';
|
||||
import { IntegrationBlock } from './Integration';
|
||||
@@ -52,7 +53,14 @@ export async function Embed(props: BlockProps<gitbookAPI.DocumentBlockEmbed>) {
|
||||
<Card
|
||||
leadingIcon={
|
||||
embed.icon ? (
|
||||
<img src={embed.icon} className={tcls('w-5', 'h-5')} alt="Logo" />
|
||||
<Image
|
||||
src={embed.icon}
|
||||
className={tcls('w-5', 'h-5')}
|
||||
alt="Logo"
|
||||
sources={{ light: { src: embed.icon } }}
|
||||
sizes={[{ width: 20 }]}
|
||||
resize={context.contentContext.imageResizer}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
href={block.data.url}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { getExtension } from './paths';
|
||||
|
||||
describe('getExtension', () => {
|
||||
it('should return the extension of a path', () => {
|
||||
expect(getExtension('test.txt')).toBe('.txt');
|
||||
});
|
||||
|
||||
it('should return an empty string if there is no extension', () => {
|
||||
expect(getExtension('test/path/to/file')).toBe('');
|
||||
});
|
||||
|
||||
it('should return the extension of a path with multiple dots', () => {
|
||||
expect(getExtension('test.with.multiple.dots.txt')).toBe('.txt');
|
||||
});
|
||||
});
|
||||
@@ -57,3 +57,15 @@ export function withTrailingSlash(pathname: string): string {
|
||||
|
||||
return pathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extension of a path.
|
||||
*/
|
||||
export function getExtension(path: string): string {
|
||||
const re = /\.[0-9a-z]+$/i;
|
||||
const match = path.match(re);
|
||||
if (match) {
|
||||
return match[0];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user