webbutv-m2/build/handler.js
2023-09-20 13:03:37 +02:00

1308 lines
35 KiB
JavaScript

import './shims.js';
import fs$1 from 'node:fs';
import path from 'node:path';
import * as fs from 'fs';
import { readdirSync, statSync } from 'fs';
import { resolve, join, normalize } from 'path';
import * as qs from 'querystring';
import { fileURLToPath } from 'node:url';
import { Server } from './server/index.js';
import { manifest, prerendered } from './server/manifest.js';
import { env } from './env.js';
function totalist(dir, callback, pre='') {
dir = resolve('.', dir);
let arr = readdirSync(dir);
let i=0, abs, stats;
for (; i < arr.length; i++) {
abs = join(dir, arr[i]);
stats = statSync(abs);
stats.isDirectory()
? totalist(abs, callback, join(pre, arr[i]))
: callback(join(pre, arr[i]), abs, stats);
}
}
/**
* @typedef ParsedURL
* @type {import('.').ParsedURL}
*/
/**
* @typedef Request
* @property {string} url
* @property {ParsedURL} _parsedUrl
*/
/**
* @param {Request} req
* @returns {ParsedURL|void}
*/
function parse$1(req) {
let raw = req.url;
if (raw == null) return;
let prev = req._parsedUrl;
if (prev && prev.raw === raw) return prev;
let pathname=raw, search='', query;
if (raw.length > 1) {
let idx = raw.indexOf('?', 1);
if (idx !== -1) {
search = raw.substring(idx);
pathname = raw.substring(0, idx);
if (search.length > 1) {
query = qs.parse(search.substring(1));
}
}
}
return req._parsedUrl = { pathname, search, query, raw };
}
const mimes = {
"ez": "application/andrew-inset",
"aw": "application/applixware",
"atom": "application/atom+xml",
"atomcat": "application/atomcat+xml",
"atomdeleted": "application/atomdeleted+xml",
"atomsvc": "application/atomsvc+xml",
"dwd": "application/atsc-dwd+xml",
"held": "application/atsc-held+xml",
"rsat": "application/atsc-rsat+xml",
"bdoc": "application/bdoc",
"xcs": "application/calendar+xml",
"ccxml": "application/ccxml+xml",
"cdfx": "application/cdfx+xml",
"cdmia": "application/cdmi-capability",
"cdmic": "application/cdmi-container",
"cdmid": "application/cdmi-domain",
"cdmio": "application/cdmi-object",
"cdmiq": "application/cdmi-queue",
"cu": "application/cu-seeme",
"mpd": "application/dash+xml",
"davmount": "application/davmount+xml",
"dbk": "application/docbook+xml",
"dssc": "application/dssc+der",
"xdssc": "application/dssc+xml",
"es": "application/ecmascript",
"ecma": "application/ecmascript",
"emma": "application/emma+xml",
"emotionml": "application/emotionml+xml",
"epub": "application/epub+zip",
"exi": "application/exi",
"fdt": "application/fdt+xml",
"pfr": "application/font-tdpfr",
"geojson": "application/geo+json",
"gml": "application/gml+xml",
"gpx": "application/gpx+xml",
"gxf": "application/gxf",
"gz": "application/gzip",
"hjson": "application/hjson",
"stk": "application/hyperstudio",
"ink": "application/inkml+xml",
"inkml": "application/inkml+xml",
"ipfix": "application/ipfix",
"its": "application/its+xml",
"jar": "application/java-archive",
"war": "application/java-archive",
"ear": "application/java-archive",
"ser": "application/java-serialized-object",
"class": "application/java-vm",
"js": "application/javascript",
"mjs": "application/javascript",
"json": "application/json",
"map": "application/json",
"json5": "application/json5",
"jsonml": "application/jsonml+json",
"jsonld": "application/ld+json",
"lgr": "application/lgr+xml",
"lostxml": "application/lost+xml",
"hqx": "application/mac-binhex40",
"cpt": "application/mac-compactpro",
"mads": "application/mads+xml",
"webmanifest": "application/manifest+json",
"mrc": "application/marc",
"mrcx": "application/marcxml+xml",
"ma": "application/mathematica",
"nb": "application/mathematica",
"mb": "application/mathematica",
"mathml": "application/mathml+xml",
"mbox": "application/mbox",
"mscml": "application/mediaservercontrol+xml",
"metalink": "application/metalink+xml",
"meta4": "application/metalink4+xml",
"mets": "application/mets+xml",
"maei": "application/mmt-aei+xml",
"musd": "application/mmt-usd+xml",
"mods": "application/mods+xml",
"m21": "application/mp21",
"mp21": "application/mp21",
"mp4s": "application/mp4",
"m4p": "application/mp4",
"doc": "application/msword",
"dot": "application/msword",
"mxf": "application/mxf",
"nq": "application/n-quads",
"nt": "application/n-triples",
"cjs": "application/node",
"bin": "application/octet-stream",
"dms": "application/octet-stream",
"lrf": "application/octet-stream",
"mar": "application/octet-stream",
"so": "application/octet-stream",
"dist": "application/octet-stream",
"distz": "application/octet-stream",
"pkg": "application/octet-stream",
"bpk": "application/octet-stream",
"dump": "application/octet-stream",
"elc": "application/octet-stream",
"deploy": "application/octet-stream",
"exe": "application/octet-stream",
"dll": "application/octet-stream",
"deb": "application/octet-stream",
"dmg": "application/octet-stream",
"iso": "application/octet-stream",
"img": "application/octet-stream",
"msi": "application/octet-stream",
"msp": "application/octet-stream",
"msm": "application/octet-stream",
"buffer": "application/octet-stream",
"oda": "application/oda",
"opf": "application/oebps-package+xml",
"ogx": "application/ogg",
"omdoc": "application/omdoc+xml",
"onetoc": "application/onenote",
"onetoc2": "application/onenote",
"onetmp": "application/onenote",
"onepkg": "application/onenote",
"oxps": "application/oxps",
"relo": "application/p2p-overlay+xml",
"xer": "application/patch-ops-error+xml",
"pdf": "application/pdf",
"pgp": "application/pgp-encrypted",
"asc": "application/pgp-signature",
"sig": "application/pgp-signature",
"prf": "application/pics-rules",
"p10": "application/pkcs10",
"p7m": "application/pkcs7-mime",
"p7c": "application/pkcs7-mime",
"p7s": "application/pkcs7-signature",
"p8": "application/pkcs8",
"ac": "application/pkix-attr-cert",
"cer": "application/pkix-cert",
"crl": "application/pkix-crl",
"pkipath": "application/pkix-pkipath",
"pki": "application/pkixcmp",
"pls": "application/pls+xml",
"ai": "application/postscript",
"eps": "application/postscript",
"ps": "application/postscript",
"provx": "application/provenance+xml",
"cww": "application/prs.cww",
"pskcxml": "application/pskc+xml",
"raml": "application/raml+yaml",
"rdf": "application/rdf+xml",
"owl": "application/rdf+xml",
"rif": "application/reginfo+xml",
"rnc": "application/relax-ng-compact-syntax",
"rl": "application/resource-lists+xml",
"rld": "application/resource-lists-diff+xml",
"rs": "application/rls-services+xml",
"rapd": "application/route-apd+xml",
"sls": "application/route-s-tsid+xml",
"rusd": "application/route-usd+xml",
"gbr": "application/rpki-ghostbusters",
"mft": "application/rpki-manifest",
"roa": "application/rpki-roa",
"rsd": "application/rsd+xml",
"rss": "application/rss+xml",
"rtf": "application/rtf",
"sbml": "application/sbml+xml",
"scq": "application/scvp-cv-request",
"scs": "application/scvp-cv-response",
"spq": "application/scvp-vp-request",
"spp": "application/scvp-vp-response",
"sdp": "application/sdp",
"senmlx": "application/senml+xml",
"sensmlx": "application/sensml+xml",
"setpay": "application/set-payment-initiation",
"setreg": "application/set-registration-initiation",
"shf": "application/shf+xml",
"siv": "application/sieve",
"sieve": "application/sieve",
"smi": "application/smil+xml",
"smil": "application/smil+xml",
"rq": "application/sparql-query",
"srx": "application/sparql-results+xml",
"gram": "application/srgs",
"grxml": "application/srgs+xml",
"sru": "application/sru+xml",
"ssdl": "application/ssdl+xml",
"ssml": "application/ssml+xml",
"swidtag": "application/swid+xml",
"tei": "application/tei+xml",
"teicorpus": "application/tei+xml",
"tfi": "application/thraud+xml",
"tsd": "application/timestamped-data",
"toml": "application/toml",
"trig": "application/trig",
"ttml": "application/ttml+xml",
"ubj": "application/ubjson",
"rsheet": "application/urc-ressheet+xml",
"td": "application/urc-targetdesc+xml",
"vxml": "application/voicexml+xml",
"wasm": "application/wasm",
"wgt": "application/widget",
"hlp": "application/winhlp",
"wsdl": "application/wsdl+xml",
"wspolicy": "application/wspolicy+xml",
"xaml": "application/xaml+xml",
"xav": "application/xcap-att+xml",
"xca": "application/xcap-caps+xml",
"xdf": "application/xcap-diff+xml",
"xel": "application/xcap-el+xml",
"xns": "application/xcap-ns+xml",
"xenc": "application/xenc+xml",
"xhtml": "application/xhtml+xml",
"xht": "application/xhtml+xml",
"xlf": "application/xliff+xml",
"xml": "application/xml",
"xsl": "application/xml",
"xsd": "application/xml",
"rng": "application/xml",
"dtd": "application/xml-dtd",
"xop": "application/xop+xml",
"xpl": "application/xproc+xml",
"xslt": "application/xml",
"xspf": "application/xspf+xml",
"mxml": "application/xv+xml",
"xhvml": "application/xv+xml",
"xvml": "application/xv+xml",
"xvm": "application/xv+xml",
"yang": "application/yang",
"yin": "application/yin+xml",
"zip": "application/zip",
"3gpp": "video/3gpp",
"adp": "audio/adpcm",
"amr": "audio/amr",
"au": "audio/basic",
"snd": "audio/basic",
"mid": "audio/midi",
"midi": "audio/midi",
"kar": "audio/midi",
"rmi": "audio/midi",
"mxmf": "audio/mobile-xmf",
"mp3": "audio/mpeg",
"m4a": "audio/mp4",
"mp4a": "audio/mp4",
"mpga": "audio/mpeg",
"mp2": "audio/mpeg",
"mp2a": "audio/mpeg",
"m2a": "audio/mpeg",
"m3a": "audio/mpeg",
"oga": "audio/ogg",
"ogg": "audio/ogg",
"spx": "audio/ogg",
"opus": "audio/ogg",
"s3m": "audio/s3m",
"sil": "audio/silk",
"wav": "audio/wav",
"weba": "audio/webm",
"xm": "audio/xm",
"ttc": "font/collection",
"otf": "font/otf",
"ttf": "font/ttf",
"woff": "font/woff",
"woff2": "font/woff2",
"exr": "image/aces",
"apng": "image/apng",
"avif": "image/avif",
"bmp": "image/bmp",
"cgm": "image/cgm",
"drle": "image/dicom-rle",
"emf": "image/emf",
"fits": "image/fits",
"g3": "image/g3fax",
"gif": "image/gif",
"heic": "image/heic",
"heics": "image/heic-sequence",
"heif": "image/heif",
"heifs": "image/heif-sequence",
"hej2": "image/hej2k",
"hsj2": "image/hsj2",
"ief": "image/ief",
"jls": "image/jls",
"jp2": "image/jp2",
"jpg2": "image/jp2",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"jpe": "image/jpeg",
"jph": "image/jph",
"jhc": "image/jphc",
"jpm": "image/jpm",
"jpx": "image/jpx",
"jpf": "image/jpx",
"jxr": "image/jxr",
"jxra": "image/jxra",
"jxrs": "image/jxrs",
"jxs": "image/jxs",
"jxsc": "image/jxsc",
"jxsi": "image/jxsi",
"jxss": "image/jxss",
"ktx": "image/ktx",
"ktx2": "image/ktx2",
"png": "image/png",
"btif": "image/prs.btif",
"pti": "image/prs.pti",
"sgi": "image/sgi",
"svg": "image/svg+xml",
"svgz": "image/svg+xml",
"t38": "image/t38",
"tif": "image/tiff",
"tiff": "image/tiff",
"tfx": "image/tiff-fx",
"webp": "image/webp",
"wmf": "image/wmf",
"disposition-notification": "message/disposition-notification",
"u8msg": "message/global",
"u8dsn": "message/global-delivery-status",
"u8mdn": "message/global-disposition-notification",
"u8hdr": "message/global-headers",
"eml": "message/rfc822",
"mime": "message/rfc822",
"3mf": "model/3mf",
"gltf": "model/gltf+json",
"glb": "model/gltf-binary",
"igs": "model/iges",
"iges": "model/iges",
"msh": "model/mesh",
"mesh": "model/mesh",
"silo": "model/mesh",
"mtl": "model/mtl",
"obj": "model/obj",
"stpz": "model/step+zip",
"stpxz": "model/step-xml+zip",
"stl": "model/stl",
"wrl": "model/vrml",
"vrml": "model/vrml",
"x3db": "model/x3d+fastinfoset",
"x3dbz": "model/x3d+binary",
"x3dv": "model/x3d-vrml",
"x3dvz": "model/x3d+vrml",
"x3d": "model/x3d+xml",
"x3dz": "model/x3d+xml",
"appcache": "text/cache-manifest",
"manifest": "text/cache-manifest",
"ics": "text/calendar",
"ifb": "text/calendar",
"coffee": "text/coffeescript",
"litcoffee": "text/coffeescript",
"css": "text/css",
"csv": "text/csv",
"html": "text/html",
"htm": "text/html",
"shtml": "text/html",
"jade": "text/jade",
"jsx": "text/jsx",
"less": "text/less",
"markdown": "text/markdown",
"md": "text/markdown",
"mml": "text/mathml",
"mdx": "text/mdx",
"n3": "text/n3",
"txt": "text/plain",
"text": "text/plain",
"conf": "text/plain",
"def": "text/plain",
"list": "text/plain",
"log": "text/plain",
"in": "text/plain",
"ini": "text/plain",
"dsc": "text/prs.lines.tag",
"rtx": "text/richtext",
"sgml": "text/sgml",
"sgm": "text/sgml",
"shex": "text/shex",
"slim": "text/slim",
"slm": "text/slim",
"spdx": "text/spdx",
"stylus": "text/stylus",
"styl": "text/stylus",
"tsv": "text/tab-separated-values",
"t": "text/troff",
"tr": "text/troff",
"roff": "text/troff",
"man": "text/troff",
"me": "text/troff",
"ms": "text/troff",
"ttl": "text/turtle",
"uri": "text/uri-list",
"uris": "text/uri-list",
"urls": "text/uri-list",
"vcard": "text/vcard",
"vtt": "text/vtt",
"yaml": "text/yaml",
"yml": "text/yaml",
"3gp": "video/3gpp",
"3g2": "video/3gpp2",
"h261": "video/h261",
"h263": "video/h263",
"h264": "video/h264",
"m4s": "video/iso.segment",
"jpgv": "video/jpeg",
"jpgm": "image/jpm",
"mj2": "video/mj2",
"mjp2": "video/mj2",
"ts": "video/mp2t",
"mp4": "video/mp4",
"mp4v": "video/mp4",
"mpg4": "video/mp4",
"mpeg": "video/mpeg",
"mpg": "video/mpeg",
"mpe": "video/mpeg",
"m1v": "video/mpeg",
"m2v": "video/mpeg",
"ogv": "video/ogg",
"qt": "video/quicktime",
"mov": "video/quicktime",
"webm": "video/webm"
};
function lookup(extn) {
let tmp = ('' + extn).trim().toLowerCase();
let idx = tmp.lastIndexOf('.');
return mimes[!~idx ? tmp : tmp.substring(++idx)];
}
const noop = () => {};
function isMatch(uri, arr) {
for (let i=0; i < arr.length; i++) {
if (arr[i].test(uri)) return true;
}
}
function toAssume(uri, extns) {
let i=0, x, len=uri.length - 1;
if (uri.charCodeAt(len) === 47) {
uri = uri.substring(0, len);
}
let arr=[], tmp=`${uri}/index`;
for (; i < extns.length; i++) {
x = extns[i] ? `.${extns[i]}` : '';
if (uri) arr.push(uri + x);
arr.push(tmp + x);
}
return arr;
}
function viaCache(cache, uri, extns) {
let i=0, data, arr=toAssume(uri, extns);
for (; i < arr.length; i++) {
if (data = cache[arr[i]]) return data;
}
}
function viaLocal(dir, isEtag, uri, extns) {
let i=0, arr=toAssume(uri, extns);
let abs, stats, name, headers;
for (; i < arr.length; i++) {
abs = normalize(join(dir, name=arr[i]));
if (abs.startsWith(dir) && fs.existsSync(abs)) {
stats = fs.statSync(abs);
if (stats.isDirectory()) continue;
headers = toHeaders(name, stats, isEtag);
headers['Cache-Control'] = isEtag ? 'no-cache' : 'no-store';
return { abs, stats, headers };
}
}
}
function is404(req, res) {
return (res.statusCode=404,res.end());
}
function send(req, res, file, stats, headers) {
let code=200, tmp, opts={};
headers = { ...headers };
for (let key in headers) {
tmp = res.getHeader(key);
if (tmp) headers[key] = tmp;
}
if (tmp = res.getHeader('content-type')) {
headers['Content-Type'] = tmp;
}
if (req.headers.range) {
code = 206;
let [x, y] = req.headers.range.replace('bytes=', '').split('-');
let end = opts.end = parseInt(y, 10) || stats.size - 1;
let start = opts.start = parseInt(x, 10) || 0;
if (end >= stats.size) {
end = stats.size - 1;
}
if (start >= stats.size) {
res.setHeader('Content-Range', `bytes */${stats.size}`);
res.statusCode = 416;
return res.end();
}
headers['Content-Range'] = `bytes ${start}-${end}/${stats.size}`;
headers['Content-Length'] = (end - start + 1);
headers['Accept-Ranges'] = 'bytes';
}
res.writeHead(code, headers);
fs.createReadStream(file, opts).pipe(res);
}
const ENCODING = {
'.br': 'br',
'.gz': 'gzip',
};
function toHeaders(name, stats, isEtag) {
let enc = ENCODING[name.slice(-3)];
let ctype = lookup(name.slice(0, enc && -3)) || '';
if (ctype === 'text/html') ctype += ';charset=utf-8';
let headers = {
'Content-Length': stats.size,
'Content-Type': ctype,
'Last-Modified': stats.mtime.toUTCString(),
};
if (enc) headers['Content-Encoding'] = enc;
if (isEtag) headers['ETag'] = `W/"${stats.size}-${stats.mtime.getTime()}"`;
return headers;
}
function sirv (dir, opts={}) {
dir = resolve(dir || '.');
let isNotFound = opts.onNoMatch || is404;
let setHeaders = opts.setHeaders || noop;
let extensions = opts.extensions || ['html', 'htm'];
let gzips = opts.gzip && extensions.map(x => `${x}.gz`).concat('gz');
let brots = opts.brotli && extensions.map(x => `${x}.br`).concat('br');
const FILES = {};
let fallback = '/';
let isEtag = !!opts.etag;
let isSPA = !!opts.single;
if (typeof opts.single === 'string') {
let idx = opts.single.lastIndexOf('.');
fallback += !!~idx ? opts.single.substring(0, idx) : opts.single;
}
let ignores = [];
if (opts.ignores !== false) {
ignores.push(/[/]([A-Za-z\s\d~$._-]+\.\w+){1,}$/); // any extn
if (opts.dotfiles) ignores.push(/\/\.\w/);
else ignores.push(/\/\.well-known/);
[].concat(opts.ignores || []).forEach(x => {
ignores.push(new RegExp(x, 'i'));
});
}
let cc = opts.maxAge != null && `public,max-age=${opts.maxAge}`;
if (cc && opts.immutable) cc += ',immutable';
else if (cc && opts.maxAge === 0) cc += ',must-revalidate';
if (!opts.dev) {
totalist(dir, (name, abs, stats) => {
if (/\.well-known[\\+\/]/.test(name)) ; // keep
else if (!opts.dotfiles && /(^\.|[\\+|\/+]\.)/.test(name)) return;
let headers = toHeaders(name, stats, isEtag);
if (cc) headers['Cache-Control'] = cc;
FILES['/' + name.normalize().replace(/\\+/g, '/')] = { abs, stats, headers };
});
}
let lookup = opts.dev ? viaLocal.bind(0, dir, isEtag) : viaCache.bind(0, FILES);
return function (req, res, next) {
let extns = [''];
let pathname = parse$1(req).pathname;
let val = req.headers['accept-encoding'] || '';
if (gzips && val.includes('gzip')) extns.unshift(...gzips);
if (brots && /(br|brotli)/i.test(val)) extns.unshift(...brots);
extns.push(...extensions); // [...br, ...gz, orig, ...exts]
if (pathname.indexOf('%') !== -1) {
try { pathname = decodeURI(pathname); }
catch (err) { /* malform uri */ }
}
let data = lookup(pathname, extns) || isSPA && !isMatch(pathname, ignores) && lookup(fallback, extns);
if (!data) return next ? next() : isNotFound(req, res);
if (isEtag && req.headers['if-none-match'] === data.headers['ETag']) {
res.writeHead(304);
return res.end();
}
if (gzips || brots) {
res.setHeader('Vary', 'Accept-Encoding');
}
setHeaders(res, pathname, data.stats);
send(req, res, data.abs, data.stats, data.headers);
};
}
var setCookie = {exports: {}};
var defaultParseOptions = {
decodeValues: true,
map: false,
silent: false,
};
function isNonEmptyString(str) {
return typeof str === "string" && !!str.trim();
}
function parseString(setCookieValue, options) {
var parts = setCookieValue.split(";").filter(isNonEmptyString);
var nameValuePairStr = parts.shift();
var parsed = parseNameValuePair(nameValuePairStr);
var name = parsed.name;
var value = parsed.value;
options = options
? Object.assign({}, defaultParseOptions, options)
: defaultParseOptions;
try {
value = options.decodeValues ? decodeURIComponent(value) : value; // decode cookie value
} catch (e) {
console.error(
"set-cookie-parser encountered an error while decoding a cookie with value '" +
value +
"'. Set options.decodeValues to false to disable this feature.",
e
);
}
var cookie = {
name: name,
value: value,
};
parts.forEach(function (part) {
var sides = part.split("=");
var key = sides.shift().trimLeft().toLowerCase();
var value = sides.join("=");
if (key === "expires") {
cookie.expires = new Date(value);
} else if (key === "max-age") {
cookie.maxAge = parseInt(value, 10);
} else if (key === "secure") {
cookie.secure = true;
} else if (key === "httponly") {
cookie.httpOnly = true;
} else if (key === "samesite") {
cookie.sameSite = value;
} else {
cookie[key] = value;
}
});
return cookie;
}
function parseNameValuePair(nameValuePairStr) {
// Parses name-value-pair according to rfc6265bis draft
var name = "";
var value = "";
var nameValueArr = nameValuePairStr.split("=");
if (nameValueArr.length > 1) {
name = nameValueArr.shift();
value = nameValueArr.join("="); // everything after the first =, joined by a "=" if there was more than one part
} else {
value = nameValuePairStr;
}
return { name: name, value: value };
}
function parse(input, options) {
options = options
? Object.assign({}, defaultParseOptions, options)
: defaultParseOptions;
if (!input) {
if (!options.map) {
return [];
} else {
return {};
}
}
if (input.headers) {
if (typeof input.headers.getSetCookie === "function") {
// for fetch responses - they combine headers of the same type in the headers array,
// but getSetCookie returns an uncombined array
input = input.headers.getSetCookie();
} else if (input.headers["set-cookie"]) {
// fast-path for node.js (which automatically normalizes header names to lower-case
input = input.headers["set-cookie"];
} else {
// slow-path for other environments - see #25
var sch =
input.headers[
Object.keys(input.headers).find(function (key) {
return key.toLowerCase() === "set-cookie";
})
];
// warn if called on a request-like object with a cookie header rather than a set-cookie header - see #34, 36
if (!sch && input.headers.cookie && !options.silent) {
console.warn(
"Warning: set-cookie-parser appears to have been called on a request object. It is designed to parse Set-Cookie headers from responses, not Cookie headers from requests. Set the option {silent: true} to suppress this warning."
);
}
input = sch;
}
}
if (!Array.isArray(input)) {
input = [input];
}
options = options
? Object.assign({}, defaultParseOptions, options)
: defaultParseOptions;
if (!options.map) {
return input.filter(isNonEmptyString).map(function (str) {
return parseString(str, options);
});
} else {
var cookies = {};
return input.filter(isNonEmptyString).reduce(function (cookies, str) {
var cookie = parseString(str, options);
cookies[cookie.name] = cookie;
return cookies;
}, cookies);
}
}
/*
Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
that are within a single set-cookie field-value, such as in the Expires portion.
This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
React Native's fetch does this for *every* header, including set-cookie.
Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
*/
function splitCookiesString(cookiesString) {
if (Array.isArray(cookiesString)) {
return cookiesString;
}
if (typeof cookiesString !== "string") {
return [];
}
var cookiesStrings = [];
var pos = 0;
var start;
var ch;
var lastComma;
var nextStart;
var cookiesSeparatorFound;
function skipWhitespace() {
while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
pos += 1;
}
return pos < cookiesString.length;
}
function notSpecialChar() {
ch = cookiesString.charAt(pos);
return ch !== "=" && ch !== ";" && ch !== ",";
}
while (pos < cookiesString.length) {
start = pos;
cookiesSeparatorFound = false;
while (skipWhitespace()) {
ch = cookiesString.charAt(pos);
if (ch === ",") {
// ',' is a cookie separator if we have later first '=', not ';' or ','
lastComma = pos;
pos += 1;
skipWhitespace();
nextStart = pos;
while (pos < cookiesString.length && notSpecialChar()) {
pos += 1;
}
// currently special character
if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
// we found cookies separator
cookiesSeparatorFound = true;
// pos is inside the next cookie, so back up and return it.
pos = nextStart;
cookiesStrings.push(cookiesString.substring(start, lastComma));
start = pos;
} else {
// in param ',' or param separator ';',
// we continue from that comma
pos = lastComma + 1;
}
} else {
pos += 1;
}
}
if (!cookiesSeparatorFound || pos >= cookiesString.length) {
cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
}
}
return cookiesStrings;
}
setCookie.exports = parse;
setCookie.exports.parse = parse;
setCookie.exports.parseString = parseString;
var splitCookiesString_1 = setCookie.exports.splitCookiesString = splitCookiesString;
class HttpError {
/**
* @param {number} status
* @param {{message: string} extends App.Error ? (App.Error | string | undefined) : App.Error} body
*/
constructor(status, body) {
this.status = status;
if (typeof body === 'string') {
this.body = { message: body };
} else if (body) {
this.body = body;
} else {
this.body = { message: `Error: ${status}` };
}
}
toString() {
return JSON.stringify(this.body);
}
}
/**
* @overload
* @param {number} status
* @param {App.Error} body
* @return {HttpError}
*/
/**
* @overload
* @param {number} status
* @param {{ message: string } extends App.Error ? App.Error | string | undefined : never} [body]
* @return {HttpError}
*/
/**
* Creates an `HttpError` object with an HTTP status code and an optional message.
* This object, if thrown during request handling, will cause SvelteKit to
* return an error response without invoking `handleError`.
* Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.
* @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
* @param {{ message: string } extends App.Error ? App.Error | string | undefined : never} body An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
*/
function error(status, body) {
if ((isNaN(status) || status < 400 || status > 599)) {
throw new Error(`HTTP error status codes must be between 400 and 599 — ${status} is invalid`);
}
return new HttpError(status, body);
}
new TextEncoder();
/**
* @param {import('http').IncomingMessage} req
* @param {number} [body_size_limit]
*/
function get_raw_body(req, body_size_limit) {
const h = req.headers;
if (!h['content-type']) {
return null;
}
const content_length = Number(h['content-length']);
// check if no request body
if (
(req.httpVersionMajor === 1 && isNaN(content_length) && h['transfer-encoding'] == null) ||
content_length === 0
) {
return null;
}
let length = content_length;
if (body_size_limit) {
if (!length) {
length = body_size_limit;
} else if (length > body_size_limit) {
throw error(
413,
`Received content-length of ${length}, but only accept up to ${body_size_limit} bytes.`
);
}
}
if (req.destroyed) {
const readable = new ReadableStream();
readable.cancel();
return readable;
}
let size = 0;
let cancelled = false;
return new ReadableStream({
start(controller) {
req.on('error', (error) => {
cancelled = true;
controller.error(error);
});
req.on('end', () => {
if (cancelled) return;
controller.close();
});
req.on('data', (chunk) => {
if (cancelled) return;
size += chunk.length;
if (size > length) {
cancelled = true;
controller.error(
error(
413,
`request body size exceeded ${
content_length ? "'content-length'" : 'BODY_SIZE_LIMIT'
} of ${length}`
)
);
return;
}
controller.enqueue(chunk);
if (controller.desiredSize === null || controller.desiredSize <= 0) {
req.pause();
}
});
},
pull() {
req.resume();
},
cancel(reason) {
cancelled = true;
req.destroy(reason);
}
});
}
/**
* @param {{
* request: import('http').IncomingMessage;
* base: string;
* bodySizeLimit?: number;
* }} options
* @returns {Promise<Request>}
*/
async function getRequest({ request, base, bodySizeLimit }) {
return new Request(base + request.url, {
// @ts-expect-error
duplex: 'half',
method: request.method,
headers: /** @type {Record<string, string>} */ (request.headers),
body: get_raw_body(request, bodySizeLimit)
});
}
/**
* @param {import('http').ServerResponse} res
* @param {Response} response
* @returns {Promise<void>}
*/
async function setResponse(res, response) {
for (const [key, value] of response.headers) {
try {
res.setHeader(
key,
key === 'set-cookie'
? splitCookiesString_1(
// This is absurd but necessary, TODO: investigate why
/** @type {string}*/ (response.headers.get(key))
)
: value
);
} catch (error) {
res.getHeaderNames().forEach((name) => res.removeHeader(name));
res.writeHead(500).end(String(error));
return;
}
}
res.writeHead(response.status);
if (!response.body) {
res.end();
return;
}
if (response.body.locked) {
res.end(
'Fatal error: Response body is locked. ' +
"This can happen when the response was already read (for example through 'response.json()' or 'response.text()')."
);
return;
}
const reader = response.body.getReader();
if (res.destroyed) {
reader.cancel();
return;
}
const cancel = (/** @type {Error|undefined} */ error) => {
res.off('close', cancel);
res.off('error', cancel);
// If the reader has already been interrupted with an error earlier,
// then it will appear here, it is useless, but it needs to be catch.
reader.cancel(error).catch(() => {});
if (error) res.destroy(error);
};
res.on('close', cancel);
res.on('error', cancel);
next();
async function next() {
try {
for (;;) {
const { done, value } = await reader.read();
if (done) break;
if (!res.write(value)) {
res.once('drain', next);
return;
}
}
res.end();
} catch (error) {
cancel(error instanceof Error ? error : new Error(String(error)));
}
}
}
/* global "" */
const server = new Server(manifest);
await server.init({ env: process.env });
const origin = env('ORIGIN', undefined);
const xff_depth = parseInt(env('XFF_DEPTH', '1'));
const address_header = env('ADDRESS_HEADER', '').toLowerCase();
const protocol_header = env('PROTOCOL_HEADER', '').toLowerCase();
const host_header = env('HOST_HEADER', 'host').toLowerCase();
const body_size_limit = parseInt(env('BODY_SIZE_LIMIT', '524288'));
const dir = path.dirname(fileURLToPath(import.meta.url));
/**
* @param {string} path
* @param {boolean} client
*/
function serve(path, client = false) {
return (
fs$1.existsSync(path) &&
sirv(path, {
etag: true,
gzip: true,
brotli: true,
setHeaders:
client &&
((res, pathname) => {
// only apply to build directory, not e.g. version.json
if (pathname.startsWith(`/${manifest.appPath}/immutable/`) && res.statusCode === 200) {
res.setHeader('cache-control', 'public,max-age=31536000,immutable');
}
})
})
);
}
// required because the static file server ignores trailing slashes
/** @returns {import('polka').Middleware} */
function serve_prerendered() {
const handler = serve(path.join(dir, 'prerendered'));
return (req, res, next) => {
let { pathname, search, query } = parse$1(req);
try {
pathname = decodeURIComponent(pathname);
} catch {
// ignore invalid URI
}
if (prerendered.has(pathname)) {
return handler(req, res, next);
}
// remove or add trailing slash as appropriate
let location = pathname.at(-1) === '/' ? pathname.slice(0, -1) : pathname + '/';
if (prerendered.has(location)) {
if (query) location += search;
res.writeHead(308, { location }).end();
} else {
next();
}
};
}
/** @type {import('polka').Middleware} */
const ssr = async (req, res) => {
/** @type {Request | undefined} */
let request;
try {
request = await getRequest({
base: origin || get_origin(req.headers),
request: req,
bodySizeLimit: body_size_limit
});
} catch (err) {
res.statusCode = err.status || 400;
res.end('Invalid request body');
return;
}
setResponse(
res,
await server.respond(request, {
platform: { req },
getClientAddress: () => {
if (address_header) {
if (!(address_header in req.headers)) {
throw new Error(
`Address header was specified with ${
"" + 'ADDRESS_HEADER'
}=${address_header} but is absent from request`
);
}
const value = /** @type {string} */ (req.headers[address_header]) || '';
if (address_header === 'x-forwarded-for') {
const addresses = value.split(',');
if (xff_depth < 1) {
throw new Error(`${"" + 'XFF_DEPTH'} must be a positive integer`);
}
if (xff_depth > addresses.length) {
throw new Error(
`${"" + 'XFF_DEPTH'} is ${xff_depth}, but only found ${
addresses.length
} addresses`
);
}
return addresses[addresses.length - xff_depth].trim();
}
return value;
}
return (
req.connection?.remoteAddress ||
// @ts-expect-error
req.connection?.socket?.remoteAddress ||
req.socket?.remoteAddress ||
// @ts-expect-error
req.info?.remoteAddress
);
}
})
);
};
/** @param {import('polka').Middleware[]} handlers */
function sequence(handlers) {
/** @type {import('polka').Middleware} */
return (req, res, next) => {
/**
* @param {number} i
* @returns {ReturnType<import('polka').Middleware>}
*/
function handle(i) {
if (i < handlers.length) {
return handlers[i](req, res, () => handle(i + 1));
} else {
return next();
}
}
return handle(0);
};
}
/**
* @param {import('http').IncomingHttpHeaders} headers
* @returns
*/
function get_origin(headers) {
const protocol = (protocol_header && headers[protocol_header]) || 'https';
const host = headers[host_header];
return `${protocol}://${host}`;
}
const handler = sequence(
[
serve(path.join(dir, 'client'), true),
serve(path.join(dir, 'static')),
serve_prerendered(),
ssr
].filter(Boolean)
);
export { handler };