net-imap 0.3.1 → 0.3.2

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.

data/rakelib/rfcs.rake ADDED
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ RFCS = {
4
+
5
+ # Historic IMAP RFCs
6
+ 822 => "Internet Message Format (OBSOLETE)",
7
+ 1730 => "IMAP4 (OBSOLETE)",
8
+ 1731 => "IMAP4 Authentication Mechanisms (OBSOLETE)",
9
+ 2060 => "IMAP4rev1 (OBSOLETE)",
10
+ 2061 => "IMAP4 Compatibility with IMAP2bis",
11
+ 2062 => "Internet Message Access Protocol - Obsolete Syntax",
12
+ 2086 => "IMAP ACL (OBSOLETE)",
13
+ 2087 => "IMAP QUOTA (OBSOLETE)",
14
+ 2088 => "IMAP LITERAL+ (OBSOLETE)",
15
+ 2095 => "IMAP/POP AUTHorize Extension for CRAM-MD5 (OBSOLETE)",
16
+ 2192 => "IMAP URL Scheme (OBSOLETE)",
17
+ 2222 => "SASL (OBSOLETE)",
18
+ 2359 => "IMAP UIDPLUS (OBSOLETE)",
19
+ 2822 => "Internet Message Format (OBSOLETE)",
20
+ 3348 => "IMAP CHILDREN (OBSOLETED)",
21
+ 4551 => "IMAP CONDSTORE (OBSOLETE)",
22
+ 5162 => "IMAP QRESYNC (OBSOLETE)",
23
+ 6237 => "IMAP MULTISEARCH (OBSOLETE)",
24
+
25
+ # Core IMAP RFCs
26
+ 3501 => "IMAP4rev1", # supported by nearly all email servers
27
+ 4466 => "Collected Extensions to IMAP4 ABNF",
28
+ 9051 => "IMAP4rev2", # not widely supported yet
29
+
30
+ # RFC-9051 Normative References (not a complete list)
31
+ 2152 => "UTF-7",
32
+ 2180 => "IMAP4 Multi-Accessed Mailbox Practice",
33
+ 2683 => "IMAP4 Implementation Recommendations",
34
+ 3503 => "Message Disposition Notification (MDN) profile IMAP",
35
+ 5234 => "ABNF",
36
+ 5788 => "IMAP4 keyword registry",
37
+
38
+ # Internet Message format and envelope and body structure
39
+ 5322 => "Internet Message Format (current)",
40
+
41
+ 1864 => "[MD5]: The Content-MD5 Header Field",
42
+ 2045 => "[MIME-IMB]: MIME Part One: Format of Internet Message Bodies",
43
+ 2046 => "[MIME-IMT]: MIME Part Two: Media Types",
44
+ 2047 => "[MIME-HDRS]: MIME Part Three: Header Extensions for Non-ASCII Text",
45
+ 2183 => "[DISPOSITION]: The Content-Disposition Header",
46
+ 2231 => "MIME Parameter Value and Encoded Word Extensions: Character Sets, Languages, and Continuations",
47
+ 2557 => "[LOCATION]: MIME Encapsulation of Aggregate Documents",
48
+ 2978 => "[CHARSET]: IANA Charset Registration Procedures, BCP 19",
49
+ 3282 => "[LANGUAGE-TAGS]: Content Language Headers",
50
+ 6532 => "[I18N-HDRS]: Internationalized Email Headers",
51
+
52
+ # SASL
53
+ 4422 => "SASL, EXTERNAL",
54
+
55
+ # stringprep
56
+ 3454 => "stringprep",
57
+ 4013 => "SASLprep",
58
+ 8265 => "PRECIS", # obsoletes SASLprep?
59
+
60
+ # SASL mechanisms (not a complete list)
61
+ 2195 => "SASL CRAM-MD5",
62
+ 4505 => "SASL ANONYMOUS",
63
+ 4616 => "SASL PLAIN",
64
+ 4752 => "SASL GSSAPI (Kerberos V5)",
65
+ 5801 => "SASL GS2-*, GS2-KRB5",
66
+ 5802 => "SASL SCRAM-*, SCRAM-SHA-1, SCRAM-SHA1-PLUS",
67
+ 5803 => "LDAP Schema for Storing SCRAM Secrets",
68
+ 6331 => "SASL DIGEST-MD5",
69
+ 6595 => "SASL SAML20",
70
+ 6616 => "SASL OPENID20",
71
+ 7628 => "SASL OAUTH10A, OAUTHBEARER",
72
+ 7677 => "SASL SCRAM-SHA-256, SCRAM-SHA256-PLUS",
73
+
74
+ # "Informational" RFCs
75
+ 1733 => "Distributed E-Mail Models in IMAP4",
76
+ 4549 => "Synchronization Operations for Disconnected IMAP4 Clients",
77
+
78
+ # TLS and other security concerns
79
+ 2595 => "Using TLS with IMAP, POP3 and ACAP",
80
+ 6151 => "Updated Security Considerations for MD5 Message-Digest and HMAC-MD5",
81
+ 7525 => "Recommendations for Secure Use of TLS and DTLS",
82
+ 7818 => "Updated TLS Server Identity Check Procedure for Email Protocols",
83
+ 8314 => "Cleartext Considered Obsolete: Use of TLS for Email",
84
+ 8996 => "Deprecating TLS 1.0 and TLS 1.1,",
85
+
86
+ # related email specifications
87
+ 6376 => "DomainKeys Identified Mail (DKIM) Signatures",
88
+ 6409 => "Message Submission for Mail",
89
+
90
+ # Other IMAP4 "Standards Track" RFCs
91
+ 5092 => "IMAP URL Scheme",
92
+ 5593 => "IMAP URL Access Identifier Extension",
93
+ 5530 => "IMAP Response Codes",
94
+ 6186 => "Use of SRV Records for Locating Email Submission/Access Services",
95
+ 8305 => "Happy Eyeballs Version 2: Better Connectivity Using Concurrency",
96
+
97
+ # IMAP4 Extensions
98
+ 2177 => "IMAP IDLE",
99
+ 2193 => "IMAP MAILBOX-REFERRALS",
100
+ 2221 => "IMAP LOGIN-REFERRALS",
101
+ 2342 => "IMAP NAMESPACE",
102
+ 2971 => "IMAP ID",
103
+ 3502 => "IMAP MULTIAPPEND",
104
+ 3516 => "IMAP BINARY",
105
+ 3691 => "IMAP UNSELECT",
106
+ 4314 => "IMAP ACL, RIGHTS=",
107
+ 4315 => "IMAP UIDPLUS",
108
+ 4467 => "IMAP URLAUTH",
109
+ 4469 => "IMAP CATENATE",
110
+ 4731 => "IMAP ESEARCH",
111
+ 4959 => "IMAP SASL-IR",
112
+ 4978 => "IMAP COMPRESS=DEFLATE",
113
+ 5032 => "IMAP WITHIN",
114
+ 5161 => "IMAP ENABLE",
115
+ 5182 => "IMAP SEARCHRES",
116
+ 5255 => "IMAP I18NLEVEL=1, I18NLEVEL=2, LANGUAGE",
117
+ 5256 => "IMAP SORT, THREAD",
118
+ 5257 => "IMAP ANNOTATE-EXPERIMENT-1",
119
+ 5258 => "IMAP LIST-EXTENDED",
120
+ 5259 => "IMAP CONVERT",
121
+ 5267 => "IMAP CONTEXT=SEARCH, CONTEXT=SORT, ESORT",
122
+ 5464 => "IMAP METADATA, METADATA-SERVER",
123
+ 5465 => "IMAP NOTIFY",
124
+ 5466 => "IMAP FILTERS",
125
+ 5524 => "IMAP URLAUTH=BINARY", # see also: [RFC Errata 6214]
126
+ 5550 => "IMAP URL-PARTIAL",
127
+ 5738 => "IMAP UTF8=ALL, UTF8=APPEND, UTF8=USER", # OBSOLETED by RFC6855
128
+ 5819 => "IMAP LIST-STATUS",
129
+ 5957 => "IMAP SORT=DISPLAY",
130
+ 6154 => "IMAP SPECIAL-USE, CREATE-SPECIAL-USE",
131
+ 6203 => "IMAP SEARCH=FUZZY",
132
+ 6785 => "IMAP IMAPSIEVE=",
133
+ 6851 => "IMAP MOVE",
134
+ 6855 => "IMAP UTF8=ACCEPT, UTF8=ONLY",
135
+ 7162 => "IMAP CONDSTORE, QRESYNC",
136
+ 7377 => "IMAP MULTISEARCH",
137
+ 7888 => "IMAP LITERAL+, LITERAL-",
138
+ 7889 => "IMAP APPENDLIMIT",
139
+ 8437 => "IMAP UNAUTHENTICATE",
140
+ 8438 => "IMAP STATUS=SIZE",
141
+ 8440 => "IMAP LIST-MYRIGHTS",
142
+ 8474 => "IMAP OBJECTID",
143
+ 8508 => "IMAP REPLACE",
144
+ 8514 => "IMAP SAVEDATE",
145
+ 8970 => "IMAP PREVIEW",
146
+ 9208 => "IMAP QUOTA, QUOTA=, QUOTASET",
147
+
148
+ # etc...
149
+ 6857 => "Post-Delivery Message Downgrading for I18n Email Messages",
150
+
151
+ }.freeze
152
+
153
+ task :rfcs => RFCS.keys.map {|n| "rfcs/rfc%04d.txt" % [n] }
154
+
155
+ RFC_RE = %r{rfcs/rfc(\d+).*\.txt}.freeze
156
+ rule RFC_RE do |t|
157
+ require "fileutils"
158
+ FileUtils.mkpath "rfcs"
159
+ require "net/http"
160
+ t.name =~ RFC_RE
161
+ rfc_url = URI("https://www.rfc-editor.org/rfc/rfc#$1.txt")
162
+ rfc_txt = Net::HTTP.get(rfc_url)
163
+ File.write(t.name, rfc_txt)
164
+ end
165
+
166
+ CLEAN.include "rfcs/rfc*.txt"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "string_prep_tables_generator"
4
+
5
+ generator = StringPrepTablesGenerator.new
6
+
7
+ file generator.json_filename => generator.json_deps do |t|
8
+ generator.generate_json_data_file
9
+ end
10
+
11
+ directory "lib/net/imap/sasl"
12
+
13
+ file "lib/net/imap/sasl/stringprep_tables.rb" => generator.rb_deps do |t|
14
+ File.write t.name, generator.stringprep_rb
15
+ end
16
+
17
+ file "lib/net/imap/sasl/saslprep_tables.rb" => generator.rb_deps do |t|
18
+ File.write t.name, generator.saslprep_rb
19
+ end
20
+
21
+ GENERATED_RUBY = FileList.new(
22
+ "lib/net/imap/sasl/stringprep_tables.rb",
23
+ "lib/net/imap/sasl/saslprep_tables.rb",
24
+ )
25
+
26
+ CLEAN.include generator.clean_deps
27
+ CLOBBER.include GENERATED_RUBY
28
+
29
+ task saslprep_rb: GENERATED_RUBY
30
+ task test: :saslprep_rb
@@ -0,0 +1,423 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Generator for stringprep regexps.
4
+ #
5
+ # Combines Unicode character classes with generated tables. Generated regexps
6
+ # are still used to test that the written regexps conform to the specification.
7
+ # Some tables don't match up well with any character properties available to
8
+ # ruby's regexp engine. Those use the table-generated regexps.
9
+ class StringPrepTablesGenerator
10
+ STRINGPREP_RFC_FILE = "rfcs/rfc3454.txt"
11
+ STRINGPREP_JSON_FILE = "rfcs/rfc3454-stringprep_tables.json"
12
+
13
+ # valid UTF-8 can't contain these codepoints
14
+ # checking for them anyway, using /\p{Cs}/ ;)
15
+ SURROGATES_RANGE = 0xD800..0xDFFF
16
+
17
+ attr_reader :json_filename, :rfc_filename
18
+
19
+ def initialize(rfc_filename: STRINGPREP_RFC_FILE,
20
+ json_filename: STRINGPREP_JSON_FILE)
21
+ @rfc_filename = rfc_filename
22
+ @json_filename = json_filename
23
+ end
24
+
25
+ # for rake deps
26
+ def json_deps; Rake::FileList.new __FILE__, STRINGPREP_RFC_FILE end
27
+ def rb_deps; Rake::FileList.new __FILE__, STRINGPREP_JSON_FILE end
28
+ def clean_deps; Rake::FileList.new STRINGPREP_JSON_FILE end
29
+
30
+ def generate_json_data_file
31
+ require "json"
32
+ rfc_filename
33
+ .then(&File.method(:read))
34
+ .then(&method(:parse_rfc_text))
35
+ .then(&JSON.method(:pretty_generate))
36
+ .then {|data| File.write json_filename, data }
37
+ end
38
+
39
+ def tables; @tables ||= load_tables_and_titles_from_json!.first end
40
+ def titles; @titles ||= load_tables_and_titles_from_json!.last end
41
+ def ranges; @ranges ||= tables.transform_values(&method(:to_ranges)) end
42
+ def arrays; @arrays ||= ranges.transform_values{|t| t.flat_map(&:to_a) } end
43
+ def sets; @sets ||= arrays.transform_values(&:to_set) end
44
+ def regexps; @regexps ||= arrays.transform_values(&method(:to_regexp)) end
45
+ def asgn_regexps; @asgn_regexps || asgn_regexps! end
46
+
47
+ def merged_tables_regex(*table_names, negate: false)
48
+ table_names
49
+ .flat_map(&arrays.method(:fetch))
50
+ .then {|array| to_regexp(array, negate: negate) }
51
+ end
52
+
53
+ def regexp_for(*names, negate: false)
54
+ asgn_regexps[[*names, negate]] ||= merged_tables_regex(*names, negate: negate)
55
+ end
56
+
57
+ def stringprep_rb
58
+ <<~RUBY
59
+ # frozen_string_literal: true
60
+
61
+ #--
62
+ # This file is generated from RFC3454, by rake. Don't edit directly.
63
+ #++
64
+
65
+ module Net::IMAP::SASL
66
+
67
+ module StringPrep
68
+
69
+ #{asgn_table "A.1"}
70
+
71
+ #{asgn_table "B.1"}
72
+
73
+ #{asgn_table "B.2"}
74
+
75
+ #{asgn_table "B.3"}
76
+
77
+ #{asgn_table "C.1.1"}
78
+
79
+ #{asgn_table "C.1.2"}
80
+
81
+ #{asgn_table "C.2.1"}
82
+
83
+ #{asgn_table "C.2.2"}
84
+
85
+ #{asgn_table "C.3"}
86
+
87
+ #{asgn_table "C.4"}
88
+
89
+ #{asgn_table "C.5"}
90
+
91
+ #{asgn_table "C.6"}
92
+
93
+ #{asgn_table "C.7"}
94
+
95
+ #{asgn_table "C.8"}
96
+
97
+ #{asgn_table "C.9"}
98
+
99
+ #{asgn_table "D.1"}
100
+
101
+ # Used to check req3 of bidirectional checks
102
+ #{asgn_table "D.1", negate: true}
103
+
104
+ #{asgn_table "D.2"}
105
+
106
+ BIDI_DESC_REQ2 = "A string with RandALCat characters must not contain LCat characters."
107
+
108
+ # Bidirectional Characters [StringPrep, §6], Requirement 2::
109
+ # If a string contains any RandALCat character, the string MUST NOT
110
+ # contain any LCat character.
111
+ BIDI_FAILS_REQ2 = #{bidi_fails_req2.inspect}.freeze
112
+
113
+ BIDI_DESC_REQ3 = "A string with RandALCat characters must start and end with RandALCat characters."
114
+
115
+ # Bidirectional Characters [StringPrep, §6], Requirement 3::
116
+ # If a string contains any RandALCat character, a RandALCat
117
+ # character MUST be the first character of the string, and a
118
+ # RandALCat character MUST be the last character of the string.
119
+ BIDI_FAILS_REQ3 = #{bidi_fails_req3.inspect}.freeze
120
+
121
+ # Bidirectional Characters [StringPrep, §6]
122
+ BIDI_FAILURE = #{bidi_failure_regexp.inspect}.freeze
123
+
124
+ # Names of each codepoint table in the RFC-3454 appendices
125
+ TABLE_TITLES = {
126
+ #{table_titles_rb}
127
+ }.freeze
128
+
129
+ # Regexps matching each codepoint table in the RFC-3454 appendices
130
+ TABLE_REGEXPS = {
131
+ #{table_regexps_rb}
132
+ }.freeze
133
+
134
+ end
135
+ end
136
+ RUBY
137
+ end
138
+
139
+ def table_titles_rb(indent = 3)
140
+ titles
141
+ .map{|t| "%p => %p," % t }
142
+ .join("\n#{" "*indent}")
143
+ end
144
+
145
+ def table_regexps_rb(indent = 3)
146
+ asgn_regexps # => { ["A.1", false] => regexp, ... }
147
+ .reject {|(_, n), _| n }
148
+ .map {|(t, _), _| "%p => %s," % [t, regexp_const_name(t)] }
149
+ .join("\n#{" "*indent}")
150
+ end
151
+
152
+ def saslprep_rb
153
+ <<~RUBY
154
+ # frozen_string_literal: true
155
+
156
+ #--
157
+ # This file is generated from RFC3454, by rake. Don't edit directly.
158
+ #++
159
+
160
+ module Net::IMAP::SASL
161
+
162
+ module SASLprep
163
+
164
+ # 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
167
+ #
168
+ # Equal to \\StringPrep\\[\\"C.1.2\\"].
169
+ # Redefined here to avoid loading the StringPrep module.
170
+ MAP_TO_SPACE = #{regex_str "C.1.2"}
171
+
172
+ # RFC4013 §2.1 Mapping - mapped to nothing
173
+ # the "commonly mapped to nothing" characters (\\StringPrep\\[\\"B.1\\"])
174
+ # that can be mapped to nothing.
175
+ #
176
+ # Equal to \\StringPrep\\[\\"B.1\\"].
177
+ # Redefined here to avoid loading the StringPrep module.
178
+ MAP_TO_NOTHING = #{regex_str "B.1"}
179
+
180
+ # RFC4013 §2.3 Prohibited Output::
181
+ # * Non-ASCII space characters — \\StringPrep\\[\\"C.1.2\\"]
182
+ # * ASCII control characters — \\StringPrep\\[\\"C.2.1\\"]
183
+ # * Non-ASCII control characters — \\StringPrep\\[\\"C.2.2\\"]
184
+ # * Private Use characters — \\StringPrep\\[\\"C.3\\"]
185
+ # * Non-character code points — \\StringPrep\\[\\"C.4\\"]
186
+ # * Surrogate code points — \\StringPrep\\[\\"C.5\\"]
187
+ # * Inappropriate for plain text characters — \\StringPrep\\[\\"C.6\\"]
188
+ # * Inappropriate for canonical representation characters — \\StringPrep\\[\\"C.7\\"]
189
+ # * Change display properties or deprecated characters — \\StringPrep\\[\\"C.8\\"]
190
+ # * Tagging characters — \\StringPrep\\[\\"C.9\\"]
191
+ TABLES_PROHIBITED = #{SASL_TABLES_PROHIBITED.inspect}.freeze
192
+
193
+ # Adds unassigned (by Unicode 3.2) codepoints to TABLES_PROHIBITED.
194
+ #
195
+ # RFC4013 §2.5 Unassigned Code Points::
196
+ # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its list of
197
+ # unassigned code points.
198
+ TABLES_PROHIBITED_STORED = ["A.1", *TABLES_PROHIBITED].freeze
199
+
200
+ # Matches codepoints prohibited by RFC4013 §2.3.
201
+ #
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.
206
+ PROHIBITED_OUTPUT = #{regex_str(*SASL_TABLES_PROHIBITED)}
207
+
208
+ # RFC4013 §2.5 Unassigned Code Points::
209
+ # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its list of
210
+ # unassigned code points.
211
+ UNASSIGNED = #{regex_str "A.1"}
212
+
213
+ # Matches codepoints prohibited by RFC4013 §2.3 and §2.5.
214
+ #
215
+ # See TABLES_PROHIBITED_STORED.
216
+ PROHIBITED_OUTPUT_STORED = Regexp.union(
217
+ UNASSIGNED, PROHIBITED_OUTPUT
218
+ ).freeze
219
+
220
+ # Bidirectional Characters [StringPrep, §6]
221
+ BIDI_FAILURE = #{bidi_failure_regexp.inspect}.freeze
222
+
223
+ # Matches strings prohibited by RFC4013 §2.3 and §2.4.
224
+ #
225
+ # This checks prohibited output and bidirectional characters.
226
+ PROHIBITED = Regexp.union(
227
+ PROHIBITED_OUTPUT, BIDI_FAILURE,
228
+ )
229
+
230
+ # Matches strings prohibited by RFC4013 §2.3, §2.4, and §2.5.
231
+ #
232
+ # This checks prohibited output, bidirectional characters, and
233
+ # unassigned codepoints.
234
+ PROHIBITED_STORED = Regexp.union(
235
+ PROHIBITED_OUTPUT_STORED, BIDI_FAILURE,
236
+ )
237
+
238
+ end
239
+ end
240
+ RUBY
241
+ end
242
+
243
+ private
244
+
245
+ def parse_rfc_text(rfc3454_text)
246
+ titles = {}
247
+ tables, = rfc3454_text
248
+ .lines
249
+ .each_with_object([]) {|line, acc|
250
+ current, table = acc.last
251
+ case line
252
+ when /^([A-D]\.[1-9](?:\.[1-9])?) (.*)/
253
+ titles[$1] = $2
254
+ when /^ {3}-{5} Start Table (\S*)/
255
+ acc << [$1, []]
256
+ when /^ {3}-{5} End Table /
257
+ acc << [nil, nil]
258
+ when /^ {3}([0-9A-F]+); ([ 0-9A-F]*)(?:;[^;]*)$/ # mapping tables
259
+ table << [$1, $2.split(/ +/)] if current
260
+ when /^ {3}([-0-9A-F]+)(?:;[^;]*)?$/ # regular tables
261
+ table << $1 if current
262
+ when /^ {3}(.*)/
263
+ raise "expected to match %p" % $1 if current
264
+ end
265
+ }
266
+ .to_h.compact
267
+ .transform_values {|t| t.first.size == 2 ? t.to_h : t }
268
+ tables["titles"] = titles
269
+ tables
270
+ end
271
+
272
+ def load_tables_and_titles_from_json!
273
+ require "json"
274
+ @tables = json_filename
275
+ .then(&File.method(:read))
276
+ .then(&JSON.method(:parse))
277
+ @titles = @tables.delete "titles"
278
+ [@tables, @titles]
279
+ end
280
+
281
+ def to_ranges(table)
282
+ (table.is_a?(Hash) ? table.keys : table)
283
+ .map{|range| range.split(?-).map{|cp| Integer cp, 16} }
284
+ .map{|s,e| s..(e || s)}
285
+ end
286
+
287
+ # Starting from a codepoints array (rather than ranges) to deduplicate merged
288
+ # tables.
289
+ def to_regexp(codepoints, negate: false)
290
+ codepoints
291
+ .grep_v(SURROGATES_RANGE) # remove surrogate codepoints from C.5 and D.2
292
+ .uniq
293
+ .sort
294
+ .chunk_while {|cp1,cp2| cp1 + 1 == cp2 } # find contiguous chunks
295
+ .map {|chunk| chunk.map{|cp| "%04x" % cp } } # convert to hex strings
296
+ .partition {|chunk| chunk[1] } # ranges vs singles
297
+ .then {|ranges, singles|
298
+ singles.flatten!
299
+ [
300
+ negate ? "^" : "",
301
+ singles.flatten.any? ? "\\u{%s}" % singles.join(" ") : "",
302
+ ranges.map {|r| "\\u{%s}-\\u{%s}" % [r.first, r.last] }.join,
303
+ codepoints.any?(SURROGATES_RANGE) ? "\\p{Cs}" : "", # not necessary :)
304
+ ].join
305
+ }
306
+ .then {|char_class| Regexp.new "[#{char_class}]" }
307
+ end
308
+
309
+ def asgn_regexps!
310
+ @asgn_regexps = {}
311
+ # preset the regexp for each table
312
+ asgn_regex "A.1", /\p{^AGE=3.2}/
313
+ # If ruby supported all unicode properties (i.e. line break = word joiner):
314
+ # /[\u{00ad 034f 1806}\p{join_c}\p{VS}\p{lb=WJ}&&\p{age=3.2}]/
315
+ asgn_table "B.1"
316
+ asgn_table "B.2"
317
+ asgn_table "B.3"
318
+ asgn_regex "C.1.1", / /
319
+ asgn_regex "C.1.2", /[\u200b\p{Zs}&&[^ ]]/
320
+ asgn_regex "C.2.1", /[\x00-\x1f\x7f]/
321
+ # C.2.2 is a union:
322
+ # Cc + Cf (as defined by Unicode 3.2) + Zl + Zp + 0xfffc
323
+ # - any codepoints covered by C.2.1 or C.8 or C.9
324
+ #
325
+ # But modern Unicode properties are significantly different, so it's better
326
+ # to just load the table definition.
327
+ asgn_table "C.2.2"
328
+ asgn_regex "C.3", /\p{private use}/
329
+ asgn_regex "C.4", /\p{noncharacter code point}/
330
+ asgn_regex "C.5", /\p{surrogate}/
331
+ asgn_regex "C.6", /[\p{in specials}&&\p{AGE=3.2}&&\p{^NChar}]/
332
+ asgn_regex "C.7", /[\p{in ideographic description characters}&&\p{AGE=3.2}]/
333
+ # C.8 is a union of \p{Bidi Control} and Unicode 3.2 properties. But those properties
334
+ # have changed for modern Unicode, and thus for modern ruby's regexp
335
+ # character properties. It's better to just load the table definition.
336
+ asgn_table "C.8"
337
+ asgn_regex "C.9", /[\p{in Tags}&&\p{AGE=3.2}]/
338
+ # Unfortunately, ruby doesn't (currently) support /[\p{Bidi
339
+ # Class=R}\p{bc=AL}]/. On the other hand, StringPrep (based on Unicode 3.2)
340
+ # might not be a good match for the modern (14.0) property value anyway.
341
+ asgn_table "D.1"
342
+ asgn_table "D.1", negate: true # used by BIDI_FAILS_REQ3
343
+ asgn_table "D.2"
344
+ @asgn_regexps
345
+ end
346
+
347
+ def regex_str(*names, negate: false)
348
+ "%p.freeze" % regexp_for(*names, negate: negate)
349
+ end
350
+
351
+ def asgn_table(name, negate: false)
352
+ asgn_regex(name, regexp_for(name, negate: negate), negate: negate)
353
+ end
354
+
355
+ def regexp_const_desc(name, negate: false)
356
+ if negate then "Matches the negation of the %s table" % [name]
357
+ else %q{%s \\StringPrep\\[\\"%s\\"]} % [titles.fetch(name), name]
358
+ end
359
+ end
360
+
361
+ def regexp_const_name(table_name, negate: false)
362
+ "IN_%s%s" % [table_name.tr(".", "_"), negate ? "_NEGATED" : ""]
363
+ end
364
+
365
+ def asgn_regex(name, regexp, negate: false)
366
+ asgn_regexps[[name, negate]] = regexp
367
+ "# %s\n%s%s = %p.freeze" % [
368
+ regexp_const_desc(name, negate: negate), " " * 4,
369
+ regexp_const_name(name, negate: negate),
370
+ regexp,
371
+ ]
372
+ end
373
+
374
+ def bidi_R_AL ; regexp_for "D.1" end
375
+ def bidi_not_R_AL ; regexp_for "D.1", negate: true end
376
+ def bidi_L ; regexp_for "D.2" end
377
+
378
+ 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
386
+ end
387
+
388
+ 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
396
+ end
397
+
398
+ # shares the bidi_R_AL definition between both req2 and req3
399
+ 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
413
+ end
414
+
415
+ SASL_TABLES_PROHIBITED = %w[
416
+ C.1.2 C.2.1 C.2.2 C.3 C.4 C.5 C.6 C.7 C.8 C.9
417
+ ].freeze
418
+
419
+ SASL_TABLES_PROHIBITED_STORED = %w[
420
+ A.1 C.1.2 C.2.1 C.2.2 C.3 C.4 C.5 C.6 C.7 C.8 C.9
421
+ ].freeze
422
+
423
+ end
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.1
4
+ version: 0.3.2
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: 2022-09-29 00:00:00.000000000 Z
12
+ date: 2022-12-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-protocol
@@ -25,6 +25,20 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: date
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
28
42
  - !ruby/object:Gem::Dependency
