diff --git a/.release b/.release
index 36bb27a..0fa4e69 160000
--- a/.release
+++ b/.release
@@ -1 +1 @@
-Subproject commit 36bb27a93862517943e04f24fd67b0df2da6cbbe
+Subproject commit 0fa4e690ffabb0157e46d56f18e4f7cfe49ce291
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 761abc0..e13a97a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
### Unreleased
+### [1.7.0] - 2024-04-29
+
+- feat: added HarakaMx
+- change: get_mx: don't filter implicit MX errors
+- fix(get_public_ip): set timeout in stun request, fixes #84
+
### [1.6.0] - 2024-04-17
- feat: normalizeDomain, for punycode/IDN names
-- feat: get_mx now _also_ returns implicit MX records
+- feat: get*mx now \_also* returns implicit MX records
- feat: added get_implicit_mx
- feat: added resolve_mx_hosts
- doc(Changes): fixed broken tag version links
@@ -62,88 +68,88 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
- chore(ci): populate test matrix with Node.js LTS versions
- chore(ci): limit dependabot updates to production deps
-#### [1.3.5] - 2022-05-27
+### [1.3.5] - 2022-05-27
- chore(ci): use shared GHA workflows
- style(es6): use dns.promises internally
- dep(async): replace async dependency with Promise.all
- doc(README): use code fences around examples (vs indention)
-#### [1.3.4] - 2022-01-05
+### [1.3.4] - 2022-01-05
- promisify get_ips_by_host (backwards compatible)
-#### [1.3.3] - 2020-01-05
+### [1.3.3] - 2020-01-05
- refactored is_local_host function to return a promise instead of using a callback #65
-#### [1.3.2] - 2021-12-20
+### [1.3.2] - 2021-12-20
- add is_local_host function #63
-#### [1.3.1] - 2021-10-13
+### [1.3.1] - 2021-10-13
- get_mx: wrap dns.resolveMx in a try haraka/Haraka#2985
- add .release scripts
- add GH workflow, publish release to NPM upon merge to master
-#### 1.3.0 - 2021-01-23
+### 1.3.0 - 2021-01-23
- Support passing an array to ip_in_list #60
-#### 1.2.4 - 2021-01-14
+### 1.2.4 - 2021-01-14
- add "any" IP to is_local_ip
- add TEST-NET-[1-3] to is_private_ip
-#### 1.2.3 - 2020-12-19
+### 1.2.3 - 2020-12-19
- fix: restore the tests wrapping the resolveMX iterable
-#### 1.2.2 - 2020-12-15
+### 1.2.2 - 2020-12-15
- get_mx: do not include implicit MX
-#### [1.2.1] - 2020-11-17
+### [1.2.1] - 2020-11-17
- bump ipaddr.js to 2.0.0 #56
-#### [1.2.0] - 2020-06-23
+### [1.2.0] - 2020-06-23
- added get_mx
- remove deprecated load_tls_ini
- remove deprecated tls_ini_section_with_defaults
-#### 1.1.5 - 2020-04-11
+### 1.1.5 - 2020-04-11
- ipv6_bogus: handle parsing broken ipv6 addresses #49
- update async to version 3.0.1 #43
-#### 1.1.4 - 2019-04-04
+### 1.1.4 - 2019-04-04
- stop is_private_ip from checking if the IP is bound to a local network interface
-#### 1.1.3 - 2019-03-01
+### 1.1.3 - 2019-03-01
- is_local_ip checks local network interfaces too
-#### 1.1.2 - 2018-11-03
+### 1.1.2 - 2018-11-03
- add is_local_ip
-#### 1.1.1 - 2018-07-19
+### 1.1.1 - 2018-07-19
- ip_in_list doesn't throw on empty list
-#### 1.1.0 - 2018-04-11
+### 1.1.0 - 2018-04-11
- add get_primary_host_name haraka/Haraka#2380
-#### 1.0.14 - 2018-01-25
+### 1.0.14 - 2018-01-25
- restore tls_ini_section_with_defaults function (deprecated since Haraka 2.0.17)
-#### 1.0.13 - 2018-01-19
+### 1.0.13 - 2018-01-19
- get_public_ip: assign timer before calling connect #29
- avoid race where timeout isn't cleared because stun connect errors immediately
@@ -152,40 +158,40 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
- eslint updates #25, #27
- improved x509 parser #22
-#### 1.0.10 - 2017-07-27
+### 1.0.10 - 2017-07-27
- added vs-stun as optional dep (from Haraka) #21
-#### 1.0.9 - 2017-06-16
+### 1.0.9 - 2017-06-16
- lint fixes for compat with eslint 4 #18
-#### 1.0.8 - 2017-03-08
+### 1.0.8 - 2017-03-08
- skip loading expired x509 (TLS) certs
- make TLS cert dir configurable
- rename certs -> cert (be consistent with haraka/plugins/tls)
- store cert/key as buffers (was strings)
-#### 1.0.7 - 2017-03-08
+### 1.0.7 - 2017-03-08
- handle undefined tls.ini section
-#### 1.0.6 - 2017-03-04
+### 1.0.6 - 2017-03-04
- add tls_ini_section_with_defaults()
- add load_tls_dir()
- add parse_x509_names()
-#### 1.0.5 - 2016-11-20
+### 1.0.5 - 2016-11-20
- add enableSNI TLS option
-#### 1.0.4 - 2016-10-25
+### 1.0.4 - 2016-10-25
- initialize TLS opts in (section != main) as booleans
-#### 1.0.3 - 2016-10-25
+### 1.0.3 - 2016-10-25
- added tls.ini loading
@@ -197,7 +203,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
[1.3.4]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.4
[1.3.5]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.5
[1.3.6]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.6
-[1.3.7]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.3.7
+[1.3.7]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.7
[1.4.0]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.4.0
[1.4.1]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.4.1
[1.5.0]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.5.0
@@ -206,3 +212,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
[1.5.3]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.5.3
[1.5.4]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.5.4
[1.6.0]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.6.0
+[1.7.0]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.7.0
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index ac187dd..90ae255 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -2,7 +2,7 @@
This handcrafted artisinal software is brought to you by:
-|
msimerson (57) |
baudehlo (4) |
DoobleD (2) |
lnedry (2) |
Juerd (1) |
olsonpm (1) |
typingArtist (1) |
+|
msimerson (58) |
baudehlo (4) |
DoobleD (2) |
lnedry (2) |
Juerd (1) |
olsonpm (1) |
typingArtist (1) |
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
this file is maintained by [.release](https://github.com/msimerson/.release)
diff --git a/README.md b/README.md
index 96ee3d8..6d1f69d 100644
--- a/README.md
+++ b/README.md
@@ -120,6 +120,49 @@ try {
}
```
+### HarakaMx
+
+An object class representing a MX. HarakaMx objects may contain the following properties:
+
+```js
+{
+ exchange: '', // required: a FQDN or IP address
+ priority: 0, // integer, a MX priority.
+ port: 25, // integer: an alternate port
+ bind: '', // an outbound IP address to bind to
+ bind_helo: '', // an outbound helo hostname
+ using_lmtp: false, // boolean, specify LMTP delivery
+ auth_user: '', // an AUTH username (required if AUTH is desired)
+ auth_pass: '', // an AUTH password (required if AUTH is desired)
+ auth_type: '', // an AUTH type that should be used with the MX.
+ from_dns: '', // the DNS name from which the MX was queried
+}
+```
+
+Create a HarakaMx object in The Usual Way:
+
+```js
+const nu = require('haraka-net-utils')
+const myMx = new nu.HarakaMx(parameter)
+```
+
+The parameter can be one of:
+
+- A string in any of the following formats:
+ - hostname
+ - hostname:port
+ - IPv4
+ - IPv4:port
+ - [IPv6]
+ - [IPv6]: port
+- A [URL](https://nodejs.org/docs/latest-v20.x/api/url.html) string
+ - smtp://mail.example.com:25
+ - lmtp://int-mail.example.com:24
+ - smtp://user:pass@host.example.com:587
+- An object, containing at least an exchange, and any of the other properties listed at the top of this section.
+
+An optional second parameter is an alias for from_dns.
+
[ci-img]: https://github.com/haraka/haraka-net-utils/actions/workflows/ci.yml/badge.svg
[ci-url]: https://github.com/haraka/haraka-net-utils/actions/workflows/ci.yml
[cov-img]: https://codecov.io/github/haraka/haraka-net-utils/coverage.svg
diff --git a/index.js b/index.js
index 77955e7..252e483 100644
--- a/index.js
+++ b/index.js
@@ -1,14 +1,14 @@
'use strict'
-// node.js built-ins
-const { Resolver } = require('dns').promises
+const { Resolver } = require('node:dns').promises
const dns = new Resolver({ timeout: 25000, tries: 1 })
-const net = require('net')
-const os = require('os')
-const punycode = require('punycode.js')
+const net = require('node:net')
+const os = require('node:os')
+const url = require('node:url')
// npm modules
const ipaddr = require('ipaddr.js')
+const punycode = require('punycode.js')
const sprintf = require('sprintf-js').sprintf
const tlds = require('haraka-tld')
@@ -287,7 +287,9 @@ exports.get_public_ip_async = async function () {
}, timeout * 1000)
// Connect to STUN Server
- const res = await this.stun.request(get_stun_server())
+ const res = await this.stun.request(get_stun_server(), {
+ maxTimeout: (timeout - 1) * 1000,
+ })
this.public_ip = res.getXorAddress().address
clearTimeout(timer)
return this.public_ip
@@ -296,22 +298,21 @@ exports.get_public_ip_async = async function () {
exports.get_public_ip = async function (cb) {
if (!cb) return exports.get_public_ip_async()
- const nu = this
- if (nu.public_ip !== undefined) return cb(null, nu.public_ip) // cache
+ if (this.public_ip !== undefined) return cb(null, this.public_ip) // cache
// manual config override, for the cases where we can't figure it out
const smtpIni = exports.config.get('smtp.ini').main
if (smtpIni.public_ip) {
- nu.public_ip = smtpIni.public_ip
- return cb(null, nu.public_ip)
+ this.public_ip = smtpIni.public_ip
+ return cb(null, this.public_ip)
}
// Initialise cache value to null to prevent running
// should we hit a timeout or the module isn't installed.
- nu.public_ip = null
+ this.public_ip = null
try {
- nu.stun = require('@msimerson/stun')
+ this.stun = require('@msimerson/stun')
} catch (e) {
e.install = 'Please install stun: "npm install -g stun"'
console.error(`${e.msg}\n${e.install}`)
@@ -324,17 +325,20 @@ exports.get_public_ip = async function (cb) {
}, timeout * 1000)
// Connect to STUN Server
- nu.stun.request(get_stun_server(), (error, res) => {
- if (timer) clearTimeout(timer)
- if (error) return cb(error)
-
- nu.public_ip = res.getXorAddress().address
- cb(null, nu.public_ip)
- })
+ this.stun.request(
+ get_stun_server(),
+ { maxTimeout: (timeout - 1) * 1000 },
+ (error, res) => {
+ if (timer) clearTimeout(timer)
+ if (error) return cb(error)
+
+ this.public_ip = res.getXorAddress().address
+ cb(null, this.public_ip)
+ },
+ )
}
function get_stun_server() {
- // STUN servers by Google
const servers = [
'stun.l.google.com:19302',
'stun1.l.google.com:19302',
@@ -345,11 +349,7 @@ function get_stun_server() {
return servers[Math.floor(Math.random() * servers.length)]
}
-exports.get_ipany_re = function (prefix, suffix, modifier) {
- if (prefix === undefined) prefix = ''
- if (suffix === undefined) suffix = ''
- if (modifier === undefined) modifier = 'mg'
- /* eslint-disable prefer-template */
+exports.get_ipany_re = function (prefix = '', suffix = '', modifier = 'mg') {
return new RegExp(
prefix +
`(` + // capture group
@@ -493,31 +493,24 @@ exports.get_mx = async (raw_domain, cb) => {
const domain = normalizeDomain(raw_domain)
try {
- const exchanges = await dns.resolveMx(domain)
+ let exchanges = await dns.resolveMx(domain)
if (exchanges && exchanges.length) {
- exchanges.map((e) => (e.from_dns = domain))
+ exchanges = exchanges.map((e) => new HarakaMx(e, domain))
if (cb) return cb(null, exchanges)
return exchanges
}
+ // no MX record(s), fall through
} catch (err) {
- // console.error(err.message)
if (fatal_mx_err(err)) {
if (cb) return cb(err, [])
throw err
}
+ // non-terminal DNS failure, fall through
}
- // no MX or non-fatal DNS failure
- try {
- const exchanges = await this.get_implicit_mx(domain)
- if (cb) return cb(null, exchanges)
- return exchanges
- } catch (err) {
- if (fatal_mx_err(err)) {
- if (cb) return cb(err, [])
- throw err
- }
- }
+ const exchanges = await this.get_implicit_mx(domain)
+ if (cb) return cb(null, exchanges)
+ return exchanges
}
exports.get_implicit_mx = async (domain) => {
@@ -528,9 +521,7 @@ exports.get_implicit_mx = async (domain) => {
return r
.filter((a) => a.status === 'fulfilled')
- .flatMap((a) =>
- a.value.map((ip) => ({ priority: 0, exchange: ip, from_dns: domain })),
- )
+ .flatMap((a) => a.value.map((ip) => new HarakaMx(ip, domain)))
}
exports.resolve_mx_hosts = async (mxes) => {
@@ -570,3 +561,92 @@ exports.resolve_mx_hosts = async (mxes) => {
return settled.filter((s) => s.status === 'fulfilled').flatMap((s) => s.value)
}
+
+class HarakaMx {
+ constructor(obj = {}, domain) {
+ switch (typeof obj) {
+ case 'string':
+ /mtp:\/\//.test(obj) ? this.fromUrl(obj) : this.fromString(obj)
+ break
+ case 'object':
+ this.fromObject(obj)
+ break
+ }
+
+ if (this.priority === undefined) this.priority = 0
+ if (domain && this.from_dns === undefined) {
+ this.from_dns = domain.toLowerCase()
+ }
+ }
+
+ fromObject(obj) {
+ for (const prop of [
+ 'exchange',
+ 'priority',
+ 'port',
+ 'bind',
+ 'bind_helo',
+ 'using_lmtp',
+ 'auth_user',
+ 'auth_pass',
+ 'auth_type',
+ 'from_dns',
+ ]) {
+ if (obj[prop] !== undefined) this[prop] = obj[prop]
+ }
+ }
+
+ fromString(str) {
+ const matches = /^\[?(.*?)\]?(?::(24|25|465|587|\d{4,5}))?$/.exec(str)
+ if (matches) {
+ this.exchange = matches[1].toLowerCase()
+ if (matches[2]) this.port = parseInt(matches[2])
+ } else {
+ this.exchange = str
+ }
+ }
+
+ fromUrl(str) {
+ const dest = new url.URL(str)
+
+ switch (dest.protocol) {
+ case 'smtp:':
+ if (!dest.port) dest.port = 25
+ break
+ case 'lmtp:':
+ this.using_lmtp = true
+ if (!dest.port) dest.port = 24
+ break
+ }
+
+ if (dest.hostname) this.exchange = dest.hostname.toLowerCase()
+ if (dest.port) this.port = parseInt(dest.port)
+ if (dest.username) this.auth_user = dest.username
+ if (dest.password) this.auth_pass = dest.password
+ }
+
+ toUrl() {
+ const proto = this.using_lmtp ? 'lmtp://' : 'smtp://'
+ const auth = this.auth_user ? `${this.auth_user}:****@` : ''
+ const host = net.isIPv6(this.exchange)
+ ? `[${this.exchange}]`
+ : this.exchange
+ const port = this.port ? this.port : proto === 'lmtp://' ? 24 : 25
+ return new url.URL(`${proto}${auth}${host}:${port}`)
+ }
+
+ toString() {
+ const from_dns = this.from_dns ? ` (from ${this.from_dns})` : ''
+ return `MX ${this.priority} ${this.toUrl()}${from_dns}`
+ }
+}
+
+/*
+ * A string of one of the following formats:
+ * hostname
+ * hostname:port
+ * ipaddress
+ * ipaddress:port
+ */
+
+exports.HarakaMx = HarakaMx
diff --git a/package.json b/package.json
index dd51bb9..01b60f8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "haraka-net-utils",
- "version": "1.6.0",
+ "version": "1.7.0",
"description": "haraka network utilities",
"main": "index.js",
"files": [
@@ -36,12 +36,12 @@
},
"homepage": "https://github.com/haraka/haraka-net-utils#readme",
"devDependencies": {
- "@haraka/eslint-config": "^1.1.3"
+ "@haraka/eslint-config": "^1.1.5"
},
"dependencies": {
- "haraka-config": "^1.1.0",
+ "haraka-config": "^1.2.4",
"haraka-tld": "^1.2.1",
- "ipaddr.js": "^2.1.0",
+ "ipaddr.js": "^2.2.0",
"punycode.js": "^2.3.1",
"openssl-wrapper": "^0.3.4",
"sprintf-js": "^1.1.3"
diff --git a/test/net_utils.js b/test/net_utils.js
index a547a83..5df6153 100644
--- a/test/net_utils.js
+++ b/test/net_utils.js
@@ -271,11 +271,9 @@ describe('get_public_ip', function () {
it('normal', function (done) {
this.net_utils.public_ip = undefined
const cb = function (err, ip) {
- // console.log(`ip: ${ip}`);
- // console.log(`err: ${err}`);
if (has_stun()) {
if (err) {
- console.log(err)
+ console.error(err)
} else {
console.log(`stun success: ${ip}`)
assert.equal(null, err)
@@ -1285,7 +1283,6 @@ describe('get_mx', function () {
this.net_utils.get_mx(c, (err, mxlist) => {
if (err) console.error(err)
assert.ifError(err)
- // assert.ok(mxlist.length);
checkValid(validCases[c], mxlist)
done()
})
@@ -1294,7 +1291,6 @@ describe('get_mx', function () {
it(`awaits MX records for ${c}`, async function () {
this.timeout(12000)
const mxlist = await this.net_utils.get_mx(c)
- // assert.ok(mxlist.length);
checkValid(validCases[c], mxlist)
})
}
@@ -1303,6 +1299,8 @@ describe('get_mx', function () {
const invalidCases = {
invalid: /queryMx (ENODATA|ENOTFOUND|ESERVFAIL) invalid/,
'gmail.xn--com-0da': /(ENOTFOUND|Cannot convert name to ASCII)/,
+ 'non-exist.haraka.tnpi.net': /ignore/,
+ 'haraka.non-exist': /ignore/,
}
function checkInvalid(expected, actual) {
@@ -1316,8 +1314,8 @@ describe('get_mx', function () {
for (const c in invalidCases) {
it(`cb does not crash on invalid name: ${c}`, function () {
this.net_utils.get_mx(c, (err, mxlist) => {
+ if (err) checkInvalid(invalidCases[c], err.message)
assert.equal(mxlist.length, 0)
- checkInvalid(invalidCases[c], err.message)
})
})
@@ -1389,3 +1387,236 @@ describe('resolve_mx_hosts', function () {
assert.equal(r.length, 8)
})
})
+
+describe('get_implicit_mx', function () {
+ beforeEach(function (done) {
+ this.net_utils = require('../index')
+ done()
+ })
+
+ it('harakamail.com', async function () {
+ const mf = await this.net_utils.get_implicit_mx('harakamail.com')
+ assert.equal(mf.length, 1)
+ })
+
+ it('mx.theartfarm.com', async function () {
+ const mf = await this.net_utils.get_implicit_mx('mx.theartfarm.com')
+ assert.equal(mf.length, 0)
+ })
+
+ it('resolve-fail-definitive.josef-froehle.de', async function () {
+ const mf = await this.net_utils.get_implicit_mx(
+ 'resolve-fail-definitive.josef-froehle.de',
+ )
+ assert.equal(mf.length, 0)
+ })
+ it('resolve-fail-a.josef-froehle.de', async function () {
+ const mf = await this.net_utils.get_implicit_mx(
+ 'resolve-fail-a.josef-froehle.de',
+ )
+ assert.equal(mf.length, 1)
+ })
+ it('resolve-fail-aaaa.josef-froehle.de', async function () {
+ const mf = await this.net_utils.get_implicit_mx(
+ 'resolve-fail-aaaa.josef-froehle.de',
+ )
+ assert.equal(mf.length, 0)
+ })
+})
+
+describe('HarakaMx', () => {
+ beforeEach(function (done) {
+ this.nu = require('../index')
+ done()
+ })
+
+ describe('fromObject', () => {
+ it('accepts an object', function () {
+ assert.deepEqual(
+ new this.nu.HarakaMx({
+ from_dns: 'example.com',
+ exchange: '.',
+ priority: 0,
+ }),
+ { from_dns: 'example.com', exchange: '.', priority: 0 },
+ )
+ })
+
+ it('sets default priority to 0', function () {
+ assert.deepEqual(new this.nu.HarakaMx({ exchange: '.' }), {
+ exchange: '.',
+ priority: 0,
+ })
+ })
+
+ it('if optional domain provided, sets from_dns', function () {
+ assert.deepEqual(new this.nu.HarakaMx({ exchange: '.' }, 'example.com'), {
+ from_dns: 'example.com',
+ exchange: '.',
+ priority: 0,
+ })
+ })
+ })
+
+ describe('fromString', function () {
+ it('parses a hostname', function () {
+ assert.deepEqual(new this.nu.HarakaMx('mail.example.com'), {
+ exchange: 'mail.example.com',
+ priority: 0,
+ })
+ })
+
+ it('parses a hostname:port', function () {
+ assert.deepEqual(new this.nu.HarakaMx('mail.example.com:25'), {
+ exchange: 'mail.example.com',
+ port: 25,
+ priority: 0,
+ })
+ })
+
+ it('parses an IPv4', function () {
+ assert.deepEqual(new this.nu.HarakaMx('192.0.2.1'), {
+ exchange: '192.0.2.1',
+ priority: 0,
+ })
+ })
+
+ it('parses an IPv4:port', function () {
+ assert.deepEqual(new this.nu.HarakaMx('192.0.2.1:25'), {
+ exchange: '192.0.2.1',
+ port: 25,
+ priority: 0,
+ })
+ })
+
+ it('parses an IPv6', function () {
+ assert.deepEqual(new this.nu.HarakaMx('2001:db8::1'), {
+ exchange: '2001:db8::1',
+ priority: 0,
+ })
+ })
+
+ it('parses an IPv6:port', function () {
+ assert.deepEqual(new this.nu.HarakaMx('2001:db8::1:25'), {
+ exchange: '2001:db8::1',
+ port: 25,
+ priority: 0,
+ })
+ })
+
+ it('parses an [IPv6]:port', function () {
+ assert.deepEqual(new this.nu.HarakaMx('[2001:db8::1]:25'), {
+ exchange: '2001:db8::1',
+ port: 25,
+ priority: 0,
+ })
+ })
+ })
+
+ describe('fromUri', function () {
+ it('parses simple URIs', function () {
+ assert.deepEqual(new this.nu.HarakaMx('smtp://192.0.2.2'), {
+ exchange: '192.0.2.2',
+ port: 25,
+ priority: 0,
+ })
+
+ assert.deepEqual(new this.nu.HarakaMx('smtp://[2001:db8::1]:25'), {
+ exchange: '[2001:db8::1]',
+ port: 25,
+ priority: 0,
+ })
+ })
+
+ it('parses more complex URIs', function () {
+ assert.deepEqual(
+ new this.nu.HarakaMx('smtp://authUser:sekretPass@[2001:db8::1]'),
+ {
+ exchange: '[2001:db8::1]',
+ port: 25,
+ priority: 0,
+ auth_pass: 'sekretPass',
+ auth_user: 'authUser',
+ },
+ )
+
+ assert.deepEqual(
+ new this.nu.HarakaMx('lmtp://authUser:sekretPass@[2001:db8::1]:25'),
+ {
+ exchange: '[2001:db8::1]',
+ port: 25,
+ priority: 0,
+ using_lmtp: true,
+ auth_pass: 'sekretPass',
+ auth_user: 'authUser',
+ },
+ )
+ })
+ })
+
+ describe('toUrl', function () {
+ it('has a reasonable toUrl()', function () {
+ assert.equal(
+ new this.nu.HarakaMx({ exchange: '.' }).toUrl(),
+ 'smtp://.:25',
+ )
+
+ assert.equal(
+ new this.nu.HarakaMx({
+ from_dns: 'example.com',
+ exchange: '.',
+ priority: 10,
+ }).toUrl(),
+ 'smtp://.:25',
+ )
+
+ assert.equal(
+ new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:25').toUrl(),
+ 'smtp://au:****@192.0.2.3:25',
+ )
+
+ assert.equal(
+ new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:465').toUrl(),
+ 'smtp://au:****@192.0.2.3:465',
+ )
+
+ assert.equal(
+ new this.nu.HarakaMx('smtp://[2001:db8::1]:25').toUrl(),
+ 'smtp://[2001:db8::1]:25',
+ )
+ })
+ })
+
+ describe('toString', function () {
+ it('has a reasonable toString()', function () {
+ assert.equal(
+ new this.nu.HarakaMx({ exchange: '.' }).toString(),
+ 'MX 0 smtp://.:25',
+ )
+
+ assert.equal(
+ new this.nu.HarakaMx({
+ from_dns: 'example.com',
+ exchange: '.',
+ priority: 10,
+ }).toString(),
+ 'MX 10 smtp://.:25 (from example.com)',
+ )
+
+ assert.equal(
+ new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:25').toString(),
+ 'MX 0 smtp://au:****@192.0.2.3:25',
+ )
+
+ assert.equal(
+ new this.nu.HarakaMx('smtp://au:ap@192.0.2.3:465').toString(),
+ 'MX 0 smtp://au:****@192.0.2.3:465',
+ )
+
+ assert.equal(
+ new this.nu.HarakaMx('smtp://[2001:db8::1]:25').toString(),
+ 'MX 0 smtp://[2001:db8::1]:25',
+ )
+ })
+ })
+})