| | 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); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | 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,}$/); |
| | 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)) ; |
| | 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); |
| |
|
| | if (pathname.indexOf('%') !== -1) { |
| | try { pathname = decodeURI(pathname); } |
| | catch (err) { } |
| | } |
| |
|
| | 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; |
| | } 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) { |
| | |
| |
|
| | var name = ""; |
| | var value = ""; |
| | var nameValueArr = nameValuePairStr.split("="); |
| | if (nameValueArr.length > 1) { |
| | name = nameValueArr.shift(); |
| | value = nameValueArr.join("="); |
| | } 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") { |
| | |
| | |
| | input = input.headers.getSetCookie(); |
| | } else if (input.headers["set-cookie"]) { |
| | |
| | input = input.headers["set-cookie"]; |
| | } else { |
| | |
| | var sch = |
| | input.headers[ |
| | Object.keys(input.headers).find(function (key) { |
| | return key.toLowerCase() === "set-cookie"; |
| | }) |
| | ]; |
| | |
| | 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); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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 === ",") { |
| | |
| | lastComma = pos; |
| | pos += 1; |
| |
|
| | skipWhitespace(); |
| | nextStart = pos; |
| |
|
| | while (pos < cookiesString.length && notSpecialChar()) { |
| | pos += 1; |
| | } |
| |
|
| | |
| | if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") { |
| | |
| | cookiesSeparatorFound = true; |
| | |
| | pos = nextStart; |
| | cookiesStrings.push(cookiesString.substring(start, lastComma)); |
| | start = pos; |
| | } else { |
| | |
| | |
| | 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 SvelteKitError extends Error { |
| | |
| | |
| | |
| | |
| | |
| | constructor(status, text, message) { |
| | super(message); |
| | this.status = status; |
| | this.text = text; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | 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']); |
| |
|
| | |
| | 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) { |
| | |
| | |
| | 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); |
| | } |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | async function getRequest({ request, base, bodySizeLimit }) { |
| | return new Request(base + request.url, { |
| | |
| | duplex: 'half', |
| | method: request.method, |
| | headers: (request.headers), |
| | body: get_raw_body(request, bodySizeLimit) |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | async function setResponse(res, response) { |
| | for (const [key, value] of response.headers) { |
| | try { |
| | res.setHeader( |
| | key, |
| | key === 'set-cookie' |
| | ? splitCookiesString_1( |
| | |
| | (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 = ( error) => { |
| | res.off('close', cancel); |
| | res.off('error', cancel); |
| |
|
| | |
| | |
| | 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))); |
| | } |
| | } |
| | } |
| |
|
| | |
| |
|
| | 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 port_header = env('PORT_HEADER', '').toLowerCase(); |
| | const body_size_limit = Number(env('BODY_SIZE_LIMIT', '524288')); |
| |
|
| | 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)); |
| |
|
| | |
| | |
| | |
| | |
| | function serve(path, client = false) { |
| | return ( |
| | fs$1.existsSync(path) && |
| | sirv(path, { |
| | etag: true, |
| | gzip: true, |
| | brotli: true, |
| | setHeaders: |
| | client && |
| | ((res, pathname) => { |
| | |
| | if (pathname.startsWith(`/${manifest.appPath}/immutable/`) && res.statusCode === 200) { |
| | res.setHeader('cache-control', 'public,max-age=31536000,immutable'); |
| | } |
| | }) |
| | }) |
| | ); |
| | } |
| |
|
| | |
| | |
| | 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 { |
| | |
| | } |
| |
|
| | if (prerendered.has(pathname)) { |
| | return handler(req, res, next); |
| | } |
| |
|
| | |
| | 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(); |
| | } |
| | }; |
| | } |
| |
|
| | |
| | const ssr = async (req, res) => { |
| | const request = await getRequest({ |
| | base: origin || get_origin(req.headers), |
| | request: req, |
| | bodySizeLimit: body_size_limit |
| | }); |
| |
|
| | 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 = (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 || |
| | |
| | req.connection?.socket?.remoteAddress || |
| | req.socket?.remoteAddress || |
| | |
| | req.info?.remoteAddress |
| | ); |
| | } |
| | }) |
| | ); |
| | }; |
| |
|
| | |
| | function sequence(handlers) { |
| | |
| | return (req, res, next) => { |
| | |
| | |
| | |
| | |
| | function handle(i) { |
| | if (i < handlers.length) { |
| | return handlers[i](req, res, () => handle(i + 1)); |
| | } else { |
| | return next(); |
| | } |
| | } |
| |
|
| | return handle(0); |
| | }; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | 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 }; |
| |
|