Spaces:
Runtime error
Runtime error
import './shims.js'; | |
import fs$1, { createReadStream } 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 { Readable } from 'node:stream'; | |
import { Server } from './server/index.js'; | |
import { manifest, prerendered, base } 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 = { | |
"3g2": "video/3gpp2", | |
"3gp": "video/3gpp", | |
"3gpp": "video/3gpp", | |
"3mf": "model/3mf", | |
"aac": "audio/aac", | |
"ac": "application/pkix-attr-cert", | |
"adp": "audio/adpcm", | |
"adts": "audio/aac", | |
"ai": "application/postscript", | |
"aml": "application/automationml-aml+xml", | |
"amlx": "application/automationml-amlx+zip", | |
"amr": "audio/amr", | |
"apng": "image/apng", | |
"appcache": "text/cache-manifest", | |
"appinstaller": "application/appinstaller", | |
"appx": "application/appx", | |
"appxbundle": "application/appxbundle", | |
"asc": "application/pgp-keys", | |
"atom": "application/atom+xml", | |
"atomcat": "application/atomcat+xml", | |
"atomdeleted": "application/atomdeleted+xml", | |
"atomsvc": "application/atomsvc+xml", | |
"au": "audio/basic", | |
"avci": "image/avci", | |
"avcs": "image/avcs", | |
"avif": "image/avif", | |
"aw": "application/applixware", | |
"bdoc": "application/bdoc", | |
"bin": "application/octet-stream", | |
"bmp": "image/bmp", | |
"bpk": "application/octet-stream", | |
"btf": "image/prs.btif", | |
"btif": "image/prs.btif", | |
"buffer": "application/octet-stream", | |
"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", | |
"cer": "application/pkix-cert", | |
"cgm": "image/cgm", | |
"cjs": "application/node", | |
"class": "application/java-vm", | |
"coffee": "text/coffeescript", | |
"conf": "text/plain", | |
"cpl": "application/cpl+xml", | |
"cpt": "application/mac-compactpro", | |
"crl": "application/pkix-crl", | |
"css": "text/css", | |
"csv": "text/csv", | |
"cu": "application/cu-seeme", | |
"cwl": "application/cwl", | |
"cww": "application/prs.cww", | |
"davmount": "application/davmount+xml", | |
"dbk": "application/docbook+xml", | |
"deb": "application/octet-stream", | |
"def": "text/plain", | |
"deploy": "application/octet-stream", | |
"dib": "image/bmp", | |
"disposition-notification": "message/disposition-notification", | |
"dist": "application/octet-stream", | |
"distz": "application/octet-stream", | |
"dll": "application/octet-stream", | |
"dmg": "application/octet-stream", | |
"dms": "application/octet-stream", | |
"doc": "application/msword", | |
"dot": "application/msword", | |
"dpx": "image/dpx", | |
"drle": "image/dicom-rle", | |
"dsc": "text/prs.lines.tag", | |
"dssc": "application/dssc+der", | |
"dtd": "application/xml-dtd", | |
"dump": "application/octet-stream", | |
"dwd": "application/atsc-dwd+xml", | |
"ear": "application/java-archive", | |
"ecma": "application/ecmascript", | |
"elc": "application/octet-stream", | |
"emf": "image/emf", | |
"eml": "message/rfc822", | |
"emma": "application/emma+xml", | |
"emotionml": "application/emotionml+xml", | |
"eps": "application/postscript", | |
"epub": "application/epub+zip", | |
"exe": "application/octet-stream", | |
"exi": "application/exi", | |
"exp": "application/express", | |
"exr": "image/aces", | |
"ez": "application/andrew-inset", | |
"fdf": "application/fdf", | |
"fdt": "application/fdt+xml", | |
"fits": "image/fits", | |
"g3": "image/g3fax", | |
"gbr": "application/rpki-ghostbusters", | |
"geojson": "application/geo+json", | |
"gif": "image/gif", | |
"glb": "model/gltf-binary", | |
"gltf": "model/gltf+json", | |
"gml": "application/gml+xml", | |
"gpx": "application/gpx+xml", | |
"gram": "application/srgs", | |
"grxml": "application/srgs+xml", | |
"gxf": "application/gxf", | |
"gz": "application/gzip", | |
"h261": "video/h261", | |
"h263": "video/h263", | |
"h264": "video/h264", | |
"heic": "image/heic", | |
"heics": "image/heic-sequence", | |
"heif": "image/heif", | |
"heifs": "image/heif-sequence", | |
"hej2": "image/hej2k", | |
"held": "application/atsc-held+xml", | |
"hjson": "application/hjson", | |
"hlp": "application/winhlp", | |
"hqx": "application/mac-binhex40", | |
"hsj2": "image/hsj2", | |
"htm": "text/html", | |
"html": "text/html", | |
"ics": "text/calendar", | |
"ief": "image/ief", | |
"ifb": "text/calendar", | |
"iges": "model/iges", | |
"igs": "model/iges", | |
"img": "application/octet-stream", | |
"in": "text/plain", | |
"ini": "text/plain", | |
"ink": "application/inkml+xml", | |
"inkml": "application/inkml+xml", | |
"ipfix": "application/ipfix", | |
"iso": "application/octet-stream", | |
"its": "application/its+xml", | |
"jade": "text/jade", | |
"jar": "application/java-archive", | |
"jhc": "image/jphc", | |
"jls": "image/jls", | |
"jp2": "image/jp2", | |
"jpe": "image/jpeg", | |
"jpeg": "image/jpeg", | |
"jpf": "image/jpx", | |
"jpg": "image/jpeg", | |
"jpg2": "image/jp2", | |
"jpgm": "image/jpm", | |
"jpgv": "video/jpeg", | |
"jph": "image/jph", | |
"jpm": "image/jpm", | |
"jpx": "image/jpx", | |
"js": "text/javascript", | |
"json": "application/json", | |
"json5": "application/json5", | |
"jsonld": "application/ld+json", | |
"jsonml": "application/jsonml+json", | |
"jsx": "text/jsx", | |
"jt": "model/jt", | |
"jxr": "image/jxr", | |
"jxra": "image/jxra", | |
"jxrs": "image/jxrs", | |
"jxs": "image/jxs", | |
"jxsc": "image/jxsc", | |
"jxsi": "image/jxsi", | |
"jxss": "image/jxss", | |
"kar": "audio/midi", | |
"ktx": "image/ktx", | |
"ktx2": "image/ktx2", | |
"less": "text/less", | |
"lgr": "application/lgr+xml", | |
"list": "text/plain", | |
"litcoffee": "text/coffeescript", | |
"log": "text/plain", | |
"lostxml": "application/lost+xml", | |
"lrf": "application/octet-stream", | |
"m1v": "video/mpeg", | |
"m21": "application/mp21", | |
"m2a": "audio/mpeg", | |
"m2v": "video/mpeg", | |
"m3a": "audio/mpeg", | |
"m4a": "audio/mp4", | |
"m4p": "application/mp4", | |
"m4s": "video/iso.segment", | |
"ma": "application/mathematica", | |
"mads": "application/mads+xml", | |
"maei": "application/mmt-aei+xml", | |
"man": "text/troff", | |
"manifest": "text/cache-manifest", | |
"map": "application/json", | |
"mar": "application/octet-stream", | |
"markdown": "text/markdown", | |
"mathml": "application/mathml+xml", | |
"mb": "application/mathematica", | |
"mbox": "application/mbox", | |
"md": "text/markdown", | |
"mdx": "text/mdx", | |
"me": "text/troff", | |
"mesh": "model/mesh", | |
"meta4": "application/metalink4+xml", | |
"metalink": "application/metalink+xml", | |
"mets": "application/mets+xml", | |
"mft": "application/rpki-manifest", | |
"mid": "audio/midi", | |
"midi": "audio/midi", | |
"mime": "message/rfc822", | |
"mj2": "video/mj2", | |
"mjp2": "video/mj2", | |
"mjs": "text/javascript", | |
"mml": "text/mathml", | |
"mods": "application/mods+xml", | |
"mov": "video/quicktime", | |
"mp2": "audio/mpeg", | |
"mp21": "application/mp21", | |
"mp2a": "audio/mpeg", | |
"mp3": "audio/mpeg", | |
"mp4": "video/mp4", | |
"mp4a": "audio/mp4", | |
"mp4s": "application/mp4", | |
"mp4v": "video/mp4", | |
"mpd": "application/dash+xml", | |
"mpe": "video/mpeg", | |
"mpeg": "video/mpeg", | |
"mpf": "application/media-policy-dataset+xml", | |
"mpg": "video/mpeg", | |
"mpg4": "video/mp4", | |
"mpga": "audio/mpeg", | |
"mpp": "application/dash-patch+xml", | |
"mrc": "application/marc", | |
"mrcx": "application/marcxml+xml", | |
"ms": "text/troff", | |
"mscml": "application/mediaservercontrol+xml", | |
"msh": "model/mesh", | |
"msi": "application/octet-stream", | |
"msix": "application/msix", | |
"msixbundle": "application/msixbundle", | |
"msm": "application/octet-stream", | |
"msp": "application/octet-stream", | |
"mtl": "model/mtl", | |
"musd": "application/mmt-usd+xml", | |
"mxf": "application/mxf", | |
"mxmf": "audio/mobile-xmf", | |
"mxml": "application/xv+xml", | |
"n3": "text/n3", | |
"nb": "application/mathematica", | |
"nq": "application/n-quads", | |
"nt": "application/n-triples", | |
"obj": "model/obj", | |
"oda": "application/oda", | |
"oga": "audio/ogg", | |
"ogg": "audio/ogg", | |
"ogv": "video/ogg", | |
"ogx": "application/ogg", | |
"omdoc": "application/omdoc+xml", | |
"onepkg": "application/onenote", | |
"onetmp": "application/onenote", | |
"onetoc": "application/onenote", | |
"onetoc2": "application/onenote", | |
"opf": "application/oebps-package+xml", | |
"opus": "audio/ogg", | |
"otf": "font/otf", | |
"owl": "application/rdf+xml", | |
"oxps": "application/oxps", | |
"p10": "application/pkcs10", | |
"p7c": "application/pkcs7-mime", | |
"p7m": "application/pkcs7-mime", | |
"p7s": "application/pkcs7-signature", | |
"p8": "application/pkcs8", | |
"pdf": "application/pdf", | |
"pfr": "application/font-tdpfr", | |
"pgp": "application/pgp-encrypted", | |
"pkg": "application/octet-stream", | |
"pki": "application/pkixcmp", | |
"pkipath": "application/pkix-pkipath", | |
"pls": "application/pls+xml", | |
"png": "image/png", | |
"prc": "model/prc", | |
"prf": "application/pics-rules", | |
"provx": "application/provenance+xml", | |
"ps": "application/postscript", | |
"pskcxml": "application/pskc+xml", | |
"pti": "image/prs.pti", | |
"qt": "video/quicktime", | |
"raml": "application/raml+yaml", | |
"rapd": "application/route-apd+xml", | |
"rdf": "application/rdf+xml", | |
"relo": "application/p2p-overlay+xml", | |
"rif": "application/reginfo+xml", | |
"rl": "application/resource-lists+xml", | |
"rld": "application/resource-lists-diff+xml", | |
"rmi": "audio/midi", | |
"rnc": "application/relax-ng-compact-syntax", | |
"rng": "application/xml", | |
"roa": "application/rpki-roa", | |
"roff": "text/troff", | |
"rq": "application/sparql-query", | |
"rs": "application/rls-services+xml", | |
"rsat": "application/atsc-rsat+xml", | |
"rsd": "application/rsd+xml", | |
"rsheet": "application/urc-ressheet+xml", | |
"rss": "application/rss+xml", | |
"rtf": "text/rtf", | |
"rtx": "text/richtext", | |
"rusd": "application/route-usd+xml", | |
"s3m": "audio/s3m", | |
"sbml": "application/sbml+xml", | |
"scq": "application/scvp-cv-request", | |
"scs": "application/scvp-cv-response", | |
"sdp": "application/sdp", | |
"senmlx": "application/senml+xml", | |
"sensmlx": "application/sensml+xml", | |
"ser": "application/java-serialized-object", | |
"setpay": "application/set-payment-initiation", | |
"setreg": "application/set-registration-initiation", | |
"sgi": "image/sgi", | |
"sgm": "text/sgml", | |
"sgml": "text/sgml", | |
"shex": "text/shex", | |
"shf": "application/shf+xml", | |
"shtml": "text/html", | |
"sieve": "application/sieve", | |
"sig": "application/pgp-signature", | |
"sil": "audio/silk", | |
"silo": "model/mesh", | |
"siv": "application/sieve", | |
"slim": "text/slim", | |
"slm": "text/slim", | |
"sls": "application/route-s-tsid+xml", | |
"smi": "application/smil+xml", | |
"smil": "application/smil+xml", | |
"snd": "audio/basic", | |
"so": "application/octet-stream", | |
"spdx": "text/spdx", | |
"spp": "application/scvp-vp-response", | |
"spq": "application/scvp-vp-request", | |
"spx": "audio/ogg", | |
"sql": "application/sql", | |
"sru": "application/sru+xml", | |
"srx": "application/sparql-results+xml", | |
"ssdl": "application/ssdl+xml", | |
"ssml": "application/ssml+xml", | |
"stk": "application/hyperstudio", | |
"stl": "model/stl", | |
"stpx": "model/step+xml", | |
"stpxz": "model/step-xml+zip", | |
"stpz": "model/step+zip", | |
"styl": "text/stylus", | |
"stylus": "text/stylus", | |
"svg": "image/svg+xml", | |
"svgz": "image/svg+xml", | |
"swidtag": "application/swid+xml", | |
"t": "text/troff", | |
"t38": "image/t38", | |
"td": "application/urc-targetdesc+xml", | |
"tei": "application/tei+xml", | |
"teicorpus": "application/tei+xml", | |
"text": "text/plain", | |
"tfi": "application/thraud+xml", | |
"tfx": "image/tiff-fx", | |
"tif": "image/tiff", | |
"tiff": "image/tiff", | |
"toml": "application/toml", | |
"tr": "text/troff", | |
"trig": "application/trig", | |
"ts": "video/mp2t", | |
"tsd": "application/timestamped-data", | |
"tsv": "text/tab-separated-values", | |
"ttc": "font/collection", | |
"ttf": "font/ttf", | |
"ttl": "text/turtle", | |
"ttml": "application/ttml+xml", | |
"txt": "text/plain", | |
"u3d": "model/u3d", | |
"u8dsn": "message/global-delivery-status", | |
"u8hdr": "message/global-headers", | |
"u8mdn": "message/global-disposition-notification", | |
"u8msg": "message/global", | |
"ubj": "application/ubjson", | |
"uri": "text/uri-list", | |
"uris": "text/uri-list", | |
"urls": "text/uri-list", | |
"vcard": "text/vcard", | |
"vrml": "model/vrml", | |
"vtt": "text/vtt", | |
"vxml": "application/voicexml+xml", | |
"war": "application/java-archive", | |
"wasm": "application/wasm", | |
"wav": "audio/wav", | |
"weba": "audio/webm", | |
"webm": "video/webm", | |
"webmanifest": "application/manifest+json", | |
"webp": "image/webp", | |
"wgsl": "text/wgsl", | |
"wgt": "application/widget", | |
"wif": "application/watcherinfo+xml", | |
"wmf": "image/wmf", | |
"woff": "font/woff", | |
"woff2": "font/woff2", | |
"wrl": "model/vrml", | |
"wsdl": "application/wsdl+xml", | |
"wspolicy": "application/wspolicy+xml", | |
"x3d": "model/x3d+xml", | |
"x3db": "model/x3d+fastinfoset", | |
"x3dbz": "model/x3d+binary", | |
"x3dv": "model/x3d-vrml", | |
"x3dvz": "model/x3d+vrml", | |
"x3dz": "model/x3d+xml", | |
"xaml": "application/xaml+xml", | |
"xav": "application/xcap-att+xml", | |
"xca": "application/xcap-caps+xml", | |
"xcs": "application/calendar+xml", | |
"xdf": "application/xcap-diff+xml", | |
"xdssc": "application/dssc+xml", | |
"xel": "application/xcap-el+xml", | |
"xenc": "application/xenc+xml", | |
"xer": "application/patch-ops-error+xml", | |
"xfdf": "application/xfdf", | |
"xht": "application/xhtml+xml", | |
"xhtml": "application/xhtml+xml", | |
"xhvml": "application/xv+xml", | |
"xlf": "application/xliff+xml", | |
"xm": "audio/xm", | |
"xml": "text/xml", | |
"xns": "application/xcap-ns+xml", | |
"xop": "application/xop+xml", | |
"xpl": "application/xproc+xml", | |
"xsd": "application/xml", | |
"xsf": "application/prs.xsf+xml", | |
"xsl": "application/xml", | |
"xslt": "application/xml", | |
"xspf": "application/xspf+xml", | |
"xvm": "application/xv+xml", | |
"xvml": "application/xv+xml", | |
"yaml": "text/yaml", | |
"yang": "application/yang", | |
"yin": "application/yin+xml", | |
"yml": "text/yaml", | |
"zip": "application/zip" | |
}; | |
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; | |
/** | |
* An error that was thrown from within the SvelteKit runtime that is not fatal and doesn't result in a 500, such as a 404. | |
* `SvelteKitError` goes through `handleError`. | |
* @extends Error | |
*/ | |
class SvelteKitError extends Error { | |
/** | |
* @param {number} status | |
* @param {string} text | |
* @param {string} message | |
*/ | |
constructor(status, text, message) { | |
super(message); | |
this.status = status; | |
this.text = text; | |
} | |
} | |
/** | |
* @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; | |
} | |
if (req.destroyed) { | |
const readable = new ReadableStream(); | |
readable.cancel(); | |
return readable; | |
} | |
let size = 0; | |
let cancelled = false; | |
return new ReadableStream({ | |
start(controller) { | |
if (body_size_limit !== undefined && content_length > body_size_limit) { | |
let message = `Content-length of ${content_length} exceeds limit of ${body_size_limit} bytes.`; | |
if (body_size_limit === 0) { | |
// https://github.com/sveltejs/kit/pull/11589 | |
// TODO this exists to aid migration — remove in a future version | |
message += ' To disable body size limits, specify Infinity rather than 0.'; | |
} | |
const error = new SvelteKitError(413, 'Payload Too Large', message); | |
controller.error(error); | |
return; | |
} | |
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 > content_length) { | |
cancelled = true; | |
const constraint = content_length ? 'content-length' : 'BODY_SIZE_LIMIT'; | |
const message = `request body size exceeded ${constraint} of ${content_length}`; | |
const error = new SvelteKitError(413, 'Payload Too Large', message); | |
controller.error(error); | |
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>} | |
*/ | |
// TODO 3.0 make the signature synchronous? | |
// eslint-disable-next-line @typescript-eslint/require-await | |
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: | |
request.method === 'GET' || request.method === 'HEAD' | |
? undefined | |
: get_raw_body(request, bodySizeLimit) | |
}); | |
} | |
/** | |
* @param {import('http').ServerResponse} res | |
* @param {Response} response | |
* @returns {Promise<void>} | |
*/ | |
// TODO 3.0 make the signature synchronous? | |
// eslint-disable-next-line @typescript-eslint/require-await | |
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))); | |
} | |
} | |
} | |
/** | |
* Converts a file on disk to a readable stream | |
* @param {string} file | |
* @returns {ReadableStream} | |
* @since 2.4.0 | |
*/ | |
function createReadableStream(file) { | |
return /** @type {ReadableStream} */ (Readable.toWeb(createReadStream(file))); | |
} | |
/* global "" */ | |
const server = new Server(manifest); | |
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 port_header = env('PORT_HEADER', '').toLowerCase(); | |
/** | |
* @param {string} bytes | |
*/ | |
function parse_body_size_limit(bytes) { | |
const multiplier = | |
{ | |
K: 1024, | |
M: 1024 * 1024, | |
G: 1024 * 1024 * 1024 | |
}[bytes[bytes.length - 1]?.toUpperCase()] ?? 1; | |
return Number(multiplier != 1 ? bytes.substring(0, bytes.length - 1) : bytes) * multiplier; | |
} | |
const body_size_limit = parse_body_size_limit(env('BODY_SIZE_LIMIT', '512K')); | |
if (isNaN(body_size_limit)) { | |
throw new Error( | |
`Invalid BODY_SIZE_LIMIT: '${env('BODY_SIZE_LIMIT')}'. Please provide a numeric value.` | |
); | |
} | |
const dir = path.dirname(fileURLToPath(import.meta.url)); | |
const asset_dir = `${dir}/client${base}`; | |
await server.init({ | |
env: process.env, | |
read: (file) => createReadableStream(`${asset_dir}/${file}`) | |
}); | |
/** | |
* @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} */ | |
let request; | |
try { | |
request = await getRequest({ | |
base: origin || get_origin(req.headers), | |
request: req, | |
bodySizeLimit: body_size_limit | |
}); | |
} catch { | |
res.statusCode = 400; | |
res.end('Bad Request'); | |
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]; | |
const port = port_header && headers[port_header]; | |
if (port) { | |
return `${protocol}://${host}:${port}`; | |
} else { | |
return `${protocol}://${host}`; | |
} | |
} | |
const handler = sequence( | |
[ | |
serve(path.join(dir, 'client'), true), | |
serve(path.join(dir, 'static')), | |
serve_prerendered(), | |
ssr | |
].filter(Boolean) | |
); | |
export { handler }; | |