import { ValidationError } from "../ValidationError.js"; import { Modifier } from "../spf/Modifier.js"; import { ValueRequirement } from "../spf/ValueRequirement.js"; import { DomainMechanism } from "../spf/DomainMechanism.js"; import { IPv4Mechanism } from "../spf/IPv4Mechanism.js"; import { IPv6Mechanism } from "../spf/IPv6Mechanism.js"; import { Mechanism } from "../spf/Mechanism.js"; import { VersionTerm } from "../spf/VersionTerm.js"; export class SpfRecord { static fields = [ new VersionTerm("v", "spf1") .required() .pos(0), new DomainMechanism("include") .multiple() .pos(1), new DomainMechanism("a") .multiple() .pos(1), new DomainMechanism("mx") .multiple() .pos(2), new DomainMechanism("ptr") .multiple() .pos(2), new IPv4Mechanism("ipv4") .multiple() .pos(2), new IPv6Mechanism("ipv6") .multiple() .pos(2), new DomainMechanism("exists") .multiple() .pos(2), new Mechanism("all") .value(ValueRequirement.PROHIBITED) .pos(3), new Modifier("redirect") .pos(4), new Modifier("exp") .pos(4), ]; constructor(text) { this.text = text; } tokenize() { return this.text.trim().split(/\s+/); } getKeyValues() { const result = []; for (const token of this.tokenize()) { const name = token.match(/^[-+?~]?(\w*)/)[1]; const term = this.constructor.fields.find(f => f.key === name); if (!term) { throw new ValidationError(`Unknown term: ${name}`); } const [directive, value] = token.split(term.separator); const [, qualifier, key] = directive.match(/^([-+?~]?)(\w*)/); result.push({ qualifier, key, value }); } return result; } validate() { const values = this.getKeyValues(); for (const term of this.constructor.fields) { if (term.isRequired && !values.some(v => v.key === term.key)) { throw new ValidationError(`Term "${term.key}" is required`); } } let lastPos = 0; for (let i = 0; i < values.length; i++) { const input = values[i]; const term = this.constructor.fields.find(d => d.key === input.key); if (!term) { throw new ValidationError(`Unknown term: ${input.key}`); } if (term.position < lastPos) { const lastDirective = this.constructor.fields.find(d => d.key === values[i-1].key); throw new ValidationError(`Term "${lastDirective.key}" must come after "${term.key}"`); } if (term instanceof Modifier && input.qualifier) { throw new ValidationError(`Modifier "${term.key}" must not have a qualifier`) } if (!input.value && term.valueRequirement === ValueRequirement.REQUIRED) { throw new ValidationError(`Term "${term.key}" is missing a value`); } if (input.value && term.valueRequirement === ValueRequirement.PROHIBITED) { throw new ValidationError(`Term "${term.key}" must not have a value`); } if (input.value) term.validate(input.value); lastPos = term.position; } } }