Aktueller Stand

This commit is contained in:
2026-01-23 01:33:35 +01:00
parent 082dc5e110
commit 2766dd12c5
10109 changed files with 1578841 additions and 77685 deletions

View File

@@ -0,0 +1,60 @@
// src/middleware/basic-auth/index.ts
import { HTTPException } from "../../http-exception.js";
import { auth } from "../../utils/basic-auth.js";
import { timingSafeEqual } from "../../utils/buffer.js";
var basicAuth = (options, ...users) => {
const usernamePasswordInOptions = "username" in options && "password" in options;
const verifyUserInOptions = "verifyUser" in options;
if (!(usernamePasswordInOptions || verifyUserInOptions)) {
throw new Error(
'basic auth middleware requires options for "username and password" or "verifyUser"'
);
}
if (!options.realm) {
options.realm = "Secure Area";
}
if (!options.invalidUserMessage) {
options.invalidUserMessage = "Unauthorized";
}
if (usernamePasswordInOptions) {
users.unshift({ username: options.username, password: options.password });
}
return async function basicAuth2(ctx, next) {
const requestUser = auth(ctx.req.raw);
if (requestUser) {
if (verifyUserInOptions) {
if (await options.verifyUser(requestUser.username, requestUser.password, ctx)) {
await next();
return;
}
} else {
for (const user of users) {
const [usernameEqual, passwordEqual] = await Promise.all([
timingSafeEqual(user.username, requestUser.username, options.hashFunction),
timingSafeEqual(user.password, requestUser.password, options.hashFunction)
]);
if (usernameEqual && passwordEqual) {
await next();
return;
}
}
}
}
const status = 401;
const headers = {
"WWW-Authenticate": 'Basic realm="' + options.realm?.replace(/"/g, '\\"') + '"'
};
const responseMessage = typeof options.invalidUserMessage === "function" ? await options.invalidUserMessage(ctx) : options.invalidUserMessage;
const res = typeof responseMessage === "string" ? new Response(responseMessage, { status, headers }) : new Response(JSON.stringify(responseMessage), {
status,
headers: {
...headers,
"content-type": "application/json"
}
});
throw new HTTPException(status, { res });
};
};
export {
basicAuth
};

View File

@@ -0,0 +1,83 @@
// src/middleware/bearer-auth/index.ts
import { HTTPException } from "../../http-exception.js";
import { timingSafeEqual } from "../../utils/buffer.js";
var TOKEN_STRINGS = "[A-Za-z0-9._~+/-]+=*";
var PREFIX = "Bearer";
var HEADER = "Authorization";
var bearerAuth = (options) => {
if (!("token" in options || "verifyToken" in options)) {
throw new Error('bearer auth middleware requires options for "token"');
}
if (!options.realm) {
options.realm = "";
}
if (options.prefix === void 0) {
options.prefix = PREFIX;
}
const realm = options.realm?.replace(/"/g, '\\"');
const prefixRegexStr = options.prefix === "" ? "" : `${options.prefix} +`;
const regexp = new RegExp(`^${prefixRegexStr}(${TOKEN_STRINGS}) *$`);
const wwwAuthenticatePrefix = options.prefix === "" ? "" : `${options.prefix} `;
const throwHTTPException = async (c, status, wwwAuthenticateHeader, messageOption) => {
const wwwAuthenticateHeaderValue = typeof wwwAuthenticateHeader === "function" ? await wwwAuthenticateHeader(c) : wwwAuthenticateHeader;
const headers = {
"WWW-Authenticate": typeof wwwAuthenticateHeaderValue === "string" ? wwwAuthenticateHeaderValue : `${wwwAuthenticatePrefix}${Object.entries(wwwAuthenticateHeaderValue).map(([key, value]) => `${key}="${value}"`).join(",")}`
};
const responseMessage = typeof messageOption === "function" ? await messageOption(c) : messageOption;
const res = typeof responseMessage === "string" ? new Response(responseMessage, { status, headers }) : new Response(JSON.stringify(responseMessage), {
status,
headers: {
...headers,
"content-type": "application/json"
}
});
throw new HTTPException(status, { res });
};
return async function bearerAuth2(c, next) {
const headerToken = c.req.header(options.headerName || HEADER);
if (!headerToken) {
await throwHTTPException(
c,
401,
options.noAuthenticationHeader?.wwwAuthenticateHeader || `${wwwAuthenticatePrefix}realm="${realm}"`,
options.noAuthenticationHeader?.message || options.noAuthenticationHeaderMessage || "Unauthorized"
);
} else {
const match = regexp.exec(headerToken);
if (!match) {
await throwHTTPException(
c,
400,
options.invalidAuthenticationHeader?.wwwAuthenticateHeader || `${wwwAuthenticatePrefix}error="invalid_request"`,
options.invalidAuthenticationHeader?.message || options.invalidAuthenticationHeaderMessage || "Bad Request"
);
} else {
let equal = false;
if ("verifyToken" in options) {
equal = await options.verifyToken(match[1], c);
} else if (typeof options.token === "string") {
equal = await timingSafeEqual(options.token, match[1], options.hashFunction);
} else if (Array.isArray(options.token) && options.token.length > 0) {
for (const token of options.token) {
if (await timingSafeEqual(token, match[1], options.hashFunction)) {
equal = true;
break;
}
}
}
if (!equal) {
await throwHTTPException(
c,
401,
options.invalidToken?.wwwAuthenticateHeader || `${wwwAuthenticatePrefix}error="invalid_token"`,
options.invalidToken?.message || options.invalidTokenMessage || "Unauthorized"
);
}
}
}
await next();
};
};
export {
bearerAuth
};

View File

@@ -0,0 +1,62 @@
// src/middleware/body-limit/index.ts
import { HTTPException } from "../../http-exception.js";
var ERROR_MESSAGE = "Payload Too Large";
var BodyLimitError = class extends Error {
constructor(message) {
super(message);
this.name = "BodyLimitError";
}
};
var bodyLimit = (options) => {
const onError = options.onError || (() => {
const res = new Response(ERROR_MESSAGE, {
status: 413
});
throw new HTTPException(413, { res });
});
const maxSize = options.maxSize;
return async function bodyLimit2(c, next) {
if (!c.req.raw.body) {
return next();
}
const hasTransferEncoding = c.req.raw.headers.has("transfer-encoding");
const hasContentLength = c.req.raw.headers.has("content-length");
if (hasTransferEncoding && hasContentLength) {
}
if (hasContentLength && !hasTransferEncoding) {
const contentLength = parseInt(c.req.raw.headers.get("content-length") || "0", 10);
return contentLength > maxSize ? onError(c) : next();
}
let size = 0;
const rawReader = c.req.raw.body.getReader();
const reader = new ReadableStream({
async start(controller) {
try {
for (; ; ) {
const { done, value } = await rawReader.read();
if (done) {
break;
}
size += value.length;
if (size > maxSize) {
controller.error(new BodyLimitError(ERROR_MESSAGE));
break;
}
controller.enqueue(value);
}
} finally {
controller.close();
}
}
});
const requestInit = { body: reader, duplex: "half" };
c.req.raw = new Request(c.req.raw, requestInit);
await next();
if (c.error instanceof BodyLimitError) {
c.res = await onError(c);
}
};
};
export {
bodyLimit
};

View File

@@ -0,0 +1,79 @@
// src/middleware/cache/index.ts
var defaultCacheableStatusCodes = [200];
var shouldSkipCache = (res) => {
const vary = res.headers.get("Vary");
return vary && vary.includes("*");
};
var cache = (options) => {
if (!globalThis.caches) {
console.log("Cache Middleware is not enabled because caches is not defined.");
return async (_c, next) => await next();
}
if (options.wait === void 0) {
options.wait = false;
}
const cacheControlDirectives = options.cacheControl?.split(",").map((directive) => directive.toLowerCase());
const varyDirectives = Array.isArray(options.vary) ? options.vary : options.vary?.split(",").map((directive) => directive.trim());
if (options.vary?.includes("*")) {
throw new Error(
'Middleware vary configuration cannot include "*", as it disallows effective caching.'
);
}
const cacheableStatusCodes = new Set(
options.cacheableStatusCodes ?? defaultCacheableStatusCodes
);
const addHeader = (c) => {
if (cacheControlDirectives) {
const existingDirectives = c.res.headers.get("Cache-Control")?.split(",").map((d) => d.trim().split("=", 1)[0]) ?? [];
for (const directive of cacheControlDirectives) {
let [name, value] = directive.trim().split("=", 2);
name = name.toLowerCase();
if (!existingDirectives.includes(name)) {
c.header("Cache-Control", `${name}${value ? `=${value}` : ""}`, { append: true });
}
}
}
if (varyDirectives) {
const existingDirectives = c.res.headers.get("Vary")?.split(",").map((d) => d.trim()) ?? [];
const vary = Array.from(
new Set(
[...existingDirectives, ...varyDirectives].map((directive) => directive.toLowerCase())
)
).sort();
if (vary.includes("*")) {
c.header("Vary", "*");
} else {
c.header("Vary", vary.join(", "));
}
}
};
return async function cache2(c, next) {
let key = c.req.url;
if (options.keyGenerator) {
key = await options.keyGenerator(c);
}
const cacheName = typeof options.cacheName === "function" ? await options.cacheName(c) : options.cacheName;
const cache3 = await caches.open(cacheName);
const response = await cache3.match(key);
if (response) {
return new Response(response.body, response);
}
await next();
if (!cacheableStatusCodes.has(c.res.status)) {
return;
}
addHeader(c);
if (shouldSkipCache(c.res)) {
return;
}
const res = c.res.clone();
if (options.wait) {
await cache3.put(key, res);
} else {
c.executionCtx.waitUntil(cache3.put(key, res));
}
};
};
export {
cache
};

View File

@@ -0,0 +1,77 @@
// src/middleware/combine/index.ts
import { compose } from "../../compose.js";
import { METHOD_NAME_ALL } from "../../router.js";
import { TrieRouter } from "../../router/trie-router/index.js";
var some = (...middleware) => {
return async function some2(c, next) {
let isNextCalled = false;
const wrappedNext = () => {
isNextCalled = true;
return next();
};
let lastError;
for (const handler of middleware) {
try {
const result = await handler(c, wrappedNext);
if (result === true && !c.finalized) {
await wrappedNext();
} else if (result === false) {
lastError = new Error("No successful middleware found");
continue;
}
lastError = void 0;
break;
} catch (error) {
lastError = error;
if (isNextCalled) {
break;
}
}
}
if (lastError) {
throw lastError;
}
};
};
var every = (...middleware) => {
return async function every2(c, next) {
const currentRouteIndex = c.req.routeIndex;
await compose(
middleware.map((m) => [
[
async (c2, next2) => {
c2.req.routeIndex = currentRouteIndex;
const res = await m(c2, next2);
if (res === false) {
throw new Error("Unmet condition");
}
return res;
}
]
])
)(c, next);
};
};
var except = (condition, ...middleware) => {
let router = void 0;
const conditions = (Array.isArray(condition) ? condition : [condition]).map((condition2) => {
if (typeof condition2 === "string") {
router ||= new TrieRouter();
router.add(METHOD_NAME_ALL, condition2, true);
} else {
return condition2;
}
}).filter(Boolean);
if (router) {
conditions.unshift((c) => !!router?.match(METHOD_NAME_ALL, c.req.path)?.[0]?.[0]?.[0]);
}
const handler = some((c) => conditions.some((cond) => cond(c)), every(...middleware));
return async function except2(c, next) {
await handler(c, next);
};
};
export {
every,
except,
some
};

View File

@@ -0,0 +1,39 @@
// src/middleware/compress/index.ts
import { COMPRESSIBLE_CONTENT_TYPE_REGEX } from "../../utils/compress.js";
var ENCODING_TYPES = ["gzip", "deflate"];
var cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/i;
var compress = (options) => {
const threshold = options?.threshold ?? 1024;
return async function compress2(ctx, next) {
await next();
const contentLength = ctx.res.headers.get("Content-Length");
if (ctx.res.headers.has("Content-Encoding") || // already encoded
ctx.res.headers.has("Transfer-Encoding") || // already encoded or chunked
ctx.req.method === "HEAD" || // HEAD request
contentLength && Number(contentLength) < threshold || // content-length below threshold
!shouldCompress(ctx.res) || // not compressible type
!shouldTransform(ctx.res)) {
return;
}
const accepted = ctx.req.header("Accept-Encoding");
const encoding = options?.encoding ?? ENCODING_TYPES.find((encoding2) => accepted?.includes(encoding2));
if (!encoding || !ctx.res.body) {
return;
}
const stream = new CompressionStream(encoding);
ctx.res = new Response(ctx.res.body.pipeThrough(stream), ctx.res);
ctx.res.headers.delete("Content-Length");
ctx.res.headers.set("Content-Encoding", encoding);
};
};
var shouldCompress = (res) => {
const type = res.headers.get("Content-Type");
return type && COMPRESSIBLE_CONTENT_TYPE_REGEX.test(type);
};
var shouldTransform = (res) => {
const cacheControl = res.headers.get("Cache-Control");
return !cacheControl || !cacheControlNoTransformRegExp.test(cacheControl);
};
export {
compress
};

View File

@@ -0,0 +1,23 @@
// src/middleware/context-storage/index.ts
import { AsyncLocalStorage } from "node:async_hooks";
var asyncLocalStorage = new AsyncLocalStorage();
var contextStorage = () => {
return async function contextStorage2(c, next) {
await asyncLocalStorage.run(c, next);
};
};
var tryGetContext = () => {
return asyncLocalStorage.getStore();
};
var getContext = () => {
const context = tryGetContext();
if (!context) {
throw new Error("Context is not available");
}
return context;
};
export {
contextStorage,
getContext,
tryGetContext
};

View File

@@ -0,0 +1,87 @@
// src/middleware/cors/index.ts
var cors = (options) => {
const defaults = {
origin: "*",
allowMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"],
allowHeaders: [],
exposeHeaders: []
};
const opts = {
...defaults,
...options
};
const findAllowOrigin = ((optsOrigin) => {
if (typeof optsOrigin === "string") {
if (optsOrigin === "*") {
return () => optsOrigin;
} else {
return (origin) => optsOrigin === origin ? origin : null;
}
} else if (typeof optsOrigin === "function") {
return optsOrigin;
} else {
return (origin) => optsOrigin.includes(origin) ? origin : null;
}
})(opts.origin);
const findAllowMethods = ((optsAllowMethods) => {
if (typeof optsAllowMethods === "function") {
return optsAllowMethods;
} else if (Array.isArray(optsAllowMethods)) {
return () => optsAllowMethods;
} else {
return () => [];
}
})(opts.allowMethods);
return async function cors2(c, next) {
function set(key, value) {
c.res.headers.set(key, value);
}
const allowOrigin = await findAllowOrigin(c.req.header("origin") || "", c);
if (allowOrigin) {
set("Access-Control-Allow-Origin", allowOrigin);
}
if (opts.credentials) {
set("Access-Control-Allow-Credentials", "true");
}
if (opts.exposeHeaders?.length) {
set("Access-Control-Expose-Headers", opts.exposeHeaders.join(","));
}
if (c.req.method === "OPTIONS") {
if (opts.origin !== "*") {
set("Vary", "Origin");
}
if (opts.maxAge != null) {
set("Access-Control-Max-Age", opts.maxAge.toString());
}
const allowMethods = await findAllowMethods(c.req.header("origin") || "", c);
if (allowMethods.length) {
set("Access-Control-Allow-Methods", allowMethods.join(","));
}
let headers = opts.allowHeaders;
if (!headers?.length) {
const requestHeaders = c.req.header("Access-Control-Request-Headers");
if (requestHeaders) {
headers = requestHeaders.split(/\s*,\s*/);
}
}
if (headers?.length) {
set("Access-Control-Allow-Headers", headers.join(","));
c.res.headers.append("Vary", "Access-Control-Request-Headers");
}
c.res.headers.delete("Content-Length");
c.res.headers.delete("Content-Type");
return new Response(null, {
headers: c.res.headers,
status: 204,
statusText: "No Content"
});
}
await next();
if (opts.origin !== "*") {
c.header("Vary", "Origin", { append: true });
}
};
};
export {
cors
};

View File

@@ -0,0 +1,55 @@
// src/middleware/csrf/index.ts
import { HTTPException } from "../../http-exception.js";
var secFetchSiteValues = ["same-origin", "same-site", "none", "cross-site"];
var isSecFetchSite = (value) => secFetchSiteValues.includes(value);
var isSafeMethodRe = /^(GET|HEAD)$/;
var isRequestedByFormElementRe = /^\b(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)\b/i;
var csrf = (options) => {
const originHandler = ((optsOrigin) => {
if (!optsOrigin) {
return (origin, c) => origin === new URL(c.req.url).origin;
} else if (typeof optsOrigin === "string") {
return (origin) => origin === optsOrigin;
} else if (typeof optsOrigin === "function") {
return optsOrigin;
} else {
return (origin) => optsOrigin.includes(origin);
}
})(options?.origin);
const isAllowedOrigin = async (origin, c) => {
if (origin === void 0) {
return false;
}
return await originHandler(origin, c);
};
const secFetchSiteHandler = ((optsSecFetchSite) => {
if (!optsSecFetchSite) {
return (secFetchSite) => secFetchSite === "same-origin";
} else if (typeof optsSecFetchSite === "string") {
return (secFetchSite) => secFetchSite === optsSecFetchSite;
} else if (typeof optsSecFetchSite === "function") {
return optsSecFetchSite;
} else {
return (secFetchSite) => optsSecFetchSite.includes(secFetchSite);
}
})(options?.secFetchSite);
const isAllowedSecFetchSite = async (secFetchSite, c) => {
if (secFetchSite === void 0) {
return false;
}
if (!isSecFetchSite(secFetchSite)) {
return false;
}
return await secFetchSiteHandler(secFetchSite, c);
};
return async function csrf2(c, next) {
if (!isSafeMethodRe.test(c.req.method) && isRequestedByFormElementRe.test(c.req.header("content-type") || "text/plain") && !await isAllowedSecFetchSite(c.req.header("sec-fetch-site"), c) && !await isAllowedOrigin(c.req.header("origin"), c)) {
const res = new Response("Forbidden", { status: 403 });
throw new HTTPException(403, { res });
}
await next();
};
};
export {
csrf
};

View File

@@ -0,0 +1,33 @@
// src/middleware/etag/digest.ts
var mergeBuffers = (buffer1, buffer2) => {
if (!buffer1) {
return buffer2;
}
const merged = new Uint8Array(
new ArrayBuffer(buffer1.byteLength + buffer2.byteLength)
);
merged.set(new Uint8Array(buffer1), 0);
merged.set(buffer2, buffer1.byteLength);
return merged;
};
var generateDigest = async (stream, generator) => {
if (!stream) {
return null;
}
let result = void 0;
const reader = stream.getReader();
for (; ; ) {
const { value, done } = await reader.read();
if (done) {
break;
}
result = await generator(mergeBuffers(result, value));
}
if (!result) {
return null;
}
return Array.prototype.map.call(new Uint8Array(result), (x) => x.toString(16).padStart(2, "0")).join("");
};
export {
generateDigest
};

View File

@@ -0,0 +1,72 @@
// src/middleware/etag/index.ts
import { generateDigest } from "./digest.js";
var RETAINED_304_HEADERS = [
"cache-control",
"content-location",
"date",
"etag",
"expires",
"vary"
];
var stripWeak = (tag) => tag.replace(/^W\//, "");
function etagMatches(etag2, ifNoneMatch) {
return ifNoneMatch != null && ifNoneMatch.split(/,\s*/).some((t) => stripWeak(t) === stripWeak(etag2));
}
function initializeGenerator(generator) {
if (!generator) {
if (crypto && crypto.subtle) {
generator = (body) => crypto.subtle.digest(
{
name: "SHA-1"
},
body
);
}
}
return generator;
}
var etag = (options) => {
const retainedHeaders = options?.retainedHeaders ?? RETAINED_304_HEADERS;
const weak = options?.weak ?? false;
const generator = initializeGenerator(options?.generateDigest);
return async function etag2(c, next) {
const ifNoneMatch = c.req.header("If-None-Match") ?? null;
await next();
const res = c.res;
let etag3 = res.headers.get("ETag");
if (!etag3) {
if (!generator) {
return;
}
const hash = await generateDigest(
// This type casing avoids the type error for `deno publish`
res.clone().body,
generator
);
if (hash === null) {
return;
}
etag3 = weak ? `W/"${hash}"` : `"${hash}"`;
}
if (etagMatches(etag3, ifNoneMatch)) {
c.res = new Response(null, {
status: 304,
statusText: "Not Modified",
headers: {
ETag: etag3
}
});
c.res.headers.forEach((_, key) => {
if (retainedHeaders.indexOf(key.toLowerCase()) === -1) {
c.res.headers.delete(key);
}
});
} else {
c.res.headers.set("ETag", etag3);
}
};
};
export {
RETAINED_304_HEADERS,
etag
};

View File

@@ -0,0 +1,107 @@
// src/middleware/ip-restriction/index.ts
import { HTTPException } from "../../http-exception.js";
import {
convertIPv4ToBinary,
convertIPv6BinaryToString,
convertIPv6ToBinary,
distinctRemoteAddr
} from "../../utils/ipaddr.js";
var IS_CIDR_NOTATION_REGEX = /\/[0-9]{0,3}$/;
var buildMatcher = (rules) => {
const functionRules = [];
const staticRules = /* @__PURE__ */ new Set();
const cidrRules = [];
for (let rule of rules) {
if (rule === "*") {
return () => true;
} else if (typeof rule === "function") {
functionRules.push(rule);
} else {
if (IS_CIDR_NOTATION_REGEX.test(rule)) {
const separatedRule = rule.split("/");
const addrStr = separatedRule[0];
const type2 = distinctRemoteAddr(addrStr);
if (type2 === void 0) {
throw new TypeError(`Invalid rule: ${rule}`);
}
const isIPv4 = type2 === "IPv4";
const prefix = parseInt(separatedRule[1]);
if (isIPv4 ? prefix === 32 : prefix === 128) {
rule = addrStr;
} else {
const addr = (isIPv4 ? convertIPv4ToBinary : convertIPv6ToBinary)(addrStr);
const mask = (1n << BigInt(prefix)) - 1n << BigInt((isIPv4 ? 32 : 128) - prefix);
cidrRules.push([isIPv4, addr & mask, mask]);
continue;
}
}
const type = distinctRemoteAddr(rule);
if (type === void 0) {
throw new TypeError(`Invalid rule: ${rule}`);
}
staticRules.add(
type === "IPv4" ? rule : convertIPv6BinaryToString(convertIPv6ToBinary(rule))
// normalize IPv6 address (e.g. 0000:0000:0000:0000:0000:0000:0000:0001 => ::1)
);
}
}
return (remote) => {
if (staticRules.has(remote.addr)) {
return true;
}
for (const [isIPv4, addr, mask] of cidrRules) {
if (isIPv4 !== remote.isIPv4) {
continue;
}
const remoteAddr = remote.binaryAddr ||= (isIPv4 ? convertIPv4ToBinary : convertIPv6ToBinary)(remote.addr);
if ((remoteAddr & mask) === addr) {
return true;
}
}
for (const rule of functionRules) {
if (rule({ addr: remote.addr, type: remote.type })) {
return true;
}
}
return false;
};
};
var ipRestriction = (getIP, { denyList = [], allowList = [] }, onError) => {
const allowLength = allowList.length;
const denyMatcher = buildMatcher(denyList);
const allowMatcher = buildMatcher(allowList);
const blockError = (c) => new HTTPException(403, {
res: c.text("Forbidden", {
status: 403
})
});
return async function ipRestriction2(c, next) {
const connInfo = getIP(c);
const addr = typeof connInfo === "string" ? connInfo : connInfo.remote.address;
if (!addr) {
throw blockError(c);
}
const type = typeof connInfo !== "string" && connInfo.remote.addressType || distinctRemoteAddr(addr);
const remoteData = { addr, type, isIPv4: type === "IPv4" };
if (denyMatcher(remoteData)) {
if (onError) {
return onError({ addr, type }, c);
}
throw blockError(c);
}
if (allowMatcher(remoteData)) {
return await next();
}
if (allowLength === 0) {
return await next();
} else {
if (onError) {
return await onError({ addr, type }, c);
}
throw blockError(c);
}
};
};
export {
ipRestriction
};

View File

@@ -0,0 +1,57 @@
// src/middleware/jsx-renderer/index.ts
import { html, raw } from "../../helper/html/index.js";
import { Fragment, createContext, jsx, useContext } from "../../jsx/index.js";
import { renderToReadableStream } from "../../jsx/streaming.js";
var RequestContext = createContext(null);
var createRenderer = (c, Layout, component, options) => (children, props) => {
const docType = typeof options?.docType === "string" ? options.docType : options?.docType === false ? "" : "<!DOCTYPE html>";
const currentLayout = component ? jsx(
(props2) => component(props2, c),
{
Layout,
...props
},
children
) : children;
const body = html`${raw(docType)}${jsx(
RequestContext.Provider,
{ value: c },
currentLayout
)}`;
if (options?.stream) {
if (options.stream === true) {
c.header("Transfer-Encoding", "chunked");
c.header("Content-Type", "text/html; charset=UTF-8");
c.header("Content-Encoding", "Identity");
} else {
for (const [key, value] of Object.entries(options.stream)) {
c.header(key, value);
}
}
return c.body(renderToReadableStream(body));
} else {
return c.html(body);
}
};
var jsxRenderer = (component, options) => function jsxRenderer2(c, next) {
const Layout = c.getLayout() ?? Fragment;
if (component) {
c.setLayout((props) => {
return component({ ...props, Layout }, c);
});
}
c.setRenderer(createRenderer(c, Layout, component, options));
return next();
};
var useRequestContext = () => {
const c = useContext(RequestContext);
if (!c) {
throw new Error("RequestContext is not provided.");
}
return c;
};
export {
RequestContext,
jsxRenderer,
useRequestContext
};

View File

@@ -0,0 +1,5 @@
// src/middleware/jwk/index.ts
import { jwk } from "./jwk.js";
export {
jwk
};

112
backend/node_modules/hono/dist/middleware/jwk/jwk.js generated vendored Normal file
View File

@@ -0,0 +1,112 @@
// src/middleware/jwk/jwk.ts
import { getCookie, getSignedCookie } from "../../helper/cookie/index.js";
import { HTTPException } from "../../http-exception.js";
import { Jwt } from "../../utils/jwt/index.js";
import "../../context.js";
var jwk = (options, init) => {
const verifyOpts = options.verification || {};
if (!options || !(options.keys || options.jwks_uri)) {
throw new Error('JWK auth middleware requires options for either "keys" or "jwks_uri" or both');
}
if (!crypto.subtle || !crypto.subtle.importKey) {
throw new Error("`crypto.subtle.importKey` is undefined. JWK auth middleware requires it.");
}
return async function jwk2(ctx, next) {
const headerName = options.headerName || "Authorization";
const credentials = ctx.req.raw.headers.get(headerName);
let token;
if (credentials) {
const parts = credentials.split(/\s+/);
if (parts.length !== 2) {
const errDescription = "invalid credentials structure";
throw new HTTPException(401, {
message: errDescription,
res: unauthorizedResponse({
ctx,
error: "invalid_request",
errDescription
})
});
} else {
token = parts[1];
}
} else if (options.cookie) {
if (typeof options.cookie == "string") {
token = getCookie(ctx, options.cookie);
} else if (options.cookie.secret) {
if (options.cookie.prefixOptions) {
token = await getSignedCookie(
ctx,
options.cookie.secret,
options.cookie.key,
options.cookie.prefixOptions
);
} else {
token = await getSignedCookie(ctx, options.cookie.secret, options.cookie.key);
}
} else {
if (options.cookie.prefixOptions) {
token = getCookie(ctx, options.cookie.key, options.cookie.prefixOptions);
} else {
token = getCookie(ctx, options.cookie.key);
}
}
}
if (!token) {
if (options.allow_anon) {
return next();
}
const errDescription = "no authorization included in request";
throw new HTTPException(401, {
message: errDescription,
res: unauthorizedResponse({
ctx,
error: "invalid_request",
errDescription
})
});
}
let payload;
let cause;
try {
const keys = typeof options.keys === "function" ? await options.keys(ctx) : options.keys;
const jwks_uri = typeof options.jwks_uri === "function" ? await options.jwks_uri(ctx) : options.jwks_uri;
payload = await Jwt.verifyWithJwks(
token,
{ keys, jwks_uri, verification: verifyOpts, allowedAlgorithms: options.alg },
init
);
} catch (e) {
cause = e;
}
if (!payload) {
if (cause instanceof Error && cause.constructor === Error) {
throw cause;
}
throw new HTTPException(401, {
message: "Unauthorized",
res: unauthorizedResponse({
ctx,
error: "invalid_token",
statusText: "Unauthorized",
errDescription: "token verification failure"
}),
cause
});
}
ctx.set("jwtPayload", payload);
await next();
};
};
function unauthorizedResponse(opts) {
return new Response("Unauthorized", {
status: 401,
statusText: opts.statusText,
headers: {
"WWW-Authenticate": `Bearer realm="${opts.ctx.req.url}",error="${opts.error}",error_description="${opts.errDescription}"`
}
});
}
export {
jwk
};

View File

@@ -0,0 +1,9 @@
// src/middleware/jwt/index.ts
import { jwt, verifyWithJwks, verify, decode, sign } from "./jwt.js";
export {
decode,
jwt,
sign,
verify,
verifyWithJwks
};

114
backend/node_modules/hono/dist/middleware/jwt/jwt.js generated vendored Normal file
View File

@@ -0,0 +1,114 @@
// src/middleware/jwt/jwt.ts
import { getCookie, getSignedCookie } from "../../helper/cookie/index.js";
import { HTTPException } from "../../http-exception.js";
import { Jwt } from "../../utils/jwt/index.js";
import "../../context.js";
var jwt = (options) => {
const verifyOpts = options.verification || {};
if (!options || !options.secret) {
throw new Error('JWT auth middleware requires options for "secret"');
}
if (!options.alg) {
throw new Error('JWT auth middleware requires options for "alg"');
}
if (!crypto.subtle || !crypto.subtle.importKey) {
throw new Error("`crypto.subtle.importKey` is undefined. JWT auth middleware requires it.");
}
return async function jwt2(ctx, next) {
const headerName = options.headerName || "Authorization";
const credentials = ctx.req.raw.headers.get(headerName);
let token;
if (credentials) {
const parts = credentials.split(/\s+/);
if (parts.length !== 2) {
const errDescription = "invalid credentials structure";
throw new HTTPException(401, {
message: errDescription,
res: unauthorizedResponse({
ctx,
error: "invalid_request",
errDescription
})
});
} else {
token = parts[1];
}
} else if (options.cookie) {
if (typeof options.cookie == "string") {
token = getCookie(ctx, options.cookie);
} else if (options.cookie.secret) {
if (options.cookie.prefixOptions) {
token = await getSignedCookie(
ctx,
options.cookie.secret,
options.cookie.key,
options.cookie.prefixOptions
);
} else {
token = await getSignedCookie(ctx, options.cookie.secret, options.cookie.key);
}
} else {
if (options.cookie.prefixOptions) {
token = getCookie(ctx, options.cookie.key, options.cookie.prefixOptions);
} else {
token = getCookie(ctx, options.cookie.key);
}
}
}
if (!token) {
const errDescription = "no authorization included in request";
throw new HTTPException(401, {
message: errDescription,
res: unauthorizedResponse({
ctx,
error: "invalid_request",
errDescription
})
});
}
let payload;
let cause;
try {
payload = await Jwt.verify(token, options.secret, {
alg: options.alg,
...verifyOpts
});
} catch (e) {
cause = e;
}
if (!payload) {
throw new HTTPException(401, {
message: "Unauthorized",
res: unauthorizedResponse({
ctx,
error: "invalid_token",
statusText: "Unauthorized",
errDescription: "token verification failure"
}),
cause
});
}
ctx.set("jwtPayload", payload);
await next();
};
};
function unauthorizedResponse(opts) {
return new Response("Unauthorized", {
status: 401,
statusText: opts.statusText,
headers: {
"WWW-Authenticate": `Bearer realm="${opts.ctx.req.url}",error="${opts.error}",error_description="${opts.errDescription}"`
}
});
}
var verifyWithJwks = Jwt.verifyWithJwks;
var verify = Jwt.verify;
var decode = Jwt.decode;
var sign = Jwt.sign;
export {
decode,
jwt,
sign,
verify,
verifyWithJwks
};

View File

@@ -0,0 +1,15 @@
// src/middleware/language/index.ts
import {
languageDetector,
detectFromCookie,
detectFromHeader,
detectFromPath,
detectFromQuery
} from "./language.js";
export {
detectFromCookie,
detectFromHeader,
detectFromPath,
detectFromQuery,
languageDetector
};

View File

@@ -0,0 +1,179 @@
// src/middleware/language/language.ts
import { setCookie, getCookie } from "../../helper/cookie/index.js";
import { parseAccept } from "../../utils/accept.js";
var DEFAULT_OPTIONS = {
order: ["querystring", "cookie", "header"],
lookupQueryString: "lang",
lookupCookie: "language",
lookupFromHeaderKey: "accept-language",
lookupFromPathIndex: 0,
caches: ["cookie"],
ignoreCase: true,
fallbackLanguage: "en",
supportedLanguages: ["en"],
cookieOptions: {
sameSite: "Strict",
secure: true,
maxAge: 365 * 24 * 60 * 60,
httpOnly: true
},
debug: false
};
function parseAcceptLanguage(header) {
return parseAccept(header).map(({ type, q }) => ({ lang: type, q }));
}
var normalizeLanguage = (lang, options) => {
if (!lang) {
return void 0;
}
try {
let normalizedLang = lang.trim();
if (options.convertDetectedLanguage) {
normalizedLang = options.convertDetectedLanguage(normalizedLang);
}
const compLang = options.ignoreCase ? normalizedLang.toLowerCase() : normalizedLang;
const compSupported = options.supportedLanguages.map(
(l) => options.ignoreCase ? l.toLowerCase() : l
);
const matchedLang = compSupported.find((l) => l === compLang);
return matchedLang ? options.supportedLanguages[compSupported.indexOf(matchedLang)] : void 0;
} catch {
return void 0;
}
};
var detectFromQuery = (c, options) => {
try {
const query = c.req.query(options.lookupQueryString);
return normalizeLanguage(query, options);
} catch {
return void 0;
}
};
var detectFromCookie = (c, options) => {
try {
const cookie = getCookie(c, options.lookupCookie);
return normalizeLanguage(cookie, options);
} catch {
return void 0;
}
};
function detectFromHeader(c, options) {
try {
const acceptLanguage = c.req.header(options.lookupFromHeaderKey);
if (!acceptLanguage) {
return void 0;
}
const languages = parseAcceptLanguage(acceptLanguage);
for (const { lang } of languages) {
const normalizedLang = normalizeLanguage(lang, options);
if (normalizedLang) {
return normalizedLang;
}
}
return void 0;
} catch {
return void 0;
}
}
function detectFromPath(c, options) {
try {
const url = new URL(c.req.url);
const pathSegments = url.pathname.split("/").filter(Boolean);
const langSegment = pathSegments[options.lookupFromPathIndex];
return normalizeLanguage(langSegment, options);
} catch {
return void 0;
}
}
var detectors = {
querystring: detectFromQuery,
cookie: detectFromCookie,
header: detectFromHeader,
path: detectFromPath
};
function validateOptions(options) {
if (!options.supportedLanguages.includes(options.fallbackLanguage)) {
throw new Error("Fallback language must be included in supported languages");
}
if (options.lookupFromPathIndex < 0) {
throw new Error("Path index must be non-negative");
}
if (!options.order.every((detector) => Object.keys(detectors).includes(detector))) {
throw new Error("Invalid detector type in order array");
}
}
function cacheLanguage(c, language, options) {
if (!Array.isArray(options.caches) || !options.caches.includes("cookie")) {
return;
}
try {
setCookie(c, options.lookupCookie, language, options.cookieOptions);
} catch (error) {
if (options.debug) {
console.error("Failed to cache language:", error);
}
}
}
var detectLanguage = (c, options) => {
let detectedLang;
for (const detectorName of options.order) {
const detector = detectors[detectorName];
if (!detector) {
continue;
}
try {
detectedLang = detector(c, options);
if (detectedLang) {
if (options.debug) {
console.log(`Language detected from ${detectorName}: ${detectedLang}`);
}
break;
}
} catch (error) {
if (options.debug) {
console.error(`Error in ${detectorName} detector:`, error);
}
continue;
}
}
const finalLang = detectedLang || options.fallbackLanguage;
if (detectedLang && options.caches) {
cacheLanguage(c, finalLang, options);
}
return finalLang;
};
var languageDetector = (userOptions) => {
const options = {
...DEFAULT_OPTIONS,
...userOptions,
cookieOptions: {
...DEFAULT_OPTIONS.cookieOptions,
...userOptions.cookieOptions
}
};
validateOptions(options);
return async function languageDetector2(ctx, next) {
try {
const lang = detectLanguage(ctx, options);
ctx.set("language", lang);
} catch (error) {
if (options.debug) {
console.error("Language detection failed:", error);
}
ctx.set("language", options.fallbackLanguage);
}
await next();
};
};
export {
DEFAULT_OPTIONS,
detectFromCookie,
detectFromHeader,
detectFromPath,
detectFromQuery,
detectors,
languageDetector,
normalizeLanguage,
parseAcceptLanguage,
validateOptions
};

View File

@@ -0,0 +1,44 @@
// src/middleware/logger/index.ts
import { getColorEnabledAsync } from "../../utils/color.js";
var humanize = (times) => {
const [delimiter, separator] = [",", "."];
const orderTimes = times.map((v) => v.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + delimiter));
return orderTimes.join(separator);
};
var time = (start) => {
const delta = Date.now() - start;
return humanize([delta < 1e3 ? delta + "ms" : Math.round(delta / 1e3) + "s"]);
};
var colorStatus = async (status) => {
const colorEnabled = await getColorEnabledAsync();
if (colorEnabled) {
switch (status / 100 | 0) {
case 5:
return `\x1B[31m${status}\x1B[0m`;
case 4:
return `\x1B[33m${status}\x1B[0m`;
case 3:
return `\x1B[36m${status}\x1B[0m`;
case 2:
return `\x1B[32m${status}\x1B[0m`;
}
}
return `${status}`;
};
async function log(fn, prefix, method, path, status = 0, elapsed) {
const out = prefix === "<--" /* Incoming */ ? `${prefix} ${method} ${path}` : `${prefix} ${method} ${path} ${await colorStatus(status)} ${elapsed}`;
fn(out);
}
var logger = (fn = console.log) => {
return async function logger2(c, next) {
const { method, url } = c.req;
const path = url.slice(url.indexOf("/", 8));
await log(fn, "<--" /* Incoming */, method, path);
const start = Date.now();
await next();
await log(fn, "-->" /* Outgoing */, method, path, c.res.status, time(start));
};
};
export {
logger
};

View File

@@ -0,0 +1,82 @@
// src/middleware/method-override/index.ts
import { parseBody } from "../../utils/body.js";
var DEFAULT_METHOD_FORM_NAME = "_method";
var methodOverride = (options) => async function methodOverride2(c, next) {
if (c.req.method === "GET") {
return await next();
}
const app = options.app;
if (!(options.header || options.query)) {
const contentType = c.req.header("content-type");
const methodFormName = options.form || DEFAULT_METHOD_FORM_NAME;
const clonedRequest = c.req.raw.clone();
const newRequest = clonedRequest.clone();
if (contentType?.startsWith("multipart/form-data")) {
const form = await clonedRequest.formData();
const method = form.get(methodFormName);
if (method) {
const newForm = await newRequest.formData();
newForm.delete(methodFormName);
const newHeaders = new Headers(clonedRequest.headers);
newHeaders.delete("content-type");
newHeaders.delete("content-length");
const request = new Request(c.req.url, {
body: newForm,
headers: newHeaders,
method
});
return app.fetch(request, c.env, getExecutionCtx(c));
}
}
if (contentType === "application/x-www-form-urlencoded") {
const params = await parseBody(clonedRequest);
const method = params[methodFormName];
if (method) {
delete params[methodFormName];
const newParams = new URLSearchParams(params);
const request = new Request(newRequest, {
body: newParams,
method
});
return app.fetch(request, c.env, getExecutionCtx(c));
}
}
} else if (options.header) {
const headerName = options.header;
const method = c.req.header(headerName);
if (method) {
const newHeaders = new Headers(c.req.raw.headers);
newHeaders.delete(headerName);
const request = new Request(c.req.raw, {
headers: newHeaders,
method
});
return app.fetch(request, c.env, getExecutionCtx(c));
}
} else if (options.query) {
const queryName = options.query;
const method = c.req.query(queryName);
if (method) {
const url = new URL(c.req.url);
url.searchParams.delete(queryName);
const request = new Request(url.toString(), {
body: c.req.raw.body,
headers: c.req.raw.headers,
method
});
return app.fetch(request, c.env, getExecutionCtx(c));
}
}
await next();
};
var getExecutionCtx = (c) => {
let executionCtx;
try {
executionCtx = c.executionCtx;
} catch {
}
return executionCtx;
};
export {
methodOverride
};

View File

@@ -0,0 +1,10 @@
// src/middleware/powered-by/index.ts
var poweredBy = (options) => {
return async function poweredBy2(c, next) {
await next();
c.res.headers.set("X-Powered-By", options?.serverName ?? "Hono");
};
};
export {
poweredBy
};

View File

@@ -0,0 +1,15 @@
// src/middleware/pretty-json/index.ts
var prettyJSON = (options) => {
const targetQuery = options?.query ?? "pretty";
return async function prettyJSON2(c, next) {
const pretty = options?.force || c.req.query(targetQuery) || c.req.query(targetQuery) === "";
await next();
if (pretty && c.res.headers.get("Content-Type")?.startsWith("application/json")) {
const obj = await c.res.json();
c.res = new Response(JSON.stringify(obj, null, options?.space ?? 2), c.res);
}
};
};
export {
prettyJSON
};

View File

@@ -0,0 +1,5 @@
// src/middleware/request-id/index.ts
import { requestId } from "./request-id.js";
export {
requestId
};

View File

@@ -0,0 +1,21 @@
// src/middleware/request-id/request-id.ts
var requestId = ({
limitLength = 255,
headerName = "X-Request-Id",
generator = () => crypto.randomUUID()
} = {}) => {
return async function requestId2(c, next) {
let reqId = headerName ? c.req.header(headerName) : void 0;
if (!reqId || reqId.length > limitLength || /[^\w\-=]/.test(reqId)) {
reqId = generator(c);
}
c.set("requestId", reqId);
if (headerName) {
c.header(headerName, reqId);
}
await next();
};
};
export {
requestId
};

View File

@@ -0,0 +1,6 @@
// src/middleware/secure-headers/index.ts
import { NONCE, secureHeaders } from "./secure-headers.js";
export {
NONCE,
secureHeaders
};

View File

@@ -0,0 +1,166 @@
// src/middleware/secure-headers/secure-headers.ts
import { encodeBase64 } from "../../utils/encode.js";
var HEADERS_MAP = {
crossOriginEmbedderPolicy: ["Cross-Origin-Embedder-Policy", "require-corp"],
crossOriginResourcePolicy: ["Cross-Origin-Resource-Policy", "same-origin"],
crossOriginOpenerPolicy: ["Cross-Origin-Opener-Policy", "same-origin"],
originAgentCluster: ["Origin-Agent-Cluster", "?1"],
referrerPolicy: ["Referrer-Policy", "no-referrer"],
strictTransportSecurity: ["Strict-Transport-Security", "max-age=15552000; includeSubDomains"],
xContentTypeOptions: ["X-Content-Type-Options", "nosniff"],
xDnsPrefetchControl: ["X-DNS-Prefetch-Control", "off"],
xDownloadOptions: ["X-Download-Options", "noopen"],
xFrameOptions: ["X-Frame-Options", "SAMEORIGIN"],
xPermittedCrossDomainPolicies: ["X-Permitted-Cross-Domain-Policies", "none"],
xXssProtection: ["X-XSS-Protection", "0"]
};
var DEFAULT_OPTIONS = {
crossOriginEmbedderPolicy: false,
crossOriginResourcePolicy: true,
crossOriginOpenerPolicy: true,
originAgentCluster: true,
referrerPolicy: true,
strictTransportSecurity: true,
xContentTypeOptions: true,
xDnsPrefetchControl: true,
xDownloadOptions: true,
xFrameOptions: true,
xPermittedCrossDomainPolicies: true,
xXssProtection: true,
removePoweredBy: true,
permissionsPolicy: {}
};
var generateNonce = () => {
const arrayBuffer = new Uint8Array(16);
crypto.getRandomValues(arrayBuffer);
return encodeBase64(arrayBuffer.buffer);
};
var NONCE = (ctx) => {
const key = "secureHeadersNonce";
const init = ctx.get(key);
const nonce = init || generateNonce();
if (init == null) {
ctx.set(key, nonce);
}
return `'nonce-${nonce}'`;
};
var secureHeaders = (customOptions) => {
const options = { ...DEFAULT_OPTIONS, ...customOptions };
const headersToSet = getFilteredHeaders(options);
const callbacks = [];
if (options.contentSecurityPolicy) {
const [callback, value] = getCSPDirectives(options.contentSecurityPolicy);
if (callback) {
callbacks.push(callback);
}
headersToSet.push(["Content-Security-Policy", value]);
}
if (options.contentSecurityPolicyReportOnly) {
const [callback, value] = getCSPDirectives(options.contentSecurityPolicyReportOnly);
if (callback) {
callbacks.push(callback);
}
headersToSet.push(["Content-Security-Policy-Report-Only", value]);
}
if (options.permissionsPolicy && Object.keys(options.permissionsPolicy).length > 0) {
headersToSet.push([
"Permissions-Policy",
getPermissionsPolicyDirectives(options.permissionsPolicy)
]);
}
if (options.reportingEndpoints) {
headersToSet.push(["Reporting-Endpoints", getReportingEndpoints(options.reportingEndpoints)]);
}
if (options.reportTo) {
headersToSet.push(["Report-To", getReportToOptions(options.reportTo)]);
}
return async function secureHeaders2(ctx, next) {
const headersToSetForReq = callbacks.length === 0 ? headersToSet : callbacks.reduce((acc, cb) => cb(ctx, acc), headersToSet);
await next();
setHeaders(ctx, headersToSetForReq);
if (options?.removePoweredBy) {
ctx.res.headers.delete("X-Powered-By");
}
};
};
function getFilteredHeaders(options) {
return Object.entries(HEADERS_MAP).filter(([key]) => options[key]).map(([key, defaultValue]) => {
const overrideValue = options[key];
return typeof overrideValue === "string" ? [defaultValue[0], overrideValue] : defaultValue;
});
}
function getCSPDirectives(contentSecurityPolicy) {
const callbacks = [];
const resultValues = [];
for (const [directive, value] of Object.entries(contentSecurityPolicy)) {
const valueArray = Array.isArray(value) ? value : [value];
valueArray.forEach((value2, i) => {
if (typeof value2 === "function") {
const index = i * 2 + 2 + resultValues.length;
callbacks.push((ctx, values) => {
values[index] = value2(ctx, directive);
});
}
});
resultValues.push(
directive.replace(
/[A-Z]+(?![a-z])|[A-Z]/g,
(match, offset) => offset ? "-" + match.toLowerCase() : match.toLowerCase()
),
...valueArray.flatMap((value2) => [" ", value2]),
"; "
);
}
resultValues.pop();
return callbacks.length === 0 ? [void 0, resultValues.join("")] : [
(ctx, headersToSet) => headersToSet.map((values) => {
if (values[0] === "Content-Security-Policy" || values[0] === "Content-Security-Policy-Report-Only") {
const clone = values[1].slice();
callbacks.forEach((cb) => {
cb(ctx, clone);
});
return [values[0], clone.join("")];
} else {
return values;
}
}),
resultValues
];
}
function getPermissionsPolicyDirectives(policy) {
return Object.entries(policy).map(([directive, value]) => {
const kebabDirective = camelToKebab(directive);
if (typeof value === "boolean") {
return `${kebabDirective}=${value ? "*" : "none"}`;
}
if (Array.isArray(value)) {
if (value.length === 0) {
return `${kebabDirective}=()`;
}
if (value.length === 1 && (value[0] === "*" || value[0] === "none")) {
return `${kebabDirective}=${value[0]}`;
}
const allowlist = value.map((item) => ["self", "src"].includes(item) ? item : `"${item}"`);
return `${kebabDirective}=(${allowlist.join(" ")})`;
}
return "";
}).filter(Boolean).join(", ");
}
function camelToKebab(str) {
return str.replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
}
function getReportingEndpoints(reportingEndpoints = []) {
return reportingEndpoints.map((endpoint) => `${endpoint.name}="${endpoint.url}"`).join(", ");
}
function getReportToOptions(reportTo = []) {
return reportTo.map((option) => JSON.stringify(option)).join(", ");
}
function setHeaders(ctx, headersToSet) {
headersToSet.forEach(([header, value]) => {
ctx.res.headers.set(header, value);
});
}
export {
NONCE,
secureHeaders
};

View File

@@ -0,0 +1,76 @@
// src/middleware/serve-static/index.ts
import { COMPRESSIBLE_CONTENT_TYPE_REGEX } from "../../utils/compress.js";
import { getMimeType } from "../../utils/mime.js";
import { defaultJoin } from "./path.js";
var ENCODINGS = {
br: ".br",
zstd: ".zst",
gzip: ".gz"
};
var ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
var DEFAULT_DOCUMENT = "index.html";
var serveStatic = (options) => {
const root = options.root ?? "./";
const optionPath = options.path;
const join = options.join ?? defaultJoin;
return async (c, next) => {
if (c.finalized) {
return next();
}
let filename;
if (options.path) {
filename = options.path;
} else {
try {
filename = decodeURIComponent(c.req.path);
if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) {
throw new Error();
}
} catch {
await options.onNotFound?.(c.req.path, c);
return next();
}
}
let path = join(
root,
!optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename
);
if (options.isDir && await options.isDir(path)) {
path = join(path, DEFAULT_DOCUMENT);
}
const getContent = options.getContent;
let content = await getContent(path, c);
if (content instanceof Response) {
return c.newResponse(content.body, content);
}
if (content) {
const mimeType = options.mimes && getMimeType(path, options.mimes) || getMimeType(path);
c.header("Content-Type", mimeType || "application/octet-stream");
if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
const acceptEncodingSet = new Set(
c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim())
);
for (const encoding of ENCODINGS_ORDERED_KEYS) {
if (!acceptEncodingSet.has(encoding)) {
continue;
}
const compressedContent = await getContent(path + ENCODINGS[encoding], c);
if (compressedContent) {
content = compressedContent;
c.header("Content-Encoding", encoding);
c.header("Vary", "Accept-Encoding", { append: true });
break;
}
}
}
await options.onFound?.(path, c);
return c.body(content);
}
await options.onNotFound?.(path, c);
await next();
return;
};
};
export {
serveStatic
};

