diff --git a/config.ts b/config.ts index 4be0d7b..4536d49 100644 --- a/config.ts +++ b/config.ts @@ -22,6 +22,7 @@ export type ConfigType = { thumbnail_method: ThumbnailMethod; thumbnail_dir: string; remove_previous_gallery: boolean; + img_verify_secret?: string; }; export enum ThumbnailMethod { @@ -132,6 +133,9 @@ export class Config { get remove_previous_gallery() { return this._return_bool("remove_previous_gallery") || false; } + get img_verify_secret() { + return this._return_string("img_verify_secret"); + } to_json(): ConfigType { return { cookies: typeof this.cookies === "string", @@ -154,6 +158,7 @@ export class Config { thumbnail_method: this.thumbnail_method, thumbnail_dir: this.thumbnail_dir, remove_previous_gallery: this.remove_previous_gallery, + img_verify_secret: this.img_verify_secret, }; } } diff --git a/routes/api/file/[id].ts b/routes/api/file/[id].ts index 1520766..2baa94f 100644 --- a/routes/api/file/[id].ts +++ b/routes/api/file/[id].ts @@ -4,6 +4,10 @@ import { get_file_response, GetFileResponseOptions, } from "../../../server/get_file_response.ts"; +import { get_string } from "../../../server/parse_form.ts"; +import pbkdf2Hmac from "pbkdf2-hmac"; +import { encode } from "std/encoding/base64.ts"; +import { get_host } from "../../../server/utils.ts"; export const handler: Handlers = { async GET(req, ctx) { @@ -17,6 +21,39 @@ export const handler: Handlers = { return new Response("File not found.", { status: 404 }); } const opts: GetFileResponseOptions = {}; + if (m.cfg.img_verify_secret) { + let verify = null; + try { + const form = await req.formData(); + verify = await get_string(form.get("verify")); + } catch (_) { + null; + const u = new URL(req.url); + verify = u.searchParams.get("verify"); + } + const tverify = encode( + new Uint8Array( + await pbkdf2Hmac( + `id`, + m.cfg.img_verify_secret, + 1000, + 64, + "SHA-512", + ), + ), + ); + if (verify === null) { + const b = new URLSearchParams(); + b.append("verify", tverify); + return Response.redirect( + `${get_host(req)}/api/file/${f.id}?${b}`, + ); + } + if (verify !== tverify) { + return new Response("Invalid verify.", { status: 400 }); + } + } + opts.cache_control = "public, 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"); diff --git a/routes/api/thumbnail/[id].ts b/routes/api/thumbnail/[id].ts index 90531a6..a31a6be 100644 --- a/routes/api/thumbnail/[id].ts +++ b/routes/api/thumbnail/[id].ts @@ -1,7 +1,11 @@ import { Handlers } from "$fresh/server.ts"; import { exists } from "std/fs/exists.ts"; import { get_task_manager } from "../../../server.ts"; -import { parse_bool, parse_int } from "../../../server/parse_form.ts"; +import { + get_string, + parse_bool, + parse_int, +} from "../../../server/parse_form.ts"; import { generate_filename, ThumbnailConfig } from "../../../thumbnail/base.ts"; import { sure_dir } from "../../../utils.ts"; import { ThumbnailMethod } from "../../../config.ts"; @@ -11,6 +15,8 @@ import { GetFileResponseOptions, } from "../../../server/get_file_response.ts"; import { get_host } from "../../../server/utils.ts"; +import pbkdf2Hmac from "pbkdf2-hmac"; +import { encode } from "std/encoding/base64.ts"; export const handler: Handlers = { async GET(req, ctx) { @@ -73,6 +79,39 @@ export const handler: Handlers = { } } const opts: GetFileResponseOptions = {}; + if (m.cfg.img_verify_secret) { + let verify = null; + try { + const form = await req.formData(); + verify = await get_string(form.get("verify")); + } catch (_) { + null; + const u = new URL(req.url); + verify = u.searchParams.get("verify"); + } + const tverify = encode( + new Uint8Array( + await pbkdf2Hmac( + `id`, + m.cfg.img_verify_secret, + 1000, + 64, + "SHA-512", + ), + ), + ); + if (verify === null) { + const b = new URLSearchParams(); + b.append("verify", tverify); + return Response.redirect( + `${get_host(req)}/api/file/${f.id}?${b}`, + ); + } + if (verify !== tverify) { + return new Response("Invalid verify.", { status: 400 }); + } + } + opts.cache_control = "public, 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"); diff --git a/server/get_file_response.ts b/server/get_file_response.ts index b509068..c6b161b 100644 --- a/server/get_file_response.ts +++ b/server/get_file_response.ts @@ -12,6 +12,7 @@ export type GetFileResponseOptions = { if_unmodified_since?: string | null; mimetype?: string | null; range?: string | null; + cache_control?: string | null; }; export async function get_file_response( @@ -249,6 +250,9 @@ export async function get_file_response( }; if (mimetype) headers["Content-Type"] = mimetype; if (i.mtime) headers["Last-Modified"] = i.mtime.toUTCString(); + if (opts?.cache_control) { + headers["Cache-Control"] = opts.cache_control; + } return new Response(f.readable, { headers }); } } catch (e) {