Skip to content

Commit

Permalink
Merge pull request #845 from CodingNagger/master
Browse files Browse the repository at this point in the history
Adding support for IP address email validation #800
  • Loading branch information
chriso authored Jul 31, 2018
2 parents 42f375f + dbbb2bd commit 9cd99d6
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 72 deletions.
20 changes: 19 additions & 1 deletion lib/isEmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ var _isFQDN = require('./isFQDN');

var _isFQDN2 = _interopRequireDefault(_isFQDN);

var _isIP = require('./isIP');

var _isIP2 = _interopRequireDefault(_isIP);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var default_email_options = {
Expand Down Expand Up @@ -91,7 +95,21 @@ function isEmail(str, options) {
}

if (!(0, _isFQDN2.default)(domain, { require_tld: options.require_tld })) {
return false;
if (!options.allow_ip_domain) {
return false;
}

if (!(0, _isIP2.default)(domain)) {
if (!domain.startsWith('[') || !domain.endsWith(']')) {
return false;
}

var noBracketdomain = domain.substr(1, domain.length - 2);

if (noBracketdomain.length === 0 || !(0, _isIP2.default)(noBracketdomain)) {
return false;
}
}
}

if (user[0] === '"') {
Expand Down
17 changes: 16 additions & 1 deletion src/lib/isEmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import assertString from './util/assertString';
import merge from './util/merge';
import isByteLength from './isByteLength';
import isFQDN from './isFQDN';
import isIP from './isIP';

const default_email_options = {
allow_display_name: false,
Expand Down Expand Up @@ -73,7 +74,21 @@ export default function isEmail(str, options) {
}

if (!isFQDN(domain, { require_tld: options.require_tld })) {
return false;
if (!options.allow_ip_domain) {
return false;
}

if (!isIP(domain)) {
if (!domain.startsWith('[') || !domain.endsWith(']')) {
return false;
}

let noBracketdomain = domain.substr(1, domain.length - 2);

if (noBracketdomain.length === 0 || !isIP(noBracketdomain)) {
return false;
}
}
}

if (user[0] === '"') {
Expand Down
48 changes: 48 additions & 0 deletions test/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ describe('Validators', () => {
'[email protected]',
'[email protected]',
'test123+invalid! [email protected]',
'[email protected]',
'[email protected]',
],
});
});
Expand Down Expand Up @@ -228,6 +230,52 @@ describe('Validators', () => {
});
});

it('should validate email addresses with allowed IPs', () => {
test({
validator: 'isEmail',
args: [{ allow_ip_domain: true }],
valid: [
'email@[123.123.123.123]',
'[email protected]',
],
invalid: [
'invalidemail@',
'invalid.com',
'@invalid.com',
'[email protected].',
'somename@gmail.com',
'[email protected].',
'[email protected]',
'gmailgmailgmailgmailgmail@gmail.com',
`${repeat('a', 64)}@${repeat('a', 251)}.com`,
`${repeat('a', 65)}@${repeat('a', 250)}.com`,
`${repeat('a', 64)}@${repeat('a', 64)}.com`,
`${repeat('a', 31)}@gmail.com`,
'[email protected] m',
'[email protected] m',
'[email protected] m',
'[email protected] m',
'[email protected] m',
'[email protected] m',
'[email protected] m',
'[email protected] m',
'[email protected] m',
'[email protected] m',
'[email protected] m',
'[email protected] m',
'[email protected] m',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'test123+invalid! [email protected]',
'[email protected]',
'[email protected]',
],
});
});

