rims 0.2.3 → 0.2.4
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 +18 -0
- data/lib/rims/cmd.rb +19 -1
- data/lib/rims/protocol.rb +2 -0
- data/lib/rims/protocol/connection.rb +71 -0
- data/lib/rims/protocol/decoder.rb +97 -80
- data/lib/rims/service.rb +29 -10
- data/lib/rims/version.rb +1 -1
- data/rims.gemspec +1 -1
- data/test/cmd/test_command.rb +2 -0
- data/test/test_protocol_connection.rb +56 -0
- data/test/test_protocol_decoder.rb +4349 -5222
- data/test/test_service.rb +24 -2
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e462eb67ba5bb6cdfa73906f100e9e11474e42e64616df8fc119a7dcaa879374
|
4
|
+
data.tar.gz: 9f860520538eb8bd52aeac784dd668d6778a9c51f343ff368548225d955c6dca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5d7aff45f4ce0d921e10136f2e161abd5f6ddf8307fc2f96909db0399cb3230f11ec073a6f401bc3f4d9a28432334a9845ae8da5e31bba30064cee2e78e33de
|
7
|
+
data.tar.gz: b9f72aa820f34528f47383d4e3c74245747edf60b0c370a24f75b3f5189c740c0887366cab1b66f7ae8237819d3407d7a31d8cc0786c487d9778a801fdb66a8a
|
data/ChangeLog
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
2019-04-25 TOKI Yoshinori <toki@freedom.ne.jp>
|
2
|
+
|
3
|
+
* test/test_protocol_decoder.rb: integrated individual IMAP
|
4
|
+
command test and IMAP command stream test.
|
5
|
+
|
6
|
+
2019-04-16 TOKI Yoshinori <toki@freedom.ne.jp>
|
7
|
+
|
8
|
+
* lib/rims/protocol/connection.rb, lib/rims/protocol/decoder.rb,
|
9
|
+
lib/rims/service.rb: autologout for too long ideling or graceful
|
10
|
+
shutdown.
|
11
|
+
|
12
|
+
2019-04-13 TOKI Yoshinori <toki@freedom.ne.jp>
|
13
|
+
|
14
|
+
* lib/rims/protocol/decoder.rb
|
15
|
+
(RIMS::Protocol::UserMailboxDecoder#idle): fix a bug of idle
|
16
|
+
command that uninitialized variable had been logged as debug
|
17
|
+
message.
|
18
|
+
|
1
19
|
2019-04-10 TOKI Yoshinori <toki@freedom.ne.jp>
|
2
20
|
|
3
21
|
* RIMS version 0.2.3 is released.
|
data/lib/rims/cmd.rb
CHANGED
@@ -408,11 +408,29 @@ module RIMS
|
|
408
408
|
Integer
|
409
409
|
) do |size|
|
410
410
|
build.chain{|c|
|
411
|
-
c.load(
|
411
|
+
c.load(connection: {
|
412
412
|
send_buffer_limit_size: size
|
413
413
|
})
|
414
414
|
}
|
415
415
|
end
|
416
|
+
options.on('--read-polling-interval=SECONDS',
|
417
|
+
Float
|
418
|
+
) do |seconds|
|
419
|
+
build.chain{|c|
|
420
|
+
c.load(connection: {
|
421
|
+
read_polling_interval_seconds: seconds
|
422
|
+
})
|
423
|
+
}
|
424
|
+
end
|
425
|
+
options.on('--command-wait-timeout=SECONDS',
|
426
|
+
Float
|
427
|
+
) do |seconds|
|
428
|
+
build.chain{|c|
|
429
|
+
c.load(connection: {
|
430
|
+
command_wait_timeout_seconds: seconds
|
431
|
+
})
|
432
|
+
}
|
433
|
+
end
|
416
434
|
options.on('--read-lock-timeout=SECONDS',
|
417
435
|
Float
|
418
436
|
) do |seconds|
|
data/lib/rims/protocol.rb
CHANGED
@@ -62,6 +62,8 @@ module RIMS
|
|
62
62
|
autoload :AuthenticationReader, 'rims/protocol/parser'
|
63
63
|
autoload :SearchParser, 'rims/protocol/parser'
|
64
64
|
autoload :FetchParser, 'rims/protocol/parser'
|
65
|
+
autoload :ConnectionLimits, 'rims/protocol/connection'
|
66
|
+
autoload :ConnectionTimer, 'rims/protocol/connection'
|
65
67
|
autoload :Decoder, 'rims/protocol/decoder'
|
66
68
|
|
67
69
|
def body(symbol: nil, option: nil, section: nil, section_list: nil, partial_origin: nil, partial_size: nil)
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'riser'
|
4
|
+
|
5
|
+
module RIMS
|
6
|
+
module Protocol
|
7
|
+
class ConnectionLimits
|
8
|
+
def initialize(read_polling_interval_seconds, command_wait_timeout_seconds)
|
9
|
+
@mutex = Thread::Mutex.new
|
10
|
+
@read_polling_interval_seconds = read_polling_interval_seconds
|
11
|
+
self.command_wait_timeout_seconds = command_wait_timeout_seconds
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :read_polling_interval_seconds
|
15
|
+
|
16
|
+
def command_wait_timeout_seconds
|
17
|
+
@mutex.synchronize{ @command_wait_timeout_seconds }
|
18
|
+
end
|
19
|
+
|
20
|
+
def command_wait_timeout_seconds=(value)
|
21
|
+
@mutex.synchronize{ @command_wait_timeout_seconds = value }
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_h
|
25
|
+
Hash[
|
26
|
+
[ :read_polling_interval_seconds,
|
27
|
+
:command_wait_timeout_seconds
|
28
|
+
].map{|n| [ n, __send__(n) ] }
|
29
|
+
]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ConnectionTimer
|
34
|
+
def initialize(limits, read_io)
|
35
|
+
@limits = limits
|
36
|
+
@read_poll = Riser::ReadPoll.new(read_io)
|
37
|
+
@command_wait_timeout = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def command_wait
|
41
|
+
if (@limits.command_wait_timeout_seconds == 0) then
|
42
|
+
if (@read_poll.call(0) != nil) then
|
43
|
+
return self
|
44
|
+
else
|
45
|
+
@command_wait_timeout = true
|
46
|
+
return
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@read_poll.reset_timer
|
51
|
+
until (@read_poll.call(@limits.read_polling_interval_seconds) != nil)
|
52
|
+
if (@read_poll.interval_seconds >= @limits.command_wait_timeout_seconds) then
|
53
|
+
@command_wait_timeout = true
|
54
|
+
return
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def command_wait_timeout?
|
62
|
+
@command_wait_timeout
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Local Variables:
|
69
|
+
# mode: Ruby
|
70
|
+
# indent-tabs-mode: nil
|
71
|
+
# End:
|
@@ -7,11 +7,21 @@ require 'time'
|
|
7
7
|
module RIMS
|
8
8
|
module Protocol
|
9
9
|
class Decoder
|
10
|
+
# to use `StringIO' as mock in unit test
|
11
|
+
using Riser::CompatibleStringIO
|
12
|
+
|
10
13
|
def self.new_decoder(*args, **opts)
|
11
14
|
InitialDecoder.new(*args, **opts)
|
12
15
|
end
|
13
16
|
|
14
|
-
|
17
|
+
IMAP_CMDs = {}
|
18
|
+
UID_CMDs = {}
|
19
|
+
|
20
|
+
def self.imap_command_normalize(name)
|
21
|
+
name.upcase
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.repl(decoder, limits, input, output, logger)
|
15
25
|
response_write = proc{|res|
|
16
26
|
begin
|
17
27
|
last_line = nil
|
@@ -31,8 +41,12 @@ module RIMS
|
|
31
41
|
|
32
42
|
decoder.ok_greeting{|res| response_write.call(res) }
|
33
43
|
|
34
|
-
|
35
|
-
|
44
|
+
conn_timer = ConnectionTimer.new(limits, input.to_io)
|
45
|
+
request_reader = RequestReader.new(input, output, logger)
|
46
|
+
|
47
|
+
until (conn_timer.command_wait_timeout?)
|
48
|
+
conn_timer.command_wait or break
|
49
|
+
|
36
50
|
begin
|
37
51
|
atom_list = request_reader.read_command
|
38
52
|
rescue
|
@@ -47,9 +61,10 @@ module RIMS
|
|
47
61
|
break unless atom_list
|
48
62
|
|
49
63
|
tag, command, *opt_args = atom_list
|
64
|
+
normalized_command = imap_command_normalize(command)
|
50
65
|
logger.info("client command: #{tag} #{command}")
|
51
66
|
if (logger.debug?) then
|
52
|
-
case (
|
67
|
+
case (normalized_command)
|
53
68
|
when 'LOGIN'
|
54
69
|
log_opt_args = opt_args.dup
|
55
70
|
log_opt_args[-1] = '********'
|
@@ -60,76 +75,29 @@ module RIMS
|
|
60
75
|
end
|
61
76
|
|
62
77
|
begin
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
when 'EXAMINE'
|
77
|
-
decoder.examine(tag, *opt_args) {|res| response_write.call(res) }
|
78
|
-
when 'CREATE'
|
79
|
-
decoder.create(tag, *opt_args) {|res| response_write.call(res) }
|
80
|
-
when 'DELETE'
|
81
|
-
decoder.delete(tag, *opt_args) {|res| response_write.call(res) }
|
82
|
-
when 'RENAME'
|
83
|
-
decoder.rename(tag, *opt_args) {|res| response_write.call(res) }
|
84
|
-
when 'SUBSCRIBE'
|
85
|
-
decoder.subscribe(tag, *opt_args) {|res| response_write.call(res) }
|
86
|
-
when 'UNSUBSCRIBE'
|
87
|
-
decoder.unsubscribe(tag, *opt_args) {|res| response_write.call(res) }
|
88
|
-
when 'LIST'
|
89
|
-
decoder.list(tag, *opt_args) {|res| response_write.call(res) }
|
90
|
-
when 'LSUB'
|
91
|
-
decoder.lsub(tag, *opt_args) {|res| response_write.call(res) }
|
92
|
-
when 'STATUS'
|
93
|
-
decoder.status(tag, *opt_args) {|res| response_write.call(res) }
|
94
|
-
when 'APPEND'
|
95
|
-
decoder.append(tag, *opt_args) {|res| response_write.call(res) }
|
96
|
-
when 'CHECK'
|
97
|
-
decoder.check(tag, *opt_args) {|res| response_write.call(res) }
|
98
|
-
when 'CLOSE'
|
99
|
-
decoder.close(tag, *opt_args) {|res| response_write.call(res) }
|
100
|
-
when 'EXPUNGE'
|
101
|
-
decoder.expunge(tag, *opt_args) {|res| response_write.call(res) }
|
102
|
-
when 'SEARCH'
|
103
|
-
decoder.search(tag, *opt_args) {|res| response_write.call(res) }
|
104
|
-
when 'FETCH'
|
105
|
-
decoder.fetch(tag, *opt_args) {|res| response_write.call(res) }
|
106
|
-
when 'STORE'
|
107
|
-
decoder.store(tag, *opt_args) {|res| response_write.call(res) }
|
108
|
-
when 'COPY'
|
109
|
-
decoder.copy(tag, *opt_args) {|res| response_write.call(res) }
|
110
|
-
when 'IDLE'
|
111
|
-
decoder.idle(tag, input, output, *opt_args) {|res| response_write.call(res) }
|
112
|
-
when 'UID'
|
113
|
-
unless (opt_args.empty?) then
|
114
|
-
uid_command, *uid_args = opt_args
|
115
|
-
logger.info("uid command: #{uid_command}")
|
116
|
-
logger.debug("uid parameter: #{uid_args}") if logger.debug?
|
117
|
-
case (uid_command.upcase)
|
118
|
-
when 'SEARCH'
|
119
|
-
decoder.search(tag, *uid_args, uid: true) {|res| response_write.call(res) }
|
120
|
-
when 'FETCH'
|
121
|
-
decoder.fetch(tag, *uid_args, uid: true) {|res| response_write.call(res) }
|
122
|
-
when 'STORE'
|
123
|
-
decoder.store(tag, *uid_args, uid: true) {|res| response_write.call(res) }
|
124
|
-
when 'COPY'
|
125
|
-
decoder.copy(tag, *uid_args, uid: true) {|res| response_write.call(res) }
|
78
|
+
if (name = IMAP_CMDs[normalized_command]) then
|
79
|
+
case (name)
|
80
|
+
when :uid
|
81
|
+
unless (opt_args.empty?) then
|
82
|
+
uid_command, *uid_args = opt_args
|
83
|
+
logger.info("uid command: #{uid_command}")
|
84
|
+
logger.debug("uid parameter: #{uid_args}") if logger.debug?
|
85
|
+
if (uid_name = UID_CMDs[imap_command_normalize(uid_command)]) then
|
86
|
+
decoder.__send__(uid_name, tag, *uid_args, uid: true) {|res| response_write.call(res) }
|
87
|
+
else
|
88
|
+
logger.error("unknown uid command: #{uid_command}")
|
89
|
+
response_write.call([ "#{tag} BAD unknown uid command\r\n" ])
|
90
|
+
end
|
126
91
|
else
|
127
|
-
logger.error(
|
128
|
-
response_write.call([ "#{tag} BAD
|
92
|
+
logger.error('empty uid parameter.')
|
93
|
+
response_write.call([ "#{tag} BAD empty uid parameter\r\n" ])
|
129
94
|
end
|
95
|
+
when :authenticate
|
96
|
+
decoder.authenticate(tag, input, output, *opt_args) {|res| response_write.call(res) }
|
97
|
+
when :idle
|
98
|
+
decoder.idle(tag, input, output, conn_timer, *opt_args) {|res| response_write.call(res) }
|
130
99
|
else
|
131
|
-
|
132
|
-
response_write.call([ "#{tag} BAD empty uid parameter\r\n" ])
|
100
|
+
decoder.__send__(name, tag, *opt_args) {|res| response_write.call(res) }
|
133
101
|
end
|
134
102
|
else
|
135
103
|
logger.error("unknown command: #{command}")
|
@@ -143,13 +111,21 @@ module RIMS
|
|
143
111
|
response_write.call([ "#{tag} BAD unexpected error\r\n" ])
|
144
112
|
end
|
145
113
|
|
146
|
-
if (
|
114
|
+
if (normalized_command == 'LOGOUT') then
|
147
115
|
break
|
148
116
|
end
|
149
117
|
|
150
118
|
decoder = decoder.next_decoder
|
151
119
|
end
|
152
120
|
|
121
|
+
if (conn_timer.command_wait_timeout?) then
|
122
|
+
if (limits.command_wait_timeout_seconds > 0) then
|
123
|
+
response_write.call([ "* BYE server autologout: idle for too long\r\n" ])
|
124
|
+
else
|
125
|
+
response_write.call([ "* BYE server autologout: shutdown\r\n" ])
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
153
129
|
nil
|
154
130
|
ensure
|
155
131
|
decoder.cleanup
|
@@ -207,16 +183,55 @@ module RIMS
|
|
207
183
|
private :guard_error
|
208
184
|
|
209
185
|
class << self
|
186
|
+
def to_imap_command(name)
|
187
|
+
imap_command_normalize(name.to_s)
|
188
|
+
end
|
189
|
+
private :to_imap_command
|
190
|
+
|
191
|
+
def kw_params(method)
|
192
|
+
params = method.parameters
|
193
|
+
params.find_all{|arg_type, arg_name|
|
194
|
+
case (arg_type)
|
195
|
+
when :key, :keyreq
|
196
|
+
true
|
197
|
+
else
|
198
|
+
false
|
199
|
+
end
|
200
|
+
}.map{|arg_type, arg_name|
|
201
|
+
arg_name
|
202
|
+
}
|
203
|
+
end
|
204
|
+
private :kw_params
|
205
|
+
|
210
206
|
def imap_command(name)
|
207
|
+
name = name.to_sym
|
208
|
+
|
209
|
+
cmd = to_imap_command(name)
|
210
|
+
IMAP_CMDs[cmd] = name
|
211
|
+
|
212
|
+
method = instance_method(name)
|
213
|
+
if (kw_params(method).find(:uid)) then
|
214
|
+
IMAP_CMDs['UID'] = :uid
|
215
|
+
UID_CMDs[cmd] = name
|
216
|
+
end
|
217
|
+
|
211
218
|
orig_name = "_#{name}".to_sym
|
212
219
|
alias_method orig_name, name
|
213
220
|
define_method name, lambda{|tag, *args, **name_args, &block|
|
214
221
|
guard_error(tag, orig_name, *args, **name_args, &block)
|
215
222
|
}
|
216
|
-
|
223
|
+
|
224
|
+
name
|
217
225
|
end
|
218
226
|
private :imap_command
|
219
227
|
|
228
|
+
def should_be_imap_command(name)
|
229
|
+
unless (IMAP_CMDs.key? to_imap_command(name)) then
|
230
|
+
raise ArgumentError, "not an IMAP command: #{name}"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
private :should_be_imap_command
|
234
|
+
|
220
235
|
def fetch_mail_store_holder_and_on_demand_recovery(mail_store_pool, username,
|
221
236
|
write_lock_timeout_seconds: ReadWriteLock::DEFAULT_TIMEOUT_SECONDS,
|
222
237
|
logger: Logger.new(STDOUT))
|
@@ -452,7 +467,7 @@ module RIMS
|
|
452
467
|
end
|
453
468
|
imap_command :copy
|
454
469
|
|
455
|
-
def idle(tag, client_input_stream, server_output_stream)
|
470
|
+
def idle(tag, client_input_stream, server_output_stream, connection_timer)
|
456
471
|
yield(not_authenticated_response(tag))
|
457
472
|
end
|
458
473
|
imap_command :idle
|
@@ -595,6 +610,7 @@ module RIMS
|
|
595
610
|
|
596
611
|
class << self
|
597
612
|
def imap_command_authenticated(name, **guard_optional)
|
613
|
+
should_be_imap_command(name)
|
598
614
|
orig_name = "_#{name}".to_sym
|
599
615
|
alias_method orig_name, name
|
600
616
|
define_method name, lambda{|tag, *args, **name_args, &block|
|
@@ -605,6 +621,7 @@ module RIMS
|
|
605
621
|
private :imap_command_authenticated
|
606
622
|
|
607
623
|
def imap_command_selected(name, **guard_optional)
|
624
|
+
should_be_imap_command(name)
|
608
625
|
orig_name = "_#{name}".to_sym
|
609
626
|
alias_method orig_name, name
|
610
627
|
define_method name, lambda{|tag, *args, **name_args, &block|
|
@@ -971,7 +988,7 @@ module RIMS
|
|
971
988
|
def search(tag, *cond_args, uid: false)
|
972
989
|
should_be_alive_folder
|
973
990
|
@folder.reload if @folder.updated?
|
974
|
-
parser =
|
991
|
+
parser = SearchParser.new(get_mail_store, @folder)
|
975
992
|
|
976
993
|
if (! cond_args.empty? && cond_args[0].upcase == 'CHARSET') then
|
977
994
|
cond_args.shift
|
@@ -1037,7 +1054,7 @@ module RIMS
|
|
1037
1054
|
end
|
1038
1055
|
end
|
1039
1056
|
|
1040
|
-
parser =
|
1057
|
+
parser = FetchParser.new(get_mail_store, @folder)
|
1041
1058
|
fetch = parser.parse(data_item_group)
|
1042
1059
|
|
1043
1060
|
yield response_stream(tag) {|res|
|
@@ -1195,7 +1212,7 @@ module RIMS
|
|
1195
1212
|
end
|
1196
1213
|
imap_command_selected :copy, exclusive: true
|
1197
1214
|
|
1198
|
-
def idle(tag, client_input_stream, server_output_stream)
|
1215
|
+
def idle(tag, client_input_stream, server_output_stream, connection_timer)
|
1199
1216
|
@logger.info('idle start...')
|
1200
1217
|
server_output_stream.write("+ continue\r\n")
|
1201
1218
|
server_output_stream.flush
|
@@ -1203,17 +1220,17 @@ module RIMS
|
|
1203
1220
|
server_response_thread = Thread.new{
|
1204
1221
|
@logger.info('idle server response thread start... ')
|
1205
1222
|
@folder.server_response_idle_wait{|server_response_list|
|
1206
|
-
@logger.debug("idle server response: #{server_response}") if @logger.debug?
|
1207
1223
|
for server_response in server_response_list
|
1224
|
+
@logger.debug("idle server response: #{server_response}") if @logger.debug?
|
1208
1225
|
server_output_stream.write(server_response)
|
1209
1226
|
end
|
1210
1227
|
server_output_stream.flush
|
1211
1228
|
}
|
1212
|
-
server_output_stream.flush
|
1213
1229
|
@logger.info('idle server response thread terminated.')
|
1214
1230
|
}
|
1215
1231
|
|
1216
1232
|
begin
|
1233
|
+
connection_timer.command_wait or return
|
1217
1234
|
line = client_input_stream.gets
|
1218
1235
|
ensure
|
1219
1236
|
@folder.server_response_idle_interrupt
|
@@ -1472,7 +1489,7 @@ module RIMS
|
|
1472
1489
|
end
|
1473
1490
|
imap_command :copy
|
1474
1491
|
|
1475
|
-
def idle(tag, client_input_stream, server_output_stream)
|
1492
|
+
def idle(tag, client_input_stream, server_output_stream, connection_timer)
|
1476
1493
|
yield(not_allowed_command_response(tag))
|
1477
1494
|
end
|
1478
1495
|
imap_command :idle
|