net-imap 0.3.9 → 0.4.0

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.

Potentially problematic release.


This version of net-imap might be problematic. Click here for more details.

Files changed (47) 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/Gemfile +1 -0
  5. data/README.md +15 -4
  6. data/Rakefile +0 -7
  7. data/benchmarks/generate_parser_benchmarks +52 -0
  8. data/benchmarks/parser.yml +578 -0
  9. data/benchmarks/stringprep.yml +1 -1
  10. data/lib/net/imap/authenticators.rb +26 -57
  11. data/lib/net/imap/command_data.rb +13 -6
  12. data/lib/net/imap/deprecated_client_options.rb +139 -0
  13. data/lib/net/imap/errors.rb +0 -34
  14. data/lib/net/imap/response_data.rb +46 -41
  15. data/lib/net/imap/response_parser/parser_utils.rb +230 -0
  16. data/lib/net/imap/response_parser.rb +667 -649
  17. data/lib/net/imap/sasl/anonymous_authenticator.rb +68 -0
  18. data/lib/net/imap/sasl/authenticators.rb +112 -0
  19. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +15 -9
  20. data/lib/net/imap/{authenticators/digest_md5.rb → sasl/digest_md5_authenticator.rb} +74 -21
  21. data/lib/net/imap/sasl/external_authenticator.rb +62 -0
  22. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  23. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +19 -14
  24. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +164 -0
  25. data/lib/net/imap/sasl/plain_authenticator.rb +93 -0
  26. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  27. data/lib/net/imap/sasl/scram_authenticator.rb +278 -0
  28. data/lib/net/imap/sasl/stringprep.rb +6 -66
  29. data/lib/net/imap/sasl/xoauth2_authenticator.rb +88 -0
  30. data/lib/net/imap/sasl.rb +139 -44
  31. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  32. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  33. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  34. data/lib/net/imap/stringprep/tables.rb +146 -0
  35. data/lib/net/imap/stringprep/trace.rb +85 -0
  36. data/lib/net/imap/stringprep.rb +159 -0
  37. data/lib/net/imap.rb +987 -690
  38. data/net-imap.gemspec +1 -1
  39. data/rakelib/saslprep.rake +4 -4
  40. data/rakelib/string_prep_tables_generator.rb +82 -60
  41. metadata +30 -13
  42. data/lib/net/imap/authenticators/plain.rb +0 -41
  43. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  44. data/lib/net/imap/response_reader.rb +0 -75
  45. data/lib/net/imap/sasl/saslprep.rb +0 -55
  46. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  47. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP < Protocol