it('should validate URLs', () => {
test({
Expand Down
152 changes: 83 additions & 69 deletions validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,74 @@ function isFQDN(str, options) {
return true;
}

var ipv4Maybe = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
var ipv6Block = /^[0-9A-F]{1,4}$/i;

function isIP(str) {
var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';

assertString(str);
version = String(version);
if (!version) {
return isIP(str, 4) || isIP(str, 6);
} else if (version === '4') {
if (!ipv4Maybe.test(str)) {
return false;
}
var parts = str.split('.').sort(function (a, b) {
return a - b;
});
return parts[3] <= 255;
} else if (version === '6') {
var blocks = str.split(':');
var foundOmissionBlock = false; // marker to indicate ::

// At least some OS accept the last 32 bits of an IPv6 address
// (i.e. 2 of the blocks) in IPv4 notation, and RFC 3493 says
// that '::ffff:a.b.c.d' is valid for IPv4-mapped IPv6 addresses,
// and '::a.b.c.d' is deprecated, but also valid.
var foundIPv4TransitionBlock = isIP(blocks[blocks.length - 1], 4);
var expectedNumberOfBlocks = foundIPv4TransitionBlock ? 7 : 8;

if (blocks.length > expectedNumberOfBlocks) {
return false;
}
// initial or final ::
if (str === '::') {
return true;
} else if (str.substr(0, 2) === '::') {
blocks.shift();
blocks.shift();
foundOmissionBlock = true;
} else if (str.substr(str.length - 2) === '::') {
blocks.pop();
blocks.pop();
foundOmissionBlock = true;
}

for (var i = 0; i < blocks.length; ++i) {
// test for a :: which can not be at the string start/end
// since those cases have been handled above
if (blocks[i] === '' && i > 0 && i < blocks.length - 1) {
if (foundOmissionBlock) {
return false; // multiple :: in address
}
foundOmissionBlock = true;
} else if (foundIPv4TransitionBlock && i === blocks.length - 1) {
// it has been checked before that the last
// block is a valid IPv4 address
} else if (!ipv6Block.test(blocks[i])) {
return false;
}
}
if (foundOmissionBlock) {
return blocks.length >= 1;
}
return blocks.length === expectedNumberOfBlocks;
}
return false;
}

var default_email_options = {
allow_display_name: false,
require_display_name: false,
Expand Down Expand Up @@ -241,7 +309,21 @@ function isEmail(str, options) {
}

if (!isFQDN(domain, { require_tld: options.require_tld })) {
return false;
if (!options.allow_ip_domain) {
return false;
}

if (!isIP(domain)) {
if (!domain.startsWith('[') || !domain.endsWith(']')) {
return false;
}

var noBracketdomain = domain.substr(1, domain.length - 2);

if (noBracketdomain.length === 0 || !isIP(noBracketdomain)) {
return false;
}
}
}

if (user[0] === '"') {
Expand All @@ -261,74 +343,6 @@ function isEmail(str, options) {
return true;
}

var ipv4Maybe = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
var ipv6Block = /^[0-9A-F]{1,4}$/i;

function isIP(str) {
var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';

assertString(str);
version = String(version);
if (!version) {
return isIP(str, 4) || isIP(str, 6);
} else if (version === '4') {
if (!ipv4Maybe.test(str)) {
return false;
}
var parts = str.split('.').sort(function (a, b) {
return a - b;
});
return parts[3] <= 255;
} else if (version === '6') {
var blocks = str.split(':');
var foundOmissionBlock = false; // marker to indicate ::

// At least some OS accept the last 32 bits of an IPv6 address
// (i.e. 2 of the blocks) in IPv4 notation, and RFC 3493 says
// that '::ffff:a.b.c.d' is valid for IPv4-mapped IPv6 addresses,
// and '::a.b.c.d' is deprecated, but also valid.
var foundIPv4TransitionBlock = isIP(blocks[blocks.length - 1], 4);
var expectedNumberOfBlocks = foundIPv4TransitionBlock ? 7 : 8;

if (blocks.length > expectedNumberOfBlocks) {
return false;
}
// initial or final ::
if (str === '::') {
return true;
} else if (str.substr(0, 2) === '::') {
blocks.shift();
blocks.shift();
foundOmissionBlock = true;
} else if (str.substr(str.length - 2) === '::') {
blocks.pop();
blocks.pop();
foundOmissionBlock = true;
}

for (var i = 0; i < blocks.length; ++i) {
// test for a :: which can not be at the string start/end
// since those cases have been handled above
if (blocks[i] === '' && i > 0 && i < blocks.length - 1) {
if (foundOmissionBlock) {
return false; // multiple :: in address
}
foundOmissionBlock = true;
} else if (foundIPv4TransitionBlock && i === blocks.length - 1) {
// it has been checked before that the last
// block is a valid IPv4 address
} else if (!ipv6Block.test(blocks[i])) {
return false;
}
}
if (foundOmissionBlock) {
return blocks.length >= 1;
}
return blocks.length === expectedNumberOfBlocks;
}
return false;
}

var default_url_options = {
protocols: ['http', 'https', 'ftp'],
require_tld: true,
Expand Down
2 changes: 1 addition & 1 deletion validator.min.js

Large diffs are not rendered by default.

0 comments on commit 9cd99d6

Please sign in to comment.