net-imap 0.3.7 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pages.yml +46 -0
  3. data/.github/workflows/test.yml +5 -12
  4. data/.gitignore +1 -0
  5. data/Gemfile +3 -0
  6. data/README.md +15 -4
  7. data/Rakefile +0 -7
  8. data/lib/net/imap/authenticators.rb +26 -57
  9. data/lib/net/imap/command_data.rb +13 -6
  10. data/lib/net/imap/deprecated_client_options.rb +139 -0
  11. data/lib/net/imap/errors.rb +20 -0
  12. data/lib/net/imap/response_data.rb +92 -47
  13. data/lib/net/imap/response_parser/parser_utils.rb +240 -0
  14. data/lib/net/imap/response_parser.rb +1265 -986
  15. data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
  16. data/lib/net/imap/sasl/authentication_exchange.rb +107 -0
  17. data/lib/net/imap/sasl/authenticators.rb +118 -0
  18. data/lib/net/imap/sasl/client_adapter.rb +72 -0
  19. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +21 -11
  20. data/lib/net/imap/sasl/digest_md5_authenticator.rb +180 -0
  21. data/lib/net/imap/sasl/external_authenticator.rb +83 -0
  22. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  23. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +25 -16
  24. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
  25. data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
  26. data/lib/net/imap/sasl/protocol_adapters.rb +45 -0
  27. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  28. data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
  29. data/lib/net/imap/sasl/stringprep.rb +6 -66
  30. data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
  31. data/lib/net/imap/sasl.rb +144 -43
  32. data/lib/net/imap/sasl_adapter.rb +21 -0
  33. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  34. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  35. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  36. data/lib/net/imap/stringprep/tables.rb +146 -0
  37. data/lib/net/imap/stringprep/trace.rb +85 -0
  38. data/lib/net/imap/stringprep.rb +159 -0
  39. data/lib/net/imap.rb +993 -609
  40. data/net-imap.gemspec +4 -3
  41. data/rakelib/benchmarks.rake +98 -0
  42. data/rakelib/saslprep.rake +4 -4
  43. data/rakelib/string_prep_tables_generator.rb +82 -60
  44. metadata +29 -13
  45. data/benchmarks/stringprep.yml +0 -65
  46. data/benchmarks/table-regexps.yml +0 -39
  47. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  48. data/lib/net/imap/authenticators/plain.rb +0 -41
  49. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  50. data/lib/net/imap/sasl/saslprep.rb +0 -55
  51. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  52. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30f6313663bc3f6e896e6166ad97a16296b52e59f9f2ef93f5b6ceb8a3031caa
4
- data.tar.gz: e916c89df8b3d7bbf7c310c0299a6b7134caf71a0514091f4f87a171247750a2
3
+ metadata.gz: da8cb634d9ee1613035c81a25d28ae9f36b3d0985bcb61da9a38aacb87854a5f
4
+ data.tar.gz: e504b145415da3a025c6f4ab973b212764c75ef4214d0fed485fdb0b17e83752
5
5
  SHA512:
