Skip to content

Idn support #6

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 7 commits into from
May 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
- 1.1.0
- support IDN email addresses

- 1.0.0 - 2016-12-08

Expand Down
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ Usage
# Address Object

The Address object is an interface to reading email addresses passed in at
SMTP time. It parses all the formats in RFC-2821 and 2822, and
supports correctly escaping email addresses.
SMTP time. It parses all the formats in RFC-2821 and 2822, as well as UTF8
email addresses according to the RFCs 5890, 5891 and 5892 providing the
domain in punycode when encountered It also supports correctly escaping
email addresses.

## API

Expand All @@ -56,21 +58,29 @@ Access the local part of the email address

* address.host

Access the domain part of the email adress
Access the domain part of the email address, decoded if necessary to punycode

* address.format()
* address.original_host

Access the domain part of the email address, unencoded and case preserved

* address.format(use_punycode=false)

Provides the email address in the appropriate `<user@host>` format. And
deals correctly with the null sender and local names.

If use_punycode = true, uses address.host instead of address.original_host.

* address.toString()

Same as format().

* address.address()
* address.address(newval=null, use_punycode=false)

Provides the email address in 'user@host' format.

If use_punycode = true, uses address.host instead of address.original_host.

Advanced Usage
--------------

Expand Down
2 changes: 2 additions & 0 deletions _idn.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 23 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
'use strict';

var punycode = require('punycode');

// a class encapsulating an email address per RFC-2821

var qchar = /([^a-zA-Z0-9!#\$\%\&\x27\*\+\x2D\/=\?\^_`{\|}~.])/;
var qchar = /([^a-zA-Z0-9!#\$\%\&\x27\*\+\x2D\/=\?\^_`{\|}~.\u0100-\uFFFF])/;

function Address (user, host) {
if (typeof user === 'object' && user.original) {
Expand All @@ -27,15 +30,18 @@ function Address (user, host) {
}
}

exports.atom_expr = /[a-zA-Z0-9!#%&*+=?\^_`{|}~\$\x27\x2D\/]+/;
var idn_allowed = require('./_idn');

exports.atom_expr = /[a-zA-Z0-9!#%&*+=?\^_`{|}~\$\x27\x2D\/\u0100-\uFFFF]+/;
exports.address_literal_expr =
/(?:\[(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|IPv6:[0-9A-Fa-f:.]+)\])/;
exports.subdomain_expr = /(?:[a-zA-Z0-9](?:[_\-a-zA-Z0-9]*[a-zA-Z0-9])?)/;
// exports.subdomain_expr = /(?:[a-zA-Z0-9](?:[_\-a-zA-Z0-9]*[a-zA-Z0-9])?)/;
exports.subdomain_expr = new RegExp('(?:' + idn_allowed.source + '(?:(?:[_\-]|' + idn_allowed.source + ')*' + idn_allowed.source + ')?)');

// you can override this when loading and re-run compile_re()
exports.domain_expr = undefined;

exports.qtext_expr = /[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]/;
exports.qtext_expr = /[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F\u0100-\uFFFF]/;
exports.text_expr = /\\([\x01-\x09\x0B\x0C\x0E-\x7F])/;

var domain_re;
Expand Down Expand Up @@ -96,19 +102,24 @@ Address.prototype.parse = function (addr) {

var localpart = matches[1];
var domainpart = matches[2];
this.original_host = domainpart;

if (/[\u0100-\uFFFF]/.test(domainpart)) {
this.is_utf8 = true;
domainpart = punycode.toASCII(domainpart);
}

this.host = domainpart.toLowerCase();

if (atoms_re.test(localpart)) {
// simple case, we are done
this.user = localpart;
// original case can be found in address.original
this.host = domainpart.toLowerCase();
return;
}
matches = qt_re.exec(localpart);
if (matches) {
localpart = matches[1];
this.user = localpart.replace(exports.text_expr, '$1', 'g');
this.host = domainpart.toLowerCase();
return;
}
throw new Error('Invalid local part in address: ' + addr);
Expand All @@ -118,24 +129,24 @@ Address.prototype.isNull = function () {
return this.user ? 0 : 1;
};

Address.prototype.format = function () {
Address.prototype.format = function (use_punycode) {
if (this.isNull()) {
return '<>';
}

var user = this.user.replace(qchar, '\\$1', 'g');
if (user !== this.user) {
return '<"' + user + '"' + (this.host ? ('@' + this.host) : '') + '>';
return '<"' + user + '"' + (this.original_host ? ('@' + (use_punycode ? this.host : this.original_host)) : '') + '>';
}
return '<' + this.address() + '>';
return '<' + this.address(null, use_punycode) + '>';
};

Address.prototype.address = function (set) {
Address.prototype.address = function (set, use_punycode) {
if (set) {
this.original = set;
this.parse(set);
}
return (this.user || '') + (this.host ? ('@' + this.host) : '');
return (this.user || '') + (this.original_host ? ('@' + (use_punycode ? this.host : this.original_host)) : '');
};

Address.prototype.toString = function () {
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "address-rfc2821",
"version": "1.0.1",
"version": "1.1.0",
"description": "RFC 2821 (Envelope) email address parser",
"author": {
"name": "The Haraka Team",
Expand Down Expand Up @@ -34,5 +34,8 @@
"grunt-mocha-test": "*",
"mocha": "*"
},
"license": "MIT"
"license": "MIT",
"dependencies": {
"punycode": "^2.1.0"
}
}
9 changes: 8 additions & 1 deletion test/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ var assert = require('assert');

var Address = require('../index').Address;

function _check(address, user, host) {
function _check(address, user, host, original_host) {
var a = new Address(address);
assert.equal(a.user, user);
assert.equal(a.host, host);
if (original_host) {
assert.equal(a.original_host, original_host);
}
}

describe('good addresses pass', function () {
Expand Down Expand Up @@ -44,6 +47,10 @@ describe('good addresses pass', function () {
it('foo@foo.x.example.com', function () {
_check('foo@foo.x.example.com', 'foo', 'foo.x.example.com');
});

it('<андрис@уайлддак.орг>', function () {
_check('<андрис@уайлддак.орг>', 'андрис', 'xn--80aalaxjd5d.xn--c1avg', 'уайлддак.орг');
});
});

describe('bad addresses fail', function () {
Expand Down