-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support Unicode international characters #151
Changes from 2 commits
b4566ba
7c7901f
0ab6884
3487199
fbd9f1c
86c3194
79e2261
24f78ef
996a71e
e836377
10bb502
38039b8
599a245
1fdcf9e
1d20f07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,10 @@ | |
|
||
// Load modules | ||
|
||
const Dns = require('dns'); | ||
/* eslint-disable */ | ||
let Dns = require('dns'); | ||
/* eslint-enable */ | ||
const Punycode = require('punycode'); | ||
|
||
|
||
// Declare internals | ||
|
@@ -132,7 +135,19 @@ internals.specials = function () { | |
} | ||
|
||
for (let i = 0; i < specials.length; ++i) { | ||
lookup[specials.charCodeAt(i)] = true; | ||
lookup[specials.codePointAt(i)] = true; | ||
} | ||
|
||
// add C0 control characters | ||
|
||
for (let i = 0; i < 33; ++i) { | ||
lookup[i] = true; | ||
} | ||
|
||
// add C1 control characters | ||
|
||
for (let i = 127; i < 160; ++i) { | ||
lookup[i] = true; | ||
} | ||
|
||
return function (code) { | ||
|
@@ -173,7 +188,7 @@ internals.validDomain = function (tldAtom, options) { | |
|
||
|
||
/** | ||
* Check that an email address conforms to RFCs 5321, 5322 and others | ||
* Check that an email address conforms to RFCs 5321, 5322, 6530 and others | ||
* | ||
* We distinguish clearly between a Mailbox as defined by RFC 5321 and an | ||
* addr-spec as defined by RFC 5322. Depending on the context, either can be | ||
|
@@ -462,10 +477,10 @@ exports.validate = internals.validate = function (email, options, callback) { | |
} | ||
else { | ||
context.prev = context.now; | ||
charCode = token.charCodeAt(0); | ||
charCode = token.codePointAt(0); | ||
|
||
// Especially if charCode == 10 | ||
if (charCode < 33 || charCode > 126 || internals.specials(charCode)) { | ||
if (internals.specials(charCode)) { | ||
|
||
// Fatal error | ||
updateResult(internals.diagnoses.errExpectingATEXT); | ||
|
@@ -660,11 +675,11 @@ exports.validate = internals.validate = function (email, options, callback) { | |
} | ||
} | ||
|
||
charCode = token.charCodeAt(0); | ||
charCode = token.codePointAt(0); | ||
// Assume this token isn't a hyphen unless we discover it is | ||
hyphenFlag = false; | ||
|
||
if (charCode < 33 || charCode > 126 || internals.specials(charCode)) { | ||
if (internals.specials(charCode)) { | ||
// Fatal error | ||
updateResult(internals.diagnoses.errExpectingATEXT); | ||
} | ||
|
@@ -676,8 +691,8 @@ exports.validate = internals.validate = function (email, options, callback) { | |
|
||
hyphenFlag = true; | ||
} | ||
// Check if it's a neither a number nor a latin letter | ||
else if (charCode < 48 || charCode > 122 || (charCode > 57 && charCode < 65) || (charCode > 90 && charCode < 97)) { | ||
// Check if it's a neither a number nor a latin/unicode letter | ||
else if (charCode < 48 || (charCode > 122 && charCode < 192) || (charCode > 57 && charCode < 65) || (charCode > 90 && charCode < 97)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @WesTyler I'm reading through some of this code as I refactor, and now I find myself asking where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I believe 192 is the beginning of the Unicode "international" character set (À). If I remember correctly, Unicode #s 123-191 are all non-character symbols. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh, ok. Thanks! |
||
// This is not an RFC 5321 subdomain, but still OK by RFC 5322 | ||
updateResult(internals.diagnoses.rfc5322Domain); | ||
} | ||
|
@@ -864,7 +879,7 @@ exports.validate = internals.validate = function (email, options, callback) { | |
// %d12 / ; include the carriage | ||
// %d14-31 / ; return, line feed, and | ||
// %d127 ; white space characters | ||
charCode = token.charCodeAt(0); | ||
charCode = token.codePointAt(0); | ||
|
||
// '\r', '\n', ' ', and '\t' have already been parsed above | ||
if (charCode > 127 || charCode === 0 || token === '[') { | ||
|
@@ -954,7 +969,7 @@ exports.validate = internals.validate = function (email, options, callback) { | |
// %d12 / ; include the carriage | ||
// %d14-31 / ; return, line feed, and | ||
// %d127 ; white space characters | ||
charCode = token.charCodeAt(0); | ||
charCode = token.codePointAt(0); | ||
|
||
if (charCode > 127 || charCode === 0 || charCode === 10) { | ||
updateResult(internals.diagnoses.errExpectingQTEXT); | ||
|
@@ -992,7 +1007,7 @@ exports.validate = internals.validate = function (email, options, callback) { | |
// %d127 ; white space characters | ||
// | ||
// i.e. obs-qp = "\" (%d0-8, %d10-31 / %d127) | ||
charCode = token.charCodeAt(0); | ||
charCode = token.codePointAt(0); | ||
|
||
if (charCode > 127) { | ||
// Fatal error | ||
|
@@ -1099,7 +1114,7 @@ exports.validate = internals.validate = function (email, options, callback) { | |
// %d12 / ; include the carriage | ||
// %d14-31 / ; return, line feed, and | ||
// %d127 ; white space characters | ||
charCode = token.charCodeAt(0); | ||
charCode = token.codePointAt(0); | ||
|
||
if (charCode > 127 || charCode === 0 || charCode === 10) { | ||
// Fatal error | ||
|
@@ -1266,7 +1281,7 @@ exports.validate = internals.validate = function (email, options, callback) { | |
|
||
if (!dnsPositive && maxResult < internals.categories.dnsWarn) { | ||
// Per RFC 5321, domain atoms are limited to letter-digit-hyphen, so we only need to check code <= 57 to check for a digit | ||
const code = atomData.domains[elementCount].charCodeAt(0); | ||
const code = atomData.domains[elementCount].codePointAt(0); | ||
if (code <= 57) { | ||
updateResult(internals.diagnoses.rfc5321TLDNumeric); | ||
} | ||
|
@@ -1311,7 +1326,7 @@ exports.validate = internals.validate = function (email, options, callback) { | |
parseData.domain += '.'; | ||
} | ||
|
||
const dnsDomain = parseData.domain; | ||
const dnsDomain = Punycode.toASCII(parseData.domain); | ||
Dns.resolveMx(dnsDomain, (err, mxRecords) => { | ||
|
||
// If we have a fatal error, then we must assume that there are no records | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,10 +18,12 @@ | |
"node": ">=4.0.0" | ||
}, | ||
"dependencies": { | ||
"punycode": "^2.1.0" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep hapi versioning scheme (2.1.x). |
||
}, | ||
"devDependencies": { | ||
"code": "3.x.x", | ||
"lab": "10.x.x" | ||
"lab": "10.x.x", | ||
"rewire": "^2.5.2" | ||
}, | ||
"scripts": { | ||
"test": "lab -a code -t 100 -L -m 5000", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
'use strict'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @skeggse's preference is to utilize a proper internationalized domain with an active MX record. I have struck out on finding such a domain, so this approach was his backup plan. I'm guessing |
||
|
||
// Load modules | ||
|
||
const Lab = require('lab'); | ||
const Code = require('code'); | ||
const Rewire = require('rewire'); | ||
const Isemail = Rewire('..'); | ||
|
||
// Test shortcuts | ||
|
||
const lab = exports.lab = Lab.script(); | ||
const describe = lab.describe; | ||
const it = lab.it; | ||
const expect = Code.expect; | ||
|
||
// declare internals | ||
|
||
const internals = {}; | ||
|
||
// Dns rewire stub | ||
|
||
const dns_stub = { | ||
resolveMx: (domainName, cb) => { | ||
|
||
internals.punyDomain = domainName; | ||
|
||
return cb(null, [domainName]); | ||
} | ||
}; | ||
|
||
Isemail.__set__('Dns', dns_stub); | ||
|
||
describe('validate() international domains', () => { | ||
|
||
it('should punycode domains', (done) => { | ||
|
||
Isemail.validate('伊昭傑@郵件.商務', { | ||
errorLevel: 0, | ||
checkDNS: true | ||
}, () => { | ||
|
||
expect(internals.punyDomain).to.equal('xn--5nqv22n.xn--lhr59c'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This works, but depends on the exact behavior of punycode. I'd rather not test whether punycode did the right thing, just whether expect(punycode.toUnicode(internals.punyDomain)).to.equal('伊昭傑@郵件.商務'); |
||
done(); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this package only supports node 4+, you can probably use
lookup.fill
to initialize it.