email_address 0.1.13 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/README.md +42 -3
- data/email_address.gemspec +1 -0
- data/lib/email_address.rb +17 -18
- data/lib/email_address/active_record_validator.rb +2 -2
- data/lib/email_address/address.rb +94 -100
- data/lib/email_address/canonical_email_address_type.rb +14 -12
- data/lib/email_address/config.rb +69 -47
- data/lib/email_address/email_address_type.rb +15 -13
- data/lib/email_address/exchanger.rb +29 -30
- data/lib/email_address/host.rb +123 -125
- data/lib/email_address/local.rb +7 -7
- data/lib/email_address/version.rb +1 -1
- data/test/email_address/test_address.rb +5 -0
- data/test/email_address/test_host.rb +29 -31
- data/test/test_aliasing.rb +54 -0
- metadata +8 -6
| @@ -29,20 +29,22 @@ | |
| 29 29 | 
             
            #    user.canonical_email #=> "patsmith@gmail.com"
         | 
| 30 30 | 
             
            ################################################################################
         | 
| 31 31 |  | 
| 32 | 
            -
             | 
| 32 | 
            +
            module EmailAddress
         | 
| 33 | 
            +
              class CanonicalEmailAddressType < ActiveRecord::Type::Value
         | 
| 33 34 |  | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 35 | 
            +
                # From user input, setter
         | 
| 36 | 
            +
                def cast(value)
         | 
| 37 | 
            +
                  super(Address.new(value).canonical)
         | 
| 38 | 
            +
                end
         | 
| 38 39 |  | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 40 | 
            +
                # From a database value
         | 
| 41 | 
            +
                def deserialize(value)
         | 
| 42 | 
            +
                  value && Address.new(value).normal
         | 
| 43 | 
            +
                end
         | 
| 43 44 |  | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 45 | 
            +
                # To a database value (string)
         | 
| 46 | 
            +
                def serialize(value)
         | 
| 47 | 
            +
                  value && Address.new(value).normal
         | 
| 48 | 
            +
                end
         | 
| 47 49 | 
             
              end
         | 
| 48 50 | 
             
            end
         | 
    
        data/lib/email_address/config.rb
    CHANGED
    
    | @@ -97,67 +97,67 @@ module EmailAddress | |
| 97 97 | 
             
              # * exchanger_match:    %w(google.com 127.0.0.1 10.9.8.0/24 ::1/64)
         | 
| 98 98 | 
             
              #
         | 
| 99 99 |  | 
| 100 | 
            -
              require  | 
| 100 | 
            +
              require "yaml"
         | 
| 101 101 |  | 
| 102 102 | 
             
              class Config
         | 
| 103 103 | 
             
                @config = {
         | 
| 104 | 
            -
                  dns_lookup: | 
| 105 | 
            -
                  dns_timeout: | 
| 106 | 
            -
                  sha1_secret: | 
| 107 | 
            -
                  munge_string: | 
| 108 | 
            -
             | 
| 109 | 
            -
                  local_downcase: | 
| 110 | 
            -
                  local_fix: | 
| 111 | 
            -
                  local_encoding: | 
| 112 | 
            -
                  local_parse: | 
| 113 | 
            -
                  local_format: | 
| 114 | 
            -
                  local_size: | 
| 115 | 
            -
                  tag_separator: | 
| 116 | 
            -
                  mailbox_size: | 
| 117 | 
            -
                  mailbox_canonical: | 
| 118 | 
            -
                  mailbox_validator: | 
| 119 | 
            -
             | 
| 120 | 
            -
                  host_encoding: | 
| 121 | 
            -
                  host_validation: | 
| 122 | 
            -
                  host_size: | 
| 123 | 
            -
                  host_allow_ip: | 
| 104 | 
            +
                  dns_lookup: :mx, # :mx, :a, :off
         | 
| 105 | 
            +
                  dns_timeout: nil,
         | 
| 106 | 
            +
                  sha1_secret: "",
         | 
| 107 | 
            +
                  munge_string: "*****",
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  local_downcase: true,
         | 
| 110 | 
            +
                  local_fix: false,
         | 
| 111 | 
            +
                  local_encoding: :ascii, # :ascii, :unicode,
         | 
| 112 | 
            +
                  local_parse: nil, # nil, Proc
         | 
| 113 | 
            +
                  local_format: :conventional, # :conventional, :relaxed, :redacted, :standard, Proc
         | 
| 114 | 
            +
                  local_size: 1..64,
         | 
| 115 | 
            +
                  tag_separator: "+", # nil, character
         | 
| 116 | 
            +
                  mailbox_size: 1..64, # without tag
         | 
| 117 | 
            +
                  mailbox_canonical: nil, # nil,  Proc
         | 
| 118 | 
            +
                  mailbox_validator: nil, # nil,  Proc
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  host_encoding: :punycode || :unicode,
         | 
| 121 | 
            +
                  host_validation: :mx || :a || :connect || :syntax,
         | 
| 122 | 
            +
                  host_size: 1..253,
         | 
| 123 | 
            +
                  host_allow_ip: false,
         | 
| 124 124 | 
             
                  host_remove_spaces: false,
         | 
| 125 | 
            -
                  host_local: | 
| 125 | 
            +
                  host_local: false,
         | 
| 126 126 |  | 
| 127 127 | 
             
                  address_validation: :parts, # :parts, :smtp, Proc
         | 
| 128 | 
            -
                  address_size: | 
| 129 | 
            -
                  address_fqdn_domain: nil | 
| 128 | 
            +
                  address_size: 3..254,
         | 
| 129 | 
            +
                  address_fqdn_domain: nil # Fully Qualified Domain Name = [host].[domain.tld]
         | 
| 130 130 | 
             
                }
         | 
| 131 131 |  | 
| 132 132 | 
             
                # 2018-04: AOL and Yahoo now under "oath.com", owned by Verizon. Keeping separate for now
         | 