View File

@@ -0,0 +1,18 @@
// src/middleware/serve-static/path.ts
var defaultJoin = (...paths) => {
let result = paths.filter((p) => p !== "").join("/");
result = result.replace(/(?<=\/)\/+/g, "");
const segments = result.split("/");
const resolved = [];
for (const segment of segments) {
if (segment === ".." && resolved.length > 0 && resolved.at(-1) !== "..") {
resolved.pop();
} else if (segment !== ".") {
resolved.push(segment);
}
}
return resolved.join("/") || ".";
};
export {
defaultJoin
};

View File

@@ -0,0 +1,25 @@
// src/middleware/timeout/index.ts
import { HTTPException } from "../../http-exception.js";
var defaultTimeoutException = new HTTPException(504, {
message: "Gateway Timeout"
});
var timeout = (duration, exception = defaultTimeoutException) => {
return async function timeout2(context, next) {
let timer;
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => {
reject(typeof exception === "function" ? exception(context) : exception);
}, duration);
});
try {
await Promise.race([next(), timeoutPromise]);
} finally {
if (timer !== void 0) {
clearTimeout(timer);
}
}
};
};
export {
timeout
};

View File

@@ -0,0 +1,9 @@
// src/middleware/timing/index.ts
import { timing, setMetric, startTime, endTime, wrapTime } from "./timing.js";
export {
endTime,
setMetric,
startTime,
timing,
wrapTime
};

