net-imap 0.3.7 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) 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/response_data.rb +46 -41
  14. data/lib/net/imap/response_parser/parser_utils.rb +230 -0
  15. data/lib/net/imap/response_parser.rb +665 -627
  16. data/lib/net/imap/sasl/anonymous_authenticator.rb +68 -0
  17. data/lib/net/imap/sasl/authenticators.rb +112 -0
  18. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +15 -9
  19. data/lib/net/imap/{authenticators/digest_md5.rb → sasl/digest_md5_authenticator.rb} +74 -21
  20. data/lib/net/imap/sasl/external_authenticator.rb +62 -0
  21. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  22. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +19 -14
  23. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +164 -0
  24. data/lib/net/imap/sasl/plain_authenticator.rb +93 -0
  25. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  26. data/lib/net/imap/sasl/scram_authenticator.rb +278 -0
  27. data/lib/net/imap/sasl/stringprep.rb +6 -66
  28. data/lib/net/imap/sasl/xoauth2_authenticator.rb +88 -0
  29. data/lib/net/imap/sasl.rb +139 -44
  30. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  31. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  32. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  33. data/lib/net/imap/stringprep/tables.rb +146 -0
  34. data/lib/net/imap/stringprep/trace.rb +85 -0
  35. data/lib/net/imap/stringprep.rb +159 -0
  36. data/lib/net/imap.rb +967 -588
  37. data/net-imap.gemspec +1 -1
  38. data/rakelib/saslprep.rake +4 -4
  39. data/rakelib/string_prep_tables_generator.rb +82 -60
  40. metadata +26 -11
  41. data/lib/net/imap/authenticators/plain.rb +0 -41
  42. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  43. data/lib/net/imap/sasl/saslprep.rb +0 -55
  44. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  45. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
data/net-imap.gemspec CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.summary = %q{Ruby client api for Internet Message Access Protocol}
17
17
  spec.description = %q{Ruby client api for Internet Message Access Protocol}
18
18
  spec.homepage = "https://github.com/ruby/net-imap"
19
- spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
19
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.3")
20
20
  spec.licenses = ["Ruby", "BSD-2-Clause"]
21
21
 
22
22
  spec.metadata["homepage_uri"] = spec.homepage
@@ -10,17 +10,17 @@ end
10
10
 
11
11
  directory "lib/net/imap/sasl"
12
12
 
13
- file "lib/net/imap/sasl/stringprep_tables.rb" => generator.rb_deps do |t|
13
+ file "lib/net/imap/stringprep/tables.rb" => generator.rb_deps do |t|
14
14
  File.write t.name, generator.stringprep_rb
15
15
  end
16
16
 
17
- file "lib/net/imap/sasl/saslprep_tables.rb" => generator.rb_deps do |t|
17
+ file "lib/net/imap/stringprep/saslprep_tables.rb" => generator.rb_deps do |t|
18
18
  File.write t.name, generator.saslprep_rb
19
19
  end
20
20
 
21
21
  GENERATED_RUBY = FileList.new(
22
- "lib/net/imap/sasl/stringprep_tables.rb",
23
- "lib/net/imap/sasl/saslprep_tables.rb",
22
+ "lib/net/imap/stringprep/tables.rb",
23
+ "lib/net/imap/stringprep/saslprep_tables.rb",
24
24
  )
25
25
 
26
26
  CLEAN.include generator.clean_deps
@@ -62,9 +62,9 @@ class StringPrepTablesGenerator
62
62
  # This file is generated from RFC3454, by rake. Don't edit directly.
63
63
  #++
64
64
 
65
- module Net::IMAP::SASL
65
+ module Net::IMAP::StringPrep
66
66
 
67
- module StringPrep
67
+ module Tables
68
68
 
69
69
  #{asgn_table "A.1"}
70
70
 
@@ -74,6 +74,12 @@ class StringPrepTablesGenerator
74
74
 
75
75
  #{asgn_table "B.3"}
76
76
 
77
+ #{asgn_mapping "B.1", ""}
78
+
79
+ #{asgn_mapping "B.2"}
80
+
81
+ #{asgn_mapping "B.3"}
82
+
77
83
  #{asgn_table "C.1.1"}
78
84
 
79
85
  #{asgn_table "C.1.2"}
@@ -105,14 +111,16 @@ class StringPrepTablesGenerator
105
111
 