5
+
6
+ # Regexps and utility methods for implementing stringprep profiles. The
7
+ # \StringPrep algorithm is defined by
8
+ # {RFC-3454}[https://www.rfc-editor.org/rfc/rfc3454.html]. Each
9
+ # codepoint table defined in the RFC-3454 appendices is matched by a Regexp
10
+ # defined in this module.
11
+ module StringPrep
12
+ autoload :NamePrep, File.expand_path("stringprep/nameprep", __dir__)
13
+ autoload :SASLprep, File.expand_path("stringprep/saslprep", __dir__)
14
+ autoload :Tables, File.expand_path("stringprep/tables", __dir__)
15
+ autoload :Trace, File.expand_path("stringprep/trace", __dir__)
16
+
17
+ # ArgumentError raised when +string+ is invalid for the stringprep
18
+ # +profile+.
19
+ class StringPrepError < ArgumentError
20
+ attr_reader :string, :profile
21
+
22
+ def initialize(*args, string: nil, profile: nil)
23
+ @string = -string.to_str unless string.nil?
24
+ @profile = -profile.to_str unless profile.nil?
25
+ super(*args)
26
+ end
27
+ end
28
+
29
+ # StringPrepError raised when +string+ contains a codepoint prohibited by
30
+ # +table+.
31
+ class ProhibitedCodepoint < StringPrepError
32
+ attr_reader :table
33
+
34
+ def initialize(table, *args, **kwargs)
35
+ @table = table
36
+ details = (title = Tables::TITLES[table]) ?
37
+ "%s [%s]" % [title, table] : table
38
+ message = "String contains a prohibited codepoint: %s" % [details]
39
+ super(message, *args, **kwargs)
40
+ end
41
+ end
42
+
43
+ # StringPrepError raised when +string+ contains bidirectional characters
44
+ # which violate the StringPrep requirements.
45
+ class BidiStringError < StringPrepError
46
+ end
47
+
48
+ # Returns a Regexp matching the given +table+ name.
49
+ def self.[](table)
50
+ Tables::REGEXPS.fetch(table)
51
+ end
52
+
53
+ module_function
54
+
55
+ # >>>
56
+ # 1. Map -- For each character in the input, check if it has a mapping
57
+ # and, if so, replace it with its mapping. This is described in
58
+ # section 3.
59
+ #
60
+ # 2. Normalize -- Possibly normalize the result of step 1 using Unicode
61
+ # normalization. This is described in section 4.
62
+ #
63
+ # 3. Prohibit -- Check for any characters that are not allowed in the
64
+ # output. If any are found, return an error. This is described in
65
+ # section 5.
66
+ #
67
+ # 4. Check bidi -- Possibly check for right-to-left characters, and if
68
+ # any are found, make sure that the whole string satisfies the
69
+ # requirements for bidirectional strings. If the string does not
70
+ # satisfy the requirements for bidirectional strings, return an
71
+ # error. This is described in section 6.
72
+ #
73
+ # The above steps MUST be performed in the order given to comply with
74
+ # this specification.
75
+ #
76
+ def stringprep(string,
77
+ maps:,
78
+ normalization:,
79
+ prohibited:,
80
+ **opts)
81
+ string = string.encode("UTF-8") # also dups (and raises invalid encoding)
82
+ map_tables!(string, *maps) if maps
83
+ string.unicode_normalize!(normalization) if normalization
84
+ check_prohibited!(string, *prohibited, **opts) if prohibited
85
+ string
86
+ end
87
+
88
+ def map_tables!(string, *tables)
89
+ tables.each do |table|
90
+ regexp, replacements = Tables::MAPPINGS.fetch(table)
91
+ string.gsub!(regexp, replacements)
92
+ end
93
+ string
94
+ end
95
+
96
+ # Checks +string+ for any codepoint in +tables+. Raises a
97
+ # ProhibitedCodepoint describing the first matching table.
98
+ #
99
+ # Also checks bidirectional characters, when <tt>bidi: true</tt>, which may
100
+ # raise a BidiStringError.
101
+ #
102
+ # +profile+ is an optional string which will be added to any exception that
103
+ # is raised (it does not affect behavior).
104
+ def check_prohibited!(string,
105
+ *tables,
106
+ bidi: false,
107
+ unassigned: "A.1",
108
+ stored: false,
109
+ profile: nil)
110
+ tables = Tables::TITLES.keys.grep(/^C/) if tables.empty?
111
+ tables |= [unassigned] if stored
112
+ tables |= %w[C.8] if bidi
113
+ table = tables.find {|t|
114
+ case t
115
+ when String then Tables::REGEXPS.fetch(t).match?(string)
116
+ when Regexp then t.match?(string)
117
+ else raise ArgumentError, "only table names and regexps can be checked"
118
+ end
119
+ }
120
+ if table
121
+ raise ProhibitedCodepoint.new(
122
+ table, string: string, profile: profile
123
+ )
124
+ end
125
+ check_bidi!(string, profile: profile) if bidi
126
+ end
127
+
128
+ # Checks that +string+ obeys all of the "Bidirectional Characters"
129
+ # requirements in RFC-3454, §6:
130
+ #
131
+ # * The characters in \StringPrep\[\"C.8\"] MUST be prohibited
132
+ # * If a string contains any RandALCat character, the string MUST NOT
133
+ # contain any LCat character.
134
+ # * If a string contains any RandALCat character, a RandALCat
135
+ # character MUST be the first character of the string, and a
136
+ # RandALCat character MUST be the last character of the string.
137
+ #
138
+ # This is usually combined with #check_prohibited!, so table "C.8" is only
139
+ # checked when <tt>c_8: true</tt>.
140
+ #
141
+ # Raises either ProhibitedCodepoint or BidiStringError unless all
142
+ # requirements are met. +profile+ is an optional string which will be
143
+ # added to any exception that is raised (it does not affect behavior).
144
+ def check_bidi!(string, c_8: false, profile: nil)
145
+ check_prohibited!(string, "C.8", profile: profile) if c_8
146
+ if Tables::BIDI_FAILS_REQ2.match?(string)
147
+ raise BidiStringError.new(
148
+ Tables::BIDI_DESC_REQ2, string: string, profile: profile,
149
+ )
150
+ elsif Tables::BIDI_FAILS_REQ3.match?(string)
151
+ raise BidiStringError.new(
152
+ Tables::BIDI_DESC_REQ3, string: string, profile: profile,
153
+ )
154
+ end
155
+ end
156
+
157
+ end
158
+ end
159
+ end