uri-smtp 0.5.0 → 0.7.1
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.
- checksums.yaml +4 -4
- data/.yardopts +6 -0
- data/CHANGELOG.md +8 -0
- data/README.md +8 -5
- data/lib/uri/smtp/version.rb +1 -1
- data/lib/uri/smtp.rb +147 -29
- data/rakelib/yard.rake +12 -0
- data/tmp/.gitkeep +0 -0
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19f134f7a4dd627d1493c292572328eba64155e9b1ec07e870aa647d54fa4ef2
|
4
|
+
data.tar.gz: ba12895246eb404221548ae749d5661ef8218d3ac3b0bf2b811b0b20253123ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ac0a82ee28bb827f44a7a74aa44215a428e6dda59f9821e59d3a7422b02aaed6e1fd9195537878e3cf9beeff919c6ef0bbdfbde905c6e5a51f89645c8f9b9b6
|
7
|
+
data.tar.gz: e824027c3c9aa864e6e34e5299ea75d0fee6af4641293ee10999c7c4aa43f330b7014e1624272ccb9fbbe499b510e565d0d1b4f6234e183386c2bfb3451a6874
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.6.0] - 2025-07-27
|
4
|
+
|
5
|
+
- Add: API-docs at https://eval.github.io/uri-smtp/
|
6
|
+
- Fix: "smtp+insecure+foo://..." not considered `#insecure?`
|
7
|
+
- Fix: "smtp://foo.org?auth=none" being ignored
|
8
|
+
- Fix: "smtp+insecure://..." having auth "insecure"
|
9
|
+
- Remove: `#starttls?`
|
10
|
+
|
3
11
|
## [0.5.0] - 2025-07-25
|
4
12
|
|
5
13
|
- Add: `uri#read_timeout`, `uri#open_timeout`
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# URI::SMTP
|
1
|
+
# URI::SMTP [](https://badge.fury.io/rb/uri-smtp) [](https://eval.github.io/uri-smtp/)
|
2
2
|
|
3
3
|
Extends Ruby's `URI` with support for SMTP-uri's.
|
4
4
|
This allows for more concise SMTP-config:
|
@@ -48,7 +48,7 @@ url.starttls #=> false
|
|
48
48
|
url.starttls? #=> false
|
49
49
|
url.tls? #=> true
|
50
50
|
url.userinfo #=> "user%40gmail.com:p%40ss"
|
51
|
-
url.decoded_userinfo #=>
|
51
|
+
url.decoded_userinfo #=> "user@gmail.com:p@ss"
|
52
52
|
url.decoded_user #=> "user@gmail.com"
|
53
53
|
url.user #=> "user%40gmail.com"
|
54
54
|
url.decoded_password #=> "p@ss"
|
@@ -74,7 +74,7 @@ URI("smtps+login://user%40gmail.com:p%40ss@smtp.gmail.com?domain=sender.org").to
|
|
74
74
|
password: "p@ss"}
|
75
75
|
```
|
76
76
|
|
77
|
-
|
77
|
+
For [ActionMailer configuration](https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration), use `format: :action_mailer` (or `:am`):
|
78
78
|
```ruby
|
79
79
|
URI("smtps+login://user%40gmail.com:p%40ss@smtp.gmail.com?domain=sender.org").to_h(format: :am)
|
80
80
|
#=>
|
@@ -87,6 +87,8 @@ URI("smtps+login://user%40gmail.com:p%40ss@smtp.gmail.com?domain=sender.org").to
|
|
87
87
|
password: "p@ss"}
|
88
88
|
```
|
89
89
|
|
90
|
+
Besides renaming some keys, this also works around a quirk in `v2.8.1` of the mail-gem (e.g. `tls: false` [skips setting up STARTTLS](https://github.com/mikel/mail/blob/2.8.1/lib/mail/network/delivery_methods/smtp.rb#L115)).
|
91
|
+
|
90
92
|
|
91
93
|
Full Rails config:
|
92
94
|
```ruby
|
@@ -116,12 +118,12 @@ There's no official specification for SMTP-URIs. There's some prior work though.
|
|
116
118
|
|
117
119
|
### auth
|
118
120
|
|
119
|
-
|
121
|
+
Any value for auth that passes the URI-parser is acceptable. Though the following values have special meaning:
|
120
122
|
|
121
123
|
- `none`
|
122
124
|
No authentication is required.
|
123
125
|
- `plain`
|
124
|
-
Authenticate with a username and password using AUTH PLAIN. This is the default behavior.
|
126
|
+
Authenticate with a username and password using AUTH PLAIN. This is the default behavior when no authentication is provided.
|
125
127
|
|
126
128
|
> [!NOTE]
|
127
129
|
> any query's value for `auth` takes precedence.
|
@@ -158,6 +160,7 @@ There's no restriction to the value of auth. Though the following values have sp
|
|
158
160
|
## Development
|
159
161
|
|
160
162
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
163
|
+
Use `bin/yard server --reload` when working on documentation.
|
161
164
|
|
162
165
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
163
166
|
|
data/lib/uri/smtp/version.rb
CHANGED
data/lib/uri/smtp.rb
CHANGED
@@ -3,20 +3,13 @@
|
|
3
3
|
require "uri"
|
4
4
|
require_relative "smtp/version"
|
5
5
|
|
6
|
+
# See https://docs.ruby-lang.org/en/master/URI.html
|
6
7
|
module URI
|
8
|
+
# Class that adds smtp(s)-scheme to the standard URI-module.
|
7
9
|
class SMTP < URI::Generic
|
8
10
|
class Error < StandardError; end
|
9
11
|
|
10
|
-
|
11
|
-
userinfo, host, port, registry,
|
12
|
-
path, opaque,
|
13
|
-
query,
|
14
|
-
fragment,
|
15
|
-
parser = DEFAULT_PARSER,
|
16
|
-
arg_check = false)
|
17
|
-
super
|
18
|
-
end
|
19
|
-
|
12
|
+
# @return [Integer]
|
20
13
|
def port
|
21
14
|
return @port if @port
|
22
15
|
return 25 if host_local?
|
@@ -25,9 +18,36 @@ module URI
|
|
25
18
|
587
|
26
19
|
end
|
27
20
|
|
21
|
+
# Return mechanism of authentication (default `"plain"`).
|
22
|
+
#
|
23
|
+
# Only returns value when {URI::SMTP#userinfo} is provided and authentication is not `"none"`.
|
24
|
+
#
|
25
|
+
# Authentication can be provided via scheme (e.g. `"smtp+login://..."`) or via
|
26
|
+
# query-params (e.g. `"smtp://foo.org?auth=cram-md5"`). The latter takes precedence when both are provided.
|
27
|
+
# A provided value of `"none"` results in `nil`. Other values are returned as is.
|
28
|
+
# @example
|
29
|
+
# # no userinfo
|
30
|
+
# URI("smtp://foo.org").auth #=> nil
|
31
|
+
#
|
32
|
+
# # "none"
|
33
|
+
# URI("smtp+none://user@foo.org").auth #=> nil
|
34
|
+
#
|
35
|
+
# # default value
|
36
|
+
# URI("smtp://user@foo.org").auth #=> "plain"
|
37
|
+
#
|
38
|
+
# # query takes precedence
|
39
|
+
# URI("smtp+login://user@foo.org?auth=cram-md5").auth #=> "cram-md5"
|
40
|
+
# @return [String, nil] mechanism of authentication or `nil`:
|
41
|
+
# @return [nil] when there's no `userinfo`.
|
42
|
+
# @return [nil] if 'auth via query' is `"none"`, e.g. `"smtp://foo.org?auth=none"`.
|
43
|
+
# @return [String] 'auth via query' when present.
|
44
|
+
# @return [nil] if 'auth via scheme' is `"none"`, e.g. `"smtp+none://foo.org"`.
|
45
|
+
# @return [String] 'auth via scheme' when present, e.g. `"smtp+login://foo.org"`.
|
46
|
+
# @return [String] else `"plain"`
|
28
47
|
def auth
|
29
48
|
# net-smtp: passing authtype without user/pw raises error
|
30
49
|
return nil unless userinfo
|
50
|
+
return nil if parsed_query["auth"] == "none"
|
31
51
|
return parsed_query["auth"] if parsed_query.has_key?("auth")
|
32
52
|
return nil if scheme_auth == "none"
|
33
53
|
return scheme_auth if scheme_auth
|
@@ -35,29 +55,35 @@ module URI
|
|
35
55
|
"plain"
|
36
56
|
end
|
37
57
|
|
38
|
-
#
|
58
|
+
# Decoded userinfo formatted as String, Array or Hash.
|
59
|
+
#
|
60
|
+
# **NOTE** not provided user or password result in `nil` (format: :array) or absent keys (format: :hash).
|
61
|
+
#
|
62
|
+
# @example no userinfo => `nil`
|
63
|
+
# URI("smtp://foo.org").decoded_userinfo #=> nil
|
64
|
+
# URI("smtp://foo.org").decoded_userinfo(format: :array) #=> nil
|
65
|
+
# URI("smtp://foo.org").decoded_userinfo(format: :hash) #=> nil
|
39
66
|
#
|
40
|
-
# format
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
# uri.decoded_userinfo #=> [nil, "token"]
|
67
|
+
# @example format `:array`
|
68
|
+
# # absent user/password is `nil`
|
69
|
+
# URI("smtp://user@foo.org").decoded_userinfo(format: :array) #=> ["user", nil]
|
70
|
+
# URI("smtp://:pw@foo.org").decoded_userinfo(format: :array) #=> [nil, "pw"]
|
71
|
+
# # decoded values
|
72
|
+
# URI("smtp://user%40gmail.com:p%40ss@foo.org").decoded_userinfo(format: :array) #=> ["user@gmail.com", "p@ss"]
|
47
73
|
#
|
48
|
-
# format
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
74
|
+
# @example format `:hash`
|
75
|
+
# # absent user/password is left out
|
76
|
+
# URI("smtp://user%40gmail.com@foo.org").decoded_userinfo(format: :hash) #=> {user: "user@gmail.com"}
|
77
|
+
# URI("smtp://:p%40ss@foo.org").decoded_userinfo(format: :hash) #=> {password: "p@ss"}
|
78
|
+
#
|
79
|
+
# @param format [Symbol] the format type, `:string` (default), `:array` or `:hash`.
|
80
|
+
# @return [String, Array, Hash] Decoded userinfo formatted as String, Array or Hash.
|
55
81
|
def decoded_userinfo(format: :string)
|
56
82
|
return if userinfo.nil?
|
57
83
|
|
58
84
|
case format
|
59
85
|
when :string
|
60
|
-
[decoded_user, decoded_password].
|
86
|
+
[decoded_user, decoded_password].join(":")
|
61
87
|
when :array
|
62
88
|
[string_presence(decoded_user), string_presence(decoded_password)]
|
63
89
|
when :hash
|
@@ -71,18 +97,36 @@ module URI
|
|
71
97
|
end
|
72
98
|
end
|
73
99
|
|
100
|
+
# The host to send mail from, i.e. the `HELO` domain.
|
101
|
+
# @return [String] the query-key `domain` when present, e.g. `"smtp://foo.org?domain=sender.org"`.
|
102
|
+
# @return [String] the `fragment` when present, e.g. `"smtp://foo.org#sender.org"`.
|
103
|
+
# @return [nil] otherwise
|
74
104
|
def domain
|
75
105
|
parsed_query["domain"] || fragment
|
76
106
|
end
|
77
107
|
|
108
|
+
# @return [Integer]
|
78
109
|
def read_timeout
|
79
110
|
parsed_query["read_timeout"]
|
80
111
|
end
|
81
112
|
|
113
|
+
# @return [Integer]
|
82
114
|
def open_timeout
|
83
115
|
parsed_query["open_timeout"]
|
84
116
|
end
|
85
117
|
|
118
|
+
# Whether or not to use `STARTTLS`.
|
119
|
+
#
|
120
|
+
# The possible return values (i.e. `:always`, `:auto` and `false`) map to what {https://github.com/ruby/net-smtp net-smtp} uses:
|
121
|
+
# - `:always` use `STARTTLS` or disconnect when server does not support it.
|
122
|
+
# - `:auto` use `STARTTLS` when supported, otherwise continue unencrypted.
|
123
|
+
# - `false` don't use `STARTTLS`.
|
124
|
+
#
|
125
|
+
# @return [false] when `tls?`.
|
126
|
+
# @return [:always, :auto, false] when query-key `starttls` is present, e.g. `"smtp://foo.org?starttls=auto"`.
|
127
|
+
# @return [false] when `host_local?` (the host is considered one for local development).
|
128
|
+
# @return [false] when `insecure?` (i.e. `scheme` starts with `"smtp+insecure"`).
|
129
|
+
# @return [:always] otherwise.
|
86
130
|
def starttls
|
87
131
|
return false if tls?
|
88
132
|
return parsed_query["starttls"] if parsed_query.has_key?("starttls")
|
@@ -91,21 +135,44 @@ module URI
|
|
91
135
|
|
92
136
|
:always
|
93
137
|
end
|
94
|
-
alias_method :starttls?, :starttls
|
95
138
|
|
139
|
+
# @return [Boolean] whether or not `scheme` starts with `"smtps"`.
|
96
140
|
def tls
|
97
141
|
!!scheme[/^smtps/]
|
98
142
|
end
|
99
143
|
alias_method :tls?, :tls
|
100
144
|
|
145
|
+
# Whether or not the scheme indicates to skip STARTTLS.
|
146
|
+
#
|
147
|
+
# @see #starttls
|
148
|
+
#
|
149
|
+
# @example
|
150
|
+
# URI("smtp+insecure://foo.org").insecure? #=> true
|
151
|
+
# # This is equivalent (though shorter and more descriptive) to
|
152
|
+
# URI("smtp://foo.org?starttls=false")
|
153
|
+
#
|
154
|
+
# # combine with authentication
|
155
|
+
# URI("smtp+insecure+login://user:pw@foo.org").insecure? #=> true
|
156
|
+
# @return [Boolean] whether `scheme` starts with `"smtp+insecure"`.
|
101
157
|
def insecure?
|
102
|
-
scheme
|
158
|
+
scheme.start_with?("smtp+insecure")
|
103
159
|
end
|
104
160
|
|
161
|
+
# Whether or not `host` is considered local.
|
162
|
+
#
|
163
|
+
# Hostnames that are considered local have certain defaults (i.e. port `25` and no `STARTTLS`).
|
164
|
+
# @example
|
165
|
+
# # Point to mailcatcher (https://github.com/sj26/mailcatcher)
|
166
|
+
# URI("smtp://127.0.0.1:1025").host_local? #=> true
|
167
|
+
#
|
168
|
+
# URI("smtp://localhost").host_local? #=> true
|
169
|
+
# @return [Boolean] whether or not `host` is considered local.
|
105
170
|
def host_local?
|
106
171
|
%w[127.0.0.1 localhost].include?(host)
|
107
172
|
end
|
108
173
|
|
174
|
+
# `query` as Hash with values `starttls`, `read_timeout` and `open_timeout` coerced.
|
175
|
+
# @return [Hash] `query` parsed.
|
109
176
|
def parsed_query
|
110
177
|
@parsed_query ||= URI.decode_www_form(query.to_s).to_h
|
111
178
|
.delete_if { |_k, v| !string_presence(v) }
|
@@ -121,6 +188,41 @@ module URI
|
|
121
188
|
end
|
122
189
|
end
|
123
190
|
|
191
|
+
# Return {Hash} representing the URI.
|
192
|
+
#
|
193
|
+
# `format` should be one of: `nil` or `:action_mailer` (or `:am`).
|
194
|
+
#
|
195
|
+
# Format `:action_mailer` matches how {https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration ActionMailer} should be configured and works around some quirks in Mail v2.8.1.
|
196
|
+
#
|
197
|
+
# **NOTE** keys with nil-values are stripped.
|
198
|
+
# @example default format
|
199
|
+
# URI("smtps+login://user%40gmail.com:p%40ss@smtp.gmail.com#sender.org").to_h
|
200
|
+
# # =>
|
201
|
+
# # {auth: "login",
|
202
|
+
# # domain: "sender.org",
|
203
|
+
# # host: "smtp.gmail.com",
|
204
|
+
# # port: 465,
|
205
|
+
# # scheme: "smtps+login",
|
206
|
+
# # starttls: false,
|
207
|
+
# # tls: true,
|
208
|
+
# # user: "user@gmail.com",
|
209
|
+
# # password: "p@ss"}
|
210
|
+
# @example format `:action_mailer`/`:am`, ActionMailer configuration
|
211
|
+
# URI("smtps+login://user%40gmail.com:p%40ss@smtp.gmail.com#sender.org").to_h(format: :am)
|
212
|
+
# # =>
|
213
|
+
# # {address: "smtp.gmail.com",
|
214
|
+
# # authentication: "login",
|
215
|
+
# # domain: "sender.org",
|
216
|
+
# # port: 465,
|
217
|
+
# # tls: true,
|
218
|
+
# # user_name: "user@gmail.com",
|
219
|
+
# # password: "p@ss"}
|
220
|
+
# @example Rails configuration
|
221
|
+
# # file: config/environments/development.rb
|
222
|
+
# # Config via env-var SMTP_URL or fallback to mailcatcher.
|
223
|
+
# config.action_mailer.smtp_settings = URI(ENV.fetch("SMTP_URL", "http://127.0.0.1:1025")).to_h(format: :am)
|
224
|
+
# @param format [Symbol] the format type, `nil` (default), `:action_mailer`/`:am`.
|
225
|
+
# @return [Hash]
|
124
226
|
def to_h(format: nil)
|
125
227
|
case format
|
126
228
|
when :am, :action_mailer
|
@@ -168,6 +270,11 @@ module URI
|
|
168
270
|
end
|
169
271
|
end
|
170
272
|
|
273
|
+
# Parse `uri` and instantiate instance of URI::SMTP.
|
274
|
+
# @example
|
275
|
+
# URI::SMTP.parse("smtps+plain://user:pw@foo.org#sender.org")
|
276
|
+
# #=> #<URI::SMTP smtps+plain://user:pw@foo.org#sender.org>
|
277
|
+
# @return [URI::SMTP] URI::SMTP instance from `uri`.
|
171
278
|
def self.parse(uri)
|
172
279
|
new(*URI.split(uri))
|
173
280
|
end
|
@@ -175,12 +282,21 @@ module URI
|
|
175
282
|
private
|
176
283
|
|
177
284
|
def scheme_auth
|
178
|
-
scheme
|
285
|
+
string_absense_in(scheme.split("+").last, %w[smtp smtps insecure])
|
179
286
|
end
|
180
287
|
|
288
|
+
# string_presence("") #=> nil
|
289
|
+
# string_presence(" ") #=> nil
|
290
|
+
# string_presence(" FOO ") #=> " FOO "
|
181
291
|
def string_presence(s)
|
182
292
|
s.to_s.strip.then { _1 unless _1.empty? }
|
183
293
|
end
|
294
|
+
|
295
|
+
# string_absense_in("foo", %w[bar baz]) #=> "foo"
|
296
|
+
# string_absense_in("bar", %w[bar baz]) #=> nil
|
297
|
+
def string_absense_in(s, array)
|
298
|
+
s unless array.include?(s)
|
299
|
+
end
|
184
300
|
end
|
185
301
|
|
186
302
|
register_scheme "SMTP", SMTP
|
@@ -189,6 +305,8 @@ end
|
|
189
305
|
|
190
306
|
module UriSmtpExtensions
|
191
307
|
def parse(uri)
|
308
|
+
# Ensure 'plus schemes' (e.g., `smtp+login://`, `smtp+oauth://`) are parsed as URI::SMTP
|
309
|
+
# instead of URI::Generic objects.
|
192
310
|
if uri.is_a?(String) && uri.start_with?("smtp")
|
193
311
|
return URI::SMTP.parse(uri)
|
194
312
|
end
|
data/rakelib/yard.rake
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "yard"
|
2
|
+
|
3
|
+
YARD::Rake::YardocTask.new(:docs) do |t|
|
4
|
+
# Options defined in `.yardopts` are read first, then merged with
|
5
|
+
# options defined here.
|
6
|
+
#
|
7
|
+
# It's recommended to define options in `.yardopts` instead of here,
|
8
|
+
# as `.yardopts` can be read by external YARD tools, like the
|
9
|
+
# hot-reload YARD server `yard server --reload`.
|
10
|
+
|
11
|
+
# t.options += ['--title', "Something custom"]
|
12
|
+
end
|
data/tmp/.gitkeep
ADDED
File without changes
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: uri-smtp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gert Goet
|
@@ -17,13 +17,16 @@ extra_rdoc_files: []
|
|
17
17
|
files:
|
18
18
|
- ".rspec"
|
19
19
|
- ".standard.yml"
|
20
|
+
- ".yardopts"
|
20
21
|
- CHANGELOG.md
|
21
22
|
- LICENSE.txt
|
22
23
|
- README.md
|
23
24
|
- Rakefile
|
24
25
|
- lib/uri/smtp.rb
|
25
26
|
- lib/uri/smtp/version.rb
|
27
|
+
- rakelib/yard.rake
|
26
28
|
- sig/uri/smtp.rbs
|
29
|
+
- tmp/.gitkeep
|
27
30
|
homepage: https://github.com/eval/uri-smtp
|
28
31
|
licenses:
|
29
32
|
- MIT
|
@@ -31,6 +34,7 @@ metadata:
|
|
31
34
|
homepage_uri: https://github.com/eval/uri-smtp
|
32
35
|
source_code_uri: https://github.com/eval/uri-smtp
|
33
36
|
changelog_uri: https://github.com/eval/uri-smtp/blob/main/CHANGELOG.md
|
37
|
+
documentation_uri: https://eval.github.io/uri-smtp/
|
34
38
|
rdoc_options: []
|
35
39
|
require_paths:
|
36
40
|
- lib
|