net-imap 0.3.4 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pages.yml +46 -0
  3. data/.github/workflows/test.yml +12 -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/data_encoding.rb +3 -3
  13. data/lib/net/imap/deprecated_client_options.rb +139 -0
  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 +665 -627
  17. data/lib/net/imap/sasl/anonymous_authenticator.rb +68 -0
  18. data/lib/net/imap/sasl/authentication_exchange.rb +107 -0
  19. data/lib/net/imap/sasl/authenticators.rb +118 -0
  20. data/lib/net/imap/sasl/client_adapter.rb +72 -0
  21. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +15 -9
  22. data/lib/net/imap/sasl/digest_md5_authenticator.rb +168 -0
  23. data/lib/net/imap/sasl/external_authenticator.rb +62 -0
  24. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  25. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +19 -14
  26. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +164 -0
  27. data/lib/net/imap/sasl/plain_authenticator.rb +93 -0
  28. data/lib/net/imap/sasl/protocol_adapters.rb +45 -0
  29. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  30. data/lib/net/imap/sasl/scram_authenticator.rb +278 -0
  31. data/lib/net/imap/sasl/stringprep.rb +6 -66
  32. data/lib/net/imap/sasl/xoauth2_authenticator.rb +88 -0
  33. data/lib/net/imap/sasl.rb +144 -43
  34. data/lib/net/imap/sasl_adapter.rb +21 -0
  35. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  36. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  37. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  38. data/lib/net/imap/stringprep/tables.rb +146 -0
  39. data/lib/net/imap/stringprep/trace.rb +85 -0
  40. data/lib/net/imap/stringprep.rb +159 -0
  41. data/lib/net/imap.rb +976 -590
  42. data/net-imap.gemspec +2 -2
  43. data/rakelib/saslprep.rake +4 -4
  44. data/rakelib/string_prep_tables_generator.rb +82 -60
  45. metadata +31 -12
  46. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  47. data/lib/net/imap/authenticators/plain.rb +0 -41
  48. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  49. data/lib/net/imap/sasl/saslprep.rb +0 -55
  50. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  51. 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