mailto.ts
7.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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:Array<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;