import { isAbsolute, join, normalize, sep, Sha1, Status } from "./deps.ts";import { createHttpError } from "./httpError.ts";import type { ErrorStatus, RedirectStatus } from "./types.d.ts";
const ENCODE_CHARS_REGEXP = /(?:[^\x21\x25\x26-\x3B\x3D\x3F-\x5B\x5D\x5F\x61-\x7A\x7E]|%(?:[^0-9A-Fa-f]|[0-9A-Fa-f][^0-9A-Fa-f]|$))+/g;const HTAB = "\t".charCodeAt(0);const SPACE = " ".charCodeAt(0);const CR = "\r".charCodeAt(0);const LF = "\n".charCodeAt(0);const UNMATCHED_SURROGATE_PAIR_REGEXP = /(^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF]([^\uDC00-\uDFFF]|$)/g;const UNMATCHED_SURROGATE_PAIR_REPLACE = "$1\uFFFD$2";const DEFAULT_CHUNK_SIZE = 16_640;
export const BODY_TYPES = ["string", "number", "bigint", "boolean", "symbol"];
export function decodeComponent(text: string) { try { return decodeURIComponent(text); } catch { return text; }}
export function encodeUrl(url: string) { return String(url) .replace(UNMATCHED_SURROGATE_PAIR_REGEXP, UNMATCHED_SURROGATE_PAIR_REPLACE) .replace(ENCODE_CHARS_REGEXP, encodeURI);}
export function getRandomFilename(prefix = "", extension = ""): string { return `${prefix}${ new Sha1().update(crypto.getRandomValues(new Uint8Array(256))).hex() }${extension ? `.${extension}` : ""}`;}
export function isAsyncIterable(value: unknown): value is AsyncIterable<any> { return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof (value as any)[Symbol.asyncIterator] === "function";}
export function isReader(value: unknown): value is Deno.Reader { return typeof value === "object" && value !== null && "read" in value && typeof (value as Record<string, unknown>).read === "function";}
function isCloser(value: unknown): value is Deno.Closer { return typeof value === "object" && value != null && "close" in value && typeof (value as Record<string, any>)["close"] === "function";}
export function isConn(value: unknown): value is Deno.Conn<Deno.NetAddr> { return typeof value === "object" && value != null && "rid" in value && typeof (value as any).rid === "number" && "localAddr" in value && "remoteAddr" in value;}
export function isListenTlsOptions( value: unknown,): value is Deno.ListenTlsOptions { return typeof value === "object" && value !== null && "certFile" in value && "keyFile" in value && "port" in value;}
export interface ReadableStreamFromReaderOptions { autoClose?: boolean;
chunkSize?: number;
strategy?: { highWaterMark?: number | undefined; size?: undefined };}
export function readableStreamFromReader( reader: Deno.Reader | (Deno.Reader & Deno.Closer), options: ReadableStreamFromReaderOptions = {},): ReadableStream<Uint8Array> { const { autoClose = true, chunkSize = DEFAULT_CHUNK_SIZE, strategy, } = options;
return new ReadableStream({ async pull(controller) { const chunk = new Uint8Array(chunkSize); try { const read = await reader.read(chunk); if (read === null) { if (isCloser(reader) && autoClose) { reader.close(); } controller.close(); return; } controller.enqueue(chunk.subarray(0, read)); } catch (e) { controller.error(e); if (isCloser(reader)) { reader.close(); } } }, cancel() { if (isCloser(reader) && autoClose) { reader.close(); } }, type: "bytes", }, strategy);}
export function isErrorStatus(value: Status): value is ErrorStatus { return [ Status.BadRequest, Status.Unauthorized, Status.PaymentRequired, Status.Forbidden, Status.NotFound, Status.MethodNotAllowed, Status.NotAcceptable, Status.ProxyAuthRequired, Status.RequestTimeout, Status.Conflict, Status.Gone, Status.LengthRequired, Status.PreconditionFailed, Status.RequestEntityTooLarge, Status.RequestURITooLong, Status.UnsupportedMediaType, Status.RequestedRangeNotSatisfiable, Status.ExpectationFailed, Status.Teapot, Status.MisdirectedRequest, Status.UnprocessableEntity, Status.Locked, Status.FailedDependency, Status.UpgradeRequired, Status.PreconditionRequired, Status.TooManyRequests, Status.RequestHeaderFieldsTooLarge, Status.UnavailableForLegalReasons, Status.InternalServerError, Status.NotImplemented, Status.BadGateway, Status.ServiceUnavailable, Status.GatewayTimeout, Status.HTTPVersionNotSupported, Status.VariantAlsoNegotiates, Status.InsufficientStorage, Status.LoopDetected, Status.NotExtended, Status.NetworkAuthenticationRequired, ].includes(value);}
export function isRedirectStatus(value: Status): value is RedirectStatus { return [ Status.MultipleChoices, Status.MovedPermanently, Status.Found, Status.SeeOther, Status.UseProxy, Status.TemporaryRedirect, Status.PermanentRedirect, ].includes(value);}
export function isHtml(value: string): boolean { return /^\s*<(?:!DOCTYPE|html|body)/i.test(value);}
export function skipLWSPChar(u8: Uint8Array): Uint8Array { const result = new Uint8Array(u8.length); let j = 0; for (let i = 0; i < u8.length; i++) { if (u8[i] === SPACE || u8[i] === HTAB) continue; result[j++] = u8[i]; } return result.slice(0, j);}
export function stripEol(value: Uint8Array): Uint8Array { if (value[value.byteLength - 1] == LF) { let drop = 1; if (value.byteLength > 1 && value[value.byteLength - 2] === CR) { drop = 2; } return value.subarray(0, value.byteLength - drop); } return value;}
const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
export function resolvePath(relativePath: string): string;export function resolvePath(rootPath: string, relativePath: string): string;export function resolvePath(rootPath: string, relativePath?: string): string { let path = relativePath; let root = rootPath;
if (relativePath === undefined) { path = rootPath; root = "."; }
if (path == null) { throw new TypeError("Argument relativePath is required."); }
if (path.includes("\0")) { throw createHttpError(400, "Malicious Path"); }
if (isAbsolute(path)) { throw createHttpError(400, "Malicious Path"); }
if (UP_PATH_REGEXP.test(normalize("." + sep + path))) { throw createHttpError(403); }
return normalize(join(root, path));}