rims 0.2.5 → 0.3.0

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.
@@ -33,21 +33,7 @@ module RIMS
33
33
  end
34
34
 
35
35
  class RequestReader
36
- def initialize(input, output, logger)
37
- @input = input
38
- @output = output
39
- @logger = logger
40
- end
41
-
42
- def read_line
43
- line = @input.gets or return
44
- @logger.debug("read line: #{Protocol.io_data_log(line)}") if @logger.debug?
45
- line.chomp!("\n")
46
- line.chomp!("\r")
47
- scan_line(line)
48
- end
49
-
50
- def scan_line(line)
36
+ def self.scan(line)
51
37
  atom_list = line.scan(/BODY(?:\.\S+)?\[.*?\](?:<\d+\.\d+>)?|[\[\]()]|".*?"|[^\[\]()\s]+/i).map{|s|
52
38
  case (s)
53
39
  when '(', ')', '[', ']', /\A NIL \z/ix
@@ -77,22 +63,14 @@ module RIMS
77
63
  end
78
64
  }
79
65
  if ((atom_list[-1].is_a? String) && (atom_list[-1] =~ /\A {\d+} \z/x)) then
80
- next_size = $&[1..-2].to_i
81
- @logger.debug("found literal: #{next_size} octets.") if @logger.debug?
82
- @output.write("+ continue\r\n")
83
- @output.flush
84
- @logger.debug('continue literal.') if @logger.debug?
85
- literal_string = @input.read(next_size) or raise 'unexpected client close.'
86
- @logger.debug("read literal: #{Protocol.io_data_log(literal_string)}") if @logger.debug?
87
- atom_list[-1] = literal_string
88
- next_atom_list = read_line or raise 'unexpected client close.'
89
- atom_list += next_atom_list
66
+ literal_size = $&[1..-2].to_i
67
+ atom_list[-1] = [ :literal, literal_size ]
90
68
  end
91
69
 
92
70
  atom_list
93
71
  end
94
72
 
95
- def parse(atom_list, last_atom=nil)
73
+ def self.parse(atom_list, last_atom=nil)
96
74
  syntax_list = []
97
75
  while (atom = atom_list.shift)
98
76
  case (atom)
@@ -105,7 +83,7 @@ module RIMS
105
83
  else
106
84
  if ((atom.is_a? Array) && (atom[0] == :body)) then
107
85
  body = atom[1]
108
- body.section_list = parse(scan_line(body.section))
86
+ body.section_list = parse(scan(body.section))
109
87
  end
110
88
  syntax_list.push(atom)
111
89
  end
@@ -118,18 +96,90 @@ module RIMS
118
96
  syntax_list
119
97
  end
120
98
 
99
+ def initialize(input, output, logger, line_length_limit: 1024*8, literal_size_limit: (1024**2)*10, command_size_limit: (1024**2)*10)
100
+ @input = input
101
+ @output = output
102
+ @logger = logger
103
+ @line_length_limit = line_length_limit
104
+ @literal_size_limit = literal_size_limit
105
+ @command_size_limit = command_size_limit
106
+ @command_tag = nil
107
+ @read_size = 0
108
+ end
109
+
110
+ attr_reader :command_tag
111
+
112
+ def gets
113
+ if (line = @input.gets($/, @line_length_limit)) then # arguments compatible with OpenSSL::Buffering#gets
114
+ if (line.bytesize < @line_length_limit) then
115
+ line
116
+ elsif (line.bytesize == @line_length_limit && (line.end_with? $/)) then
117
+ line
118
+ else
119
+ raise LineTooLongError.new('line too long.', line_fragment: line)
120
+ end
121
+ end
122
+ end
123
+
124
+ def read_literal(size)
125
+ @logger.debug("found literal: #{size} octets.") if @logger.debug?
126
+ if (size > @literal_size_limit) then
127
+ raise LiteralSizeTooLargeError.new('literal size too large', @command_tag)
128
+ end
129
+ if (@read_size + size > @command_size_limit) then
130
+ raise CommandSizeTooLargeError.new('command size too large', @command_tag)
131
+ end
132
+ @output.write("+ continue\r\n")
133
+ @output.flush
134
+ @logger.debug('continue literal.') if @logger.debug?
135
+ literal_string = @input.read(size) or raise 'unexpected client close.'
136
+ @read_size += size
137
+ @logger.debug("read literal: #{Protocol.io_data_log(literal_string)}") if @logger.debug?
138
+
139
+ literal_string
140
+ end
141
+ private :read_literal
142
+
143
+ def read_line
144
+ line = gets or return
145
+ @logger.debug("read line: #{Protocol.io_data_log(line)}") if @logger.debug?
146
+ line.chomp!("\n")
147
+ line.chomp!("\r")
148
+ @read_size += line.bytesize
149
+ if (@read_size > @command_size_limit) then
150
+ raise CommandSizeTooLargeError.new('command size too large', @command_tag)
151
+ end
152
+ atom_list = self.class.scan(line)
153
+
154
+ if (@command_tag.nil? && ! atom_list.empty?) then
155
+ unless ((atom_list[0].is_a? String) && ! (atom_list[0].start_with? '*', '+')) then
156
+ raise SyntaxError, "invalid command tag: #{atom_list[0]}"
157
+ end
158
+ @command_tag = atom_list[0]
159
+ end
160
+
161
+ if ((atom_list[-1].is_a? Array) && (atom_list[-1][0] == :literal)) then
162
+ atom_list[-1] = read_literal(atom_list[-1][1])
163
+ next_atom_list = read_line or raise 'unexpected client close.'
164
+ atom_list += next_atom_list
165
+ end
166
+
167
+ atom_list
168
+ end
169
+ private :read_line
170
+
121
171
  def read_command
