net-imap 0.3.7 → 0.5.6

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/COPYING +56 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +3 -22
  6. data/README.md +25 -8
  7. data/Rakefile +0 -7
  8. data/docs/styles.css +72 -23
  9. data/lib/net/imap/authenticators.rb +26 -57
  10. data/lib/net/imap/command_data.rb +74 -54
  11. data/lib/net/imap/config/attr_accessors.rb +75 -0
  12. data/lib/net/imap/config/attr_inheritance.rb +90 -0
  13. data/lib/net/imap/config/attr_type_coercion.rb +61 -0
  14. data/lib/net/imap/config.rb +470 -0
  15. data/lib/net/imap/data_encoding.rb +18 -6
  16. data/lib/net/imap/data_lite.rb +226 -0
  17. data/lib/net/imap/deprecated_client_options.rb +142 -0
  18. data/lib/net/imap/errors.rb +27 -1
  19. data/lib/net/imap/esearch_result.rb +180 -0
  20. data/lib/net/imap/fetch_data.rb +597 -0
  21. data/lib/net/imap/flags.rb +1 -1
  22. data/lib/net/imap/response_data.rb +250 -440
  23. data/lib/net/imap/response_parser/parser_utils.rb +245 -0
  24. data/lib/net/imap/response_parser.rb +1867 -1184
  25. data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
  26. data/lib/net/imap/sasl/authentication_exchange.rb +139 -0
  27. data/lib/net/imap/sasl/authenticators.rb +122 -0
  28. data/lib/net/imap/sasl/client_adapter.rb +123 -0
  29. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +24 -14
  30. data/lib/net/imap/sasl/digest_md5_authenticator.rb +342 -0
  31. data/lib/net/imap/sasl/external_authenticator.rb +83 -0
  32. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  33. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +28 -18
  34. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
  35. data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
  36. data/lib/net/imap/sasl/protocol_adapters.rb +101 -0
  37. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  38. data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
  39. data/lib/net/imap/sasl/stringprep.rb +6 -66
  40. data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
  41. data/lib/net/imap/sasl.rb +148 -44
  42. data/lib/net/imap/sasl_adapter.rb +20 -0
  43. data/lib/net/imap/search_result.rb +146 -0
  44. data/lib/net/imap/sequence_set.rb +1565 -0
  45. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  46. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  47. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  48. data/lib/net/imap/stringprep/tables.rb +146 -0
  49. data/lib/net/imap/stringprep/trace.rb +85 -0
  50. data/lib/net/imap/stringprep.rb +159 -0
  51. data/lib/net/imap/uidplus_data.rb +244 -0
  52. data/lib/net/imap/vanished_data.rb +56 -0
  53. data/lib/net/imap.rb +2090 -823
  54. data/net-imap.gemspec +7 -8
  55. data/rakelib/benchmarks.rake +91 -0
  56. data/rakelib/rfcs.rake +2 -0
  57. data/rakelib/saslprep.rake +4 -4
  58. data/rakelib/string_prep_tables_generator.rb +84 -60
  59. data/sample/net-imap.rb +167 -0
  60. metadata +45 -49
  61. data/.github/dependabot.yml +0 -6
  62. data/.github/workflows/test.yml +0 -38
  63. data/.gitignore +0 -10
  64. data/benchmarks/stringprep.yml +0 -65
  65. data/benchmarks/table-regexps.yml +0 -39
  66. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  67. data/lib/net/imap/authenticators/plain.rb +0 -41
  68. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  69. data/lib/net/imap/sasl/saslprep.rb +0 -55
  70. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  71. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
data/lib/net/imap/sasl.rb CHANGED
@@ -5,13 +5,12 @@ module Net
5
5
 
6
6
  # Pluggable authentication mechanisms for protocols which support SASL
7
7
  # (Simple Authentication and Security Layer), such as IMAP4, SMTP, LDAP, and
