Class: EmailAddress::Host
- Inherits:
-
Object
- Object
- EmailAddress::Host
- Defined in:
- lib/email_address/host.rb
Overview
The EmailAddress Host is found on the right-hand side of the “@” symbol. It can be:
-
Host name (domain name with optional subdomain)
-
International Domain Name, in Unicode (Display) or Punycode (DNS) format
-
IP Address format, either IPv4 or IPv6, enclosed in square brackets. This is not Conventionally supported, but is part of the specification.
-
It can contain an optional comment, enclosed in parenthesis, either at beginning or ending of the host name. This is not well defined, so it not supported here, expect to parse it off, if found.
For matching and query capabilities, the host name is parsed into these parts (with example data for “subdomain.example.co.uk”):
-
host_name: “subdomain.example.co.uk”
-
dns_name: punycode(“subdomain.example.co.uk”)
-
subdomain: “subdomain”
-
registration_name: “example”
-
domain_name: “example.co.uk”
-
tld: “uk”
-
tld2: “co.uk” (the 1 or 2 term TLD we could guess)
-
ip_address: nil or “ipaddress” used in [ipaddress] syntax
The provider (Email Service Provider or ESP) is looked up according to the provider configuration rules, setting the config attribute to values of that provider.
Constant Summary collapse
- MAX_HOST_LENGTH =
255- DNS_HOST_REGEX =
Sometimes, you just need a Regexp…
/ [\p{L}\p{N}]+ (?: (?: \-{1,2} | \.) [\p{L}\p{N}]+ )*/x
- IPv6_HOST_REGEX =
The IPv4 and IPv6 were lifted from Resolv::IPv?::Regex and tweaked to not A…z anchor at the edges.
/\[IPv6: (?: (?:(?x-mi: (?:[0-9A-Fa-f]{1,4}:){7} [0-9A-Fa-f]{1,4} )) | (?:(?x-mi: (?: (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) :: (?: (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) )) | (?:(?x-mi: (?: (?:[0-9A-Fa-f]{1,4}:){6,6}) (?: \d+)\.(?: \d+)\.(?: \d+)\.(?: \d+) )) | (?:(?x-mi: (?: (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) :: (?: (?:[0-9A-Fa-f]{1,4}:)*) (?: \d+)\.(?: \d+)\.(?: \d+)\.(?: \d+) )))\]/ix
- IPv4_HOST_REGEX =
/\[((?x-mi:0 |1(?:[0-9][0-9]?)? |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? |[3-9][0-9]?))\.((?x-mi:0 |1(?:[0-9][0-9]?)? |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? |[3-9][0-9]?))\.((?x-mi:0 |1(?:[0-9][0-9]?)? |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? |[3-9][0-9]?))\.((?x-mi:0 |1(?:[0-9][0-9]?)? |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? |[3-9][0-9]?))\]/x
- CANONICAL_HOST_REGEX =
Matches conventional host name and punycode: domain.tld, x–punycode.tld
/\A #{DNS_HOST_REGEX} \z/x
- STANDARD_HOST_REGEX =
Matches Host forms: DNS name, IPv4, or IPv6 formats
/\A (?: #{DNS_HOST_REGEX} | #{IPv4_HOST_REGEX} | #{IPv6_HOST_REGEX}) \z/ix
Instance Attribute Summary collapse
-
#comment ⇒ Object
Returns the value of attribute comment.
-
#config ⇒ Object
Returns the value of attribute config.
-
#dns_name ⇒ Object
Returns the value of attribute dns_name.
-
#domain_name ⇒ Object
Returns the value of attribute domain_name.
-
#host_name ⇒ Object
Returns the value of attribute host_name.
-
#ip_address ⇒ Object
Returns the value of attribute ip_address.
-
#provider ⇒ Object
Returns the value of attribute provider.
-
#registration_name ⇒ Object
Returns the value of attribute registration_name.
-
#subdomains ⇒ Object
Returns the value of attribute subdomains.
-
#tld ⇒ Object
Returns the value of attribute tld.
-
#tld2 ⇒ Object
Returns the value of attribute tld2.
Instance Method Summary collapse
-
#canonical ⇒ Object
The canonical host name is the simplified, DNS host name.
-
#dmarc ⇒ Object
Returns a hash of the domain’s DMARC (en.wikipedia.org/wiki/DMARC) settings.
-
#dns_a_record ⇒ Object
Returns: [official_hostname, alias_hostnames, address_family, *address_list].
-
#dns_enabled? ⇒ Boolean
True if the :dns_lookup setting is enabled.
-
#domain_matches?(rule) ⇒ Boolean
Does domain == rule or glob matches? (also tests the DNS (punycode) name) Requires optionally starts with a “@”.
-
#exchangers ⇒ Object
Returns an array of EmailAddress::Exchanger hosts configured in DNS.
-
#find_provider ⇒ Object
:nodoc:.
-
#fqdn? ⇒ Boolean
Is this a fully-qualified domain name?.
-
#has_dns_a_record? ⇒ Boolean
True if the host name has a DNS A Record.
-
#initialize(host_name, config = {}) ⇒ Host
constructor
host name - * host type - :email for an email host, :mx for exchanger host.
- #ip? ⇒ Boolean
-
#ip_matches?(cidr) ⇒ Boolean
True if the host is an IP Address form, and that address matches the passed CIDR string (“10.9.8.0/24” or “2001:.…/64”).
- #ipv4? ⇒ Boolean
- #ipv6? ⇒ Boolean
-
#matches?(rules) ⇒ Boolean
Takes a email address string, returns true if it matches a rule Rules of the follow formats are evaluated: * “example.” => registration name * “.com” => top-level domain name * “google” => email service provider designation * “@goog*.com” => Glob match * IPv4 or IPv6 or CIDR Address.
-
#munge ⇒ Object
Returns the munged version of the name, replacing everything after the initial two characters with “*****” or the configured “munge_string”.
-
#name ⇒ Object
(also: #to_s)
Returns the String representation of the host name (or IP).
-
#parse(host) ⇒ Object
:nodoc:.
-
#parse_comment(host) ⇒ Object
:nodoc:.
-
#parts ⇒ Object
Returns a hash of the parts of the host name after parsing.
- #provider_matches?(rule) ⇒ Boolean
-
#registration_name_matches?(rule) ⇒ Boolean
Does “example.” match any tld?.
-
#set_provider(name, provider_config = {}) ⇒ Object
:nodoc:.
-
#tld_matches?(rule) ⇒ Boolean
Does “sub.example.com” match “.com” and “.example.com” top level names? Matches TLD (uk) or TLD2 (co.uk).
-
#txt(alternate_host = nil) ⇒ Object
Returns a DNS TXT Record.
-
#txt_hash(alternate_host = nil) ⇒ Object
Parses TXT record pairs into a hash.
-
#valid?(rule = @config[:dns_lookup]||:mx) ⇒ Boolean
Returns true if the host name is valid according to the current configuration.
-
#valid_ip? ⇒ Boolean
Returns true if the IP address given in that form of the host name is a potentially valid IP address.
Constructor Details
#initialize(host_name, config = {}) ⇒ Host
host name -
* host type - :email for an email host, :mx for exchanger host
85 86 87 88 89 90 |
# File 'lib/email_address/host.rb', line 85 def initialize(host_name, config={}) @original = host_name ||= '' config[:host_type] ||= :email @config = config parse(host_name) end |
Instance Attribute Details
#comment ⇒ Object
Returns the value of attribute comment.
33 34 35 |
# File 'lib/email_address/host.rb', line 33 def comment @comment end |
#config ⇒ Object
Returns the value of attribute config.
33 34 35 |
# File 'lib/email_address/host.rb', line 33 def config @config end |
#dns_name ⇒ Object
Returns the value of attribute dns_name.
33 34 35 |
# File 'lib/email_address/host.rb', line 33 def dns_name @dns_name end |
#domain_name ⇒ Object
Returns the value of attribute domain_name.
33 34 35 |
# File 'lib/email_address/host.rb', line 33 def domain_name @domain_name end |
#host_name ⇒ Object
Returns the value of attribute host_name.
33 34 35 |
# File 'lib/email_address/host.rb', line 33 def host_name @host_name end |
#ip_address ⇒ Object
Returns the value of attribute ip_address.
33 34 35 |
# File 'lib/email_address/host.rb', line 33 def ip_address @ip_address end |
#provider ⇒ Object
Returns the value of attribute provider.
33 34 35 |
# File 'lib/email_address/host.rb', line 33 def provider @provider end |
#registration_name ⇒ Object
Returns the value of attribute registration_name.
33 34 35 |
# File 'lib/email_address/host.rb', line 33 def registration_name @registration_name end |
#subdomains ⇒ Object
Returns the value of attribute subdomains.
33 34 35 |
# File 'lib/email_address/host.rb', line 33 def subdomains @subdomains end |
#tld ⇒ Object
Returns the value of attribute tld.
33 34 35 |
# File 'lib/email_address/host.rb', line 33 def tld @tld end |
#tld2 ⇒ Object
Returns the value of attribute tld2.
33 34 35 |
# File 'lib/email_address/host.rb', line 33 def tld2 @tld2 end |
Instance Method Details
#canonical ⇒ Object
The canonical host name is the simplified, DNS host name
107 108 109 |
# File 'lib/email_address/host.rb', line 107 def canonical self.dns_name end |
#dmarc ⇒ Object
Returns a hash of the domain’s DMARC (en.wikipedia.org/wiki/DMARC) settings.
340 341 342 |
# File 'lib/email_address/host.rb', line 340 def dmarc self.dns_name ? self.txt_hash("_dmarc." + self.dns_name) : {} end |
#dns_a_record ⇒ Object
Returns: [official_hostname, alias_hostnames, address_family, *address_list]
303 304 305 306 307 |
# File 'lib/email_address/host.rb', line 303 def dns_a_record @_dns_a_record ||= Socket.gethostbyname(self.dns_name) rescue SocketError # not found, but could also mean network not work @_dns_a_record ||= [] end |
#dns_enabled? ⇒ Boolean
True if the :dns_lookup setting is enabled
293 294 295 |
# File 'lib/email_address/host.rb', line 293 def dns_enabled? EmailAddress::Config.setting(:dns_lookup) end |
#domain_matches?(rule) ⇒ Boolean
Does domain == rule or glob matches? (also tests the DNS (punycode) name) Requires optionally starts with a “@”.
266 267 268 269 270 271 |
# File 'lib/email_address/host.rb', line 266 def domain_matches?(rule) rule = $1 if rule =~ /\A@(.+)/ return rule if File.fnmatch?(rule, self.domain_name) return rule if File.fnmatch?(rule, self.dns_name) false end |
#exchangers ⇒ Object
Returns an array of EmailAddress::Exchanger hosts configured in DNS. The array will be empty if none are configured.
311 312 313 314 |
# File 'lib/email_address/host.rb', line 311 def exchangers return nil if @config[:host_type] != :email || !self.dns_enabled? @_exchangers ||= EmailAddress::Exchanger.cached(self.dns_name) end |
#find_provider ⇒ Object
:nodoc:
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/email_address/host.rb', line 171 def find_provider # :nodoc: return self.provider if self.provider EmailAddress::Config.providers.each do |provider, config| if config[:host_match] && self.matches?(config[:host_match]) return self.set_provider(provider, config) end end return self.set_provider(:default) unless self.dns_enabled? provider = self.exchangers.provider if provider != :default self.set_provider(provider, EmailAddress::Config.provider(self.provider)) end self.provider ||= self.set_provider(:default) end |
#fqdn? ⇒ Boolean
Is this a fully-qualified domain name?
208 209 210 |
# File 'lib/email_address/host.rb', line 208 def fqdn? self.tld ? true : false end |
#has_dns_a_record? ⇒ Boolean
True if the host name has a DNS A Record
298 299 300 |
# File 'lib/email_address/host.rb', line 298 def has_dns_a_record? dns_a_record.size > 0 ? true : false end |
#ip? ⇒ Boolean
212 213 214 |
# File 'lib/email_address/host.rb', line 212 def ip? self.ip_address.nil? ? false : true end |
#ip_matches?(cidr) ⇒ Boolean
True if the host is an IP Address form, and that address matches the passed CIDR string (“10.9.8.0/24” or “2001:.…/64”)
275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/email_address/host.rb', line 275 def ip_matches?(cidr) return false unless self.ip_address return cidr if !cidr.include?("/") && cidr == self.ip_address c = NetAddr::CIDR.create(cidr) if cidr.include?(":") && self.ip_address.include?(":") return cidr if c.matches?(self.ip_address) elsif cidr.include?(".") && self.ip_address.include?(".") return cidr if c.matches?(self.ip_address) end false end |
#ipv4? ⇒ Boolean
216 217 218 |
# File 'lib/email_address/host.rb', line 216 def ipv4? self.ip? && self.ip_address.include?(".") end |
#ipv6? ⇒ Boolean
220 221 222 |
# File 'lib/email_address/host.rb', line 220 def ipv6? self.ip? && self.ip_address.include?(":") end |
#matches?(rules) ⇒ Boolean
Takes a email address string, returns true if it matches a rule Rules of the follow formats are evaluated:
-
“example.” => registration name
-
“.com” => top-level domain name
-
“google” => email service provider designation
-
“@goog*.com” => Glob match
-
IPv4 or IPv6 or CIDR Address
235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/email_address/host.rb', line 235 def matches?(rules) rules = Array(rules) return false if rules.empty? rules.each do |rule| return rule if rule == self.domain_name || rule == self.dns_name return rule if registration_name_matches?(rule) return rule if tld_matches?(rule) return rule if domain_matches?(rule) return rule if self.provider && provider_matches?(rule) return rule if self.ip_matches?(rule) end false end |
#munge ⇒ Object
Returns the munged version of the name, replacing everything after the initial two characters with “*****” or the configured “munge_string”.
113 114 115 |
# File 'lib/email_address/host.rb', line 113 def munge self.host_name.sub(/\A(.{1,2}).*/) { |m| $1 + @config[:munge_string] } end |
#name ⇒ Object Also known as: to_s
Returns the String representation of the host name (or IP)
93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/email_address/host.rb', line 93 def name if self.ipv4? "[#{self.ip_address}]" elsif self.ipv6? "[IPv6:#{self.ip_address}]" elsif @config[:host_encoding] && @config[:host_encoding] == :unicode ::SimpleIDN.to_unicode(self.host_name) else self.dns_name end end |
#parse(host) ⇒ Object
:nodoc:
122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/email_address/host.rb', line 122 def parse(host) # :nodoc: host = self.parse_comment(host) if host =~ /\A\[IPv6:(.+)\]/i self.ip_address = $1 elsif host =~ /\A\[(\d{1,3}(\.\d{1,3}){3})\]/ # IPv4 self.ip_address = $1 else self.host_name = host end end |
#parse_comment(host) ⇒ Object
:nodoc:
134 135 136 137 138 139 140 141 142 |
# File 'lib/email_address/host.rb', line 134 def parse_comment(host) # :nodoc: if host =~ /\A\((.+?)\)(.+)/ # (comment)domain.tld self.comment, host = $1, $2 end if host =~ /\A(.+)\((.+?)\)\z/ # domain.tld(comment) host, self.comment = $1, $2 end host end |
#parts ⇒ Object
Returns a hash of the parts of the host name after parsing.
197 198 199 200 201 |
# File 'lib/email_address/host.rb', line 197 def parts { host_name:self.host_name, dns_name:self.dns_name, subdomain:self.subdomains, registration_name:self.registration_name, domain_name:self.domain_name, tld2:self.tld2, tld:self.tld, ip_address:self.ip_address } end |
#provider_matches?(rule) ⇒ Boolean
260 261 262 |
# File 'lib/email_address/host.rb', line 260 def provider_matches?(rule) rule.to_s =~ /\A[\w\-]*\z/ && self.provider && self.provider == rule.to_sym end |
#registration_name_matches?(rule) ⇒ Boolean
Does “example.” match any tld?
250 251 252 |
# File 'lib/email_address/host.rb', line 250 def registration_name_matches?(rule) self.registration_name + '.' == rule ? true : false end |
#set_provider(name, provider_config = {}) ⇒ Object
:nodoc:
191 192 193 194 |
# File 'lib/email_address/host.rb', line 191 def set_provider(name, provider_config={}) # :nodoc: self.config = EmailAddress::Config.all_settings(provider_config, @config) self.provider = name end |
#tld_matches?(rule) ⇒ Boolean
Does “sub.example.com” match “.com” and “.example.com” top level names? Matches TLD (uk) or TLD2 (co.uk)
256 257 258 |
# File 'lib/email_address/host.rb', line 256 def tld_matches?(rule) rule.match(/\A\.(.+)\z/) && ($1 == self.tld || $1 == self.tld2) ? true : false end |
#txt(alternate_host = nil) ⇒ Object
Returns a DNS TXT Record
317 318 319 320 321 322 323 |
# File 'lib/email_address/host.rb', line 317 def txt(alternate_host=nil) Resolv::DNS.open do |dns| records = dns.getresources(alternate_host || self.dns_name, Resolv::DNS::Resource::IN::TXT) records.empty? ? nil : records.map(&:data).join(" ") end end |
#txt_hash(alternate_host = nil) ⇒ Object
Parses TXT record pairs into a hash
326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/email_address/host.rb', line 326 def txt_hash(alternate_host=nil) fields = {} record = self.txt(alternate_host) return fields unless record record.split(/\s*;\s*/).each do |pair| (n,v) = pair.split(/\s*=\s*/) fields[n.to_sym] = v end fields end |
#valid?(rule = @config[:dns_lookup]||:mx) ⇒ Boolean
Returns true if the host name is valid according to the current configuration
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'lib/email_address/host.rb', line 349 def valid?(rule=@config[:dns_lookup]||:mx) if self.provider != :default # well known true elsif self.ip_address @config[:host_allow_ip] && self.valid_ip? elsif rule == :mx self.exchangers.mx_ips.size > 0 elsif rule == :a self.has_dns_a_record? elsif rule == :off self.to_s.size <= MAX_HOST_LENGTH else false end end |
#valid_ip? ⇒ Boolean
Returns true if the IP address given in that form of the host name is a potentially valid IP address. It does not check if the address is reachable.
368 369 370 371 372 373 374 375 376 |
# File 'lib/email_address/host.rb', line 368 def valid_ip? if self.ip_address.nil? false elsif self.ip_address.include?(":") self.ip_address =~ Resolv::IPv6::Regex elsif self.ip_address.include?(".") self.ip_address =~ Resolv::IPv4::Regex end end |