// TODO: The below has been modified from the original sirv package to support // the feature of mounting the served files from a certain path (in this case, `/~partytown/`) // It would be good to bring this into Astro for all integrations to take advantage of, // and potentially also to respect your config automatically for things like `base` path. // @ts-nocheck /** * @license * * The MIT License (MIT) * * Copyright (c) Luke Edwards (https://lukeed.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ import * as fs from 'fs'; import { join, normalize, resolve } from 'path'; // import { totalist } from 'totalist/sync'; // import { parse } from '@polka/url'; import { lookup } from 'mrmime'; import { URL } from 'url'; 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; } export default function (dir, opts = {}) { dir = resolve(dir || '.'); let mountTo = opts.mount || ''; 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 fileLookup = opts.dev ? viaLocal.bind(0, dir, isEtag) : viaCache.bind(0, FILES); return function (req, res, next) { let extns = ['']; let pathname = new URL(req.url, 'https://example.dev').pathname; // NEW if (mountTo && pathname.startsWith(mountTo)) { pathname = pathname.substring(mountTo.length); } // NEW END 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 = fileLookup(pathname, extns) || (isSPA && !isMatch(pathname, ignores) && fileLookup(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); }; }