├── .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 |
--------------------------------------------------------------------------------