106
112
  BIDI_DESC_REQ2 = "A string with RandALCat characters must not contain LCat characters."
107
113
 
108
- # Bidirectional Characters [StringPrep, §6], Requirement 2::
114
+ # Bidirectional Characters [StringPrep, §6], Requirement 2
115
+ # >>>
109
116
  # If a string contains any RandALCat character, the string MUST NOT
110
117
  # contain any LCat character.
111
118
  BIDI_FAILS_REQ2 = #{bidi_fails_req2.inspect}.freeze
112
119
 
113
120
  BIDI_DESC_REQ3 = "A string with RandALCat characters must start and end with RandALCat characters."
114
121
 
115
- # Bidirectional Characters [StringPrep, §6], Requirement 3::
122
+ # Bidirectional Characters [StringPrep, §6], Requirement 3
123
+ # >>>
116
124
  # If a string contains any RandALCat character, a RandALCat
117
125
  # character MUST be the first character of the string, and a
118
126
  # RandALCat character MUST be the last character of the string.
@@ -122,15 +130,21 @@ class StringPrepTablesGenerator
122
130
  BIDI_FAILURE = #{bidi_failure_regexp.inspect}.freeze
123
131
 
124
132
  # Names of each codepoint table in the RFC-3454 appendices
125
- TABLE_TITLES = {
133
+ TITLES = {
126
134
  #{table_titles_rb}
127
135
  }.freeze
128
136
 
129
137
  # Regexps matching each codepoint table in the RFC-3454 appendices
130
- TABLE_REGEXPS = {
138
+ REGEXPS = {
131
139
  #{table_regexps_rb}
132
140
  }.freeze
133
141
 
142
+ MAPPINGS = {
143
+ "B.1" => [IN_B_1, MAP_B_1].freeze,
144
+ "B.2" => [IN_B_2, MAP_B_2].freeze,
145
+ "B.3" => [IN_B_3, MAP_B_3].freeze,
146
+ }.freeze
147
+
134
148
  end
135
149
  end
136
150
  RUBY
@@ -157,27 +171,30 @@ class StringPrepTablesGenerator
157
171
  # This file is generated from RFC3454, by rake. Don't edit directly.
158
172
  #++
159
173
 
160
- module Net::IMAP::SASL
174
+ module Net::IMAP::StringPrep
161
175
 
162
176
  module SASLprep
163
177
 
164
178
  # RFC4013 §2.1 Mapping - mapped to space
165
- # * non-ASCII space characters (\\StringPrep\\[\\"C.1.2\\"]) that can be
166
- # mapped to SPACE (U+0020), and
179
+ # >>>
180
+ # non-ASCII space characters (\\StringPrep\\[\\"C.1.2\\"]) that can
181
+ # be mapped to SPACE (U+0020)
167
182
  #
168
183
  # Equal to \\StringPrep\\[\\"C.1.2\\"].
169
- # Redefined here to avoid loading the StringPrep module.
184
+ # Redefined here to avoid loading StringPrep::Tables unless necessary.
170
185
  MAP_TO_SPACE = #{regex_str "C.1.2"}
171
186
 
172
187
  # RFC4013 §2.1 Mapping - mapped to nothing
173
- # the "commonly mapped to nothing" characters (\\StringPrep\\[\\"B.1\\"])
174
- # that can be mapped to nothing.
188
+ # >>>
189
+ # the "commonly mapped to nothing" characters
190
+ # (\\StringPrep\\[\\"B.1\\"]) that can be mapped to nothing.
175
191
  #
176
192
  # Equal to \\StringPrep\\[\\"B.1\\"].
177
- # Redefined here to avoid loading the StringPrep module.
193
+ # Redefined here to avoid loading StringPrep::Tables unless necessary.
178
194
  MAP_TO_NOTHING = #{regex_str "B.1"}
179
195
 
180
- # RFC4013 §2.3 Prohibited Output::
196
+ # RFC4013 §2.3 Prohibited Output
197
+ # >>>
181
198
  # * Non-ASCII space characters — \\StringPrep\\[\\"C.1.2\\"]
182
199
  # * ASCII control characters — \\StringPrep\\[\\"C.2.1\\"]
183
200
  # * Non-ASCII control characters — \\StringPrep\\[\\"C.2.2\\"]
@@ -192,45 +209,52 @@ class StringPrepTablesGenerator
192
209
 
193
210
  # Adds unassigned (by Unicode 3.2) codepoints to TABLES_PROHIBITED.
194
211
  #
195
- # RFC4013 §2.5 Unassigned Code Points::
196
- # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its list of
197
- # unassigned code points.
212
+ # RFC4013 §2.5 Unassigned Code Points
213
+ # >>>
214
+ # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its
215
+ # list of unassigned code points.
198
216
  TABLES_PROHIBITED_STORED = ["A.1", *TABLES_PROHIBITED].freeze
199
217
 
200
- # Matches codepoints prohibited by RFC4013 §2.3.
218
+ # A Regexp matching codepoints prohibited by RFC4013 §2.3.
201
219
  #
202
- # See TABLES_PROHIBITED.
203
- #
204
- # Equal to +Regexp.union+ of the TABLES_PROHIBITED tables. Redefined
205
- # here to avoid loading the StringPrep module unless necessary.
220
+ # This combines all of the TABLES_PROHIBITED tables.
206
221
  PROHIBITED_OUTPUT = #{regex_str(*SASL_TABLES_PROHIBITED)}
207
222
 
208
- # RFC4013 §2.5 Unassigned Code Points::
209
- # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its list of
210
- # unassigned code points.
223
+ # RFC4013 §2.5 Unassigned Code Points
224
+ # >>>
225
+ # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its
226
+ # list of unassigned code points.
227
+ #
228
+ # Equal to \\StringPrep\\[\\"A.1\\"].
229
+ # Redefined here to avoid loading StringPrep::Tables unless necessary.
211
230
  UNASSIGNED = #{regex_str "A.1"}
212
231
 
213
- # Matches codepoints prohibited by RFC4013 §2.3 and §2.5.
232
+ # A Regexp matching codepoints prohibited by RFC4013 §2.3 and §2.5.
214
233
  #
215
- # See TABLES_PROHIBITED_STORED.
234
+ # This combines PROHIBITED_OUTPUT and UNASSIGNED.
216
235
  PROHIBITED_OUTPUT_STORED = Regexp.union(
217
236
  UNASSIGNED, PROHIBITED_OUTPUT
218
237
  ).freeze
219
238
 
220
239
  # Bidirectional Characters [StringPrep, §6]
240
+ #
241
+ # A Regexp for strings that don't satisfy StringPrep's Bidirectional
242
+ # Characters rules.
243
+ #
244
+ # Equal to StringPrep::Tables::BIDI_FAILURE.
245
+ # Redefined here to avoid loading StringPrep::Tables unless necessary.
221
246
  BIDI_FAILURE = #{bidi_failure_regexp.inspect}.freeze
222
247
 
223
- # Matches strings prohibited by RFC4013 §2.3 and §2.4.
248
+ # A Regexp matching strings prohibited by RFC4013 §2.3 and §2.4.
224
249
  #
225
- # This checks prohibited output and bidirectional characters.
250
+ # This combines PROHIBITED_OUTPUT and BIDI_FAILURE.
226
251
  PROHIBITED = Regexp.union(
227
252
  PROHIBITED_OUTPUT, BIDI_FAILURE,
228
253
  )
229
254
 
230
- # Matches strings prohibited by RFC4013 §2.3, §2.4, and §2.5.
255
+ # A Regexp matching strings prohibited by RFC4013 §2.3, §2.4, and §2.5.
231
256
  #
232
- # This checks prohibited output, bidirectional characters, and
233
- # unassigned codepoints.
257
+ # This combines PROHIBITED_OUTPUT_STORED and BIDI_FAILURE.
234
258
  PROHIBITED_STORED = Regexp.union(
235
259
  PROHIBITED_OUTPUT_STORED, BIDI_FAILURE,
236
260
  )
@@ -284,6 +308,15 @@ class StringPrepTablesGenerator
284
308
  .map{|s,e| s..(e || s)}
285
309
  end
286
310
 
311
+ # TODO: DRY with unicode_normalize
312
+ def to_map(table)
313
+ table = table.to_hash
314
+ .transform_keys { Integer _1, 16 }
315
+ .transform_keys { [_1].pack("U*") }
316
+ .transform_values {|cps| cps.map { Integer _1, 16 } }
317
+ .transform_values { _1.pack("U*") }
318
+ end
319
+
287
320
  # Starting from a codepoints array (rather than ranges) to deduplicate merged
288
321
  # tables.
289
322
  def to_regexp(codepoints, negate: false)
@@ -352,6 +385,13 @@ class StringPrepTablesGenerator
352
385
  asgn_regex(name, regexp_for(name, negate: negate), negate: negate)
353
386
  end
354
387
 
388
+ def asgn_mapping(name, replacement = to_map(tables[name]))
389
+ cname = name.tr(?., ?_).upcase
390
+ "# Replacements for %s\n%s%s = %p.freeze" % [
391
+ "IN_#{name}", " " * 2, "MAP_#{cname}", replacement,
392
+ ]
393
+ end
394
+
355
395
  def regexp_const_desc(name, negate: false)
356
396
  if negate then "Matches the negation of the %s table" % [name]
357
397
  else %q{%s \\StringPrep\\[\\"%s\\"]} % [titles.fetch(name), name]
@@ -376,40 +416,22 @@ class StringPrepTablesGenerator
376
416
  def bidi_L ; regexp_for "D.2" end
377
417
 
378
418
  def bidi_fails_req2
379
- / # RandALCat followed by LCat
380
- (?<r_and_al_cat>#{bidi_R_AL.source})
381
- .*?
382
- (?<l_cat>#{bidi_L.source})
383
- | # RandALCat preceded by LCat
384
- \g<l_cat> .*? \g<r_and_al_cat>
385
- /mux
419
+ Regexp.union(
420
+ /#{bidi_R_AL}.*?#{bidi_L}/mu, # RandALCat followed by LCat
421
+ /#{bidi_L}.*?#{bidi_R_AL}/mu, # RandALCat preceded by LCat
422
+ )
386
423
  end
387
424
 
388
425
  def bidi_fails_req3
389
- / # contains RandALCat but doesn't start with RandALCat
390
- \A(?<not_r_nor_al>#{bidi_not_R_AL})
391
- .*?
392
- (?<r_and_al_cat>#{bidi_R_AL})
393
- | # contains RandALCat but doesn't end with RandALCat
394
- \g<r_and_al_cat> .*? \g<not_r_nor_al>\z
395
- /mux
426
+ # contains RandALCat:
427
+ Regexp.union(
428
+ /\A#{bidi_not_R_AL}.*?#{bidi_R_AL}/mu, # but doesn't start with RandALCat
429
+ /#{bidi_R_AL}.*?#{bidi_not_R_AL}\z/mu, # but doesn't end with RandALCat
430
+ )
396
431
  end
397
432
 
398
- # shares the bidi_R_AL definition between both req2 and req3
399
433
  def bidi_failure_regexp
400
- req3_with_backref = bidi_fails_req3.source
401
- .gsub(%r{\(\?\<r_and_al_cat\>\(.*?\)\)}, "\g<r_and_al_cat>")
402
- .then{|re|"(?mx-i:#{re})"}
403
- /#{bidi_fails_req2} | #{req3_with_backref}/mux
404
- end
405
-
406
- def bidi_consts
407
- <<~RUBY
408
- #############
409
- # Bidirectional checks.
410
- #
411
-
412
- RUBY
434
+ Regexp.union(bidi_fails_req2, bidi_fails_req3)
413
435
  end
414
436
 
415
437
  SASL_TABLES_PROHIBITED = %w[
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-imap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.7
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-07-26 00:00:00.000000000 Z
12
+ date: 2023-10-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-protocol
@@ -76,33 +76,48 @@ extensions: []
76
76
  extra_rdoc_files: []
77
77
  files:
78
78
  - ".github/dependabot.yml"
79
+ - ".github/workflows/pages.yml"
79
80
  - ".github/workflows/test.yml"
80
81
  - ".gitignore"
81
82
  - Gemfile
82
83
  - LICENSE.txt
83
84
  - README.md
84
85
  - Rakefile
86
+ - benchmarks/generate_parser_benchmarks
87
+ - benchmarks/parser.yml
85
88
  - benchmarks/stringprep.yml
86
89
  - benchmarks/table-regexps.yml
87
90
  - docs/styles.css
88
91
  - lib/net/imap.rb
89
92
  - lib/net/imap/authenticators.rb
90
- - lib/net/imap/authenticators/cram_md5.rb
91
- - lib/net/imap/authenticators/digest_md5.rb
92
- - lib/net/imap/authenticators/login.rb
93
- - lib/net/imap/authenticators/plain.rb
94
- - lib/net/imap/authenticators/xoauth2.rb
95
93
  - lib/net/imap/command_data.rb
96
94
  - lib/net/imap/data_encoding.rb
95
+ - lib/net/imap/deprecated_client_options.rb
97
96
  - lib/net/imap/errors.rb
98
97
  - lib/net/imap/flags.rb
99
98
  - lib/net/imap/response_data.rb
100
99
  - lib/net/imap/response_parser.rb
100
+ - lib/net/imap/response_parser/parser_utils.rb
101
101
  - lib/net/imap/sasl.rb
102
- - lib/net/imap/sasl/saslprep.rb
103
- - lib/net/imap/sasl/saslprep_tables.rb
102
+ - lib/net/imap/sasl/anonymous_authenticator.rb
103
+ - lib/net/imap/sasl/authenticators.rb
104
+ - lib/net/imap/sasl/cram_md5_authenticator.rb
105
+ - lib/net/imap/sasl/digest_md5_authenticator.rb
106
+ - lib/net/imap/sasl/external_authenticator.rb
107
+ - lib/net/imap/sasl/gs2_header.rb
108
+ - lib/net/imap/sasl/login_authenticator.rb
109
+ - lib/net/imap/sasl/oauthbearer_authenticator.rb
110
+ - lib/net/imap/sasl/plain_authenticator.rb
111
+ - lib/net/imap/sasl/scram_algorithm.rb
112
+ - lib/net/imap/sasl/scram_authenticator.rb
104
113
  - lib/net/imap/sasl/stringprep.rb
105
- - lib/net/imap/sasl/stringprep_tables.rb
114
+ - lib/net/imap/sasl/xoauth2_authenticator.rb
115
+ - lib/net/imap/stringprep.rb
116
+ - lib/net/imap/stringprep/nameprep.rb
117
+ - lib/net/imap/stringprep/saslprep.rb
118
+ - lib/net/imap/stringprep/saslprep_tables.rb
119
+ - lib/net/imap/stringprep/tables.rb
120
+ - lib/net/imap/stringprep/trace.rb
106
121
  - net-imap.gemspec
107
122
  - rakelib/rdoc.rake
108
123
  - rakelib/rfcs.rake
@@ -123,7 +138,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
123
138
  requirements:
124
139
  - - ">="
125
140
  - !ruby/object:Gem::Version
126
- version: 2.6.0
141
+ version: 2.7.3
127
142
  required_rubygems_version: !ruby/object:Gem::Requirement
128
143
  requirements:
129
144
  - - ">="
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Authenticator for the "+PLAIN+" SASL mechanism, specified in
4
- # RFC4616[https://tools.ietf.org/html/rfc4616]. See Net::IMAP#authenticate.
5
- #
6
- # +PLAIN+ authentication sends the password in cleartext.
7
- # RFC3501[https://tools.ietf.org/html/rfc3501] encourages servers to disable
8
- # cleartext authentication until after TLS has been negotiated.
9
- # RFC8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2 or
10
- # greater be used for all traffic, and deprecate cleartext access ASAP. +PLAIN+
11
- # can be secured by TLS encryption.
12
- class Net::IMAP::PlainAuthenticator
13
-
14
- def process(data)
15
- return "#@authzid\0#@username\0#@password"
16
- end
17
-
18
- # :nodoc:
19
- NULL = -"\0".b
20
-
21
- private
22
-
23
- # +username+ is the authentication identity, the identity whose +password+ is
24
- # used. +username+ is referred to as +authcid+ by
25
- # RFC4616[https://tools.ietf.org/html/rfc4616].
26
- #
27
- # +authzid+ is the authorization identity (identity to act as). It can
28
- # usually be left blank. When +authzid+ is left blank (nil or empty string)
29
- # the server will derive an identity from the credentials and use that as the
30
- # authorization identity.
31
- def initialize(username, password, authzid: nil)
32
- raise ArgumentError, "username contains NULL" if username&.include?(NULL)
33
- raise ArgumentError, "password contains NULL" if password&.include?(NULL)
34
- raise ArgumentError, "authzid contains NULL" if authzid&.include?(NULL)
35
- @username = username
36
- @password = password
37
- @authzid = authzid
38
- end
39
-
40
- Net::IMAP.add_authenticator "PLAIN", self
41
- end
@@ -1,20 +0,0 @@
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
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "saslprep_tables"
4
-
5
- module Net::IMAP::SASL
6
-
7
- # SASLprep#saslprep can be used to prepare a string according to [RFC4013].
8
- #
9
- # \SASLprep maps characters three ways: to nothing, to space, and Unicode
10
- # normalization form KC. \SASLprep prohibits codepoints from nearly all
11
- # standard StringPrep tables (RFC3454, Appendix "C"), and uses \StringPrep's
12
- # standard bidirectional characters requirements (Appendix "D"). \SASLprep
13
- # also uses \StringPrep's definition of "Unassigned" codepoints (Appendix "A").
14
- module SASLprep
15
-
16
- # Used to short-circuit strings that don't need preparation.
17
- ASCII_NO_CTRLS = /\A[\x20-\x7e]*\z/u.freeze
18
-
19
- module_function
20
-
21
- # Prepares a UTF-8 +string+ for comparison, using the \SASLprep profile
22
- # RFC4013 of the StringPrep algorithm RFC3454.
23
- #
24
- # By default, prohibited strings will return +nil+. When +exception+ is
25
- # +true+, a StringPrepError describing the violation will be raised.
26
- #
27
- # When +stored+ is +true+, "unassigned" codepoints will be prohibited. For
28
- # \StringPrep and the \SASLprep profile, "unassigned" refers to Unicode 3.2,
29
- # and not later versions. See RFC3454 §7 for more information.
30
- #
31
- def saslprep(str, stored: false, exception: false)
32
- return str if ASCII_NO_CTRLS.match?(str) # raises on incompatible encoding
33
- str = str.encode("UTF-8") # also dups (and raises for invalid encoding)
34
- str.gsub!(MAP_TO_SPACE, " ")
35
- str.gsub!(MAP_TO_NOTHING, "")
36
- str.unicode_normalize!(:nfkc)
37
- # These regexps combine the prohibited and bidirectional checks
38
- return str unless str.match?(stored ? PROHIBITED_STORED : PROHIBITED)
39
- return nil unless exception
40
- # raise helpful errors to indicate *why* it failed:
41
- tables = stored ? TABLES_PROHIBITED_STORED : TABLES_PROHIBITED
42
- StringPrep.check_prohibited! str, *tables, bidi: true, profile: "SASLprep"
43
- raise StringPrep::InvalidStringError.new(
44
- "unknown error", string: string, profile: "SASLprep"
45
- )
46
- rescue ArgumentError, Encoding::CompatibilityError => ex
47
- if /invalid byte sequence|incompatible encoding/.match? ex.message
48
- return nil unless exception
49
- raise StringPrepError.new(ex.message, string: str, profile: "saslprep")
50
- end
51
- raise ex
52
- end
53
-
54
- end
55
- end
@@ -1,98 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # This file is generated from RFC3454, by rake. Don't edit directly.
5
- #++
6
-
7
- module Net::IMAP::SASL
8
-
9
- module SASLprep
10
-
11
- # RFC4013 §2.1 Mapping - mapped to space
12
- # * non-ASCII space characters (\StringPrep\[\"C.1.2\"]) that can be
13
- # mapped to SPACE (U+0020), and
14
- #
15
- # Equal to \StringPrep\[\"C.1.2\"].
16
- # Redefined here to avoid loading the StringPrep module.
17
- MAP_TO_SPACE = /[\u200b\p{Zs}&&[^ ]]/.freeze
18
-
19
- # RFC4013 §2.1 Mapping - mapped to nothing
20
- # the "commonly mapped to nothing" characters (\StringPrep\[\"B.1\"])
21
- # that can be mapped to nothing.
22
- #
23
- # Equal to \StringPrep\[\"B.1\"].
24
- # Redefined here to avoid loading the StringPrep module.
25
- MAP_TO_NOTHING = /[\u{00ad 034f 1806 2060 feff}\u{180b}-\u{180d}\u{200b}-\u{200d}\u{fe00}-\u{fe0f}]/.freeze
26
-
27
- # RFC4013 §2.3 Prohibited Output::
28
- # * Non-ASCII space characters — \StringPrep\[\"C.1.2\"]
29
- # * ASCII control characters — \StringPrep\[\"C.2.1\"]
30
- # * Non-ASCII control characters — \StringPrep\[\"C.2.2\"]
31
- # * Private Use characters — \StringPrep\[\"C.3\"]
32
- # * Non-character code points — \StringPrep\[\"C.4\"]
33
- # * Surrogate code points — \StringPrep\[\"C.5\"]
34
- # * Inappropriate for plain text characters — \StringPrep\[\"C.6\"]
35
- # * Inappropriate for canonical representation characters — \StringPrep\[\"C.7\"]
36
- # * Change display properties or deprecated characters — \StringPrep\[\"C.8\"]
37
- # * Tagging characters — \StringPrep\[\"C.9\"]
38
- TABLES_PROHIBITED = ["C.1.2", "C.2.1", "C.2.2", "C.3", "C.4", "C.5", "C.6", "C.7", "C.8", "C.9"].freeze
39
-
40
- # Adds unassigned (by Unicode 3.2) codepoints to TABLES_PROHIBITED.
41
- #
42
- # RFC4013 §2.5 Unassigned Code Points::
43
- # This profile specifies the \StringPrep\[\"A.1\"] table as its list of
44
- # unassigned code points.
45
- TABLES_PROHIBITED_STORED = ["A.1", *TABLES_PROHIBITED].freeze
46
-
47
- # Matches codepoints prohibited by RFC4013 §2.3.
48
- #
49
- # See TABLES_PROHIBITED.
50
- #
51
- # Equal to +Regexp.union+ of the TABLES_PROHIBITED tables. Redefined
52
- # here to avoid loading the StringPrep module unless necessary.
53
- PROHIBITED_OUTPUT = /[\u{06dd 070f 1680 180e 3000 feff e0001}\u{0000}-\u{001f}\u{007f}-\u{00a0}\u{0340}-\u{0341}\u{2000}-\u{200f}\u{2028}-\u{202f}\u{205f}-\u{2063}\u{206a}-\u{206f}\u{2ff0}-\u{2ffb}\u{e000}-\u{f8ff}\u{fdd0}-\u{fdef}\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{e0020}-\u{e007f}\u{efffe}-\u{10ffff}\p{Cs}]/.freeze
54
-
55
- # RFC4013 §2.5 Unassigned Code Points::
56
- # This profile specifies the \StringPrep\[\"A.1\"] table as its list of
57
- # unassigned code points.
58
- UNASSIGNED = /\p{^AGE=3.2}/.freeze
59
-
60
- # Matches codepoints prohibited by RFC4013 §2.3 and §2.5.
61
- #
62
- # See TABLES_PROHIBITED_STORED.
63
- PROHIBITED_OUTPUT_STORED = Regexp.union(
64
- UNASSIGNED, PROHIBITED_OUTPUT
65
- ).freeze
66
-
67
- # Bidirectional Characters [StringPrep, §6]
68
- BIDI_FAILURE = /(?mx-i: # RandALCat followed by LCat
69
- (?<r_and_al_cat>[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}])
70
- .*?
71
- (?<l_cat>[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}])
72
- | # RandALCat preceded by LCat
73
- \g<l_cat> .*? \g<r_and_al_cat>
74
- ) | (?mx-i: # contains RandALCat but doesn't start with RandALCat
75
- \A(?<not_r_nor_al>(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]))
76
- .*?
77
- g<r_and_al_cat>
78
- | # contains RandALCat but doesn't end with RandALCat
79
- \g<r_and_al_cat> .*? \g<not_r_nor_al>\z
80
- )/mx.freeze
81
-
82
- # Matches strings prohibited by RFC4013 §2.3 and §2.4.
83
- #
84
- # This checks prohibited output and bidirectional characters.
85
- PROHIBITED = Regexp.union(
86
- PROHIBITED_OUTPUT, BIDI_FAILURE,
87
- )
88
-
89
- # Matches strings prohibited by RFC4013 §2.3, §2.4, and §2.5.
90
- #
91
- # This checks prohibited output, bidirectional characters, and
92
- # unassigned codepoints.
93
- PROHIBITED_STORED = Regexp.union(
94
- PROHIBITED_OUTPUT_STORED, BIDI_FAILURE,
95
- )
96
-
97
- end
98
- end