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 +4 -4
- data/ChangeLog +65 -0
- data/lib/rims.rb +0 -2
- data/lib/rims/cksum_kvs.rb +1 -1
- data/lib/rims/cmd.rb +67 -8
- data/lib/rims/lock.rb +2 -2
- data/lib/rims/mail_store.rb +38 -52
- data/lib/rims/protocol/decoder.rb +1274 -789
- data/lib/rims/protocol/parser.rb +79 -39
- data/lib/rims/rfc822.rb +31 -24
- data/lib/rims/service.rb +67 -20
- data/lib/rims/test.rb +4 -1
- data/lib/rims/version.rb +1 -1
- data/test/cmd/test_command.rb +1103 -108
- data/test/test_mail_store.rb +23 -137
- data/test/test_protocol_auth.rb +8 -1
- data/test/test_protocol_decoder.rb +615 -175
- data/test/test_protocol_fetch.rb +276 -147
- data/test/test_protocol_request.rb +6 -0
- data/test/test_protocol_search.rb +27 -1
- data/test/test_service.rb +99 -6
- metadata +2 -3
- data/lib/rims/pool.rb +0 -85
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7acc91008294ca9589a19ecb657d5a04cc9d21c17e6349e059dd371bba0f4dbb
|
4
|
+
data.tar.gz: f750c5ef8ea6064033d1b6e1e0329964db61e56f1e9e1535883a19fcb1738aa3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
|
data/lib/rims.rb
CHANGED
@@ -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'
|
data/lib/rims/cksum_kvs.rb
CHANGED
@@ -10,7 +10,7 @@ module RIMS
|
|
10
10
|
|
11
11
|
def md5_cksum_parse(key, s)
|
12
12
|
if (s) then
|
13
|
-
s =~ /\
|
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
|
data/lib/rims/cmd.rb
CHANGED
@@ -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 =~
|
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 =~
|
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(
|
439
|
-
|
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(
|
448
|
-
|
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(
|
457
|
-
|
513
|
+
c.load(drb_services: {
|
514
|
+
engine: {
|
515
|
+
cleanup_write_lock_timeout_seconds: seconds
|
516
|
+
}
|
458
517
|
})
|
459
518
|
}
|
460
519
|
end
|
data/lib/rims/lock.rb
CHANGED
@@ -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($!)
|
data/lib/rims/mail_store.rb
CHANGED
@@ -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 =~ /\
|
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 =~ /\
|
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 =~ /\
|
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
|
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 =
|
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,
|
109
|
+
decoder.authenticate(tag, input_gets, output_write, *opt_args) {|res| response_write.call(res) }
|
97
110
|
when :idle
|
98
|
-
decoder.idle(tag,
|
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
|
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(
|
176
|
+
def guard_error(imap_command, tag, *args, **kw_args, &block)
|
160
177
|
begin
|
161
|
-
if (
|
162
|
-
__send__(imap_command, tag, *args
|
178
|
+
if (kw_args.empty?) then
|
179
|
+
__send__(imap_command, tag, *args, &block)
|
163
180
|
else
|
164
|
-
__send__(imap_command, tag, *args, **
|
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
|
207
|
-
name = name.to_sym
|
208
|
-
|
223
|
+
def should_be_imap_command(name)
|
209
224
|
cmd = to_imap_command(name)
|
210
|
-
IMAP_CMDs
|
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 (
|
214
|
-
|
215
|
-
|
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, **
|
221
|
-
guard_error(
|
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
|
229
|
-
|
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
|
-
|
242
|
-
|
243
|
-
}
|
260
|
+
logger.info("open mail store: #{unique_user_id} [ #{username} ]")
|
261
|
+
engine = services[:engine, unique_user_id]
|
244
262
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
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
|
-
|
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
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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
|
-
@
|
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
|
343
|
+
def make_not_authenticated_response(tag)
|
308
344
|
[ "#{tag} NO not authenticated\r\n" ]
|
309
345
|
end
|
310
|
-
private :
|
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
|
-
|
319
|
-
|
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(@
|
333
|
-
write_lock_timeout_seconds: @write_lock_timeout_seconds,
|
334
|
-
**@next_decoder_optional)
|
363
|
+
MailDeliveryDecoder.new(self, @services, @auth, @logger)
|
335
364
|
else
|
336
|
-
|
337
|
-
|
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,
|
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,
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
455
|
+
yield(make_not_authenticated_response(tag))
|
432
456
|
end
|
433
457
|
imap_command :append
|
434
458
|
|
435
459
|
def check(tag)
|
436
|
-
yield(
|
460
|
+
yield(make_not_authenticated_response(tag))
|
437
461
|
end
|
438
462
|
imap_command :check
|
439
463
|
|
440
464
|
def close(tag)
|
441
|
-
yield(
|
465
|
+
yield(make_not_authenticated_response(tag))
|
442
466
|
end
|
443
467
|
imap_command :close
|
444
468
|
|
445
469
|
def expunge(tag)
|
446
|
-
yield(
|
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(
|
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(
|
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(
|
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(
|
490
|
+
yield(make_not_authenticated_response(tag))
|
467
491
|
end
|
468
492
|
imap_command :copy
|
469
493
|
|
470
|
-
def idle(tag,
|
471
|
-
yield(
|
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
|
477
|
-
def
|
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
|
504
|
-
|
505
|
+
def next_decoder
|
506
|
+
self
|
505
507
|
end
|
506
|
-
private :get_mail_store
|
507
508
|
|
508
509
|
def auth?
|
509
|
-
|
510
|
+
false
|
510
511
|
end
|
511
512
|
|
512
513
|
def selected?
|
513
|
-
|
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
|
561
|
-
|
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
|
-
|
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
|
-
|
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
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
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
|
-
|
550
|
+
imap_command :login
|
672
551
|
|
673
552
|
def select(tag, mbox_name)
|
674
|
-
|
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
|
-
|
555
|
+
imap_command :select
|
689
556
|
|
690
557
|
def examine(tag, mbox_name)
|
691
|
-
|
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
|
-
|
560
|
+
imap_command :examine
|
706
561
|
|
707
562
|
def create(tag, mbox_name)
|
708
|
-
|
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
|
-
|
565
|
+
imap_command :create
|
720
566
|
|
721
567
|
def delete(tag, mbox_name)
|
722
|
-
|
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
|
-
|
570
|
+
imap_command :delete
|
738
571
|
|
739
572
|
def rename(tag, src_name, dst_name)
|
740
|
-
|
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
|
-
|
575
|
+
imap_command :rename
|
757
576
|
|
758
577
|
def subscribe(tag, mbox_name)
|
759
|
-
|
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
|
-
|
580
|
+
imap_command :subscribe
|
770
581
|
|
771
582
|
def unsubscribe(tag, mbox_name)
|
772
|
-
|
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
|
-
|
585
|
+
imap_command :unsubscribe
|
806
586
|
|
807
587
|
def list(tag, ref_name, mbox_name)
|
808
|
-
|
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
|
-
|
590
|
+
imap_command :list
|
821
591
|
|
822
592
|
def lsub(tag, ref_name, mbox_name)
|
823
|
-
|
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
|
-
|
595
|
+
imap_command :lsub
|
836
596
|
|
837
597
|
def status(tag, mbox_name, data_item_group)
|
838
|
-
|
839
|
-
|
840
|
-
|
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
|
-
|
866
|
-
|
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
|
-
|
605
|
+
imap_command :append
|
873
606
|
|
874
|
-
def
|
875
|
-
|
876
|
-
|
607
|
+
def check(tag)
|
608
|
+
raise ProtocolError, 'invalid command in logout state.'
|
609
|
+
end
|
610
|
+
imap_command :check
|
877
611
|
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
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
|
-
|
617
|
+
def expunge(tag)
|
618
|
+
raise ProtocolError, 'invalid command in logout state.'
|
887
619
|
end
|
888
|
-
|
620
|
+
imap_command :expunge
|
889
621
|
|
890
|
-
def
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
923
|
-
|
924
|
-
|
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
|
-
|
929
|
-
|
853
|
+
def select(token, tag, mbox_name)
|
854
|
+
if (token) then
|
855
|
+
close_no_response(token)
|
930
856
|
end
|
931
857
|
|
932
|
-
|
933
|
-
|
934
|
-
|
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
|
-
|
871
|
+
yield(res)
|
937
872
|
|
938
|
-
|
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
|
-
|
945
|
-
end
|
946
|
-
imap_command_authenticated :append, exclusive: true
|
875
|
+
imap_command_authenticated :select
|
947
876
|
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
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
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
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
|
-
|
966
|
-
res << "#{tag} OK CLOSE completed\r\n"
|
967
|
-
}
|
968
|
-
end
|
969
|
-
imap_command_selected :close, exclusive: true
|
895
|
+
yield(res)
|
970
896
|
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
@folder.reload if @folder.updated?
|
897
|
+
new_token
|
898
|
+
end
|
899
|
+
imap_command_authenticated :examine
|
975
900
|
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
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
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
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
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
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
|
-
|
1001
|
-
|
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
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
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
|
-
|
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
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
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
|
-
|
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
|
-
|
1039
|
-
|
1297
|
+
yield(res)
|
1298
|
+
end
|
1299
|
+
imap_command_selected :search
|
1040
1300
|
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
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
|
-
|
1046
|
-
|
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
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
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
|
-
|
1058
|
-
|
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
|
-
|
1071
|
-
|
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
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
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
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
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
|
-
|
1395
|
+
msg_list = folder.msg_find_all(msg_set, uid: uid)
|
1122
1396
|
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
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
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
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 (
|
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 (
|
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(#{
|
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
|
1182
|
-
|
1183
|
-
@
|
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
|
-
|
1186
|
-
|
1187
|
-
|
1554
|
+
def auth?
|
1555
|
+
! @engine.nil?
|
1556
|
+
end
|
1188
1557
|
|
1189
|
-
|
1190
|
-
|
1558
|
+
def selected?
|
1559
|
+
! @token.nil?
|
1560
|
+
end
|
1191
1561
|
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
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
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
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
|
-
|
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
|
-
|
1686
|
+
imap_command :close
|
1214
1687
|
|
1215
|
-
def
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
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
|
-
|
1221
|
-
|
1222
|
-
@
|
1223
|
-
for
|
1224
|
-
|
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
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
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
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
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
|
-
|
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(
|
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
|
-
@
|
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
|
-
@
|
1765
|
+
@last_user_cache_value_engine = nil
|
1286
1766
|
end
|
1287
1767
|
|
1288
|
-
def
|
1768
|
+
def engine_cached?(username)
|
1289
1769
|
@last_user_cache_key_username == username
|
1290
1770
|
end
|
1291
|
-
private :
|
1771
|
+
private :engine_cached?
|
1292
1772
|
|
1293
|
-
def
|
1294
|
-
unless (
|
1295
|
-
|
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
|
-
@
|
1777
|
+
@last_user_cache_value_engine
|
1300
1778
|
end
|
1301
|
-
private :
|
1779
|
+
private :engine_cache
|
1302
1780
|
|
1303
|
-
def
|
1304
|
-
if (
|
1305
|
-
|
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
|
-
@
|
1308
|
-
|
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 :
|
1802
|
+
private :release_engine_cache
|
1316
1803
|
|
1317
1804
|
def auth?
|
1318
|
-
@
|
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
|
-
|
1327
|
-
@
|
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
|
-
|
1334
|
-
|
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
|
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 :
|
1850
|
+
private :make_not_allowed_command_response
|
1361
1851
|
|
1362
1852
|
def select(tag, mbox_name)
|
1363
|
-
yield(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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,
|
1413
|
-
user_decoder = UserMailboxDecoder.new(self,
|
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 (
|
1922
|
+
if (engine_cached? username) then
|
1436
1923
|
res = []
|
1437
|
-
|
1438
|
-
deliver_to_user(tag, username, mbox_name, opt_args, msg_text,
|
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
|
-
|
1442
|
-
self.class.
|
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,
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
1973
|
+
yield(make_not_allowed_command_response(tag))
|
1489
1974
|
end
|
1490
1975
|
imap_command :copy
|
1491
1976
|
|
1492
|
-
def idle(tag,
|
1493
|
-
yield(
|
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
|