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.
-
#hosted_service? ⇒ Boolean
True if host is hosted at the provider, not a public provider host name.
-
#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
86 87 88 89 90 91 |
# File 'lib/email_address/host.rb', line 86 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.
34 35 36 |
# File 'lib/email_address/host.rb', line 34 def comment @comment end |
#config ⇒ Object
Returns the value of attribute config.
34 35 36 |
# File 'lib/email_address/host.rb', line 34 def config @config end |
#dns_name ⇒ Object
Returns the value of attribute dns_name.
34 35 36 |
# File 'lib/email_address/host.rb', line 34 def dns_name @dns_name end |
#domain_name ⇒ Object
Returns the value of attribute domain_name.
34 35 36 |
# File 'lib/email_address/host.rb', line 34 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.
34 35 36 |
# File 'lib/email_address/host.rb', line 34 def ip_address @ip_address end |
#provider ⇒ Object
Returns the value of attribute provider.
34 35 36 |
# File 'lib/email_address/host.rb', line 34 def provider @provider end |
#registration_name ⇒ Object
Returns the value of attribute registration_name.
34 35 36 |
# File 'lib/email_address/host.rb', line 34 def registration_name @registration_name end |
#subdomains ⇒ Object
Returns the value of attribute subdomains.
34 35 36 |
# File 'lib/email_address/host.rb', line 34 def subdomains @subdomains end |
#tld ⇒ Object
Returns the value of attribute tld.
34 35 36 |
# File 'lib/email_address/host.rb', line 34 def tld @tld end |
#tld2 ⇒ Object
Returns the value of attribute tld2.
34 35 36 |
# File 'lib/email_address/host.rb', line 34 def tld2 @tld2 end |
Instance Method Details
#canonical ⇒ Object
The canonical host name is the simplified, DNS host name
108 109 110 |
# File 'lib/email_address/host.rb', line 108 def canonical self.dns_name end |
#dmarc ⇒ Object
Returns a hash of the domain’s DMARC (en.wikipedia.org/wiki/DMARC) settings.
349 350 351 |
# File 'lib/email_address/host.rb', line 349 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]
312 313 314 315 316 |
# File 'lib/email_address/host.rb', line 312 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
302 303 304 |
# File 'lib/email_address/host.rb', line 302 def dns_enabled? EmailAddress::Config.setting(:dns_lookup).equal?(:off) ? false : true end |
#domain_matches?(rule) ⇒ Boolean
Does domain == rule or glob matches? (also tests the DNS (punycode) name) Requires optionally starts with a “@”.
275 276 277 278 279 280 |
# File 'lib/email_address/host.rb', line 275 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.
320 321 322 323 |
# File 'lib/email_address/host.rb', line 320 def exchangers return nil if @config[:host_type] != :email || !self.dns_enabled? @_exchangers ||= EmailAddress::Exchanger.cached(self.dns_name) end |
#find_provider ⇒ Object
:nodoc:
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/email_address/host.rb', line 180 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(provider)) end self.provider ||= self.set_provider(:default) end |
#fqdn? ⇒ Boolean
Is this a fully-qualified domain name?
217 218 219 |
# File 'lib/email_address/host.rb', line 217 def fqdn? self.tld ? true : false end |
#has_dns_a_record? ⇒ Boolean
True if the host name has a DNS A Record
307 308 309 |
# File 'lib/email_address/host.rb', line 307 def has_dns_a_record? dns_a_record.size > 0 ? true : false end |
#hosted_service? ⇒ Boolean
True if host is hosted at the provider, not a public provider host name
173 174 175 176 177 178 |
# File 'lib/email_address/host.rb', line 173 def hosted_service? return false unless registration_name find_provider return false unless config[:host_match] ! matches?(config[:host_match]) end |
#ip? ⇒ Boolean
221 222 223 |
# File 'lib/email_address/host.rb', line 221 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”)
284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/email_address/host.rb', line 284 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
225 226 227 |
# File 'lib/email_address/host.rb', line 225 def ipv4? self.ip? && self.ip_address.include?(".") end |
#ipv6? ⇒ Boolean
229 230 231 |
# File 'lib/email_address/host.rb', line 229 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
244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/email_address/host.rb', line 244 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”.
114 115 116 |
# File 'lib/email_address/host.rb', line 114 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)
94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/email_address/host.rb', line 94 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:
123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/email_address/host.rb', line 123 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:
135 136 137 138 139 140 141 142 143 |
# File 'lib/email_address/host.rb', line 135 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.
206 207 208 209 210 |
# File 'lib/email_address/host.rb', line 206 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
269 270 271 |
# File 'lib/email_address/host.rb', line 269 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?
259 260 261 |
# File 'lib/email_address/host.rb', line 259 def registration_name_matches?(rule) self.registration_name + '.' == rule ? true : false end |
#set_provider(name, provider_config = {}) ⇒ Object
:nodoc:
200 201 202 203 |
# File 'lib/email_address/host.rb', line 200 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)
265 266 267 |
# File 'lib/email_address/host.rb', line 265 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
326 327 328 329 330 331 332 |
# File 'lib/email_address/host.rb', line 326 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
335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/email_address/host.rb', line 335 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
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/email_address/host.rb', line 358 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.
377 378 379 380 381 382 383 384 385 |
# File 'lib/email_address/host.rb', line 377 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 |