View File

@@ -0,0 +1,102 @@
// src/middleware/timing/timing.ts
import "../../context.js";
var getTime = () => {
try {
return performance.now();
} catch {
}
return Date.now();
};
var timing = (config) => {
const options = {
total: true,
enabled: true,
totalDescription: "Total Response Time",
autoEnd: true,
crossOrigin: false,
...config
};
return async function timing2(c, next) {
const headers = [];
const timers = /* @__PURE__ */ new Map();
if (c.get("metric")) {
return await next();
}
c.set("metric", { headers, timers });
if (options.total) {
startTime(c, "total", options.totalDescription);
}
await next();
if (options.total) {
endTime(c, "total");
}
if (options.autoEnd) {
timers.forEach((_, key) => endTime(c, key));
}
const enabled = typeof options.enabled === "function" ? options.enabled(c) : options.enabled;
if (enabled) {
c.res.headers.append("Server-Timing", headers.join(","));
const crossOrigin = typeof options.crossOrigin === "function" ? options.crossOrigin(c) : options.crossOrigin;
if (crossOrigin) {
c.res.headers.append(
"Timing-Allow-Origin",
typeof crossOrigin === "string" ? crossOrigin : "*"
);
}
}
};
};
var setMetric = (c, name, valueDescription, description, precision) => {
const metrics = c.get("metric");
if (!metrics) {
console.warn("Metrics not initialized! Please add the `timing()` middleware to this route!");
return;
}
if (typeof valueDescription === "number") {
const dur = valueDescription.toFixed(precision || 1);
const metric = description ? `${name};dur=${dur};desc="${description}"` : `${name};dur=${dur}`;
metrics.headers.push(metric);
} else {
const metric = valueDescription ? `${name};desc="${valueDescription}"` : `${name}`;
metrics.headers.push(metric);
}
};
var startTime = (c, name, description) => {
const metrics = c.get("metric");
if (!metrics) {
console.warn("Metrics not initialized! Please add the `timing()` middleware to this route!");
return;
}
metrics.timers.set(name, { description, start: getTime() });
};
var endTime = (c, name, precision) => {
const metrics = c.get("metric");
if (!metrics) {
console.warn("Metrics not initialized! Please add the `timing()` middleware to this route!");
return;
}
const timer = metrics.timers.get(name);
if (!timer) {
console.warn(`Timer "${name}" does not exist!`);
return;
}
const { description, start } = timer;
const duration = getTime() - start;
setMetric(c, name, duration, description, precision);
metrics.timers.delete(name);
};
async function wrapTime(c, name, callable, description, precision) {
startTime(c, name, description);
try {
return await callable;
} finally {
endTime(c, name, precision);
}
}
export {
endTime,
setMetric,
startTime,
timing,
wrapTime
};

View File

@@ -0,0 +1,25 @@
// src/middleware/trailing-slash/index.ts
var trimTrailingSlash = () => {
return async function trimTrailingSlash2(c, next) {
await next();
if (c.res.status === 404 && (c.req.method === "GET" || c.req.method === "HEAD") && c.req.path !== "/" && c.req.path.at(-1) === "/") {
const url = new URL(c.req.url);
url.pathname = url.pathname.substring(0, url.pathname.length - 1);
c.res = c.redirect(url.toString(), 301);
}
};
};
var appendTrailingSlash = () => {
return async function appendTrailingSlash2(c, next) {
await next();
if (c.res.status === 404 && (c.req.method === "GET" || c.req.method === "HEAD") && c.req.path.at(-1) !== "/") {
const url = new URL(c.req.url);
url.pathname += "/";
c.res = c.redirect(url.toString(), 301);
}
};
};
export {
appendTrailingSlash,
trimTrailingSlash
};