166 lines
5.2 KiB
JavaScript
166 lines
5.2 KiB
JavaScript
var define_IMAGES_LOCAL_PATTERNS_default = [];
|
|
var define_IMAGES_REMOTE_PATTERNS_default = [];
|
|
let NEXT_IMAGE_REGEXP;
|
|
async function fetchImage(fetcher, imageUrl, ctx) {
|
|
if (!imageUrl || imageUrl.length > 3072 || imageUrl.startsWith("//")) {
|
|
return getUrlErrorResponse();
|
|
}
|
|
if (imageUrl.startsWith("/")) {
|
|
const url2 = URL.parse(imageUrl, "http://n");
|
|
if (url2 == null) {
|
|
return getUrlErrorResponse();
|
|
}
|
|
const pathname = decodeURIComponent(url2.pathname);
|
|
NEXT_IMAGE_REGEXP ??= /\/_next\/image($|\/)/;
|
|
if (NEXT_IMAGE_REGEXP.test(pathname)) {
|
|
return getUrlErrorResponse();
|
|
}
|
|
if (define_IMAGES_LOCAL_PATTERNS_default.length > 0 && !define_IMAGES_LOCAL_PATTERNS_default.some((p) => matchLocalPattern(p, url2))) {
|
|
return getUrlErrorResponse();
|
|
}
|
|
return fetcher?.fetch(`http://assets.local${imageUrl}`);
|
|
}
|
|
let url;
|
|
try {
|
|
url = new URL(imageUrl);
|
|
} catch {
|
|
return getUrlErrorResponse();
|
|
}
|
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
return getUrlErrorResponse();
|
|
}
|
|
if (!define_IMAGES_REMOTE_PATTERNS_default.some((p) => matchRemotePattern(p, url))) {
|
|
return getUrlErrorResponse();
|
|
}
|
|
const imgResponse = await fetch(imageUrl, { cf: { cacheEverything: true } });
|
|
if (!imgResponse.body) {
|
|
return imgResponse;
|
|
}
|
|
const buffer = new ArrayBuffer(32);
|
|
try {
|
|
let contentType;
|
|
const [respBody, contentBody] = imgResponse.body.tee();
|
|
const reader = contentBody.getReader({ mode: "byob" });
|
|
const { value } = await reader.read(new Uint8Array(buffer));
|
|
ctx.waitUntil(reader.cancel());
|
|
if (value) {
|
|
contentType = detectContentType(value);
|
|
}
|
|
if (!contentType) {
|
|
contentType = imgResponse.headers.get("content-type") ?? "";
|
|
}
|
|
if (!contentType.startsWith("image/") || contentType.includes(",")) {
|
|
contentType = void 0;
|
|
}
|
|
if (contentType && !(contentType === SVG && true)) {
|
|
const headers = new Headers(imgResponse.headers);
|
|
headers.set("content-type", contentType);
|
|
headers.set("content-disposition", "attachment");
|
|
headers.set("content-security-policy", "script-src 'none'; frame-src 'none'; sandbox;");
|
|
return new Response(respBody, { ...imgResponse, headers });
|
|
}
|
|
ctx.waitUntil(respBody.cancel());
|
|
return new Response('"url" parameter is valid but image type is not allowed', {
|
|
status: 400
|
|
});
|
|
} catch {
|
|
return new Response('"url" parameter is valid but upstream response is invalid', {
|
|
status: 400
|
|
});
|
|
}
|
|
}
|
|
function matchRemotePattern(pattern, url) {
|
|
if (pattern.protocol !== void 0 && pattern.protocol.replace(/:$/, "") !== url.protocol.replace(/:$/, "")) {
|
|
return false;
|
|
}
|
|
if (pattern.port !== void 0 && pattern.port !== url.port) {
|
|
return false;
|
|
}
|
|
if (pattern.hostname === void 0 || !new RegExp(pattern.hostname).test(url.hostname)) {
|
|
return false;
|
|
}
|
|
if (pattern.search !== void 0 && pattern.search !== url.search) {
|
|
return false;
|
|
}
|
|
return new RegExp(pattern.pathname).test(url.pathname);
|
|
}
|
|
function matchLocalPattern(pattern, url) {
|
|
if (pattern.search !== void 0 && pattern.search !== url.search) {
|
|
return false;
|
|
}
|
|
return new RegExp(pattern.pathname).test(url.pathname);
|
|
}
|
|
function getUrlErrorResponse() {
|
|
return new Response(`"url" parameter is not allowed`, { status: 400 });
|
|
}
|
|
const AVIF = "image/avif";
|
|
const WEBP = "image/webp";
|
|
const PNG = "image/png";
|
|
const JPEG = "image/jpeg";
|
|
const JXL = "image/jxl";
|
|
const JP2 = "image/jp2";
|
|
const HEIC = "image/heic";
|
|
const GIF = "image/gif";
|
|
const SVG = "image/svg+xml";
|
|
const ICO = "image/x-icon";
|
|
const ICNS = "image/x-icns";
|
|
const TIFF = "image/tiff";
|
|
const BMP = "image/bmp";
|
|
const PDF = "application/pdf";
|
|
function detectContentType(buffer) {
|
|
if ([255, 216, 255].every((b, i) => buffer[i] === b)) {
|
|
return JPEG;
|
|
}
|
|
if ([137, 80, 78, 71, 13, 10, 26, 10].every((b, i) => buffer[i] === b)) {
|
|
return PNG;
|
|
}
|
|
if ([71, 73, 70, 56].every((b, i) => buffer[i] === b)) {
|
|
return GIF;
|
|
}
|
|
if ([82, 73, 70, 70, 0, 0, 0, 0, 87, 69, 66, 80].every((b, i) => !b || buffer[i] === b)) {
|
|
return WEBP;
|
|
}
|
|
if ([60, 63, 120, 109, 108].every((b, i) => buffer[i] === b)) {
|
|
return SVG;
|
|
}
|
|
if ([60, 115, 118, 103].every((b, i) => buffer[i] === b)) {
|
|
return SVG;
|
|
}
|
|
if ([0, 0, 0, 0, 102, 116, 121, 112, 97, 118, 105, 102].every((b, i) => !b || buffer[i] === b)) {
|
|
return AVIF;
|
|
}
|
|
if ([0, 0, 1, 0].every((b, i) => buffer[i] === b)) {
|
|
return ICO;
|
|
}
|
|
if ([105, 99, 110, 115].every((b, i) => buffer[i] === b)) {
|
|
return ICNS;
|
|
}
|
|
if ([73, 73, 42, 0].every((b, i) => buffer[i] === b)) {
|
|
return TIFF;
|
|
}
|
|
if ([66, 77].every((b, i) => buffer[i] === b)) {
|
|
return BMP;
|
|
}
|
|
if ([255, 10].every((b, i) => buffer[i] === b)) {
|
|
return JXL;
|
|
}
|
|
if ([0, 0, 0, 12, 74, 88, 76, 32, 13, 10, 135, 10].every((b, i) => buffer[i] === b)) {
|
|
return JXL;
|
|
}
|
|
if ([0, 0, 0, 0, 102, 116, 121, 112, 104, 101, 105, 99].every((b, i) => !b || buffer[i] === b)) {
|
|
return HEIC;
|
|
}
|
|
if ([37, 80, 68, 70, 45].every((b, i) => buffer[i] === b)) {
|
|
return PDF;
|
|
}
|
|
if ([0, 0, 0, 12, 106, 80, 32, 32, 13, 10, 135, 10].every((b, i) => buffer[i] === b)) {
|
|
return JP2;
|
|
}
|
|
}
|
|
export {
|
|
detectContentType,
|
|
fetchImage,
|
|
matchLocalPattern,
|
|
matchRemotePattern
|
|
};
|