From 0ea6da38e4a40e5fd1c8fa68f67e780a73843872 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sun, 2 Jun 2024 13:33:46 +0800 Subject: [PATCH] Add new config use_path_based_img_url Fix parseBitInt --- config.ts | 5 ++ fresh.gen.ts | 2 + routes/api/file/[id].ts | 7 +++ routes/api/thumbnail/[id].ts | 5 ++ routes/thumbnail/[id].ts | 8 ++-- routes/thumbnail/[verify]/[id].ts | 80 +++++++++++++++++++++++++++++++ utils.ts | 5 +- utils_test.ts | 13 ++++- 8 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 routes/thumbnail/[verify]/[id].ts diff --git a/config.ts b/config.ts index 73880b6..92a9e43 100644 --- a/config.ts +++ b/config.ts @@ -35,6 +35,7 @@ export type ConfigType = { /** EH metadata cache time in hours */ eh_metadata_cache_time: number; random_file_secret?: string; + use_path_based_img_url: boolean; }; export enum ThumbnailMethod { @@ -204,6 +205,9 @@ export class Config { get random_file_secret() { return this._return_string("random_file_secret"); } + get use_path_based_img_url() { + return this._return_bool("use_path_based_img_url") ?? true; + } to_json(): ConfigType { return { cookies: typeof this.cookies === "string", @@ -238,6 +242,7 @@ export class Config { this.download_timeout_check_interval, eh_metadata_cache_time: this.eh_metadata_cache_time, random_file_secret: this.random_file_secret, + use_path_based_img_url: this.use_path_based_img_url, }; } } diff --git a/fresh.gen.ts b/fresh.gen.ts index a40623f..ede2534 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -38,6 +38,7 @@ import * as $file_verify_id_ from "./routes/file/[verify]/[id].ts"; import * as $file_middleware from "./routes/file/_middleware.ts"; import * as $index from "./routes/index.ts"; import * as $thumbnail_id_ from "./routes/thumbnail/[id].ts"; +import * as $thumbnail_verify_id_ from "./routes/thumbnail/[verify]/[id].ts"; import * as $thumbnail_middleware from "./routes/thumbnail/_middleware.ts"; import * as $upload from "./routes/upload.tsx"; import * as $Upload from "./islands/Upload.tsx"; @@ -82,6 +83,7 @@ const manifest = { "./routes/file/_middleware.ts": $file_middleware, "./routes/index.ts": $index, "./routes/thumbnail/[id].ts": $thumbnail_id_, + "./routes/thumbnail/[verify]/[id].ts": $thumbnail_verify_id_, "./routes/thumbnail/_middleware.ts": $thumbnail_middleware, "./routes/upload.tsx": $upload, }, diff --git a/routes/api/file/[id].ts b/routes/api/file/[id].ts index ada68e0..4a9715c 100644 --- a/routes/api/file/[id].ts +++ b/routes/api/file/[id].ts @@ -12,6 +12,7 @@ import type { EhFileExtend } from "../../../server/files.ts"; import { User, UserPermission } from "../../../db.ts"; import { SortableURLSearchParams } from "../../../server/SortableURLSearchParams.ts"; import { isNumNaN, parseBigInt } from "../../../utils.ts"; +import { extname } from "@std/path"; export const handler: Handlers = { async GET(req, ctx) { @@ -77,6 +78,12 @@ export const handler: Handlers = { ), ); if (verify === null) { + if (m.cfg.use_path_based_img_url) { + const ext = extname(f.path); + return Response.redirect( + `${get_host(req)}/file/${tverify}/${f.id}${ext}`, + ); + } const b = new URLSearchParams(); b.append("verify", tverify); return Response.redirect( diff --git a/routes/api/thumbnail/[id].ts b/routes/api/thumbnail/[id].ts index 5f61ee5..f63d85b 100644 --- a/routes/api/thumbnail/[id].ts +++ b/routes/api/thumbnail/[id].ts @@ -167,6 +167,11 @@ export const handler: Handlers = { ); const b = new URLSearchParams(bs.toString()); b.append("verify", tverify); + if (m.cfg.use_path_based_img_url) { + return Response.redirect( + `${get_host(req)}/thumbnail/${b}/${f.id}.jpg`, + ); + } return Response.redirect( `${get_host(req)}/thumbnail/${f.id}?${b}`, ); diff --git a/routes/thumbnail/[id].ts b/routes/thumbnail/[id].ts index dbd0512..966f0ef 100644 --- a/routes/thumbnail/[id].ts +++ b/routes/thumbnail/[id].ts @@ -21,10 +21,6 @@ export const handler: Handlers = { const m = get_task_manager(); const b = m.cfg.thumbnail_dir; await sure_dir(b); - const f = m.db.get_file(id); - if (!f) { - return new Response("File not found.", { status: 404 }); - } const u = new URL(req.url); if (!m.cfg.img_verify_secret) { return new Response("Can not verify.", { status: 400 }); @@ -57,6 +53,10 @@ export const handler: Handlers = { ) { return new Response("params is missing", { status: 400 }); } + const f = m.db.get_file(id); + if (!f) { + return new Response("File not found.", { status: 404 }); + } const cfg: ThumbnailConfig = { width, height, diff --git a/routes/thumbnail/[verify]/[id].ts b/routes/thumbnail/[verify]/[id].ts new file mode 100644 index 0000000..26f8b1a --- /dev/null +++ b/routes/thumbnail/[verify]/[id].ts @@ -0,0 +1,80 @@ +import { Handlers } from "$fresh/server.ts"; +import { exists } from "@std/fs/exists"; +import { get_task_manager } from "../../../server.ts"; +import { parse_int } from "../../../server/parse_form.ts"; +import { generate_filename, ThumbnailConfig } from "../../../thumbnail/base.ts"; +import { isNumNaN, parseBigInt, sure_dir } from "../../../utils.ts"; +import { + get_file_response, + GetFileResponseOptions, +} from "../../../server/get_file_response.ts"; +import pbkdf2Hmac from "pbkdf2-hmac"; +import { encodeBase64 as encode } from "@std/encoding/base64"; +import { SortableURLSearchParams } from "../../../server/SortableURLSearchParams.ts"; + +export const handler: Handlers = { + async GET(req, ctx) { + const id = parseBigInt(ctx.params.id); + if (isNumNaN(id)) { + return new Response("Bad Request", { status: 400 }); + } + const m = get_task_manager(); + const b = m.cfg.thumbnail_dir; + await sure_dir(b); + if (!m.cfg.img_verify_secret) { + return new Response("Can not verify.", { status: 400 }); + } + // U2 存在将 & 错误的编码为 & 的BUG + const tmp = ctx.params.verify.replaceAll("&", "&"); + const search = new URLSearchParams(tmp); + const verify = search.get("verify"); + if (!verify) return new Response("Verify is needed.", { status: 400 }); + const bs = new SortableURLSearchParams(tmp, ["verify"]); + const tverify = encode( + new Uint8Array( + await pbkdf2Hmac( + `${id}${bs.toString2()}`, + m.cfg.img_verify_secret, + 1000, + 64, + "SHA-512", + ), + ), + ); + if (verify !== tverify) { + return new Response("verify is invalid.", { status: 400 }); + } + const width = await parse_int(search.get("width"), null); + const height = await parse_int(search.get("height"), null); + const quality = await parse_int(search.get("quality"), null); + const method = await parse_int(search.get("method"), null); + const align = await parse_int(search.get("align"), null); + if ( + width === null || height === null || quality === null || + method === null || align === null + ) { + return new Response("params is missing", { status: 400 }); + } + const f = m.db.get_file(id); + if (!f) { + return new Response("File not found.", { status: 404 }); + } + const cfg: ThumbnailConfig = { + width, + height, + quality, + method, + align, + }; + const output = generate_filename(b, f, cfg); + if (!(await exists(output))) { + return new Response("file not exists.", { status: 500 }); + } + const opts: GetFileResponseOptions = {}; + opts.cache_control = "public, no-transform, max-age=31536000"; + opts.range = req.headers.get("range"); + opts.if_modified_since = req.headers.get("If-Modified-Since"); + opts.if_unmodified_since = req.headers.get("If-Unmodified-Since"); + return await get_file_response(output, opts); + }, +}; diff --git a/utils.ts b/utils.ts index d6ae1bf..cd3cdd2 100644 --- a/utils.ts +++ b/utils.ts @@ -300,7 +300,10 @@ export function toJSON(obj: unknown) { export function parseBigInt(str: string) { const t = parseInt(str); if (isNaN(t)) return t; - return !Number.isSafeInteger(t) ? BigInt(str) : t; + if (Number.isSafeInteger(t)) return t; + const m = str.match(/^(\+|-)?\d+/); + if (!m) return NaN; + return BigInt(m[0]); } export function isNumNaN(num: number | bigint) { diff --git a/utils_test.ts b/utils_test.ts index 2f44e34..9f3dd28 100644 --- a/utils_test.ts +++ b/utils_test.ts @@ -9,6 +9,7 @@ import { compareNum, filterFilename, map, + parseBigInt, promiseState, PromiseStatus, sleep, @@ -37,7 +38,7 @@ Deno.test("promiseState_test", async () => { }); Deno.test("Pid_Test", async () => { - if (Deno.build.os == "windows") { + if (Deno.build.os == "windows" || Deno.build.os == "linux") { assertEquals(await check_running(Deno.pid), true); } }); @@ -180,3 +181,13 @@ Deno.test("compareNum_test", () => { 11n, ]); }); + +Deno.test("parseBigInt_test", () => { + assertEquals(parseBigInt("1.jpg"), 1); + assertEquals(parseBigInt("9007199254740992.png"), 9007199254740992n); + assertEquals(parseBigInt("+3_3"), 3); + assertEquals(parseBigInt("+9007199254740992"), 9007199254740992n); + assertEquals(parseBigInt("-9007199254740992.3"), -9007199254740992n); + assertEquals(parseBigInt("--9007199254740992"), NaN); + assertEquals(parseBigInt("--3"), NaN); +});