rims 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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