truemail 2.2.1 → 2.3.2

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.
data/bin/console CHANGED
@@ -3,14 +3,7 @@
3
3
  # frozen_string_literal: true
4
4
 
5
5
  require 'bundler/setup'
6
- require 'truemail'
6
+ require 'pry'
7
+ require_relative '../lib/truemail'
7
8
 
8
- # You can add fixtures and/or initialization code here to make experimenting
9
- # with your gem easier. You can also use a different console, if you like.
10
-
11
- # (If you use this, don't forget to add pry to your Gemfile!)
12
- # require "pry"
13
- # Pry.start
14
-
15
- require 'irb'
16
- IRB.start(__FILE__)
9
+ Pry.start
data/lib/truemail.rb CHANGED
@@ -46,7 +46,7 @@ module Truemail
46
46
  end
47
47
 
48
48
  def check_argument_type(argument)
49
- raise_unless(argument.is_a?(String), Truemail::INVALID_TYPE, Truemail::TypeError)
49
+ raise_unless(argument.is_a?(::String), Truemail::INVALID_TYPE, Truemail::TypeError)
50
50
  end
51
51
 
52
52
  def determine_configuration(custom_configuration)
@@ -5,7 +5,6 @@ module Truemail
5
5
  class Base < Truemail::Worker
6
6
  require 'net/http'
7
7
  require 'ipaddr'
8
- require 'resolv'
9
8
 
10
9
  private
11
10
 
@@ -14,7 +14,7 @@ module Truemail
14
14
 
15
15
  def a_record
16
16
  Truemail::Wrapper.call(configuration: configuration) do
17
- Resolv::DNS.new.getaddress(verifier_domain).to_s
17
+ Truemail::Dns::Resolver.a_record(verifier_domain, configuration: configuration)
18
18
  end
19
19
  end
20
20
 
@@ -15,7 +15,7 @@ module Truemail
15
15
  private
16
16
 
17
17
  def detect_ip_via_ipify
18
- Net::HTTP.get(URI(Truemail::Audit::Ip::GET_MY_IP_URL))
18
+ ::Net::HTTP.get(URI(Truemail::Audit::Ip::GET_MY_IP_URL))
19
19
  end
20
20
 
21
21
  def detect_current_host_ip
@@ -15,13 +15,14 @@ module Truemail
15
15
  private
16
16
 
17
17
  def current_host_reverse_lookup
18
- IPAddr.new(current_host_ip).reverse
18
+ ::IPAddr.new(current_host_ip).reverse
19
19
  end
20
20
 
21
21
  def ptr_records
22
22
  @ptr_records ||= Truemail::Wrapper.call(configuration: configuration) do
