twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

mailto.ts (7235B)


      1 import { URISchemeHandler, URIComponents, URIOptions } from "../uri";
      2 import { pctEncChar, pctDecChars, unescapeComponent } from "../uri";
      3 import punycode from "punycode";
      4 import { merge, subexp, toUpperCase, toArray } from "../util";
      5 
      6 export interface MailtoHeaders {
      7 	[hfname:string]:string
      8 }
      9 
     10 export interface MailtoComponents extends URIComponents {
     11 	to:Array<string>,
     12 	headers?:MailtoHeaders,
     13 	subject?:string,
     14 	body?:string
     15 }
     16 
     17 const O:MailtoHeaders = {};
     18 const isIRI = true;
     19 
     20 //RFC 3986
     21 const UNRESERVED$$ = "[A-Za-z0-9\\-\\.\\_\\~" + (isIRI ? "\\xA0-\\u200D\\u2010-\\u2029\\u202F-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF" : "") + "]";
     22 const HEXDIG$$ = "[0-9A-Fa-f]";  //case-insensitive
     23 const PCT_ENCODED$ = subexp(subexp("%[EFef]" + HEXDIG$$ + "%" + HEXDIG$$ + HEXDIG$$ + "%" + HEXDIG$$ + HEXDIG$$) + "|" + subexp("%[89A-Fa-f]" + HEXDIG$$ + "%" + HEXDIG$$ + HEXDIG$$) + "|" + subexp("%" + HEXDIG$$ + HEXDIG$$));  //expanded
     24 
     25 //RFC 5322, except these symbols as per RFC 6068: @ : / ? # [ ] & ; =
     26 //const ATEXT$$ = "[A-Za-z0-9\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~]";
     27 //const WSP$$ = "[\\x20\\x09]";
     28 //const OBS_QTEXT$$ = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]";  //(%d1-8 / %d11-12 / %d14-31 / %d127)
     29 //const QTEXT$$ = merge("[\\x21\\x23-\\x5B\\x5D-\\x7E]", OBS_QTEXT$$);  //%d33 / %d35-91 / %d93-126 / obs-qtext
     30 //const VCHAR$$ = "[\\x21-\\x7E]";
     31 //const WSP$$ = "[\\x20\\x09]";
     32 //const OBS_QP$ = subexp("\\\\" + merge("[\\x00\\x0D\\x0A]", OBS_QTEXT$$));  //%d0 / CR / LF / obs-qtext
     33 //const FWS$ = subexp(subexp(WSP$$ + "*" + "\\x0D\\x0A") + "?" + WSP$$ + "+");
     34 //const QUOTED_PAIR$ = subexp(subexp("\\\\" + subexp(VCHAR$$ + "|" + WSP$$)) + "|" + OBS_QP$);
     35 //const QUOTED_STRING$ = subexp('\\"' + subexp(FWS$ + "?" + QCONTENT$) + "*" + FWS$ + "?" + '\\"');
     36 const ATEXT$$ = "[A-Za-z0-9\\!\\$\\%\\'\\*\\+\\-\\^\\_\\`\\{\\|\\}\\~]";
     37 const QTEXT$$ = "[\\!\\$\\%\\'\\(\\)\\*\\+\\,\\-\\.0-9\\<\\>A-Z\\x5E-\\x7E]";
     38 const VCHAR$$ = merge(QTEXT$$, "[\\\"\\\\]");
     39 const DOT_ATOM_TEXT$ = subexp(ATEXT$$ + "+" + subexp("\\." + ATEXT$$ + "+") + "*");
     40 const QUOTED_PAIR$ = subexp("\\\\" + VCHAR$$);
     41 const QCONTENT$ = subexp(QTEXT$$ + "|" + QUOTED_PAIR$);
     42 const QUOTED_STRING$ = subexp('\\"' + QCONTENT$ + "*" + '\\"');
     43 
     44 //RFC 6068
     45 const DTEXT_NO_OBS$$ = "[\\x21-\\x5A\\x5E-\\x7E]";  //%d33-90 / %d94-126
     46 const SOME_DELIMS$$ = "[\\!\\$\\'\\(\\)\\*\\+\\,\\;\\:\\@]";
     47 const QCHAR$ = subexp(UNRESERVED$$ + "|" + PCT_ENCODED$ + "|" + SOME_DELIMS$$);
     48 const DOMAIN$ = subexp(DOT_ATOM_TEXT$ + "|" + "\\[" + DTEXT_NO_OBS$$ + "*" + "\\]");
     49 const LOCAL_PART$ = subexp(DOT_ATOM_TEXT$ + "|" + QUOTED_STRING$);
     50 const ADDR_SPEC$ = subexp(LOCAL_PART$ + "\\@" + DOMAIN$);
     51 const TO$ = subexp(ADDR_SPEC$ + subexp("\\," + ADDR_SPEC$) + "*");
     52 const HFNAME$ = subexp(QCHAR$ + "*");
     53 const HFVALUE$ = HFNAME$;
     54 const HFIELD$ = subexp(HFNAME$ + "\\=" + HFVALUE$);
     55 const HFIELDS2$ = subexp(HFIELD$ + subexp("\\&" + HFIELD$) + "*");
     56 const HFIELDS$ = subexp("\\?" + HFIELDS2$);
     57 const MAILTO_URI = new RegExp("^mailto\\:" + TO$ + "?" + HFIELDS$ + "?$");
     58 
     59 const UNRESERVED = new RegExp(UNRESERVED$$, "g");
     60 const PCT_ENCODED = new RegExp(PCT_ENCODED$, "g");
     61 const NOT_LOCAL_PART = new RegExp(merge("[^]", ATEXT$$, "[\\.]", '[\\"]', VCHAR$$), "g");
     62 const NOT_DOMAIN = new RegExp(merge("[^]", ATEXT$$, "[\\.]", "[\\[]", DTEXT_NO_OBS$$, "[\\]]"), "g");
     63 const NOT_HFNAME = new RegExp(merge("[^]", UNRESERVED$$, SOME_DELIMS$$), "g");
     64 const NOT_HFVALUE = NOT_HFNAME;
     65 const TO = new RegExp("^" + TO$ + "$");
     66 const HFIELDS = new RegExp("^" + HFIELDS2$ + "$");
     67 
     68 function decodeUnreserved(str:string):string {
     69 	const decStr = pctDecChars(str);
     70 	return (!decStr.match(UNRESERVED) ? str : decStr);
     71 }
     72 
     73 const handler:URISchemeHandler<MailtoComponents> =  {
     74 	scheme : "mailto",
     75 
     76 	parse : function (components:URIComponents, options:URIOptions):MailtoComponents {
     77 		const mailtoComponents = components as MailtoComponents;
     78 		const to = mailtoComponents.to = (mailtoComponents.path ? mailtoComponents.path.split(",") : []);
     79 		mailtoComponents.path = undefined;
     80 
     81 		if (mailtoComponents.query) {
     82 			let unknownHeaders = false
     83 			const headers:MailtoHeaders = {};
     84 			const hfields = mailtoComponents.query.split("&");
     85 
     86 			for (let x = 0, xl = hfields.length; x < xl; ++x) {
     87 				const hfield = hfields[x].split("=");
     88 
     89 				switch (hfield[0]) {
     90 					case "to":
     91 						const toAddrs = hfield[1].split(",");
     92 						for (let x = 0, xl = toAddrs.length; x < xl; ++x) {
     93 							to.push(toAddrs[x]);
     94 						}
     95 						break;
     96 					case "subject":
     97 						mailtoComponents.subject = unescapeComponent(hfield[1], options);
     98 						break;
     99 					case "body":
    100 						mailtoComponents.body = unescapeComponent(hfield[1], options);
    101 						break;
    102 					default:
    103 						unknownHeaders = true;
    104 						headers[unescapeComponent(hfield[0], options)] = unescapeComponent(hfield[1], options);
    105 						break;
    106 				}
    107 			}
    108 
    109 			if (unknownHeaders) mailtoComponents.headers = headers;
    110 		}
    111 
    112 		mailtoComponents.query = undefined;
    113 
    114 		for (let x = 0, xl = to.length; x < xl; ++x) {
    115 			const addr = to[x].split("@");
    116 
    117 			addr[0] = unescapeComponent(addr[0]);
    118 
    119 			if (!options.unicodeSupport) {
    120 				//convert Unicode IDN -> ASCII IDN
    121 				try {
    122 					addr[1] = punycode.toASCII(unescapeComponent(addr[1], options).toLowerCase());
    123 				} catch (e) {
    124 					mailtoComponents.error = mailtoComponents.error || "Email address's domain name can not be converted to ASCII via punycode: " + e;
    125 				}
    126 			} else {
    127 				addr[1] = unescapeComponent(addr[1], options).toLowerCase();
    128 			}
    129 
    130 			to[x] = addr.join("@");
    131 		}
    132 
    133 		return mailtoComponents;
    134 	},
    135 
    136 	serialize : function (mailtoComponents:MailtoComponents, options:URIOptions):URIComponents {
    137 		const components = mailtoComponents as URIComponents;
    138 		const to = toArray(mailtoComponents.to);
    139 		if (to) {
    140 			for (let x = 0, xl = to.length; x < xl; ++x) {
    141 				const toAddr = String(to[x]);
    142 				const atIdx = toAddr.lastIndexOf("@");
    143 				const localPart = (toAddr.slice(0, atIdx)).replace(PCT_ENCODED, decodeUnreserved).replace(PCT_ENCODED, toUpperCase).replace(NOT_LOCAL_PART, pctEncChar);
    144 				let domain = toAddr.slice(atIdx + 1);
    145 
    146 				//convert IDN via punycode
    147 				try {
    148 					domain = (!options.iri ? punycode.toASCII(unescapeComponent(domain, options).toLowerCase()) : punycode.toUnicode(domain));
    149 				} catch (e) {
    150 					components.error = components.error || "Email address's domain name can not be converted to " + (!options.iri ? "ASCII" : "Unicode") + " via punycode: " + e;
    151 				}
    152 
    153 				to[x] = localPart + "@" + domain;
    154 			}
    155 
    156 			components.path = to.join(",");
    157 		}
    158 
    159 		const headers = mailtoComponents.headers = mailtoComponents.headers || {};
    160 
    161 		if (mailtoComponents.subject) headers["subject"] = mailtoComponents.subject;
    162 		if (mailtoComponents.body) headers["body"] = mailtoComponents.body;
    163 
    164 		const fields = [];
    165 		for (const name in headers) {
    166 			if (headers[name] !== O[name]) {
    167 				fields.push(
    168 					name.replace(PCT_ENCODED, decodeUnreserved).replace(PCT_ENCODED, toUpperCase).replace(NOT_HFNAME, pctEncChar) +
    169 					"=" +
    170 					headers[name].replace(PCT_ENCODED, decodeUnreserved).replace(PCT_ENCODED, toUpperCase).replace(NOT_HFVALUE, pctEncChar)
    171 				);
    172 			}
    173 		}
    174 		if (fields.length) {
    175 			components.query = fields.join("&");
    176 		}
    177 
    178 		return components;
    179 	}
    180 }
    181 
    182 export default handler;