Skip to content

Commit

Permalink
Remove punycode dep. Update tlds. Closes #7. Closes #9
Browse files Browse the repository at this point in the history
  • Loading branch information
hueniverse committed Sep 20, 2019
1 parent 4ce903a commit 0d6ee44
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 133 deletions.
91 changes: 0 additions & 91 deletions lib/common.js

This file was deleted.

84 changes: 75 additions & 9 deletions lib/domain.js
@@ -1,9 +1,15 @@
'use strict';

const Common = require('./common');
// Url is loaded as needed


const internals = {};
const internals = {
minDomainSegments: 2,
nonAsciiRx: /[^\x00-\x7f]/,
domainControlRx: /[\x00-\x20]/, // Control + space
tldSegmentRx: /^[a-zA-Z](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?$/,
domainSegmentRx: /^[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?$/
};


exports.analyze = function (domain, options = {}) {
Expand All @@ -13,28 +19,88 @@ exports.analyze = function (domain, options = {}) {
}

if (!domain) {
return Common.error('Domain must be a non-empty string');
return { error: 'Domain must be a non-empty string' };
}

if (domain.length > 256) {
return Common.error('Domain too long');
return { error: 'Domain too long' };
}

const ascii = !Common.nonAsciiRx.test(domain);
const ascii = !internals.nonAsciiRx.test(domain);
if (!ascii) {
if (options.allowUnicode === false) { // Defaults to true
return Common.error('Domain contains forbidden Unicode characters');
return { error: 'Domain contains forbidden Unicode characters' };
}

const normalized = domain.normalize('NFC');
domain = Common.punycode(normalized);
domain = domain.normalize('NFC');
}

if (internals.domainControlRx.test(domain)) {
return { error: 'Domain contains invalid character' };
}

domain = internals.punycode(domain);

// https://tools.ietf.org/html/rfc1035 section 2.3.1

const minDomainSegments = options.minDomainSegments || internals.minDomainSegments;

const segments = domain.split('.');
if (segments.length < minDomainSegments) {
return { error: 'Domain lacks the minimum required number of segments' };
}

return Common.domain(domain, options);
const tlds = options.tlds;
if (tlds) {
const tld = segments[segments.length - 1].toLowerCase();
if (tlds.deny && tlds.deny.has(tld) ||
tlds.allow && !tlds.allow.has(tld)) {

return { error: 'Domain uses forbidden TLD' };
}
}

for (let i = 0; i < segments.length; ++i) {
const segment = segments[i];

if (!segment.length) {
return { error: 'Domain contains empty dot-separated segment' };
}

if (segment.length > 63) {
return { error: 'Domain contains dot-separated segment that is too long' };
}

if (i < segments.length - 1) {
if (!internals.domainSegmentRx.test(segment)) {
return { error: 'Domain contains invalid character' };
}
}
else {
if (!internals.tldSegmentRx.test(segment)) {
return { error: 'Domain contains invalid tld character' };
}
}
}
};


exports.isValid = function (domain, options) {

return !exports.analyze(domain, options);
};


internals.punycode = function (domain) {

// $lab:coverage:off$
const url = typeof Url !== 'undefined' ? Url : require('url'); // eslint-disable-line no-undef
// $lab:coverage:on$

try {
return new url.URL(`http://${domain}`).host;
}
catch (err) {
return domain;
}
};
58 changes: 35 additions & 23 deletions lib/email.js
@@ -1,9 +1,13 @@
'use strict';

const Common = require('./common');
const Domain = require('./domain');

// Util loaded as needed

const internals = {};

const internals = {
nonAsciiRx: /[^\x00-\x7f]/
};


exports.analyze = function (email, options) {
Expand All @@ -25,52 +29,46 @@ internals.email = function (email, options = {}) {
}

if (!email) {
return Common.error('Address must be a non-empty string');
return { error: 'Address must be a non-empty string' };
}

// Unicode

const ascii = !Common.nonAsciiRx.test(email);
const ascii = !internals.nonAsciiRx.test(email);
if (!ascii) {
if (options.allowUnicode === false) { // Defaults to true
return Common.error('Address contains forbidden Unicode characters');
return { error: 'Address contains forbidden Unicode characters' };
}

const normalized = email.normalize('NFC');
email = Common.punycode(normalized);
email = email.normalize('NFC');
}

// Basic structure

const parts = email.split('@');
if (parts.length !== 2) {
return Common.error(parts.length > 2 ? 'Address cannot contain more than one @ character' : 'Address must contain one @ character');
return { error: parts.length > 2 ? 'Address cannot contain more than one @ character' : 'Address must contain one @ character' };
}

const local = parts[0];
const domain = parts[1];
const [local, domain] = parts;

if (!local) {
return Common.error('Address local part cannot be empty');
}

if (!domain) {
return Common.error('Domain cannot be empty');
return { error: 'Address local part cannot be empty' };
}

if (!options.ignoreLength) {
if (email.length > 254) { // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
return Common.error('Address too long');
return { error: 'Address too long' };
}

if (Common.encoder.encode(local).length > 64) { // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1
return Common.error('Address local part too long');
if (internals.encoder.encode(local).length > 64) { // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1
return { error: 'Address local part too long' };
}
}

// Validate parts

return internals.local(local, ascii) || Common.domain(domain, options);
return internals.local(local, ascii) || Domain.analyze(domain, options);
};


Expand All @@ -79,12 +77,12 @@ internals.local = function (local, ascii) {
const segments = local.split('.');
for (const segment of segments) {
if (!segment.length) {
return Common.error('Address local part contains empty dot-separated segment');
return { error: 'Address local part contains empty dot-separated segment' };
}

if (ascii) {
if (!internals.atextRx.test(segment)) {
return Common.error('Address local part contains invalid character');
return { error: 'Address local part contains invalid character' };
}

continue;
Expand All @@ -97,7 +95,7 @@ internals.local = function (local, ascii) {

const binary = internals.binary(char);
if (!internals.atomRx.test(binary)) {
return Common.error('Address local part contains invalid character');
return { error: 'Address local part contains invalid character' };
}
}
}
Expand All @@ -106,10 +104,24 @@ internals.local = function (local, ascii) {

internals.binary = function (char) {

return Array.from(Common.encoder.encode(char)).map((v) => String.fromCharCode(v)).join('');
return Array.from(internals.encoder.encode(char)).map((v) => String.fromCharCode(v)).join('');
};


internals.encoder = function () {

// $lab:coverage:off$

if (typeof TextEncoder !== 'undefined') {
return new TextEncoder();
}

return new (require('util').TextEncoder)();

// $lab:coverage:on$
}();


/*
From RFC 5321:
Expand Down

0 comments on commit 0d6ee44

Please sign in to comment.