Skip to content

Commit

Permalink
Config smtp-delivery via url
Browse files Browse the repository at this point in the history
  • Loading branch information
eval committed Sep 20, 2019
1 parent aba0b5f commit a5cf440
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 2 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,14 @@ Mail.defaults do
end
```

An url is also accepted for smtp:
```ruby
Mail.defaults do
# note the URL-encoded userinfo:
delivery_method :smtp, url: 'smtps://user%40gmail.com:[email protected]'
end
```


Exim requires its own delivery manager, and can be used like so:

Expand Down
85 changes: 83 additions & 2 deletions lib/mail/network/delivery_methods/smtp.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true
require 'mail/smtp_envelope'
require 'mail/utilities'

module Mail
# == Sending Email with SMTP
Expand All @@ -23,6 +24,7 @@ module Mail
# :password => '<password>',
# :authentication => 'plain',
# :enable_starttls_auto => true }
#
# end
#
# === Sending via GMail
Expand All @@ -35,8 +37,20 @@ module Mail
# :password => '<password>',
# :authentication => 'plain',
# :enable_starttls_auto => true }
#
# # ...or using the url-attribute (note the URL-encoded userinfo):
# delivery_method :smtp,
# { :url => 'smtps://user%40gmail.com:[email protected]' }
# end
#
# === Sending via Fastmail
#
# Mail.defaults do
# delivery_method :smtp,
# { :url => 'smtps://user%40fastmail.fm:[email protected]' }
# end
#
#
# === Certificate verification
#
# When using TLS, some mail servers provide certificates that are self-signed
Expand Down Expand Up @@ -74,6 +88,68 @@ module Mail
#
# mail.deliver!
class SMTP
class UrlResolver

DEFAULTS = {
"smtp" => {
:address => 'localhost',
:port => 25,
:domain => 'localhost.localdomain',
:enable_starttls_auto => true
},
"smtps" => {
:address => 'localhost',
:port => 465,
:domain => 'localhost.localdomain',
:enable_starttls_auto => false,
:tls => true
}
}

def initialize(url)
@uri = url.is_a?(URI) ? url : uri_parser.parse(url)
unless DEFAULTS.has_key?(@uri.scheme)
raise ArgumentError, "#{url} is not a valid SMTP-url. Required format: smtp(s)://host?domain=sender.org"
end
@query = uri.query
end

def to_hash
config = raw_config
config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a? String }
config
end

private
attr_reader :uri

def raw_config
scheme_defaults.merge(query_hash).merge({
:address => uri.host,
:port => uri.port,
:user_name => uri.user,
:password => uri.password
}.delete_if {|_key, value| Utilities.blank?(value) })
end

def uri_parser
Utilities.uri_parser
end

def query_hash
@query_hash = begin
result = Hash[(@query || "").split("&").map { |pair| k,v = pair.split("="); [k.to_sym, v] }]
result[:open_timeout] &&= result[:open_timeout].to_i
result[:read_timeout] &&= result[:read_timeout].to_i
result
end
end

def scheme_defaults
DEFAULTS[uri.scheme]
end
end

attr_accessor :settings

DEFAULTS = {
Expand All @@ -92,8 +168,13 @@ class SMTP
:read_timeout => nil
}

def initialize(values)
self.settings = DEFAULTS.merge(values)
def initialize(config)
settings = DEFAULTS

if config.has_key?(:url)
settings = settings.merge(UrlResolver.new(config.delete(:url)).to_hash)
end
self.settings = settings.merge(config)
end

def deliver!(mail)
Expand Down
60 changes: 60 additions & 0 deletions spec/mail/network/delivery_methods/smtp_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,66 @@
MockSMTP.clear_deliveries
end

describe 'settings via url' do
def smtp_delivery_settings_for_url(url, options = {})
Mail.defaults { delivery_method :smtp, {:url => url}.merge(options) }

Mail.delivery_method.settings
end

it 'provides smtp defaults' do
expect(smtp_delivery_settings_for_url('smtp:///')).to \
include(:address => 'localhost',
:port => 25,
:domain => 'localhost.localdomain',
:enable_starttls_auto => true)
end

it 'provides smtps defaults' do
expect(smtp_delivery_settings_for_url('smtps:///')).to \
include(:address => 'localhost',
:port => 465,
:domain => 'localhost.localdomain',
:tls => true)
end

it 'allows for all attributes to be set' do
url = 'smtp://mailer.org:12345?domain=sender.org&authentication=plain&openssl_verify_mode=peer&tls=true&open_timeout=1&read_timeout=2'
expect(smtp_delivery_settings_for_url(url)).to \
include(:address => 'mailer.org',
:port => 12345,
:domain => 'sender.org',
:authentication => 'plain',
:openssl_verify_mode => 'peer',
:tls => 'true',
:open_timeout => 1,
:read_timeout => 2)
end

it 'allows for url-attributes to be overriden' do
overrides = {:address => 'real-host', :domain => 'real-domain'}
expect(smtp_delivery_settings_for_url('smtp://user:pw@host?domain=sender.org', overrides)).to \
include(overrides)
end

it 'escapes credentials' do
expect(smtp_delivery_settings_for_url('smtps://user%40host:pw-with-%[email protected]')).to \
include(:user_name => 'user@host',
:password => 'pw-with-/')
end

it 'accepts an URI' do
expect(smtp_delivery_settings_for_url(URI.parse('smtps://host'))).to \
include(:address => 'host')
end

it 'should raise an error for url without smtp(s) scheme' do
expect {
smtp_delivery_settings_for_url('foo://host')
}.to raise_error(/is not a valid SMTP-url/)
end
end

describe "general usage" do
it "dot-stuff unterminated last line of the message" do
Mail.deliver do
Expand Down

0 comments on commit a5cf440

Please sign in to comment.