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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +3 -0
- data/Rakefile +4 -1
- data/benchmarks/stringprep.yml +65 -0
- data/benchmarks/table-regexps.yml +39 -0
- data/docs/styles.css +36 -0
- data/lib/net/imap/command_data.rb +8 -11
- data/lib/net/imap/data_encoding.rb +101 -5
- data/lib/net/imap/errors.rb +1 -1
- data/lib/net/imap/flags.rb +104 -77
- data/lib/net/imap/response_data.rb +1077 -317
- data/lib/net/imap/response_parser.rb +66 -1
- data/lib/net/imap/sasl/saslprep.rb +55 -0
- data/lib/net/imap/sasl/saslprep_tables.rb +98 -0
- data/lib/net/imap/sasl/stringprep.rb +68 -0
- data/lib/net/imap/sasl/stringprep_tables.rb +153 -0
- data/lib/net/imap/sasl.rb +78 -0
- data/lib/net/imap.rb +272 -77
- data/net-imap.gemspec +2 -0
- data/rakelib/rdoc.rake +70 -0
- data/rakelib/rfcs.rake +166 -0
- data/rakelib/saslprep.rake +30 -0
- data/rakelib/string_prep_tables_generator.rb +423 -0
- metadata +28 -2
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.
|
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
|
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
|