8
- # XMPP. {RFC-4422}[https://tools.ietf.org/html/rfc4422] specifies the
9
- # common SASL framework and the +EXTERNAL+ mechanism, and the
10
- # {SASL mechanism registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
11
- # lists the specification for others.
12
- #
13
- # "SASL is conceptually a framework that provides an abstraction layer
14
- # between protocols and mechanisms as illustrated in the following diagram."
8
+ # XMPP. {RFC-4422}[https://www.rfc-editor.org/rfc/rfc4422] specifies the
9
+ # common \SASL framework:
10
+ # >>>
11
+ # SASL is conceptually a framework that provides an abstraction layer
12
+ # between protocols and mechanisms as illustrated in the following
13
+ # diagram.
15
14
  #
16
15
  # SMTP LDAP XMPP Other protocols ...
17
16
  # \ | | /
@@ -21,58 +20,163 @@ module Net
21
20
  # / | | \
22
21
  # EXTERNAL GSSAPI PLAIN Other mechanisms ...
23
22
  #
23
+ # Net::IMAP uses SASL via the Net::IMAP#authenticate method.
24
+ #
25
+ # == Mechanisms
26
+ #
27
+ # Each mechanism has different properties and requirements. Please consult
28
+ # the documentation for the specific mechanisms you are using:
29
+ #
30
+ # +ANONYMOUS+::
31
+ # See AnonymousAuthenticator.
32
+ #
33
+ # Allows the user to gain access to public services or resources without
34
+ # authenticating or disclosing an identity.
35
+ #
36
+ # +EXTERNAL+::
37
+ # See ExternalAuthenticator.
38
+ #
39
+ # Authenticates using already established credentials, such as a TLS
40
+ # certificate or IPSec.
41
+ #
42
+ # +OAUTHBEARER+::
43
+ # See OAuthBearerAuthenticator.
44
+ #
45
+ # Login using an OAuth2 Bearer token. This is the standard mechanism
46
+ # for using OAuth2 with \SASL, but it is not yet deployed as widely as
47
+ # +XOAUTH2+.
48
+ #
49
+ # +PLAIN+::
50
+ # See PlainAuthenticator.
51
+ #
52
+ # Login using clear-text username and password.
53
+ #
54
+ # +SCRAM-SHA-1+::
55
+ # +SCRAM-SHA-256+::
56
+ # See ScramAuthenticator.
57
+ #
58
+ # Login by username and password. The password is not sent to the
59
+ # server but is used in a salted challenge/response exchange.
60
+ # +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
61
+ # Net::IMAP::SASL. New authenticators can easily be added for any other
62
+ # <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
63
+ # OpenSSL::Digest.
64
+ #
65
+ # +XOAUTH2+::
66
+ # See XOAuth2Authenticator.
67
+ #
68
+ # Login using a username and an OAuth2 access token. Non-standard and
69
+ # obsoleted by +OAUTHBEARER+, but widely supported.
70
+ #
71
+ # See the {SASL mechanism
72
+ # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
73
+ # for a list of all SASL mechanisms and their specifications. To register
74
+ # new authenticators, see Authenticators.
75
+ #
76
+ # === Deprecated mechanisms
77
+ #
78
+ # <em>Obsolete mechanisms should be avoided, but are still available for
79
+ # backwards compatibility.</em>
80
+ #
81
+ # >>>
82
+ # For +DIGEST-MD5+ see DigestMD5Authenticator.
83
+ #
84
+ # For +LOGIN+, see LoginAuthenticator.
85
+ #
86
+ # For +CRAM-MD5+, see CramMD5Authenticator.
87
+ #
88
+ # <em>Using a deprecated mechanism will print a warning.</em>
89
+ #
24
90
  module SASL
91
+ # Exception class for any client error detected during the authentication
92
+ # exchange.
93
+ #
94
+ # When the _server_ reports an authentication failure, it will respond
95
+ # with a protocol specific error instead, e.g: +BAD+ or +NO+ in IMAP.
96
+ #
97
+ # When the client encounters any error, it *must* consider the
98
+ # authentication exchange to be unsuccessful and it might need to drop the
99
+ # connection. For example, if the server reports that the authentication
100
+ # exchange was successful or the protocol does not allow additional
101
+ # authentication attempts.
102
+ Error = Class.new(StandardError)
25
103
 
26
- # autoloading to avoid loading all of the regexps when they aren't used.
104
+ # Indicates an authentication exchange that will be or has been canceled
105
+ # by the client, not due to any error or failure during processing.
106
+ AuthenticationCanceled = Class.new(Error)
27
107
 