| 133 133 | 
             
                @providers = {
         | 
| 134 134 | 
             
                  aol: {
         | 
| 135 | 
            -
                    host_match: | 
| 135 | 
            +
                    host_match: %w[aol. compuserve. netscape. aim. cs.]
         | 
| 136 136 | 
             
                  },
         | 
| 137 137 | 
             
                  google: {
         | 
| 138 | 
            -
                    host_match: | 
| 139 | 
            -
                    exchanger_match: | 
| 140 | 
            -
                    local_size: | 
| 138 | 
            +
                    host_match: %w[gmail.com googlemail.com],
         | 
| 139 | 
            +
                    exchanger_match: %w[google.com googlemail.com],
         | 
| 140 | 
            +
                    local_size: 5..64,
         | 
| 141 141 | 
             
                    local_private_size: 1..64, # When hostname not in host_match (private label)
         | 
| 142 | 
            -
                    mailbox_canonical: ->(m) {m. | 
| 142 | 
            +
                    mailbox_canonical: ->(m) { m.delete(".") }
         | 
| 143 143 | 
             
                  },
         | 
| 144 144 | 
             
                  msn: {
         | 
| 145 | 
            -
                    host_match: | 
| 146 | 
            -
                     | 
| 145 | 
            +
                    host_match: %w[msn. hotmail. outlook. live.],
         | 
| 146 | 
            +
                    exchanger_match: %w[outlook.com],
         | 
| 147 | 
            +
                    mailbox_validator: ->(m, t) { m =~ /\A\w[\-\w]*(?:\.[\-\w]+)*\z/i }
         | 
| 147 148 | 
             
                  },
         | 
| 148 149 | 
             
                  yahoo: {
         | 
| 149 | 
            -
                    host_match: | 
| 150 | 
            -
                    exchanger_match: | 
| 151 | 
            -
                  } | 
| 150 | 
            +
                    host_match: %w[yahoo. ymail. rocketmail.],
         | 
| 151 | 
            +
                    exchanger_match: %w[yahoodns yahoo-inc]
         | 
| 152 | 
            +
                  }
         | 
| 152 153 | 
             
                }
         | 
| 153 154 |  | 
| 154 | 
            -
             | 
| 155 155 | 
             
                # Loads messages: {"en"=>{"email_address"=>{"invalid_address"=>"Invalid Email Address",...}}}
         | 
| 156 156 | 
             
                # Rails/I18n gem: t(email_address.error, scope: "email_address")
         | 
| 157 | 
            -
                @errors = YAML.load_file(File.dirname(__FILE__)+"/messages.yaml")
         | 
| 157 | 
            +
                @errors = YAML.load_file(File.dirname(__FILE__) + "/messages.yaml")
         | 
| 158 158 |  | 
| 159 159 | 
             
                # Set multiple default configuration settings
         | 
| 160 | 
            -
                def self.configure(config={})
         | 
| 160 | 
            +
                def self.configure(config = {})
         | 
| 161 161 | 
             
                  @config.merge!(config)
         | 
| 162 162 | 
             
                end
         | 
| 163 163 |  | 
| @@ -168,34 +168,56 @@ module EmailAddress | |
| 168 168 | 
             
                end
         | 
| 169 169 |  | 
| 170 170 | 
             
                # Returns the hash of Provider rules
         | 
| 171 | 
            -
                 | 
| 172 | 
            -
                   | 
| 171 | 
            +
                class << self
         | 
| 172 | 
            +
                  attr_reader :providers
         | 
| 173 173 | 
             
                end
         | 
| 174 174 |  | 
| 175 175 | 
             
                # Configure or lookup a provider by name.
         | 
| 176 | 
            -
                def self.provider(name, config={})
         | 
| 176 | 
            +
                def self.provider(name, config = {})
         | 
| 177 177 | 
             
                  name = name.to_sym
         | 
| 178 178 | 
             
                  if config.size > 0
         | 
| 179 | 
            -
                    @providers[name]  | 
| 180 | 
            -
                    @providers[name].merge!(config)
         | 
| 179 | 
            +
                    @providers[name.to_sym] = config
         | 
| 181 180 | 
             
                  end
         | 
| 182 181 | 
             
                  @providers[name]
         | 
| 183 182 | 
             
                end
         | 
| 184 183 |  | 
| 185 | 
            -
                def self.error_message(name, locale="en")
         | 
| 184 | 
            +
                def self.error_message(name, locale = "en")
         | 
| 186 185 | 
             
                  @errors[locale]["email_address"][name.to_s] || name.to_s
         | 
| 187 186 | 
             
                end
         | 
| 188 187 |  | 
| 189 188 | 
             
                # Customize your own error message text.
         | 
