Class: MailAuth::Spf::Record

Inherits:
Object
  • Object
show all
Defined in:
lib/mailauth/spf/record.rb

Overview

A v=spf1 record, read and taken apart. Parsing is complete before evaluation begins, so v=spf1 -all ip6 is a broken record — not a fail — even though nothing after -all can ever be evaluated.

Constant Summary collapse

VERSION =
"v=spf1"
MODIFIER =

A modifier is a name and an "=" reached before any ":" or "/". redirect:example.com therefore isn't one — it's a mechanism named "redirect", which is to say a typo. The name stops at the first "=": every one after it belongs to the macro-string, where "=" is both a legal delimiter and a legal literal.

/\A(?<name>[^:\/=]*)=(?<value>.*)\z/
MODIFIER_NAME =
/\A[a-z][a-z0-9\-_.]*\z/i
MECHANISM =
/\A(?<qualifier>[+\-~?])?(?<name>[a-z0-9_.\-]+)(?<argument>[:\/].*)?\z/i
ARGUMENT =

domain-spec followed by an optional dual-cidr-length. The domain is matched lazily so that a trailing "/24" is read as a prefix length, while the slashes a domain-spec is allowed to contain stay in the name.

/\A(?<spec>.*?)(?:\/(?<cidr4>\d+))?(?:\/\/(?<cidr6>\d+))?\z/
DUAL_CIDR =
/\A(?:\/(?<cidr4>\d+))?(?:\/\/(?<cidr6>\d+))?\z/
ADDRESS =
/\A(?<address>.*?)(?:\/(?<length>\d+))?\z/
PRINTABLE =

SPF is defined over 7-bit ASCII, and a record carrying anything else has been mangled somewhere between its author and us.

/\A[\x20-\x7e]*\z/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(text, context) ⇒ Record

Returns a new instance of Record.



52
53
54
55
56
57
58
59
# File 'lib/mailauth/spf/record.rb', line 52

def initialize(text, context)
  @text = text
  @context = context
  @terms = []
  @modifiers = {}

  parse
end

Instance Attribute Details

#redirectObject (readonly)

Returns the value of attribute redirect.



50
51
52
# File 'lib/mailauth/spf/record.rb', line 50

def redirect
  @redirect
end

#termsObject (readonly)

Returns the value of attribute terms.



50
51
52
# File 'lib/mailauth/spf/record.rb', line 50

def terms
  @terms
end

Class Method Details

.fetch(domain, budget:, context:) ⇒ Object

Zero is not an error but an answer — the domain publishes no policy. Two is: nothing chooses between them.



40
41
42
43
44
45
46
47
48
# File 'lib/mailauth/spf/record.rb', line 40

def self.fetch(domain, budget:, context:)
  records = budget.txt(domain).select { |record| record.strip.split(/\s+/).first&.downcase == VERSION }

  case records.length
  when 0 then raise NoRecord, "no SPF record for #{domain}"
  when 1 then new(records.first, context)
  else raise PermError, "#{records.length} SPF records for #{domain}"
  end
end