truemail 2.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/truemail.rb CHANGED
@@ -10,7 +10,7 @@ module Truemail
10
10
  class << self
11
11
  def configuration(&block)
12
12
  @configuration ||= begin
13
- return unless block_given?
13
+ return unless block
14
14
  configuration = Truemail::Configuration.new(&block)
15
15
  raise_unless(configuration.complete?, Truemail::INCOMPLETE_CONFIG)
16
16
  configuration
@@ -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,34 +7,31 @@ 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
- attr_accessor :whitelist_validation, :not_rfc_mx_lookup_flow, :smtp_safe_check
28
+ attr_accessor :whitelist_validation, :not_rfc_mx_lookup_flow, :smtp_fail_fast, :smtp_safe_check
25
29
 
26
30
  def initialize(&block)
27
31
  instance_initializer.each do |instace_variable, value|
28
32
  instance_variable_set(:"@#{instace_variable}", value)
29
33
  end
30
- tap(&block) if block_given?
31
- end
32
-
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
34
+ tap(&block) if block
38
35
  end
39
36
 
40
37
  def verifier_email=(email)
@@ -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,7 +104,9 @@ 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,
109
+ smtp_fail_fast: false,
105
110
  smtp_safe_check: false
106
111
  }
107
112
  end
@@ -136,13 +141,17 @@ module Truemail
136
141
  end
137
142
 
138
143
  def validate_validation_type(settings)
139
- raise_unless(settings, 'hash with settings', settings.is_a?(Hash))
144
+ raise_unless(settings, 'hash with settings', settings.is_a?(::Hash))
140
145
  settings.each do |domain, validation_type|
141
146
  check_domain(domain)
142
147
  check_validation_type(validation_type)
143
148
  end
144
149
  end
145
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
+
146
155
  def logger_options(current_options)
147
156
  Truemail::Configuration::DEFAULT_LOGGER_OPTIONS.merge(current_options).values
148
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,7 +55,9 @@ 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,
60
+ smtp_fail_fast: executor_configuration.smtp_fail_fast,
59
61
  smtp_safe_check: executor_configuration.smtp_safe_check,
60
62
  email_pattern: email_pattern,
61
63
  smtp_error_body_pattern: smtp_error_body_pattern
@@ -13,6 +13,10 @@ module Truemail
13
13
 
14
14
  attr_reader :validation_type
15
15
 
16
+ def replace_invalid_chars
17
+ ->(value) { value.encode('UTF-8', invalid: :replace) }
18
+ end
19
+
16
20
  def smtp_debug
17
21
  validation_smtp_debug = executor_result.smtp_debug
18
22
  return unless validation_smtp_debug
@@ -22,7 +26,7 @@ module Truemail
22
26
  mail_host: smtp_request.host,
23
27
  port_opened: smtp_response.port_opened,
24
28
  connection: smtp_response.connection,
25
- errors: smtp_response.errors
29
+ errors: smtp_response.errors.transform_values(&replace_invalid_chars)
26
30
  }
27
31
  end
28
32
  end
@@ -30,7 +34,7 @@ module Truemail
30
34
  def result
31
35
  @result ||=
32
36
  {
33
- date: Time.now,
37
+ date: ::Time.now,
34
38
  email: executor_result.email,
35
39
  validation_type: validation_type,
36
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