UNPKG

@sveltejs/kit

Version:

Here be dragons, etc etc.

467 lines (371 loc) • 10.9 kB
'use strict'; var path = require('path'); var fs = require('fs'); var standard = require('./standard.js'); function list(dir, callback, pre='') { dir = path.resolve('.', dir); let arr = fs.readdirSync(dir); let i=0, abs, stats; for (; i < arr.length; i++) { abs = path.join(dir, arr[i]); stats = fs.statSync(abs); stats.isDirectory() ? list(abs, callback, path.join(pre, arr[i])) : callback(path.join(pre, arr[i]), abs, stats); } } function parse(str) { let i=0, j=0, k, v; let out={}, arr=str.split('&'); for (; i < arr.length; i++) { j = arr[i].indexOf('='); v = !!~j && arr[i].substring(j+1) || ''; k = !!~j ? arr[i].substring(0, j) : arr[i]; out[k] = out[k] !== void 0 ? [].concat(out[k], v) : v; } return out; } function parser (req, toDecode) { let url = req.url; if (url == null) return; let obj = req._parsedUrl; if (obj && obj._raw === url) return obj; obj = { path: url, pathname: url, search: null, query: null, href: url, _raw: url }; if (url.length > 1) { if (toDecode && !req._decoded && !!~url.indexOf('%', 1)) { let nxt = url; try { nxt = decodeURIComponent(url); } catch (e) {/* bad */} url = req.url = obj.href = obj.path = obj.pathname = obj._raw = nxt; req._decoded = true; } let idx = url.indexOf('?', 1); if (idx !== -1) { obj.search = url.substring(idx); obj.query = obj.search.substring(1); obj.pathname = url.substring(0, idx); if (toDecode && obj.query.length > 0) { obj.query = parse(obj.query); } } } return (req._parsedUrl = obj); } var lite = new standard.Mime_1(standard.standard); 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 = path.normalize(path.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'] = '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); } function isEncoding(name, type, headers) { headers['Content-Encoding'] = type; headers['Content-Type'] = lite.getType(name.replace(/\.([^.]*)$/, '')) || ''; } function toHeaders(name, stats, isEtag) { let headers = { 'Content-Length': stats.size, 'Content-Type': lite.getType(name) || '', 'Last-Modified': stats.mtime.toUTCString(), }; if (isEtag) headers['ETag'] = `W/"${stats.size}-${stats.mtime.getTime()}"`; if (/\.br$/.test(name)) isEncoding(name, 'br', headers); if (/\.gz$/.test(name)) isEncoding(name, 'gzip', headers); return headers; } function sirv (dir, opts={}) { dir = path.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(/\w\.\w+$/); // 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) { list(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 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] let pathname = req.path || parser(req, true).pathname; 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(); } setHeaders(res, pathname, data.stats); send(req, res, data.abs, data.stats, data.headers); }; } function read_only_form_data() { const map = new Map(); return { append(key, value) { if (map.has(key)) { (map.get(key) ).push(value); } else { map.set(key, [value]); } }, data: new ReadOnlyFormData(map) }; } class ReadOnlyFormData { #map constructor(map) { this.#map = map; } get(key) { const value = this.#map.get(key); return value && value[0]; } getAll(key) { return this.#map.get(key); } has(key) { return this.#map.has(key); } *[Symbol.iterator]() { for (const [key, value] of this.#map) { for (let i = 0; i < value.length; i += 1) { yield [key, value[i]]; } } } *entries() { for (const [key, value] of this.#map) { for (let i = 0; i < value.length; i += 1) { yield [key, value[i]]; } } } *keys() { for (const [key, value] of this.#map) { for (let i = 0; i < value.length; i += 1) { yield key; } } } *values() { for (const [, value] of this.#map) { for (let i = 0; i < value.length; i += 1) { yield value; } } } } function get_body(req) { const headers = req.headers; const has_body = headers['content-type'] !== undefined && // https://github.com/jshttp/type-is/blob/c1f4388c71c8a01f79934e68f630ca4a15fffcd6/index.js#L81-L95 (headers['transfer-encoding'] !== undefined || !isNaN(Number(headers['content-length']))); if (!has_body) return Promise.resolve(undefined); const [type, ...directives] = (headers['content-type'] ).split(/;\s*/); switch (type) { case 'application/octet-stream': return get_buffer(req); case 'text/plain': return get_text(req); case 'application/json': return get_json(req); case 'application/x-www-form-urlencoded': return get_urlencoded(req); case 'multipart/form-data': const boundary = directives.find((directive) => directive.startsWith('boundary=')); if (!boundary) throw new Error(`Missing boundary`); return get_multipart(req, boundary.slice('boundary='.length)); default: throw new Error(`Invalid Content-Type ${type}`); } } async function get_json(req) { return JSON.parse(await get_text(req)); } async function get_urlencoded(req) { const text = await get_text(req); const { data, append } = read_only_form_data(); text .replace(/\+/g, ' ') .split('&') .forEach((str) => { const [key, value] = str.split('='); append(decodeURIComponent(key), decodeURIComponent(value)); }); return data; } async function get_multipart(req, boundary) { const text = await get_text(req); const parts = text.split(`--${boundary}`); const nope = () => { throw new Error('Malformed form data'); }; if (parts[0] !== '' || parts[parts.length - 1].trim() !== '--') { nope(); } const { data, append } = read_only_form_data(); parts.slice(1, -1).forEach((part) => { const match = /\s*([\s\S]+?)\r\n\r\n([\s\S]*)\s*/.exec(part) ; const raw_headers = match[1]; const body = match[2].trim(); let key; raw_headers.split('\r\n').forEach((str) => { const [raw_header, ...raw_directives] = str.split('; '); let [name, value] = raw_header.split(': '); name = name.toLowerCase(); const directives = {}; raw_directives.forEach((raw_directive) => { const [name, value] = raw_directive.split('='); directives[name] = JSON.parse(value); // TODO is this right? }); if (name === 'content-disposition') { if (value !== 'form-data') nope(); if (directives.filename) { // TODO we probably don't want to do this automatically throw new Error('File upload is not yet implemented'); } if (directives.name) { key = directives.name; } } }); if (!key) nope(); append(key, body); }); return data; } function get_text(req) { return new Promise((fulfil, reject) => { let data = ''; req.on('error', reject); req.on('data', (chunk) => { data += chunk; }); req.on('end', () => { fulfil(data); }); }); } function get_buffer(req) { return new Promise((fulfil, reject) => { let data = new Uint8Array(0); req.on('error', reject); req.on('data', (chunk) => { const new_data = new Uint8Array(data.length + chunk.length); for (let i = 0; i < data.length; i += 1) { new_data[i] = data[i]; } for (let i = 0; i < chunk.length; i += 1) { new_data[i + data.length] = chunk[i]; } data = new_data; }); req.on('end', () => { fulfil(data.buffer); }); }); } exports.get_body = get_body; exports.sirv = sirv; //# sourceMappingURL=index4.js.map