| 190 | 
            -
                def self.error_messages(hash= | 
| 191 | 
            -
                   | 
| 192 | 
            -
                   | 
| 189 | 
            +
                def self.error_messages(hash = {}, locale = "en", *extra)
         | 
| 190 | 
            +
                  hash = extra.first if extra.first.is_a? Hash
         | 
| 191 | 
            +
                  unless hash.empty?
         | 
| 192 | 
            +
                    @errors[locale]["email_address"] = @errors[locale]["email_address"].merge(hash)
         | 
| 193 | 
            +
                  end
         | 
| 194 | 
            +
                  @errors[locale]["email_address"]
         | 
| 193 195 | 
             
                end
         | 
| 194 196 |  | 
| 195 197 | 
             
                def self.all_settings(*configs)
         | 
| 196 198 | 
             
                  config = @config.clone
         | 
| 197 | 
            -
                  configs.each {|c| config.merge!(c) }
         | 
| 199 | 
            +
                  configs.each { |c| config.merge!(c) }
         | 
| 198 200 | 
             
                  config
         | 
| 199 201 | 
             
                end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                def initialize(overrides = {})
         | 
| 204 | 
            +
                  @config = Config.all_settings(overrides)
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                def []=(setting, value)
         | 
| 208 | 
            +
                  @config[setting.to_sym] = value
         | 
| 209 | 
            +
                end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                def [](setting)
         | 
| 212 | 
            +
                  @config[setting.to_sym]
         | 
| 213 | 
            +
                end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                def configure(settings)
         | 
| 216 | 
            +
                  @config = @config.merge(settings)
         | 
| 217 | 
            +
                end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                def to_h
         | 
| 220 | 
            +
                  @config
         | 
| 221 | 
            +
                end
         | 
| 200 222 | 
             
              end
         | 
| 201 223 | 
             
            end
         | 
| @@ -29,20 +29,22 @@ | |
| 29 29 | 
             
            #    user.canonical_email #=> "patsmith@gmail.com"
         | 
| 30 30 | 
             
            ################################################################################
         | 
| 31 31 |  | 
| 32 | 
            -
             | 
| 32 | 
            +
            module EmailAddress
         | 
| 33 | 
            +
              class EmailAddressType < ActiveRecord::Type::Value
         | 
| 33 34 |  | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 35 | 
            +
                # From user input, setter
         | 
| 36 | 
            +
                def cast(value)
         | 
| 37 | 
            +
                  super(Address.new(value).normal)
         | 
| 38 | 
            +
                end
         | 
| 38 39 |  | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 40 | 
            +
                # From a database value
         | 
| 41 | 
            +
                def deserialize(value)
         | 
| 42 | 
            +
                  value && Address.new(value).normal
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                # To a database value (string)
         | 
| 46 | 
            +
                def serialize(value)
         | 
| 47 | 
            +
                  value && Address.new(value).normal
         | 
| 48 | 
            +
                end
         | 
| 47 49 | 
             
              end
         | 
| 48 50 | 
             
            end
         | 
| @@ -1,16 +1,16 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 5 | 
            -
            require  | 
| 3 | 
            +
            require "resolv"
         | 
| 4 | 
            +
            require "netaddr"
         | 
| 5 | 
            +
            require "socket"
         | 
| 6 6 |  | 
| 7 7 | 
             
            module EmailAddress
         | 
| 8 8 | 
             
              class Exchanger
         | 
| 9 9 | 
             
                include Enumerable
         | 
| 10 10 |  | 
| 11 | 
            -
                def self.cached(host, config={})
         | 
| 11 | 
            +
                def self.cached(host, config = {})
         | 
| 12 12 | 
             
                  @host_cache ||= {}
         | 
| 13 | 
            -
                  @cache_size ||= ENV[ | 
| 13 | 
            +
                  @cache_size ||= ENV["EMAIL_ADDRESS_CACHE_SIZE"].to_i || 100
         | 
| 14 14 | 
             
                  if @host_cache.has_key?(host)
         | 
| 15 15 | 
             
                    o = @host_cache.delete(host)
         | 
| 16 16 | 
             
                    @host_cache[host] = o # LRU cache, move to end
         | 
| @@ -22,22 +22,24 @@ module EmailAddress | |
| 22 22 | 
             
                  end
         | 
| 23 23 | 
             
                end
         | 
| 24 24 |  | 
| 25 | 
            -
                def initialize(host, config={})
         | 
| 25 | 
            +
                def initialize(host, config = {})
         | 
| 26 26 | 
             
                  @host = host
         | 
| 27 | 
            -
                  @config = config
         | 
| 27 | 
            +
                  @config = config.is_a?(Hash) ? Config.new(config) : config
         | 
| 28 | 
            +
                  @dns_disabled = @config[:host_validation] == :syntax || @config[:dns_lookup] == :off
         | 
| 28 29 | 
             
                end
         | 
| 29 30 |  | 
| 30 31 | 
             
                def each(&block)
         | 
| 32 | 
            +
                  return if @dns_disabled
         | 
| 31 33 | 
             
                  mxers.each do |m|
         | 
| 32 | 
            -
                    yield({host:m[0], ip:m[1], priority:m[2]})
         | 
| 34 | 
            +
                    yield({host: m[0], ip: m[1], priority: m[2]})
         | 
| 33 35 | 
             
                  end
         | 
| 34 36 | 
             
                end
         | 
| 35 37 |  | 
| 36 38 | 
             
                # Returns the provider name based on the MX-er host names, or nil if not matched
         | 
| 37 39 | 
             
                def provider
         | 
| 38 40 | 
             
                  return @provider if defined? @provider
         | 
| 39 | 
            -
                   | 
| 40 | 
            -
                    if config[:exchanger_match] &&  | 
| 41 | 
            +
                  Config.providers.each do |provider, config|
         | 
| 42 | 
            +
                    if config[:exchanger_match] && matches?(config[:exchanger_match])
         | 
| 41 43 | 
             
                      return @provider = provider
         | 
| 42 44 | 
             
                    end
         | 
| 43 45 | 
             
                  end
         | 
| @@ -50,40 +52,37 @@ module EmailAddress | |
| 50 52 | 
             
                # may not find provider by MX name or IP. I'm not sure about the "0.0.0.0" ip, it should
         | 
| 51 53 | 
             
                # be good in this context, but in "listen" context it means "all bound IP's"
         | 
| 52 54 | 
             
                def mxers
         | 
| 53 | 
            -
                  return [["example.com", "0.0.0.0", 1]] if @ | 
| 54 | 
            -
                  @mxers ||= Resolv::DNS.open  | 
| 55 | 
            +
                  return [["example.com", "0.0.0.0", 1]] if @dns_disabled
         | 
| 56 | 
            +
                  @mxers ||= Resolv::DNS.open { |dns|
         | 
| 55 57 | 
             
                    dns.timeouts = @config[:dns_timeout] if @config[:dns_timeout]
         | 
| 56 58 |  | 
| 57 59 | 
             
                    ress = begin
         | 
| 58 60 | 
             
                      dns.getresources(@host, Resolv::DNS::Resource::IN::MX)
         | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            +
                           rescue Resolv::ResolvTimeout
         | 
| 62 | 
            +
                             []
         | 
| 61 63 | 
             
                    end
         | 
| 62 64 |  | 
| 63 | 
            -
                    records = ress.map  | 
| 64 | 
            -
                       | 
| 65 | 
            -
                         | 
| 66 | 
            -
                          [r.exchange.to_s, IPSocket::getaddress(r.exchange.to_s), r.preference]
         | 
| 67 | 
            -
                        else
         | 
| 68 | 
            -
                          nil
         | 
| 69 | 
            -
                        end
         | 
| 70 | 
            -
                      rescue SocketError # not found, but could also mean network not work or it could mean one record doesn't resolve an address
         | 
| 71 | 
            -
                        nil
         | 
| 65 | 
            +
                    records = ress.map { |r|
         | 
| 66 | 
            +
                      if r.exchange.to_s > " "
         | 
| 67 | 
            +
                        [r.exchange.to_s, IPSocket.getaddress(r.exchange.to_s), r.preference]
         | 
| 72 68 | 
             
                      end
         | 
| 73 | 
            -
                     | 
| 69 | 
            +
                    }
         | 
| 74 70 | 
             
                    records.compact
         | 
| 75 | 
            -
                   | 
| 71 | 
            +
                  }
         | 
| 72 | 
            +
                # not found, but could also mean network not work or it could mean one record doesn't resolve an address
         | 
| 73 | 
            +
                rescue SocketError
         | 
| 74 | 
            +
                  [["example.com", "0.0.0.0", 1]]
         | 
| 76 75 | 
             
                end
         | 
| 77 76 |  | 
| 78 77 | 
             
                # Returns Array of domain names for the MX'ers, used to determine the Provider
         | 
| 79 78 | 
             
                def domains
         | 
| 80 | 
            -
                  @_domains ||= mxers.map {|m|  | 
| 79 | 
            +
                  @_domains ||= mxers.map { |m| Host.new(m.first).domain_name }.sort.uniq
         | 
| 81 80 | 
             
                end
         | 
| 82 81 |  | 
| 83 82 | 
             
                # Returns an array of MX IP address (String) for the given email domain
         | 
| 84 83 | 
             
                def mx_ips
         | 
| 85 | 
            -
                  return ["0.0.0.0"] if @ | 
| 86 | 
            -
                  mxers.map {|m| m[1] }
         | 
| 84 | 
            +
                  return ["0.0.0.0"] if @dns_disabled
         | 
| 85 | 
            +
                  mxers.map { |m| m[1] }
         | 
| 87 86 | 
             
                end
         | 
| 88 87 |  | 
| 89 88 | 
             
                # Simple matcher, takes an array of CIDR addresses (ip/bits) and strings.
         | 
| @@ -96,9 +95,9 @@ module EmailAddress | |
| 96 95 | 
             
                  rules = Array(rules)
         | 
| 97 96 | 
             
                  rules.each do |rule|
         | 
| 98 97 | 
             
                    if rule.include?("/")
         | 
| 99 | 
            -
                      return rule if  | 
| 98 | 
            +
                      return rule if in_cidr?(rule)
         | 
| 100 99 | 
             
                    else
         | 
| 101 | 
            -
                       | 
| 100 | 
            +
                      each { |mx| return rule if mx[:host].end_with?(rule) }
         | 
| 102 101 | 
             
                    end
         | 
| 103 102 | 
             
                  end
         | 
| 104 103 | 
             
                  false
         | 
    
        data/lib/email_address/host.rb
    CHANGED
    
    | @@ -1,8 +1,7 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            require  | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 5 | 
            -
            require 'net/smtp'
         | 
| 1 | 
            +
            require "simpleidn"
         | 
| 2 | 
            +
            require "resolv"
         | 
| 3 | 
            +
            require "netaddr"
         | 
| 4 | 
            +
            require "net/smtp"
         | 
| 6 5 |  | 
| 7 6 | 
             
            module EmailAddress
         | 
| 8 7 | 
             
              ##############################################################################
         | 
| @@ -34,12 +33,12 @@ module EmailAddress | |
| 34 33 | 
             
              class Host
         | 
| 35 34 | 
             
                attr_reader :host_name
         | 
| 36 35 | 
             
                attr_accessor :dns_name, :domain_name, :registration_name,
         | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 36 | 
            +
                  :tld, :tld2, :subdomains, :ip_address, :config, :provider,
         | 
| 37 | 
            +
                  :comment, :error_message, :reason
         | 
| 39 38 | 
             
                MAX_HOST_LENGTH = 255
         | 
| 40 39 |  | 
| 41 40 | 
             
                # Sometimes, you just need a Regexp...
         | 
| 42 | 
            -
                DNS_HOST_REGEX | 
| 41 | 
            +
                DNS_HOST_REGEX = / [\p{L}\p{N}]+ (?: (?: \-{1,2} | \.) [\p{L}\p{N}]+ )*/x
         | 
| 43 42 |  | 
| 44 43 | 
             
                # The IPv4 and IPv6 were lifted from Resolv::IPv?::Regex and tweaked to not
         | 
| 45 44 | 
             
                # \A...\z anchor at the edges.
         | 
| @@ -85,46 +84,45 @@ module EmailAddress | |
| 85 84 |  | 
| 86 85 | 
             
                # host name -
         | 
| 87 86 | 
             
                #   * host type - :email for an email host, :mx for exchanger host
         | 
| 88 | 
            -
                def initialize(host_name, config={})
         | 
| 89 | 
            -
                  @original | 
| 87 | 
            +
                def initialize(host_name, config = {})
         | 
| 88 | 
            +
                  @original = host_name ||= ""
         | 
| 90 89 | 
             
                  config[:host_type] ||= :email
         | 
| 91 | 
            -
                  @config | 
| 92 | 
            -
                  @error | 
| 90 | 
            +
                  @config = config.is_a?(Hash) ? Config.new(config) : config
         | 
| 91 | 
            +
                  @error = @error_message = nil
         | 
| 93 92 | 
             
                  parse(host_name)
         | 
| 94 93 | 
             
                end
         | 
| 95 94 |  | 
| 96 95 | 
             
                # Returns the String representation of the host name (or IP)
         | 
| 97 96 | 
             
                def name
         | 
| 98 | 
            -
                  if  | 
| 99 | 
            -
                    "[#{ | 
| 100 | 
            -
                  elsif  | 
| 101 | 
            -
                    "[IPv6:#{ | 
| 97 | 
            +
                  if ipv4?
         | 
| 98 | 
            +
                    "[#{ip_address}]"
         | 
| 99 | 
            +
                  elsif ipv6?
         | 
| 100 | 
            +
                    "[IPv6:#{ip_address}]"
         | 
| 102 101 | 
             
                  elsif @config[:host_encoding] && @config[:host_encoding] == :unicode
         | 
| 103 | 
            -
                    ::SimpleIDN.to_unicode( | 
| 102 | 
            +
                    ::SimpleIDN.to_unicode(host_name)
         | 
| 104 103 | 
             
                  else
         | 
| 105 | 
            -
                     | 
| 104 | 
            +
                    dns_name
         | 
| 106 105 | 
             
                  end
         | 
| 107 106 | 
             
                end
         | 
| 108 | 
            -
                alias  | 
| 107 | 
            +
                alias to_s name
         | 
| 109 108 |  | 
| 110 109 | 
             
                # The canonical host name is the simplified, DNS host name
         | 
| 111 110 | 
             
                def canonical
         | 
| 112 | 
            -
                   | 
| 111 | 
            +
                  dns_name
         | 
| 113 112 | 
             
                end
         | 
| 114 113 |  | 
| 115 114 | 
             
                # Returns the munged version of the name, replacing everything after the
         | 
| 116 115 | 
             
                # initial two characters with "*****" or the configured "munge_string".
         | 
| 117 116 | 
             
                def munge
         | 
| 118 | 
            -
                   | 
| 117 | 
            +
                  host_name.sub(/\A(.{1,2}).*/) { |m| $1 + @config[:munge_string] }
         | 
| 119 118 | 
             
                end
         | 
| 120 119 |  | 
| 121 120 | 
             
                ############################################################################
         | 
| 122 121 | 
             
                # Parsing
         | 
| 123 122 | 
             
                ############################################################################
         | 
| 124 123 |  | 
| 125 | 
            -
             | 
| 126 124 | 
             
                def parse(host) # :nodoc:
         | 
| 127 | 
            -
                  host =  | 
| 125 | 
            +
                  host = parse_comment(host)
         | 
| 128 126 |  | 
| 129 127 | 
             
                  if host =~ /\A\[IPv6:(.+)\]/i
         | 
| 130 128 | 
             
                    self.ip_address = $1
         | 
| @@ -149,34 +147,34 @@ module EmailAddress | |
| 149 147 | 
             
                  name = fully_qualified_domain_name(name.downcase)
         | 
| 150 148 | 
             
                  @host_name = name
         | 
| 151 149 | 
             
                  if @config[:host_remove_spaces]
         | 
| 152 | 
            -
                    @host_name = @host_name. | 
| 150 | 
            +
                    @host_name = @host_name.delete(" ")
         | 
| 153 151 | 
             
                  end
         | 
| 154 | 
            -
                   | 
| 155 | 
            -
                     | 
| 152 | 
            +
                  @dns_name = if /[^[:ascii:]]/.match(host_name)
         | 
| 153 | 
            +
                    ::SimpleIDN.to_ascii(host_name)
         | 
| 156 154 | 
             
                  else
         | 
| 157 | 
            -
                     | 
| 155 | 
            +
                    host_name
         | 
| 158 156 | 
             
                  end
         | 
| 159 157 |  | 
| 160 158 | 
             
                  # Subdomain only (root@localhost)
         | 
| 161 | 
            -
                  if name.index( | 
| 159 | 
            +
                  if name.index(".").nil?
         | 
| 162 160 | 
             
                    self.subdomains = name
         | 
| 163 161 |  | 
| 164 162 | 
             
                  # Split sub.domain from .tld: *.com, *.xx.cc, *.cc
         | 
| 165 163 | 
             
                  elsif name =~ /\A(.+)\.(\w{3,10})\z/ ||
         | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 164 | 
            +
                      name =~ /\A(.+)\.(\w{1,3}\.\w\w)\z/ ||
         | 
| 165 | 
            +
                      name =~ /\A(.+)\.(\w\w)\z/
         | 
| 168 166 |  | 
| 169 167 | 
             
                    sub_and_domain, self.tld2 = [$1, $2] # sub+domain, com || co.uk
         | 
| 170 | 
            -
                    self.tld =  | 
| 168 | 
            +
                    self.tld = tld2.sub(/\A.+\./, "") # co.uk => uk
         | 
| 171 169 | 
             
                    if sub_and_domain =~ /\A(.+)\.(.+)\z/ # is subdomain? sub.example [.tld2]
         | 
| 172 | 
            -
                      self.subdomains | 
| 170 | 
            +
                      self.subdomains = $1
         | 
| 173 171 | 
             
                      self.registration_name = $2
         | 
| 174 172 | 
             
                    else
         | 
| 175 173 | 
             
                      self.registration_name = sub_and_domain
         | 
| 176 | 
            -
                      #self.domain_name = sub_and_domain + '.' + self.tld2
         | 
| 174 | 
            +
                      # self.domain_name = sub_and_domain + '.' + self.tld2
         | 
| 177 175 | 
             
                    end
         | 
| 178 | 
            -
                    self.domain_name =  | 
| 179 | 
            -
                     | 
| 176 | 
            +
                    self.domain_name = registration_name + "." + tld2
         | 
| 177 | 
            +
                    find_provider
         | 
| 180 178 | 
             
                  else # Bad format
         | 
| 181 179 | 
             
                    self.subdomains = self.tld = self.tld2 = ""
         | 
| 182 180 | 
             
                    self.domain_name = self.registration_name = name
         | 
| @@ -187,7 +185,7 @@ module EmailAddress | |
| 187 185 | 
             
                  dn = @config[:address_fqdn_domain]
         | 
| 188 186 | 
             
                  if !dn
         | 
| 189 187 | 
             
                    if (host_part.nil? || host_part <= " ") && @config[:host_local]
         | 
| 190 | 
            -
                       | 
| 188 | 
            +
                      "localhost"
         | 
| 191 189 | 
             
                    else
         | 
| 192 190 | 
             
                      host_part
         | 
| 193 191 | 
             
                    end
         | 
| @@ -205,39 +203,37 @@ module EmailAddress | |
| 205 203 | 
             
                  return false unless registration_name
         | 
| 206 204 | 
             
                  find_provider
         | 
| 207 205 | 
             
                  return false unless config[:host_match]
         | 
| 208 | 
            -
                  ! | 
| 206 | 
            +
                  !matches?(config[:host_match])
         | 
| 209 207 | 
             
                end
         | 
| 210 208 |  | 
| 211 209 | 
             
                def find_provider # :nodoc:
         | 
| 212 | 
            -
                  return  | 
| 210 | 
            +
                  return provider if provider
         | 
| 213 211 |  | 
| 214 | 
            -
                   | 
| 215 | 
            -
                    if config[:host_match] &&  | 
| 216 | 
            -
                      return  | 
| 212 | 
            +
                  Config.providers.each do |provider, config|
         | 
| 213 | 
            +
                    if config[:host_match] && matches?(config[:host_match])
         | 
| 214 | 
            +
                      return set_provider(provider, config)
         | 
| 217 215 | 
             
                    end
         | 
| 218 216 | 
             
                  end
         | 
| 219 217 |  | 
| 220 | 
            -
                  return  | 
| 218 | 
            +
                  return set_provider(:default) unless dns_enabled?
         | 
| 221 219 |  | 
| 222 | 
            -
                  provider  | 
| 223 | 
            -
                  if provider != :default
         | 
| 224 | 
            -
                    self.set_provider(provider,
         | 
| 225 | 
            -
                      EmailAddress::Config.provider(provider))
         | 
| 226 | 
            -
                  end
         | 
| 227 | 
            -
             | 
| 228 | 
            -
                  self.provider ||= self.set_provider(:default)
         | 
| 220 | 
            +
                  self.provider ||= set_provider(:default)
         | 
| 229 221 | 
             
                end
         | 
| 230 222 |  | 
| 231 | 
            -
                def set_provider(name, provider_config={}) # :nodoc:
         | 
| 232 | 
            -
                   | 
| 233 | 
            -
                   | 
| 223 | 
            +
                def set_provider(name, provider_config = {}) # :nodoc:
         | 
| 224 | 
            +
                  config.configure(provider_config)
         | 
| 225 | 
            +
                  @provider = name
         | 
| 234 226 | 
             
                end
         | 
| 235 227 |  | 
| 236 228 | 
             
                # Returns a hash of the parts of the host name after parsing.
         | 
| 237 229 | 
             
                def parts
         | 
| 238 | 
            -
                  { | 
| 239 | 
            -
             | 
| 240 | 
            -
             | 
| 230 | 
            +
                  {host_name: host_name, dns_name: dns_name, subdomain: subdomains,
         | 
| 231 | 
            +
                   registration_name: registration_name, domain_name: domain_name,
         | 
| 232 | 
            +
                   tld2: tld2, tld: tld, ip_address: ip_address,}
         | 
| 233 | 
            +
                end
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                def hosted_provider
         | 
| 236 | 
            +
                  Exchanger.cached(dns_name).provider
         | 
| 241 237 | 
             
                end
         | 
| 242 238 |  | 
| 243 239 | 
             
                ############################################################################
         | 
| @@ -246,19 +242,19 @@ module EmailAddress | |
| 246 242 |  | 
| 247 243 | 
             
                # Is this a fully-qualified domain name?
         | 
| 248 244 | 
             
                def fqdn?
         | 
| 249 | 
            -
                   | 
| 245 | 
            +
                  tld ? true : false
         | 
| 250 246 | 
             
                end
         | 
| 251 247 |  | 
| 252 248 | 
             
                def ip?
         | 
| 253 | 
            -
                   | 
| 249 | 
            +
                  ip_address.nil? ? false : true
         | 
| 254 250 | 
             
                end
         | 
| 255 251 |  | 
| 256 252 | 
             
                def ipv4?
         | 
| 257 | 
            -
                   | 
| 253 | 
            +
                  ip? && ip_address.include?(".")
         | 
| 258 254 | 
             
                end
         | 
| 259 255 |  | 
| 260 256 | 
             
                def ipv6?
         | 
| 261 | 
            -
                   | 
| 257 | 
            +
                  ip? && ip_address.include?(":")
         | 
| 262 258 | 
             
                end
         | 
| 263 259 |  | 
| 264 260 | 
             
                ############################################################################
         | 
| @@ -276,25 +272,25 @@ module EmailAddress | |
| 276 272 | 
             
                  rules = Array(rules)
         | 
| 277 273 | 
             
                  return false if rules.empty?
         | 
| 278 274 | 
             
                  rules.each do |rule|
         | 
| 279 | 
            -
                    return rule if rule ==  | 
| 275 | 
            +
                    return rule if rule == domain_name || rule == dns_name
         | 
| 280 276 | 
             
                    return rule if registration_name_matches?(rule)
         | 
| 281 277 | 
             
                    return rule if tld_matches?(rule)
         | 
| 282 278 | 
             
                    return rule if domain_matches?(rule)
         | 
| 283 279 | 
             
                    return rule if self.provider && provider_matches?(rule)
         | 
| 284 | 
            -
                    return rule if  | 
| 280 | 
            +
                    return rule if ip_matches?(rule)
         | 
| 285 281 | 
             
                  end
         | 
| 286 282 | 
             
                  false
         | 
| 287 283 | 
             
                end
         | 
| 288 284 |  | 
| 289 285 | 
             
                # Does "example." match any tld?
         | 
| 290 286 | 
             
                def registration_name_matches?(rule)
         | 
| 291 | 
            -
                  "#{ | 
| 287 | 
            +
                  rule == "#{registration_name}."
         | 
| 292 288 | 
             
                end
         | 
| 293 289 |  | 
| 294 290 | 
             
                # Does "sub.example.com" match ".com" and ".example.com" top level names?
         | 
| 295 291 | 
             
                # Matches TLD (uk) or TLD2 (co.uk)
         | 
| 296 292 | 
             
                def tld_matches?(rule)
         | 
| 297 | 
            -
                  rule.match(/\A\.(.+)\z/) && ($1 ==  | 
| 293 | 
            +
                  rule.match(/\A\.(.+)\z/) && ($1 == tld || $1 == tld2) ? true : false
         | 
| 298 294 | 
             
                end
         | 
| 299 295 |  | 
| 300 296 | 
             
                def provider_matches?(rule)
         | 
| @@ -305,20 +301,20 @@ module EmailAddress | |
| 305 301 | 
             
                # Requires optionally starts with a "@".
         | 
| 306 302 | 
             
                def domain_matches?(rule)
         | 
| 307 303 | 
             
                  rule = $1 if rule =~ /\A@(.+)/
         | 
| 308 | 
            -
                  return rule if File.fnmatch?(rule,  | 
| 309 | 
            -
                  return rule if File.fnmatch?(rule,  | 
| 304 | 
            +
                  return rule if domain_name && File.fnmatch?(rule, domain_name)
         | 
| 305 | 
            +
                  return rule if dns_name && File.fnmatch?(rule, dns_name)
         | 
| 310 306 | 
             
                  false
         | 
| 311 307 | 
             
                end
         | 
| 312 308 |  | 
| 313 309 | 
             
                # True if the host is an IP Address form, and that address matches
         | 
| 314 310 | 
             
                # the passed CIDR string ("10.9.8.0/24" or "2001:..../64")
         | 
| 315 311 | 
             
                def ip_matches?(cidr)
         | 
| 316 | 
            -
                  return false unless  | 
| 317 | 
            -
                  return cidr if !cidr.include?("/") && cidr ==  | 
| 318 | 
            -
                  if cidr.include?(":") &&  | 
| 319 | 
            -
                    return cidr if NetAddr::IPv6Net.parse(cidr).contains(NetAddr::IPv6.parse( | 
| 320 | 
            -
                  elsif cidr.include?(".") &&  | 
| 321 | 
            -
                    return cidr if NetAddr::IPv4Net.parse(cidr).contains(NetAddr::IPv4.parse( | 
| 312 | 
            +
                  return false unless ip_address
         | 
| 313 | 
            +
                  return cidr if !cidr.include?("/") && cidr == ip_address
         | 
| 314 | 
            +
                  if cidr.include?(":") && ip_address.include?(":")
         | 
| 315 | 
            +
                    return cidr if NetAddr::IPv6Net.parse(cidr).contains(NetAddr::IPv6.parse(ip_address))
         | 
| 316 | 
            +
                  elsif cidr.include?(".") && ip_address.include?(".")
         | 
| 317 | 
            +
                    return cidr if NetAddr::IPv4Net.parse(cidr).contains(NetAddr::IPv4.parse(ip_address))
         | 
| 322 318 | 
             
                  end
         | 
| 323 319 | 
             
                  false
         | 
| 324 320 | 
             
                end
         | 
| @@ -329,33 +325,36 @@ module EmailAddress | |
| 329 325 |  | 
| 330 326 | 
             
                # True if the :dns_lookup setting is enabled
         | 
| 331 327 | 
             
                def dns_enabled?
         | 
| 332 | 
            -
                  [: | 
| 328 | 
            +
                  return false if @config[:dns_lookup] == :off
         | 
| 329 | 
            +
                  return false if @config[:host_validation] == :syntax
         | 
| 330 | 
            +
                  true
         | 
| 333 331 | 
             
                end
         | 
| 334 332 |  | 
| 335 333 | 
             
                # Returns: [official_hostname, alias_hostnames, address_family, *address_list]
         | 
| 336 334 | 
             
                def dns_a_record
         | 
| 337 335 | 
             
                  @_dns_a_record = "0.0.0.0" if @config[:dns_lookup] == :off
         | 
| 338 | 
            -
                  @_dns_a_record ||= Socket.gethostbyname( | 
| 336 | 
            +
                  @_dns_a_record ||= Socket.gethostbyname(dns_name)
         | 
| 339 337 | 
             
                rescue SocketError # not found, but could also mean network not work
         | 
| 340 338 | 
             
                  @_dns_a_record ||= []
         | 
| 341 339 | 
             
                end
         | 
| 342 340 |  | 
| 343 | 
            -
                # Returns an array of  | 
| 341 | 
            +
                # Returns an array of Exchanger hosts configured in DNS.
         | 
| 344 342 | 
             
                # The array will be empty if none are configured.
         | 
| 345 343 | 
             
                def exchangers
         | 
| 346 | 
            -
                  #return nil if @config[:host_type] != :email || !self.dns_enabled?
         | 
| 347 | 
            -
                  @_exchangers ||=  | 
| 344 | 
            +
                  # return nil if @config[:host_type] != :email || !self.dns_enabled?
         | 
| 345 | 
            +
                  @_exchangers ||= Exchanger.cached(dns_name, @config)
         | 
| 348 346 | 
             
                end
         | 
| 349 347 |  | 
| 350 348 | 
             
                # Returns a DNS TXT Record
         | 
| 351 | 
            -
                def txt(alternate_host=nil)
         | 
| 349 | 
            +
                def txt(alternate_host = nil)
         | 
| 350 | 
            +
                  return nil unless dns_enabled?
         | 
| 352 351 | 
             
                  Resolv::DNS.open do |dns|
         | 
| 353 352 | 
             
                    dns.timeouts = @config[:dns_timeout] if @config[:dns_timeout]
         | 
| 354 353 | 
             
                    records = begin
         | 
| 355 | 
            -
                      dns.getresources(alternate_host ||  | 
| 356 | 
            -
             | 
| 357 | 
            -
             | 
| 358 | 
            -
             | 
| 354 | 
            +
                      dns.getresources(alternate_host || dns_name,
         | 
| 355 | 
            +
                        Resolv::DNS::Resource::IN::TXT)
         | 
| 356 | 
            +
                              rescue Resolv::ResolvTimeout
         | 
| 357 | 
            +
                                []
         | 
| 359 358 | 
             
                    end
         | 
| 360 359 |  | 
| 361 360 | 
             
                    records.empty? ? nil : records.map(&:data).join(" ")
         | 
| @@ -363,13 +362,13 @@ module EmailAddress | |
| 363 362 | 
             
                end
         | 
| 364 363 |  | 
| 365 364 | 
             
                # Parses TXT record pairs into a hash
         | 
| 366 | 
            -
                def txt_hash(alternate_host=nil)
         | 
| 365 | 
            +
                def txt_hash(alternate_host = nil)
         | 
| 367 366 | 
             
                  fields = {}
         | 
| 368 | 
            -
                  record =  | 
| 367 | 
            +
                  record = txt(alternate_host)
         | 
| 369 368 | 
             
                  return fields unless record
         | 
| 370 369 |  | 
| 371 370 | 
             
                  record.split(/\s*;\s*/).each do |pair|
         | 
| 372 | 
            -
                    (n,v) = pair.split(/\s*=\s*/)
         | 
| 371 | 
            +
                    (n, v) = pair.split(/\s*=\s*/)
         | 
| 373 372 | 
             
                    fields[n.to_sym] = v
         | 
| 374 373 | 
             
                  end
         | 
| 375 374 | 
             
                  fields
         | 
| @@ -378,7 +377,7 @@ module EmailAddress | |
| 378 377 | 
             
                # Returns a hash of the domain's DMARC (https://en.wikipedia.org/wiki/DMARC)
         | 
| 379 378 | 
             
                # settings.
         | 
| 380 379 | 
             
                def dmarc
         | 
| 381 | 
            -
                   | 
| 380 | 
            +
                  dns_name ? txt_hash("_dmarc." + dns_name) : {}
         | 
| 382 381 | 
             
                end
         | 
| 383 382 |  | 
| 384 383 | 
             
                ############################################################################
         | 
| @@ -386,13 +385,13 @@ module EmailAddress | |
| 386 385 | 
             
                ############################################################################
         | 
| 387 386 |  | 
| 388 387 | 
             
                # Returns true if the host name is valid according to the current configuration
         | 
| 389 | 
            -
                def valid?(rules={})
         | 
| 388 | 
            +
                def valid?(rules = {})
         | 
| 390 389 | 
             
                  host_validation = rules[:host_validation] || @config[:host_validation] || :mx
         | 
| 391 | 
            -
                  dns_lookup | 
| 390 | 
            +
                  dns_lookup = rules[:dns_lookup] || host_validation
         | 
| 392 391 | 
             
                  self.error_message = nil
         | 
| 393 | 
            -
                  if  | 
| 392 | 
            +
                  if ip_address
         | 
| 394 393 | 
             
                    valid_ip?
         | 
| 395 | 
            -
                  elsif ! | 
| 394 | 
            +
                  elsif !valid_format?
         | 
| 396 395 | 
             
                    false
         | 
| 397 396 | 
             
                  elsif dns_lookup == :connect
         | 
| 398 397 | 
             
                    valid_mx? && connect
         | 
| @@ -407,8 +406,9 @@ module EmailAddress | |
| 407 406 |  | 
| 408 407 | 
             
                # True if the host name has a DNS A Record
         | 
| 409 408 | 
             
                def valid_dns?
         | 
| 409 | 
            +
                  return true unless dns_enabled?
         | 
| 410 410 | 
             
                  bool = dns_a_record.size > 0 || set_error(:domain_unknown)
         | 
| 411 | 
            -
                  if  | 
| 411 | 
            +
                  if localhost? && !@config[:host_local]
         | 
| 412 412 | 
             
                    bool = set_error(:domain_no_localhost)
         | 
| 413 413 | 
             
                  end
         | 
| 414 414 | 
             
                  bool
         | 
| @@ -416,10 +416,11 @@ module EmailAddress | |
| 416 416 |  | 
| 417 417 | 
             
                # True if the host name has valid MX servers configured in DNS
         | 
| 418 418 | 
             
                def valid_mx?
         | 
| 419 | 
            -
                   | 
| 419 | 
            +
                  return true unless dns_enabled?
         | 
| 420 | 
            +
                  if exchangers.nil?
         | 
| 420 421 | 
             
                    set_error(:domain_unknown)
         | 
| 421 | 
            -
                  elsif  | 
| 422 | 
            -
                    if  | 
| 422 | 
            +
                  elsif exchangers.mx_ips.size > 0
         | 
| 423 | 
            +
                    if localhost? && !@config[:host_local]
         | 
| 423 424 | 
             
                      set_error(:domain_no_localhost)
         | 
| 424 425 | 
             
                    else
         | 
| 425 426 | 
             
                      true
         | 
| @@ -433,9 +434,9 @@ module EmailAddress | |
| 433 434 |  | 
| 434 435 | 
             
                # True if the host_name passes Regular Expression match and size limits.
         | 
| 435 436 | 
             
                def valid_format?
         | 
| 436 | 
            -
                  if  | 
| 437 | 
            +
                  if host_name =~ CANONICAL_HOST_REGEX && to_s.size <= MAX_HOST_LENGTH
         | 
| 437 438 | 
             
                    return true if localhost?
         | 
| 438 | 
            -
                    return true if  | 
| 439 | 
            +
                    return true if host_name.include?(".") # require FQDN
         | 
| 439 440 | 
             
                  end
         | 
| 440 441 | 
             
                  set_error(:domain_invalid)
         | 
| 441 442 | 
             
                end
         | 
| @@ -444,12 +445,12 @@ module EmailAddress | |
| 444 445 | 
             
                # is a potentially valid IP address. It does not check if the address
         | 
| 445 446 | 
             
                # is reachable.
         | 
| 446 447 | 
             
                def valid_ip?
         | 
| 447 | 
            -
                  if  | 
| 448 | 
            +
                  if !@config[:host_allow_ip]
         | 
| 448 449 | 
             
                    bool = set_error(:ip_address_forbidden)
         | 
| 449 | 
            -
                  elsif  | 
| 450 | 
            -
                    bool =  | 
| 451 | 
            -
                  elsif  | 
| 452 | 
            -
                    bool =  | 
| 450 | 
            +
                  elsif ip_address.include?(":")
         | 
| 451 | 
            +
                    bool = ip_address.match(Resolv::IPv6::Regex) ? true : set_error(:ipv6_address_invalid)
         | 
| 452 | 
            +
                  elsif ip_address.include?(".")
         | 
| 453 | 
            +
                    bool = ip_address.match(Resolv::IPv4::Regex) ? true : set_error(:ipv4_address_invalid)
         | 
| 453 454 | 
             
                  end
         | 
| 454 455 | 
             
                  if bool && (localhost? && !@config[:host_local])
         | 
| 455 456 | 
             
                    bool = set_error(:ip_address_no_localhost)
         | 
| @@ -458,20 +459,20 @@ module EmailAddress | |
| 458 459 | 
             
                end
         | 
| 459 460 |  | 
| 460 461 | 
             
                def localhost?
         | 
| 461 | 
            -
                  if  | 
| 462 | 
            +
                  if ip_address
         | 
| 462 463 | 
             
                    rel =
         | 
| 463 | 
            -
                      if  | 
| 464 | 
            -
                        NetAddr::IPv6Net.parse(""+"::1").rel(
         | 
| 465 | 
            -
                          NetAddr::IPv6Net.parse( | 
| 464 | 
            +
                      if ip_address.include?(":")
         | 
| 465 | 
            +
                        NetAddr::IPv6Net.parse("" + "::1").rel(
         | 
| 466 | 
            +
                          NetAddr::IPv6Net.parse(ip_address)
         | 
| 466 467 | 
             
                        )
         | 
| 467 468 | 
             
                      else
         | 
| 468 | 
            -
                        NetAddr::IPv4Net.parse(""+"127.0.0.0/8").rel(
         | 
| 469 | 
            -
                          NetAddr::IPv4Net.parse( | 
| 469 | 
            +
                        NetAddr::IPv4Net.parse("" + "127.0.0.0/8").rel(
         | 
| 470 | 
            +
                          NetAddr::IPv4Net.parse(ip_address)
         | 
| 470 471 | 
             
                        )
         | 
| 471 472 | 
             
                      end
         | 
| 472 473 | 
             
                    !rel.nil? && rel >= 0
         | 
| 473 474 | 
             
                  else
         | 
| 474 | 
            -
                     | 
| 475 | 
            +
                    host_name == "localhost"
         | 
| 475 476 | 
             
                  end
         | 
| 476 477 | 
             
                end
         | 
| 477 478 |  | 
| @@ -479,33 +480,30 @@ module EmailAddress | |
| 479 480 | 
             
                # as an email address check, but is provided to assist in problem resolution.
         | 
| 480 481 | 
             
                # If you abuse this, you *could* be blocked by the ESP.
         | 
| 481 482 | 
             
                def connect
         | 
| 482 | 
            -
                   | 
| 483 | 
            -
             | 
| 484 | 
            -
             | 
| 483 | 
            +
                  smtp = Net::SMTP.new(host_name || ip_address)
         | 
| 484 | 
            +
                  smtp.start(@config[:helo_name] || "localhost")
         | 
| 485 | 
            +
                  smtp.finish
         | 
| 486 | 
            +
                  true
         | 
| 487 | 
            +
                rescue Net::SMTPFatalError => e
         | 
| 488 | 
            +
                  set_error(:server_not_available, e.to_s)
         | 
| 489 | 
            +
                rescue SocketError => e
         | 
| 490 | 
            +
                  set_error(:server_not_available, e.to_s)
         | 
| 491 | 
            +
                ensure
         | 
| 492 | 
            +
                  if smtp&.started?
         | 
| 485 493 | 
             
                    smtp.finish
         | 
| 486 | 
            -
                    true
         | 
| 487 | 
            -
                  rescue Net::SMTPFatalError => e
         | 
| 488 | 
            -
                    set_error(:server_not_available, e.to_s)
         | 
| 489 | 
            -
                  rescue SocketError => e
         | 
| 490 | 
            -
                    set_error(:server_not_available, e.to_s)
         | 
| 491 | 
            -
                  ensure
         | 
| 492 | 
            -
                    if smtp && smtp.started?
         | 
| 493 | 
            -
                      smtp.finish
         | 
| 494 | 
            -
                    end
         | 
| 495 494 | 
             
                  end
         | 
| 496 495 | 
             
                end
         | 
| 497 496 |  | 
| 498 | 
            -
                def set_error(err, reason=nil)
         | 
| 499 | 
            -
                  @error | 
| 500 | 
            -
                  @reason | 
| 501 | 
            -
                  @error_message =  | 
| 497 | 
            +
                def set_error(err, reason = nil)
         | 
| 498 | 
            +
                  @error = err
         | 
| 499 | 
            +
                  @reason = reason
         | 
| 500 | 
            +
                  @error_message = Config.error_message(err)
         | 
| 502 501 | 
             
                  false
         | 
| 503 502 | 
             
                end
         | 
| 504 503 |  | 
| 505 504 | 
             
                # The inverse of valid? -- Returns nil (falsey) if valid, otherwise error message
         | 
| 506 505 | 
             
                def error
         | 
| 507 | 
            -
                   | 
| 506 | 
            +
                  valid? ? nil : @error_message
         | 
| 508 507 | 
             
                end
         | 
| 509 | 
            -
             | 
| 510 508 | 
             
              end
         | 
| 511 509 | 
             
            end
         |