28
- autoload :StringPrep, File.expand_path("sasl/stringprep", __dir__)
29
- autoload :SASLprep, File.expand_path("#{__dir__}/sasl/saslprep", __dir__)
108
+ # Indicates an error when processing a server challenge, e.g: an invalid
109
+ # or unparsable challenge. An underlying exception may be available as
110
+ # the exception's #cause.
111
+ AuthenticationError = Class.new(Error)
30
112
 
31
- # ArgumentError raised when +string+ is invalid for the stringprep
32
- # +profile+.
33
- class StringPrepError < ArgumentError
34
- attr_reader :string, :profile
113
+ # Indicates that authentication cannot proceed because one of the server's
114
+ # messages has not passed integrity checks.
115
+ AuthenticationFailed = Class.new(Error)
35
116
 
36
- def initialize(*args, string: nil, profile: nil)
37
- @string = -string.to_str unless string.nil?
38
- @profile = -profile.to_str unless profile.nil?
39
- super(*args)
40
- end
41
- end
117
+ # Indicates that authentication cannot proceed because the server ended
118
+ # authentication prematurely.
119
+ class AuthenticationIncomplete < AuthenticationFailed
120
+ # The success response from the server
121
+ attr_reader :response
42
122
 
43
- # StringPrepError raised when +string+ contains a codepoint prohibited by
44
- # +table+.
45
- class ProhibitedCodepoint < StringPrepError
46
- attr_reader :table
47
-
48
- def initialize(table, *args, **kwargs)
49
- @table = -table.to_str
50
- details = (title = StringPrep::TABLE_TITLES[table]) ?
51
- "%s [%s]" % [title, table] : table
52
- message = "String contains a prohibited codepoint: %s" % [details]
53
- super(message, *args, **kwargs)
123
+ def initialize(response, message = "authentication ended prematurely")
124
+ super(message)
125
+ @response = response
54
126
  end
55
127
  end
56
128
 
57
- # StringPrepError raised when +string+ contains bidirectional characters
58
- # which violate the StringPrep requirements.
59
- class BidiStringError < StringPrepError
129
+ # autoloading to avoid loading all of the regexps when they aren't used.
130
+ sasl_stringprep_rb = File.expand_path("sasl/stringprep", __dir__)
131
+ autoload :StringPrep, sasl_stringprep_rb
132
+ autoload :SASLprep, sasl_stringprep_rb
133
+ autoload :StringPrepError, sasl_stringprep_rb
134
+ autoload :ProhibitedCodepoint, sasl_stringprep_rb
135
+ autoload :BidiStringError, sasl_stringprep_rb
136
+
137
+ sasl_dir = File.expand_path("sasl", __dir__)
138
+ autoload :AuthenticationExchange, "#{sasl_dir}/authentication_exchange"
139
+ autoload :ClientAdapter, "#{sasl_dir}/client_adapter"
140
+ autoload :ProtocolAdapters, "#{sasl_dir}/protocol_adapters"
141
+
142
+ autoload :Authenticators, "#{sasl_dir}/authenticators"
143
+ autoload :GS2Header, "#{sasl_dir}/gs2_header"
144
+ autoload :ScramAlgorithm, "#{sasl_dir}/scram_algorithm"
145
+
146
+ autoload :AnonymousAuthenticator, "#{sasl_dir}/anonymous_authenticator"
147
+ autoload :ExternalAuthenticator, "#{sasl_dir}/external_authenticator"
148
+ autoload :OAuthBearerAuthenticator, "#{sasl_dir}/oauthbearer_authenticator"
149
+ autoload :PlainAuthenticator, "#{sasl_dir}/plain_authenticator"
150
+ autoload :ScramAuthenticator, "#{sasl_dir}/scram_authenticator"
151
+ autoload :ScramSHA1Authenticator, "#{sasl_dir}/scram_authenticator"
152
+ autoload :ScramSHA256Authenticator, "#{sasl_dir}/scram_authenticator"
153
+ autoload :XOAuth2Authenticator, "#{sasl_dir}/xoauth2_authenticator"
154
+
155
+ autoload :CramMD5Authenticator, "#{sasl_dir}/cram_md5_authenticator"
156
+ autoload :DigestMD5Authenticator, "#{sasl_dir}/digest_md5_authenticator"
157
+ autoload :LoginAuthenticator, "#{sasl_dir}/login_authenticator"
158
+
159
+ # Returns the default global SASL::Authenticators instance.
160
+ def self.authenticators; @authenticators ||= Authenticators.new end
161
+
162
+ # Creates a new SASL authenticator, using SASL::Authenticators#new.
163
+ #
164
+ # +registry+ defaults to SASL.authenticators. All other arguments are
165
+ # forwarded to to <tt>registry.new</tt>.
166
+ def self.authenticator(*args, registry: authenticators, **kwargs, &block)
167
+ registry.new(*args, **kwargs, &block)
60
168
  end