172
+ @command_tag = nil
173
+ @read_size = 0
122
174
  while (atom_list = read_line)
123
175
  if (atom_list.empty?) then
176
+ @read_size = 0
124
177
  next
125
178
  end
126
179
  if (atom_list.length < 2) then
127
- raise 'need for tag and command.'
128
- end
129
- if (atom_list[0] =~ /\A [*+]/x) then
130
- raise "invalid command tag: #{atom_list[0]}"
180
+ raise SyntaxError, 'need for tag and command.'
131
181
  end
132
- return parse(atom_list)
182
+ return self.class.parse(atom_list)
133
183
  end
134
184
 
135
185
  nil
@@ -228,13 +278,15 @@ module RIMS
228
278
  end
229
279
 
230
280
  class SearchParser
231
- def initialize(mail_store, folder)
281
+ def initialize(mail_store, folder, charset_aliases: RFC822::DEFAULT_CHARSET_ALIASES, charset_convert_options: nil)
232
282
  @mail_store = mail_store
233
283
  @folder = folder
284
+ @charset_aliases = charset_aliases
285
+ @charset_convert_options = charset_convert_options || {}
234
286
  @charset = nil
235
287
  @mail_cache = Hash.new{|hash, uid|
236
288
  if (msg_txt = @mail_store.msg_text(@folder.mbox_id, uid)) then
237
- hash[uid] = RFC822::Message.new(msg_txt)
289
+ hash[uid] = RFC822::Message.new(msg_txt, charset_aliases: @charset_aliases)
238
290
  end
239
291
  }
240
292
  end
@@ -244,38 +296,38 @@ module RIMS
244
296
  end
245
297
  private :get_mail
246
298
 
247
- attr_accessor :charset
299
+ attr_reader :charset
248
300
 
249
- def string_include?(search_string, text)
250
- if (search_string.ascii_only?) then
251
- unless (text.encoding.ascii_compatible?) then
252
- text = text.encode('utf-8')
253
- end
254
- else
255
- if (@charset) then
256
- search_string = search_string.dup.force_encoding(@charset)
257
- text = text.encode(@charset)
258
- end
301
+ def charset=(new_charset)
302
+ charset_encoding = @charset_aliases[new_charset] || Encoding.find(new_charset)
303
+ if (charset_encoding.dummy?) then
304
+ # same error type as `Encoding.find'
305
+ raise ArgumentError, "not a searchable charset: #{new_charset}"
259
306
  end
307
+ @charset = charset_encoding
308
+ end
260
309
 
261
- text.include? search_string
310
+ def force_charset(string)
311
+ string = string.dup
312
+ string.force_encoding(@charset)
313
+ string.valid_encoding? or raise SyntaxError, "invalid #{@charset} string: #{string.inspect}"
314
+ string
262
315
  end
263
- private :string_include?
316
+ private :force_charset
264
317
 