6
- metadata.gz: f19ca9b2808b3d293b1a6ffef2c8b3287f1f69471c914bf775d51b3259d3dc837cee635d647c575dab70795b06b54c37214d2c51e9138a2093df5d3e20d5b478
7
- data.tar.gz: d6c5b5c78de3f328486d4e107f1d6c336bb13be4738ad0d1b4e3b7b9b6af89f1a7bc77d09a7a27bd9f61160f38f4a5dc6c3a1ec5ae3f9f33705d6c52188e41f5
6
+ metadata.gz: 0cef39a5ac6454ded84c395fb2671df678e17611d7eff42665113c21f955ebcb6ffdf9035dd1e6fdb3de38aa38f43431537ded3d484ffba7d699b478bb8c3e1e
7
+ data.tar.gz: 23f777092b2940b725685285a6372b56744a9968fcc76872ebf9ef3136fb3665a89a85174dc34636e547b90d8d3a1945627ce79390f10122aadb57b57004f789
@@ -0,0 +1,46 @@
1
+ name: Deploy RDoc site to Pages
2
+
3
+ on:
4
+ push:
5
+ branches: [ 'master' ]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ pages: write
11
+ id-token: write
12
+
13
+ concurrency:
14
+ group: "pages"
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v4
23
+ - name: Setup Ruby
24
+ uses: ruby/setup-ruby@250fcd6a742febb1123a77a841497ccaa8b9e939 # v1.152.0
25
+ with:
26
+ ruby-version: '3.2'
27
+ bundler-cache: true
28
+ - name: Setup Pages
29
+ id: pages
30
+ uses: actions/configure-pages@v3
31
+ - name: Build with RDoc
32
+ run: bundle exec rake rdoc
33
+ - name: Upload artifact
34
+ uses: actions/upload-pages-artifact@v2
35
+ with: { path: 'doc' }
36
+
37
+ deploy:
38
+ environment:
39
+ name: github-pages
40
+ url: ${{ steps.deployment.outputs.page_url }}
41
+ runs-on: ubuntu-latest
42
+ needs: build
43
+ steps:
44
+ - name: Deploy to GitHub Pages
45
+ id: deployment
46
+ uses: actions/deploy-pages@v2
@@ -7,7 +7,7 @@ jobs:
7
7
  uses: ruby/actions/.github/workflows/ruby_versions.yml@master
8
8
  with:
9
9
  engine: cruby
10
- min_version: 2.6
10
+ min_version: 2.7
11
11
 
12
12
  build:
13
13
  needs: ruby-versions
@@ -17,22 +17,15 @@ jobs:
17
17
  ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
18
18
  os: [ ubuntu-latest, macos-latest ]
19
19
  experimental: [false]
20
- include:
21
- # - ruby: 2.6
22
- # os: ubuntu-latest
23
- # experimental: true
24
- - ruby: 2.6
25
- os: macos-latest
26
- experimental: false
27
20
  runs-on: ${{ matrix.os }}
28
21
  continue-on-error: ${{ matrix.experimental }}
29
22
  steps:
30
- - uses: actions/checkout@v3
23
+ - uses: actions/checkout@v4
31
24
  - name: Set up Ruby
32
25
  uses: ruby/setup-ruby@v1
33
26
  with:
34
27
  ruby-version: ${{ matrix.ruby }}
35
- - name: Install dependencies
36
- run: bundle install
28
+ bundler-cache: true
29
+ rubygems: latest
37
30
  - name: Run test
38
- run: rake test
31
+ run: bundle exec rake test
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  /Gemfile.lock
11
+ benchmarks/Gemfile*
data/Gemfile CHANGED
@@ -7,3 +7,6 @@ gemspec
7
7
  gem "rake"
8
8
  gem "rdoc"
9
9
  gem "test-unit"
10
+ gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core"
11
+
12
+ gem "benchmark-driver"
data/README.md CHANGED
@@ -21,11 +21,24 @@ Or install it yourself as:
21
21
 
22
22
  ## Usage
23
23
 
24
+ ### Connect with TLS to port 993
25
+
26
+ ```ruby
27
+ imap = Net::IMAP.new('mail.example.com', ssl: true)
28
+ imap.port => 993
29
+ imap.tls_verified? => true
30
+ case imap.greeting.name
31
+ in /OK/i
32
+ # The client is connected in the "Not Authenticated" state.
33
+ imap.authenticate("PLAIN", "joe_user", "joes_password")
34
+ in /PREAUTH/i
35
+ # The client is connected in the "Authenticated" state.
36
+ end
37
+ ```
38
+
24
39
  ### List sender and subject of all recent messages in the default mailbox
25
40
 
26
41
  ```ruby
27
- imap = Net::IMAP.new('mail.example.com')
28
- imap.authenticate('LOGIN', 'joe_user', 'joes_password')
29
42
  imap.examine('INBOX')
30
43
  imap.search(["RECENT"]).each do |message_id|
31
44
  envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
@@ -36,8 +49,6 @@ end
36
49
  ### Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
37
50
 
38
51
  ```ruby
