You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
7.6 KiB

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