net-imap 0.3.7 → 0.5.6

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/COPYING +56 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +3 -22
  6. data/README.md +25 -8
  7. data/Rakefile +0 -7
  8. data/docs/styles.css +72 -23
  9. data/lib/net/imap/authenticators.rb +26 -57
  10. data/lib/net/imap/command_data.rb +74 -54
  11. data/lib/net/imap/config/attr_accessors.rb +75 -0
  12. data/lib/net/imap/config/attr_inheritance.rb +90 -0
  13. data/lib/net/imap/config/attr_type_coercion.rb +61 -0
  14. data/lib/net/imap/config.rb +470 -0
  15. data/lib/net/imap/data_encoding.rb +18 -6
  16. data/lib/net/imap/data_lite.rb +226 -0
  17. data/lib/net/imap/deprecated_client_options.rb +142 -0
  18. data/lib/net/imap/errors.rb +27 -1
  19. data/lib/net/imap/esearch_result.rb +180 -0
  20. data/lib/net/imap/fetch_data.rb +597 -0
  21. data/lib/net/imap/flags.rb +1 -1
  22. data/lib/net/imap/response_data.rb +250 -440
  23. data/lib/net/imap/response_parser/parser_utils.rb +245 -0
  24. data/lib/net/imap/response_parser.rb +1867 -1184
  25. data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
  26. data/lib/net/imap/sasl/authentication_exchange.rb +139 -0
  27. data/lib/net/imap/sasl/authenticators.rb +122 -0
  28. data/lib/net/imap/sasl/client_adapter.rb +123 -0
  29. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +24 -14
  30. data/lib/net/imap/sasl/digest_md5_authenticator.rb +342 -0
  31. data/lib/net/imap/sasl/external_authenticator.rb +83 -0
  32. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  33. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +28 -18
  34. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
  35. data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
  36. data/lib/net/imap/sasl/protocol_adapters.rb +101 -0
  37. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  38. data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
  39. data/lib/net/imap/sasl/stringprep.rb +6 -66
  40. data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
  41. data/lib/net/imap/sasl.rb +148 -44
  42. data/lib/net/imap/sasl_adapter.rb +20 -0
  43. data/lib/net/imap/search_result.rb +146 -0
  44. data/lib/net/imap/sequence_set.rb +1565 -0
  45. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  46. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  47. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  48. data/lib/net/imap/stringprep/tables.rb +146 -0
  49. data/lib/net/imap/stringprep/trace.rb +85 -0
  50. data/lib/net/imap/stringprep.rb +159 -0
  51. data/lib/net/imap/uidplus_data.rb +244 -0
  52. data/lib/net/imap/vanished_data.rb +56 -0
  53. data/lib/net/imap.rb +2090 -823
  54. data/net-imap.gemspec +7 -8
  55. data/rakelib/benchmarks.rake +91 -0
  56. data/rakelib/rfcs.rake +2 -0
  57. data/rakelib/saslprep.rake +4 -4
  58. data/rakelib/string_prep_tables_generator.rb +84 -60
  59. data/sample/net-imap.rb +167 -0
  60. metadata +45 -49
  61. data/.github/dependabot.yml +0 -6
  62. data/.github/workflows/test.yml +0 -38
  63. data/.gitignore +0 -10
  64. data/benchmarks/stringprep.yml +0 -65
  65. data/benchmarks/table-regexps.yml +0 -39
  66. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  67. data/lib/net/imap/authenticators/plain.rb +0 -41
  68. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  69. data/lib/net/imap/sasl/saslprep.rb +0 -55
  70. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  71. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
data/net-imap.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  name = File.basename(__FILE__, ".gemspec")
4
4
  version = ["lib", Array.new(name.count("-"), "..").join("/")].find do |dir|
5
- break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
5
+ break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb"), :encoding=> 'utf-8') do |line|
6
6
  /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
7
7
  end rescue nil
8
8
  end
@@ -11,21 +11,23 @@ Gem::Specification.new do |spec|
11
11
  spec.name = name
12
12
  spec.version = version
13
13
  spec.authors = ["Shugo Maeda", "nicholas a. evans"]
14
- spec.email = ["shugo@ruby-lang.org", "nick@ekenosen.net"]
14
+ spec.email = ["shugo@ruby-lang.org", "nick@rubinick.dev"]
15
15
 
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(">= 3.1.0")
20
20
  spec.licenses = ["Ruby", "BSD-2-Clause"]
