class SPF::Record

Constants

DEFAULT_QUALIFIER
RESULTS_BY_QUALIFIER

Attributes

errors[R]
terms[R]
text[R]

Public Class Methods

new(options) click to toggle source
Calls superclass method
# File lib/spf/model.rb, line 843
def initialize(options)
  super()
  @parse_text       = (@text = options[:text] if not self.instance_variable_defined?(:@parse_text)).dup
  @terms          ||= []
  @global_mods    ||= {}
  @errors           = []
  @ip_netblocks     = []
  @record_domain    = options[:record_domain]
  @raise_exceptions = options.has_key?(:raise_exceptions) ? options[:raise_exceptions] : true
end
new_from_string(text, options = {}) click to toggle source
# File lib/spf/model.rb, line 854
def self.new_from_string(text, options = {})
  options[:text] = text
  record = new(options)
  record.parse
  return record
end

Public Instance Methods

error(exception) click to toggle source
# File lib/spf/model.rb, line 861
def error(exception)
  raise exception if @raise_exceptions
  @errors << exception
end
eval(server, request, want_result = true) click to toggle source
# File lib/spf/model.rb, line 987
def eval(server, request, want_result = true)
  raise SPF::OptionRequiredError.new('SPF server object required for record evaluation') unless server
  raise SPF::OptionRequiredError.new('Request object required for record evaluation')    unless request
  begin
    @terms.each do |term|
      if SPF::Mech === term
        # Term is a mechanism.
        mech = term
        if mech.match(server, request, request.ip_address != nil)
          result_name = RESULTS_BY_QUALIFIER[mech.qualifier]
          result_class = server.result_class(result_name)
          result = result_class.new([server, request, "Mechanism '#{term.text}' matched"])
          mech.explain(server, request, result)
          raise result if want_result
        end
      elsif SPF::PositionalMod === term
        # Term is a positional modifier.
        mod = term
        mod.process(server, request)
      elsif SPF::UnknownMod === term
        # Term is an unknown modifier.  Ignore it (RFC 4408, 6/3).
      else
        # Invalid term object encountered:
        error(SPF::UnexpectedTermObjectError.new("Unexpected term object '#{term}' encountered."))
      end
    end
    server.throw_result(:neutral_by_default, request,
      'Default neutral result due to no mechanism matches')
  rescue SPF::Result => result
    # Process global modifiers in ascending order of precedence:
    global_mods.each do |global_mod|
      global_mod.process(server, request, result)
    end
    raise result if want_result
  end
end
global_mod(mod_name) click to toggle source
# File lib/spf/model.rb, line 979
def global_mod(mod_name)
  return @global_mods[mod_name]
end
global_mods() click to toggle source
# File lib/spf/model.rb, line 975
def global_mods
  return @global_mods.values.sort {|a,b| a.precedence <=> b.precedence }
end
ip_netblocks() click to toggle source
# File lib/spf/model.rb, line 866
def ip_netblocks
  @ip_netblocks.flatten!
  return @ip_netblocks
end
parse() click to toggle source
# File lib/spf/model.rb, line 871
def parse
  unless self.instance_variable_defined?(:@parse_text) and @parse_text
    error(SPF::NothingToParseError.new('Nothing to parse for record'))
    return
  end
  self.parse_version_tag
  while @parse_text.length > 0
    term = nil
    begin
      term = self.parse_term
    rescue SPF::Error => e
      term.errors << e if term
      @errors     << e
      raise if @raise_exceptions
      return if SPF::JunkInRecordError === e or SPF::JunkInTermError === e
    end
  end
end
parse_term() click to toggle source
# File lib/spf/model.rb, line 898
def parse_term
  regex = /
    ^
    (
      #{SPF::Mech::QUALIFIER_PATTERN}?
      (#{SPF::Mech::NAME_PATTERN})
      [^\x20]*
    )
    (?: \x20+ | $ )
  /x

  term = nil
  if @parse_text.sub!(regex, '') and $&
    # Looks like a mechanism:
    mech_text  = $1
    mech_name  = $2.downcase
    mech_class = self.mech_classes[mech_name.to_sym]
    exception  = nil
    unless mech_class
      exception = SPF::InvalidMechError.new("Unknown mechanism type '#{mech_name}' in '#{self.version_tag}' record")
      error(exception)
      mech_class = SPF::Mech
    end
    options = {:raise_exceptions => @raise_exceptions}
    if instance_variable_defined?("@record_domain")
      options[:record_domain] = @record_domain
    end
    term = mech = mech_class.new_from_string(mech_text, options)
    term.errors << exception if exception
    @ip_netblocks << mech.ip_netblocks if mech.ip_netblocks
    @terms << mech
    if mech_class == SPF::Mech
      raise SPF::InvalidMechError.new("Unknown mechanism type '#{mech_name}' in '#{self.version_tag}' record")
    end
  elsif (
    @parse_text.sub!(/
      ^
      (
        (#{SPF::Mod::NAME_PATTERN}) =
        [^\x20]*
      )
      (?: \x20+ | $ )
    /x, '') and $&
  )
    # Looks like a modifier:
    mod_text  = $1
    mod_name  = $2.downcase
    mod_class = self.class::MOD_CLASSES[mod_name.to_sym] || SPF::UnknownMod
    if mod_class
      # Known modifier.
      term = mod = mod_class.new_from_string(mod_text, {:raise_exceptions => @raise_exceptions})
      if SPF::GlobalMod === mod
        # Global modifier.
        if @global_mods[mod_name]
          raise SPF::DuplicateGlobalModError.new("Duplicate global modifier '#{mod_name}' encountered")
        end
        @global_mods[mod_name] = mod
      elsif SPF::PositionalMod === mod
        # Positional modifier, queue normally:
        @terms << mod
      end
    end

  else
    token_text = @parse_text.sub(/\s.*/, '')
    hint = nil
    if token_text =~ /#{SPF::Term::IPV4_ADDRESS_PATTERN}/x
      hint = 'missing ip4: before IPv4 address?'
    elsif token_text =~ /#{SPF::Term::IPV6_ADDRESS_PATTERN}/x
      hint = 'missing ip6: before IPv6 address?'
    end
    raise SPF::JunkInRecordError.new("Junk encountered in record '#{@text}'", @text, @parse_text, hint)
  end
  @errors.concat(term.errors)
  return term
end
parse_version_tag() click to toggle source
# File lib/spf/model.rb, line 890
def parse_version_tag
  @parse_text.sub!(/^#{self.version_tag_pattern}\s*/ix, '')
  unless $1
    raise SPF::InvalidRecordVersionError.new(
      "Not a '#{self.version_tag}' record: '#{@text}'")
  end
end
to_s() click to toggle source
# File lib/spf/model.rb, line 983
def to_s
  return [version_tag, @terms, @global_mods].join(' ')
end