web: Components: Refactoring of email parsing
This commit is contained in:
parent
021ac77d2f
commit
f2e8ce6b2a
@ -3,7 +3,7 @@ import styled from 'styled-components'
|
||||
import PropTypes from 'prop-types'
|
||||
import isEqual from "lodash/isEqual";
|
||||
import TextInput from '../text-input'
|
||||
import Email from '../../utils/email';
|
||||
import { Email, parseAddress } from '../../utils/email';
|
||||
|
||||
const borderColor = {
|
||||
default: '#D0D5DA',
|
||||
@ -43,19 +43,17 @@ class EmailInput extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
validationFirstEmail = value => {
|
||||
const emailUtility = new Email(value);
|
||||
const email = emailUtility.ParseAddress();
|
||||
return {
|
||||
isValidEmail: email[2],
|
||||
email: email[1]
|
||||
};
|
||||
}
|
||||
|
||||
checkEmail = (value) => {
|
||||
|
||||
const emailObj = this.validationFirstEmail(value);
|
||||
const { email, isValidEmail } = emailObj;
|
||||
if(!value.length)
|
||||
{
|
||||
!this.state.isValidEmail && this.setState({ isValidEmail: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const emailObj = parseAddress(value);
|
||||
const email = emailObj.email;
|
||||
const isValidEmail = emailObj.isValid();
|
||||
|
||||
email !== this.state.lastValidEmail && isValidEmail && this.setState({ lastValidEmail: email });
|
||||
|
||||
@ -63,7 +61,7 @@ class EmailInput extends React.Component {
|
||||
&& (isValidEmail !== this.state.isValidEmail || (email !== this.state.lastValidEmail && isValidEmail) || value.length === 0)
|
||||
&& this.props.onValidateInput(emailObj);
|
||||
|
||||
value.length === 0 ? this.setState({ isValidEmail: true }) : this.setState({ isValidEmail });
|
||||
this.setState({ isValidEmail });
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,156 +1,256 @@
|
||||
import emailAddresses from 'email-addresses';
|
||||
import punycode from 'punycode';
|
||||
import { parseErrorTypes } from './constants';
|
||||
import emailAddresses from "email-addresses";
|
||||
import punycode from "punycode";
|
||||
import { parseErrorTypes } from "./constants";
|
||||
|
||||
class Email {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
this.parsedObjs = {
|
||||
addresses: [],
|
||||
errors: []
|
||||
};
|
||||
}
|
||||
|
||||
addressesToArray = (value) => {
|
||||
if (!value) return null;
|
||||
const normalizedStr = this.normalizeString(value);
|
||||
let arrayOfAddressObj = [];
|
||||
const normalizedStrLength = normalizedStr.length;
|
||||
|
||||
for (let i = 0; i < normalizedStrLength; i++) {
|
||||
const contact2Obj = this.contact2Obj(normalizedStr[i]);
|
||||
const obj2Contact = this.obj2Contact(contact2Obj);
|
||||
const addressObj = emailAddresses.parseOneAddress(obj2Contact);
|
||||
addressObj != null && arrayOfAddressObj.push(addressObj);
|
||||
}
|
||||
return arrayOfAddressObj.length === 0 ? null : arrayOfAddressObj;
|
||||
}
|
||||
|
||||
normalizeString = (string) => {
|
||||
let mass = [];
|
||||
let e = string.replace(/[\s,;]*$/, ",");
|
||||
for (let t, i = false, o = 0, a = 0, s = e.length; s > a; a += 1) {
|
||||
switch (e.charAt(a)) {
|
||||
case ",":
|
||||
case ";":
|
||||
if (!i) {
|
||||
t = e.substring(o, a);
|
||||
t = t.trim();
|
||||
if (t) {
|
||||
mass.push(t);
|
||||
}
|
||||
o = a + 1;
|
||||
const getParts = string => {
|
||||
let mass = [];
|
||||
let e = string.replace(/[\s,;]*$/, ",");
|
||||
for (let t, i = false, o = 0, a = 0, s = e.length; s > a; a += 1) {
|
||||
switch (e.charAt(a)) {
|
||||
case ",":
|
||||
case ";":
|
||||
if (!i) {
|
||||
t = e.substring(o, a);
|
||||
t = t.trim();
|
||||
if (t) {
|
||||
mass.push(t);
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
"\\" !== e.charAt(a - 1) && '"' !== e.charAt(a + 1) && (i = !i);
|
||||
}
|
||||
o = a + 1;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
"\\" !== e.charAt(a - 1) && '"' !== e.charAt(a + 1) && (i = !i);
|
||||
}
|
||||
return mass;
|
||||
}
|
||||
return mass;
|
||||
};
|
||||
|
||||
writeError = () => {
|
||||
this.parsedObjs.errors.push({ message: "Incorrect email", type: parseErrorTypes.IncorrectEmail, errorItem: this.value });
|
||||
this.parsedObjs.addresses.push("", this.value, false);
|
||||
}
|
||||
|
||||
|
||||
contact2Obj = (object) => {
|
||||
let t = /^"(.*)"\s*<([^>]+)>$/,
|
||||
n = /^(.*)<([^>]+)>$/,
|
||||
i = object.match(t) || object.match(n);
|
||||
return i ? {
|
||||
name: (i[1].replace(/\\"/g, '"').replace(/\\\\/g, "\\")).trim(),
|
||||
email: (i[2]).trim()
|
||||
} : {
|
||||
email: object
|
||||
const str2Obj = str => {
|
||||
let t = /^"(.*)"\s*<([^>]+)>$/,
|
||||
n = /^(.*)<([^>]+)>$/,
|
||||
i = str.match(t) || str.match(n);
|
||||
return i
|
||||
? {
|
||||
name: i[1]
|
||||
.replace(/\\"/g, '"')
|
||||
.replace(/\\\\/g, "\\")
|
||||
.trim(),
|
||||
email: i[2].trim()
|
||||
}
|
||||
: {
|
||||
email: str
|
||||
};
|
||||
};
|
||||
|
||||
const obj2str = object => {
|
||||
let t = undefined;
|
||||
if (object.email) {
|
||||
t = object.email;
|
||||
object.name &&
|
||||
(t =
|
||||
'"' +
|
||||
object.name.replace(/\\/g, "\\\\").replace(/"/g, '\\"') +
|
||||
'" <' +
|
||||
t +
|
||||
">");
|
||||
}
|
||||
return t;
|
||||
};
|
||||
|
||||
const normalizeString = str => {
|
||||
return obj2str(str2Obj(str));
|
||||
};
|
||||
|
||||
const checkErrors = parsedAddress => {
|
||||
const errors = [];
|
||||
if (
|
||||
parsedAddress.domain.indexOf(".") === -1 ||
|
||||
!/(^((?!-)[a-zA-Z0-9-]{2,63}\.)+[a-zA-Z]{2,63}\.?$)/.test(
|
||||
parsedAddress.domain
|
||||
)
|
||||
) {
|
||||
errors.push({
|
||||
message: "Incorrect domain",
|
||||
type: parseErrorTypes.IncorrectEmail,
|
||||
errorItem: parsedAddress
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
parsedAddress.domain.indexOf("[") === 0 &&
|
||||
parsedAddress.domain.indexOf("]") === parsedAddress.domain.length - 1
|
||||
) {
|
||||
errors.push({
|
||||
message: "Domains as ip address are not supported",
|
||||
type: parseErrorTypes.IncorrectEmail,
|
||||
errorItem: parsedAddress
|
||||
});
|
||||
}
|
||||
|
||||
if (!/^[\x00-\x7F]+$/.test(punycode.toUnicode(parsedAddress.domain))) {
|
||||
errors.push({
|
||||
message: "Punycode domains are not supported",
|
||||
type: parseErrorTypes.IncorrectEmail,
|
||||
errorItem: parsedAddress
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!/^[\x00-\x7F]+$/.test(parsedAddress.local) ||
|
||||
!/^([a-zA-Z0-9]+)([_\-\.\+][a-zA-Z0-9]+)*$/.test(parsedAddress.local)
|
||||
) {
|
||||
errors.push({
|
||||
message: "Incorrect localpart",
|
||||
type: parseErrorTypes.IncorrectEmail,
|
||||
errorItem: parsedAddress
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
/\s+/.test(parsedAddress.local) ||
|
||||
parsedAddress.local !== parsedAddress.parts.local.tokens
|
||||
) {
|
||||
errors.push({
|
||||
message: "Incorrect, localpart contains spaces",
|
||||
type: parseErrorTypes.IncorrectEmail,
|
||||
errorItem: parsedAddress
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
/\s+/.test(parsedAddress.domain) ||
|
||||
parsedAddress.domain !== parsedAddress.parts.domain.tokens
|
||||
) {
|
||||
errors.push({
|
||||
message: "Incorrect, domain contains spaces",
|
||||
type: parseErrorTypes.IncorrectEmail,
|
||||
errorItem: parsedAddress
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse addresses from string
|
||||
* @param {String} str
|
||||
* @return {Array} result with array of Email objects
|
||||
*/
|
||||
export const parseAddresses = (str, options = new EmailOptions()) => {
|
||||
if (!(options instanceof EmailOptions)) throw "Invalid options";
|
||||
|
||||
const parts = getParts(str);
|
||||
const resultEmails = [];
|
||||
|
||||
let i,
|
||||
n = parts.length;
|
||||
for (i = 0; i < n; i++) {
|
||||
const normalizedStr = normalizeString(parts[i]);
|
||||
const parsedAddress = emailAddresses.parseOneAddress(normalizedStr);
|
||||
|
||||
const errors = [];
|
||||
|
||||
if (!parsedAddress) {
|
||||
errors.push({
|
||||
message: "Incorrect email",
|
||||
type: parseErrorTypes.IncorrectEmail
|
||||
});
|
||||
} else {
|
||||
errors.concat(checkErrors(parsedAddress));
|
||||
}
|
||||
|
||||
resultEmails.push(
|
||||
parsedAddress
|
||||
? new Email(parsedAddress.name, parsedAddress.address, errors)
|
||||
: new Email(null, parts[i], errors)
|
||||
);
|
||||
}
|
||||
|
||||
return resultEmails;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse address from string
|
||||
* @param {String} str
|
||||
* @return {Email} result
|
||||
*/
|
||||
export const parseAddress = (str, options = new EmailOptions()) => {
|
||||
const parsedEmails = parseAddresses(str, options);
|
||||
|
||||
if (!parseAddresses.length) {
|
||||
return new Email("", str, [
|
||||
{ message: "No one email parsed", type: parseErrorTypes.EmptyRecipients }
|
||||
]);
|
||||
}
|
||||
|
||||
if (parseAddresses.length > 1) {
|
||||
return new Email("", str, [
|
||||
{ message: "To many email parsed", type: parseErrorTypes.IncorrectEmail }
|
||||
]);
|
||||
}
|
||||
|
||||
const resultEmail = parsedEmails[0];
|
||||
|
||||
return resultEmail;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check domain validity
|
||||
* @param {String} domain
|
||||
* @return {Bool} result
|
||||
*/
|
||||
export const isValidDomainName = domain => {
|
||||
let parsed = emailAddresses.parseOneAddress("test@" + domain);
|
||||
return parsed && parsed.domain === domain && domain.indexOf(".") !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare emails
|
||||
* @param {String}/{Object} email1
|
||||
* @param {String}/{Object} email2
|
||||
* @return {Bool} result
|
||||
*/
|
||||
export const isEqualEmail = (email1, email2) => {
|
||||
let parsed1 = parseAddress(email1);
|
||||
let parsed2 = parseAddress(email2);
|
||||
|
||||
if (!parsed1.isValid || !parsed2.isValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsed1.email === parsed2.email;
|
||||
};
|
||||
|
||||
export class Email {
|
||||
constructor(name, email, parseErrors) {
|
||||
this.name = name || "";
|
||||
this.email = email;
|
||||
this.parseErrors = parseErrors;
|
||||
}
|
||||
|
||||
isValid = () => {
|
||||
return this.parseErrors.length === 0;
|
||||
};
|
||||
|
||||
obj2Contact = (object) => {
|
||||
let t = undefined;
|
||||
if (object.email) {
|
||||
t = object.email;
|
||||
object.name && (t = '"' + object.name.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '" <' + t + ">");
|
||||
equals = function(addr) {
|
||||
if (typeof addr === "object" && addr instanceof Email) {
|
||||
return this.email === addr.email && this.name === addr.name;
|
||||
} else if (typeof addr === "string") {
|
||||
var parsed = parseAddress(addr);
|
||||
return this.email === parsed.email && this.name === parsed.name;
|
||||
}
|
||||
return t;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
ParseAddress = () => {
|
||||
const addresses = this.addressesToArray(this.value)
|
||||
if (addresses === null) {
|
||||
this.writeError(this.value);
|
||||
}
|
||||
else {
|
||||
this.validateEmail(addresses[0]);
|
||||
}
|
||||
|
||||
return this.parsedObjs.addresses;
|
||||
}
|
||||
|
||||
|
||||
validateEmail = (parsedObject) => {
|
||||
|
||||
let isValid = true;
|
||||
if (parsedObject.domain.indexOf(".") === -1 || !/(^((?!-)[a-zA-Z0-9-]{2,63}\.)+[a-zA-Z]{2,63}\.?$)/.test(parsedObject.domain)) {
|
||||
isValid = false;
|
||||
this.parsedObjs.errors.push({ message: "Incorrect domain", type: parseErrorTypes.IncorrectEmail, errorItem: parsedObject });
|
||||
}
|
||||
|
||||
if (parsedObject.domain.indexOf('[') === 0 && parsedObject.domain.indexOf(']') === parsedObject.domain.length - 1) {
|
||||
this.parsedObjs.errors.push({ message: "Domains as ip address are not supported", type: parseErrorTypes.IncorrectEmail, errorItem: parsedObject });
|
||||
}
|
||||
|
||||
if (!/^[\x00-\x7F]+$/.test(punycode.toUnicode(parsedObject.domain))) {
|
||||
isValid = false;
|
||||
this.parsedObjs.errors.push({ message: "Punycode domains are not supported", type: parseErrorTypes.IncorrectEmail, errorItem: parsedObject });
|
||||
}
|
||||
|
||||
if (!/^[\x00-\x7F]+$/.test(parsedObject.local) || !/^([a-zA-Z0-9]+)([_\-\.\+][a-zA-Z0-9]+)*$/.test(parsedObject.local)) {
|
||||
isValid = false;
|
||||
this.parsedObjs.errors.push({ message: "Incorrect localpart", type: parseErrorTypes.IncorrectEmail, errorItem: parsedObject });
|
||||
}
|
||||
|
||||
if (/\s+/.test(parsedObject.local) || parsedObject.local !== parsedObject.parts.local.tokens) {
|
||||
isValid = false;
|
||||
this.parsedObjs.errors.push({ message: "Incorrect, localpart contains spaces", type: parseErrorTypes.IncorrectEmail, errorItem: parsedObject });
|
||||
}
|
||||
|
||||
if (/\s+/.test(parsedObject.domain) || parsedObject.domain !== parsedObject.parts.domain.tokens) {
|
||||
isValid = false;
|
||||
this.parsedObjs.errors.push({ message: "Incorrect, domain contains spaces", type: parseErrorTypes.IncorrectEmail, errorItem: parsedObject });
|
||||
}
|
||||
|
||||
this.parsedObjs.addresses.push(parsedObject.name || "", parsedObject.address, isValid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check domain validity
|
||||
* @param {String} domain
|
||||
* @return {Bool} result
|
||||
*/
|
||||
IsValidDomainName = (domain) => {
|
||||
let parsed = emailAddresses.parseOneAddress("test@" + domain);
|
||||
return !!parsed && parsed.domain === domain && domain.indexOf(".") !== -1;
|
||||
}
|
||||
/**
|
||||
* Compare emails
|
||||
* @param {String}/{Object} email1
|
||||
* @param {String}/{Object} email2
|
||||
* @return {Bool} result
|
||||
*/
|
||||
IsEqualEmail = (email1, email2) => {
|
||||
let parsed1 = this.ParseAddress(email1);
|
||||
let parsed2 = this.ParseAddress(email2);
|
||||
|
||||
if (!parsed1.isValid ||
|
||||
!parsed2.isValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsed1.email === parsed2.email;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Email;
|
||||
export class EmailOptions {
|
||||
constructor() {
|
||||
this.allowDomainPunycode = false;
|
||||
this.allowLocalPartPunycode = false;
|
||||
this.allowDomainIp = false;
|
||||
this.allowStrictLocalPart = true;
|
||||
this.allowSpaces = false;
|
||||
this.allowName = true;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user