mirror of
https://github.com/Sevichecc/Urara-Blog.git
synced 2025-05-04 01:29:30 +08:00
1175 lines
31 KiB
Text
1175 lines
31 KiB
Text
import './shims-7fd472a1.js';
|
|
import * as fs from 'fs';
|
|
import fs__default, { readdirSync, statSync } from 'fs';
|
|
import path, { resolve, join, normalize } from 'path';
|
|
import * as qs from 'querystring';
|
|
import { fileURLToPath } from 'url';
|
|
import { Server } from 'SERVER';
|
|
import { manifest } from 'MANIFEST';
|
|
|
|
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 (start >= stats.size || end >= 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 = decodeURIComponent(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 nameValue = parts.shift().split("=");
|
|
var name = nameValue.shift();
|
|
var value = nameValue.join("="); // everything after the first =, joined by a "=" if there was more than one part
|
|
|
|
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, // grab everything before the first =
|
|
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 parse(input, options) {
|
|
options = options
|
|
? Object.assign({}, defaultParseOptions, options)
|
|
: defaultParseOptions;
|
|
|
|
if (!input) {
|
|
if (!options.map) {
|
|
return [];
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
if (input.headers && input.headers["set-cookie"]) {
|
|
// fast-path for node.js (which automatically normalizes header names to lower-case
|
|
input = input.headers["set-cookie"];
|
|
} else if (input.headers) {
|
|
// 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;
|
|
|
|
/** @param {import('http').IncomingMessage} req */
|
|
function get_raw_body(req) {
|
|
const h = req.headers;
|
|
|
|
if (!h['content-type']) {
|
|
return null;
|
|
}
|
|
|
|
const length = Number(h['content-length']);
|
|
|
|
// check if no request body
|
|
// https://github.com/jshttp/type-is/blob/c1f4388c71c8a01f79934e68f630ca4a15fffcd6/index.js#L81-L95
|
|
if (isNaN(length) && h['transfer-encoding'] == null) {
|
|
return null;
|
|
}
|
|
|
|
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) => {
|
|
controller.error(error);
|
|
});
|
|
|
|
req.on('end', () => {
|
|
if (cancelled) return;
|
|
controller.close();
|
|
});
|
|
|
|
req.on('data', (chunk) => {
|
|
if (cancelled) return;
|
|
|
|
size += chunk.length;
|
|
if (size > length) {
|
|
controller.error(new Error('content-length exceeded'));
|
|
return;
|
|
}
|
|
|
|
controller.enqueue(chunk);
|
|
|
|
if (controller.desiredSize === null || controller.desiredSize <= 0) {
|
|
req.pause();
|
|
}
|
|
});
|
|
},
|
|
|
|
pull() {
|
|
req.resume();
|
|
},
|
|
|
|
cancel(reason) {
|
|
cancelled = true;
|
|
req.destroy(reason);
|
|
}
|
|
});
|
|
}
|
|
|
|
/** @type {import('@sveltejs/kit/node').getRequest} */
|
|
async function getRequest(base, req) {
|
|
let headers = /** @type {Record<string, string>} */ (req.headers);
|
|
if (req.httpVersionMajor === 2) {
|
|
// we need to strip out the HTTP/2 pseudo-headers because node-fetch's
|
|
// Request implementation doesn't like them
|
|
// TODO is this still true with Node 18
|
|
headers = Object.assign({}, headers);
|
|
delete headers[':method'];
|
|
delete headers[':path'];
|
|
delete headers[':authority'];
|
|
delete headers[':scheme'];
|
|
}
|
|
|
|
return new Request(base + req.url, {
|
|
method: req.method,
|
|
headers,
|
|
body: get_raw_body(req)
|
|
});
|
|
}
|
|
|
|
/** @type {import('@sveltejs/kit/node').setResponse} */
|
|
async function setResponse(res, response) {
|
|
const headers = Object.fromEntries(response.headers);
|
|
|
|
if (response.headers.has('set-cookie')) {
|
|
const header = /** @type {string} */ (response.headers.get('set-cookie'));
|
|
const split = splitCookiesString_1(header);
|
|
|
|
// @ts-expect-error
|
|
headers['set-cookie'] = split;
|
|
}
|
|
|
|
res.writeHead(response.status, headers);
|
|
|
|
if (!response.body) {
|
|
res.end();
|
|
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 ENV_PREFIX */
|
|
|
|
const expected = new Set([
|
|
'SOCKET_PATH',
|
|
'HOST',
|
|
'PORT',
|
|
'ORIGIN',
|
|
'XFF_DEPTH',
|
|
'ADDRESS_HEADER',
|
|
'PROTOCOL_HEADER',
|
|
'HOST_HEADER'
|
|
]);
|
|
|
|
if (ENV_PREFIX) {
|
|
for (const name in process.env) {
|
|
if (name.startsWith(ENV_PREFIX)) {
|
|
const unprefixed = name.slice(ENV_PREFIX.length);
|
|
if (!expected.has(unprefixed)) {
|
|
throw new Error(
|
|
`You should change envPrefix (${ENV_PREFIX}) to avoid conflicts with existing environment variables — unexpectedly saw ${name}`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {any} fallback
|
|
*/
|
|
function env(name, fallback) {
|
|
const prefixed = ENV_PREFIX + name;
|
|
return prefixed in process.env ? process.env[prefixed] : fallback;
|
|
}
|
|
|
|
/* global ENV_PREFIX */
|
|
|
|
const server = new Server(manifest);
|
|
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 __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
/**
|
|
* @param {string} path
|
|
* @param {boolean} client
|
|
*/
|
|
function serve(path, client = false) {
|
|
return (
|
|
fs__default.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.appDir}/immutable/`)) {
|
|
res.setHeader('cache-control', 'public,max-age=31536000,immutable');
|
|
}
|
|
})
|
|
})
|
|
);
|
|
}
|
|
|
|
/** @type {import('polka').Middleware} */
|
|
const ssr = async (req, res) => {
|
|
let request;
|
|
|
|
try {
|
|
request = await getRequest(origin || get_origin(req.headers), req);
|
|
} catch (err) {
|
|
res.statusCode = err.status || 400;
|
|
res.end(err.reason || 'Invalid request body');
|
|
return;
|
|
}
|
|
|
|
if (address_header && !(address_header in req.headers)) {
|
|
throw new Error(
|
|
`Address header was specified with ${
|
|
ENV_PREFIX + 'ADDRESS_HEADER'
|
|
}=${address_header} but is absent from request`
|
|
);
|
|
}
|
|
|
|
setResponse(
|
|
res,
|
|
await server.respond(request, {
|
|
getClientAddress: () => {
|
|
if (address_header) {
|
|
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(`${ENV_PREFIX + 'XFF_DEPTH'} must be a positive integer`);
|
|
}
|
|
|
|
if (xff_depth > addresses.length) {
|
|
throw new Error(
|
|
`${ENV_PREFIX + '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 */
|
|
function handle(i) {
|
|
handlers[i](req, res, () => {
|
|
if (i < handlers.length) handle(i + 1);
|
|
else next();
|
|
});
|
|
}
|
|
|
|
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(__dirname, '/client'), true),
|
|
serve(path.join(__dirname, '/static')),
|
|
serve(path.join(__dirname, '/prerendered')),
|
|
ssr
|
|
].filter(Boolean)
|
|
);
|
|
|
|
export { env as e, handler as h, parse$1 as p };
|