Skip to content

Commit

Permalink
Merge pull request #193 from hapijs/recursive-descent
Browse files Browse the repository at this point in the history
Rewrite as a recursive descent parser
  • Loading branch information
skeggse committed Oct 21, 2018
2 parents 9e343f1 + a622c30 commit 061e7bc
Show file tree
Hide file tree
Showing 9 changed files with 1,790 additions and 1,360 deletions.
91 changes: 91 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'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,
errMalformedUnicode: 163
};
108 changes: 108 additions & 0 deletions lib/diagnoses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
'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._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.
*
* @param {Set<number>=} exclusions The set of diagnoses to exclude per the
* configuration.
* @return {?Diagnosis} The worst diagnosis.
*/
getWorstDiagnosis(exclusions = new Set()) {

// Use the precomputed worst diagnosis if it's not excluded.
if (!this._worstDiagnosis || !exclusions.has(this._worstDiagnosis.type)) {
return this._worstDiagnosis;
}

// Otherwise, find the worst non-excluded diagnosis.
return this._diagnoses.reduce((worst, diagnosis) => {

if (exclusions.has(diagnosis.type) || (worst && diagnosis.type <= worst.type)) {
return worst;
}

return diagnosis;
}, null);
}

/**
* For compatibility with existing versions of isemail, this function gets
* the first fatal diagnosis, or the worst non-fatal diagnosis.
*
* @param {Set<number>} exclusions The set of diagnoses to exclude per the
* configuration.
* @return {?Diagnosis} The backwards-compatible diagnosis.
*/
getLegacyDiagnosis(exclusions) {

const firstFatalDiagnosis = this._diagnoses.find(({ type }) => type >= Constants.categories.rfc5322 && !exclusions.has(type));

if (firstFatalDiagnosis) {
return firstFatalDiagnosis;
}

return this.getWorstDiagnosis(exclusions);
}
}


module.exports = Diagnoses;
Loading

0 comments on commit 061e7bc

Please sign in to comment.