21
21
 
22
22
  spec.metadata["homepage_uri"] = spec.homepage
23
23
  spec.metadata["source_code_uri"] = spec.homepage
24
+ spec.metadata["changelog_uri"] = spec.homepage + "/releases"
24
25
 
25
26
  # Specify which files should be added to the gem when it is released.
26
27
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
27
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
28
- `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(bin|test|spec|features|rfcs)/}) }
28
+ spec.files = Dir.chdir(__dir__) do
29
+ `git ls-files -z 2>/dev/null`.split("\x0")
30
+ .grep_v(%r{^(\.git(ignore)?|\.mailmap|(\.github|bin|test|spec|benchmarks|features|rfcs)/)})
29
31
  end
30
32
  spec.bindir = "exe"
31
33
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -33,7 +35,4 @@ Gem::Specification.new do |spec|
33
35
 
34
36
  spec.add_dependency "net-protocol"
35
37
  spec.add_dependency "date"
36
-
37
- spec.add_development_dependency "digest"
38
- spec.add_development_dependency "strscan"
39
38
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ PARSER_TEST_FIXTURES = FileList.new "test/net/imap/fixtures/response_parser/*.yml"
4
+ CLOBBER.include "benchmarks/parser.yml"
5
+ CLEAN.include "benchmarks/Gemfile-*"
6
+
7
+ BENCHMARK_INIT = <<RUBY
8
+ require "yaml"
9
+ require "net/imap"
10
+
11
+ def load_response(file, name)
12
+ YAML.unsafe_load_file(file).dig(:tests, name, :response)
13
+ .force_encoding "ASCII-8BIT" \\
14
+ or abort "ERRORO: missing %p fixture data in %p" % [name, file]
15
+ end
16
+
17
+ parser = Net::IMAP::ResponseParser.new
18
+ RUBY
19
+
20
+ file "benchmarks/parser.yml" => PARSER_TEST_FIXTURES do |t|
21
+ require "yaml"
22
+ require "pathname"
23
+ require "net/imap"
24
+
25
+ path = Pathname.new(__dir__) / "../test/net/imap/fixtures/response_parser"
26
+ files = path.glob("*.yml")
27
+ tests = files.flat_map {|file|
28
+ file.read
29
+ .gsub(%r{([-:]) !ruby/(object|struct|array):\S+}) { $1 }
30
+ .then {
31
+ YAML.safe_load(_1, filename: file,
32
+ permitted_classes: [Symbol, Regexp], aliases: true)
33
+ }
34
+ .fetch(:tests)
35
+ .select {|test_name, test|
36
+ :parser_assert_equal == test.fetch(:test_type) {
37
+ test.key?(:expected) ? :parser_assert_equal : :parser_pending
38
+ }
39
+ }
40
+ .map {|test_name, test| [test_name.to_s, test.fetch(:response)] }
41
+ }
42
+
43
+ benchmarks = tests.map {|fixture_name, response|
44
+ {"name" => fixture_name.delete_prefix("test_"),
45
+ "prelude" => "response = -%s.b" % [response.dump],
46
+ "script" => "parser.parse(response)"}
47
+ }
48
+ .sort_by { _1["name"] }
49
+
50
+ YAML.dump({"prelude" => BENCHMARK_INIT, "benchmark" => benchmarks})
51
+ .then { File.write t.name, _1 }
52
+ end
53
+
54
+ namespace :benchmarks do
55
+ desc "Generate benchmarks from fixture data"
56
+ task :generate => "benchmarks/parser.yml"
57
+
58
+ desc "run the parser benchmarks comparing multiple gem versions"
59
+ task :compare => :generate do |task, args|
60
+ cd Pathname.new(__dir__) + ".."
61
+ current = `git describe --tags --dirty`.chomp
62
+ current = "dev" if current.empty?
63
+ versions = args.to_a
64
+ if versions.empty?
65
+ latest = %x{git describe --tags --abbrev=0 --match 'v*.*.*'}.chomp
66
+ versions = latest.empty? ? [] : [latest.delete_prefix("v")]
67
+ end
68
+ versions = versions.to_h { [_1, "Gemfile-v#{_1}"] }
69
+ cd "benchmarks" do
70
+ versions.each do |version, gemfile|
71
+ File.write gemfile, <<~RUBY
72
+ # frozen_string_literal: true
73
+ source "https://rubygems.org"
74
+ gem "net-imap", #{version.dump}
75
+ RUBY
76
+ end
77
+ versions = {current => "../Gemfile" , **versions}.map {
78
+ "%s::/usr/bin/env BUNDLE_GEMFILE=%s ruby" % _1
79
+ }.join(";")
80
+
81
+ extra = ENV.fetch("BENCHMARK_ARGS", "").shellsplit
82
+
83
+ sh("benchmark-driver",
84
+ "--bundler",
85
+ "-e", versions,
86
+ "parser.yml",
87
+ *extra)
88
+ end
89
+ end
90
+
91
+ end
data/rakelib/rfcs.rake CHANGED
@@ -145,6 +145,8 @@ RFCS = {
145
145
  8514 => "IMAP SAVEDATE",
146
146
  8970 => "IMAP PREVIEW",
147
147
  9208 => "IMAP QUOTA, QUOTA=, QUOTASET",
148
+ 9394 => "IMAP PARTIAL",
149
+ 9586 => "IMAP UIDONLY",
148
150
 
149
151
  # etc...
150
152
  3629 => "UTF8",
@@ -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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set" unless defined?(::Set)
4
+
3
5
  # Generator for stringprep regexps.
4
6
  #
5
7
  # Combines Unicode character classes with generated tables. Generated regexps
@@ -62,9 +64,9 @@ class StringPrepTablesGenerator
62
64
  # This file is generated from RFC3454, by rake. Don't edit directly.
63
65
  #++
64
66
 
65
- module Net::IMAP::SASL
67
+ module Net::IMAP::StringPrep
66
68
 
67
- module StringPrep
69
+ module Tables
68
70
 
69
71
  #{asgn_table "A.1"}
70
72
 
@@ -74,6 +76,12 @@ class StringPrepTablesGenerator
74
76
 
75
77
  #{asgn_table "B.3"}
76
78
 
79
+ #{asgn_mapping "B.1", ""}
80
+
81
+ #{asgn_mapping "B.2"}
82
+
83
+ #{asgn_mapping "B.3"}
84
+
77
85
  #{asgn_table "C.1.1"}
78
86
 
79
87
  #{asgn_table "C.1.2"}
@@ -105,14 +113,16 @@ class StringPrepTablesGenerator
105
113
 
106
114
  BIDI_DESC_REQ2 = "A string with RandALCat characters must not contain LCat characters."
107
115
 
108
- # Bidirectional Characters [StringPrep, §6], Requirement 2::
116
+ # Bidirectional Characters [StringPrep, §6], Requirement 2
117
+ # >>>
109
118
  # If a string contains any RandALCat character, the string MUST NOT
110
119
  # contain any LCat character.
111
120
  BIDI_FAILS_REQ2 = #{bidi_fails_req2.inspect}.freeze
112
121
 
113
122
  BIDI_DESC_REQ3 = "A string with RandALCat characters must start and end with RandALCat characters."
114
123
 
115
- # Bidirectional Characters [StringPrep, §6], Requirement 3::
124
+ # Bidirectional Characters [StringPrep, §6], Requirement 3
125
+ # >>>
116
126
  # If a string contains any RandALCat character, a RandALCat
117
127
  # character MUST be the first character of the string, and a
118
128
  # RandALCat character MUST be the last character of the string.
@@ -122,15 +132,21 @@ class StringPrepTablesGenerator
122
132
  BIDI_FAILURE = #{bidi_failure_regexp.inspect}.freeze
123
133
 
124
134
  # Names of each codepoint table in the RFC-3454 appendices
125
- TABLE_TITLES = {
135
+ TITLES = {
126
136
  #{table_titles_rb}
127
137
  }.freeze
128
138
 
129
139
  # Regexps matching each codepoint table in the RFC-3454 appendices
130
- TABLE_REGEXPS = {
140
+ REGEXPS = {
131
141
  #{table_regexps_rb}
132
142
  }.freeze
133
143
 
144
+ MAPPINGS = {
145
+ "B.1" => [IN_B_1, MAP_B_1].freeze,
146
+ "B.2" => [IN_B_2, MAP_B_2].freeze,
147
+ "B.3" => [IN_B_3, MAP_B_3].freeze,
148
+ }.freeze
149
+
134
150
  end
135
151
  end
136
152
  RUBY
@@ -157,27 +173,30 @@ class StringPrepTablesGenerator
157
173
  # This file is generated from RFC3454, by rake. Don't edit directly.
158
174
  #++
159
175
 
160
- module Net::IMAP::SASL
176
+ module Net::IMAP::StringPrep
161
177
 
162
178
  module SASLprep
163
179
 
164
180
  # 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
181
+ # >>>
182
+ # non-ASCII space characters (\\StringPrep\\[\\"C.1.2\\"]) that can
183
+ # be mapped to SPACE (U+0020)
167
184
  #
168
185
  # Equal to \\StringPrep\\[\\"C.1.2\\"].
169
- # Redefined here to avoid loading the StringPrep module.
186
+ # Redefined here to avoid loading StringPrep::Tables unless necessary.
170
187
  MAP_TO_SPACE = #{regex_str "C.1.2"}
171
188
 
172
189
  # RFC4013 §2.1 Mapping - mapped to nothing
173
- # the "commonly mapped to nothing" characters (\\StringPrep\\[\\"B.1\\"])
174
- # that can be mapped to nothing.
190
+ # >>>
191
+ # the "commonly mapped to nothing" characters
192
+ # (\\StringPrep\\[\\"B.1\\"]) that can be mapped to nothing.
175
193
  #
176
194
  # Equal to \\StringPrep\\[\\"B.1\\"].
177
- # Redefined here to avoid loading the StringPrep module.
195
+ # Redefined here to avoid loading StringPrep::Tables unless necessary.
178
196
  MAP_TO_NOTHING = #{regex_str "B.1"}
179
197
 
180
- # RFC4013 §2.3 Prohibited Output::
198
+ # RFC4013 §2.3 Prohibited Output
199
+ # >>>
181
200
  # * Non-ASCII space characters — \\StringPrep\\[\\"C.1.2\\"]
182
201
  # * ASCII control characters — \\StringPrep\\[\\"C.2.1\\"]
183
202
  # * Non-ASCII control characters — \\StringPrep\\[\\"C.2.2\\"]
@@ -192,45 +211,52 @@ class StringPrepTablesGenerator
192
211
 
193
212
  # Adds unassigned (by Unicode 3.2) codepoints to TABLES_PROHIBITED.
194
213
  #
195
- # RFC4013 §2.5 Unassigned Code Points::
196
- # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its list of
197
- # unassigned code points.
214
+ # RFC4013 §2.5 Unassigned Code Points
215
+ # >>>
216
+ # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its
217
+ # list of unassigned code points.
198
218
  TABLES_PROHIBITED_STORED = ["A.1", *TABLES_PROHIBITED].freeze
199
219
 
200
- # Matches codepoints prohibited by RFC4013 §2.3.
220
+ # A Regexp matching codepoints prohibited by RFC4013 §2.3.
201
221
  #
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.
222
+ # This combines all of the TABLES_PROHIBITED tables.
206
223
  PROHIBITED_OUTPUT = #{regex_str(*SASL_TABLES_PROHIBITED)}
207
224
 
208
- # RFC4013 §2.5 Unassigned Code Points::
209
- # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its list of
210
- # unassigned code points.
225
+ # RFC4013 §2.5 Unassigned Code Points
226
+ # >>>
227
+ # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its
228
+ # list of unassigned code points.
229
+ #
230
+ # Equal to \\StringPrep\\[\\"A.1\\"].
231
+ # Redefined here to avoid loading StringPrep::Tables unless necessary.
211
232
  UNASSIGNED = #{regex_str "A.1"}
212
233
 
213
- # Matches codepoints prohibited by RFC4013 §2.3 and §2.5.
234
+ # A Regexp matching codepoints prohibited by RFC4013 §2.3 and §2.5.
214
235
  #
215
- # See TABLES_PROHIBITED_STORED.
236
+ # This combines PROHIBITED_OUTPUT and UNASSIGNED.
216
237
  PROHIBITED_OUTPUT_STORED = Regexp.union(
217
238
  UNASSIGNED, PROHIBITED_OUTPUT
218
239
  ).freeze
219
240
 
220
241
  # Bidirectional Characters [StringPrep, §6]
242
+ #
243
+ # A Regexp for strings that don't satisfy StringPrep's Bidirectional
244
+ # Characters rules.
245
+ #
246
+ # Equal to StringPrep::Tables::BIDI_FAILURE.
247
+ # Redefined here to avoid loading StringPrep::Tables unless necessary.
221
248
  BIDI_FAILURE = #{bidi_failure_regexp.inspect}.freeze
222
249
 
223
- # Matches strings prohibited by RFC4013 §2.3 and §2.4.
250
+ # A Regexp matching strings prohibited by RFC4013 §2.3 and §2.4.
224
251
  #
225
- # This checks prohibited output and bidirectional characters.
252
+ # This combines PROHIBITED_OUTPUT and BIDI_FAILURE.
226
253
  PROHIBITED = Regexp.union(
227
254
  PROHIBITED_OUTPUT, BIDI_FAILURE,
228
255
  )
229
256
 
230
- # Matches strings prohibited by RFC4013 §2.3, §2.4, and §2.5.
257
+ # A Regexp matching strings prohibited by RFC4013 §2.3, §2.4, and §2.5.
231
258
  #
232
- # This checks prohibited output, bidirectional characters, and
233
- # unassigned codepoints.
259
+ # This combines PROHIBITED_OUTPUT_STORED and BIDI_FAILURE.
234
260
  PROHIBITED_STORED = Regexp.union(
235
261
  PROHIBITED_OUTPUT_STORED, BIDI_FAILURE,
236
262
  )
@@ -284,6 +310,15 @@ class StringPrepTablesGenerator
284
310
  .map{|s,e| s..(e || s)}
285
311
  end
286
312
 
313
+ # TODO: DRY with unicode_normalize
314
+ def to_map(table)
315
+ table = table.to_hash
316
+ .transform_keys { Integer _1, 16 }
317
+ .transform_keys { [_1].pack("U*") }
318
+ .transform_values {|cps| cps.map { Integer _1, 16 } }
319
+ .transform_values { _1.pack("U*") }
320
+ end
321
+
287
322
  # Starting from a codepoints array (rather than ranges) to deduplicate merged
288
323
  # tables.
289
324
  def to_regexp(codepoints, negate: false)
@@ -352,6 +387,13 @@ class StringPrepTablesGenerator
352
387
  asgn_regex(name, regexp_for(name, negate: negate), negate: negate)
353
388
  end
354
389
 
390
+ def asgn_mapping(name, replacement = to_map(tables[name]))
391
+ cname = name.tr(?., ?_).upcase
392
+ "# Replacements for %s\n%s%s = %p.freeze" % [
393
+ "IN_#{name}", " " * 2, "MAP_#{cname}", replacement,
394
+ ]
395
+ end
396
+
355
397
  def regexp_const_desc(name, negate: false)
356
398
  if negate then "Matches the negation of the %s table" % [name]
357
399
  else %q{%s \\StringPrep\\[\\"%s\\"]} % [titles.fetch(name), name]
@@ -376,40 +418,22 @@ class StringPrepTablesGenerator
376
418
  def bidi_L ; regexp_for "D.2" end
377
419
 
378
420
  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
421
+ Regexp.union(
422
+ /#{bidi_R_AL}.*?#{bidi_L}/mu, # RandALCat followed by LCat
423
+ /#{bidi_L}.*?#{bidi_R_AL}/mu, # RandALCat preceded by LCat
424
+ )
386
425
  end
387
426
 
388
427
  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
428
+ # contains RandALCat:
429
+ Regexp.union(
430
+ /\A#{bidi_not_R_AL}.*?#{bidi_R_AL}/mu, # but doesn't start with RandALCat
431
+ /#{bidi_R_AL}.*?#{bidi_not_R_AL}\z/mu, # but doesn't end with RandALCat
432
+ )
396
433
  end
397
434
 
398
- # shares the bidi_R_AL definition between both req2 and req3
399
435
  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
436
+ Regexp.union(bidi_fails_req2, bidi_fails_req3)
413
437
  end
414
438
 
415
439
  SASL_TABLES_PROHIBITED = %w[
@@ -0,0 +1,167 @@
1
+ require 'net/imap'
2
+ require "getoptlong"
3
+
4
+ $stdout.sync = true
5
+ $port = nil
6
+ $user = ENV["USER"] || ENV["LOGNAME"]
7
+ $auth = "login"
8
+ $ssl = false
9
+ $starttls = false
10
+
11
+ def usage
12
+ <<EOF
13
+ usage: #{$0} [options] <host>
14
+
15
+ --help print this message
16
+ --port=PORT specifies port
17
+ --user=USER specifies user
18
+ --auth=AUTH specifies auth type
19
+ --starttls use starttls
20
+ --ssl use ssl
21
+ EOF
22
+ end
23
+
24
+ begin
25
+ require 'io/console'
26
+ rescue LoadError
27
+ def _noecho(&block)
28
+ system("stty", "-echo")
29
+ begin
30
+ yield STDIN
31
+ ensure
32
+ system("stty", "echo")
33
+ end
34
+ end
35
+ else
36
+ def _noecho(&block)
37
+ STDIN.noecho(&block)
38
+ end
39
+ end
40
+
41
+ def get_password
42
+ print "password: "
43
+ begin
44
+ return _noecho(&:gets).chomp
45
+ ensure
46
+ puts
47
+ end
48
+ end
49
+
50
+ def get_command
51
+ printf("%s@%s> ", $user, $host)
52
+ if line = gets
53
+ return line.strip.split(/\s+/)
54
+ else
55
+ return nil
56
+ end
57
+ end
58
+
59
+ parser = GetoptLong.new
60
+ parser.set_options(['--debug', GetoptLong::NO_ARGUMENT],
61
+ ['--help', GetoptLong::NO_ARGUMENT],
62
+ ['--port', GetoptLong::REQUIRED_ARGUMENT],
63
+ ['--user', GetoptLong::REQUIRED_ARGUMENT],
64
+ ['--auth', GetoptLong::REQUIRED_ARGUMENT],
65
+ ['--starttls', GetoptLong::NO_ARGUMENT],
66
+ ['--ssl', GetoptLong::NO_ARGUMENT])
67
+ begin
68
+ parser.each_option do |name, arg|
69
+ case name
70
+ when "--port"
71
+ $port = arg
72
+ when "--user"
73
+ $user = arg
74
+ when "--auth"
75
+ $auth = arg
76
+ when "--ssl"
77
+ $ssl = true
78
+ when "--starttls"
79
+ $starttls = true
80
+ when "--debug"
81
+ Net::IMAP.debug = true
82
+ when "--help"
83
+ usage
84
+ exit
85
+ end
86
+ end
87
+ rescue
88
+ abort usage
89
+ end
90
+
91
+ $host = ARGV.shift
92
+ unless $host
93
+ abort usage
94
+ end
95
+
96
+ imap = Net::IMAP.new($host, :port => $port, :ssl => $ssl)
97
+ begin
98
+ imap.starttls if $starttls
99
+ class << password = method(:get_password)
100
+ alias to_str call
101
+ end
102
+ imap.authenticate($auth, $user, password)
103
+ while true
104
+ cmd, *args = get_command
105
+ break unless cmd
106
+ begin
107
+ case cmd
108
+ when "list"
109
+ for mbox in imap.list("", args[0] || "*")
110
+ if mbox.attr.include?(Net::IMAP::NOSELECT)
111
+ prefix = "!"
112
+ elsif mbox.attr.include?(Net::IMAP::MARKED)
113
+ prefix = "*"
114
+ else
115
+ prefix = " "
116
+ end
117
+ print prefix, mbox.name, "\n"
118
+ end
119
+ when "select"
120
+ imap.select(args[0] || "inbox")
121
+ print "ok\n"
122
+ when "close"
123
+ imap.close
124
+ print "ok\n"
125
+ when "summary"
126
+ unless messages = imap.responses["EXISTS"][-1]
127
+ puts "not selected"
128
+ next
129
+ end
130
+ if messages > 0
131
+ for data in imap.fetch(1..-1, ["ENVELOPE"])
132
+ print data.seqno, ": ", data.attr["ENVELOPE"].subject, "\n"
133
+ end
134
+ else
135
+ puts "no message"
136
+ end
137
+ when "fetch"
138
+ if args[0]
139
+ data = imap.fetch(args[0].to_i, ["RFC822.HEADER", "RFC822.TEXT"])[0]
140
+ puts data.attr["RFC822.HEADER"]
141
+ puts data.attr["RFC822.TEXT"]
142
+ else
143
+ puts "missing argument"
144
+ end
145
+ when "logout", "exit", "quit"
146
+ break
147
+ when "help", "?"
148
+ print <<EOF
149
+ list [pattern] list mailboxes
150
+ select [mailbox] select mailbox
151
+ close close mailbox
152
+ summary display summary
153
+ fetch [msgno] display message
154
+ logout logout
155
+ help, ? display help message
156
+ EOF
157
+ else
158
+ print "unknown command: ", cmd, "\n"
159
+ end
160
+ rescue Net::IMAP::Error
161
+ puts $!
162
+ end
163
+ end
164
+ ensure
165
+ imap.logout
166
+ imap.disconnect
167
+ end