265
- def mail_body_text(mail)
266
- if (mail.text? || mail.message?) then
267
- body_txt = mail.body.raw_source
268
- if (charset = mail.charset) then
269
- if (body_txt.encoding != Encoding.find(charset)) then
270
- body_txt = body_txt.dup.force_encoding(charset)
271
- end
272
- end
273
- body_txt
318
+ def encode_charset(string)
319
+ if (string.encoding == @charset) then
320
+ string
274
321
  else
275
- nil
322
+ string.encode(@charset, **@charset_convert_options)
276
323
  end
277
324
  end
278
- private :mail_body_text
325
+ private :encode_charset
326
+
327
+ def compile_search_regexp(search_string)
328
+ Regexp.new(Regexp.quote(search_string), true)
329
+ end
330
+ private :compile_search_regexp
279
331
 
280
332
  def end_of_cond
281
333
  proc{|msg| true }
@@ -310,13 +362,29 @@ module RIMS
310
362
  private :parse_msg_flag_enabled
311
363
 
312
364
  def parse_search_header(field_name, search_string)
365
+ if (@charset) then
366
+ search_string = force_charset(search_string)
367
+ search_regexp = compile_search_regexp(search_string)
368
+ search_header = proc{|mail|
369
+ mail.mime_decoded_header_field_value_list(field_name, @charset, charset_convert_options: @charset_convert_options).any?{|field_value|
370
+ search_regexp.match? field_value
371
+ }
372
+ }
373
+ else
374
+ search_string = search_string.b
375
+ search_regexp = compile_search_regexp(search_string)
376
+ search_header = proc{|mail|
377
+ mail.header.field_value_list(field_name).any?{|field_value|
378
+ search_regexp.match? field_value
379
+ }
380
+ }
381
+ end
382
+
313
383
  proc{|next_cond|
314
384
  proc{|msg|
315
385
  mail = get_mail(msg)
316
386
  if (mail.header.key? field_name) then
317
- mail.header.field_value_list(field_name).any?{|field_value|
318
- string_include?(search_string, field_value)
319
- } && next_cond.call(msg)
387
+ search_header.call(mail) && next_cond.call(msg)
320
388
  else
321
389
  false
322
390
  end
@@ -359,10 +427,40 @@ module RIMS
359
427
  private :parse_mail_bytesize
360
428
 
361
429
  def parse_body(search_string)
430
+ if (@charset)
431
+ search_string = force_charset(search_string)
432
+ search_regexp = compile_search_regexp(search_string)
433
+ search_body = proc{|mail|
434
+ if (mail.text? || mail.messge?) then
435
+ search_regexp.match? encode_charset(mail.mime_charset_body_text)
436
+ elsif (mail.multipart?) then
437
+ mail.parts.any?{|next_mail|
438
+ search_body.call(next_mail)
439
+ }
440
+ else
441
+ false
442
+ end
443
+ }
444
+ else
445
+ search_string = search_string.b
446
+ search_regexp = compile_search_regexp(search_string)
447
+ search_body = proc{|mail|
448
+ if (mail.text? || mail.message?)then
449
+ search_regexp.match? mail.mime_binary_body_string
450
+ elsif (mail.multipart?) then
451
+ mail.parts.any?{|next_mail|
452
+ search_body.call(next_mail)
453
+ }
454
+ else
455
+ false
456
+ end
457
+ }
458
+ end
459
+
362
460
  proc{|next_cond|
363
461
  proc{|msg|
364
- if (text = mail_body_text(get_mail(msg))) then
365
- string_include?(search_string, text) && next_cond.call(msg)
462
+ if (mail = get_mail(msg)) then
463
+ search_body.call(mail) && next_cond.call(msg)
366
464
  else
367
465
  false
368
466
  end
@@ -421,19 +519,44 @@ module RIMS
421
519
  private :parse_or
422
520
 
423
521
  def parse_text(search_string)
424
- search_text = proc{|message_text| string_include?(search_string, message_text) }
425
- search_mail = proc{|mail|
426
- if (mail.multipart?) then
427
- search_text.call(mail.header.raw_source) || mail.parts.any?{|m| search_mail.call(m) }
428
- else
429
- body_text = mail_body_text(mail)
430
- search_text.call(mail.header.raw_source) || (body_text && search_text.call(body_text))
431
- end
432
- }
522
+ if (@charset) then
523
+ search_string = force_charset(search_string)
524
+ search_regexp = compile_search_regexp(search_string)
525
+ search_text = proc{|mail|
526
+ if (search_regexp.match? mail.mime_decoded_header_text(@charset, charset_convert_options: @charset_convert_options)) then
527
+ true
528
+ elsif (mail.text? || mail.message?) then
529
+ search_regexp.match? encode_charset(mail.mime_charset_body_text)
530
+ elsif (mail.multipart?) then
531
+ mail.parts.any?{|next_mail|
532
+ search_text.call(next_mail)
533
+ }
534
+ else
535
+ false
536
+ end
537
+ }
538
+ else
539
+ search_string = search_string.b
540
+ search_regexp = compile_search_regexp(search_string)
541
+ search_text = proc{|mail|
542
+ if (search_regexp.match? mail.header.raw_source) then
543
+ true
544
+ elsif (mail.text? || mail.message?) then
545
+ search_regexp.match? mail.mime_binary_body_string
546
+ elsif (mail.multipart?) then
547
+ mail.parts.any?{|next_mail|
548
+ search_text.call(next_mail)
549
+ }
550
+ else
551
+ false
552
+ end
553
+ }
554
+ end
555
+
433
556
  proc{|next_cond|
434
557
  proc{|msg|
435
558
  mail = get_mail(msg)
436
- search_mail.call(mail) && next_cond.call(msg)
559
+ search_text.call(mail) && next_cond.call(msg)
437
560
  }
438
561
  }
439
562
  end
@@ -690,14 +813,10 @@ module RIMS
690
813
  if ((array.length > 0) && (array.first.is_a? Array)) then
691
814
  s = '('.b
692
815
  array = array.dup
693
- while (object = array.shift)
694
- case (object)
695
- when Array
696
- s << encode_bodystructure(object)
697
- else
698
- s << ' '.b << encode_value(object)
699
- end
700
- end
816
+ begin
817
+ s << encode_bodystructure(array.shift)
818
+ end while ((array.length > 0) && (array.first.is_a? Array))
819
+ s << ' '.b << array.map{|i| encode_value(i) }.join(' '.b)
701
820
  s << ')'.b
702
821
  elsif ((array.length > 0) && (array.first.upcase == 'MESSAGE')) then
703
822
  msg_body_list = array[0..7].map{|v| encode_value(v) }
@@ -757,13 +876,12 @@ module RIMS
757
876
  end
758
877
  include Utils
759
878
 
760
- def initialize(mail_store, folder)
879
+ def initialize(mail_store, folder, charset_aliases: RFC822::DEFAULT_CHARSET_ALIASES)
761
880
  @mail_store = mail_store
762
881
  @folder = folder
763
- @charset = nil
764
882
  @mail_cache = Hash.new{|hash, uid|
765
883
  if (msg_txt = @mail_store.msg_text(@folder.mbox_id, uid)) then
766
- hash[uid] = RFC822::Message.new(msg_txt)
884
+ hash[uid] = RFC822::Message.new(msg_txt, charset_aliases: charset_aliases)
767
885
  end
768
886
  }
769
887
  end
@@ -773,29 +891,6 @@ module RIMS
773
891
  end
774
892
  private :get_mail
775
893
 
776
- def make_array(value)
777
- if (value) then
778
- if (value.is_a? Array) then
779
- list = value
780
- else
781
- list = [ value ]
782
- end
783
-
784
- if (block_given?) then
785
- yield(list)
786
- else
787
- list
788
- end
789
- end
790
- end
791
- private :make_array
792
-
793
- def make_address_list(email_address)
794
- mailbox, host = email_address.split(/@/, 2)
795
- [ nil, nil, mailbox, host ]
796
- end
797
- private :make_address_list
798
-
799
894
  def expand_macro(cmd_list)
800
895
  func_list = cmd_list.map{|name| parse_cached(name) }
801
896
  proc{|msg|
@@ -804,76 +899,116 @@ module RIMS
804
899
  end
805
900
  private :expand_macro
806
901
 
807
- def get_header_field(mail, name, default=nil)
808
- if (field = mail[name]) then
809
- if (block_given?) then
810
- yield(field)
811
- else
812
- field
813
- end
902
+ def make_body_params(name_value_pair_list)
903
+ if (name_value_pair_list && ! name_value_pair_list.empty?) then
904
+ name_value_pair_list.flatten
814
905
  else
815
- default
906
+ # not allowed empty body field parameters.
907
+ # RFC 3501 / 9. Formal Syntax:
908
+ # body-fld-param = "(" string SP string *(SP string SP string) ")" / nil
909
+ nil
816
910
  end
817
911
  end
818
- private :get_header_field
912
+ private :make_body_params
819
913
 
820
- def get_bodystructure_data(mail)
914
+ def get_body_disposition(mail)
915
+ if (disposition_type = mail.content_disposition_upcase) then
916
+ [ disposition_type,
917
+ make_body_params(mail.content_disposition_parameter_list)
918
+ ]
919
+ else
920
+ # not allowed empty body field disposition.
921
+ # RFC 3501 / 9. Formal Syntax:
922
+ # body-fld-dsp = "(" string SP body-fld-param ")" / nil
923
+ nil
924
+ end
925
+ end
926
+ private :get_body_disposition
927
+
928
+ def get_body_lang(mail)
929
+ if (tag_list = mail.content_language_upcase) then
930
+ unless (tag_list.empty?) then
931
+ if (tag_list.length == 1) then
932
+ tag_list[0]
933
+ else
934
+ tag_list
935
+ end
936
+ end
937
+ end
938
+ end
939
+ private :get_body_lang
940
+
941
+ def get_bodystructure_data(mail, extension: false)
942
+ body_data = []
821
943
  if (mail.multipart?) then # body_type_mpart
822
- mpart_data = []
823
- mpart_data.concat(mail.parts.map{|part_msg| get_bodystructure_data(part_msg) })
824
- mpart_data << mail.media_sub_type_upcase
825
- elsif (mail.text?) then # body_type_text
826
- text_data = []
827
-
828
- # media_text
829
- text_data << mail.media_main_type_upcase
830
- text_data << mail.media_sub_type_upcase
831
-
832
- # body_fields
833
- text_data << mail.content_type_parameters.flatten
834
- text_data << mail.header['Content-Id']
835
- text_data << mail.header['Content-Description']
836
- text_data << mail.header.fetch_upcase('Content-Transfer-Encoding')
837
- text_data << mail.raw_source.bytesize
838
-
839
- # body_fld_lines
840
- text_data << mail.raw_source.each_line.count
841
- elsif (mail.message?) then # body_type_msg
842
- msg_data = []
843
-
844
- # message_media
845
- msg_data << mail.media_main_type_upcase
846
- msg_data << mail.media_sub_type_upcase
847
-
848
- # body_fields
849
- msg_data << mail.content_type_parameters.flatten
850
- msg_data << mail.header['Content-Id']
851
- msg_data << mail.header['Content-Description']
852
- msg_data << mail.header.fetch_upcase('Content-Transfer-Encoding')
853
- msg_data << mail.raw_source.bytesize
854
-
855
- # envelope
856
- msg_data << get_envelope_data(mail.message)
857
-
858
- # body
859
- msg_data << get_bodystructure_data(mail.message)
860
-
861
- # body_fld_lines
862
- msg_data << mail.raw_source.each_line.count
863
- else # body_type_basic
864
- basic_data = []
865
-
866
- # media_basic
867
- basic_data << mail.media_main_type_upcase
868
- basic_data << mail.media_sub_type_upcase
869
-
870
- # body_fields
871
- basic_data << mail.content_type_parameters.flatten
872
- basic_data << mail.header['Content-Id']
873
- basic_data << mail.header['Content-Description']
874
- basic_data << mail.header.fetch_upcase('Content-Transfer-Encoding')
875
- basic_data << mail.raw_source.bytesize
944
+ body_data.concat(mail.parts.map{|part_msg| get_bodystructure_data(part_msg, extension: extension) })
945
+ body_data << mail.media_sub_type_upcase
946
+
947
+ # body_ext_mpart
948
+ if (extension) then
949
+ body_data << make_body_params(mail.content_type_parameter_list)
950
+ body_data << get_body_disposition(mail)
951
+ body_data << get_body_lang(mail)
952
+ body_data << mail.header['Content-Location']
953
+ end
954
+ else
955
+ if (mail.text?) then # body_type_text
956
+ # media_text
957
+ body_data << mail.media_main_type_upcase
958
+ body_data << mail.media_sub_type_upcase
959
+
960
+ # body_fields
961
+ body_data << make_body_params(mail.content_type_parameter_list)
962
+ body_data << mail.header['Content-Id']
963
+ body_data << mail.header['Content-Description']
964
+ body_data << mail.header.fetch_upcase('Content-Transfer-Encoding')
965
+ body_data << mail.raw_source.bytesize
966
+
967
+ # body_fld_lines
968
+ body_data << mail.raw_source.each_line.count
969
+ elsif (mail.message?) then # body_type_msg
970
+ # message_media
971
+ body_data << mail.media_main_type_upcase
972
+ body_data << mail.media_sub_type_upcase
973
+
974
+ # body_fields
975
+ body_data << make_body_params(mail.content_type_parameter_list)
976
+ body_data << mail.header['Content-Id']
977
+ body_data << mail.header['Content-Description']
978
+ body_data << mail.header.fetch_upcase('Content-Transfer-Encoding')
979
+ body_data << mail.raw_source.bytesize
980
+
981
+ # envelope
982
+ body_data << get_envelope_data(mail.message)
983
+
984
+ # body
985
+ body_data << get_bodystructure_data(mail.message, extension: extension)
986
+
987
+ # body_fld_lines
988
+ body_data << mail.raw_source.each_line.count
989
+ else # body_type_basic
990
+ # media_basic
991
+ body_data << mail.media_main_type_upcase
992
+ body_data << mail.media_sub_type_upcase
993
+
994
+ # body_fields
995
+ body_data << make_body_params(mail.content_type_parameter_list)
996
+ body_data << mail.header['Content-Id']
997
+ body_data << mail.header['Content-Description']
998
+ body_data << mail.header.fetch_upcase('Content-Transfer-Encoding')
999
+ body_data << mail.raw_source.bytesize
1000
+ end
1001
+
1002
+ # body_ext_1part
1003
+ if (extension) then
1004
+ body_data << mail.header['Content-MD5']
1005
+ body_data << get_body_disposition(mail)
1006
+ body_data << get_body_lang(mail)
1007
+ body_data << mail.header['Content-Location']
1008
+ end
876
1009
  end
1010
+
1011
+ body_data
877
1012
  end
878
1013
  private :get_bodystructure_data
879
1014
 
@@ -881,12 +1016,12 @@ module RIMS
881
1016
  env_data = []
882
1017
  env_data << mail.header['Date']
883
1018
  env_data << mail.header['Subject']
884
- env_data << mail.from
885
- env_data << mail.sender
886
- env_data << mail.reply_to
887
- env_data << mail.to
888
- env_data << mail.cc
889
- env_data << mail.bcc
1019
+ env_data << mail.from&.map(&:to_a)
1020
+ env_data << mail.sender&.map(&:to_a)
1021
+ env_data << mail.reply_to&.map(&:to_a)
1022
+ env_data << mail.to&.map(&:to_a)
1023
+ env_data << mail.cc&.map(&:to_a)
1024
+ env_data << mail.bcc&.map(&:to_a)
890
1025
  env_data << mail.header['In-Reply-To']
891
1026
  env_data << mail.header['Message-Id']
892
1027
  end
@@ -1034,9 +1169,9 @@ module RIMS
1034
1169
  end
1035
1170
  private :parse_body
1036
1171
 
1037
- def parse_bodystructure(msg_att_name)
1172
+ def parse_bodystructure(msg_att_name, extension: false)
1038
1173
  proc{|msg|
1039
- ''.b << msg_att_name << ' '.b << encode_bodystructure(get_bodystructure_data(get_mail(msg)))
1174
+ ''.b << msg_att_name << ' '.b << encode_bodystructure(get_bodystructure_data(get_mail(msg), extension: extension))
1040
1175
  }
1041
1176
  end
1042
1177
  private :parse_bodystructure
@@ -1095,9 +1230,9 @@ module RIMS
1095
1230
  when 'ALL'
1096
1231
  fetch = expand_macro(%w[ FLAGS INTERNALDATE RFC822.SIZE ENVELOPE ])
1097
1232
  when 'BODY'
1098
- fetch = parse_bodystructure(fetch_att)
1233
+ fetch = parse_bodystructure(fetch_att, extension: false)
1099
1234
  when 'BODYSTRUCTURE'
1100
- fetch = parse_bodystructure(fetch_att)
1235
+ fetch = parse_bodystructure(fetch_att, extension: true)
1101
1236
  when 'ENVELOPE'
1102
1237
  fetch = parse_envelope(fetch_att)
1103
1238
  when 'FAST'