Module: MailAuth::Spf::Macro

Defined in:
lib/mailauth/spf/macro.rb

Overview

RFC 7208 §7. The little templating language a record uses to build a name out of the message in front of it — exists:%{ir}.%{d} asks a question about this particular client rather than about the domain in general.

Constant Summary collapse

DELIMITERS =

Whatever a transformer splits on, the pieces are rejoined with dots — the result is going into a domain name.

".-+,/_="
EXPANSION =

The expansion form, plus the three escapes, ordered so a lone % only matches once nothing legal does — exactly when it's a syntax error.

/%\{([^}]*)\}|%%|%_|%-|%/
BODY =

macro-letter transformers *delimiter

/\A([a-z])(\d*)([r]?)([#{Regexp.escape(DELIMITERS)}]*)\z/i
RESERVED =

RFC 3986's unreserved set. An uppercase macro letter expands the same as its lowercase twin and then URL-escapes everything outside this.

/[^A-Za-z0-9\-._~]/
TOPLABEL =

toplabel = ( *alphanum ALPHA alphanum ) / ( 1alphanum "-" *( alphanum / "-" ) alphanum )

/(?:[a-z0-9]*[a-z][a-z0-9]*|[a-z0-9]+-[a-z0-9\-]*[a-z0-9])/i
DOMAIN_END =

The tail of a domain-spec must be a real label rather than something a macro happened to produce, or a macro expansion standing alone.

/\.#{TOPLABEL}\.?\z/o

Class Method Summary collapse

Class Method Details

.domain_spec(string, context) ⇒ Object

Checked before expansion, not after — a macro may expand into something no resolver will answer for, but that fails the mechanism, not the record.



47
48
49
50
51
52
53
54
55
# File 'lib/mailauth/spf/macro.rb', line 47

def domain_spec(string, context)
  if string.to_s.empty?
    raise PermError, "empty domain-spec"
  elsif !ends_well?(string)
    raise PermError, "domain-spec #{string} does not end in a macro or a top-level label"
  else
    expand(string, context).downcase.chomp(".")
  end
end

.expand(string, context) ⇒ Object

Raises PermError on anything the ABNF doesn't allow: a record that can't be parsed can't be obeyed.



32
33
34
35
36
37
38
39
40
41
42
# File 'lib/mailauth/spf/macro.rb', line 32

def expand(string, context)
  string.to_s.gsub(EXPANSION) do |match|
    case match
    when "%%" then "%"
    when "%_" then " "
    when "%-" then "%20"
    when "%"  then raise PermError, "stray % in #{string}"
    else expansion(Regexp.last_match(1), context)
    end
  end
end