Skip to content

Commit c7cf494

Browse files
committed
Config smtp-delivery via url
1 parent aba0b5f commit c7cf494

File tree

3 files changed

+150
-2
lines changed

3 files changed

+150
-2
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,14 @@ Mail.defaults do
265265
end
266266
```
267267

268+
An url is also accepted for smtp:
269+
```ruby
270+
Mail.defaults do
271+
# note the URL-encoded userinfo:
272+
delivery_method :smtp, url: 'smtps://user%40gmail.com:[email protected]'
273+
end
274+
```
275+
268276

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

lib/mail/network/delivery_methods/smtp.rb

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# frozen_string_literal: true
22
require 'mail/smtp_envelope'
3+
require 'mail/utilities'
34

45
module Mail
56
# == Sending Email with SMTP
@@ -35,8 +36,20 @@ module Mail
3536
# :password => '<password>',
3637
# :authentication => 'plain',
3738
# :enable_starttls_auto => true }
39+
#
40+
# # ...or using the url-attribute (note the URL-encoded userinfo):
41+
# delivery_method :smtp,
42+
# { :url => 'smtps://user%40gmail.com:[email protected]' }
3843
# end
3944
#
45+
# === Sending via Fastmail
46+
#
47+
# Mail.defaults do
48+
# delivery_method :smtp,
49+
# { :url => 'smtps://user%40fastmail.fm:[email protected]' }
50+
# end
51+
#
52+
#
4053
# === Certificate verification
4154
#
4255
# When using TLS, some mail servers provide certificates that are self-signed
@@ -74,6 +87,68 @@ module Mail
7487
#
7588
# mail.deliver!
7689
class SMTP
90+
class UrlResolver
91+
92+
DEFAULTS = {
93+
"smtp" => {
94+
:address => 'localhost',
95+
:port => 25,
96+
:domain => 'localhost.localdomain',
97+
:enable_starttls_auto => true
98+
},
99+
"smtps" => {
100+
:address => 'localhost',
101+
:port => 465,
102+
:domain => 'localhost.localdomain',
103+
:enable_starttls_auto => false,
104+
:tls => true
105+
}
106+
}
107+
108+
def initialize(url)
109+
@uri = url.is_a?(URI) ? url : uri_parser.parse(url)
110+
unless DEFAULTS.has_key?(@uri.scheme)
111+
raise ArgumentError, "#{url} is not a valid SMTP-url. Required format: smtp(s)://host?domain=sender.org"
112+
end
113+
@query = uri.query
114+
end
115+
116+
def to_hash
117+
config = raw_config
118+
config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a? String }
119+
config
120+
end
121+
122+
private
123+
attr_reader :uri
124+
125+
def raw_config
126+
scheme_defaults.merge(query_hash).merge({
127+
:address => uri.host,
128+
:port => uri.port,
129+
:user_name => uri.user,
130+
:password => uri.password
131+
}.delete_if {|_key, value| Utilities.blank?(value) })
132+
end
133+
134+
def uri_parser
135+
Utilities.uri_parser
136+
end
137+
138+
def query_hash
139+
@query_hash = begin
140+
result = Hash[(@query || "").split("&").map { |pair| k,v = pair.split("="); [k.to_sym, v] }]
141+
result[:open_timeout] &&= result[:open_timeout].to_i
142+
result[:read_timeout] &&= result[:read_timeout].to_i
143+
result
144+
end
145+
end
146+
147+
def scheme_defaults
148+
DEFAULTS[uri.scheme]
149+
end
150+
end
151+
77152
attr_accessor :settings
78153

79154
DEFAULTS = {
@@ -92,8 +167,13 @@ class SMTP
92167
:read_timeout => nil
93168
}
94169

95-
def initialize(values)
96-
self.settings = DEFAULTS.merge(values)
170+
def initialize(config)
171+
settings = DEFAULTS
172+
173+
if config.has_key?(:url)
174+
settings = settings.merge(UrlResolver.new(config.delete(:url)).to_hash)
175+
end
176+
self.settings = settings.merge(config)
97177
end
98178

99179
def deliver!(mail)

spec/mail/network/delivery_methods/smtp_spec.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,66 @@
2525
MockSMTP.clear_deliveries
2626
end
2727

28+
describe 'settings via url' do
29+
def smtp_delivery_settings_for_url(url, options = {})
30+
Mail.defaults { delivery_method :smtp, {:url => url}.merge(options) }
31+
32+
Mail.delivery_method.settings
33+
end
34+
35+
it 'provides smtp defaults' do
36+
expect(smtp_delivery_settings_for_url('smtp:///')).to \
37+
include(:address => 'localhost',
38+
:port => 25,
39+
:domain => 'localhost.localdomain',
40+
:enable_starttls_auto => true)
41+
end
42+
43+
it 'provides smtps defaults' do
44+
expect(smtp_delivery_settings_for_url('smtps:///')).to \
45+
include(:address => 'localhost',
46+
:port => 465,
47+
:domain => 'localhost.localdomain',
48+
:tls => true)
49+
end
50+
51+
it 'allows for all attributes to be set' do
52+
url = 'smtp://mailer.org:12345?domain=sender.org&authentication=plain&openssl_verify_mode=peer&tls=true&open_timeout=1&read_timeout=2'
53+
expect(smtp_delivery_settings_for_url(url)).to \
54+
include(:address => 'mailer.org',
55+
:port => 12345,
56+
:domain => 'sender.org',
57+
:authentication => 'plain',
58+
:openssl_verify_mode => 'peer',
59+
:tls => 'true',
60+
:open_timeout => 1,
61+
:read_timeout => 2)
62+
end
63+
64+
it 'allows for url-attributes to be overriden' do
65+
overrides = {:address => 'real-host', :domain => 'real-domain'}
66+
expect(smtp_delivery_settings_for_url('smtp://user:pw@host?domain=sender.org', overrides)).to \
67+
include(overrides)
68+
end
69+
70+
it 'escapes credentials' do
71+
expect(smtp_delivery_settings_for_url('smtps://user%40host:pw-with-%[email protected]')).to \
72+
include(:user_name => 'user@host',
73+
:password => 'pw-with-/')
74+
end
75+
76+
it 'accepts an URI' do
77+
expect(smtp_delivery_settings_for_url(URI.parse('smtps://host'))).to \
78+
include(:address => 'host')
79+
end
80+
81+
it 'should raise an error for url without smtp(s) scheme' do
82+
expect {
83+
smtp_delivery_settings_for_url('foo://host')
84+
}.to raise_error(/is not a valid SMTP-url/)
85+
end
86+
end
87+
2888
describe "general usage" do
2989
it "dot-stuff unterminated last line of the message" do
3090
Mail.deliver do

0 commit comments

Comments
 (0)