email-addresses.js 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  1. // email-addresses.js - RFC 5322 email address parser
  2. // v 3.1.0
  3. //
  4. // http://tools.ietf.org/html/rfc5322
  5. //
  6. // This library does not validate email addresses.
  7. // emailAddresses attempts to parse addresses using the (fairly liberal)
  8. // grammar specified in RFC 5322.
  9. //
  10. // email-addresses returns {
  11. // ast: <an abstract syntax tree based on rfc5322>,
  12. // addresses: [{
  13. // node: <node in ast for this address>,
  14. // name: <display-name>,
  15. // address: <addr-spec>,
  16. // local: <local-part>,
  17. // domain: <domain>
  18. // }, ...]
  19. // }
  20. //
  21. // emailAddresses.parseOneAddress and emailAddresses.parseAddressList
  22. // work as you might expect. Try it out.
  23. //
  24. // Many thanks to Dominic Sayers and his documentation on the is_email function,
  25. // http://code.google.com/p/isemail/ , which helped greatly in writing this parser.
  26. (function (global) {
  27. "use strict";
  28. function parse5322(opts) {
  29. // tokenizing functions
  30. function inStr() { return pos < len; }
  31. function curTok() { return parseString[pos]; }
  32. function getPos() { return pos; }
  33. function setPos(i) { pos = i; }
  34. function nextTok() { pos += 1; }
  35. function initialize() {
  36. pos = 0;
  37. len = parseString.length;
  38. }
  39. // parser helper functions
  40. function o(name, value) {
  41. return {
  42. name: name,
  43. tokens: value || "",
  44. semantic: value || "",
  45. children: []
  46. };
  47. }
  48. function wrap(name, ast) {
  49. var n;
  50. if (ast === null) { return null; }
  51. n = o(name);
  52. n.tokens = ast.tokens;
  53. n.semantic = ast.semantic;
  54. n.children.push(ast);
  55. return n;
  56. }
  57. function add(parent, child) {
  58. if (child !== null) {
  59. parent.tokens += child.tokens;
  60. parent.semantic += child.semantic;
  61. }
  62. parent.children.push(child);
  63. return parent;
  64. }
  65. function compareToken(fxnCompare) {
  66. var tok;
  67. if (!inStr()) { return null; }
  68. tok = curTok();
  69. if (fxnCompare(tok)) {
  70. nextTok();
  71. return o('token', tok);
  72. }
  73. return null;
  74. }
  75. function literal(lit) {
  76. return function literalFunc() {
  77. return wrap('literal', compareToken(function (tok) {
  78. return tok === lit;
  79. }));
  80. };
  81. }
  82. function and() {
  83. var args = arguments;
  84. return function andFunc() {
  85. var i, s, result, start;
  86. start = getPos();
  87. s = o('and');
  88. for (i = 0; i < args.length; i += 1) {
  89. result = args[i]();
  90. if (result === null) {
  91. setPos(start);
  92. return null;
  93. }
  94. add(s, result);
  95. }
  96. return s;
  97. };
  98. }
  99. function or() {
  100. var args = arguments;
  101. return function orFunc() {
  102. var i, result, start;
  103. start = getPos();
  104. for (i = 0; i < args.length; i += 1) {
  105. result = args[i]();
  106. if (result !== null) {
  107. return result;
  108. }
  109. setPos(start);
  110. }
  111. return null;
  112. };
  113. }
  114. function opt(prod) {
  115. return function optFunc() {
  116. var result, start;
  117. start = getPos();
  118. result = prod();
  119. if (result !== null) {
  120. return result;
  121. }
  122. else {
  123. setPos(start);
  124. return o('opt');
  125. }
  126. };
  127. }
  128. function invis(prod) {
  129. return function invisFunc() {
  130. var result = prod();
  131. if (result !== null) {
  132. result.semantic = "";
  133. }
  134. return result;
  135. };
  136. }
  137. function colwsp(prod) {
  138. return function collapseSemanticWhitespace() {
  139. var result = prod();
  140. if (result !== null && result.semantic.length > 0) {
  141. result.semantic = " ";
  142. }
  143. return result;
  144. };
  145. }
  146. function star(prod, minimum) {
  147. return function starFunc() {
  148. var s, result, count, start, min;
  149. start = getPos();
  150. s = o('star');
  151. count = 0;
  152. min = minimum === undefined ? 0 : minimum;
  153. while ((result = prod()) !== null) {
  154. count = count + 1;
  155. add(s, result);
  156. }
  157. if (count >= min) {
  158. return s;
  159. }
  160. else {
  161. setPos(start);
  162. return null;
  163. }
  164. };
  165. }
  166. // One expects names to get normalized like this:
  167. // " First Last " -> "First Last"
  168. // "First Last" -> "First Last"
  169. // "First Last" -> "First Last"
  170. function collapseWhitespace(s) {
  171. return s.replace(/([ \t]|\r\n)+/g, ' ').replace(/^\s*/, '').replace(/\s*$/, '');
  172. }
  173. // UTF-8 pseudo-production (RFC 6532)
  174. // RFC 6532 extends RFC 5322 productions to include UTF-8
  175. // using the following productions:
  176. // UTF8-non-ascii = UTF8-2 / UTF8-3 / UTF8-4
  177. // UTF8-2 = <Defined in Section 4 of RFC3629>
  178. // UTF8-3 = <Defined in Section 4 of RFC3629>
  179. // UTF8-4 = <Defined in Section 4 of RFC3629>
  180. //
  181. // For reference, the extended RFC 5322 productions are:
  182. // VCHAR =/ UTF8-non-ascii
  183. // ctext =/ UTF8-non-ascii
  184. // atext =/ UTF8-non-ascii
  185. // qtext =/ UTF8-non-ascii
  186. // dtext =/ UTF8-non-ascii
  187. function isUTF8NonAscii(tok) {
  188. // In JavaScript, we just deal directly with Unicode code points,
  189. // so we aren't checking individual bytes for UTF-8 encoding.
  190. // Just check that the character is non-ascii.
  191. return tok.charCodeAt(0) >= 128;
  192. }
  193. // common productions (RFC 5234)
  194. // http://tools.ietf.org/html/rfc5234
  195. // B.1. Core Rules
  196. // CR = %x0D
  197. // ; carriage return
  198. function cr() { return wrap('cr', literal('\r')()); }
  199. // CRLF = CR LF
  200. // ; Internet standard newline
  201. function crlf() { return wrap('crlf', and(cr, lf)()); }
  202. // DQUOTE = %x22
  203. // ; " (Double Quote)
  204. function dquote() { return wrap('dquote', literal('"')()); }
  205. // HTAB = %x09
  206. // ; horizontal tab
  207. function htab() { return wrap('htab', literal('\t')()); }
  208. // LF = %x0A
  209. // ; linefeed
  210. function lf() { return wrap('lf', literal('\n')()); }
  211. // SP = %x20
  212. function sp() { return wrap('sp', literal(' ')()); }
  213. // VCHAR = %x21-7E
  214. // ; visible (printing) characters
  215. function vchar() {
  216. return wrap('vchar', compareToken(function vcharFunc(tok) {
  217. var code = tok.charCodeAt(0);
  218. var accept = (0x21 <= code && code <= 0x7E);
  219. if (opts.rfc6532) {
  220. accept = accept || isUTF8NonAscii(tok);
  221. }
  222. return accept;
  223. }));
  224. }
  225. // WSP = SP / HTAB
  226. // ; white space
  227. function wsp() { return wrap('wsp', or(sp, htab)()); }
  228. // email productions (RFC 5322)
  229. // http://tools.ietf.org/html/rfc5322
  230. // 3.2.1. Quoted characters
  231. // quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
  232. function quotedPair() {
  233. var qp = wrap('quoted-pair',
  234. or(
  235. and(literal('\\'), or(vchar, wsp)),
  236. obsQP
  237. )());
  238. if (qp === null) { return null; }
  239. // a quoted pair will be two characters, and the "\" character
  240. // should be semantically "invisible" (RFC 5322 3.2.1)
  241. qp.semantic = qp.semantic[1];
  242. return qp;
  243. }
  244. // 3.2.2. Folding White Space and Comments
  245. // FWS = ([*WSP CRLF] 1*WSP) / obs-FWS
  246. function fws() {
  247. return wrap('fws', or(
  248. obsFws,
  249. and(
  250. opt(and(
  251. star(wsp),
  252. invis(crlf)
  253. )),
  254. star(wsp, 1)
  255. )
  256. )());
  257. }
  258. // ctext = %d33-39 / ; Printable US-ASCII
  259. // %d42-91 / ; characters not including
  260. // %d93-126 / ; "(", ")", or "\"
  261. // obs-ctext
  262. function ctext() {
  263. return wrap('ctext', or(
  264. function ctextFunc1() {
  265. return compareToken(function ctextFunc2(tok) {
  266. var code = tok.charCodeAt(0);
  267. var accept =
  268. (33 <= code && code <= 39) ||
  269. (42 <= code && code <= 91) ||
  270. (93 <= code && code <= 126);
  271. if (opts.rfc6532) {
  272. accept = accept || isUTF8NonAscii(tok);
  273. }
  274. return accept;
  275. });
  276. },
  277. obsCtext
  278. )());
  279. }
  280. // ccontent = ctext / quoted-pair / comment
  281. function ccontent() {
  282. return wrap('ccontent', or(ctext, quotedPair, comment)());
  283. }
  284. // comment = "(" *([FWS] ccontent) [FWS] ")"
  285. function comment() {
  286. return wrap('comment', and(
  287. literal('('),
  288. star(and(opt(fws), ccontent)),
  289. opt(fws),
  290. literal(')')
  291. )());
  292. }
  293. // CFWS = (1*([FWS] comment) [FWS]) / FWS
  294. function cfws() {
  295. return wrap('cfws', or(
  296. and(
  297. star(
  298. and(opt(fws), comment),
  299. 1
  300. ),
  301. opt(fws)
  302. ),
  303. fws
  304. )());
  305. }
  306. // 3.2.3. Atom
  307. //atext = ALPHA / DIGIT / ; Printable US-ASCII
  308. // "!" / "#" / ; characters not including
  309. // "$" / "%" / ; specials. Used for atoms.
  310. // "&" / "'" /
  311. // "*" / "+" /
  312. // "-" / "/" /
  313. // "=" / "?" /
  314. // "^" / "_" /
  315. // "`" / "{" /
  316. // "|" / "}" /
  317. // "~"
  318. function atext() {
  319. return wrap('atext', compareToken(function atextFunc(tok) {
  320. var accept =
  321. ('a' <= tok && tok <= 'z') ||
  322. ('A' <= tok && tok <= 'Z') ||
  323. ('0' <= tok && tok <= '9') ||
  324. (['!', '#', '$', '%', '&', '\'', '*', '+', '-', '/',
  325. '=', '?', '^', '_', '`', '{', '|', '}', '~'].indexOf(tok) >= 0);
  326. if (opts.rfc6532) {
  327. accept = accept || isUTF8NonAscii(tok);
  328. }
  329. return accept;
  330. }));
  331. }
  332. // atom = [CFWS] 1*atext [CFWS]
  333. function atom() {
  334. return wrap('atom', and(colwsp(opt(cfws)), star(atext, 1), colwsp(opt(cfws)))());
  335. }
  336. // dot-atom-text = 1*atext *("." 1*atext)
  337. function dotAtomText() {
  338. var s, maybeText;
  339. s = wrap('dot-atom-text', star(atext, 1)());
  340. if (s === null) { return s; }
  341. maybeText = star(and(literal('.'), star(atext, 1)))();
  342. if (maybeText !== null) {
  343. add(s, maybeText);
  344. }
  345. return s;
  346. }
  347. // dot-atom = [CFWS] dot-atom-text [CFWS]
  348. function dotAtom() {
  349. return wrap('dot-atom', and(invis(opt(cfws)), dotAtomText, invis(opt(cfws)))());
  350. }
  351. // 3.2.4. Quoted Strings
  352. // qtext = %d33 / ; Printable US-ASCII
  353. // %d35-91 / ; characters not including
  354. // %d93-126 / ; "\" or the quote character
  355. // obs-qtext
  356. function qtext() {
  357. return wrap('qtext', or(
  358. function qtextFunc1() {
  359. return compareToken(function qtextFunc2(tok) {
  360. var code = tok.charCodeAt(0);
  361. var accept =
  362. (33 === code) ||
  363. (35 <= code && code <= 91) ||
  364. (93 <= code && code <= 126);
  365. if (opts.rfc6532) {
  366. accept = accept || isUTF8NonAscii(tok);
  367. }
  368. return accept;
  369. });
  370. },
  371. obsQtext
  372. )());
  373. }
  374. // qcontent = qtext / quoted-pair
  375. function qcontent() {
  376. return wrap('qcontent', or(qtext, quotedPair)());
  377. }
  378. // quoted-string = [CFWS]
  379. // DQUOTE *([FWS] qcontent) [FWS] DQUOTE
  380. // [CFWS]
  381. function quotedString() {
  382. return wrap('quoted-string', and(
  383. invis(opt(cfws)),
  384. invis(dquote), star(and(opt(colwsp(fws)), qcontent)), opt(invis(fws)), invis(dquote),
  385. invis(opt(cfws))
  386. )());
  387. }
  388. // 3.2.5 Miscellaneous Tokens
  389. // word = atom / quoted-string
  390. function word() {
  391. return wrap('word', or(atom, quotedString)());
  392. }
  393. // phrase = 1*word / obs-phrase
  394. function phrase() {
  395. return wrap('phrase', or(obsPhrase, star(word, 1))());
  396. }
  397. // 3.4. Address Specification
  398. // address = mailbox / group
  399. function address() {
  400. return wrap('address', or(mailbox, group)());
  401. }
  402. // mailbox = name-addr / addr-spec
  403. function mailbox() {
  404. return wrap('mailbox', or(nameAddr, addrSpec)());
  405. }
  406. // name-addr = [display-name] angle-addr
  407. function nameAddr() {
  408. return wrap('name-addr', and(opt(displayName), angleAddr)());
  409. }
  410. // angle-addr = [CFWS] "<" addr-spec ">" [CFWS] /
  411. // obs-angle-addr
  412. function angleAddr() {
  413. return wrap('angle-addr', or(
  414. and(
  415. invis(opt(cfws)),
  416. literal('<'),
  417. addrSpec,
  418. literal('>'),
  419. invis(opt(cfws))
  420. ),
  421. obsAngleAddr
  422. )());
  423. }
  424. // group = display-name ":" [group-list] ";" [CFWS]
  425. function group() {
  426. return wrap('group', and(
  427. displayName,
  428. literal(':'),
  429. opt(groupList),
  430. literal(';'),
  431. invis(opt(cfws))
  432. )());
  433. }
  434. // display-name = phrase
  435. function displayName() {
  436. return wrap('display-name', function phraseFixedSemantic() {
  437. var result = phrase();
  438. if (result !== null) {
  439. result.semantic = collapseWhitespace(result.semantic);
  440. }
  441. return result;
  442. }());
  443. }
  444. // mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list
  445. function mailboxList() {
  446. return wrap('mailbox-list', or(
  447. and(
  448. mailbox,
  449. star(and(literal(','), mailbox))
  450. ),
  451. obsMboxList
  452. )());
  453. }
  454. // address-list = (address *("," address)) / obs-addr-list
  455. function addressList() {
  456. return wrap('address-list', or(
  457. and(
  458. address,
  459. star(and(literal(','), address))
  460. ),
  461. obsAddrList
  462. )());
  463. }
  464. // group-list = mailbox-list / CFWS / obs-group-list
  465. function groupList() {
  466. return wrap('group-list', or(
  467. mailboxList,
  468. invis(cfws),
  469. obsGroupList
  470. )());
  471. }
  472. // 3.4.1 Addr-Spec Specification
  473. // local-part = dot-atom / quoted-string / obs-local-part
  474. function localPart() {
  475. // note: quoted-string, dotAtom are proper subsets of obs-local-part
  476. // so we really just have to look for obsLocalPart, if we don't care about the exact parse tree
  477. return wrap('local-part', or(obsLocalPart, dotAtom, quotedString)());
  478. }
  479. // dtext = %d33-90 / ; Printable US-ASCII
  480. // %d94-126 / ; characters not including
  481. // obs-dtext ; "[", "]", or "\"
  482. function dtext() {
  483. return wrap('dtext', or(
  484. function dtextFunc1() {
  485. return compareToken(function dtextFunc2(tok) {
  486. var code = tok.charCodeAt(0);
  487. var accept =
  488. (33 <= code && code <= 90) ||
  489. (94 <= code && code <= 126);
  490. if (opts.rfc6532) {
  491. accept = accept || isUTF8NonAscii(tok);
  492. }
  493. return accept;
  494. });
  495. },
  496. obsDtext
  497. )()
  498. );
  499. }
  500. // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
  501. function domainLiteral() {
  502. return wrap('domain-literal', and(
  503. invis(opt(cfws)),
  504. literal('['),
  505. star(and(opt(fws), dtext)),
  506. opt(fws),
  507. literal(']'),
  508. invis(opt(cfws))
  509. )());
  510. }
  511. // domain = dot-atom / domain-literal / obs-domain
  512. function domain() {
  513. return wrap('domain', function domainCheckTLD() {
  514. var result = or(obsDomain, dotAtom, domainLiteral)();
  515. if (opts.rejectTLD) {
  516. if (result && result.semantic && result.semantic.indexOf('.') < 0) {
  517. return null;
  518. }
  519. }
  520. // strip all whitespace from domains
  521. if (result) {
  522. result.semantic = result.semantic.replace(/\s+/g, '');
  523. }
  524. return result;
  525. }());
  526. }
  527. // addr-spec = local-part "@" domain
  528. function addrSpec() {
  529. return wrap('addr-spec', and(
  530. localPart, literal('@'), domain
  531. )());
  532. }
  533. // 3.6.2 Originator Fields
  534. // Below we only parse the field body, not the name of the field
  535. // like "From:", "Sender:", or "Reply-To:". Other libraries that
  536. // parse email headers can parse those and defer to these productions
  537. // for the "RFC 5322" part.
  538. // RFC 6854 2.1. Replacement of RFC 5322, Section 3.6.2. Originator Fields
  539. // from = "From:" (mailbox-list / address-list) CRLF
  540. function fromSpec() {
  541. return wrap('from', or(
  542. mailboxList,
  543. addressList
  544. )());
  545. }
  546. // RFC 6854 2.1. Replacement of RFC 5322, Section 3.6.2. Originator Fields
  547. // sender = "Sender:" (mailbox / address) CRLF
  548. function senderSpec() {
  549. return wrap('sender', or(
  550. mailbox,
  551. address
  552. )());
  553. }
  554. // RFC 6854 2.1. Replacement of RFC 5322, Section 3.6.2. Originator Fields
  555. // reply-to = "Reply-To:" address-list CRLF
  556. function replyToSpec() {
  557. return wrap('reply-to', addressList());
  558. }
  559. // 4.1. Miscellaneous Obsolete Tokens
  560. // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
  561. // %d11 / ; characters that do not
  562. // %d12 / ; include the carriage
  563. // %d14-31 / ; return, line feed, and
  564. // %d127 ; white space characters
  565. function obsNoWsCtl() {
  566. return opts.strict ? null : wrap('obs-NO-WS-CTL', compareToken(function (tok) {
  567. var code = tok.charCodeAt(0);
  568. return ((1 <= code && code <= 8) ||
  569. (11 === code || 12 === code) ||
  570. (14 <= code && code <= 31) ||
  571. (127 === code));
  572. }));
  573. }
  574. // obs-ctext = obs-NO-WS-CTL
  575. function obsCtext() { return opts.strict ? null : wrap('obs-ctext', obsNoWsCtl()); }
  576. // obs-qtext = obs-NO-WS-CTL
  577. function obsQtext() { return opts.strict ? null : wrap('obs-qtext', obsNoWsCtl()); }
  578. // obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR)
  579. function obsQP() {
  580. return opts.strict ? null : wrap('obs-qp', and(
  581. literal('\\'),
  582. or(literal('\0'), obsNoWsCtl, lf, cr)
  583. )());
  584. }
  585. // obs-phrase = word *(word / "." / CFWS)
  586. function obsPhrase() {
  587. if (opts.strict ) return null;
  588. return opts.atInDisplayName ? wrap('obs-phrase', and(
  589. word,
  590. star(or(word, literal('.'), literal('@'), colwsp(cfws)))
  591. )()) :
  592. wrap('obs-phrase', and(
  593. word,
  594. star(or(word, literal('.'), colwsp(cfws)))
  595. )());
  596. }
  597. // 4.2. Obsolete Folding White Space
  598. // NOTE: read the errata http://www.rfc-editor.org/errata_search.php?rfc=5322&eid=1908
  599. // obs-FWS = 1*([CRLF] WSP)
  600. function obsFws() {
  601. return opts.strict ? null : wrap('obs-FWS', star(
  602. and(invis(opt(crlf)), wsp),
  603. 1
  604. )());
  605. }
  606. // 4.4. Obsolete Addressing
  607. // obs-angle-addr = [CFWS] "<" obs-route addr-spec ">" [CFWS]
  608. function obsAngleAddr() {
  609. return opts.strict ? null : wrap('obs-angle-addr', and(
  610. invis(opt(cfws)),
  611. literal('<'),
  612. obsRoute,
  613. addrSpec,
  614. literal('>'),
  615. invis(opt(cfws))
  616. )());
  617. }
  618. // obs-route = obs-domain-list ":"
  619. function obsRoute() {
  620. return opts.strict ? null : wrap('obs-route', and(
  621. obsDomainList,
  622. literal(':')
  623. )());
  624. }
  625. // obs-domain-list = *(CFWS / ",") "@" domain
  626. // *("," [CFWS] ["@" domain])
  627. function obsDomainList() {
  628. return opts.strict ? null : wrap('obs-domain-list', and(
  629. star(or(invis(cfws), literal(','))),
  630. literal('@'),
  631. domain,
  632. star(and(
  633. literal(','),
  634. invis(opt(cfws)),
  635. opt(and(literal('@'), domain))
  636. ))
  637. )());
  638. }
  639. // obs-mbox-list = *([CFWS] ",") mailbox *("," [mailbox / CFWS])
  640. function obsMboxList() {
  641. return opts.strict ? null : wrap('obs-mbox-list', and(
  642. star(and(
  643. invis(opt(cfws)),
  644. literal(',')
  645. )),
  646. mailbox,
  647. star(and(
  648. literal(','),
  649. opt(and(
  650. mailbox,
  651. invis(cfws)
  652. ))
  653. ))
  654. )());
  655. }
  656. // obs-addr-list = *([CFWS] ",") address *("," [address / CFWS])
  657. function obsAddrList() {
  658. return opts.strict ? null : wrap('obs-addr-list', and(
  659. star(and(
  660. invis(opt(cfws)),
  661. literal(',')
  662. )),
  663. address,
  664. star(and(
  665. literal(','),
  666. opt(and(
  667. address,
  668. invis(cfws)
  669. ))
  670. ))
  671. )());
  672. }
  673. // obs-group-list = 1*([CFWS] ",") [CFWS]
  674. function obsGroupList() {
  675. return opts.strict ? null : wrap('obs-group-list', and(
  676. star(and(
  677. invis(opt(cfws)),
  678. literal(',')
  679. ), 1),
  680. invis(opt(cfws))
  681. )());
  682. }
  683. // obs-local-part = word *("." word)
  684. function obsLocalPart() {
  685. return opts.strict ? null : wrap('obs-local-part', and(word, star(and(literal('.'), word)))());
  686. }
  687. // obs-domain = atom *("." atom)
  688. function obsDomain() {
  689. return opts.strict ? null : wrap('obs-domain', and(atom, star(and(literal('.'), atom)))());
  690. }
  691. // obs-dtext = obs-NO-WS-CTL / quoted-pair
  692. function obsDtext() {
  693. return opts.strict ? null : wrap('obs-dtext', or(obsNoWsCtl, quotedPair)());
  694. }
  695. /////////////////////////////////////////////////////
  696. // ast analysis
  697. function findNode(name, root) {
  698. var i, stack, node;
  699. if (root === null || root === undefined) { return null; }
  700. stack = [root];
  701. while (stack.length > 0) {
  702. node = stack.pop();
  703. if (node.name === name) {
  704. return node;
  705. }
  706. for (i = node.children.length - 1; i >= 0; i -= 1) {
  707. stack.push(node.children[i]);
  708. }
  709. }
  710. return null;
  711. }
  712. function findAllNodes(name, root) {
  713. var i, stack, node, result;
  714. if (root === null || root === undefined) { return null; }
  715. stack = [root];
  716. result = [];
  717. while (stack.length > 0) {
  718. node = stack.pop();
  719. if (node.name === name) {
  720. result.push(node);
  721. }
  722. for (i = node.children.length - 1; i >= 0; i -= 1) {
  723. stack.push(node.children[i]);
  724. }
  725. }
  726. return result;
  727. }
  728. function findAllNodesNoChildren(names, root) {
  729. var i, stack, node, result, namesLookup;
  730. if (root === null || root === undefined) { return null; }
  731. stack = [root];
  732. result = [];
  733. namesLookup = {};
  734. for (i = 0; i < names.length; i += 1) {
  735. namesLookup[names[i]] = true;
  736. }
  737. while (stack.length > 0) {
  738. node = stack.pop();
  739. if (node.name in namesLookup) {
  740. result.push(node);
  741. // don't look at children (hence findAllNodesNoChildren)
  742. } else {
  743. for (i = node.children.length - 1; i >= 0; i -= 1) {
  744. stack.push(node.children[i]);
  745. }
  746. }
  747. }
  748. return result;
  749. }
  750. function giveResult(ast) {
  751. var addresses, groupsAndMailboxes, i, groupOrMailbox, result;
  752. if (ast === null) {
  753. return null;
  754. }
  755. addresses = [];
  756. // An address is a 'group' (i.e. a list of mailboxes) or a 'mailbox'.
  757. groupsAndMailboxes = findAllNodesNoChildren(['group', 'mailbox'], ast);
  758. for (i = 0; i < groupsAndMailboxes.length; i += 1) {
  759. groupOrMailbox = groupsAndMailboxes[i];
  760. if (groupOrMailbox.name === 'group') {
  761. addresses.push(giveResultGroup(groupOrMailbox));
  762. } else if (groupOrMailbox.name === 'mailbox') {
  763. addresses.push(giveResultMailbox(groupOrMailbox));
  764. }
  765. }
  766. result = {
  767. ast: ast,
  768. addresses: addresses,
  769. };
  770. if (opts.simple) {
  771. result = simplifyResult(result);
  772. }
  773. if (opts.oneResult) {
  774. return oneResult(result);
  775. }
  776. if (opts.simple) {
  777. return result && result.addresses;
  778. } else {
  779. return result;
  780. }
  781. }
  782. function giveResultGroup(group) {
  783. var i;
  784. var groupName = findNode('display-name', group);
  785. var groupResultMailboxes = [];
  786. var mailboxes = findAllNodesNoChildren(['mailbox'], group);
  787. for (i = 0; i < mailboxes.length; i += 1) {
  788. groupResultMailboxes.push(giveResultMailbox(mailboxes[i]));
  789. }
  790. return {
  791. node: group,
  792. parts: {
  793. name: groupName,
  794. },
  795. type: group.name, // 'group'
  796. name: grabSemantic(groupName),
  797. addresses: groupResultMailboxes,
  798. };
  799. }
  800. function giveResultMailbox(mailbox) {
  801. var name = findNode('display-name', mailbox);
  802. var aspec = findNode('addr-spec', mailbox);
  803. var cfws = findAllNodes('cfws', mailbox);
  804. var comments = findAllNodesNoChildren(['comment'], mailbox);
  805. var local = findNode('local-part', aspec);
  806. var domain = findNode('domain', aspec);
  807. return {
  808. node: mailbox,
  809. parts: {
  810. name: name,
  811. address: aspec,
  812. local: local,
  813. domain: domain,
  814. comments: cfws
  815. },
  816. type: mailbox.name, // 'mailbox'
  817. name: grabSemantic(name),
  818. address: grabSemantic(aspec),
  819. local: grabSemantic(local),
  820. domain: grabSemantic(domain),
  821. comments: concatComments(comments),
  822. groupName: grabSemantic(mailbox.groupName),
  823. };
  824. }
  825. function grabSemantic(n) {
  826. return n !== null && n !== undefined ? n.semantic : null;
  827. }
  828. function simplifyResult(result) {
  829. var i;
  830. if (result && result.addresses) {
  831. for (i = 0; i < result.addresses.length; i += 1) {
  832. delete result.addresses[i].node;
  833. }
  834. }
  835. return result;
  836. }
  837. function concatComments(comments) {
  838. var result = '';
  839. if (comments) {
  840. for (var i = 0; i < comments.length; i += 1) {
  841. result += grabSemantic(comments[i]);
  842. }
  843. }
  844. return result;
  845. }
  846. function oneResult(result) {
  847. if (!result) { return null; }
  848. if (!opts.partial && result.addresses.length > 1) { return null; }
  849. return result.addresses && result.addresses[0];
  850. }
  851. /////////////////////////////////////////////////////
  852. var parseString, pos, len, parsed, startProduction;
  853. opts = handleOpts(opts, {});
  854. if (opts === null) { return null; }
  855. parseString = opts.input;
  856. startProduction = {
  857. 'address': address,
  858. 'address-list': addressList,
  859. 'angle-addr': angleAddr,
  860. 'from': fromSpec,
  861. 'group': group,
  862. 'mailbox': mailbox,
  863. 'mailbox-list': mailboxList,
  864. 'reply-to': replyToSpec,
  865. 'sender': senderSpec,
  866. }[opts.startAt] || addressList;
  867. if (!opts.strict) {
  868. initialize();
  869. opts.strict = true;
  870. parsed = startProduction(parseString);
  871. if (opts.partial || !inStr()) {
  872. return giveResult(parsed);
  873. }
  874. opts.strict = false;
  875. }
  876. initialize();
  877. parsed = startProduction(parseString);
  878. if (!opts.partial && inStr()) { return null; }
  879. return giveResult(parsed);
  880. }
  881. function parseOneAddressSimple(opts) {
  882. return parse5322(handleOpts(opts, {
  883. oneResult: true,
  884. rfc6532: true,
  885. simple: true,
  886. startAt: 'address-list',
  887. }));
  888. }
  889. function parseAddressListSimple(opts) {
  890. return parse5322(handleOpts(opts, {
  891. rfc6532: true,
  892. simple: true,
  893. startAt: 'address-list',
  894. }));
  895. }
  896. function parseFromSimple(opts) {
  897. return parse5322(handleOpts(opts, {
  898. rfc6532: true,
  899. simple: true,
  900. startAt: 'from',
  901. }));
  902. }
  903. function parseSenderSimple(opts) {
  904. return parse5322(handleOpts(opts, {
  905. oneResult: true,
  906. rfc6532: true,
  907. simple: true,
  908. startAt: 'sender',
  909. }));
  910. }
  911. function parseReplyToSimple(opts) {
  912. return parse5322(handleOpts(opts, {
  913. rfc6532: true,
  914. simple: true,
  915. startAt: 'reply-to',
  916. }));
  917. }
  918. function handleOpts(opts, defs) {
  919. function isString(str) {
  920. return Object.prototype.toString.call(str) === '[object String]';
  921. }
  922. function isObject(o) {
  923. return o === Object(o);
  924. }
  925. function isNullUndef(o) {
  926. return o === null || o === undefined;
  927. }
  928. var defaults, o;
  929. if (isString(opts)) {
  930. opts = { input: opts };
  931. } else if (!isObject(opts)) {
  932. return null;
  933. }
  934. if (!isString(opts.input)) { return null; }
  935. if (!defs) { return null; }
  936. defaults = {
  937. oneResult: false,
  938. partial: false,
  939. rejectTLD: false,
  940. rfc6532: false,
  941. simple: false,
  942. startAt: 'address-list',
  943. strict: false,
  944. atInDisplayName: false
  945. };
  946. for (o in defaults) {
  947. if (isNullUndef(opts[o])) {
  948. opts[o] = !isNullUndef(defs[o]) ? defs[o] : defaults[o];
  949. }
  950. }
  951. return opts;
  952. }
  953. parse5322.parseOneAddress = parseOneAddressSimple;
  954. parse5322.parseAddressList = parseAddressListSimple;
  955. parse5322.parseFrom = parseFromSimple;
  956. parse5322.parseSender = parseSenderSimple;
  957. parse5322.parseReplyTo = parseReplyToSimple;
  958. if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
  959. module.exports = parse5322;
  960. } else {
  961. global.emailAddresses = parse5322;
  962. }
  963. }(this));