web: Components: Refactoring of email parsing

This commit is contained in:
Alexey Safronov 2019-10-10 12:34:25 +03:00
parent 021ac77d2f
commit f2e8ce6b2a
2 changed files with 255 additions and 157 deletions

View File

@ -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 });
}

View File

@ -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;
}
}