UNPKG

@microbitsclub/paywall-solid

Version:
280 lines (279 loc) 7.61 kB
const isCharCodeRange = (c, a, b) => { const u = c.charCodeAt(0); return u >= a && u <= b; }; const isCharRange = (c, a, b) => { return isCharCodeRange(c, a.charCodeAt(0), b.charCodeAt(0)); }; const isNumeric = (c) => { const u = c.charCodeAt(0); return u >= 48 && u <= 57; }; const isUpperAlpha = (c) => { const u = c.charCodeAt(0); return u >= 65 && u <= 90; }; const isLowerAlpha = (c) => { const u = c.charCodeAt(0); return u >= 97 && u <= 122; }; const isAlpha = (c) => { const u = c.charCodeAt(0); return (u >= 65 && u <= 90) || (u >= 97 && u <= 122); }; const parseProto = (s) => { const l = s.length; for (let i = 0; i < l; ++i) { const c = s[i]; if (c === ':') { if (s[i + 1] === '/' && s[i + 2] === '/') { return s.slice(0, i); } else { break; } } if (!isAlpha(c)) { break; } } return undefined; }; const parseHostname = (s) => { const l = s.length + 1; let isIp6 = s[0] === '['; for (let i = 0; i < l; ++i) { const c = s[i]; if (isIp6) { if (i === 0) continue; if (c === ':') continue; if (isNumeric(c)) continue; if (isCharRange(c, 'a', 'f')) continue; if (isCharRange(c, 'A', 'F')) continue; if (c === ']') { if (i === 1) return undefined; return [s.slice(i + 1), s.slice(1, i)]; } break; } else { if (c === ':' || c === '/' || c === '?' || c === undefined) { if (i === 0) return undefined; return [s.slice(i), s.slice(0, i)]; } } } return undefined; }; const parsePort = (s) => { if (s[0] !== ':') return undefined; const l = s.length + 1; for (let i = 1; i < l; ++i) { const c = s[i]; if (c === undefined || !isNumeric(c)) { if (i === 1) break; return s.slice(1, i); } } return undefined; }; export const createUrlPath = (path, trailing = false) => { if (typeof path === 'string') { const pathResult = parsePath(path); if (pathResult === undefined || pathResult[0].length > 0) { throw Error('Invalid path'); } return pathResult[1]; } else { return { path, trailing }; } }; const parsePath = (s) => { if (s[0] !== '/') return undefined; const l = s.length + 1; const path = []; let part = ''; let trailing = true; for (let i = 1; i < l; ++i) { const c = s[i]; if (c === undefined || c === '?') { if (part.length > 0) path.push(part); return [s.slice(i), { path, trailing }]; } if (c === '/') { trailing = true; if (part.length === 0) break; path.push(part); part = ''; continue; } part += c; trailing = false; } return undefined; }; const parseQuery = (s) => { if (s[0] !== '?') return undefined; const l = s.length + 1; const query = {}; let pivot = 0; let count = 0; for (let i = 1; i < l; ++i) { const c = s[i]; if (c === undefined || c === '&') { const [key, val] = s.slice(pivot + 1, i).split('='); if (val !== undefined) { const prevVal = query[key]; if (prevVal === undefined) { query[key] = decodeURIComponent(val ?? ''); count += 1; } else if (typeof prevVal === 'string') { query[key] = [prevVal, decodeURIComponent(val ?? '')]; } else { prevVal.push(decodeURIComponent(val ?? '')); } } pivot = i; } } return count === 0 ? undefined : query; }; export const parseUrl = (s) => { const proto = parseProto(s); if (proto !== undefined) { s = s.slice(proto.length + 3); } let hostname; const hostnameResult = parseHostname(s); if (hostnameResult !== undefined) { s = hostnameResult[0]; hostname = hostnameResult[1]; } let port; const portResult = parsePort(s); if (portResult !== undefined) { s = s.slice(portResult.length + 1); port = Number.parseInt(portResult); } let path = []; let trailing; const pathResult = parsePath(s); if (pathResult !== undefined) { s = pathResult[0]; path = pathResult[1].path; trailing = pathResult[1].trailing; } const query = parseQuery(s); const url = { proto, hostname, port, path, trailing: trailing ?? false, query, }; if (url.proto === undefined) delete url.proto; if (url.hostname === undefined) delete url.hostname; if (url.port === undefined) delete url.port; if (url.query === undefined) delete url.query; return url; }; export const stringifyUrlPath = ({ path, trailing }) => { let s = ''; if (path) s += '/' + path.join('/'); if (trailing === true) s += '/'; return s; }; export const stringifyUrl = (url) => { let s = ''; if (url.proto) s += url.proto + '://'; if (url.hostname) s += url.hostname; if (url.port) s += `:${url.port}`; s += stringifyUrlPath(url); if (url.query !== undefined) { const parts = []; for (const k in url.query) { const v = url.query[k]; if (v !== undefined) { if (typeof v === 'string') { parts.push(`${k}=${encodeURIComponent(v)}`); } else { for (const x of v) { parts.push(`${k}=${encodeURIComponent(x)}`); } } } } s += `?${parts.join('&')}`; } return s; }; export const URL_PROTOCOL_PORT_MAP = { http: 80, https: 443, ws: 80, wss: 443, }; export const normalizeUrl = (url) => { // Protocol and explicit port if (url.proto !== undefined && url.port !== undefined) { const defaultPort = URL_PROTOCOL_PORT_MAP[url.proto]; if (defaultPort === url.port) { const { port: _, ...oldUrl } = url; const newUrl = { ...oldUrl, proto: url.proto, path: url.path ?? [], trailing: url.trailing ?? false, }; return newUrl; } } // No protocol and explicit port if (url.proto === undefined && url.port !== undefined) { for (const proto in URL_PROTOCOL_PORT_MAP) { if (URL_PROTOCOL_PORT_MAP[proto] === url.port) { const { port: _, ...oldUrl } = url; const newUrl = { ...oldUrl, proto, path: url.path ?? [], trailing: url.trailing ?? false, }; return newUrl; } } } const newUrl = { ...url, path: url.path ?? [], trailing: url.trailing ?? false, }; return newUrl; };