39
- imap = Net::IMAP.new('mail.example.com')
40
- imap.authenticate('LOGIN', 'joe_user', 'joes_password')
41
52
  imap.select('Mail/sent-mail')
42
53
  if not imap.list('Mail/', 'sent-apr03')
43
54
  imap.create('Mail/sent-apr03')
data/Rakefile CHANGED
@@ -10,11 +10,4 @@ Rake::TestTask.new(:test) do |t|
10
10
  t.test_files = FileList["test/**/test_*.rb"]
11
11
  end
12
12
 
13
- task :sync_tool do
14
- require 'fileutils'
15
- FileUtils.cp "../ruby/tool/lib/core_assertions.rb", "./test/lib"
16
- FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib"
17
- FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib"
18
- end
19
-
20
13
  task :default => :test
@@ -1,68 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Registry for SASL authenticators used by Net::IMAP.
3
+ # Backward compatible delegators from Net::IMAP to Net::IMAP::SASL.
4
4
  module Net::IMAP::Authenticators
5
5
 
6
- # Adds an authenticator for Net::IMAP#authenticate to use. +mechanism+ is the
7
- # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
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.
14
- #
15
- # See PlainAuthenticator, XOauth2Authenticator, and DigestMD5Authenticator for
16
- # examples.
17
- def add_authenticator(auth_type, authenticator)
18
- authenticators[auth_type] = authenticator
6
+ # Deprecated. Use Net::IMAP::SASL.add_authenticator instead.
7
+ def add_authenticator(...)
8
+ warn(
9
+ "%s.%s is deprecated. Use %s.%s instead." % [
10
+ Net::IMAP, __method__, Net::IMAP::SASL, __method__
11
+ ],
12
+ uplevel: 1
13
+ )
14
+ Net::IMAP::SASL.add_authenticator(...)
19
15
  end
20
16
 
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)
50
- end
51
- end
52
-
53
- private
54
-
55
- def authenticators
56
- @authenticators ||= {}
17
+ # Deprecated. Use Net::IMAP::SASL.authenticator instead.
18
+ def authenticator(...)
19
+ warn(
20
+ "%s.%s is deprecated. Use %s.%s instead." % [
21
+ Net::IMAP, __method__, Net::IMAP::SASL, __method__
22
+ ],
23
+ uplevel: 1
24
+ )
25
+ Net::IMAP::SASL.authenticator(...)
57
26
  end
58
27
 
28
+ Net::IMAP.extend self
59
29
  end
60
30
 
61
- Net::IMAP.extend Net::IMAP::Authenticators
31
+ class Net::IMAP
32
+ PlainAuthenticator = SASL::PlainAuthenticator # :nodoc:
33
+ deprecate_constant :PlainAuthenticator
62
34
 
63
- require_relative "authenticators/plain"
64
-
65
- require_relative "authenticators/login"
66
- require_relative "authenticators/cram_md5"
67
- require_relative "authenticators/digest_md5"
68
- require_relative "authenticators/xoauth2"
35
+ XOauth2Authenticator = SASL::XOAuth2Authenticator # :nodoc:
36
+ deprecate_constant :XOauth2Authenticator
37
+ end
@@ -52,13 +52,20 @@ module Net
52
52
  end
53
53
 
54
54
  def send_string_data(str, tag = nil)
55
- case str
56
- when ""
55
+ if str.empty?
57
56
  put_string('""')
58
- when /[\x80-\xff\r\n]/n
59
- # literal
57
+ elsif str.match?(/[\r\n]/n)
58
+ # literal, because multiline
60
59
  send_literal(str, tag)
