Skip to content

Commit

Permalink
Rewrite as a recursive descent parser
Browse files Browse the repository at this point in the history
This simplifies some of our validation, and opens the door to let users of the module run their own DNS check through a general-purpose API.
  • Loading branch information
skeggse committed Oct 12, 2018
1 parent d003d9b commit 20d2aff
Show file tree
Hide file tree
Showing 8 changed files with 1,687 additions and 1,311 deletions.
90 changes: 90 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use strict';

exports.categories = {
valid: 1,
dnsWarn: 7,
rfc5321: 15,
cfws: 31,
deprecated: 63,
rfc5322: 127,
error: 255
};

exports.diagnoses = {

// Address is valid

valid: 0,

// Address is valid for SMTP but has unusual elements

rfc5321TLD: 9,
rfc5321TLDNumeric: 10,
rfc5321QuotedString: 11,
rfc5321AddressLiteral: 12,

// Address is valid for message, but must be modified for envelope

cfwsComment: 17,
cfwsFWS: 18,

// Address contains non-ASCII when the allowUnicode option is false
// Has to be > internals.defaultThreshold so that it's rejected
// without an explicit errorLevel:
undesiredNonAscii: 25,

// Address contains deprecated elements, but may still be valid in some contexts

deprecatedLocalPart: 33,
deprecatedFWS: 34,
deprecatedQTEXT: 35,
deprecatedQP: 36,
deprecatedComment: 37,
deprecatedCTEXT: 38,
deprecatedIPv6: 39,
deprecatedCFWSNearAt: 49,

// Address is only valid according to broad definition in RFC 5322, but is otherwise invalid

rfc5322Domain: 65,
rfc5322TooLong: 66,
rfc5322LocalTooLong: 67,
rfc5322DomainTooLong: 68,
rfc5322LabelTooLong: 69,
rfc5322DomainLiteral: 70,
rfc5322DomainLiteralOBSDText: 71,
rfc5322IPv6GroupCount: 72,
rfc5322IPv62x2xColon: 73,
rfc5322IPv6BadCharacter: 74,
rfc5322IPv6MaxGroups: 75,
rfc5322IPv6ColonStart: 76,
rfc5322IPv6ColonEnd: 77,

// Address is invalid for any purpose

errExpectingDTEXT: 129,
errNoLocalPart: 130,
errNoDomain: 131,
errConsecutiveDots: 132,
errATEXTAfterCFWS: 133,
errATEXTAfterQS: 134,
errATEXTAfterDomainLiteral: 135,
errExpectingQPair: 136,
errExpectingATEXT: 137,
errExpectingQTEXT: 138,
errExpectingCTEXT: 139,
errBackslashEnd: 140,
errDotStart: 141,
errDotEnd: 142,
errDomainHyphenStart: 143,
errDomainHyphenEnd: 144,
errUnclosedQuotedString: 145,
errUnclosedComment: 146,
errUnclosedDomainLiteral: 147,
errFWSCRLFx2: 148,
errFWSCRLFEnd: 149,
errCRNoLF: 150,
errUnknownTLD: 160,
errDomainTooShort: 161,
errDotAfterDomainLiteral: 162
};
86 changes: 86 additions & 0 deletions lib/diagnoses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use strict';

// Load modules

const Constants = require('./constants');

/**
* Diagnosis stores a diagnosis-index pair.
*/
class Diagnosis {
/**
* @param {number} type The diagnostic code.
* @param {number} index The index of the diagnosis.
*/
constructor(type, index) {

this.type = type;
this.index = index;
}
}


/**
* Diagnoses tracks a set of diagnostic codes for a string, along with their
* positions in the string.
*/
class Diagnoses {
constructor() {

this._diagnoses = [];
this._legacyDiagnosis = null;
this._worstDiagnosis = null;
}

/**
* Report the given diagnosis at the given index.
*
* @param {number} type The diagnosis to report.
* @param {number} index The
*/
diagnose(type, index) {

const diagnosis = new Diagnosis(type, index);

if (this._worstDiagnosis === null || type > this._worstDiagnosis.type) {
this._worstDiagnosis = diagnosis;
}

this._diagnoses.push(diagnosis);
}

/**
* Check whether the given diagnosis has been reported.
*
* @param {number} queryType The diagnosis to query.
* @return {boolean} Whether the diagnosis has been reported.
*/
hasDiagnosis(queryType) {

return this._diagnoses.some(({ type }) => type === queryType);
}

/**
* Get the worst diagnosis encountered.
*
* @return {number} The worst diagnosis.
*/
getWorstDiagnosis() {

return this._worstDiagnosis;
}

/**
* For compatibility with existing versions of isemail, this function gets
* the first fatal diagnosis, or the worst non-fatal diagnosis.
*
* @return {number} The backwards-compatible diagnosis.
*/
getLegacyDiagnosis() {

return this._diagnoses.find(({ type }) => type >= Constants.categories.rfc5322) || this._worstDiagnosis;
}
}


module.exports = Diagnoses;
Loading

0 comments on commit 20d2aff

Please sign in to comment.