rims 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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