23
- Resolv::DNS.new.getresources(
24
- current_host_reverse_lookup, Resolv::DNS::Resource::IN::PTR
23
+ Truemail::Dns::Resolver.ptr_records(
24
+ current_host_reverse_lookup,
25
+ configuration: configuration
25
26
  ).map { |ptr_record| ptr_record.name.to_s }
26
27
  end || []
27
28
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Truemail
4
4
  class Auditor < Truemail::Executor
5
- Result = Struct.new(:current_host_ip, :warnings, :configuration, keyword_init: true) do
5
+ Result = ::Struct.new(:current_host_ip, :warnings, :configuration, keyword_init: true) do
6
6
  def initialize(warnings: {}, **args)
7
7
  super
8
8
  end
@@ -7,19 +7,23 @@ module Truemail
7
7
  DEFAULT_CONNECTION_ATTEMPTS = 2
8
8
  DEFAULT_VALIDATION_TYPE = :smtp
9
9
  DEFAULT_LOGGER_OPTIONS = { tracking_event: :error, stdout: false, log_absolute_path: nil }.freeze
10
-
11
- attr_reader :email_pattern,
12
- :smtp_error_body_pattern,
13
- :verifier_email,
10
+ SETTERS = %i[
11
+ email_pattern
12
+ smtp_error_body_pattern
13
+ connection_timeout
14
+ response_timeout
15
+ connection_attempts
16
+ whitelisted_domains
17
+ blacklisted_domains
18
+ ].freeze
19
+
20
+ attr_reader :verifier_email,
14
21
  :verifier_domain,
15
- :connection_timeout,
16
- :response_timeout,
17
- :connection_attempts,
18
22
  :default_validation_type,
19
23
  :validation_type_by_domain,
20
- :whitelisted_domains,
21
- :blacklisted_domains,
22
- :logger
24
+ :dns,
25
+ :logger,
26
+ *Truemail::Configuration::SETTERS
23
27
 
24
28
  attr_accessor :whitelist_validation, :not_rfc_mx_lookup_flow, :smtp_fail_fast, :smtp_safe_check
25
29
 
@@ -30,13 +34,6 @@ module Truemail
30
34
  tap(&block) if block
31
35
  end
32
36
 
33
- %i[email_pattern smtp_error_body_pattern].each do |method|
34
- define_method("#{method}=") do |argument|
35
- raise_unless(argument, __method__, argument.is_a?(Regexp))
36
- instance_variable_set(:"@#{method}", argument)
37
- end
38
- end
39
-
40
37
  def verifier_email=(email)
41
38
  validate_arguments(email, __method__)
42
39
  @verifier_email = email.downcase
@@ -48,15 +45,8 @@ module Truemail
48
45
  @verifier_domain = domain.downcase
49
46
  end
50
47
 
51
- %i[connection_timeout response_timeout connection_attempts].each do |method|
52
- define_method("#{method}=") do |argument|
53
- raise_unless(argument, __method__, argument.is_a?(Integer) && argument.positive?)
54
- instance_variable_set(:"@#{method}", argument)
55
- end
56
- end
57
-
58
48
  def default_validation_type=(argument)
59
- raise_unless(argument, __method__, argument.is_a?(Symbol) && Truemail::Validator::VALIDATION_TYPES.include?(argument))
49
+ raise_unless(argument, __method__, argument.is_a?(::Symbol) && Truemail::Validator::VALIDATION_TYPES.include?(argument))
60
50
  @default_validation_type = argument
61
51
  end
62
52
 
@@ -65,18 +55,31 @@ module Truemail
65
55
  validation_type_by_domain.merge!(settings)
66
56
  end
67
57
 
68
- %i[whitelisted_domains blacklisted_domains].each do |method|
58
+ def argument_consistent?(argument)
59
+ case argument
60
+ when ::Array then check_domain_list(argument)
61
+ when ::Integer then argument.positive?
62
+ when ::Regexp then true
63
+ end
64
+ end
65
+
66
+ Truemail::Configuration::SETTERS.each do |method|
69
67
  define_method("#{method}=") do |argument|
70
- raise_unless(argument, __method__, argument.is_a?(Array) && check_domain_list(argument))
68
+ raise_unless(argument, __method__, argument_consistent?(argument))
71
69
  instance_variable_set(:"@#{method}", argument)
72
70
  end
73
71
  end
74
72
 
73
+ def dns=(argument)
74
+ raise_unless(argument, __method__, argument.is_a?(::Array) && check_dns_settings(argument))
75
+ @dns = argument
76
+ end
77
+
75
78
  def logger=(options)
76
79
  tracking_event, stdout, log_absolute_path = logger_options(options)
77
80
  valid_event = Truemail::Log::Event::TRACKING_EVENTS.key?(tracking_event)
78
81
  stdout_only = stdout && log_absolute_path.nil?
79
- file_only = log_absolute_path.is_a?(String)
82
+ file_only = log_absolute_path.is_a?(::String)
80
83
  both_types = stdout && file_only
81
84
  argument_info = valid_event ? log_absolute_path : tracking_event
82
85
  raise_unless(argument_info, __method__, valid_event && (stdout_only || file_only || both_types))
@@ -89,7 +92,7 @@ module Truemail
89
92
 
90
93
  private
91
94
 
92
- def instance_initializer
95
+ def instance_initializer # rubocop:disable Metrics/MethodLength
93
96
  {
94
97
  email_pattern: Truemail::RegexConstant::REGEX_EMAIL_PATTERN,
95
98
  smtp_error_body_pattern: Truemail::RegexConstant::REGEX_SMTP_ERROR_BODY_PATTERN,
@@ -101,6 +104,7 @@ module Truemail
101
104
  whitelisted_domains: [],
102
105
  whitelist_validation: false,
103
106
  blacklisted_domains: [],
107
+ dns: [],
104
108
  not_rfc_mx_lookup_flow: false,
105
109
  smtp_fail_fast: false,
106
110
  smtp_safe_check: false
@@ -137,13 +141,17 @@ module Truemail
137
141
  end
138
142
 
139
143
  def validate_validation_type(settings)
140
- raise_unless(settings, 'hash with settings', settings.is_a?(Hash))
144
+ raise_unless(settings, 'hash with settings', settings.is_a?(::Hash))
141
145
  settings.each do |domain, validation_type|
142
146
  check_domain(domain)
143
147
  check_validation_type(validation_type)
144
148
  end
145
149
  end
146
150
 
151
+ def check_dns_settings(dns_servers)
152
+ dns_servers.all? { |dns_server| Truemail::RegexConstant::REGEX_DNS_SERVER_ADDRESS_PATTERN.match?(dns_server.to_s) }
153
+ end
154
+
147
155
  def logger_options(current_options)
148
156
  Truemail::Configuration::DEFAULT_LOGGER_OPTIONS.merge(current_options).values
149
157
  end
data/lib/truemail/core.rb CHANGED
@@ -10,32 +10,28 @@ module Truemail
10
10
  require_relative '../truemail/validator'
11
11
  require_relative '../truemail/logger'
12
12
 
13
- ConfigurationError = Class.new(StandardError)
14
- TypeError = Class.new(StandardError)
15
-
16
- ArgumentError = Class.new(StandardError) do
13
+ ConfigurationError = ::Class.new(::StandardError)
14
+ TypeError = ::Class.new(::StandardError)
15
+ ArgumentError = ::Class.new(::StandardError) do
17
16
  def initialize(arg_value, arg_name)
18
17
  super("#{arg_value} is not a valid #{arg_name}")
19
18
  end
20
19
  end
21
20
 
22
- PunycodeRepresenter = Class.new do
23
- require 'simpleidn'
24
-
25
- def self.call(email)
26
- return unless email.is_a?(String)
27
- return email if email.ascii_only?
28
- user, domain = email.split('@')
29
- "#{user}@#{SimpleIDN.to_ascii(domain.downcase)}"
30
- end
31
- end
32
-
33
21
  module RegexConstant
34
22
  REGEX_DOMAIN = /[\p{L}0-9]+([\-.]{1}[\p{L}0-9]+)*\.\p{L}{2,63}/i.freeze
35
23
  REGEX_EMAIL_PATTERN = /(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-.+]*)@(#{REGEX_DOMAIN})\z)/.freeze
36
24
  REGEX_DOMAIN_PATTERN = /(?=\A.{4,255}\z)(\A#{REGEX_DOMAIN}\z)/.freeze
37
25
  REGEX_DOMAIN_FROM_EMAIL = /\A.+@(.+)\z/.freeze
38
26
  REGEX_SMTP_ERROR_BODY_PATTERN = /(?=.*550)(?=.*(user|account|customer|mailbox)).*/i.freeze
27
+ REGEX_PORT_NUMBER = /(6553[0-5]|655[0-2][0-9]\d|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|[1-9](\d){0,3})/.freeze
28
+ REGEX_DNS_SERVER_ADDRESS_PATTERN = /\A((1\d|[1-9]|2[0-4])?\d|25[0-5])(\.\g<1>){3}(:#{REGEX_PORT_NUMBER})?\z/.freeze
29
+ end
30
+
31
+ module Dns
32
+ require_relative '../truemail/dns/punycode_representer'
33
+ require_relative '../truemail/dns/worker'
34
+ require_relative '../truemail/dns/resolver'
39
35
  end
40
36
 
41
37
  module Audit
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Truemail
4
+ module Dns
5
+ PunycodeRepresenter = Class.new do
6
+ require 'simpleidn'
7
+
8
+ def self.call(email)
9
+ return unless email.is_a?(::String)
10
+ return email if email.ascii_only?
11
+ user, domain = email.split('@')
12
+ "#{user}@#{SimpleIDN.to_ascii(domain.downcase)}"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Truemail
4
+ module Dns
5
+ class Resolver
6
+ WORKER_ACTIONS = %i[dns_lookup a_record a_records cname_records mx_records ptr_records].freeze
7
+
8
+ class << self
9
+ Truemail::Dns::Resolver::WORKER_ACTIONS.each do |worker_action|
10
+ define_method(worker_action) do |argument, configuration:|
11
+ Truemail::Dns::Worker.new(configuration.dns).public_send(worker_action, argument)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Truemail
4
+ module Dns
5
+ require 'resolv'
6
+
7
+ class Worker < ::Resolv::DNS
8
+ DEFAULT_DNS_PORT = 53
9
+
10
+ attr_reader :dns_gateway
11
+
12
+ def initialize(dns_servers)
13
+ super(dns_servers.empty? ? nil : config_info(dns_servers))
14
+ end
15
+
16
+ def dns_lookup(host_address)
17
+ getname(host_address).to_s
18
+ end
19
+
20
+ def a_record(host_name)
21
+ getaddress(host_name).to_s
22
+ end
23
+
24
+ def a_records(host_name)
25
+ getaddresses(host_name).map(&:to_s)
26
+ end
27
+
28
+ def cname_records(host_name)
29
+ getresources(host_name, ::Resolv::DNS::Resource::IN::CNAME)
30
+ end
31
+
32
+ def mx_records(host_name)
33
+ getresources(host_name, ::Resolv::DNS::Resource::IN::MX)
34
+ end
35
+
36
+ def ptr_records(host_address)
37
+ getresources(host_address, ::Resolv::DNS::Resource::IN::PTR)
38
+ end
39
+
40
+ private
41
+
42
+ def nameserver_port(server)
43
+ server_address, server_port = server.split(':')
44
+ [server_address, server_port ? server_port.to_i : Truemail::Dns::Worker::DEFAULT_DNS_PORT]
45
+ end
46
+
47
+ def config_info(dns_servers)
48
+ @dns_gateway = { nameserver_port: dns_servers.map { |server| nameserver_port(server) } }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -13,7 +13,7 @@ module Truemail
13
13
  def result
14
14
  @result ||=
15
15
  {
16
- date: Time.now,
16
+ date: ::Time.now,
17
17
  current_host_ip: executor_result.current_host_ip,
18
18
  warnings: warnings(executor_result.warnings),
19
19
  configuration: configuration
@@ -30,7 +30,7 @@ module Truemail
30
30
 
31
31
  alias warnings errors
32
32
 
33
- %i[validation_type_by_domain whitelisted_domains blacklisted_domains].each do |method|
33
+ %i[validation_type_by_domain whitelisted_domains blacklisted_domains dns].each do |method|
34
34
  define_method(method) do
35
35
  value = executor_configuration.public_send(method)
36
36
  return if value.empty?
@@ -55,6 +55,7 @@ module Truemail
55
55
  whitelist_validation: executor_configuration.whitelist_validation,
56
56
  whitelisted_domains: whitelisted_domains,
57
57
  blacklisted_domains: blacklisted_domains,
58
+ dns: dns,
58
59
  not_rfc_mx_lookup_flow: executor_configuration.not_rfc_mx_lookup_flow,
59
60
  smtp_fail_fast: executor_configuration.smtp_fail_fast,
60
61
  smtp_safe_check: executor_configuration.smtp_safe_check,
@@ -34,7 +34,7 @@ module Truemail
34
34
  def result
35
35
  @result ||=
36
36
  {
37
- date: Time.now,
37
+ date: ::Time.now,
38
38
  email: executor_result.email,
39
39
  validation_type: validation_type,
40
40
  success: executor_result.success,
@@ -20,8 +20,8 @@ module Truemail
20
20
  enumerable_object.inject([]) do |formatted_data, (key, value)|
21
21
  data =
22
22
  case
23
- when value.is_a?(Hash) then "\n#{printer(value)}"
24
- when value.is_a?(Array) then value.join(', ')
23
+ when value.is_a?(::Hash) then "\n#{printer(value)}"
24
+ when value.is_a?(::Array) then value.join(', ')
25
25
  else value
26
26
  end
27
27
  formatted_data << "#{key.to_s.tr('_', ' ')}: #{data}".chomp << "\n"
@@ -23,7 +23,7 @@ module Truemail
23
23
  def init_log_file
24
24
  output_file = Pathname(file)
25
25
  return output_file if output_file.exist?
26
- output_file.parent.mkpath && FileUtils.touch(output_file)
26
+ output_file.parent.mkpath && ::FileUtils.touch(output_file)
27
27
  output_file
28
28
  end
29
29
 
@@ -3,8 +3,6 @@
3
3
  module Truemail
4
4
  module Validate
5
5
  class Mx < Truemail::Validate::Base
6
- require 'resolv'
7
-
8
6
  ERROR = 'target host(s) not found'
9
7
  NULL_MX_RECORD = 'null_mx_record'
10
8
 
@@ -43,11 +41,11 @@ module Truemail
43
41
  end
44
42
 
45
43
  def mx_records(hostname)
46
- domain_mx_records = Resolv::DNS.new.getresources(hostname, Resolv::DNS::Resource::IN::MX)
44
+ domain_mx_records = Truemail::Dns::Resolver.mx_records(hostname, configuration: configuration)
47
45
  return [Truemail::Validate::Mx::NULL_MX_RECORD] if null_mx?(domain_mx_records)
48
- domain_mx_records.sort_by(&:preference).map do |mx_record|
49
- Resolv.getaddresses(mx_record.exchange.to_s)
50
- end.flatten
46
+ domain_mx_records.sort_by(&:preference).flat_map do |mx_record|
47
+ Truemail::Dns::Resolver.a_records(mx_record.exchange.to_s, configuration: configuration)
48
+ end
51
49
  end
52
50
 
53
51
  def mail_servers_found?
@@ -64,15 +62,15 @@ module Truemail
64
62
  end
65
63
 
66
64
  def a_record(hostname)
67
- Resolv.getaddress(hostname)
65
+ Truemail::Dns::Resolver.a_record(hostname, configuration: configuration)
68
66
  end
69
67
 
70
68
  def hosts_from_cname_records?
71
- cname_records = Resolv::DNS.new.getresources(domain, Resolv::DNS::Resource::IN::CNAME)
69
+ cname_records = Truemail::Dns::Resolver.cname_records(domain, configuration: configuration)
72
70
  return if cname_records.empty?
73
71
  cname_records.each do |cname_record|
74
72
  host = a_record(cname_record.name.to_s)
75
- hostname = Resolv.getname(host)
73
+ hostname = Truemail::Dns::Resolver.dns_lookup(host, configuration: configuration)
76
74
  found_hosts = mx_records(hostname)
77
75
  fetch_target_hosts(found_hosts.empty? ? [host] : found_hosts)
78
76
  end