net-imap 0.2.3 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/test.yml +2 -2
- data/.gitignore +1 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +62 -0
- data/README.md +1 -2
- data/Rakefile +3 -0
- data/benchmarks/stringprep.yml +65 -0
- data/benchmarks/table-regexps.yml +39 -0
- data/docs/styles.css +36 -0
- data/lib/net/imap/authenticators/cram_md5.rb +5 -3
- data/lib/net/imap/authenticators/digest_md5.rb +11 -7
- data/lib/net/imap/authenticators/login.rb +4 -1
- data/lib/net/imap/authenticators/xoauth2.rb +20 -0
- data/lib/net/imap/authenticators.rb +41 -17
- data/lib/net/imap/command_data.rb +8 -11
- data/lib/net/imap/data_encoding.rb +101 -5
- data/lib/net/imap/errors.rb +1 -1
- data/lib/net/imap/flags.rb +105 -77
- data/lib/net/imap/response_data.rb +1077 -317
- data/lib/net/imap/response_parser.rb +66 -1
- data/lib/net/imap/sasl/saslprep.rb +55 -0
- data/lib/net/imap/sasl/saslprep_tables.rb +98 -0
- data/lib/net/imap/sasl/stringprep.rb +72 -0
- data/lib/net/imap/sasl/stringprep_tables.rb +153 -0
- data/lib/net/imap/sasl.rb +78 -0
- data/lib/net/imap.rb +1209 -283
- data/net-imap.gemspec +6 -4
- data/rakelib/rdoc.rake +70 -0
- data/rakelib/rfcs.rake +168 -0
- data/rakelib/saslprep.rake +30 -0
- data/rakelib/string_prep_tables_generator.rb +423 -0
- metadata +34 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7942e0dcda47ac417806fb03aec58b66b8bac3ccf51f53f2f7ac2c6a588e35af
|
4
|
+
data.tar.gz: 38530b6af862085a9d1ff3c59c6536152acc15bb7d925d8547cd4feebea42872
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5bd29fb28b2b3ed9b1a0589e38f8844c47e671006d50deb4462fe5e8cfd97ad923ef0af3114a094d31cbb5943ff0ac9a15e549e67ac08be68eb04a0915eae67f
|
7
|
+
data.tar.gz: 7562e44288caa3538203d415f4d0b1dc4a44a77428e40c4b44046b86fb26b1abc2a4226229f4f494e54f21240cc96dea9106afded971829ec5873f507adab7c9
|
data/.github/workflows/test.yml
CHANGED
@@ -7,7 +7,7 @@ jobs:
|
|
7
7
|
name: build (${{ matrix.ruby }} / ${{ matrix.os }})
|
8
8
|
strategy:
|
9
9
|
matrix:
|
10
|
-
ruby: [ head, '3.0', '2.7' ]
|
10
|
+
ruby: [ head, '3.1', '3.0', '2.7' ]
|
11
11
|
os: [ ubuntu-latest, macos-latest ]
|
12
12
|
experimental: [false]
|
13
13
|
include:
|
@@ -20,7 +20,7 @@ jobs:
|
|
20
20
|
runs-on: ${{ matrix.os }}
|
21
21
|
continue-on-error: ${{ matrix.experimental }}
|
22
22
|
steps:
|
23
|
-
- uses: actions/checkout@
|
23
|
+
- uses: actions/checkout@v3
|
24
24
|
- name: Set up Ruby
|
25
25
|
uses: ruby/setup-ruby@v1
|
26
26
|
with:
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
@@ -20,3 +20,65 @@ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
20
20
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
21
21
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
22
22
|
SUCH DAMAGE.
|
23
|
+
|
24
|
+
-------------------------------------------------------------------------
|
25
|
+
|
26
|
+
This software includes documentation which has been copied from the relevant
|
27
|
+
RFCs. The copied documentation is covered by the following licenses:
|
28
|
+
|
29
|
+
RFC 3501 (Editor: M. Crispin)
|
30
|
+
Full Copyright Statement
|
31
|
+
|
32
|
+
Copyright (C) The Internet Society (2003). All Rights Reserved.
|
33
|
+
|
34
|
+
This document and translations of it may be copied and furnished to
|
35
|
+
others, and derivative works that comment on or otherwise explain it
|
36
|
+
or assist in its implementation may be prepared, copied, published
|
37
|
+
and distributed, in whole or in part, without restriction of any
|
38
|
+
kind, provided that the above copyright notice and this paragraph are
|
39
|
+
included on all such copies and derivative works. However, this
|
40
|
+
document itself may not be modified in any way, such as by removing
|
41
|
+
the copyright notice or references to the Internet Society or other
|
42
|
+
Internet organizations, except as needed for the purpose of
|
43
|
+
developing Internet standards in which case the procedures for
|
44
|
+
copyrights defined in the Internet Standards process must be
|
45
|
+
followed, or as required to translate it into languages other than
|
46
|
+
English.
|
47
|
+
|
48
|
+
The limited permissions granted above are perpetual and will not be
|
49
|
+
revoked by the Internet Society or its successors or assigns. v This
|
50
|
+
document and the information contained herein is provided on an "AS
|
51
|
+
IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK
|
52
|
+
FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
53
|
+
LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL
|
54
|
+
NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY
|
55
|
+
OR FITNESS FOR A PARTICULAR PURPOSE.
|
56
|
+
|
57
|
+
|
58
|
+
RFC9051 (Editors: A. Melnikov, B. Leiba)
|
59
|
+
Copyright Notice
|
60
|
+
|
61
|
+
Copyright (c) 2021 IETF Trust and the persons identified as the
|
62
|
+
document authors. All rights reserved.
|
63
|
+
|
64
|
+
This document is subject to BCP 78 and the IETF Trust's Legal
|
65
|
+
Provisions Relating to IETF Documents
|
66
|
+
(https://trustee.ietf.org/license-info) in effect on the date of
|
67
|
+
publication of this document. Please review these documents
|
68
|
+
carefully, as they describe your rights and restrictions with respect
|
69
|
+
to this document. Code Components extracted from this document must
|
70
|
+
include Simplified BSD License text as described in Section 4.e of
|
71
|
+
the Trust Legal Provisions and are provided without warranty as
|
72
|
+
described in the Simplified BSD License.
|
73
|
+
|
74
|
+
This document may contain material from IETF Documents or IETF
|
75
|
+
Contributions published or made publicly available before November
|
76
|
+
10, 2008. The person(s) controlling the copyright in some of this
|
77
|
+
material may not have granted the IETF Trust the right to allow
|
78
|
+
modifications of such material outside the IETF Standards Process.
|
79
|
+
Without obtaining an adequate license from the person(s) controlling
|
80
|
+
the copyright in such materials, this document may not be modified
|
81
|
+
outside the IETF Standards Process, and derivative works of it may
|
82
|
+
not be created outside the IETF Standards Process, except to format
|
83
|
+
it for publication as an RFC or to translate it into languages other
|
84
|
+
than English.
|
data/README.md
CHANGED
@@ -51,11 +51,10 @@ imap.expunge
|
|
51
51
|
|
52
52
|
## Development
|
53
53
|
|
54
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
54
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
55
55
|
|
56
56
|
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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
57
57
|
|
58
58
|
## Contributing
|
59
59
|
|
60
60
|
Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/net-imap.
|
61
|
-
|
data/Rakefile
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
---
|
2
|
+
prelude: |
|
3
|
+
begin
|
4
|
+
require "mongo" # gem install mongo
|
5
|
+
require "idn" # gem install idn-ruby
|
6
|
+
rescue LoadError
|
7
|
+
warn "You must 'gem install mongo idn-ruby' for this benchmark."
|
8
|
+
raise
|
9
|
+
end
|
10
|
+
|
11
|
+
MStrPrep = Mongo::Auth::StringPrep
|
12
|
+
|
13
|
+
# this indirection will slow it down a little bit
|
14
|
+
def mongo_saslprep(string)
|
15
|
+
MStrPrep.prepare(string,
|
16
|
+
MStrPrep::Profiles::SASL::MAPPINGS,
|
17
|
+
MStrPrep::Profiles::SASL::PROHIBITED,
|
18
|
+
normalize: true,
|
19
|
+
bidi: true)
|
20
|
+
rescue Mongo::Error::FailedStringPrepValidation
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
$LOAD_PATH.unshift "./lib"
|
25
|
+
require "net/imap"
|
26
|
+
def net_imap_saslprep(string)
|
27
|
+
Net::IMAP::SASL::SASLprep.saslprep string, exception: false
|
28
|
+
end
|
29
|
+
|
30
|
+
def libidn_saslprep(string)
|
31
|
+
IDN::Stringprep.with_profile(string, "SASLprep")
|
32
|
+
rescue IDN::Stringprep::StringprepError
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
benchmark:
|
37
|
+
- net_imap_saslprep "I\u00ADX" # RFC example 1. IX
|
38
|
+
- net_imap_saslprep "user" # RFC example 2. user
|
39
|
+
- net_imap_saslprep "USER" # RFC example 3. user
|
40
|
+
- net_imap_saslprep "\u00aa" # RFC example 4. a
|
41
|
+
- net_imap_saslprep "\u2168" # RFC example 5. IX
|
42
|
+
- net_imap_saslprep "\u0007" # RFC example 6. Error - prohibited character
|
43
|
+
- net_imap_saslprep "\u0627\u0031" # RFC example 7. Error - bidirectional check
|
44
|
+
- net_imap_saslprep "I\u2000X" # map to space: I X
|
45
|
+
- net_imap_saslprep "a longer string, e.g. a password"
|
46
|
+
|
47
|
+
- libidn_saslprep "I\u00ADX" # RFC example 1. IX
|
48
|
+
- libidn_saslprep "user" # RFC example 2. user
|
49
|
+
- libidn_saslprep "USER" # RFC example 3. user
|
50
|
+
- libidn_saslprep "\u00aa" # RFC example 4. a
|
51
|
+
- libidn_saslprep "\u2168" # RFC example 5. IX
|
52
|
+
- libidn_saslprep "\u0007" # RFC example 6. Error - prohibited character
|
53
|
+
- libidn_saslprep "\u0627\u0031" # RFC example 7. Error - bidirectional check
|
54
|
+
- libidn_saslprep "I\u2000X" # map to space: I X
|
55
|
+
- libidn_saslprep "a longer string, e.g. a password"
|
56
|
+
|
57
|
+
- mongo_saslprep "I\u00ADX" # RFC example 1. IX
|
58
|
+
- mongo_saslprep "user" # RFC example 2. user
|
59
|
+
- mongo_saslprep "USER" # RFC example 3. user
|
60
|
+
- mongo_saslprep "\u00aa" # RFC example 4. a
|
61
|
+
- mongo_saslprep "\u2168" # RFC example 5. IX
|
62
|
+
- mongo_saslprep "\u0007" # RFC example 6. Error - prohibited character
|
63
|
+
- mongo_saslprep "\u0627\u0031" # RFC example 7. Error - bidirectional check
|
64
|
+
- mongo_saslprep "I\u2000X" # map to space: I X
|
65
|
+
- mongo_saslprep "a longer string, e.g. a password"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
prelude: |
|
2
|
+
require "json"
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
all_codepoints = (0..0x10ffff).map{_1.chr("UTF-8") rescue nil}.compact
|
6
|
+
|
7
|
+
rfc3454_tables = Dir["rfcs/rfc3454*.json"]
|
8
|
+
.first
|
9
|
+
.then{File.read _1}
|
10
|
+
.then{JSON.parse _1}
|
11
|
+
titles = rfc3454_tables.delete("titles")
|
12
|
+
|
13
|
+
sets = rfc3454_tables
|
14
|
+
.transform_values{|t|t.keys rescue t}
|
15
|
+
.transform_values{|table|
|
16
|
+
table
|
17
|
+
.map{_1.split(?-).map{|i|Integer i, 16}}
|
18
|
+
.flat_map{_2 ? (_1.._2).to_a : _1}
|
19
|
+
.to_set
|
20
|
+
}
|
21
|
+
|
22
|
+
TABLE_A1_SET = sets.fetch "A.1"
|
23
|
+
ASSIGNED_3_2 = /\p{AGE=3.2}/
|
24
|
+
UNASSIGNED_3_2 = /\P{AGE=3.2}/
|
25
|
+
TABLE_A1_REGEX = /(?-mix:[\u{0000}-\u{001f}\u{007f}-\u{00a0}\u{0340}-\u{0341}\u{06dd}\u{070f}\u{1680}\u{180e}\u{2000}-\u{200f}\u{2028}-\u{202f}\u{205f}-\u{2063}\u{206a}-\u{206f}\u{2ff0}-\u{2ffb}\u{3000}\u{e000}-\u{f8ff}\u{fdd0}-\u{fdef}\u{feff}\u{fff9}-\u{ffff}\u{1d173}-\u{1d17a}\u{1fffe}-\u{1ffff}\u{2fffe}-\u{2ffff}\u{3fffe}-\u{3ffff}\u{4fffe}-\u{4ffff}\u{5fffe}-\u{5ffff}\u{6fffe}-\u{6ffff}\u{7fffe}-\u{7ffff}\u{8fffe}-\u{8ffff}\u{9fffe}-\u{9ffff}\u{afffe}-\u{affff}\u{bfffe}-\u{bffff}\u{cfffe}-\u{cffff}\u{dfffe}-\u{dffff}\u{e0001}\u{e0020}-\u{e007f}\u{efffe}-\u{10ffff}])|(?-mix:\p{Cs})/.freeze
|
26
|
+
|
27
|
+
benchmark:
|
28
|
+
|
29
|
+
# matches A.1
|
30
|
+
- script: "all_codepoints.grep(TABLE_A1_SET)"
|
31
|
+
- script: "all_codepoints.grep(TABLE_A1_REGEX)"
|
32
|
+
- script: "all_codepoints.grep(UNASSIGNED_3_2)"
|
33
|
+
- script: "all_codepoints.grep_v(ASSIGNED_3_2)"
|
34
|
+
|
35
|
+
# doesn't match A.1
|
36
|
+
- script: "all_codepoints.grep_v(TABLE_A1_SET)"
|
37
|
+
- script: "all_codepoints.grep_v(TABLE_A1_REGEX)"
|
38
|
+
- script: "all_codepoints.grep_v(UNASSIGNED_3_2)"
|
39
|
+
- script: "all_codepoints.grep(ASSIGNED_3_2)"
|
data/docs/styles.css
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
/* this is a work in progress. :) */
|
2
|
+
|
3
|
+
main .method-header {
|
4
|
+
background: rgba(27,31,35,0.05);
|
5
|
+
border: 1px solid #6C8C22;
|
6
|
+
padding: 0.5em;
|
7
|
+
border-radius: 4px;
|
8
|
+
/* padding: 0 0.5em; */
|
9
|
+
/* border-width: 0 1px; */
|
10
|
+
/* border-color: #6C8C22; */
|
11
|
+
/* border-style: solid; */
|
12
|
+
}
|
13
|
+
|
14
|
+
main .method-description, main .aliases {
|
15
|
+
padding-left: 1em;
|
16
|
+
}
|
17
|
+
|
18
|
+
body {
|
19
|
+
/*
|
20
|
+
* The default (300) can be too low contrast. Also, many fonts don't
|
21
|
+
* distinguish between 300->400, so <em>...</em> had no effect.
|
22
|
+
*/
|
23
|
+
font-weight: 400;
|
24
|
+
}
|
25
|
+
|
26
|
+
@media only screen and (min-width: 600px) {
|
27
|
+
nav {
|
28
|
+
height: 100%;
|
29
|
+
position: fixed;
|
30
|
+
overflow-y: scroll;
|
31
|
+
}
|
32
|
+
|
33
|
+
nav #class-metadata {
|
34
|
+
margin-bottom: 5em;
|
35
|
+
}
|
36
|
+
}
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "digest/md5"
|
4
|
-
|
5
3
|
# Authenticator for the "+CRAM-MD5+" SASL mechanism, specified in
|
6
4
|
# RFC2195[https://tools.ietf.org/html/rfc2195]. See Net::IMAP#authenticate.
|
7
5
|
#
|
@@ -23,7 +21,11 @@ class Net::IMAP::CramMD5Authenticator
|
|
23
21
|
|
24
22
|
private
|
25
23
|
|
26
|
-
def initialize(user, password)
|
24
|
+
def initialize(user, password, warn_deprecation: true, **_ignored)
|
25
|
+
if warn_deprecation
|
26
|
+
warn "WARNING: CRAM-MD5 mechanism is deprecated." # TODO: recommend SCRAM
|
27
|
+
end
|
28
|
+
require "digest/md5"
|
27
29
|
@user = user
|
28
30
|
@password = password
|
29
31
|
end
|
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "digest/md5"
|
4
|
-
require "strscan"
|
5
|
-
|
6
3
|
# Net::IMAP authenticator for the "`DIGEST-MD5`" SASL mechanism type, specified
|
7
4
|
# in RFC2831(https://tools.ietf.org/html/rfc2831). See Net::IMAP#authenticate.
|
8
5
|
#
|
@@ -29,8 +26,8 @@ class Net::IMAP::DigestMD5Authenticator
|
|
29
26
|
sparams[k] = v
|
30
27
|
end
|
31
28
|
|
32
|
-
raise DataFormatError, "Bad Challenge: '#{challenge}'" unless c.
|
33
|
-
raise Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth")
|
29
|
+
raise Net::IMAP::DataFormatError, "Bad Challenge: '#{challenge}'" unless c.eos?
|
30
|
+
raise Net::IMAP::Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth")
|
34
31
|
|
35
32
|
response = {
|
36
33
|
:nonce => sparams['nonce'],
|
@@ -77,11 +74,18 @@ class Net::IMAP::DigestMD5Authenticator
|
|
77
74
|
end
|
78
75
|
end
|
79
76
|
|
80
|
-
def initialize(user, password, authname = nil)
|
77
|
+
def initialize(user, password, authname = nil, warn_deprecation: true)
|
78
|
+
if warn_deprecation
|
79
|
+
warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331."
|
80
|
+
# TODO: recommend SCRAM instead.
|
81
|
+
end
|
82
|
+
require "digest/md5"
|
83
|
+
require "strscan"
|
81
84
|
@user, @password, @authname = user, password, authname
|
82
85
|
@nc, @stage = {}, STAGE_ONE
|
83
86
|
end
|
84
87
|
|
88
|
+
|
85
89
|
private
|
86
90
|
|
87
91
|
STAGE_ONE = :stage_one
|
@@ -100,7 +104,7 @@ class Net::IMAP::DigestMD5Authenticator
|
|
100
104
|
def qdval(k, v)
|
101
105
|
return if k.nil? or v.nil?
|
102
106
|
if %w"username authzid realm nonce cnonce digest-uri qop".include? k
|
103
|
-
v.gsub
|
107
|
+
v = v.gsub(/([\\"])/, "\\\1")
|
104
108
|
return '%s="%s"' % [k, v]
|
105
109
|
else
|
106
110
|
return '%s=%s' % [k, v]
|
@@ -33,7 +33,10 @@ class Net::IMAP::LoginAuthenticator
|
|
33
33
|
STATE_USER = :USER
|
34
34
|
STATE_PASSWORD = :PASSWORD
|
35
35
|
|
36
|
-
def initialize(user, password)
|
36
|
+
def initialize(user, password, warn_deprecation: true, **_ignored)
|
37
|
+
if warn_deprecation
|
38
|
+
warn "WARNING: LOGIN SASL mechanism is deprecated. Use PLAIN instead."
|
39
|
+
end
|
37
40
|
@user = user
|
38
41
|
@password = password
|
39
42
|
@state = STATE_USER
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Net::IMAP::XOauth2Authenticator
|
4
|
+
def process(_data)
|
5
|
+
build_oauth2_string(@user, @oauth2_token)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def initialize(user, oauth2_token)
|
11
|
+
@user = user
|
12
|
+
@oauth2_token = oauth2_token
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_oauth2_string(user, oauth2_token)
|
16
|
+
format("user=%s\1auth=Bearer %s\1\1", user, oauth2_token)
|
17
|
+
end
|
18
|
+
|
19
|
+
Net::IMAP.add_authenticator 'XOAUTH2', self
|
20
|
+
end
|
@@ -3,29 +3,51 @@
|
|
3
3
|
# Registry for SASL authenticators used by Net::IMAP.
|
4
4
|
module Net::IMAP::Authenticators
|
5
5
|
|
6
|
-
# Adds an authenticator for
|
6
|
+
# Adds an authenticator for Net::IMAP#authenticate to use. +mechanism+ is the
|
7
7
|
# {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
8
|
+
# implemented by +authenticator+ (for instance, <tt>"PLAIN"</tt>).
|
9
|
+
#
|
10
|
+
# The +authenticator+ must respond to +#new+ (or #call), receiving the
|
11
|
+
# authenticator configuration and return a configured authentication session.
|
12
|
+
# The authenticator session must respond to +#process+, receiving the server's
|
13
|
+
# challenge and returning the client's response.
|
13
14
|
#
|
14
|
-
#
|
15
|
-
#
|
15
|
+
# See PlainAuthenticator, XOauth2Authenticator, and DigestMD5Authenticator for
|
16
|
+
# examples.
|
16
17
|
def add_authenticator(auth_type, authenticator)
|
17
18
|
authenticators[auth_type] = authenticator
|
18
19
|
end
|
19
20
|
|
20
|
-
#
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
# :call-seq:
|
22
|
+
# authenticator(mechanism, ...) -> authenticator
|
23
|
+
# authenticator(mech, *creds, **props) {|prop, auth| val } -> authenticator
|
24
|
+
# authenticator(mechanism, authnid, creds, authzid=nil) -> authenticator
|
25
|
+
# authenticator(mechanism, **properties) -> authenticator
|
26
|
+
# authenticator(mechanism) {|propname, authctx| value } -> authenticator
|
27
|
+
#
|
28
|
+
# Builds a new authentication session context for +mechanism+.
|
29
|
+
#
|
30
|
+
# [Note]
|
31
|
+
# This method is intended for internal use by connection protocol code only.
|
32
|
+
# Protocol client users should see refer to their client's documentation,
|
33
|
+
# e.g. Net::IMAP#authenticate for Net::IMAP.
|
34
|
+
#
|
35
|
+
# The call signatures documented for this method are recommendations for
|
36
|
+
# authenticator implementors. All arguments (other than +mechanism+) are
|
37
|
+
# forwarded to the registered authenticator's +#new+ (or +#call+) method, and
|
38
|
+
# each authenticator must document its own arguments.
|
39
|
+
#
|
40
|
+
# The returned object represents a single authentication exchange and <em>must
|
41
|
+
# not</em> be reused for multiple authentication attempts.
|
42
|
+
def authenticator(mechanism, *authargs, **properties, &callback)
|
43
|
+
authenticator = authenticators.fetch(mechanism.upcase) do
|
44
|
+
raise ArgumentError, 'unknown auth type - "%s"' % mechanism
|
45
|
+
end
|
46
|
+
if authenticator.respond_to?(:new)
|
47
|
+
authenticator.new(*authargs, **properties, &callback)
|
48
|
+
else
|
49
|
+
authenticator.call(*authargs, **properties, &callback)
|
27
50
|
end
|
28
|
-
authenticators[auth_type].new(*args)
|
29
51
|
end
|
30
52
|
|
31
53
|
private
|
@@ -38,7 +60,9 @@ end
|
|
38
60
|
|
39
61
|
Net::IMAP.extend Net::IMAP::Authenticators
|
40
62
|
|
41
|
-
require_relative "authenticators/login"
|
42
63
|
require_relative "authenticators/plain"
|
64
|
+
|
65
|
+
require_relative "authenticators/login"
|
43
66
|
require_relative "authenticators/cram_md5"
|
44
67
|
require_relative "authenticators/digest_md5"
|
68
|
+
require_relative "authenticators/xoauth2"
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "date"
|
4
|
+
|
3
5
|
require_relative "errors"
|
4
6
|
|
5
7
|
module Net
|
@@ -21,7 +23,7 @@ module Net
|
|
21
23
|
validate_data(i)
|
22
24
|
end
|
23
25
|
end
|
24
|
-
when Time
|
26
|
+
when Time, Date, DateTime
|
25
27
|
when Symbol
|
26
28
|
else
|
27
29
|
data.validate
|
@@ -38,7 +40,9 @@ module Net
|
|
38
40
|
send_number_data(data)
|
39
41
|
when Array
|
40
42
|
send_list_data(data, tag)
|
41
|
-
when
|
43
|
+
when Date
|
44
|
+
send_date_data(data)
|
45
|
+
when Time, DateTime
|
42
46
|
send_time_data(data)
|
43
47
|
when Symbol
|
44
48
|
send_symbol_data(data)
|
@@ -101,15 +105,8 @@ module Net
|
|
101
105
|
put_string(")")
|
102
106
|
end
|
103
107
|
|
104
|
-
|
105
|
-
|
106
|
-
def send_time_data(time)
|
107
|
-
t = time.dup.gmtime
|
108
|
-
s = format('"%2d-%3s-%4d %02d:%02d:%02d +0000"',
|
109
|
-
t.day, DATE_MONTH[t.month - 1], t.year,
|
110
|
-
t.hour, t.min, t.sec)
|
111
|
-
put_string(s)
|
112
|
-
end
|
108
|
+
def send_date_data(date) put_string Net::IMAP.encode_date(date) end
|
109
|
+
def send_time_data(time) put_string Net::IMAP.encode_time(time) end
|
113
110
|
|
114
111
|
def send_symbol_data(symbol)
|
115
112
|
put_string("\\" + symbol.to_s)
|
@@ -1,10 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "date"
|
4
|
+
|
3
5
|
require_relative "errors"
|
4
6
|
|
5
7
|
module Net
|
6
8
|
class IMAP < Protocol
|
7
9
|
|
10
|
+
# strftime/strptime format for an IMAP4 +date+, excluding optional dquotes.
|
11
|
+
# Use via the encode_date and decode_date methods.
|
12
|
+
#
|
13
|
+
# date = date-text / DQUOTE date-text DQUOTE
|
14
|
+
# date-text = date-day "-" date-month "-" date-year
|
15
|
+
#
|
16
|
+
# date-day = 1*2DIGIT
|
17
|
+
# ; Day of month
|
18
|
+
# date-month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" /
|
19
|
+
# "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
|
20
|
+
# date-year = 4DIGIT
|
21
|
+
STRFDATE = "%d-%b-%Y"
|
22
|
+
|
23
|
+
# strftime/strptime format for an IMAP4 +date-time+, including dquotes.
|
24
|
+
# See the encode_datetime and decode_datetime methods.
|
25
|
+
#
|
26
|
+
# date-time = DQUOTE date-day-fixed "-" date-month "-" date-year
|
27
|
+
# SP time SP zone DQUOTE
|
28
|
+
#
|
29
|
+
# date-day-fixed = (SP DIGIT) / 2DIGIT
|
30
|
+
# ; Fixed-format version of date-day
|
31
|
+
# date-month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" /
|
32
|
+
# "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
|
33
|
+
# date-year = 4DIGIT
|
34
|
+
# time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
|
35
|
+
# ; Hours minutes seconds
|
36
|
+
# zone = ("+" / "-") 4DIGIT
|
37
|
+
# ; Signed four-digit value of hhmm representing
|
38
|
+
# ; hours and minutes east of Greenwich (that is,
|
39
|
+
# ; the amount that the given time differs from
|
40
|
+
# ; Universal Time). Subtracting the timezone
|
41
|
+
# ; from the given time will give the UT form.
|
42
|
+
# ; The Universal Time zone is "+0000".
|
43
|
+
#
|
44
|
+
# Note that Time.strptime <tt>"%d"</tt> flexibly parses either space or zero
|
45
|
+
# padding. However, the DQUOTEs are *not* optional.
|
46
|
+
STRFTIME = '"%d-%b-%Y %H:%M:%S %z"'
|
47
|
+
|
8
48
|
# Decode a string from modified UTF-7 format to UTF-8.
|
9
49
|
#
|
10
50
|
# UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a
|
@@ -35,14 +75,70 @@ module Net
|
|
35
75
|
}.force_encoding("ASCII-8BIT")
|
36
76
|
end
|
37
77
|
|
38
|
-
# Formats +time+ as an
|
39
|
-
def self.
|
40
|
-
|
78
|
+
# Formats +time+ as an IMAP4 date.
|
79
|
+
def self.encode_date(date)
|
80
|
+
date.to_date.strftime STRFDATE
|
81
|
+
end
|
82
|
+
|
83
|
+
# :call-seq: decode_date(string) -> Date
|
84
|
+
#
|
85
|
+
# Decodes +string+ as an IMAP formatted "date".
|
86
|
+
#
|
87
|
+
# Double quotes are optional. Day of month may be padded with zero or
|
88
|
+
# space. See STRFDATE.
|
89
|
+
def self.decode_date(string)
|
90
|
+
string = string.delete_prefix('"').delete_suffix('"')
|
91
|
+
Date.strptime(string, STRFDATE)
|
92
|
+
end
|
93
|
+
|
94
|
+
# :call-seq: encode_datetime(time) -> string
|
95
|
+
#
|
96
|
+
# Formats +time+ as an IMAP4 date-time.
|
97
|
+
def self.encode_datetime(time)
|
98
|
+
time.to_datetime.strftime STRFTIME
|
41
99
|
end
|
42
100
|
|
43
|
-
#
|
101
|
+
# :call-seq: decode_datetime(string) -> DateTime
|
102
|
+
#
|
103
|
+
# Decodes +string+ as an IMAP4 formatted "date-time".
|
104
|
+
#
|
105
|
+
# Note that double quotes are not optional. See STRFTIME.
|
106
|
+
def self.decode_datetime(string)
|
107
|
+
DateTime.strptime(string, STRFTIME)
|
108
|
+
end
|
109
|
+
|
110
|
+
# :call-seq: decode_time(string) -> Time
|
111
|
+
#
|
112
|
+
# Decodes +string+ as an IMAP4 formatted "date-time".
|
113
|
+
#
|
114
|
+
# Same as +decode_datetime+, but returning a Time instead.
|
115
|
+
def self.decode_time(string)
|
116
|
+
decode_datetime(string).to_time
|
117
|
+
end
|
118
|
+
|
119
|
+
class << self
|
120
|
+
alias encode_time encode_datetime
|
121
|
+
alias format_date encode_date
|
122
|
+
alias format_time encode_time
|
123
|
+
alias parse_date decode_date
|
124
|
+
alias parse_datetime decode_datetime
|
125
|
+
alias parse_time decode_time
|
126
|
+
|
127
|
+
# alias format_datetime encode_datetime # n.b. this is overridden below...
|
128
|
+
end
|
129
|
+
|
130
|
+
# DEPRECATED:: The original version returned incorrectly formatted strings.
|
131
|
+
# Strings returned by encode_datetime or format_time use the
|
132
|
+
# correct IMAP4rev1 syntax for "date-time".
|
133
|
+
#
|
134
|
+
# This invalid format has been temporarily retained for backward
|
135
|
+
# compatibility. A future release will change this method to return the
|
136
|
+
# correct format.
|
44
137
|
def self.format_datetime(time)
|
45
|
-
|
138
|
+
warn("#{self}.format_datetime incorrectly formats IMAP date-time. " \
|
139
|
+
"Convert to #{self}.encode_datetime or #{self}.format_time instead.",
|
140
|
+
uplevel: 1, category: :deprecated)
|
141
|
+
time.strftime("%d-%b-%Y %H:%M %z")
|
46
142
|
end
|
47
143
|
|
48
144
|
# Common validators of number and nz_number types
|
data/lib/net/imap/errors.rb
CHANGED