rims 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e462eb67ba5bb6cdfa73906f100e9e11474e42e64616df8fc119a7dcaa879374
4
- data.tar.gz: 9f860520538eb8bd52aeac784dd668d6778a9c51f343ff368548225d955c6dca
3
+ metadata.gz: 7acc91008294ca9589a19ecb657d5a04cc9d21c17e6349e059dd371bba0f4dbb
4
+ data.tar.gz: f750c5ef8ea6064033d1b6e1e0329964db61e56f1e9e1535883a19fcb1738aa3
5
5
  SHA512:
6
- metadata.gz: b5d7aff45f4ce0d921e10136f2e161abd5f6ddf8307fc2f96909db0399cb3230f11ec073a6f401bc3f4d9a28432334a9845ae8da5e31bba30064cee2e78e33de
7
- data.tar.gz: b9f72aa820f34528f47383d4e3c74245747edf60b0c370a24f75b3f5189c740c0887366cab1b66f7ae8237819d3407d7a31d8cc0786c487d9778a801fdb66a8a
6
+ metadata.gz: 003c20745f575ba8801f6b72956b42b3329f45fc84f70fe68146767fae15a657f9a3cb1767f759877e8cf442738873a05182c835d697c702e4fad7a575b560d3
7
+ data.tar.gz: 449554fe7059796dd4fbbce1c433b7abc2a411ad5ee24acccc0618bb1043e2fb6b9a006dc0e93eb5c32813409a80b2bb677c37e39437e819153c38233cbaa194
data/ChangeLog CHANGED
@@ -1,5 +1,70 @@
1
+ 2019-06-10 TOKI Yoshinori <toki@freedom.ne.jp>
2
+
3
+ * RIMS version 0.2.5 is released.
4
+
5
+ 2019-06-07 TOKI Yoshinori <toki@freedom.ne.jp>
6
+
7
+ * lib/rims/protocol/decoder.rb: IMAP CLOSE command may not send
8
+ untagged EXPUNGE responses.
9
+
10
+ see: RFC 3501 / 6.4.2. CLOSE Command
11
+ <https://tools.ietf.org/html/rfc3501#section-6.4.2>
12
+
13
+ The CLOSE command permanently removes all messages that have the
14
+ \Deleted flag set from the currently selected mailbox, and returns
15
+ to the authenticated state from the selected state. No untagged
16
+ EXPUNGE responses are sent.
17
+
18
+ 2019-06-05 TOKI Yoshinori <toki@freedom.ne.jp>
19
+
20
+ * test/cmd/test_command.rb: IMAP server system test.
21
+
22
+ 2019-05-24 TOKI Yoshinori <toki@freedom.ne.jp>
23
+
24
+ * lib/rims/protocol/parser.rb: fix bodystructure response for
25
+ FETCH command. removed extra SP of bodystructure response on a
26
+ multipart message.
27
+
28
+ see: RFC 3501 / 9. Formal Syntax
29
+
30
+ body-type-mpart = 1*body SP media-subtype [SP body-ext-mpart]
31
+
32
+ `1*body' indicates that there is no extra separator.
33
+
34
+ 2019-05-22 TOKI Yoshinori <toki@freedom.ne.jp>
35
+
36
+ * lib/rims/protocol/parser.rb: date comparison search (BEFORE, ON,
37
+ SENTBEFORE, SENTON, SENTSINCE, SINCE) disregarding timezone. TEXT
38
+ keyword searches a multipart message.
39
+
40
+ 2019-05-12 TOKI Yoshinori <toki@freedom.ne.jp>
41
+
42
+ * lib/rims/cmd.rb, lib/rims/service.rb: add multi-process server
43
+ options to server command.
44
+
45
+ 2019-05-11 TOKI Yoshinori <toki@freedom.ne.jp>
46
+
47
+ * lib/rims/mail_store.rb, lib/rims/pool.rb: obsoleted mail store
48
+ holder. obsoleted mail store pool. obsoleted generic object pool.
49
+
50
+ 2019-05-10 TOKI Yoshinori <toki@freedom.ne.jp>
51
+
52
+ * lib/rims/mail_store.rb, lib/rims/protocol/decoder.rb,
53
+ lib/rims/service.rb: mail store pool is replaced by dRuby
54
+ services.
55
+
56
+ * lib/rims/protocol/decoder.rb: mail store holder is replaced by
57
+ decoder engine.
58
+
59
+ 2019-05-08 TOKI Yoshinori <toki@freedom.ne.jp>
60
+
61
+ * lib/rims/protocol/decoder.rb: decoder engine for dRuby services
62
+ is defined.
63
+
1
64
  2019-04-25 TOKI Yoshinori <toki@freedom.ne.jp>
2
65
 
66
+ * RIMS version 0.2.4 is released.
67
+
3
68
  * test/test_protocol_decoder.rb: integrated individual IMAP
4
69
  command test and IMAP command stream test.
5
70
 
@@ -18,11 +18,9 @@ module RIMS
18
18
  autoload :LockError, 'rims/lock'
19
19
  autoload :MailFolder, 'rims/mail_store'
20
20
  autoload :MailStore, 'rims/mail_store'
21
- autoload :MailStoreHolder, 'rims/mail_store'
22
21
  autoload :MailboxDB, 'rims/db'
23
22
  autoload :MessageDB, 'rims/db'
24
23
  autoload :MessageSetSyntaxError, 'rims/protocol'
25
- autoload :ObjectPool, 'rims/pool'
26
24
  autoload :Password, 'rims/passwd'
27
25
  autoload :Protocol, 'rims/protocol'
28
26
  autoload :ProtocolError, 'rims/protocol'
@@ -10,7 +10,7 @@ module RIMS
10
10
 
11
11
  def md5_cksum_parse(key, s)
12
12
  if (s) then
13
- s =~ /\Amd5 (\S+?)\n/ or raise "checksum format error at key: #{key}"
13
+ s =~ /\A md5 \s (\S+?) \n/x or raise "checksum format error at key: #{key}"
14
14
  md5_cksum = $1
15
15
  value = $'
16
16
  if (Digest::MD5.hexdigest(value) != md5_cksum) then
@@ -25,7 +25,7 @@ module RIMS
25
25
  def self.command_function(method_name, description)
26
26
  module_function(method_name)
27
27
  method_name = method_name.to_s
28
- unless (method_name =~ /^cmd_/) then
28
+ unless (method_name =~ /\A cmd_/x) then
29
29
  raise "invalid command function name: #{method_name}"
30
30
  end
31
31
  cmd_name = $'.gsub(/_/, '-')
@@ -62,7 +62,7 @@ module RIMS
62
62
  w = CMDs.keys.map{|k| k.length }.max + 4
63
63
  fmt = " %- #{w}s%s"
64
64
  CMDs.each do |cmd_name, cmd_entry|
65
- if ((! show_debug_command) && (cmd_name =~ /^debug/)) then
65
+ if ((! show_debug_command) && (cmd_name =~ /\A debug/x)) then
66
66
  next
67
67
  end
68
68
  puts format(fmt, cmd_name, cmd_entry[:description])
@@ -377,6 +377,40 @@ module RIMS
377
377
  })
378
378
  }
379
379
  end
380
+ options.on('--process-num=NUMBER',
381
+ Integer
382
+ ) do |num|
383
+ build.chain{|c|
384
+ c.load(server: {
385
+ process_num: num
386
+ })
387
+ }
388
+ end
389
+ options.on('--process-queue-size=SIZE',
390
+ Integer
391
+ ) do |size|
392
+ build.chain{|c|
393
+ c.load(server: {
394
+ process_queue_size: size
395
+ })
396
+ }
397
+ end
398
+ options.on('--process-queue-polling-timeout=SECONDS',
399
+ Float) do |seconds|
400
+ build.chain{|c|
401
+ c.load(server: {
402
+ process_queue_polling_timeout_seconds: seconds
403
+ })
404
+ }
405
+ end
406
+ options.on('--process-send-io-polling-timeout=SECONDS',
407
+ Float) do |seconds|
408
+ build.chain{|c|
409
+ c.load(server: {
410
+ process_send_io_polling_timeout_seconds: seconds
411
+ })
412
+ }
413
+ end
380
414
  options.on('--thread-num=NUMBER',
381
415
  Integer
382
416
  ) do |num|
@@ -431,12 +465,33 @@ module RIMS
431
465
  })
432
466
  }
433
467
  end
468
+ options.on('--drb-process-num=NUMBER',
469
+ Integer
470
+ ) do |num|
471
+ build.chain{|c|
472
+ c.load(drb_services: {
473
+ process_num: num
474
+ })
475
+ }
476
+ end
477
+ options.on('--bulk-response-count=COUNT',
478
+ Integer) do |count|
479
+ build.chain{|c|
480
+ c.load(drb_services: {
481
+ engine: {
482
+ bulk_response_count: count
483
+ }
484
+ })
485
+ }
486
+ end
434
487
  options.on('--read-lock-timeout=SECONDS',
435
488
  Float
436
489
  ) do |seconds|
437
490
  build.chain{|c|
438
- c.load(lock: {
439
- read_lock_timeout_seconds: seconds
491
+ c.load(drb_services: {
492
+ engine: {
493
+ read_lock_timeout_seconds: seconds
494
+ }
440
495
  })
441
496
  }
442
497
  end
@@ -444,8 +499,10 @@ module RIMS
444
499
  Float
445
500
  ) do |seconds|
446
501
  build.chain{|c|
447
- c.load(lock: {
448
- write_lock_timeout_seconds: seconds
502
+ c.load(drb_services: {
503
+ engine: {
504
+ write_lock_timeout_seconds: seconds
505
+ }
449
506
  })
450
507
  }
451
508
  end
@@ -453,8 +510,10 @@ module RIMS
453
510
  Float
454
511
  ) do |seconds|
455
512
  build.chain{|c|
456
- c.load(lock: {
457
- cleanup_write_lock_timeout_seconds: seconds
513
+ c.load(drb_services: {
514
+ engine: {
515
+ cleanup_write_lock_timeout_seconds: seconds
516
+ }
458
517
  })
459
518
  }
460
519
  end
@@ -119,9 +119,9 @@ module RIMS
119
119
 
120
120
  def self.write_lock_timeout_detach(first_timeout_seconds, detached_timeout_seconds, logger: Logger.new(STDOUT)) # yields: timeout_seconds
121
121
  begin
122
- logger.debug('ready to detach write-lock timeout.')
122
+ logger.debug('ready to detach write-lock timeout.') if logger.debug?
123
123
  yield(first_timeout_seconds)
124
- logger.debug('not detached write-lock timeout.')
124
+ logger.debug('not detached write-lock timeout.') if logger.debug?
125
125
  nil
126
126
  rescue WriteLockTimeoutError
127
127
  logger.warn($!)
@@ -32,6 +32,20 @@ module RIMS
32
32
  @channel = ServerResponseChannel.new
33
33
  end
34
34
 
35
+ def self.build(unique_user_id, kvs_meta_open, kvs_text_open)
36
+ kvs_build = proc{|kvs_open, db_name|
37
+ kvs_open.call(MAILBOX_DATA_STRUCTURE_VERSION, unique_user_id, db_name)
38
+ }
39
+
40
+ mail_store = MailStore.new(DB::Meta.new(kvs_build.call(kvs_meta_open, 'meta')),
41
+ DB::Message.new(kvs_build.call(kvs_text_open, 'message'))) {|mbox_id|
42
+ DB::Mailbox.new(kvs_build.call(kvs_meta_open, "mailbox_#{mbox_id}"))
43
+ }
44
+ mail_store.add_mbox('INBOX') unless mail_store.mbox_id('INBOX')
45
+
46
+ mail_store
47
+ end
48
+
35
49
  def_delegators :@rw_lock, :read_synchronize, :write_synchronize
36
50
 
37
51
  def get_mbox_db(mbox_id)
@@ -138,7 +152,7 @@ module RIMS
138
152
 
139
153
  def add_mbox(name)
140
154
  transaction{
141
- name = 'INBOX' if (name =~ /\AINBOX\z/i)
155
+ name = 'INBOX' if (name =~ /\A INBOX \z/xi)
142
156
  name = name.b
143
157
 
144
158
  mbox_id = @meta_db.add_mbox(name)
@@ -179,7 +193,7 @@ module RIMS
179
193
  old_name = @meta_db.mbox_name(mbox_id) or raise "not found a mailbox: #{mbox_id}."
180
194
  old_name = old_name.dup.force_encoding('utf-8')
181
195
 
182
- new_name = 'INBOX' if (new_name =~ /\AINBOX\z/i)
196
+ new_name = 'INBOX' if (new_name =~ /\A INBOX \z/xi)
183
197
  @meta_db.rename_mbox(mbox_id, new_name.b)
184
198
 
185
199
  @meta_db.cnum_succ!
@@ -195,7 +209,7 @@ module RIMS
195
209
  end
196
210
 
197
211
  def mbox_id(mbox_name)
198
- mbox_name = 'INBOX' if (mbox_name =~ /\AINBOX\z/i)
212
+ mbox_name = 'INBOX' if (mbox_name =~ /\A INBOX \z/xi)
199
213
  @meta_db.mbox_id(mbox_name.b)
200
214
  end
201
215
 
@@ -348,20 +362,9 @@ module RIMS
348
362
  }
349
363
  end
350
364
 
351
- def select_mbox(mbox_id)
365
+ def open_folder(mbox_id, read_only: false)
352
366
  @meta_db.mbox_name(mbox_id) or raise "not found a mailbox: #{mbox_id}."
353
- MailFolder.new(mbox_id, self).attach(@channel)
354
- end
355
-
356
- def examine_mbox(mbox_id)
357
- @meta_db.mbox_name(mbox_id) or raise "not found a mailbox: #{mbox_id}."
358
- MailFolder.new(mbox_id, self, read_only: true).attach(@channel)
359
- end
360
-
361
- def self.build_pool(kvs_meta_open, kvs_text_open)
362
- RIMS::ObjectPool.new{|object_pool, unique_user_id|
363
- RIMS::MailStoreHolder.build(object_pool, unique_user_id, kvs_meta_open, kvs_text_open)
364
- }
367
+ MailFolder.new(mbox_id, self, read_only: read_only).attach(@channel)
365
368
  end
366
369
  end
367
370
 
@@ -381,6 +384,16 @@ module RIMS
381
384
  @uid_map = nil
382
385
  end
383
386
 
387
+ attr_reader :mbox_id
388
+
389
+ def alive?
390
+ ! @mail_store.mbox_name(@mbox_id).nil?
391
+ end
392
+
393
+ def should_be_alive
394
+ alive? or raise "deleted folder: #{@mbox_id}"
395
+ end
396
+
384
397
  def attach(server_response_channel)
385
398
  @pub, @sub = server_response_channel.make_pub_sub_pair(@mbox_id)
386
399
  self
@@ -415,8 +428,6 @@ module RIMS
415
428
  @mail_store.cnum != @cnum
416
429
  end
417
430
 
418
- attr_reader :mbox_id
419
-
420
431
  def [](msg_idx)
421
432
  @msg_list[msg_idx]
422
433
  end
@@ -501,6 +512,13 @@ module RIMS
501
512
  end
502
513
  end
503
514
  end
515
+ @cnum = nil
516
+ @msg_list = nil
517
+ @uid_map = nil
518
+ self
519
+ end
520
+
521
+ def detach
504
522
  @mail_store = nil
505
523
  @pub.detach
506
524
  @sub.detach
@@ -523,9 +541,9 @@ module RIMS
523
541
 
524
542
  def self.parse_msg_seq(msg_seq_desc, last_number)
525
543
  case (msg_seq_desc)
526
- when /\A(\d+|\*)\z/
544
+ when /\A (\d+|\*) \z/x
527
545
  msg_seq_pair = [ $&, $& ]
528
- when /\A(\d+|\*):(\d+|\*)\z/
546
+ when /\A (\d+|\*):(\d+|\*) \z/x
529
547
  msg_seq_pair = [ $1, $2 ]
530
548
  else
531
549
  raise MessageSetSyntaxError, "invalid message sequence format: #{msg_seq_desc}"
@@ -559,38 +577,6 @@ module RIMS
559
577
  msg_set
560
578
  end
561
579
  end
562
-
563
- class MailStoreHolder < ObjectPool::ObjectHolder
564
- extend Forwardable
565
-
566
- def self.build(object_pool, unique_user_id, kvs_meta_open, kvs_text_open)
567
- kvs_build = proc{|kvs_open, db_name|
568
- kvs_open.call(MAILBOX_DATA_STRUCTURE_VERSION, unique_user_id, db_name)
569
- }
570
-
571
- mail_store = MailStore.new(DB::Meta.new(kvs_build.call(kvs_meta_open, 'meta')),
572
- DB::Message.new(kvs_build.call(kvs_text_open, 'message'))) {|mbox_id|
573
- DB::Mailbox.new(kvs_build.call(kvs_meta_open, "mailbox_#{mbox_id}"))
574
- }
575
- mail_store.add_mbox('INBOX') unless mail_store.mbox_id('INBOX')
576
-
577
- new(object_pool, unique_user_id, mail_store)
578
- end
579
-
580
- def initialize(object_pool, unique_user_id, mail_store)
581
- super(object_pool, unique_user_id)
582
- @mail_store = mail_store
583
- end
584
-
585
- alias unique_user_id object_key
586
- attr_reader :mail_store
587
-
588
- def_delegators :@mail_store, :read_synchronize, :write_synchronize
589
-
590
- def object_destroy
591
- @mail_store.close
592
- end
593
- end
594
580
  end
595
581
 
596
582
  # Local Variables:
@@ -22,15 +22,21 @@ module RIMS
22
22
  end
23
23
 
24
24
  def self.repl(decoder, limits, input, output, logger)
25
+ input_gets = input.method(:gets)
26
+ output_write = lambda{|res|
27
+ last_line = nil
28
+ for data in res
29
+ logger.debug("response data: #{Protocol.io_data_log(data)}") if logger.debug?
30
+ output << data
31
+ last_line = data
32
+ end
33
+ output.flush
34
+
35
+ last_line
36
+ }
25
37
  response_write = proc{|res|
26
38
  begin
27
- last_line = nil
28
- for data in res
29
- logger.debug("response data: #{Protocol.io_data_log(data)}") if logger.debug?
30
- output << data
31
- last_line = data
32
- end
33
- output.flush
39
+ last_line = output_write.call(res)
34
40
  logger.info("server response: #{last_line.strip}")
35
41
  rescue
36
42
  logger.error('response write error.')
@@ -68,6 +74,13 @@ module RIMS
68
74
  when 'LOGIN'
69
75
  log_opt_args = opt_args.dup
70
76
  log_opt_args[-1] = '********'
77
+ when 'AUTHENTICATE'
78
+ if (opt_args[1]) then
79
+ log_opt_args = opt_args.dup
80
+ log_opt_args[1] = '********'
81
+ else
82
+ log_opt_args = opt_args
83
+ end
71
84
  else
72
85
  log_opt_args = opt_args
73
86
  end
@@ -93,9 +106,9 @@ module RIMS
93
106
  response_write.call([ "#{tag} BAD empty uid parameter\r\n" ])
94
107
  end
95
108
  when :authenticate
96
- decoder.authenticate(tag, input, output, *opt_args) {|res| response_write.call(res) }
109
+ decoder.authenticate(tag, input_gets, output_write, *opt_args) {|res| response_write.call(res) }
97
110
  when :idle
98
- decoder.idle(tag, input, output, conn_timer, *opt_args) {|res| response_write.call(res) }
111
+ decoder.idle(tag, input_gets, output_write, conn_timer, *opt_args) {|res| response_write.call(res) }
99
112
  else
100
113
  decoder.__send__(name, tag, *opt_args) {|res| response_write.call(res) }
101
114
  end
@@ -128,14 +141,18 @@ module RIMS
128
141
 
129
142
  nil
130
143
  ensure
131
- decoder.cleanup
144
+ # don't forget to clean up if the next decoder has been generated
145
+ decoder.next_decoder.cleanup
132
146
  end
133
147
 
134
148
  def initialize(auth, logger)
135
149
  @auth = auth
136
150
  @logger = logger
151
+ @next_decoder = self
137
152
  end
138
153
 
154
+ attr_reader :next_decoder
155
+
139
156
  def response_stream(tag)
140
157
  Enumerator.new{|res|
141
158
  begin
@@ -156,12 +173,12 @@ module RIMS
156
173
  end
157
174
  private :response_stream
158
175
 
159
- def guard_error(tag, imap_command, *args, **name_args)
176
+ def guard_error(imap_command, tag, *args, **kw_args, &block)
160
177
  begin
161
- if (name_args.empty?) then
162
- __send__(imap_command, tag, *args) {|res| yield(res) }
178
+ if (kw_args.empty?) then
179
+ __send__(imap_command, tag, *args, &block)
163
180
  else
164
- __send__(imap_command, tag, *args, **name_args) {|res| yield(res) }
181
+ __send__(imap_command, tag, *args, **kw_args, &block)
165
182
  end
166
183
  rescue SyntaxError
167
184
  @logger.error('client command syntax error.')
@@ -203,63 +220,71 @@ module RIMS
203
220
  end
204
221
  private :kw_params
205
222
 
206
- def imap_command(name)
207
- name = name.to_sym
208
-
223
+ def should_be_imap_command(name)
209
224
  cmd = to_imap_command(name)
210
- IMAP_CMDs[cmd] = name
225
+ unless (IMAP_CMDs.key? cmd) then
226
+ raise ArgumentError, "not an IMAP command: #{name}"
227
+ end
211
228
 
212
229
  method = instance_method(name)
213
- if (kw_params(method).find(:uid)) then
214
- IMAP_CMDs['UID'] = :uid
215
- UID_CMDs[cmd] = name
230
+ if (UID_CMDs.key? cmd) then
231
+ unless (kw_params(method).include? :uid) then
232
+ raise ArgumentError, "not defined `uid' keyword parameter: #{name}"
233
+ end
234
+ else
235
+ if (kw_params(method).include? :uid) then
236
+ raise ArgumentError, "not allowed `uid' keyword parameter: #{name}"
237
+ end
216
238
  end
