├── .codeclimate.yml ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── codeql.yml │ └── publish.yml ├── .gitignore ├── .gitmodules ├── .prettierrc.yml ├── CHANGELOG.md ├── CONTRIBUTORS.md ├── README.md ├── eslint.config.mjs ├── index.js ├── package.json └── test ├── Address.js ├── basic.js ├── emails.txt └── functions.js /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | eslint: 3 | enabled: true 4 | channel: 'eslint-9' 5 | config: 6 | config: 'eslint.config.mjs' 7 | 8 | ratings: 9 | paths: 10 | - '**.js' 11 | 12 | checks: 13 | method-complexity: 14 | config: 15 | threshold: 10 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # The documentation: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: 'npm' 7 | directory: '/' 8 | schedule: 9 | interval: 'weekly' 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request, push] 4 | 5 | env: 6 | CI: true 7 | 8 | jobs: 9 | lint: 10 | uses: haraka/.github/.github/workflows/lint.yml@master 11 | 12 | coverage: 13 | uses: haraka/.github/.github/workflows/coverage.yml@master 14 | secrets: inherit 15 | 16 | test: 17 | needs: [lint] 18 | uses: haraka/.github/.github/workflows/ubuntu.yml@master 19 | 20 | windows: 21 | needs: [lint] 22 | uses: haraka/.github/.github/workflows/windows.yml@master 23 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '18 7 * * 4' 11 | 12 | jobs: 13 | codeql: 14 | uses: haraka/.github/.github/workflows/codeql.yml@master 15 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - package.json 9 | 10 | env: 11 | CI: true 12 | 13 | jobs: 14 | publish: 15 | uses: haraka/.github/.github/workflows/publish.yml@master 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | 30 | # Optional npm cache directory 31 | .npm 32 | .npmrc 33 | 34 | # Optional REPL history 35 | .node_repl_history 36 | 37 | package-lock.json 38 | .nyc_output 39 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule ".release"] 2 | path = .release 3 | url = git@github.com:msimerson/.release.git 4 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | semi: false 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The format is based on [Keep a Changelog](https://keepachangelog.com/). 4 | 5 | ### Unreleased 6 | 7 | ### [2.2.3] - 2025-01-13 8 | 9 | - dep(eslint): upgrade to v9 10 | 11 | ### [2.2.2] - 2024-04-29 12 | 13 | - doc(CONTRIBUTORS): added 14 | - dep(eslint-config): bumped to 1.1.5 15 | - chore: prettier 16 | 17 | ### [2.2.1] - 2024-04-08 18 | 19 | - dep: eslint-plugin-haraka -> @haraka/eslint-config 20 | - populate `[files]` in package.json. Delete .npmignore. 21 | - updated scripts{} in package.json 22 | - lint: remove duplicate / stale rules from .eslintrc 23 | - prettier (except index) 24 | 25 | ### [2.2.0] - 2024-02-23 26 | 27 | - feat: option to allow comma in display name #52 28 | - dep(email-addresses): bump from 4.0.0 to 5.0.0 #58 29 | - chore: replace a couple regex with slice (perf & sec) #63 30 | - test: a few more tests to boost coverage #63 31 | - test: drop node 10, add node 16 #61 32 | - ci: restore GH workflow for PRs #57 33 | - ci: add dependabot.yml #55 34 | - doc: add inline documentation for parse #60 35 | - doc(Changes): make PR #s into links #54 36 | - doc(README): add result of console.logs #56 37 | - doc(README): add links to RFC 2822, 5322 #53 38 | - doc(README): enabled syntax highlighting with code fences #51 39 | 40 | ### 2.1.0 - 2021-02-26 41 | 42 | - make parse accept an options object as second argument 43 | - allow comma (,) in display name, default off [#52](https://github.com/haraka/node-address-rfc2822/pull/52) 44 | 45 | ### 2.0.6 - 2020-11-17 46 | 47 | - replace travis/appveyor CI tests with Github Actions [#48](https://github.com/haraka/node-address-rfc2822/pull/48) 48 | - test: when splitting lines, use os.EOL 49 | - allow @ symbol in display name [#47](https://github.com/haraka/node-address-rfc2822/pull/47) 50 | 51 | ### 2.0.5 - 2020-06-02 52 | 53 | - update email-addresses to 3.1.0 [#46](https://github.com/haraka/node-address-rfc2822/pull/46) 54 | - test framework: nodeunit -> mocha 55 | 56 | ### 2.0.4 - 2018-06-29 57 | 58 | - throw a proper error object, not a string. 59 | 60 | ### 2.0.3 - 2018-03-01 61 | 62 | - use es6 classes 63 | - export the Address class [#29](https://github.com/haraka/node-address-rfc2822/pull/29) 64 | 65 | ### 2.0.2 - 2018-02-24 66 | 67 | - Fix a possible regexp backtracking DoS [#28](https://github.com/haraka/node-address-rfc2822/pull/28) 68 | 69 | ### 2.0.1 - 2017-06-26 70 | 71 | - trim the line in parse() [#24](https://github.com/haraka/node-address-rfc2822/pull/24) 72 | 73 | ### 1.0.2 - 2016-06-16 74 | 75 | - updated for eslint 4 compat [#23](https://github.com/haraka/node-address-rfc2822/pull/23) 76 | - use email-addresses for parser [#20](https://github.com/haraka/node-address-rfc2822/pull/20) 77 | 78 | ### 1.0.1 - 2016-09-23 79 | 80 | - use native to[lower|upper]Case functions vs regex 81 | - remove node 0.12 testing 82 | - remove node 0.10, 5, add node 6 83 | - throw error on nothing to parse 84 | 85 | ### 1.0.0 - 2016-02-23 86 | 87 | - Initial implementation 88 | 89 | [2.2.0]: https://github.com/haraka/node-address-rfc2822/releases/tag/v2.2.0 90 | [2.2.1]: https://github.com/haraka/node-address-rfc2822/releases/tag/v2.2.1 91 | [2.0.6]: https://github.com/haraka/node-address-rfc2822/releases/tag/2.0.6 92 | [0.0.2]: https://github.com/haraka/node-address-rfc2822/releases/tag/v0.0.2 93 | [2.2.2]: https://github.com/haraka/node-address-rfc2822/releases/tag/v2.2.2 94 | [2.2.3]: https://github.com/haraka/node-address-rfc2822/releases/tag/v2.2.3 95 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | This handcrafted artisinal software is brought to you by: 4 | 5 | |
msimerson (40) |
baudehlo (13) |
osm (2) |
0xflotus (1) |
diasbruno (1) |
kesselb (1) |
fionawhim (1) | 6 | | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | 7 | |
markstos (1) |
dwali (1) | 8 | 9 | this file is generated by [.release](https://github.com/msimerson/.release). 10 | Contribute to this project to get your GitHub profile included here. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status][ci-img]][ci-url] 2 | [![Code Climate][clim-img]][clim-url] 3 | [![Coverage Status][cov-img]][cov-url] 4 | 5 | # address-rfc2822 6 | 7 | Parser for RFC 2822 & 5322 (Header) format email addresses. 8 | 9 | This module parses RFC 2822 headers containing addresses such as From, To, CC, and BCC headers. 10 | 11 | It is almost a direct port of the perl module Mail::Address and I'm grateful to the original authors of that module for the clean code and the tests. 12 | 13 | ## Installation 14 | 15 | `npm install address-rfc2822` 16 | 17 | ## Usage 18 | 19 | 20 | ```js 21 | const addrparser = require('address-rfc2822') 22 | 23 | const addresses = addrparser.parse('Matt Sergeant ') 24 | const address = addresses[0] 25 | 26 | console.log(`Email address: ${address.address}`) // helpme+npm@gmail.com 27 | console.log(`Email name: ${address.name()}`) // Matt Sergeant 28 | console.log(`Reformatted: ${address.format()}`) // Matt Sergeant 29 | console.log(`User part: ${address.user()}`) // helpme+npm 30 | console.log(`Host part: ${address.host()}`) // gmail.com 31 | ``` 32 | 33 | ## More Info 34 | 35 | - [RFC 2822](https://tools.ietf.org/html/rfc2822) 36 | - [RFC 5322](https://tools.ietf.org/html/rfc5322) 37 | 38 | ## License 39 | 40 | This module is MIT licensed. 41 | 42 | [ci-img]: https://github.com/haraka/node-address-rfc2822/actions/workflows/ci.yml/badge.svg 43 | [ci-url]: https://github.com/haraka/node-address-rfc2822/actions/workflows/ci.yml 44 | [cov-img]: https://codecov.io/github/haraka/node-address-rfc2822/coverage.svg 45 | [cov-url]: https://codecov.io/github/haraka/node-address-rfc2822?branch=master 46 | [clim-img]: https://codeclimate.com/github/haraka/haraka-plugin-template/badges/gpa.svg 47 | [clim-url]: https://codeclimate.com/github/haraka/haraka-plugin-template 48 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from 'globals' 2 | import path from 'node:path' 3 | import { fileURLToPath } from 'node:url' 4 | import js from '@eslint/js' 5 | import { FlatCompat } from '@eslint/eslintrc' 6 | 7 | const __filename = fileURLToPath(import.meta.url) 8 | const __dirname = path.dirname(__filename) 9 | const compat = new FlatCompat({ 10 | baseDirectory: __dirname, 11 | recommendedConfig: js.configs.recommended, 12 | allConfig: js.configs.all, 13 | }) 14 | 15 | export default [ 16 | ...compat.extends('@haraka'), 17 | { 18 | languageOptions: { 19 | globals: { 20 | ...globals.node, 21 | ...globals.mocha, 22 | }, 23 | }, 24 | }, 25 | ] 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ea_lib = require('email-addresses') 4 | 5 | /** 6 | * @param {string} line string to parse 7 | * @param {object|null} opts 8 | * @param {string|null} opts.startAt Start the parser at one of address, address-list, angle-addr, from, group, mailbox, mailbox-list, reply-to, sender. Default: address-list 9 | * @param {boolean|null} opts.atInDisplayName Allow the @ character in the display name of the email address. Default: true 10 | * @param {boolean|null} opts.allowCommaInDisplayName - Allow the , character in the display name of the email address. Default: false 11 | * @returns {Address[]} 12 | * @throws {Error} if input string is empty 13 | * @throws {Error} if no addresses are found 14 | */ 15 | exports.parse = function parse(line, opts = null) { 16 | if (!line) throw new Error('Nothing to parse') 17 | 18 | line = line.trim() 19 | 20 | const defaultOpts = { 21 | startAt: null, 22 | allowAtInDisplayName: true, 23 | allowCommaInDisplayName: false, 24 | } 25 | 26 | const { startAt, allowAtInDisplayName, allowCommaInDisplayName } = 27 | typeof opts === 'object' 28 | ? Object.assign({}, defaultOpts, opts) 29 | : Object.assign({}, defaultOpts, { startAt: opts }) 30 | 31 | const addr = ea_lib({ 32 | input: line, 33 | rfc6532: true, // unicode 34 | partial: false, // return failed parses 35 | simple: false, // simple AST 36 | strict: false, // turn off obs- features in the rfc 37 | rejectTLD: false, // domains require a "." 38 | startAt: startAt || null, 39 | atInDisplayName: allowAtInDisplayName, 40 | commaInDisplayName: allowCommaInDisplayName, 41 | }) 42 | 43 | if (!addr || addr.length === 0) throw new Error('No results') 44 | 45 | return addr.addresses.map(map_addresses) 46 | } 47 | 48 | function map_addresses(adr) { 49 | if (adr.type === 'group') { 50 | return new Group(adr.name, adr.addresses.map(map_addresses)) 51 | } 52 | let comments 53 | if (adr.parts.comments) { 54 | comments = adr.parts.comments 55 | .map((c) => c.tokens.trim()) 56 | .join(' ') 57 | .trim() 58 | // if (comments.length) { 59 | // comments = '(' + comments + ')'; 60 | // } 61 | } 62 | let l = adr.local 63 | if (!adr.name && /:/.test(l)) l = `"${l}"` 64 | return new Address(adr.name, `${l}@${adr.domain}`, comments) 65 | } 66 | 67 | exports.parseFrom = function (line) { 68 | return exports.parse(line, 'from') 69 | } 70 | 71 | exports.parseSender = function (line) { 72 | return exports.parse(line, 'sender') 73 | } 74 | 75 | exports.parseReplyTo = function (line) { 76 | return exports.parse(line, 'reply-to') 77 | } 78 | 79 | class Group { 80 | constructor(display_name, addresses) { 81 | this.phrase = display_name 82 | this.addresses = addresses 83 | } 84 | 85 | format() { 86 | return `${this.phrase}:${this.addresses.map((a) => a.format()).join(',')}` 87 | } 88 | 89 | name() { 90 | let phrase = this.phrase 91 | 92 | if (!(phrase && phrase.length)) phrase = this.comment 93 | 94 | return _extract_name(phrase) 95 | } 96 | } 97 | 98 | class Address { 99 | constructor(phrase, address, comment) { 100 | this.phrase = phrase || '' 101 | this.address = address || '' 102 | this.comment = comment || '' 103 | } 104 | 105 | host() { 106 | const match = /.*@(.*)$/.exec(this.address) 107 | if (!match) return null 108 | return match[1] 109 | } 110 | 111 | user() { 112 | const match = /^(.*)@/.exec(this.address) 113 | if (!match) return null 114 | return match[1] 115 | } 116 | 117 | format() { 118 | const phrase = this.phrase 119 | const email = this.address 120 | let comment = this.comment 121 | 122 | const addr = [] 123 | const atext = new RegExp("^[\\-\\w !#$%&'*+/=?^`{|}~]+$") 124 | 125 | if (phrase && phrase.length) { 126 | addr.push( 127 | atext.test(phrase.trim()) 128 | ? phrase 129 | : _quote_no_esc(phrase) 130 | ? phrase 131 | : `"${phrase}"`, 132 | ) 133 | 134 | if (email && email.length) { 135 | addr.push(`<${email}>`) 136 | } 137 | } else if (email && email.length) { 138 | addr.push(email) 139 | } 140 | 141 | if (comment && /\S/.test(comment)) { 142 | comment = comment.replace(/^\s*\(?/, '(').replace(/\)?\s*$/, ')') 143 | } 144 | 145 | if (comment && comment.length) { 146 | addr.push(comment) 147 | } 148 | 149 | return addr.join(' ') 150 | } 151 | 152 | name() { 153 | let phrase = this.phrase 154 | const addr = this.address 155 | 156 | if (!(phrase && phrase.length)) { 157 | phrase = this.comment 158 | } 159 | 160 | let name = _extract_name(phrase) 161 | 162 | // first.last@domain address 163 | if (name === '') { 164 | const match = /([^%.@_]+([._][^%.@_]+)+)[@%]/.exec(addr) 165 | if (match) { 166 | name = match[1].replace(/[._]+/g, ' ') 167 | name = _extract_name(name) 168 | } 169 | } 170 | 171 | if (name === '' && /\/g=/i.test(addr)) { 172 | // X400 style address 173 | let match = /\/g=([^/]*)/i.exec(addr) 174 | const f = match[1] 175 | match = /\/s=([^/]*)/i.exec(addr) 176 | const l = match[1] 177 | name = _extract_name(`${f} ${l}`) 178 | } 179 | 180 | return name 181 | } 182 | } 183 | 184 | exports.Address = Address 185 | 186 | // This is because JS regexps have no equivalent of 187 | // zero-width negative look-behind assertion for: /(? { 11 | const lines = rows.split(EOLRE) 12 | // console.log(lines) 13 | if (lines[0] === '') lines.shift() 14 | return lines.filter((l) => { 15 | return !/^#/.test(l) 16 | }) 17 | }) 18 | 19 | describe('parse', function () { 20 | it('throws on empty line', function () { 21 | assert.throws( 22 | () => { 23 | parse('') 24 | }, 25 | { message: 'Nothing to parse' }, 26 | ) 27 | }) 28 | 29 | tests.forEach(function (test) { 30 | it(test[0], function () { 31 | const details = {} 32 | details.format = test[1] 33 | if (test[2]) details.name = test[2] 34 | 35 | const parsed = parse(test[0])[0] 36 | // console.log(`Parsed: ${parsed}`); 37 | 38 | for (const k in details) { 39 | assert.equal( 40 | parsed[k](), 41 | details[k], 42 | `Test '${k}' for '${parsed[k]()}' = '${details[k]}' from ${JSON.stringify(parsed)}`, 43 | ) 44 | } 45 | }) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /test/emails.txt: -------------------------------------------------------------------------------- 1 | foo@example 2 | "foo@example" 3 | Foo@Example 4 | 5 | "Joe & J. Harvey" , JJV @ BBN 6 | "Joe & J. Harvey" 7 | Joe & J. Harvey 8 | 9 | "Joe & J. Harvey" 10 | "Joe & J. Harvey" 11 | Joe & J. Harvey 12 | 13 | JJV @ BBN 14 | JJV@BBN 15 | 16 | 17 | "spickett@tiac.net" 18 | "spickett@tiac.net" 19 | Spickett@Tiac.Net 20 | 21 | rls@intgp8.ih.att.com (-Schieve,R.L.) 22 | rls@intgp8.ih.att.com (-Schieve,R.L.) 23 | R.L. -Schieve 24 | 25 | #bodg fred@tiuk.ti.com 26 | #bodg 27 | # 28 | # 29 | m-sterni@mars.dsv.su.se 30 | m-sterni@mars.dsv.su.se 31 | 32 | 33 | jrh%cup.portal.com@portal.unix.portal.com 34 | jrh%cup.portal.com@portal.unix.portal.com 35 | Cup Portal Com 36 | 37 | astrachan@austlcm.sps.mot.com ('paul astrachan/xvt3') 38 | astrachan@austlcm.sps.mot.com ('paul astrachan/xvt3') 39 | Paul Astrachan/Xvt3 40 | 41 | TWINE57%SDELVB.decnet@SNYBUFVA.CS.SNYBUF.EDU (JAMES R. TWINE - THE NERD) 42 | TWINE57%SDELVB.decnet@SNYBUFVA.CS.SNYBUF.EDU (JAMES R. TWINE - THE NERD) 43 | James R. Twine - The Nerd 44 | 45 | David Apfelbaum 46 | David Apfelbaum 47 | David Apfelbaum 48 | 49 | "JAMES R. TWINE - THE NERD" 50 | "JAMES R. TWINE - THE NERD" 51 | James R. Twine - The Nerd 52 | 53 | bilsby@signal.dra (Fred C. M. Bilsby) 54 | bilsby@signal.dra (Fred C. M. Bilsby) 55 | Fred C. M. Bilsby 56 | 57 | /G=Owen/S=Smith/O=SJ-Research/ADMD=INTERSPAN/C=GB/@mhs-relay.ac.uk 58 | /G=Owen/S=Smith/O=SJ-Research/ADMD=INTERSPAN/C=GB/@mhs-relay.ac.uk 59 | Owen Smith 60 | 61 | apardon@rc1.vub.ac.be (Antoon Pardon) 62 | apardon@rc1.vub.ac.be (Antoon Pardon) 63 | Antoon Pardon 64 | 65 | "Stephen Burke, Liverpool" 66 | "Stephen Burke, Liverpool" 67 | Stephen Burke 68 | 69 | Andy Duplain 70 | Andy Duplain 71 | Andy Duplain 72 | 73 | Gunnar Zoetl 74 | Gunnar Zoetl 75 | Gunnar Zoetl 76 | 77 | The Newcastle Info-Server 78 | The Newcastle Info-Server 79 | The Newcastle Info-Server 80 | 81 | wsinda@nl.tue.win.info (Dick Alstein) 82 | wsinda@nl.tue.win.info (Dick Alstein) 83 | Dick Alstein 84 | 85 | mserv@rusmv1.rus.uni-stuttgart.de (RUS Mail Server) 86 | mserv@rusmv1.rus.uni-stuttgart.de (RUS Mail Server) 87 | RUS Mail Server 88 | 89 | Suba.Peddada@eng.sun.com (Suba Peddada [CONTRACTOR]) 90 | Suba.Peddada@eng.sun.com (Suba Peddada [CONTRACTOR]) 91 | Suba Peddada 92 | 93 | ftpmail-adm@info2.rus.uni-stuttgart.de 94 | ftpmail-adm@info2.rus.uni-stuttgart.de 95 | 96 | 97 | Paul Manser (0032 memo) 98 | Paul Manser (0032 memo) 99 | Paul Manser 100 | 101 | "gregg (g.) woodcock" 102 | "gregg (g.) woodcock" 103 | Gregg Woodcock 104 | 105 | Clive Bittlestone 106 | Clive Bittlestone 107 | Clive Bittlestone 108 | 109 | Graham.Barr@tiuk.ti.com 110 | Graham.Barr@tiuk.ti.com 111 | Graham Barr 112 | 113 | #"Graham Bisset, UK Net Support, +44 224 728109" 114 | #"Graham Bisset, UK Net Support, +44 224 728109" 115 | #Graham Bisset 116 | # 117 | #a909937 (Graham Barr (0004 bodg)) 118 | #a909937 (Graham Barr (0004 bodg)) 119 | #Graham Barr 120 | # 121 | a909062@node_cb83.node_cb83 (Colin x Maytum (0013 bro5)) 122 | a909062@node_cb83.node_cb83 (Colin x Maytum (0013 bro5)) 123 | Colin x Maytum 124 | 125 | a909062@node_cb83.node_cb83 (Colin Maytum (0013 bro5)) 126 | a909062@node_cb83.node_cb83 (Colin Maytum (0013 bro5)) 127 | Colin Maytum 128 | 129 | Derek.Roskell%dero@msg.ti.com 130 | Derek.Roskell%dero@msg.ti.com 131 | Derek Roskell 132 | 133 | ":sysmail"@ Some-Group. Some-Org, Muhammed.(I am the greatest) Ali @(the)Vegas.WBA 134 | ":sysmail"@Some-Group.Some-Org 135 | 136 | 137 | david d `zoo' zuhn 138 | david d `zoo' zuhn 139 | David D `Zoo' Zuhn 140 | 141 | "Christopher S. Arthur" 142 | "Christopher S. Arthur" 143 | Christopher S. Arthur 144 | 145 | Jeffrey A Law 146 | Jeffrey A Law 147 | Jeffrey A Law 148 | 149 | lidl@uunet.uu.net (Kurt J. Lidl) 150 | lidl@uunet.uu.net (Kurt J. Lidl) 151 | Kurt J. Lidl 152 | 153 | Kresten_Thorup@NeXT.COM (Kresten Krab Thorup) 154 | Kresten_Thorup@NeXT.COM (Kresten Krab Thorup) 155 | Kresten Krab Thorup 156 | 157 | hjl@nynexst.com (H.J. Lu) 158 | hjl@nynexst.com (H.J. Lu) 159 | H.J. Lu 160 | 161 | #@oleane.net:hugues@afp.com a!b@c.d foo!bar!foobar!root 162 | #@oleane.net:hugues@afp.com 163 | #Oleane Net:Hugues 164 | # 165 | (foo@bar.com (foobar), ned@foo.com (nedfoo) ) 166 | kevin@goess.org (foo@bar.com (foobar), ned@foo.com (nedfoo) ) 167 | 168 | 169 | eBay's Half 170 | eBay's Half 171 | eBay's Half 172 | 173 | #outlook@example.com; semicolons@example.com 174 | #outlook@example.com 175 | # 176 | # 177 | "Foo; Bar" , Baz 178 | "Foo; Bar" 179 | Foo; Bar 180 | 181 | "Имя Фамилия" 182 | "Имя Фамилия" 183 | Имя Фамилия 184 | 185 | # RFC 6854 186 | Nightly Monitor Robot:; 187 | Nightly Monitor Robot: 188 | Nightly Monitor Robot 189 | 190 | Managing Partners:ben@example.com,carol@example.com; 191 | Managing Partners:ben@example.com,carol@example.com 192 | Managing Partners 193 | 194 | "Boomer" <123456.1234@compuserve.com> 195 | Boomer <123456.1234@compuserve.com> 196 | Boomer 197 | -------------------------------------------------------------------------------- /test/functions.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | 3 | const address = require('../index') 4 | 5 | describe('isAllLower', function () { 6 | it('lower latin string', function (done) { 7 | assert.equal(true, address.isAllLower('abcdefg')) 8 | done() 9 | }) 10 | }) 11 | 12 | describe('isAllUpper', function () { 13 | it('upper latin string', function (done) { 14 | assert.equal(true, address.isAllUpper('ABCDEFG')) 15 | done() 16 | }) 17 | }) 18 | 19 | describe('nameCase', function () { 20 | it('john doe -> John Doe', function (done) { 21 | assert.equal('John Doe', address.nameCase('john doe')) 22 | done() 23 | }) 24 | it('JANE SMITH -> Jane Smith', function (done) { 25 | assert.equal('Jane Smith', address.nameCase('JANE SMITH')) 26 | done() 27 | }) 28 | it('marty mcleod -> Marty McLeod', function (done) { 29 | assert.equal('Marty McLeod', address.nameCase('marty mcleod')) 30 | done() 31 | }) 32 | it("martin o'mally -> Martin O'Malley", function (done) { 33 | assert.equal("Martin O'Malley", address.nameCase("martin o'malley")) 34 | done() 35 | }) 36 | it('level iii support -> Level III Support', function (done) { 37 | assert.equal('Level III Support', address.nameCase('level iii support')) 38 | done() 39 | }) 40 | }) 41 | 42 | describe('parseFrom', function () { 43 | it('Travis CI ', function () { 44 | try { 45 | const r = address.parseFrom('Travis CI ') 46 | assert.deepEqual(r[0], { 47 | phrase: 'Travis CI', 48 | comment: '', 49 | address: 'builds@travis-ci.org', 50 | }) 51 | } catch (e) { 52 | console.error(e) 53 | } 54 | }) 55 | 56 | it('root (Cron Daemon)', function () { 57 | try { 58 | const r = address.parseFrom('root (Cron Daemon)') 59 | assert.deepEqual(r[0], { address: '' }) 60 | } catch (e) { 61 | assert.equal(e.message, 'No results') 62 | } 63 | }) 64 | }) 65 | 66 | describe('parseSender', function () { 67 | it('"Anne Standley, PMPM" ', function () { 68 | try { 69 | const r = address.parseSender( 70 | '"Anne Standley, PMPM" ', 71 | ) 72 | assert.deepEqual(r[0], { 73 | address: 'info=protectmypublicmedia.org@mail172.atl101.mcdlv.net', 74 | comment: '', 75 | phrase: 'Anne Standley, PMPM', 76 | }) 77 | // console.log(r); 78 | } catch (e) { 79 | console.error(e) 80 | } 81 | }) 82 | }) 83 | 84 | describe('parseReplyTo', function () { 85 | it('=?utf-8?Q?Anne=20Standley=2C=20Protect=20My=20Public=20Media?= ', function (done) { 86 | try { 87 | const r = address.parseReplyTo( 88 | '=?utf-8?Q?Anne=20Standley=2C=20Protect=20My=20Public=20Media?= ', 89 | ) 90 | assert.deepEqual(r[0], { 91 | address: 'info@protectmypublicmedia.org', 92 | comment: '', 93 | phrase: 94 | '=?utf-8?Q?Anne=20Standley=2C=20Protect=20My=20Public=20Media?=', 95 | }) 96 | // console.log(r); 97 | } catch (e) { 98 | console.error(e) 99 | } 100 | done() 101 | }) 102 | }) 103 | 104 | describe('parse with options', function () { 105 | it('should not allow parsing display name with comma by default', function (done) { 106 | try { 107 | address.parse('Foo, Bar ') 108 | } catch (e) { 109 | assert.equal(e.message, 'No results') 110 | } 111 | done() 112 | }) 113 | 114 | it('should allow parsing display name with comma', function (done) { 115 | try { 116 | const [r] = address.parse('Foo, Bar ', { 117 | allowCommaInDisplayName: true, 118 | }) 119 | assert.equal('foo@example.com', r.address) 120 | assert.equal('Foo, Bar', r.phrase) 121 | } catch (e) { 122 | console.error(e) 123 | } 124 | done() 125 | }) 126 | }) 127 | --------------------------------------------------------------------------------