29
43
  name: digest
30
44
  requirement: !ruby/object:Gem::Requirement
@@ -68,6 +82,9 @@ files:
68
82
  - LICENSE.txt
69
83
  - README.md
70
84
  - Rakefile
85
+ - benchmarks/stringprep.yml
86
+ - benchmarks/table-regexps.yml
87
+ - docs/styles.css
71
88
  - lib/net/imap.rb
72
89
  - lib/net/imap/authenticators.rb
73
90
  - lib/net/imap/authenticators/cram_md5.rb
@@ -81,7 +98,16 @@ files:
81
98
  - lib/net/imap/flags.rb
82
99
  - lib/net/imap/response_data.rb
83
100
  - lib/net/imap/response_parser.rb
101
+ - lib/net/imap/sasl.rb
102
+ - lib/net/imap/sasl/saslprep.rb
103
+ - lib/net/imap/sasl/saslprep_tables.rb
104
+ - lib/net/imap/sasl/stringprep.rb
105
+ - lib/net/imap/sasl/stringprep_tables.rb
84
106
  - net-imap.gemspec
107
+ - rakelib/rdoc.rake
108
+ - rakelib/rfcs.rake
109
+ - rakelib/saslprep.rake
110
+ - rakelib/string_prep_tables_generator.rb
85
111
  homepage: https://github.com/ruby/net-imap
86
112
  licenses:
87
113
  - Ruby