217
239
 
240
+ nil
241
+ end
242
+ private :should_be_imap_command
243
+
244
+ def imap_command(name)
245
+ should_be_imap_command(name)
218
246
  orig_name = "_#{name}".to_sym
219
247
  alias_method orig_name, name
220
- define_method name, lambda{|tag, *args, **name_args, &block|
221
- guard_error(tag, orig_name, *args, **name_args, &block)
248
+ define_method name, lambda{|tag, *args, **kw_args, &block|
249
+ guard_error(orig_name, tag, *args, **kw_args, &block)
222
250
  }
223
-
224
- name
251
+ name.to_sym
225
252
  end
226
253
  private :imap_command
227
254
 
228
- def should_be_imap_command(name)
229
- unless (IMAP_CMDs.key? to_imap_command(name)) then
230
- raise ArgumentError, "not an IMAP command: #{name}"
231
- end
232
- end
233
- private :should_be_imap_command
234
-
235
- def fetch_mail_store_holder_and_on_demand_recovery(mail_store_pool, username,
236
- write_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
237
- logger: Logger.new(STDOUT))
255
+ def make_engine_and_recovery_if_needed(services, username,
256
+ logger: Logger.new(STDOUT))
238
257
  unique_user_id = Authentication.unique_user_id(username)
239
258
  logger.debug("unique user ID: #{username} -> #{unique_user_id}") if logger.debug?
240
259
 
241
- mail_store_holder = mail_store_pool.get(unique_user_id) {
242
- logger.info("open mail store: #{unique_user_id} [ #{username} ]")
243
- }
260
+ logger.info("open mail store: #{unique_user_id} [ #{username} ]")
261
+ engine = services[:engine, unique_user_id]
244
262
 
245
- mail_store_holder.write_synchronize(write_lock_timeout_seconds) {
246
- if (mail_store_holder.mail_store.abort_transaction?) then
247
- logger.warn("user data recovery start: #{username}")
248
- yield("* OK [ALERT] start user data recovery.\r\n")
249
- mail_store_holder.mail_store.recovery_data(logger: logger).sync
250
- logger.warn("user data recovery end: #{username}")
251
- yield("* OK completed user data recovery.\r\n")
252
- end
253
- }
263
+ begin
264
+ engine.recovery_if_needed(username) {|msg| yield(msg) }
265
+ rescue
266
+ engine.destroy
267
+ raise
268
+ end
254
269
 
255
- mail_store_holder
270
+ engine
256
271
  end
257
272
  end
258
273
 
274
+ def make_logout_response(tag)
275
+ [ "* BYE server logout\r\n",
276
+ "#{tag} OK LOGOUT completed\r\n"
277
+ ]
278
+ end
279
+ private :make_logout_response
280
+
259
281
  def ok_greeting
260
282
  yield([ "* OK RIMS v#{VERSION} IMAP4rev1 service ready.\r\n" ])
261
283
  end
262
284
 
285
+ # common IMAP command
286
+ IMAP_CMDs['CAPABILITY'] = :capability
287
+
263
288
  def capability(tag)
264
289
  capability_list = %w[ IMAP4rev1 UIDPLUS IDLE ]
265
290
  capability_list += @auth.capability.map{|auth_capability| "AUTH=#{auth_capability}" }
@@ -269,29 +294,40 @@ module RIMS
269
294
  yield(res)
270
295
  end
271
296
  imap_command :capability
272
-
273
- def next_decoder
274
- self
275
- end
276
297
  end
277
298
 
278
299
  class InitialDecoder < Decoder
279
- def initialize(mail_store_pool, auth, logger,
280
- mail_delivery_user: Service::DEFAULT_CONFIG.mail_delivery_user,
281
- write_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
282
- **next_decoder_optional)
300
+ class << self
301
+ def imap_command(name)
302
+ name = name.to_sym
303
+
304
+ cmd = to_imap_command(name)
305
+ Decoder::IMAP_CMDs[cmd] = name
306
+
307
+ method = instance_method(name)
308
+ if (kw_params(method).include? :uid) then
309
+ Decoder::IMAP_CMDs['UID'] = :uid
310
+ Decoder::UID_CMDs[cmd] = name
311
+ end
312
+
313
+ orig_name = "_#{name}".to_sym
314
+ alias_method orig_name, name
315
+ define_method name, lambda{|tag, *args, **kw_args, &block|
316
+ guard_error(orig_name, tag, *args, **kw_args, &block)
317
+ }
318
+
319
+ name
320
+ end
321
+ private :imap_command
322
+ end
323
+
324
+ def initialize(services, auth, logger,
325
+ mail_delivery_user: Service::DEFAULT_CONFIG.mail_delivery_user)
283
326
  super(auth, logger)
284
- @next_decoder = self
285
- @mail_store_pool = mail_store_pool
286
- @folder = nil
287
- @auth = auth
327
+ @services = services
288
328
  @mail_delivery_user = mail_delivery_user
289
- @write_lock_timeout_seconds = write_lock_timeout_seconds
290
- @next_decoder_optional = next_decoder_optional
291
329
  end
292
330
 
293
- attr_reader :next_decoder
294
-
295
331
  def auth?
296
332
  false
297
333
  end
@@ -304,10 +340,10 @@ module RIMS
304
340
  nil
305
341
  end
306
342
 
307
- def not_authenticated_response(tag)
343
+ def make_not_authenticated_response(tag)
308
344
  [ "#{tag} NO not authenticated\r\n" ]
309
345
  end
310
- private :not_authenticated_response
346
+ private :make_not_authenticated_response
311
347
 
312
348
  def noop(tag)
313
349
  yield([ "#{tag} OK NOOP completed\r\n" ])
@@ -315,38 +351,26 @@ module RIMS
315
351
  imap_command :noop
316
352
 
317
353
  def logout(tag)
318
- cleanup
319
- res = []
320
- res << "* BYE server logout\r\n"
321
- res << "#{tag} OK LOGOUT completed\r\n"
322
- yield(res)
354
+ @next_decoder = LogoutDecoder.new(self)
355
+ yield(make_logout_response(tag))
323
356
  end
324
357
  imap_command :logout
325
358
 
326
359
  def accept_authentication(username)
327
- cleanup
328
-
329
360
  case (username)
330
361
  when @mail_delivery_user
331
362
  @logger.info("mail delivery user: #{username}")
332
- MailDeliveryDecoder.new(@mail_store_pool, @auth, @logger,
333
- write_lock_timeout_seconds: @write_lock_timeout_seconds,
334
- **@next_decoder_optional)
363
+ MailDeliveryDecoder.new(self, @services, @auth, @logger)
335
364
  else
336
- mail_store_holder =
337
- self.class.fetch_mail_store_holder_and_on_demand_recovery(@mail_store_pool, username,
338
- write_lock_timeout_seconds: @write_lock_timeout_seconds,
339
- logger: @logger) {|msg| yield(msg) }
340
- UserMailboxDecoder.new(self, mail_store_holder, @auth, @logger,
341
- write_lock_timeout_seconds: @write_lock_timeout_seconds,
342
- **@next_decoder_optional)
365
+ engine = self.class.make_engine_and_recovery_if_needed(@services, username, logger: @logger) {|msg| yield(msg) }
366
+ UserMailboxDecoder.new(self, engine, @auth, @logger)
343
367
  end
344
368
  end
345
369
  private :accept_authentication
346
370
 
347
- def authenticate(tag, client_response_input_stream, server_challenge_output_stream,
371
+ def authenticate(tag, client_response_input_gets, server_challenge_output_write,
348
372
  auth_type, inline_client_response_data_base64=nil)
349
- auth_reader = AuthenticationReader.new(@auth, client_response_input_stream, server_challenge_output_stream, @logger)
373
+ auth_reader = AuthenticationReader.new(@auth, client_response_input_gets, server_challenge_output_write, @logger)
350
374
  if (username = auth_reader.authenticate_client(auth_type, inline_client_response_data_base64)) then
351
375
  if (username != :*) then
352
376
  yield response_stream(tag) {|res|
@@ -378,177 +402,119 @@ module RIMS
378
402
  imap_command :login
379
403
 
380
404
  def select(tag, mbox_name)
381
- yield(not_authenticated_response(tag))
405
+ yield(make_not_authenticated_response(tag))
382
406
  end
383
407
  imap_command :select
384
408
 
385
409
  def examine(tag, mbox_name)
386
- yield(not_authenticated_response(tag))
410
+ yield(make_not_authenticated_response(tag))
387
411
  end
388
412
  imap_command :examine
389
413
 
390
414
  def create(tag, mbox_name)
391
- yield(not_authenticated_response(tag))
415
+ yield(make_not_authenticated_response(tag))
392
416
  end
393
417
  imap_command :create
394
418
 
395
419
  def delete(tag, mbox_name)
396
- yield(not_authenticated_response(tag))
420
+ yield(make_not_authenticated_response(tag))
397
421
  end
398
422
  imap_command :delete
399
423
 
400
424
  def rename(tag, src_name, dst_name)
401
- yield(not_authenticated_response(tag))
425
+ yield(make_not_authenticated_response(tag))
402
426
  end
403
427
  imap_command :rename
404
428
 
405
429
  def subscribe(tag, mbox_name)
406
- yield(not_authenticated_response(tag))
430
+ yield(make_not_authenticated_response(tag))
407
431
  end
408
432
  imap_command :subscribe
409
433
 
410
434
  def unsubscribe(tag, mbox_name)
411
- yield(not_authenticated_response(tag))
435
+ yield(make_not_authenticated_response(tag))
412
436
  end
413
437
  imap_command :unsubscribe
414
438
 
415
439
  def list(tag, ref_name, mbox_name)
416
- yield(not_authenticated_response(tag))
440
+ yield(make_not_authenticated_response(tag))
417
441
  end
418
442
  imap_command :list
419
443
 
420
444
  def lsub(tag, ref_name, mbox_name)
421
- yield(not_authenticated_response(tag))
445
+ yield(make_not_authenticated_response(tag))
422
446
  end
423
447
  imap_command :lsub
424
448
 
425
449
  def status(tag, mbox_name, data_item_group)
426
- yield(not_authenticated_response(tag))
450
+ yield(make_not_authenticated_response(tag))
427
451
  end
428
452
  imap_command :status
429
453
 
430
454
  def append(tag, mbox_name, *opt_args, msg_text)
431
- yield(not_authenticated_response(tag))
455
+ yield(make_not_authenticated_response(tag))
432
456
  end
433
457
  imap_command :append
434
458
 
435
459
  def check(tag)
436
- yield(not_authenticated_response(tag))
460
+ yield(make_not_authenticated_response(tag))
437
461
  end
438
462
  imap_command :check
439
463
 
440
464
  def close(tag)
441
- yield(not_authenticated_response(tag))
465
+ yield(make_not_authenticated_response(tag))
442
466
  end
443
467
  imap_command :close
444
468
 
445
469
  def expunge(tag)
446
- yield(not_authenticated_response(tag))
470
+ yield(make_not_authenticated_response(tag))
447
471
  end
448
472
  imap_command :expunge
449
473
 
450
474
  def search(tag, *cond_args, uid: false)
451
- yield(not_authenticated_response(tag))
475
+ yield(make_not_authenticated_response(tag))
452
476
  end
453
477
  imap_command :search
454
478
 
455
479
  def fetch(tag, msg_set, data_item_group, uid: false)
456
- yield(not_authenticated_response(tag))
480
+ yield(make_not_authenticated_response(tag))
457
481
  end
458
482
  imap_command :fetch
459
483
 
460
484
  def store(tag, msg_set, data_item_name, data_item_value, uid: false)
461
- yield(not_authenticated_response(tag))
485
+ yield(make_not_authenticated_response(tag))
462
486
  end
463
487
  imap_command :store
464
488
 
465
489
  def copy(tag, msg_set, mbox_name, uid: false)
466
- yield(not_authenticated_response(tag))
490
+ yield(make_not_authenticated_response(tag))
467
491
  end
468
492
  imap_command :copy
469
493
 
470
- def idle(tag, client_input_stream, server_output_stream, connection_timer)
471
- yield(not_authenticated_response(tag))
494
+ def idle(tag, client_input_gets, server_output_write, connection_timer)
495
+ yield(make_not_authenticated_response(tag))
472
496
  end
473
497
  imap_command :idle
474
498
  end
475
499
 
476
- class AuthenticatedDecoder < Decoder
477
- def authenticate(tag, client_response_input_stream, server_challenge_output_stream,
478
- auth_type, inline_client_response_data_base64=nil, &block)
479
- yield([ "#{tag} NO duplicated authentication\r\n" ])
480
- end
481
- imap_command :authenticate
482
-
483
- def login(tag, username, password, &block)
484
- yield([ "#{tag} NO duplicated login\r\n" ])
485
- end
486
- imap_command :login
487
- end
488
-
489
- class UserMailboxDecoder < AuthenticatedDecoder
490
- def initialize(parent_decoder, mail_store_holder, auth, logger,
491
- read_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
492
- write_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
493
- cleanup_write_lock_timeout_seconds: 1)
494
- super(auth, logger)
500
+ class LogoutDecoder < Decoder
501
+ def initialize(parent_decoder)
495
502
  @parent_decoder = parent_decoder
496
- @mail_store_holder = mail_store_holder
497
- @read_lock_timeout_seconds = read_lock_timeout_seconds
498
- @write_lock_timeout_seconds = write_lock_timeout_seconds
499
- @cleanup_write_lock_timeout_seconds = cleanup_write_lock_timeout_seconds
500
- @folder = nil
501
503
  end
502
504
 
503
- def get_mail_store
504
- @mail_store_holder.mail_store
505
+ def next_decoder
506
+ self
505
507
  end
506
- private :get_mail_store
507
508
 
508
509
  def auth?
509
- @mail_store_holder != nil
510
+ false
510
511
  end
511
512
 
512
513
  def selected?
513
- @folder != nil
514
- end
515
-
516
- def alive_folder?
517
- get_mail_store.mbox_name(@folder.mbox_id) != nil
518
- end
519
- private :alive_folder?
520
-
521
- def close_folder(&block)
522
- if (auth? && selected? && alive_folder?) then
523
- @folder.reload if @folder.updated?
524
- @folder.close(&block)
525
- @folder = nil
526
- end
527
-
528
- nil
514
+ false
529
515
  end
530
- private :close_folder
531
516
 
532
517
  def cleanup
533
- unless (@mail_store_holder.nil?) then
534
- begin
535
- @mail_store_holder.write_synchronize(@cleanup_write_lock_timeout_seconds) {
536
- close_folder
537
- @mail_store_holder.mail_store.sync
538
- }
539
- rescue WriteLockTimeoutError
540
- @logger.warn("give up to close folder becaue of write-lock timeout over #{@write_lock_timeout_seconds} seconds")
541
- @folder = nil
542
- end
543
- tmp_mail_store_holder = @mail_store_holder
544
- ReadWriteLock.write_lock_timeout_detach(@cleanup_write_lock_timeout_seconds, @write_lock_timeout_seconds, logger: @logger) {|timeout_seconds|
545
- tmp_mail_store_holder.return_pool{
546
- @logger.info("close mail store: #{tmp_mail_store_holder.unique_user_id}")
547
- }
548
- }
549
- @mail_store_holder = nil
550
- end
551
-
552
518
  unless (@parent_decoder.nil?) then
553
519
  @parent_decoder.cleanup
554
520
  @parent_decoder = nil
@@ -557,471 +523,760 @@ module RIMS
557
523
  nil
558
524
  end
559
525
 
560
- def should_be_alive_folder
561
- alive_folder? or raise "deleted folder: #{@folder.mbox_id}"
562
- end
563
- private :should_be_alive_folder
564
-
565
- def guard_authenticated(tag, imap_command, *args, exclusive: false, **name_args)
566
- if (auth?) then
567
- if (exclusive.nil?) then
568
- guard_error(tag, imap_command, *args, **name_args) {|res|
569
- yield(res)
570
- }
571
- else
572
- begin
573
- if (exclusive) then
574
- @mail_store_holder.write_synchronize(@write_lock_timeout_seconds) {
575
- guard_authenticated(tag, imap_command, *args, exclusive: nil, **name_args) {|res|
576
- yield(res)
577
- }
578
- }
579
- else
580
- @mail_store_holder.read_synchronize(@read_lock_timeout_seconds){
581
- guard_authenticated(tag, imap_command, *args, exclusive: nil, **name_args) {|res|
582
- yield(res)
583
- }
584
- }
585
- end
586
- rescue ReadLockTimeoutError
587
- @logger.error("write-lock timeout over #{@write_lock_timeout_seconds} seconds")
588
- yield([ "#{tag} BAD write-lock timeout over #{@write_lock_timeout_seconds} seconds" ])
589
- rescue WriteLockTimeoutError
590
- @logger.error("read-lock timeout over #{@read_lock_timeout_seconds} seconds")
591
- yield([ "#{tag} BAD read-lock timeout over #{@read_lock_timeout_seconds} seconds" ])
592
- end
593
- end
594
- else
595
- yield([ "#{tag} NO not authenticated\r\n" ])
596
- end
597
- end
598
- private :guard_authenticated
599
-
600
- def guard_selected(tag, imap_command, *args, **name_args)
601
- if (selected?) then
602
- guard_authenticated(tag, imap_command, *args, **name_args) {|res|
603
- yield(res)
604
- }
605
- else
606
- yield([ "#{tag} NO not selected\r\n" ])
607
- end
608
- end
609
- private :guard_selected
610
-
611
- class << self
612
- def imap_command_authenticated(name, **guard_optional)
613
- should_be_imap_command(name)
614
- orig_name = "_#{name}".to_sym
615
- alias_method orig_name, name
616
- define_method name, lambda{|tag, *args, **name_args, &block|
617
- guard_authenticated(tag, orig_name, *args, **name_args.merge(guard_optional), &block)
618
- }
619
- name.to_sym
620
- end
621
- private :imap_command_authenticated
622
-
623
- def imap_command_selected(name, **guard_optional)
624
- should_be_imap_command(name)
625
- orig_name = "_#{name}".to_sym
626
- alias_method orig_name, name
627
- define_method name, lambda{|tag, *args, **name_args, &block|
628
- guard_selected(tag, orig_name, *args, **name_args.merge(guard_optional), &block)
629
- }
630
- name.to_sym
631
- end
632
- private :imap_command_selected
526
+ def capability(tag)
527
+ raise ProtocolError, 'invalid command in logout state.'
633
528
  end
529
+ imap_command :capability
634
530
 
635
531
  def noop(tag)
636
- res = []
637
- if (auth? && selected?) then
638
- begin
639
- @mail_store_holder.read_synchronize(@read_lock_timeout_seconds) {
640
- @folder.server_response_fetch{|r| res << r } if @folder.server_response?
641
- }
642
- rescue ReadLockTimeoutError
643
- @logger.warn("give up to get folder status because of read-lock timeout over #{@read_lock_timeout_seconds} seconds")
644
- end
645
- end
646
- res << "#{tag} OK NOOP completed\r\n"
647
- yield(res)
532
+ raise ProtocolError, 'invalid command in logout state.'
648
533
  end
649
534
  imap_command :noop
650
535
 
651
536
  def logout(tag)
652
- cleanup
653
- res = []
654
- res << "* BYE server logout\r\n"
655
- res << "#{tag} OK LOGOUT completed\r\n"
656
- yield(res)
537
+ raise ProtocolError, 'invalid command in logout state.'
657
538
  end
658
539
  imap_command :logout
659
540
 
660
- def folder_open_msgs
661
- all_msgs = get_mail_store.mbox_msg_num(@folder.mbox_id)
662
- recent_msgs = get_mail_store.mbox_flag_num(@folder.mbox_id, 'recent')
663
- unseen_msgs = all_msgs - get_mail_store.mbox_flag_num(@folder.mbox_id, 'seen')
664
- yield("* #{all_msgs} EXISTS\r\n")
665
- yield("* #{recent_msgs} RECENT\r\n")
666
- yield("* OK [UNSEEN #{unseen_msgs}]\r\n")
667
- yield("* OK [UIDVALIDITY #{@folder.mbox_id}]\r\n")
668
- yield("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n")
669
- nil
541
+ def authenticate(tag, client_response_input_gets, server_challenge_output_write,
542
+ auth_type, inline_client_response_data_base64=nil)
543
+ raise ProtocolError, 'invalid command in logout state.'
544
+ end
545
+ imap_command :authenticate
546
+
547
+ def login(tag, username, password)
548
+ raise ProtocolError, 'invalid command in logout state.'
670
549
  end
671
- private :folder_open_msgs
550
+ imap_command :login
672
551
 
673
552
  def select(tag, mbox_name)
674
- res = []
675
- @folder = nil
676
- mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
677
- if (id = get_mail_store.mbox_id(mbox_name_utf8)) then
678
- @folder = get_mail_store.select_mbox(id)
679
- folder_open_msgs do |msg|
680
- res << msg
681
- end
682
- res << "#{tag} OK [READ-WRITE] SELECT completed\r\n"
683
- else
684
- res << "#{tag} NO not found a mailbox\r\n"
685
- end
686
- yield(res)
553
+ raise ProtocolError, 'invalid command in logout state.'
687
554
  end
688
- imap_command_authenticated :select
555
+ imap_command :select
689
556
 
690
557
  def examine(tag, mbox_name)
691
- res = []
692
- @folder = nil
693
- mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
694
- if (id = get_mail_store.mbox_id(mbox_name_utf8)) then
695
- @folder = get_mail_store.examine_mbox(id)
696
- folder_open_msgs do |msg|
697
- res << msg
698
- end
699
- res << "#{tag} OK [READ-ONLY] EXAMINE completed\r\n"
700
- else
701
- res << "#{tag} NO not found a mailbox\r\n"
702
- end
703
- yield(res)
558
+ raise ProtocolError, 'invalid command in logout state.'
704
559
  end
705
- imap_command_authenticated :examine
560
+ imap_command :examine
706
561
 
707
562
  def create(tag, mbox_name)
708
- res = []
709
- @folder.server_response_fetch{|r| res << r } if selected?
710
- mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
711
- if (get_mail_store.mbox_id(mbox_name_utf8)) then
712
- res << "#{tag} NO duplicated mailbox\r\n"
713
- else
714
- get_mail_store.add_mbox(mbox_name_utf8)
715
- res << "#{tag} OK CREATE completed\r\n"
716
- end
717
- yield(res)
563
+ raise ProtocolError, 'invalid command in logout state.'
718
564
  end
719
- imap_command_authenticated :create, exclusive: true
565
+ imap_command :create
720
566
 
721
567
  def delete(tag, mbox_name)
722
- res = []
723
- @folder.server_response_fetch{|r| res << r } if selected?
724
- mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
725
- if (id = get_mail_store.mbox_id(mbox_name_utf8)) then
726
- if (id != get_mail_store.mbox_id('INBOX')) then
727
- get_mail_store.del_mbox(id)
728
- res << "#{tag} OK DELETE completed\r\n"
729
- else
730
- res << "#{tag} NO not delete inbox\r\n"
731
- end
732
- else
733
- res << "#{tag} NO not found a mailbox\r\n"
734
- end
735
- yield(res)
568
+ raise ProtocolError, 'invalid command in logout state.'
736
569
  end
737
- imap_command_authenticated :delete, exclusive: true
570
+ imap_command :delete
738
571
 
739
572
  def rename(tag, src_name, dst_name)
740
- res = []
741
- @folder.server_response_fetch{|r| res << r } if selected?
742
- src_name_utf8 = Net::IMAP.decode_utf7(src_name)
743
- dst_name_utf8 = Net::IMAP.decode_utf7(dst_name)
744
- unless (id = get_mail_store.mbox_id(src_name_utf8)) then
745
- return yield(res << "#{tag} NO not found a mailbox\r\n")
746
- end
747
- if (id == get_mail_store.mbox_id('INBOX')) then
748
- return yield(res << "#{tag} NO not rename inbox\r\n")
749
- end
750
- if (get_mail_store.mbox_id(dst_name_utf8)) then
751
- return yield(res << "#{tag} NO duplicated mailbox\r\n")
752
- end
753
- get_mail_store.rename_mbox(id, dst_name_utf8)
754
- return yield(res << "#{tag} OK RENAME completed\r\n")
573
+ raise ProtocolError, 'invalid command in logout state.'
755
574
  end
756
- imap_command_authenticated :rename, exclusive: true
575
+ imap_command :rename
757
576
 
758
577
  def subscribe(tag, mbox_name)
759
- res = []
760
- @folder.server_response_fetch{|r| res << r } if selected?
761
- mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
762
- if (_mbox_id = get_mail_store.mbox_id(mbox_name_utf8)) then
763
- res << "#{tag} OK SUBSCRIBE completed\r\n"
764
- else
765
- res << "#{tag} NO not found a mailbox\r\n"
766
- end
767
- yield(res)
578
+ raise ProtocolError, 'invalid command in logout state.'
768
579
  end
769
- imap_command_authenticated :subscribe
580
+ imap_command :subscribe
770
581
 
771
582
  def unsubscribe(tag, mbox_name)
772
- res = []
773
- @folder.server_response_fetch{|r| res << r } if selected?
774
- if (_mbox_id = get_mail_store.mbox_id(mbox_name)) then
775
- res << "#{tag} NO not implemented subscribe/unsbscribe command\r\n"
776
- else
777
- res << "#{tag} NO not found a mailbox\r\n"
778
- end
779
- yield(res)
780
- end
781
- imap_command_authenticated :unsubscribe
782
-
783
- def list_mbox(ref_name, mbox_name)
784
- ref_name_utf8 = Net::IMAP.decode_utf7(ref_name)
785
- mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
786
-
787
- mbox_filter = Protocol.compile_wildcard(mbox_name_utf8)
788
- mbox_list = get_mail_store.each_mbox_id.map{|id| [ id, get_mail_store.mbox_name(id) ] }
789
- mbox_list.keep_if{|id, name| name.start_with? ref_name_utf8 }
790
- mbox_list.keep_if{|id, name| name[(ref_name_utf8.length)..-1] =~ mbox_filter }
791
-
792
- for id, name_utf8 in mbox_list
793
- name = Net::IMAP.encode_utf7(name_utf8)
794
- attrs = '\Noinferiors'
795
- if (get_mail_store.mbox_flag_num(id, 'recent') > 0) then
796
- attrs << ' \Marked'
797
- else
798
- attrs << ' \Unmarked'
799
- end
800
- yield("(#{attrs}) NIL #{Protocol.quote(name)}")
801
- end
802
-
803
- nil
583
+ raise ProtocolError, 'invalid command in logout state.'
804
584
  end
805
- private :list_mbox
585
+ imap_command :unsubscribe
806
586
 
807
587
  def list(tag, ref_name, mbox_name)
808
- res = []
809
- @folder.server_response_fetch{|r| res << r } if selected?
810
- if (mbox_name.empty?) then
811
- res << "* LIST (\\Noselect) NIL \"\"\r\n"
812
- else
813
- list_mbox(ref_name, mbox_name) do |mbox_entry|
814
- res << "* LIST #{mbox_entry}\r\n"
815
- end
816
- end
817
- res << "#{tag} OK LIST completed\r\n"
818
- yield(res)
588
+ raise ProtocolError, 'invalid command in logout state.'
819
589
  end
820
- imap_command_authenticated :list
590
+ imap_command :list
821
591
 
822
592
  def lsub(tag, ref_name, mbox_name)
823
- res = []
824
- @folder.server_response_fetch{|r| res << r } if selected?
825
- if (mbox_name.empty?) then
826
- res << "* LSUB (\\Noselect) NIL \"\"\r\n"
827
- else
828
- list_mbox(ref_name, mbox_name) do |mbox_entry|
829
- res << "* LSUB #{mbox_entry}\r\n"
830
- end
831
- end
832
- res << "#{tag} OK LSUB completed\r\n"
833
- yield(res)
593
+ raise ProtocolError, 'invalid command in logout state.'
834
594
  end
835
- imap_command_authenticated :lsub
595
+ imap_command :lsub
836
596
 
837
597
  def status(tag, mbox_name, data_item_group)
838
- res = []
839
- @folder.server_response_fetch{|r| res << r } if selected?
840
- mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
841
- if (id = get_mail_store.mbox_id(mbox_name_utf8)) then
842
- unless ((data_item_group.is_a? Array) && (data_item_group[0] == :group)) then
843
- raise SyntaxError, 'second arugment is not a group list.'
844
- end
845
-
846
- values = []
847
- for item in data_item_group[1..-1]
848
- case (item.upcase)
849
- when 'MESSAGES'
850
- values << 'MESSAGES' << get_mail_store.mbox_msg_num(id)
851
- when 'RECENT'
852
- values << 'RECENT' << get_mail_store.mbox_flag_num(id, 'recent')
853
- when 'UIDNEXT'
854
- values << 'UIDNEXT' << get_mail_store.uid(id)
855
- when 'UIDVALIDITY'
856
- values << 'UIDVALIDITY' << id
857
- when 'UNSEEN'
858
- unseen_flags = get_mail_store.mbox_msg_num(id) - get_mail_store.mbox_flag_num(id, 'seen')
859
- values << 'UNSEEN' << unseen_flags
860
- else
861
- raise SyntaxError, "unknown status data: #{item}"
862
- end
863
- end
598
+ raise ProtocolError, 'invalid command in logout state.'
599
+ end
600
+ imap_command :status
864
601
 
865
- res << "* STATUS #{Protocol.quote(mbox_name)} (#{values.join(' ')})\r\n"
866
- res << "#{tag} OK STATUS completed\r\n"
867
- else
868
- res << "#{tag} NO not found a mailbox\r\n"
869
- end
870
- yield(res)
602
+ def append(tag, mbox_name, *opt_args, msg_text)
603
+ raise ProtocolError, 'invalid command in logout state.'
871
604
  end
872
- imap_command_authenticated :status
605
+ imap_command :append
873
606
 
874
- def mailbox_size_server_response_multicast_push(mbox_id)
875
- all_msgs = get_mail_store.mbox_msg_num(mbox_id)
876
- recent_msgs = get_mail_store.mbox_flag_num(mbox_id, 'recent')
607
+ def check(tag)
608
+ raise ProtocolError, 'invalid command in logout state.'
609
+ end
610
+ imap_command :check
877
611
 
878
- f = get_mail_store.examine_mbox(mbox_id)
879
- begin
880
- f.server_response_multicast_push("* #{all_msgs} EXISTS\r\n")
881
- f.server_response_multicast_push("* #{recent_msgs} RECENT\r\n")
882
- ensure
883
- f.close
884
- end
612
+ def close(tag)
613
+ raise ProtocolError, 'invalid command in logout state.'
614
+ end
615
+ imap_command :close
885
616
 
886
- nil
617
+ def expunge(tag)
618
+ raise ProtocolError, 'invalid command in logout state.'
887
619
  end
888
- private :mailbox_size_server_response_multicast_push
620
+ imap_command :expunge
889
621
 
890
- def append(tag, mbox_name, *opt_args, msg_text)
891
- res = []
892
- mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
893
- if (mbox_id = get_mail_store.mbox_id(mbox_name_utf8)) then
894
- msg_flags = []
895
- msg_date = Time.now
896
-
897
- if ((! opt_args.empty?) && (opt_args[0].is_a? Array)) then
898
- opt_flags = opt_args.shift
899
- if (opt_flags[0] != :group) then
900
- raise SyntaxError, 'bad flag list.'
901
- end
902
- for flag_atom in opt_flags[1..-1]
903
- case (flag_atom.upcase)
904
- when '\ANSWERED'
905
- msg_flags << 'answered'
906
- when '\FLAGGED'
907
- msg_flags << 'flagged'
908
- when '\DELETED'
909
- msg_flags << 'deleted'
910
- when '\SEEN'
911
- msg_flags << 'seen'
912
- when '\DRAFT'
913
- msg_flags << 'draft'
622
+ def search(tag, *cond_args, uid: false)
623
+ raise ProtocolError, 'invalid command in logout state.'
624
+ end
625
+ imap_command :search
626
+
627
+ def fetch(tag, msg_set, data_item_group, uid: false)
628
+ raise ProtocolError, 'invalid command in logout state.'
629
+ end
630
+ imap_command :fetch
631
+
632
+ def store(tag, msg_set, data_item_name, data_item_value, uid: false)
633
+ raise ProtocolError, 'invalid command in logout state.'
634
+ end
635
+ imap_command :store
636
+
637
+ def copy(tag, msg_set, mbox_name, uid: false)
638
+ raise ProtocolError, 'invalid command in logout state.'
639
+ end
640
+ imap_command :copy
641
+
642
+ def idle(tag, client_input_gets, server_output_write, connection_timer)
643
+ raise ProtocolError, 'invalid command in logout state.'
644
+ end
645
+ imap_command :idle
646
+ end
647
+
648
+ class AuthenticatedDecoder < Decoder
649
+ def authenticate(tag, client_response_input_gets, server_challenge_output_write,
650
+ auth_type, inline_client_response_data_base64=nil, &block)
651
+ yield([ "#{tag} NO duplicated authentication\r\n" ])
652
+ end
653
+ imap_command :authenticate
654
+
655
+ def login(tag, username, password, &block)
656
+ yield([ "#{tag} NO duplicated login\r\n" ])
657
+ end
658
+ imap_command :login
659
+ end
660
+
661
+ class UserMailboxDecoder < AuthenticatedDecoder
662
+ class Engine
663
+ def initialize(unique_user_id, mail_store, logger,
664
+ bulk_response_count: 100,
665
+ read_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
666
+ write_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
667
+ cleanup_write_lock_timeout_seconds: 1)
668
+ @unique_user_id = unique_user_id
669
+ @mail_store = mail_store
670
+ @logger = logger
671
+ @bulk_response_count = bulk_response_count
672
+ @read_lock_timeout_seconds = read_lock_timeout_seconds
673
+ @write_lock_timeout_seconds = write_lock_timeout_seconds
674
+ @cleanup_write_lock_timeout_seconds = cleanup_write_lock_timeout_seconds
675
+ @folders = {}
676
+ end
677
+
678
+ attr_reader :unique_user_id
679
+ attr_reader :mail_store # for test only
680
+
681
+ def recovery_if_needed(username)
682
+ @mail_store.write_synchronize(@write_lock_timeout_seconds) {
683
+ if (@mail_store.abort_transaction?) then
684
+ @logger.warn("user data recovery start: #{username}")
685
+ yield("* OK [ALERT] start user data recovery.\r\n")
686
+ @mail_store.recovery_data(logger: @logger).sync
687
+ @logger.warn("user data recovery end: #{username}")
688
+ yield("* OK completed user data recovery.\r\n")
689
+
690
+ self
691
+ end
692
+ }
693
+ end
694
+
695
+ def open_folder(mbox_id, read_only: false)
696
+ folder = @mail_store.open_folder(mbox_id, read_only: read_only)
697
+ token = folder.object_id
698
+ if (@folders.key? token) then
699
+ raise "internal error: duplicated folder token: #{token}"
700
+ end
701
+ @folders[token] = folder
702
+
703
+ token
704
+ end
705
+ private :open_folder
706
+
707
+ def close_folder(token)
708
+ folder = @folders.delete(token) or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
709
+ folder.reload if folder.updated?
710
+ begin
711
+ if (block_given?) then
712
+ saved_recent_msgs = @mail_store.mbox_flag_num(folder.mbox_id, 'recent')
713
+ folder.close do |msg_num|
714
+ yield("* #{msg_num} EXPUNGE\r\n")
715
+ end
716
+ last_recent_msgs = @mail_store.mbox_flag_num(folder.mbox_id, 'recent')
717
+ if (last_recent_msgs != saved_recent_msgs) then
718
+ yield("* #{last_recent_msgs} RECENT\r\n")
719
+ end
720
+ else
721
+ folder.close
722
+ end
723
+ ensure
724
+ folder.detach
725
+ end
726
+ @mail_store.sync
727
+
728
+ nil
729
+ end
730
+ private :close_folder
731
+
732
+ def cleanup(token)
733
+ if (token) then
734
+ begin
735
+ @mail_store.write_synchronize(@cleanup_write_lock_timeout_seconds) {
736
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
737
+ close_folder(token) do |untagged_response|
738
+ folder.server_response_multicast_push(untagged_response)
739
+ end
740
+ }
741
+ rescue WriteLockTimeoutError
742
+ @logger.warn("give up to close folder becaue of write-lock timeout over #{@write_lock_timeout_seconds} seconds")
743
+ @folders.delete(token)
744
+ end
745
+ end
746
+
747
+ nil
748
+ end
749
+
750
+ def destroy
751
+ tmp_mail_store = @mail_store
752
+ ReadWriteLock.write_lock_timeout_detach(@cleanup_write_lock_timeout_seconds, @write_lock_timeout_seconds, logger: @logger) {|timeout_seconds|
753
+ @mail_store.write_synchronize(timeout_seconds) {
754
+ @logger.info("close mail store: #{@unique_user_id}")
755
+ tmp_mail_store.close
756
+ }
757
+ }
758
+ @mail_store = nil
759
+
760
+ nil
761
+ end
762
+
763
+ def guard_authenticated(imap_command, token, tag, *args, exclusive: false, **kw_args, &block)
764
+ if (exclusive.nil?) then
765
+ if (kw_args.empty?) then
766
+ __send__(imap_command, token, tag, *args, &block)
767
+ else
768
+ __send__(imap_command, token, tag, *args, **kw_args, &block)
769
+ end
770
+ else
771
+ begin
772
+ if (exclusive) then
773
+ @mail_store.write_synchronize(@write_lock_timeout_seconds) {
774
+ guard_authenticated(imap_command, token, tag, *args, exclusive: nil, **kw_args, &block)
775
+ }
914
776
  else
915
- raise SyntaxError, "invalid flag: #{flag_atom}"
777
+ @mail_store.read_synchronize(@read_lock_timeout_seconds){
778
+ guard_authenticated(imap_command, token, tag, *args, exclusive: nil, **kw_args, &block)
779
+ }
916
780
  end
781
+ rescue ReadLockTimeoutError
782
+ @logger.error("write-lock timeout over #{@write_lock_timeout_seconds} seconds")
783
+ yield([ "#{tag} BAD write-lock timeout over #{@write_lock_timeout_seconds} seconds" ])
784
+ rescue WriteLockTimeoutError
785
+ @logger.error("read-lock timeout over #{@read_lock_timeout_seconds} seconds")
786
+ yield([ "#{tag} BAD read-lock timeout over #{@read_lock_timeout_seconds} seconds" ])
917
787
  end
918
788
  end
789
+ end
790
+ private :guard_authenticated
791
+
792
+ def guard_selected(imap_command, token, tag, *args, **kw_args, &block)
793
+ if (token) then
794
+ guard_authenticated(imap_command, token, tag, *args, **kw_args, &block)
795
+ else
796
+ yield([ "#{tag} NO not selected\r\n" ])
797
+ end
798
+ end
799
+ private :guard_selected
800
+
801
+ class << self
802
+ def imap_command_authenticated(name, **guard_optional)
803
+ orig_name = "_#{name}".to_sym
804
+ alias_method orig_name, name
805
+ define_method name, lambda{|token, tag, *args, **kw_args, &block|
806
+ guard_authenticated(orig_name, token, tag, *args, **kw_args, **guard_optional, &block)
807
+ }
808
+ name.to_sym
809
+ end
810
+ private :imap_command_authenticated
811
+
812
+ def imap_command_selected(name, **guard_optional)
813
+ orig_name = "_#{name}".to_sym
814
+ alias_method orig_name, name
815
+ define_method name, lambda{|token, tag, *args, **kw_args, &block|
816
+ guard_selected(orig_name, token, tag, *args, **kw_args, **guard_optional, &block)
817
+ }
818
+ name.to_sym
819
+ end
820
+ private :imap_command_selected
821
+ end
919
822
 
920
- if ((! opt_args.empty?) && (opt_args[0].is_a? String)) then
823
+ def noop(token, tag)
824
+ res = []
825
+ if (token) then
826
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
921
827
  begin
922
- msg_date = Time.parse(opt_args.shift)
923
- rescue ArgumentError
924
- raise SyntaxError, $!.message
828
+ @mail_store.read_synchronize(@read_lock_timeout_seconds) {
829
+ folder.server_response_fetch{|r| res << r } if folder.server_response?
830
+ }
831
+ rescue ReadLockTimeoutError
832
+ @logger.warn("give up to get folder status because of read-lock timeout over #{@read_lock_timeout_seconds} seconds")
925
833
  end
926
834
  end
835
+ res << "#{tag} OK NOOP completed\r\n"
836
+ yield(res)
837
+ end
838
+
839
+ def folder_open_msgs(token)
840
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
841
+ all_msgs = @mail_store.mbox_msg_num(folder.mbox_id)
842
+ recent_msgs = @mail_store.mbox_flag_num(folder.mbox_id, 'recent')
843
+ unseen_msgs = all_msgs - @mail_store.mbox_flag_num(folder.mbox_id, 'seen')
844
+ yield("* #{all_msgs} EXISTS\r\n")
845
+ yield("* #{recent_msgs} RECENT\r\n")
846
+ yield("* OK [UNSEEN #{unseen_msgs}]\r\n")
847
+ yield("* OK [UIDVALIDITY #{folder.mbox_id}]\r\n")
848
+ yield("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n")
849
+ nil
850
+ end
851
+ private :folder_open_msgs
927
852
 
928
- unless (opt_args.empty?) then
929
- raise SyntaxError, "unknown option: #{opt_args.inspect}"
853
+ def select(token, tag, mbox_name)
854
+ if (token) then
855
+ close_no_response(token)
930
856
  end
931
857
 
932
- uid = get_mail_store.add_msg(mbox_id, msg_text, msg_date)
933
- for flag_name in msg_flags
934
- get_mail_store.set_msg_flag(mbox_id, uid, flag_name, true)
858
+ res = []
859
+ new_token = nil
860
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
861
+
862
+ if (id = @mail_store.mbox_id(mbox_name_utf8)) then
863
+ new_token = open_folder(id)
864
+ folder_open_msgs(new_token) do |msg|
865
+ res << msg
866
+ end
867
+ res << "#{tag} OK [READ-WRITE] SELECT completed\r\n"
868
+ else
869
+ res << "#{tag} NO not found a mailbox\r\n"
935
870
  end
936
- mailbox_size_server_response_multicast_push(mbox_id)
871
+ yield(res)
937
872
 
938
- @folder.server_response_fetch{|r| res << r } if selected?
939
- res << "#{tag} OK [APPENDUID #{mbox_id} #{uid}] APPEND completed\r\n"
940
- else
941
- @folder.server_response_fetch{|r| res << r } if selected?
942
- res << "#{tag} NO [TRYCREATE] not found a mailbox\r\n"
873
+ new_token
943
874
  end
944
- yield(res)
945
- end
946
- imap_command_authenticated :append, exclusive: true
875
+ imap_command_authenticated :select
947
876
 
948
- def check(tag)
949
- res = []
950
- @folder.server_response_fetch{|r| res << r }
951
- get_mail_store.sync
952
- res << "#{tag} OK CHECK completed\r\n"
953
- yield(res)
954
- end
955
- imap_command_selected :check, exclusive: true
877
+ def examine(token, tag, mbox_name)
878
+ if (token) then
879
+ close_no_response(token)
880
+ end
956
881
 
957
- def close(tag, &block)
958
- yield response_stream(tag) {|res|
959
- @folder.server_response_fetch{|r| res << r }
960
- close_folder do |msg_num|
961
- r = "* #{msg_num} EXPUNGE\r\n"
962
- res << r
963
- @folder.server_response_multicast_push(r)
882
+ res = []
883
+ new_token = nil
884
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
885
+
886
+ if (id = @mail_store.mbox_id(mbox_name_utf8)) then
887
+ new_token = open_folder(id, read_only: true)
888
+ folder_open_msgs(new_token) do |msg|
889
+ res << msg
890
+ end
891
+ res << "#{tag} OK [READ-ONLY] EXAMINE completed\r\n"
892
+ else
893
+ res << "#{tag} NO not found a mailbox\r\n"
964
894
  end
965
- get_mail_store.sync
966
- res << "#{tag} OK CLOSE completed\r\n"
967
- }
968
- end
969
- imap_command_selected :close, exclusive: true
895
+ yield(res)
970
896
 
971
- def expunge(tag)
972
- return yield([ "#{tag} NO cannot expunge in read-only mode\r\n" ]) if @folder.read_only?
973
- should_be_alive_folder
974
- @folder.reload if @folder.updated?
897
+ new_token
898
+ end
899
+ imap_command_authenticated :examine
975
900
 
976
- yield response_stream(tag) {|res|
977
- @folder.server_response_fetch{|r| res << r }
978
- @folder.expunge_mbox do |msg_num|
979
- r = "* #{msg_num} EXPUNGE\r\n"
980
- res << r
981
- @folder.server_response_multicast_push(r)
901
+ def create(token, tag, mbox_name)
902
+ res = []
903
+ if (token) then
904
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
905
+ folder.server_response_fetch{|r| res << r }
982
906
  end
983
- res << "#{tag} OK EXPUNGE completed\r\n"
984
- }
985
- end
986
- imap_command_selected :expunge, exclusive: true
907
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
908
+ if (@mail_store.mbox_id(mbox_name_utf8)) then
909
+ res << "#{tag} NO duplicated mailbox\r\n"
910
+ else
911
+ @mail_store.add_mbox(mbox_name_utf8)
912
+ res << "#{tag} OK CREATE completed\r\n"
913
+ end
914
+ yield(res)
915
+ end
916
+ imap_command_authenticated :create, exclusive: true
987
917
 
988
- def search(tag, *cond_args, uid: false)
989
- should_be_alive_folder
990
- @folder.reload if @folder.updated?
991
- parser = SearchParser.new(get_mail_store, @folder)
992
-
993
- if (! cond_args.empty? && cond_args[0].upcase == 'CHARSET') then
994
- cond_args.shift
995
- charset_string = cond_args.shift or raise SyntaxError, 'need for a charset string of CHARSET'
996
- charset_string.is_a? String or raise SyntaxError, "CHARSET charset string expected as <String> but was <#{charset_string.class}>."
997
- parser.charset = charset_string
918
+ def delete(token, tag, mbox_name)
919
+ res = []
920
+ if (token) then
921
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
922
+ folder.server_response_fetch{|r| res << r }
923
+ end
924
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
925
+ if (id = @mail_store.mbox_id(mbox_name_utf8)) then
926
+ if (id != @mail_store.mbox_id('INBOX')) then
927
+ @mail_store.del_mbox(id)
928
+ res << "#{tag} OK DELETE completed\r\n"
929
+ else
930
+ res << "#{tag} NO not delete inbox\r\n"
931
+ end
932
+ else
933
+ res << "#{tag} NO not found a mailbox\r\n"
934
+ end
935
+ yield(res)
936
+ end
937
+ imap_command_authenticated :delete, exclusive: true
938
+
939
+ def rename(token, tag, src_name, dst_name)
940
+ res = []
941
+ if (token) then
942
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
943
+ folder.server_response_fetch{|r| res << r }
944
+ end
945
+ src_name_utf8 = Net::IMAP.decode_utf7(src_name)
946
+ dst_name_utf8 = Net::IMAP.decode_utf7(dst_name)
947
+ unless (id = @mail_store.mbox_id(src_name_utf8)) then
948
+ return yield(res << "#{tag} NO not found a mailbox\r\n")
949
+ end
950
+ if (id == @mail_store.mbox_id('INBOX')) then
951
+ return yield(res << "#{tag} NO not rename inbox\r\n")
952
+ end
953
+ if (@mail_store.mbox_id(dst_name_utf8)) then
954
+ return yield(res << "#{tag} NO duplicated mailbox\r\n")
955
+ end
956
+ @mail_store.rename_mbox(id, dst_name_utf8)
957
+ res << "#{tag} OK RENAME completed\r\n"
958
+ yield(res)
998
959
  end
960
+ imap_command_authenticated :rename, exclusive: true
999
961
 
1000
- if (cond_args.empty?) then
1001
- raise SyntaxError, 'required search arguments.'
962
+ def subscribe(token, tag, mbox_name)
963
+ res = []
964
+ if (token) then
965
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
966
+ folder.server_response_fetch{|r| res << r }
967
+ end
968
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
969
+ if (@mail_store.mbox_id(mbox_name_utf8)) then
970
+ res << "#{tag} OK SUBSCRIBE completed\r\n"
971
+ else
972
+ res << "#{tag} NO not found a mailbox\r\n"
973
+ end
974
+ yield(res)
1002
975
  end
976
+ imap_command_authenticated :subscribe
1003
977
 
1004
- if (cond_args[0].upcase == 'UID' && cond_args.length >= 2) then
1005
- begin
1006
- msg_set = @folder.parse_msg_set(cond_args[1], uid: true)
1007
- msg_src = @folder.msg_find_all(msg_set, uid: true)
1008
- cond_args.shift(2)
1009
- rescue MessageSetSyntaxError
1010
- msg_src = @folder.each_msg
978
+ def unsubscribe(token, tag, mbox_name)
979
+ res = []
980
+ if (token) then
981
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
982
+ folder.server_response_fetch{|r| res << r }
1011
983
  end
1012
- else
984
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
985
+ if (@mail_store.mbox_id(mbox_name_utf8)) then
986
+ res << "#{tag} NO not implemented subscribe/unsbscribe command\r\n"
987
+ else
988
+ res << "#{tag} NO not found a mailbox\r\n"
989
+ end
990
+ yield(res)
991
+ end
992
+ imap_command_authenticated :unsubscribe
993
+
994
+ def list_mbox(ref_name, mbox_name)
995
+ ref_name_utf8 = Net::IMAP.decode_utf7(ref_name)
996
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
997
+
998
+ mbox_filter = Protocol.compile_wildcard(mbox_name_utf8)
999
+ mbox_list = @mail_store.each_mbox_id.map{|id| [ id, @mail_store.mbox_name(id) ] }
1000
+ mbox_list.keep_if{|id, name| name.start_with? ref_name_utf8 }
1001
+ mbox_list.keep_if{|id, name| name[(ref_name_utf8.length)..-1] =~ mbox_filter }
1002
+
1003
+ for id, name_utf8 in mbox_list
1004
+ name = Net::IMAP.encode_utf7(name_utf8)
1005
+ attrs = '\Noinferiors'
1006
+ if (@mail_store.mbox_flag_num(id, 'recent') > 0) then
1007
+ attrs << ' \Marked'
1008
+ else
1009
+ attrs << ' \Unmarked'
1010
+ end
1011
+ yield("(#{attrs}) NIL #{Protocol.quote(name)}")
1012
+ end
1013
+
1014
+ nil
1015
+ end
1016
+ private :list_mbox
1017
+
1018
+ def list(token, tag, ref_name, mbox_name)
1019
+ res = []
1020
+ if (token) then
1021
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1022
+ folder.server_response_fetch{|r| res << r }
1023
+ end
1024
+ if (mbox_name.empty?) then
1025
+ res << "* LIST (\\Noselect) NIL \"\"\r\n"
1026
+ else
1027
+ list_mbox(ref_name, mbox_name) do |mbox_entry|
1028
+ res << "* LIST #{mbox_entry}\r\n"
1029
+ end
1030
+ end
1031
+ res << "#{tag} OK LIST completed\r\n"
1032
+ yield(res)
1033
+ end
1034
+ imap_command_authenticated :list
1035
+
1036
+ def lsub(token, tag, ref_name, mbox_name)
1037
+ res = []
1038
+ if (token) then
1039
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1040
+ folder.server_response_fetch{|r| res << r }
1041
+ end
1042
+ if (mbox_name.empty?) then
1043
+ res << "* LSUB (\\Noselect) NIL \"\"\r\n"
1044
+ else
1045
+ list_mbox(ref_name, mbox_name) do |mbox_entry|
1046
+ res << "* LSUB #{mbox_entry}\r\n"
1047
+ end
1048
+ end
1049
+ res << "#{tag} OK LSUB completed\r\n"
1050
+ yield(res)
1051
+ end
1052
+ imap_command_authenticated :lsub
1053
+
1054
+ def status(token, tag, mbox_name, data_item_group)
1055
+ res = []
1056
+ if (token) then
1057
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1058
+ folder.server_response_fetch{|r| res << r }
1059
+ end
1060
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
1061
+ if (id = @mail_store.mbox_id(mbox_name_utf8)) then
1062
+ unless ((data_item_group.is_a? Array) && (data_item_group[0] == :group)) then
1063
+ raise SyntaxError, 'second arugment is not a group list.'
1064
+ end
1065
+
1066
+ values = []
1067
+ for item in data_item_group[1..-1]
1068
+ case (item.upcase)
1069
+ when 'MESSAGES'
1070
+ values << 'MESSAGES' << @mail_store.mbox_msg_num(id)
1071
+ when 'RECENT'
1072
+ values << 'RECENT' << @mail_store.mbox_flag_num(id, 'recent')
1073
+ when 'UIDNEXT'
1074
+ values << 'UIDNEXT' << @mail_store.uid(id)
1075
+ when 'UIDVALIDITY'
1076
+ values << 'UIDVALIDITY' << id
1077
+ when 'UNSEEN'
1078
+ unseen_flags = @mail_store.mbox_msg_num(id) - @mail_store.mbox_flag_num(id, 'seen')
1079
+ values << 'UNSEEN' << unseen_flags
1080
+ else
1081
+ raise SyntaxError, "unknown status data: #{item}"
1082
+ end
1083
+ end
1084
+
1085
+ res << "* STATUS #{Protocol.quote(mbox_name)} (#{values.join(' ')})\r\n"
1086
+ res << "#{tag} OK STATUS completed\r\n"
1087
+ else
1088
+ res << "#{tag} NO not found a mailbox\r\n"
1089
+ end
1090
+ yield(res)
1091
+ end
1092
+ imap_command_authenticated :status
1093
+
1094
+ def mailbox_size_server_response_multicast_push(mbox_id)
1095
+ all_msgs = @mail_store.mbox_msg_num(mbox_id)
1096
+ recent_msgs = @mail_store.mbox_flag_num(mbox_id, 'recent')
1097
+
1098
+ f = @mail_store.open_folder(mbox_id, read_only: true)
1013
1099
  begin
1014
- msg_set = @folder.parse_msg_set(cond_args[0], uid: false)
1015
- msg_src = @folder.msg_find_all(msg_set, uid: false)
1016
- cond_args.shift
1017
- rescue MessageSetSyntaxError
1018
- msg_src = @folder.each_msg
1100
+ f.server_response_multicast_push("* #{all_msgs} EXISTS\r\n")
1101
+ f.server_response_multicast_push("* #{recent_msgs} RECENT\r\n")
1102
+ ensure
1103
+ f.close
1019
1104
  end
1105
+
1106
+ nil
1020
1107
  end
1021
- cond = parser.parse(cond_args)
1108
+ private :mailbox_size_server_response_multicast_push
1109
+
1110
+ def append(token, tag, mbox_name, *opt_args, msg_text)
1111
+ res = []
1112
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
1113
+ if (mbox_id = @mail_store.mbox_id(mbox_name_utf8)) then
1114
+ msg_flags = []
1115
+ msg_date = Time.now
1116
+
1117
+ if ((! opt_args.empty?) && (opt_args[0].is_a? Array)) then
1118
+ opt_flags = opt_args.shift
1119
+ if (opt_flags[0] != :group) then
1120
+ raise SyntaxError, 'bad flag list.'
1121
+ end
1122
+ for flag_atom in opt_flags[1..-1]
1123
+ case (flag_atom.upcase)
1124
+ when '\ANSWERED'
1125
+ msg_flags << 'answered'
1126
+ when '\FLAGGED'
1127
+ msg_flags << 'flagged'
1128
+ when '\DELETED'
1129
+ msg_flags << 'deleted'
1130
+ when '\SEEN'
1131
+ msg_flags << 'seen'
1132
+ when '\DRAFT'
1133
+ msg_flags << 'draft'
1134
+ else
1135
+ raise SyntaxError, "invalid flag: #{flag_atom}"
1136
+ end
1137
+ end
1138
+ end
1139
+
1140
+ if ((! opt_args.empty?) && (opt_args[0].is_a? String)) then
1141
+ begin
1142
+ msg_date = Time.parse(opt_args.shift)
1143
+ rescue ArgumentError
1144
+ raise SyntaxError, $!.message
1145
+ end
1146
+ end
1147
+
1148
+ unless (opt_args.empty?) then
1149
+ raise SyntaxError, "unknown option: #{opt_args.inspect}"
1150
+ end
1151
+
1152
+ uid = @mail_store.add_msg(mbox_id, msg_text, msg_date)
1153
+ for flag_name in msg_flags
1154
+ @mail_store.set_msg_flag(mbox_id, uid, flag_name, true)
1155
+ end
1156
+
1157
+ mailbox_size_server_response_multicast_push(mbox_id)
1158
+ if (token) then
1159
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1160
+ folder.server_response_fetch{|r| res << r }
1161
+ end
1162
+
1163
+ res << "#{tag} OK [APPENDUID #{mbox_id} #{uid}] APPEND completed\r\n"
1164
+ else
1165
+ if (token) then
1166
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1167
+ folder.server_response_fetch{|r| res << r }
1168
+ end
1169
+ res << "#{tag} NO [TRYCREATE] not found a mailbox\r\n"
1170
+ end
1171
+ yield(res)
1172
+ end
1173
+ imap_command_authenticated :append, exclusive: true
1174
+
1175
+ def check(token, tag)
1176
+ res = []
1177
+ if (token) then
1178
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1179
+ folder.server_response_fetch{|r| res << r }
1180
+ end
1181
+ @mail_store.sync
1182
+ res << "#{tag} OK CHECK completed\r\n"
1183
+ yield(res)
1184
+ end
1185
+ imap_command_selected :check, exclusive: true
1186
+
1187
+ def close_no_response(token)
1188
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1189
+ close_folder(token) do |untagged_response|
1190
+ # IMAP CLOSE command may not send untagged EXPUNGE
1191
+ # responses, but notifies other connections of them.
1192
+ folder.server_response_multicast_push(untagged_response)
1193
+ end
1194
+
1195
+ nil
1196
+ end
1197
+ private :close_no_response
1198
+
1199
+ def close(token, tag)
1200
+ close_no_response(token)
1201
+ yield([ "#{tag} OK CLOSE completed\r\n" ])
1202
+ end
1203
+ imap_command_selected :close, exclusive: true
1204
+
1205
+ def expunge(token, tag)
1206
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1207
+ folder.should_be_alive
1208
+ return yield([ "#{tag} NO cannot expunge in read-only mode\r\n" ]) if folder.read_only?
1209
+ folder.reload if folder.updated?
1210
+
1211
+ res = []
1212
+ folder.server_response_fetch{|r|
1213
+ res << r
1214
+ if (res.length >= @bulk_response_count) then
1215
+ yield(res)
1216
+ res = []
1217
+ end
1218
+ }
1219
+
1220
+ folder.expunge_mbox do |msg_num|
1221
+ r = "* #{msg_num} EXPUNGE\r\n"
1222
+ res << r
1223
+ if (res.length >= @bulk_response_count) then
1224
+ yield(res)
1225
+ res = []
1226
+ end
1227
+ folder.server_response_multicast_push(r)
1228
+ end
1229
+
1230
+ res << "#{tag} OK EXPUNGE completed\r\n"
1231
+ yield(res)
1232
+ end
1233
+ imap_command_selected :expunge, exclusive: true
1234
+
1235
+ def search(token, tag, *cond_args, uid: false)
1236
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1237
+ folder.should_be_alive
1238
+ folder.reload if folder.updated?
1239
+ parser = SearchParser.new(@mail_store, folder)
1240
+
1241
+ if (! cond_args.empty? && cond_args[0].upcase == 'CHARSET') then
1242
+ cond_args.shift
1243
+ charset_string = cond_args.shift or raise SyntaxError, 'need for a charset string of CHARSET'
1244
+ charset_string.is_a? String or raise SyntaxError, "CHARSET charset string expected as <String> but was <#{charset_string.class}>."
1245
+ parser.charset = charset_string
1246
+ end
1247
+
1248
+ if (cond_args.empty?) then
1249
+ raise SyntaxError, 'required search arguments.'
1250
+ end
1251
+
1252
+ if (cond_args[0].upcase == 'UID' && cond_args.length >= 2) then
1253
+ begin
1254
+ msg_set = folder.parse_msg_set(cond_args[1], uid: true)
1255
+ msg_src = folder.msg_find_all(msg_set, uid: true)
1256
+ cond_args.shift(2)
1257
+ rescue MessageSetSyntaxError
1258
+ msg_src = folder.each_msg
1259
+ end
1260
+ else
1261
+ begin
1262
+ msg_set = folder.parse_msg_set(cond_args[0], uid: false)
1263
+ msg_src = folder.msg_find_all(msg_set, uid: false)
1264
+ cond_args.shift
1265
+ rescue MessageSetSyntaxError
1266
+ msg_src = folder.each_msg
1267
+ end
1268
+ end
1269
+ cond = parser.parse(cond_args)
1270
+
1271
+ res = []
1272
+ folder.server_response_fetch{|r|
1273
+ res << r
1274
+ if (res.length >= @bulk_response_count) then
1275
+ yield(res)
1276
+ res = []
1277
+ end
1278
+ }
1022
1279
 
1023
- yield response_stream(tag) {|res|
1024
- @folder.server_response_fetch{|r| res << r }
1025
1280
  res << '* SEARCH'
1026
1281
  for msg in msg_src
1027
1282
  if (cond.call(msg)) then
@@ -1030,133 +1285,157 @@ module RIMS
1030
1285
  else
1031
1286
  res << " #{msg.num}"
1032
1287
  end
1288
+ if (res.length >= @bulk_response_count) then
1289
+ yield(res)
1290
+ res = []
1291
+ end
1033
1292
  end
1034
1293
  end
1294
+
1035
1295
  res << "\r\n"
1036
1296
  res << "#{tag} OK SEARCH completed\r\n"
1037
- }
1038
- end
1039
- imap_command_selected :search
1297
+ yield(res)
1298
+ end
1299
+ imap_command_selected :search
1040
1300
 
1041
- def fetch(tag, msg_set, data_item_group, uid: false)
1042
- should_be_alive_folder
1043
- @folder.reload if @folder.updated?
1301
+ def fetch(token, tag, msg_set, data_item_group, uid: false)
1302
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1303
+ folder.should_be_alive
1304
+ folder.reload if folder.updated?
1044
1305
 
1045
- msg_set = @folder.parse_msg_set(msg_set, uid: uid)
1046
- msg_list = @folder.msg_find_all(msg_set, uid: uid)
1306
+ msg_set = folder.parse_msg_set(msg_set, uid: uid)
1307
+ msg_list = folder.msg_find_all(msg_set, uid: uid)
1047
1308
 
1048
- unless ((data_item_group.is_a? Array) && data_item_group[0] == :group) then
1049
- data_item_group = [ :group, data_item_group ]
1050
- end
1051
- if (uid) then
1052
- unless (data_item_group.find{|i| (i.is_a? String) && (i.upcase == 'UID') }) then
1053
- data_item_group = [ :group, 'UID' ] + data_item_group[1..-1]
1309
+ unless ((data_item_group.is_a? Array) && data_item_group[0] == :group) then
1310
+ data_item_group = [ :group, data_item_group ]
1311
+ end
1312
+ if (uid) then
1313
+ unless (data_item_group.find{|i| (i.is_a? String) && (i.upcase == 'UID') }) then
1314
+ data_item_group = [ :group, 'UID' ] + data_item_group[1..-1]
1315
+ end
1054
1316
  end
1055
- end
1056
1317
 
1057
- parser = FetchParser.new(get_mail_store, @folder)
1058
- fetch = parser.parse(data_item_group)
1318
+ parser = FetchParser.new(@mail_store, folder)
1319
+ fetch = parser.parse(data_item_group)
1320
+
1321
+ res = []
1322
+ folder.server_response_fetch{|r|
1323
+ res << r
1324
+ if (res.length >= @bulk_response_count) then
1325
+ yield(res)
1326
+ res = []
1327
+ end
1328
+ }
1059
1329
 
1060
- yield response_stream(tag) {|res|
1061
- @folder.server_response_fetch{|r| res << r }
1062
1330
  for msg in msg_list
1063
1331
  res << ('* '.b << msg.num.to_s.b << ' FETCH '.b << fetch.call(msg) << "\r\n".b)
1332
+ if (res.length >= @bulk_response_count) then
1333
+ yield(res)
1334
+ res = []
1335
+ end
1064
1336
  end
1065
- res << "#{tag} OK FETCH completed\r\n"
1066
- }
1067
- end
1068
- imap_command_selected :fetch
1069
1337
 
1070
- def store(tag, msg_set, data_item_name, data_item_value, uid: false)
1071
- return yield([ "#{tag} NO cannot store in read-only mode\r\n" ]) if @folder.read_only?
1072
- should_be_alive_folder
1073
- @folder.reload if @folder.updated?
1074
-
1075
- msg_set = @folder.parse_msg_set(msg_set, uid: uid)
1076
- name, option = data_item_name.split(/\./, 2)
1077
-
1078
- case (name.upcase)
1079
- when 'FLAGS'
1080
- action = :flags_replace
1081
- when '+FLAGS'
1082
- action = :flags_add
1083
- when '-FLAGS'
1084
- action = :flags_del
1085
- else
1086
- raise SyntaxError, "unknown store action: #{name}"
1338
+ res << "#{tag} OK FETCH completed\r\n"
1339
+ yield(res)
1087
1340
  end
1341
+ imap_command_selected :fetch
1342
+
1343
+ def store(token, tag, msg_set, data_item_name, data_item_value, uid: false)
1344
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1345
+ folder.should_be_alive
1346
+ return yield([ "#{tag} NO cannot store in read-only mode\r\n" ]) if folder.read_only?
1347
+ folder.reload if folder.updated?
1348
+
1349
+ msg_set = folder.parse_msg_set(msg_set, uid: uid)
1350
+ name, option = data_item_name.split(/\./, 2)
1351
+
1352
+ case (name.upcase)
1353
+ when 'FLAGS'
1354
+ action = :flags_replace
1355
+ when '+FLAGS'
1356
+ action = :flags_add
1357
+ when '-FLAGS'
1358
+ action = :flags_del
1359
+ else
1360
+ raise SyntaxError, "unknown store action: #{name}"
1361
+ end
1088
1362
 
1089
- case (option && option.upcase)
1090
- when 'SILENT'
1091
- is_silent = true
1092
- when nil
1093
- is_silent = false
1094
- else
1095
- raise SyntaxError, "unknown store option: #{option.inspect}"
1096
- end
1363
+ case (option && option.upcase)
1364
+ when 'SILENT'
1365
+ is_silent = true
1366
+ when nil
1367
+ is_silent = false
1368
+ else
1369
+ raise SyntaxError, "unknown store option: #{option.inspect}"
1370
+ end
1097
1371
 
1098
- if ((data_item_value.is_a? Array) && data_item_value[0] == :group) then
1099
- flag_list = []
1100
- for flag_atom in data_item_value[1..-1]
1101
- case (flag_atom.upcase)
1102
- when '\ANSWERED'
1103
- flag_list << 'answered'
1104
- when '\FLAGGED'
1105
- flag_list << 'flagged'
1106
- when '\DELETED'
1107
- flag_list << 'deleted'
1108
- when '\SEEN'
1109
- flag_list << 'seen'
1110
- when '\DRAFT'
1111
- flag_list << 'draft'
1112
- else
1113
- raise SyntaxError, "invalid flag: #{flag_atom}"
1372
+ if ((data_item_value.is_a? Array) && data_item_value[0] == :group) then
1373
+ flag_list = []
1374
+ for flag_atom in data_item_value[1..-1]
1375
+ case (flag_atom.upcase)
1376
+ when '\ANSWERED'
1377
+ flag_list << 'answered'
1378
+ when '\FLAGGED'
1379
+ flag_list << 'flagged'
1380
+ when '\DELETED'
1381
+ flag_list << 'deleted'
1382
+ when '\SEEN'
1383
+ flag_list << 'seen'
1384
+ when '\DRAFT'
1385
+ flag_list << 'draft'
1386
+ else
1387
+ raise SyntaxError, "invalid flag: #{flag_atom}"
1388
+ end
1114
1389
  end
1390
+ rest_flag_list = (MailStore::MSG_FLAG_NAMES - %w[ recent ]) - flag_list
1391
+ else
1392
+ raise SyntaxError, 'third arugment is not a group list.'
1115
1393
  end
1116
- rest_flag_list = (MailStore::MSG_FLAG_NAMES - %w[ recent ]) - flag_list
1117
- else
1118
- raise SyntaxError, 'third arugment is not a group list.'
1119
- end
1120
1394
 
1121
- msg_list = @folder.msg_find_all(msg_set, uid: uid)
1395
+ msg_list = folder.msg_find_all(msg_set, uid: uid)
1122
1396
 
1123
- for msg in msg_list
1124
- case (action)
1125
- when :flags_replace
1126
- for name in flag_list
1127
- get_mail_store.set_msg_flag(@folder.mbox_id, msg.uid, name, true)
1128
- end
1129
- for name in rest_flag_list
1130
- get_mail_store.set_msg_flag(@folder.mbox_id, msg.uid, name, false)
1131
- end
1132
- when :flags_add
1133
- for name in flag_list
1134
- get_mail_store.set_msg_flag(@folder.mbox_id, msg.uid, name, true)
1135
- end
1136
- when :flags_del
1137
- for name in flag_list
1138
- get_mail_store.set_msg_flag(@folder.mbox_id, msg.uid, name, false)
1397
+ for msg in msg_list
1398
+ case (action)
1399
+ when :flags_replace
1400
+ for name in flag_list
1401
+ @mail_store.set_msg_flag(folder.mbox_id, msg.uid, name, true)
1402
+ end
1403
+ for name in rest_flag_list
1404
+ @mail_store.set_msg_flag(folder.mbox_id, msg.uid, name, false)
1405
+ end
1406
+ when :flags_add
1407
+ for name in flag_list
1408
+ @mail_store.set_msg_flag(folder.mbox_id, msg.uid, name, true)
1409
+ end
1410
+ when :flags_del
1411
+ for name in flag_list
1412
+ @mail_store.set_msg_flag(folder.mbox_id, msg.uid, name, false)
1413
+ end
1414
+ else
1415
+ raise "internal error: unknown action: #{action}"
1139
1416
  end
1140
- else
1141
- raise "internal error: unknown action: #{action}"
1142
1417
  end
1143
- end
1144
1418
 
1145
- if (is_silent) then
1146
- silent_res = []
1147
- @folder.server_response_fetch{|r| silent_res << r }
1148
- silent_res << "#{tag} OK STORE completed\r\n"
1149
- yield(silent_res)
1150
- else
1151
- yield response_stream(tag) {|res|
1152
- @folder.server_response_fetch{|r| res << r }
1419
+ res = []
1420
+ folder.server_response_fetch{|r|
1421
+ res << r
1422
+ if (res.length >= @bulk_response_count) then
1423
+ yield(res)
1424
+ res = []
1425
+ end
1426
+ }
1427
+
1428
+ if (is_silent) then
1429
+ res << "#{tag} OK STORE completed\r\n"
1430
+ yield(res)
1431
+ else
1153
1432
  for msg in msg_list
1154
1433
  flag_atom_list = nil
1155
1434
 
1156
- if (get_mail_store.msg_exist? @folder.mbox_id, msg.uid) then
1435
+ if (@mail_store.msg_exist? folder.mbox_id, msg.uid) then
1157
1436
  flag_atom_list = []
1158
1437
  for name in MailStore::MSG_FLAG_NAMES
1159
- if (get_mail_store.msg_flag(@folder.mbox_id, msg.uid, name)) then
1438
+ if (@mail_store.msg_flag(folder.mbox_id, msg.uid, name)) then
1160
1439
  flag_atom_list << "\\#{name.capitalize}"
1161
1440
  end
1162
1441
  end
@@ -1168,96 +1447,302 @@ module RIMS
1168
1447
  else
1169
1448
  res << "* #{msg.num} FETCH (FLAGS (#{flag_atom_list.join(' ')}))\r\n"
1170
1449
  end
1450
+ if (res.length >= @bulk_response_count) then
1451
+ yield(res)
1452
+ res = []
1453
+ end
1171
1454
  else
1172
- @logger.warn("not found a message and skipped: uidvalidity(#{@folder.mbox_id}) uid(#{msg.uid})")
1455
+ @logger.warn("not found a message and skipped: uidvalidity(#{folder.mbox_id}) uid(#{msg.uid})")
1173
1456
  end
1174
1457
  end
1458
+
1175
1459
  res << "#{tag} OK STORE completed\r\n"
1460
+ yield(res)
1461
+ end
1462
+ end
1463
+ imap_command_selected :store, exclusive: true
1464
+
1465
+ def copy(token, tag, msg_set, mbox_name, uid: false)
1466
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1467
+ folder.should_be_alive
1468
+ folder.reload if folder.updated?
1469
+
1470
+ res = []
1471
+ mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
1472
+ msg_set = folder.parse_msg_set(msg_set, uid: uid)
1473
+
1474
+ if (mbox_id = @mail_store.mbox_id(mbox_name_utf8)) then
1475
+ msg_list = folder.msg_find_all(msg_set, uid: uid)
1476
+
1477
+ src_uids = []
1478
+ dst_uids = []
1479
+ for msg in msg_list
1480
+ src_uids << msg.uid
1481
+ dst_uids << @mail_store.copy_msg(msg.uid, folder.mbox_id, mbox_id)
1482
+ end
1483
+
1484
+ if (msg_list.size > 0) then
1485
+ mailbox_size_server_response_multicast_push(mbox_id)
1486
+ folder.server_response_fetch{|r| res << r }
1487
+ res << "#{tag} OK [COPYUID #{mbox_id} #{src_uids.join(',')} #{dst_uids.join(',')}] COPY completed\r\n"
1488
+ else
1489
+ folder.server_response_fetch{|r| res << r }
1490
+ res << "#{tag} OK COPY completed\r\n"
1491
+ end
1492
+ else
1493
+ folder.server_response_fetch{|r| res << r }
1494
+ res << "#{tag} NO [TRYCREATE] not found a mailbox\r\n"
1495
+ end
1496
+ yield(res)
1497
+ end
1498
+ imap_command_selected :copy, exclusive: true
1499
+
1500
+ def idle(token, tag, client_input_gets, server_output_write, connection_timer)
1501
+ folder = @folders[token] or raise KeyError.new("undefined folder token: #{token}", key: token, receiver: self)
1502
+ folder.should_be_alive
1503
+
1504
+ @logger.info('idle start...')
1505
+ server_output_write.call([ "+ continue\r\n" ])
1506
+
1507
+ server_response_thread = Thread.new{
1508
+ @logger.info('idle server response thread start... ')
1509
+ folder.server_response_idle_wait{|server_response_list|
1510
+ for server_response in server_response_list
1511
+ @logger.debug("idle server response: #{server_response}") if @logger.debug?
1512
+ end
1513
+ server_output_write.call(server_response_list)
1514
+ }
1515
+ @logger.info('idle server response thread terminated.')
1176
1516
  }
1517
+
1518
+ begin
1519
+ connection_timer.command_wait or return
1520
+ line = client_input_gets.call
1521
+ ensure
1522
+ folder.server_response_idle_interrupt
1523
+ server_response_thread.join
1524
+ end
1525
+
1526
+ res = []
1527
+ if (line) then
1528
+ line.chomp!("\n")
1529
+ line.chomp!("\r")
1530
+ if (line.upcase == "DONE") then
1531
+ @logger.info('idle terminated.')
1532
+ res << "#{tag} OK IDLE terminated\r\n"
1533
+ else
1534
+ @logger.warn('unexpected client response and idle terminated.')
1535
+ @logger.debug("unexpected client response data: #{line}") if @logger.debug?
1536
+ res << "#{tag} BAD unexpected client response\r\n"
1537
+ end
1538
+ else
1539
+ @logger.warn('unexpected client connection close and idle terminated.')
1540
+ res << "#{tag} BAD unexpected client connection close\r\n"
1541
+ end
1542
+ yield(res)
1177
1543
  end
1544
+ imap_command_selected :idle, exclusive: nil
1178
1545
  end
1179
- imap_command_selected :store, exclusive: true
1180
1546
 
1181
- def copy(tag, msg_set, mbox_name, uid: false)
1182
- should_be_alive_folder
1183
- @folder.reload if @folder.updated?
1547
+ def initialize(parent_decoder, engine, auth, logger)
1548
+ super(auth, logger)
1549
+ @parent_decoder = parent_decoder
1550
+ @engine = engine
1551
+ @token = nil
1552
+ end
1184
1553
 
1185
- res = []
1186
- mbox_name_utf8 = Net::IMAP.decode_utf7(mbox_name)
1187
- msg_set = @folder.parse_msg_set(msg_set, uid: uid)
1554
+ def auth?
1555
+ ! @engine.nil?
1556
+ end
1188
1557
 
1189
- if (mbox_id = get_mail_store.mbox_id(mbox_name_utf8)) then
1190
- msg_list = @folder.msg_find_all(msg_set, uid: uid)
1558
+ def selected?
1559
+ ! @token.nil?
1560
+ end
1191
1561
 
1192
- src_uids = []
1193
- dst_uids = []
1194
- for msg in msg_list
1195
- src_uids << msg.uid
1196
- dst_uids << get_mail_store.copy_msg(msg.uid, @folder.mbox_id, mbox_id)
1562
+ def cleanup
1563
+ unless (@engine.nil?) then
1564
+ begin
1565
+ @engine.cleanup(@token)
1566
+ ensure
1567
+ @token = nil
1197
1568
  end
1198
1569
 
1199
- if msg_list.size > 0
1200
- mailbox_size_server_response_multicast_push(mbox_id)
1201
- @folder.server_response_fetch{|r| res << r }
1202
- res << "#{tag} OK [COPYUID #{mbox_id} #{src_uids.join(',')} #{dst_uids.join(',')}] COPY completed\r\n"
1203
- else
1204
- @folder.server_response_fetch{|r| res << r }
1205
- res << "#{tag} OK COPY completed\r\n"
1570
+ begin
1571
+ @engine.destroy
1572
+ ensure
1573
+ @engine = nil
1206
1574
  end
1207
- else
1208
- @folder.server_response_fetch{|r| res << r }
1209
- res << "#{tag} NO [TRYCREATE] not found a mailbox\r\n"
1210
1575
  end
1211
- yield(res)
1576
+
1577
+ unless (@parent_decoder.nil?) then
1578
+ @parent_decoder.cleanup
1579
+ @parent_decoder = nil
1580
+ end
1581
+
1582
+ nil
1583
+ end
1584
+
1585
+ def noop(tag, &block)
1586
+ @engine.noop(@token, tag, &block)
1587
+ end
1588
+ imap_command :noop
1589
+
1590
+ def logout(tag)
1591
+ if (@token) then
1592
+ old_token = @token
1593
+ @token = nil
1594
+ @engine.cleanup(old_token)
1595
+ end
1596
+
1597
+ @next_decoder = LogoutDecoder.new(self)
1598
+ yield(make_logout_response(tag))
1599
+ end
1600
+ imap_command :logout
1601
+
1602
+ def select(tag, mbox_name)
1603
+ ret_val = nil
1604
+ old_token = @token
1605
+ @token = @engine.select(old_token, tag, mbox_name) {|res|
1606
+ ret_val = yield(res)
1607
+ }
1608
+
1609
+ ret_val
1610
+ end
1611
+ imap_command :select
1612
+
1613
+ def examine(tag, mbox_name)
1614
+ ret_val = nil
1615
+ old_token = @token
1616
+ @token = @engine.examine(old_token, tag, mbox_name) {|res|
1617
+ ret_val = yield(res)
1618
+ }
1619
+
1620
+ ret_val
1621
+ end
1622
+ imap_command :examine
1623
+
1624
+ def create(tag, mbox_name, &block)
1625
+ @engine.create(@token, tag, mbox_name, &block)
1626
+ end
1627
+ imap_command :create
1628
+
1629
+ def delete(tag, mbox_name, &block)
1630
+ @engine.delete(@token, tag, mbox_name, &block)
1631
+ end
1632
+ imap_command :delete
1633
+
1634
+ def rename(tag, src_name, dst_name, &block)
1635
+ @engine.rename(@token, tag, src_name, dst_name, &block)
1636
+ end
1637
+ imap_command :rename
1638
+
1639
+ def subscribe(tag, mbox_name, &block)
1640
+ @engine.subscribe(@token, tag, mbox_name, &block)
1641
+ end
1642
+ imap_command :subscribe
1643
+
1644
+ def unsubscribe(tag, mbox_name, &block)
1645
+ @engine.unsubscribe(@token, tag, mbox_name, &block)
1646
+ end
1647
+ imap_command :unsubscribe
1648
+
1649
+ def list(tag, ref_name, mbox_name, &block)
1650
+ @engine.list(@token, tag, ref_name, mbox_name, &block)
1651
+ end
1652
+ imap_command :list
1653
+
1654
+ def lsub(tag, ref_name, mbox_name, &block)
1655
+ @engine.lsub(@token, tag, ref_name, mbox_name, &block)
1656
+ end
1657
+ imap_command :lsub
1658
+
1659
+ def status(tag, mbox_name, data_item_group, &block)
1660
+ @engine.status(@token, tag, mbox_name, data_item_group, &block)
1661
+ end
1662
+ imap_command :status
1663
+
1664
+ def append(tag, mbox_name, *opt_args, msg_text, &block)
1665
+ @engine.append(@token, tag, mbox_name, *opt_args, msg_text, &block)
1666
+ end
1667
+ imap_command :append
1668
+
1669
+ def check(tag, &block)
1670
+ @engine.check(@token, tag, &block)
1671
+ end
1672
+ imap_command :check
1673
+
1674
+ def close(tag, &block)
1675
+ old_token = @token
1676
+ @token = nil
1677
+
1678
+ yield response_stream(tag) {|res|
1679
+ @engine.close(old_token, tag) {|bulk_res|
1680
+ for r in bulk_res
1681
+ res << r
1682
+ end
1683
+ }
1684
+ }
1212
1685
  end
1213
- imap_command_selected :copy, exclusive: true
1686
+ imap_command :close
1214
1687
 
1215
- def idle(tag, client_input_stream, server_output_stream, connection_timer)
1216
- @logger.info('idle start...')
1217
- server_output_stream.write("+ continue\r\n")
1218
- server_output_stream.flush
1688
+ def expunge(tag)
1689
+ yield response_stream(tag) {|res|
1690
+ @engine.expunge(@token, tag) {|bulk_res|
1691
+ for r in bulk_res
1692
+ res << r
1693
+ end
1694
+ }
1695
+ }
1696
+ end
1697
+ imap_command :expunge
1219
1698
 
1220
- server_response_thread = Thread.new{
1221
- @logger.info('idle server response thread start... ')
1222
- @folder.server_response_idle_wait{|server_response_list|
1223
- for server_response in server_response_list
1224
- @logger.debug("idle server response: #{server_response}") if @logger.debug?
1225
- server_output_stream.write(server_response)
1699
+ def search(tag, *cond_args, uid: false)
1700
+ yield response_stream(tag) {|res|
1701
+ @engine.search(@token, tag, *cond_args, uid: uid) {|bulk_res|
1702
+ for r in bulk_res
1703
+ res << r
1226
1704
  end
1227
- server_output_stream.flush
1228
1705
  }
1229
- @logger.info('idle server response thread terminated.')
1230
1706
  }
1707
+ end
1708
+ imap_command :search
1231
1709
 
1232
- begin
1233
- connection_timer.command_wait or return
1234
- line = client_input_stream.gets
1235
- ensure
1236
- @folder.server_response_idle_interrupt
1237
- server_response_thread.join
1238
- end
1710
+ def fetch(tag, msg_set, data_item_group, uid: false)
1711
+ yield response_stream(tag) {|res|
1712
+ @engine.fetch(@token, tag, msg_set, data_item_group, uid: uid) {|bulk_res|
1713
+ for r in bulk_res
1714
+ res << r
1715
+ end
1716
+ }
1717
+ }
1718
+ end
1719
+ imap_command :fetch
1239
1720
 
1240
- res = []
1241
- if (line) then
1242
- line.chomp!("\n")
1243
- line.chomp!("\r")
1244
- if (line.upcase == "DONE") then
1245
- @logger.info('idle terminated.')
1246
- res << "#{tag} OK IDLE terminated\r\n"
1247
- else
1248
- @logger.warn('unexpected client response and idle terminated.')
1249
- @logger.debug("unexpected client response data: #{line}")
1250
- res << "#{tag} BAD unexpected client response\r\n"
1251
- end
1252
- else
1253
- @logger.warn('unexpected client connection close and idle terminated.')
1254
- res << "#{tag} BAD unexpected client connection close\r\n"
1255
- end
1256
- yield(res)
1721
+ def store(tag, msg_set, data_item_name, data_item_value, uid: false)
1722
+ yield response_stream(tag) {|res|
1723
+ @engine.store(@token, tag, msg_set, data_item_name, data_item_value, uid: uid) {|bulk_res|
1724
+ for r in bulk_res
1725
+ res << r
1726
+ end
1727
+ }
1728
+ }
1729
+ end
1730
+ imap_command :store
1731
+
1732
+ def copy(tag, msg_set, mbox_name, uid: false, &block)
1733
+ @engine.copy(@token, tag, msg_set, mbox_name, uid: uid, &block)
1734
+ end
1735
+ imap_command :copy
1736
+
1737
+ def idle(tag, client_input_gets, server_output_write, connection_timer, &block)
1738
+ @engine.idle(@token, tag, client_input_gets, server_output_write, connection_timer, &block)
1257
1739
  end
1258
- imap_command_selected :idle, exclusive: nil
1740
+ imap_command :idle
1259
1741
  end
1260
1742
 
1743
+ # alias
1744
+ Decoder::Engine = UserMailboxDecoder::Engine
1745
+
1261
1746
  def Decoder.encode_delivery_target_mailbox(username, mbox_name)
1262
1747
  "b64user-mbox #{Protocol.encode_base64(username)} #{mbox_name}"
1263
1748
  end
@@ -1271,51 +1756,53 @@ module RIMS
1271
1756
  end
1272
1757
 
1273
1758
  class MailDeliveryDecoder < AuthenticatedDecoder
1274
- def initialize(mail_store_pool, auth, logger,
1275
- write_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
1276
- cleanup_write_lock_timeout_seconds: 1,
1277
- **mailbox_decoder_optional)
1759
+ def initialize(parent_decoder, services, auth, logger)
1278
1760
  super(auth, logger)
1279
- @mail_store_pool = mail_store_pool
1761
+ @parent_decoder = parent_decoder
1762
+ @services = services
1280
1763
  @auth = auth
1281
- @write_lock_timeout_seconds = write_lock_timeout_seconds
1282
- @cleanup_write_lock_timeout_seconds = cleanup_write_lock_timeout_seconds
1283
- @mailbox_decoder_optional = mailbox_decoder_optional
1284
1764
  @last_user_cache_key_username = nil
1285
- @last_user_cache_value_mail_store_holder = nil
1765
+ @last_user_cache_value_engine = nil
1286
1766
  end
1287
1767
 
1288
- def user_mail_store_cached?(username)
1768
+ def engine_cached?(username)
1289
1769
  @last_user_cache_key_username == username
1290
1770
  end
1291
- private :user_mail_store_cached?
1771
+ private :engine_cached?
1292
1772
 
1293
- def fetch_user_mail_store_holder(username)
1294
- unless (user_mail_store_cached? username) then
1295
- release_user_mail_store_holder
1296
- @last_user_cache_value_mail_store_holder = yield
1297
- @last_user_cache_key_username = username
1773
+ def engine_cache(username)
1774
+ unless (engine_cached? username) then
1775
+ raise "not cached: #{username}"
1298
1776
  end
1299
- @last_user_cache_value_mail_store_holder
1777
+ @last_user_cache_value_engine
1300
1778
  end
1301
- private :fetch_user_mail_store_holder
1779
+ private :engine_cache
1302
1780
 
1303
- def release_user_mail_store_holder
1304
- if (@last_user_cache_value_mail_store_holder) then
1305
- mail_store_holder = @last_user_cache_value_mail_store_holder
1781
+ def store_engine_cache(username)
1782
+ if (engine_cached? username) then
1783
+ raise "already cached: #{username}"
1784
+ end
1785
+
1786
+ release_engine_cache
1787
+ @last_user_cache_value_engine = yield
1788
+ @last_user_cache_key_username = username # success to store engine cache
1789
+
1790
+ @last_user_cache_value_engine
1791
+ end
1792
+ private :store_engine_cache
1793
+
1794
+ def release_engine_cache
1795
+ if (@last_user_cache_value_engine) then
1796
+ engine = @last_user_cache_value_engine
1306
1797
  @last_user_cache_key_username = nil
1307
- @last_user_cache_value_mail_store_holder = nil
1308
- ReadWriteLock.write_lock_timeout_detach(@cleanup_write_lock_timeout_seconds, @write_lock_timeout_seconds, logger: @logger) {|timeout_seconds|
1309
- mail_store_holder.return_pool{
1310
- @logger.info("close cached mail store to deliver message: #{mail_store_holder.unique_user_id}")
1311
- }
1312
- }
1798
+ @last_user_cache_value_engine = nil
1799
+ engine.destroy
1313
1800
  end
1314
1801
  end
1315
- private :release_user_mail_store_holder
1802
+ private :release_engine_cache
1316
1803
 
1317
1804
  def auth?
1318
- @mail_store_pool != nil
1805
+ @services != nil
1319
1806
  end
1320
1807
 
1321
1808
  def selected?
@@ -1323,18 +1810,21 @@ module RIMS
1323
1810
  end
1324
1811
 
1325
1812
  def cleanup
1326
- release_user_mail_store_holder
1327
- @mail_store_pool = nil unless @mail_store_pool.nil?
1813
+ release_engine_cache
1814
+ @services = nil unless @services.nil?
1328
1815
  @auth = nil unless @auth.nil?
1816
+
1817
+ unless (@parent_decoder.nil?) then
1818
+ @parent_decoder.cleanup
1819
+ @parent_decoder = nil
1820
+ end
1821
+
1329
1822
  nil
1330
1823
  end
1331
1824
 
1332
1825
  def logout(tag)
1333
- cleanup
1334
- res = []
1335
- res << "* BYE server logout\r\n"
1336
- res << "#{tag} OK LOGOUT completed\r\n"
1337
- yield(res)
1826
+ @next_decoder = LogoutDecoder.new(self)
1827
+ yield(make_logout_response(tag))
1338
1828
  end
1339
1829
  imap_command :logout
1340
1830
 
@@ -1354,66 +1844,63 @@ module RIMS
1354
1844
  end
1355
1845
  imap_command :capability
1356
1846
 
1357
- def not_allowed_command_response(tag)
1847
+ def make_not_allowed_command_response(tag)
1358
1848
  [ "#{tag} NO not allowed command on mail delivery user\r\n" ]
1359
1849
  end
1360
- private :not_allowed_command_response
1850
+ private :make_not_allowed_command_response
1361
1851
 
1362
1852
  def select(tag, mbox_name)
1363
- yield(not_allowed_command_response(tag))
1853
+ yield(make_not_allowed_command_response(tag))
1364
1854
  end
1365
1855
  imap_command :select
1366
1856
 
1367
1857
  def examine(tag, mbox_name)
1368
- yield(not_allowed_command_response(tag))
1858
+ yield(make_not_allowed_command_response(tag))
1369
1859
  end
1370
1860
  imap_command :examine
1371
1861
 
1372
1862
  def create(tag, mbox_name)
1373
- yield(not_allowed_command_response(tag))
1863
+ yield(make_not_allowed_command_response(tag))
1374
1864
  end
1375
1865
  imap_command :create
1376
1866
 
1377
1867
  def delete(tag, mbox_name)
1378
- yield(not_allowed_command_response(tag))
1868
+ yield(make_not_allowed_command_response(tag))
1379
1869
  end
1380
1870
  imap_command :delete
1381
1871
 
1382
1872
  def rename(tag, src_name, dst_name)
1383
- yield(not_allowed_command_response(tag))
1873
+ yield(make_not_allowed_command_response(tag))
1384
1874
  end
1385
1875
  imap_command :rename
1386
1876
 
1387
1877
  def subscribe(tag, mbox_name)
1388
- yield(not_allowed_command_response(tag))
1878
+ yield(make_not_allowed_command_response(tag))
1389
1879
  end
1390
1880
  imap_command :subscribe
1391
1881
 
1392
1882
  def unsubscribe(tag, mbox_name)
1393
- yield(not_allowed_command_response(tag))
1883
+ yield(make_not_allowed_command_response(tag))
1394
1884
  end
1395
1885
  imap_command :unsubscribe
1396
1886
 
1397
1887
  def list(tag, ref_name, mbox_name)
1398
- yield(not_allowed_command_response(tag))
1888
+ yield(make_not_allowed_command_response(tag))
1399
1889
  end
1400
1890
  imap_command :list
1401
1891
 
1402
1892
  def lsub(tag, ref_name, mbox_name)
1403
- yield(not_allowed_command_response(tag))
1893
+ yield(make_not_allowed_command_response(tag))
1404
1894
  end
1405
1895
  imap_command :lsub
1406
1896
 
1407
1897
  def status(tag, mbox_name, data_item_group)
1408
- yield(not_allowed_command_response(tag))
1898
+ yield(make_not_allowed_command_response(tag))
1409
1899
  end
1410
1900
  imap_command :status
1411
1901
 
1412
- def deliver_to_user(tag, username, mbox_name, opt_args, msg_text, mail_store_holder, res)
1413
- user_decoder = UserMailboxDecoder.new(self, mail_store_holder, @auth, @logger,
1414
- write_lock_timeout_seconds: @write_lock_timeout_seconds,
1415
- cleanup_write_lock_timeout_seconds: @cleanup_write_lock_timeout_seconds,
1416
- **@mailbox_decoder_optional)
1902
+ def deliver_to_user(tag, username, mbox_name, opt_args, msg_text, engine, res)
1903
+ user_decoder = UserMailboxDecoder.new(self, engine, @auth, @logger)
1417
1904
  user_decoder.append(tag, mbox_name, *opt_args, msg_text) {|append_response|
1418
1905
  if (append_response.last.split(' ', 3)[1] == 'OK') then
1419
1906
  @logger.info("message delivery: successed to deliver #{msg_text.bytesize} octets message.")
@@ -1432,18 +1919,16 @@ module RIMS
1432
1919
  @logger.info("message delivery: user #{username}, mailbox #{mbox_name}")
1433
1920
 
1434
1921
  if (@auth.user? username) then
1435
- if (user_mail_store_cached? username) then
1922
+ if (engine_cached? username) then
1436
1923
  res = []
1437
- mail_store_holder = fetch_user_mail_store_holder(username)
1438
- deliver_to_user(tag, username, mbox_name, opt_args, msg_text, mail_store_holder, res)
1924
+ engine = engine_cache(username)
1925
+ deliver_to_user(tag, username, mbox_name, opt_args, msg_text, engine, res)
1439
1926
  else
1440
1927
  res = Enumerator.new{|stream_res|
1441
- mail_store_holder = fetch_user_mail_store_holder(username) {
1442
- self.class.fetch_mail_store_holder_and_on_demand_recovery(@mail_store_pool, username,
1443
- write_lock_timeout_seconds: @write_lock_timeout_seconds,
1444
- logger: @logger) {|msg| stream_res << msg }
1928
+ engine = store_engine_cache(username) {
1929
+ self.class.make_engine_and_recovery_if_needed(@services, username, logger: @logger) {|msg| stream_res << msg }
1445
1930
  }
1446
- deliver_to_user(tag, username, mbox_name, opt_args, msg_text, mail_store_holder, stream_res)
1931
+ deliver_to_user(tag, username, mbox_name, opt_args, msg_text, engine, stream_res)
1447
1932
  }
1448
1933
  end
1449
1934
  yield(res)
@@ -1455,42 +1940,42 @@ module RIMS
1455
1940
  imap_command :append
1456
1941
 
1457
1942
  def check(tag)
1458
- yield(not_allowed_command_response(tag))
1943
+ yield(make_not_allowed_command_response(tag))
1459
1944
  end
1460
1945
  imap_command :check
1461
1946
 
1462
1947
  def close(tag)
1463
- yield(not_allowed_command_response(tag))
1948
+ yield(make_not_allowed_command_response(tag))
1464
1949
  end
1465
1950
  imap_command :close
1466
1951
 
1467
1952
  def expunge(tag)
1468
- yield(not_allowed_command_response(tag))
1953
+ yield(make_not_allowed_command_response(tag))
1469
1954
  end
1470
1955
  imap_command :expunge
1471
1956
 
1472
1957
  def search(tag, *cond_args, uid: false)
1473
- yield(not_allowed_command_response(tag))
1958
+ yield(make_not_allowed_command_response(tag))
1474
1959
  end
1475
1960
  imap_command :search
1476
1961
 
1477
1962
  def fetch(tag, msg_set, data_item_group, uid: false)
1478
- yield(not_allowed_command_response(tag))
1963
+ yield(make_not_allowed_command_response(tag))
1479
1964
  end
1480
1965
  imap_command :fetch
1481
1966
 
1482
1967
  def store(tag, msg_set, data_item_name, data_item_value, uid: false)
1483
- yield(not_allowed_command_response(tag))
1968
+ yield(make_not_allowed_command_response(tag))
1484
1969
  end
1485
1970
  imap_command :store
1486
1971
 
1487
1972
  def copy(tag, msg_set, mbox_name, uid: false)
1488
- yield(not_allowed_command_response(tag))
1973
+ yield(make_not_allowed_command_response(tag))
1489
1974
  end
1490
1975
  imap_command :copy
1491
1976
 
1492
- def idle(tag, client_input_stream, server_output_stream, connection_timer)
1493
- yield(not_allowed_command_response(tag))
1977
+ def idle(tag, client_input_gets, server_output_write, connection_timer)
1978
+ yield(make_not_allowed_command_response(tag))
1494
1979
  end
1495
1980
  imap_command :idle
1496
1981
  end