Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 1x 1x 1x 1x 1x 9x 9x 9x 9x 7x 9x 9x 9x 4x 4x 4x 4x 4x 3x 3x 15x 15x 15x 11x 11x 11x 11x 15x 10x 15x 15x 1x 1x 1x 9x 9x 9x 3x 3x 3x 3x 3x 3x 3x 3x 1x 15x 15x 15x 15x 15x | import type {H3Event} from 'h3'
import sharp from 'sharp'
const allowedMimeTypes = new Set(['image/jpeg', 'image/png', 'image/webp', 'image/gif'])
const sizePattern = /^(\d{1,4})(?:x(\d{1,4}))?$/
const maxDimension = 4096
const maxPixels = 16_777_216
function requestedSize(event: H3Event) {
const value = getQuery(event).size
if (value === undefined || value === '') return null
if (typeof value !== 'string') throw createError({statusCode: 400, statusMessage: 'Paramètre size invalide'})
const match = sizePattern.exec(value)
const width = Number(match?.[1])
const height = match?.[2] ? Number(match[2]) : undefined
if (!match || width < 1 || width > maxDimension || (height !== undefined && (height < 1 || height > maxDimension || width * height > maxPixels))) {
throw createError({
statusCode: 400,
statusMessage: 'Utilisez size=largeur ou size=largeurxhauteur, avec une dimension maximale de 4096 px'
})
}
return {width, height}
}
export async function imageResponse(event: H3Event) {
const id = Number(getRouterParam(event, 'id'))
if (!Number.isInteger(id) || id < 1) throw createError({statusCode: 400, statusMessage: 'Image invalide'})
const db = database(event)
await ready(db)
const image = await db.prepare('SELECT content,mime_type FROM images WHERE id=?').bind(id).first<{
content: string;
mime_type: string
}>()
if (!image) throw createError({statusCode: 404, statusMessage: 'Image introuvable'})
const match = /^data:([^;,]+);base64,([a-zA-Z0-9+/=\r\n]+)$/.exec(image.content)
const mimeType = allowedMimeTypes.has(image.mime_type) ? image.mime_type : match?.[1]
if (!match || !mimeType || !allowedMimeTypes.has(mimeType)) throw createError({
statusCode: 415,
statusMessage: 'Format d’image non pris en charge'
})
const size = requestedSize(event)
const original = Buffer.from(match[2]!, 'base64')
const output = size
? await sharp(original, {animated: mimeType === 'image/gif', limitInputPixels: 40_000_000})
.resize({
width: size.width,
height: size.height,
fit: size.height ? 'fill' : 'inside',
withoutEnlargement: false
})
.toBuffer()
: original
setHeader(event, 'content-type', mimeType)
setHeader(event, 'cache-control', 'public, max-age=31536000, immutable')
setHeader(event, 'x-content-type-options', 'nosniff')
return output
}
|