61
169
 
62
- #--
63
- # We could just extend SASLprep module directly. It's done this way so
64
- # SASLprep can be lazily autoloaded. Most users won't need it.
65
- #++
66
- extend self
170
+ # Delegates to ::authenticators. See Authenticators#add_authenticator.
171
+ def self.add_authenticator(...) authenticators.add_authenticator(...) end
172
+
173
+ module_function
67
174
 
68
- # See SASLprep#saslprep.
175
+ # See Net::IMAP::StringPrep::SASLprep#saslprep.
69
176
  def saslprep(string, **opts)
70
- SASLprep.saslprep(string, **opts)
177
+ Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
71
178
  end
72
179
 
73
180
  end
74
181
  end
75
-
76
182
  end
77
-
78
- Net::IMAP.extend Net::IMAP::SASL
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+
6
+ # Experimental
7
+ class SASLAdapter < SASL::ClientAdapter
8
+ include SASL::ProtocolAdapters::IMAP
9
+
10
+ RESPONSE_ERRORS = [NoResponseError, BadResponseError, ByeResponseError]
11
+ .freeze
12
+
13
+ def response_errors; RESPONSE_ERRORS end
14
+ def sasl_ir_capable?; client.capable?("SASL-IR") end
15
+ def drop_connection; client.logout! end
16
+ def drop_connection!; client.disconnect end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+
6
+ # An array of sequence numbers returned by Net::IMAP#search, or unique
7
+ # identifiers returned by Net::IMAP#uid_search.
8
+ #
9
+ # For backward compatibility, SearchResult inherits from Array.
10
+ class SearchResult < Array
11
+
12
+ # Returns a SearchResult populated with the given +seq_nums+.
13
+ #
14
+ # Net::IMAP::SearchResult[1, 3, 5, modseq: 9]
15
+ # # => Net::IMAP::SearchResult[1, 3, 5, modseq: 9]
16
+ def self.[](*seq_nums, modseq: nil)
17
+ new(seq_nums, modseq: modseq)
18
+ end
19
+
20
+ # A modification sequence number, as described by the +CONDSTORE+
21
+ # extension in {[RFC7162
22
+ # §3.1.6]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.6].
23
+ attr_reader :modseq
24
+
25
+ # Returns a SearchResult populated with the given +seq_nums+.
26
+ #
27
+ # Net::IMAP::SearchResult.new([1, 3, 5], modseq: 9)
28
+ # # => Net::IMAP::SearchResult[1, 3, 5, modseq: 9]
29
+ def initialize(seq_nums, modseq: nil)
30
+ super(seq_nums.to_ary.map { Integer _1 })
31
+ @modseq = Integer modseq if modseq
32
+ end
33
+
34
+ # Returns whether +other+ is a SearchResult with the same values and the
35
+ # same #modseq. The order of numbers is irrelevant.
36
+ #
37
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
38
+ # Net::IMAP::SearchResult[123, 456, modseq: 789]
39
+ # # => true
40
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
41
+ # Net::IMAP::SearchResult[456, 123, modseq: 789]
42
+ # # => true
43
+ #
44
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
45
+ # Net::IMAP::SearchResult[987, 654, modseq: 789]
46
+ # # => false
47
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
48
+ # Net::IMAP::SearchResult[1, 2, 3, modseq: 9999]
49
+ # # => false
50
+ #
51
+ # SearchResult can be compared directly with Array, if #modseq is nil and
52
+ # the array is sorted.
53
+ #
54
+ # Net::IMAP::SearchResult[9, 8, 6, 4, 1] == [1, 4, 6, 8, 9] # => true
55
+ # Net::IMAP::SearchResult[3, 5, 7, modseq: 99] == [3, 5, 7] # => false
56
+ #
57
+ # Note that Array#== does require matching order and ignores #modseq.
58
+ #
59
+ # [9, 8, 6, 4, 1] == Net::IMAP::SearchResult[1, 4, 6, 8, 9] # => false
60
+ # [3, 5, 7] == Net::IMAP::SearchResult[3, 5, 7, modseq: 99] # => true
61
+ #
62
+ def ==(other)
63
+ (modseq ?
64
+ other.is_a?(self.class) && modseq == other.modseq :
65
+ other.is_a?(Array)) &&
66
+ size == other.size &&
67
+ sort == other.sort
68
+ end
69
+
70
+ # Hash equality. Unlike #==, order will be taken into account.
71
+ def hash
72
+ return super if modseq.nil?
73
+ [super, self.class, modseq].hash
74
+ end
75
+
76
+ # Hash equality. Unlike #==, order will be taken into account.
77
+ def eql?(other)
78
+ return super if modseq.nil?
79
+ self.class == other.class && hash == other.hash
80
+ end
81
+
82
+ # Returns a string that represents the SearchResult.
83
+ #
84
+ # Net::IMAP::SearchResult[123, 456, 789].inspect
85
+ # # => "[123, 456, 789]"
86
+ #
87
+ # Net::IMAP::SearchResult[543, 210, 678, modseq: 2048].inspect
88
+ # # => "Net::IMAP::SearchResult[543, 210, 678, modseq: 2048]"
89
+ #
90
+ def inspect
91
+ return super if modseq.nil?
92
+ "%s[%s, modseq: %p]" % [self.class, join(", "), modseq]
93
+ end
94
+
95
+ # Returns a string that follows the formal \IMAP syntax.
96
+ #
97
+ # data = Net::IMAP::SearchResult[2, 8, 32, 128, 256, 512]
98
+ # data.to_s # => "* SEARCH 2 8 32 128 256 512"
99
+ # data.to_s("SEARCH") # => "* SEARCH 2 8 32 128 256 512"
100
+ # data.to_s("SORT") # => "* SORT 2 8 32 128 256 512"
101
+ # data.to_s(nil) # => "2 8 32 128 256 512"
102
+ #
103
+ # data = Net::IMAP::SearchResult[1, 3, 16, 1024, modseq: 2048]
104
+ # data.to_s # => "* SEARCH 1 3 16 1024 (MODSEQ 2048)"
105
+ # data.to_s("SORT") # => "* SORT 1 3 16 1024 (MODSEQ 2048)"
106
+ # data.to_s(nil) # => "1 3 16 1024 (MODSEQ 2048)"
107
+ #
108
+ def to_s(type = "SEARCH")
109
+ str = +""
110
+ str << "* %s " % [type.to_str] unless type.nil?
111
+ str << join(" ")
112
+ str << " (MODSEQ %d)" % [modseq] if modseq
113
+ -str
114
+ end
115
+
116
+ # Converts the SearchResult into a SequenceSet.
117
+ #
118
+ # Net::IMAP::SearchResult[9, 1, 2, 4, 10, 12, 3, modseq: 123_456]
119
+ # .to_sequence_set
120
+ # # => Net::IMAP::SequenceSet["1:4,9:10,12"]
121
+ def to_sequence_set; SequenceSet[*self] end
122
+
123
+ def pretty_print(pp)
124
+ return super if modseq.nil?
125
+ pp.text self.class.name + "["
126
+ pp.group_sub do
127
+ pp.nest(2) do
128
+ pp.breakable ""
129
+ each do |num|
130
+ pp.pp num
131
+ pp.text ","
132
+ pp.fill_breakable
133
+ end
134
+ pp.breakable ""
135
+ pp.text "modseq: "
136
+ pp.pp modseq
137
+ end
138
+ pp.breakable ""
139
+ pp.text "]"
140
+ end
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+ end