UNPKG

@tdb/web

Version:

Common condiguration for serving a web-site and testing web-based UI components.

182 lines (153 loc) 7.26 kB
import { URISchemeHandler, URIComponents, URIOptions } from '../uri'; import { pctEncChar, pctDecChars, unescapeComponent } from '../uri'; import punycode from 'punycode'; import { merge, subexp, toUpperCase, toArray } from '../util'; export interface MailtoHeaders { [hfname:string]:string } export interface MailtoComponents extends URIComponents { to:string[], headers?:MailtoHeaders, subject?:string, body?:string } const O:MailtoHeaders = {}; const isIRI = true; // RFC 3986 const UNRESERVED$$ = '[A-Za-z0-9\\-\\.\\_\\~' + (isIRI ? '\\xA0-\\u200D\\u2010-\\u2029\\u202F-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF' : '') + ']'; const HEXDIG$$ = '[0-9A-Fa-f]'; // case-insensitive const PCT_ENCODED$ = subexp(subexp('%[EFef]' + HEXDIG$$ + '%' + HEXDIG$$ + HEXDIG$$ + '%' + HEXDIG$$ + HEXDIG$$) + '|' + subexp('%[89A-Fa-f]' + HEXDIG$$ + '%' + HEXDIG$$ + HEXDIG$$) + '|' + subexp('%' + HEXDIG$$ + HEXDIG$$)); // expanded // RFC 5322, except these symbols as per RFC 6068: @ : / ? # [ ] & ; = // const ATEXT$$ = "[A-Za-z0-9\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~]"; // const WSP$$ = "[\\x20\\x09]"; // const OBS_QTEXT$$ = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]"; //(%d1-8 / %d11-12 / %d14-31 / %d127) // const QTEXT$$ = merge("[\\x21\\x23-\\x5B\\x5D-\\x7E]", OBS_QTEXT$$); //%d33 / %d35-91 / %d93-126 / obs-qtext // const VCHAR$$ = "[\\x21-\\x7E]"; // const WSP$$ = "[\\x20\\x09]"; // const OBS_QP$ = subexp("\\\\" + merge("[\\x00\\x0D\\x0A]", OBS_QTEXT$$)); //%d0 / CR / LF / obs-qtext // const FWS$ = subexp(subexp(WSP$$ + "*" + "\\x0D\\x0A") + "?" + WSP$$ + "+"); // const QUOTED_PAIR$ = subexp(subexp("\\\\" + subexp(VCHAR$$ + "|" + WSP$$)) + "|" + OBS_QP$); // const QUOTED_STRING$ = subexp('\\"' + subexp(FWS$ + "?" + QCONTENT$) + "*" + FWS$ + "?" + '\\"'); const ATEXT$$ = '[A-Za-z0-9\\!\\$\\%\\\'\\*\\+\\-\\^\\_\\`\\{\\|\\}\\~]'; const QTEXT$$ = '[\\!\\$\\%\\\'\\(\\)\\*\\+\\,\\-\\.0-9\\<\\>A-Z\\x5E-\\x7E]'; const VCHAR$$ = merge(QTEXT$$, '[\\"\\\\]'); const DOT_ATOM_TEXT$ = subexp(ATEXT$$ + '+' + subexp('\\.' + ATEXT$$ + '+') + '*'); const QUOTED_PAIR$ = subexp('\\\\' + VCHAR$$); const QCONTENT$ = subexp(QTEXT$$ + '|' + QUOTED_PAIR$); const QUOTED_STRING$ = subexp('\\"' + QCONTENT$ + '*' + '\\"'); // RFC 6068 const DTEXT_NO_OBS$$ = '[\\x21-\\x5A\\x5E-\\x7E]'; // %d33-90 / %d94-126 const SOME_DELIMS$$ = '[\\!\\$\\\'\\(\\)\\*\\+\\,\\;\\:\\@]'; const QCHAR$ = subexp(UNRESERVED$$ + '|' + PCT_ENCODED$ + '|' + SOME_DELIMS$$); const DOMAIN$ = subexp(DOT_ATOM_TEXT$ + '|' + '\\[' + DTEXT_NO_OBS$$ + '*' + '\\]'); const LOCAL_PART$ = subexp(DOT_ATOM_TEXT$ + '|' + QUOTED_STRING$); const ADDR_SPEC$ = subexp(LOCAL_PART$ + '\\@' + DOMAIN$); const TO$ = subexp(ADDR_SPEC$ + subexp('\\,' + ADDR_SPEC$) + '*'); const HFNAME$ = subexp(QCHAR$ + '*'); const HFVALUE$ = HFNAME$; const HFIELD$ = subexp(HFNAME$ + '\\=' + HFVALUE$); const HFIELDS2$ = subexp(HFIELD$ + subexp('\\&' + HFIELD$) + '*'); const HFIELDS$ = subexp('\\?' + HFIELDS2$); const MAILTO_URI = new RegExp('^mailto\\:' + TO$ + '?' + HFIELDS$ + '?$'); const UNRESERVED = new RegExp(UNRESERVED$$, 'g'); const PCT_ENCODED = new RegExp(PCT_ENCODED$, 'g'); const NOT_LOCAL_PART = new RegExp(merge('[^]', ATEXT$$, '[\\.]', '[\\"]', VCHAR$$), 'g'); const NOT_DOMAIN = new RegExp(merge('[^]', ATEXT$$, '[\\.]', '[\\[]', DTEXT_NO_OBS$$, '[\\]]'), 'g'); const NOT_HFNAME = new RegExp(merge('[^]', UNRESERVED$$, SOME_DELIMS$$), 'g'); const NOT_HFVALUE = NOT_HFNAME; const TO = new RegExp('^' + TO$ + '$'); const HFIELDS = new RegExp('^' + HFIELDS2$ + '$'); function decodeUnreserved(str:string):string { const decStr = pctDecChars(str); return (!decStr.match(UNRESERVED) ? str : decStr); } const handler:URISchemeHandler<MailtoComponents> = { scheme : 'mailto', parse : function (components:URIComponents, options:URIOptions):MailtoComponents { const mailtoComponents = components as MailtoComponents; const to = mailtoComponents.to = (mailtoComponents.path ? mailtoComponents.path.split(',') : []); mailtoComponents.path = undefined; if (mailtoComponents.query) { let unknownHeaders = false const headers:MailtoHeaders = {}; const hfields = mailtoComponents.query.split('&'); for (let x = 0, xl = hfields.length; x < xl; ++x) { const hfield = hfields[x].split('='); switch (hfield[0]) { case 'to': const toAddrs = hfield[1].split(','); for (let x = 0, xl = toAddrs.length; x < xl; ++x) { to.push(toAddrs[x]); } break; case 'subject': mailtoComponents.subject = unescapeComponent(hfield[1], options); break; case 'body': mailtoComponents.body = unescapeComponent(hfield[1], options); break; default: unknownHeaders = true; headers[unescapeComponent(hfield[0], options)] = unescapeComponent(hfield[1], options); break; } } if (unknownHeaders) { mailtoComponents.headers = headers; } } mailtoComponents.query = undefined; for (let x = 0, xl = to.length; x < xl; ++x) { const addr = to[x].split('@'); addr[0] = unescapeComponent(addr[0]); if (!options.unicodeSupport) { // convert Unicode IDN -> ASCII IDN try { addr[1] = punycode.toASCII(unescapeComponent(addr[1], options).toLowerCase()); } catch (e) { mailtoComponents.error = mailtoComponents.error || 'Email address\'s domain name can not be converted to ASCII via punycode: ' + e; } } else { addr[1] = unescapeComponent(addr[1], options).toLowerCase(); } to[x] = addr.join('@'); } return mailtoComponents; }, serialize : function (mailtoComponents:MailtoComponents, options:URIOptions):URIComponents { const components = mailtoComponents as URIComponents; const to = toArray(mailtoComponents.to); if (to) { for (let x = 0, xl = to.length; x < xl; ++x) { const toAddr = String(to[x]); const atIdx = toAddr.lastIndexOf('@'); const localPart = (toAddr.slice(0, atIdx)).replace(PCT_ENCODED, decodeUnreserved).replace(PCT_ENCODED, toUpperCase).replace(NOT_LOCAL_PART, pctEncChar); let domain = toAddr.slice(atIdx + 1); // convert IDN via punycode try { domain = (!options.iri ? punycode.toASCII(unescapeComponent(domain, options).toLowerCase()) : punycode.toUnicode(domain)); } catch (e) { components.error = components.error || 'Email address\'s domain name can not be converted to ' + (!options.iri ? 'ASCII' : 'Unicode') + ' via punycode: ' + e; } to[x] = localPart + '@' + domain; } components.path = to.join(','); } const headers = mailtoComponents.headers = mailtoComponents.headers || {}; if (mailtoComponents.subject) { headers.subject = mailtoComponents.subject; } if (mailtoComponents.body) { headers.body = mailtoComponents.body; } const fields = []; for (const name in headers) { if (headers[name] !== O[name]) { fields.push( name.replace(PCT_ENCODED, decodeUnreserved).replace(PCT_ENCODED, toUpperCase).replace(NOT_HFNAME, pctEncChar) + '=' + headers[name].replace(PCT_ENCODED, decodeUnreserved).replace(PCT_ENCODED, toUpperCase).replace(NOT_HFVALUE, pctEncChar) ); } } if (fields.length) { components.query = fields.join('&'); } return components; } } export default handler;