61
- when /[(){ \x00-\x1f\x7f%*"\\]/n
60
+ elsif !str.ascii_only?
61
+ if @utf8_strings
62
+ # quoted string
63
+ send_quoted_string(str)
64
+ else
65
+ # literal, because of non-ASCII bytes
66
+ send_literal(str, tag)
67
+ end
68
+ elsif str.match?(/[(){ \x00-\x1f\x7f%*"\\]/n)
62
69
  # quoted string
63
70
  send_quoted_string(str)
64
71
  else
@@ -67,7 +74,7 @@ module Net
67
74
  end
68
75
 
69
76
  def send_quoted_string(str)
70
- put_string('"' + str.gsub(/["\\]/n, "\\\\\\&") + '"')
77
+ put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"')
71
78
  end
72
79
 
73
80
  def send_literal(str, tag = nil)
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP < Protocol
5
+
6
+ # This module handles deprecated arguments to various Net::IMAP methods.
7
+ module DeprecatedClientOptions
8
+
9
+ # :call-seq:
10
+ # Net::IMAP.new(host, **options) # standard keyword options
11
+ # Net::IMAP.new(host, options) # obsolete hash options
12
+ # Net::IMAP.new(host, port) # obsolete port argument
13
+ # Net::IMAP.new(host, port, usessl, certs = nil, verify = true) # deprecated SSL arguments
14
+ #
15
+ # Translates Net::IMAP.new arguments for backward compatibility.
16
+ #
17
+ # ==== Obsolete arguments
18
+ #
19
+ # Using obsolete arguments does not a warning. Obsolete arguments will be
20
+ # deprecated by a future release.
21
+ #
22
+ # If a second positional argument is given and it is a hash (or is
23
+ # convertable via +#to_hash+), it is converted to keyword arguments.
24
+ #
25
+ # # Obsolete:
26
+ # Net::IMAP.new("imap.example.com", options_hash)
27
+ # # Use instead:
28
+ # Net::IMAP.new("imap.example.com", **options_hash)
29
+ #
30
+ # If a second positional argument is given and it is not a hash, it is
31
+ # converted to the +port+ keyword argument.
32
+ # # Obsolete:
33
+ # Net::IMAP.new("imap.example.com", 114433)
34
+ # # Use instead:
35
+ # Net::IMAP.new("imap.example.com", port: 114433)
36
+ #
37
+ # ==== Deprecated arguments
38
+ #
39
+ # Using deprecated arguments prints a warning. Convert to keyword
40
+ # arguments to avoid the warning. Deprecated arguments will be removed in
41
+ # a future release.
42
+ #
43
+ # If +usessl+ is false, +certs+, and +verify+ are ignored. When it true,
44
+ # all three arguments are converted to the +ssl+ keyword argument.
45
+ # Without +certs+ or +verify+, it is converted to <tt>ssl: true</tt>.
46
+ # # DEPRECATED:
47
+ # Net::IMAP.new("imap.example.com", nil, true) # => prints a warning
48
+ # # Use instead:
49
+ # Net::IMAP.new("imap.example.com", ssl: true)
50
+ #
51
+ # When +certs+ is a path to a directory, it is converted to <tt>ca_path:
52
+ # certs</tt>.
53
+ # # DEPRECATED:
54
+ # Net::IMAP.new("imap.example.com", nil, true, "/path/to/certs") # => prints a warning
55
+ # # Use instead:
56
+ # Net::IMAP.new("imap.example.com", ssl: {ca_path: "/path/to/certs"})
57
+ #
58
+ # When +certs+ is a path to a file, it is converted to <tt>ca_file:
59
+ # certs</tt>.
60
+ # # DEPRECATED:
61
+ # Net::IMAP.new("imap.example.com", nil, true, "/path/to/cert.pem") # => prints a warning
62
+ # # Use instead:
63
+ # Net::IMAP.new("imap.example.com", ssl: {ca_file: "/path/to/cert.pem"})
64
+ #
65
+ # When +verify+ is +false+, it is converted to <tt>verify_mode:
66
+ # OpenSSL::SSL::VERIFY_NONE</tt>.
67
+ # # DEPRECATED:
68
+ # Net::IMAP.new("imap.example.com", nil, true, nil, false) # => prints a warning
69
+ # # Use instead:
70
+ # Net::IMAP.new("imap.example.com", ssl: {verify_mode: OpenSSL::SSL::VERIFY_NONE})
71
+ #
72
+ def initialize(host, port_or_options = nil, *deprecated, **options)
73
+ if port_or_options.nil? && deprecated.empty?
74
+ super host, **options
75
+ elsif options.any?
76
+ # Net::IMAP.new(host, *__invalid__, **options)
77
+ raise ArgumentError, "Do not combine deprecated and keyword arguments"
78
+ elsif port_or_options.respond_to?(:to_hash) and deprecated.any?
79
+ # Net::IMAP.new(host, options, *__invalid__)
80
+ raise ArgumentError, "Do not use deprecated SSL params with options hash"
81
+ elsif port_or_options.respond_to?(:to_hash)
82
+ super host, **Hash.try_convert(port_or_options)
83
+ elsif deprecated.empty?
84
+ super host, port: port_or_options
85
+ elsif deprecated.shift
86
+ warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1
87
+ super host, port: port_or_options, ssl: create_ssl_params(*deprecated)
88
+ else
89
+ warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1
90
+ super host, port: port_or_options, ssl: false
91
+ end
92
+ end
93
+
94
+ # :call-seq:
95
+ # starttls(**options) # standard
96
+ # starttls(options = {}) # obsolete
97
+ # starttls(certs = nil, verify = true) # deprecated
98
+ #
99
+ # Translates Net::IMAP#starttls arguments for backward compatibility.
100
+ #
101
+ # Support for +certs+ and +verify+ will be dropped in a future release.
102
+ #
103
+ # See ::new for interpretation of +certs+ and +verify+.
104
+ def starttls(*deprecated, **options)
105
+ if deprecated.empty?
106
+ super(**options)
107
+ elsif options.any?
108
+ # starttls(*__invalid__, **options)
109
+ raise ArgumentError, "Do not combine deprecated and keyword options"
110
+ elsif deprecated.first.respond_to?(:to_hash) && deprecated.length > 1
111
+ # starttls(*__invalid__, **options)
112
+ raise ArgumentError, "Do not use deprecated verify param with options hash"
113
+ elsif deprecated.first.respond_to?(:to_hash)
114
+ super(**Hash.try_convert(deprecated.first))
115
+ else
116
+ warn "DEPRECATED: Call Net::IMAP#starttls with keyword options", uplevel: 1
117
+ super(**create_ssl_params(*deprecated))
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ def create_ssl_params(certs = nil, verify = true)
124
+ params = {}
125
+ if certs
126
+ if File.file?(certs)
127
+ params[:ca_file] = certs
128
+ elsif File.directory?(certs)
129
+ params[:ca_path] = certs
130
+ end
131
+ end
132
+ params[:verify_mode] =
133
+ verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
134
+ params
135
+ end
136
+
137
+ end
138
+ end
139
+ end
@@ -47,7 +47,27 @@ module Net
47
47
  class ByeResponseError < ResponseError
48
48
  end
49
49
 
50
+ # Error raised when the server sends an invalid response.
51
+ #
52
+ # This is different from UnknownResponseError: the response has been
53
+ # rejected. Although it may be parsable, the server is forbidden from
54
+ # sending it in the current context. The client should automatically
55
+ # disconnect, abruptly (without logout).
56
+ #
57
+ # Note that InvalidResponseError does not inherit from ResponseError: it
58
+ # can be raised before the response is fully parsed. A related
59
+ # ResponseParseError or ResponseError may be the #cause.
60
+ class InvalidResponseError < Error
61
+ end
62
+
50
63
  # Error raised upon an unknown response from the server.
64
+ #
65
+ # This is different from InvalidResponseError: the response may be a
66
+ # valid extension response and the server may be allowed to send it in
67
+ # this context, but Net::IMAP either does not know how to parse it or
68
+ # how to handle it. This could result from enabling unknown or
69
+ # unhandled extensions. The connection may still be usable,
70
+ # but—depending on context—it may be prudent to disconnect.
51
71
  class UnknownResponseError < ResponseError
52
72
  end
53
73