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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 397075dab0a3fa9bdf66a3a3be278b2d1eb3f67f6e2b12e73dcd27bf37190e66
4
- data.tar.gz: 8e0734fcdda1d88e9720cefea0c8a2464f4fd4ddb5cfd68c67bc56635c9ef6f3
3
+ metadata.gz: e462eb67ba5bb6cdfa73906f100e9e11474e42e64616df8fc119a7dcaa879374
4
+ data.tar.gz: 9f860520538eb8bd52aeac784dd668d6778a9c51f343ff368548225d955c6dca
5
5
  SHA512:
6
- metadata.gz: 5b9fa09f69d35c243e8be71387fe29c4ab3b2237e2a530737e5108dba9371579c2fda3d9fa6bfa7bbebb0ffef3121e6e84863fa111c31b93d1ef8aeeb979c870
7
- data.tar.gz: 8cb95a095d7f15f0fe6c7a7ac3bb6d4ec845e6950923c50dc69a93428c082576faaf0a48d83273a923d923dafc6a85eced6affb2c5039408f9b83add0cddd6e9
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.
@@ -408,11 +408,29 @@ module RIMS
408
408
  Integer
409
409
  ) do |size|
410
410
  build.chain{|c|
411
- c.load(server: {
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|
@@ -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
- def self.repl(decoder, input, output, logger)
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
- request_reader = Protocol::RequestReader.new(input, output, logger)
35
- loop do
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 (command.upcase)
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
- case (command.upcase)
64
- when 'CAPABILITY'
65
- decoder.capability(tag, *opt_args) {|res| response_write.call(res) }
66
- when 'NOOP'
67
- decoder.noop(tag, *opt_args) {|res| response_write.call(res) }
68
- when 'LOGOUT'
69
- decoder.logout(tag, *opt_args) {|res| response_write.call(res) }
70
- when 'AUTHENTICATE'
71
- decoder.authenticate(tag, input, output, *opt_args) {|res| response_write.call(res) }
72
- when 'LOGIN'
73
- decoder.login(tag, *opt_args) {|res| response_write.call(res) }
74
- when 'SELECT'
75
- decoder.select(tag, *opt_args) {|res| response_write.call(res) }
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("unknown uid command: #{uid_command}")
128
- response_write.call([ "#{tag} BAD unknown uid command\r\n" ])
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
- logger.error('empty uid parameter.')
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 (command.upcase == 'LOGOUT') then
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
- name.to_sym
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 = Protocol::SearchParser.new(get_mail_store, @folder)
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 = Protocol::FetchParser.new(get_mail_store, @folder)
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