net-imap 0.6.4 → 0.6.4.1

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: ffcec5e3cc42d7f72c63520110ec442f760c35472401de495001f420cfa059c5
4
- data.tar.gz: aedd703997fc651cb6c67635a3d20a4159720c6e1bda41c07322f2bd67a627d6
3
+ metadata.gz: 032600f694434b77edab2496c4bff9a7adc5c912301c0a7b386706dfccdc2345
4
+ data.tar.gz: 69dd23250cda6c1eb7a9bbeabe10969389c10047f12610d6bec3407d23e16854
5
5
  SHA512:
6
- metadata.gz: 1fa95302f29607d152b991535a9d14cda5dae1a1fce06b1d831dd2b09f69b2fd845367e0ebed73dd4e79d04c064a801e59865e76947060a4811304728458f3c7
7
- data.tar.gz: 2dc9a56758d6f370affc9540a8ca26b8ad866ca5ef0e5b9bc2c30ff6257e3461e5ed23173e1c5e98a2e95c1e0f0a844c96f9e34d41151e9196ab513ac6987d33
6
+ metadata.gz: 98ede1ee28c608ceea04bd5a00a114029da96eae454dc222cb046308c625241df423ceac7811322e6f641f3899ba1ca7a7d3f1bbfbf839632d2cf1a01c963c59
7
+ data.tar.gz: 810b3514de99b738216d7d49c917a05ee0c73211530bd81fe55540d158736dd7ba84f841d13f6d959b3dca01c3e32d792e500e7ee8f8eb5a2ab6899a53bf095e
@@ -16,14 +16,15 @@ module Net
16
16
  when nil
17
17
  when String
18
18
  when Integer
19
- NumValidator.ensure_number(data)
19
+ # Covers modseq-valzer, which is the largest valid IMAP integer
20
+ if data.negative?
21
+ raise DataFormatError, "Integer argument must be unsigned: #{data}"
22
+ elsif 0xffff_ffff_ffff_ffff < data
23
+ raise DataFormatError, "Integer argument must fit in 64 bits: #{data}"
24
+ end
20
25
  when Array
21
- if data[0] == 'CHANGEDSINCE'
22
- NumValidator.ensure_mod_sequence_value(data[1])
23
- else
24
- data.each do |i|
25
- validate_data(i)
26
- end
26
+ data.each do |i|
27
+ validate_data(i)
27
28
  end
28
29
  when Time, Date, DateTime
29
30
  when Symbol
@@ -76,23 +77,29 @@ module Net
76
77
  end
77
78
  end
78
79
 
79
- def send_quoted_string(str)
80
- put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"')
81
- end
80
+ def send_quoted_string(str) = QuotedString.new(data: str).send_data(self)
82
81
 
83
82
  def send_binary_literal(*, **) = send_literal(*, **, binary: true)
84
83
 
85
84
  # `non_sync` is an optional tri-state flag:
86
85
  # * `true` -> Force non-synchronizing `LITERAL+`/`LITERAL-` behavior.
87
- # TODO: raise or warn when capabilities don't allow non_sync.
86
+ # NOTE: raises DataFormatError when server doesn't support
87
+ # non-synchronizing literal, or literal is too large for LITERAL-.
88
88
  # * `false` -> Force normal synchronizing literal behavior.
89
89
  # * `nil` -> (default) Currently behaves like `false` (will be dynamic).
90
90
  def send_literal(str, tag = nil, binary: false, non_sync: nil)
91
+ bytesize = str.bytesize
91
92
  synchronize do
92
- non_sync = non_sync_literal?(str.bytesize) if non_sync.nil?
93
+ if non_sync && !non_sync_literal_allowed?(bytesize)
94
+ # TODO: check in Printer, so we don't need to close the connection.
95
+ @sock.close
96
+ raise DataFormatError, "Connection closed: " \
97
+ "Cannot send non-synchronizing literal without known server support"
98
+ end
99
+ non_sync = non_sync_literal?(bytesize) if non_sync.nil?
93
100
  prefix = "~" if binary
94
101
  plus = "+" if non_sync
95
- put_string("#{prefix}{#{str.bytesize}#{plus}}\r\n")
102
+ put_string("#{prefix}{#{bytesize}#{plus}}\r\n")
96
103
  if non_sync
97
104
  put_string(str)
98
105
  return
@@ -112,14 +119,22 @@ module Net
112
119
  end
113
120
 
114
121
  def non_sync_literal?(bytesize)
115
- capabilities_cached? &&
116
- bytesize <= config.max_non_synchronizing_literal &&
117
- (capable?("LITERAL+") ||
118
- bytesize <= 4096 && (capable?("IMAP4rev2") || capable?("LITERAL-")))
122
+ bytesize <= config.max_non_synchronizing_literal \
123
+ && non_sync_literal_allowed?(bytesize)
124
+ end
125
+
126
+ def non_sync_literal_allowed?(bytesize)
127
+ return unless capabilities_cached?
128
+ return "+" if capable?("LITERAL+")
129
+ return "-" if capable_literal_minus? && bytesize <= 4096
130
+ false
119
131
  end
120
132
 
133
+ def capable_literal_minus? = capable?("LITERAL-") || capable?("IMAP4rev2")
134
+
135
+ # NOTE: +num+ should already be an Integer
121
136
  def send_number_data(num)
122
- put_string(num.to_s)
137
+ put_string(Integer(num).to_s)
123
138
  end
124
139
 
125
140
  def send_list_data(list, tag = nil)
@@ -154,36 +169,38 @@ module Net
154
169
  end
155
170
  end
156
171
 
157
- # Represents IMAP +text+ data, which may contain any 7-bit ASCII character,
158
- # except for +NULL+, +CR+, or +LF+. +text+ is extended to allow any
159
- # multibyte +UTF-8+ character when either +UTF8=ACCEPT+ or +IMAP4rev2+ have
160
- # been enabled, or when the server supports only +IMAP4rev2+ and not earlier
161
- # IMAP revisions, or when the server advertises +UTF8=ONLY+.
172
+ # Represents IMAP +text+ or +quoted+ data, which share the same
173
+ # validations of decoded #data, and differ only in how they are formatted.
174
+ #
175
+ # +data+ may contain any 7-bit ASCII character except +NULL+, +CR+, or +LF+.
176
+ # Any multibyte +UTF-8+ character is also allowed when the connection
177
+ # supports UTF8: either +UTF8=ACCEPT+ or +IMAP4rev2+ have been enabled, or
178
+ # the server supports only +IMAP4rev2+ and not earlier IMAP revisions, or
179
+ # the server advertises +UTF8=ONLY+.
162
180
  #
163
- # NOTE: The current implementation does not validate whether the connection
164
- # currently supports UTF-8. Future versions may change.
181
+ # NOTE: This does not verify whether the connection supports UTF-8, but that
182
+ # may change in future versions.
165
183
  #
166
184
  # The string's bytes must be valid ASCII or valid UTF-8. The string's
167
185
  # reported encoding is ignored, but the string is _not_ transcoded.
168
- class RawText < CommandData # :nodoc:
186
+ class ValidNonLiteralData < CommandData
169
187
  def initialize(data:)
170
188
  data = String(data.to_str)
171
- data = if data.encoding in Encoding::ASCII | Encoding::UTF_8
172
- -data
173
- elsif data.ascii_only?
174
- -(data.dup.force_encoding("ASCII"))
175
- else
176
- -(data.dup.force_encoding("UTF-8"))
189
+ unless data.encoding in Encoding::ASCII | Encoding::UTF_8
190
+ data = data.dup.force_encoding(data.ascii_only? ? "ASCII" : "UTF-8")
177
191
  end
192
+ data = -data
178
193
  super
179
194
  validate
180
195
  end
181
196
 
182
197
  def validate
183
- if data.include?("\0")
184
- raise DataFormatError, "NULL byte must be binary literal encoded"
198
+ if !(data.encoding in Encoding::ASCII | Encoding::UTF_8)
199
+ raise DataFormatError, "must use ASCII or UTF-8 encoding"
185
200
  elsif !data.valid_encoding?
186
201
  raise DataFormatError, "invalid UTF-8 must be literal encoded"
202
+ elsif data.include?("\0")
203
+ raise DataFormatError, "NULL byte must be binary literal encoded"
187
204
  elsif /[\r\n]/.match?(data)
188
205
  raise DataFormatError, "CR and LF bytes must be literal encoded"
189
206
  end
@@ -191,12 +208,27 @@ module Net
191
208
 
192
209
  def ascii_only? = data.ascii_only?
193
210
 
194
- def send_data(imap, tag) = imap.__send__(:put_string, data)
211
+ def send_data(imap, tag = nil) = imap.__send__(:put_string, formatted)
212
+ end
213
+
214
+ # Represents IMAP +text+ data, which covers everything in the IMAP grammar,
215
+ # except for +literal+, +literal8+, and the concluding +CRLF+.
216
+ #
217
+ # NOTE: The current implementation does not verify that the connection
218
+ # supports UTF-8. Future versions may validate this.
219
+ class RawText < ValidNonLiteralData # :nodoc:
220
+ # raw: no formatting necessary
221
+ alias formatted data
195
222
  end
196
223
 
197
224
  class RawData < CommandData # :nodoc:
198
225
  def initialize(data:)
199
- data = split_parts(data)
226
+ case data
227
+ in String then data = self.class.split(data)
228
+ in Array if data.all? { _1 in RawText | Literal }
229
+ else
230
+ raise TypeError, "expected String or Array[#{RawText} | #{Literal}]"
231
+ end
200
232
  super
201
233
  validate
202
234
  end
@@ -205,14 +237,16 @@ module Net
205
237
 
206
238
  def validate
207
239
  return unless data.last in RawText(data: text)
208
- if text.rindex(/~?\{[1-9]\d*\+?\}\z/n)
240
+ if text.rindex(/\{\d+\+?\}\z/n)
209
241
  raise DataFormatError, "RawData cannot end with literal continuation"
210
242
  end
211
243
  end
212
244
 
213
- private
214
-
215
- def split_parts(data)
245
+ # Splits an input +string+ into an array of RawText and Literal/Literal8.
246
+ #
247
+ # NOTE: unlike RawData#validate, this does not prevent the final RawText
248
+ # from ending with a literal prefix.
249
+ def self.split(data)
216
250
  data = data.b # dups and ensures BINARY encoding
217
251
  parts = []
218
252
  while data.match(/(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n)
@@ -226,7 +260,7 @@ module Net
226
260
  parts
227
261
  end
228
262
 
229
- def extract_literal(data, binary:, bytesize:, non_sync:)
263
+ def self.extract_literal(data, binary:, bytesize:, non_sync:)
230
264
  if data.bytesize < bytesize
231
265
  raise DataFormatError, "Too few bytes in string for literal, " \
232
266
  "expected: %s, remaining: %s" % [bytesize, data.bytesize]
@@ -234,6 +268,7 @@ module Net
234
268
  literal = data.byteslice(0, bytesize)
235
269
  (binary ? Literal8 : Literal).new(data: literal, non_sync:)
236
270
  end
271
+ private_class_method :extract_literal
237
272
  end
238
273
 
239
274
  class Atom < CommandData # :nodoc:
@@ -247,6 +282,8 @@ module Net
247
282
  or raise DataFormatError, "#{self.class} must be ASCII only"
248
283
  data.match?(ResponseParser::Patterns::ATOM_SPECIALS) \
249
284
  and raise DataFormatError, "#{self.class} must not contain atom-specials"
285
+ data.empty? \
286
+ and raise DataFormatError, "#{self.class} must not be empty"
250
287
  end
251
288
 
252
289
  def send_data(imap, tag)
@@ -260,10 +297,13 @@ module Net
260
297
  end
261
298
  end
262
299
 
263
- class QuotedString < CommandData # :nodoc:
264
- def send_data(imap, tag)
265
- imap.__send__(:send_quoted_string, data)
266
- end
300
+ # Represents a IMAP +quoted+ string, which can encode any valid ASCII or
301
+ # UTF-8 string, unless it contains any +CR+, +LF+, or +NULL+ bytes.
302
+ #
303
+ # NOTE: The current implementation does not verify that the connection
304
+ # supports UTF-8. Future versions may validate this.
305
+ class QuotedString < ValidNonLiteralData # :nodoc:
306
+ def formatted = %("#{data.gsub(/["\\]/, "\\\\\\&")}")
267
307
  end
268
308
 
269
309
  class Literal < Data.define(:data, :non_sync) # :nodoc:
@@ -310,7 +310,7 @@ module Net
310
310
  #
311
311
  # * original: +-1+ (_never_ send non-synchronizing literals)
312
312
  # * +0.6+: 16 KiB
313
- attr_accessor :max_non_synchronizing_literal, type: Integer?, defaults: {
313
+ attr_accessor :max_non_synchronizing_literal, type: Integer, defaults: {
314
314
  0.0r => -1,
315
315
  0.6r => 16 << 16, # 16 KiB
316
316
  }
@@ -155,7 +155,8 @@ module Net
155
155
 
156
156
  # Common validators of number and nz_number types
157
157
  module NumValidator # :nodoc
158
- NUMBER_RE = /\A(?:0|[1-9]\d*)\z/
158
+ NUMBER_RE = /\A\d+\z/
159
+ NZ_NUMBER_RE = /\A[1-9]\d*\z/
159
160
  module_function
160
161
 
161
162
  # Check if argument is a valid 'number' according to RFC 3501
@@ -251,7 +252,7 @@ module Net
251
252
  def coerce_number(num)
252
253
  case num
253
254
  when Integer then ensure_number num
254
- when NUMBER_RE then ensure_number Integer num
255
+ when NUMBER_RE then ensure_number num.to_i
255
256
  else
256
257
  raise DataFormatError, "%p is not a valid number" % [num]
257
258
  end
@@ -260,8 +261,8 @@ module Net
260
261
  # Like #ensure_nz_number, but usable with numeric String input.
261
262
  def coerce_nz_number(num)
262
263
  case num
263
- when Integer then ensure_nz_number num
264
- when NUMBER_RE then ensure_nz_number Integer num
264
+ when Integer then ensure_nz_number num
265
+ when NZ_NUMBER_RE then ensure_nz_number num.to_i
265
266
  else
266
267
  raise DataFormatError, "%p is not a valid nz-number" % [num]
267
268
  end
@@ -271,7 +272,7 @@ module Net
271
272
  def coerce_number64(num)
272
273
  case num
273
274
  when Integer then ensure_number64 num
274
- when NUMBER_RE then ensure_number64 Integer num
275
+ when NUMBER_RE then ensure_number64 num.to_i
275
276
  else
276
277
  raise DataFormatError, "%p is not a valid number64" % [num]
277
278
  end
@@ -280,8 +281,8 @@ module Net
280
281
  # Like #ensure_nz_number64, but usable with numeric String input.
281
282
  def coerce_nz_number64(num)
282
283
  case num
283
- when Integer then ensure_nz_number64 num
284
- when NUMBER_RE then ensure_nz_number64 Integer num
284
+ when Integer then ensure_nz_number64 num
285
+ when NZ_NUMBER_RE then ensure_nz_number64 num.to_i
285
286
  else
286
287
  raise DataFormatError, "%p is not a valid nz-number64" % [num]
287
288
  end
@@ -291,7 +292,7 @@ module Net
291
292
  def coerce_mod_sequence_value(num)
292
293
  case num
293
294
  when Integer then ensure_mod_sequence_value num
294
- when NUMBER_RE then ensure_mod_sequence_value Integer num
295
+ when NUMBER_RE then ensure_mod_sequence_value num.to_i
295
296
  else
296
297
  raise DataFormatError, "%p is not a valid mod-sequence-value" % [num]
297
298
  end
@@ -301,7 +302,7 @@ module Net
301
302
  def coerce_mod_sequence_valzer(num)
302
303
  case num
303
304
  when Integer then ensure_mod_sequence_valzer num
304
- when NUMBER_RE then ensure_mod_sequence_valzer Integer num
305
+ when NUMBER_RE then ensure_mod_sequence_valzer num.to_i
305
306
  else
306
307
  raise DataFormatError, "%p is not a valid mod-sequence-valzer" % [num]
307
308
  end
@@ -2212,10 +2212,7 @@ module Net
2212
2212
  if $1
2213
2213
  return Token.new(T_SPACE, $+)
2214
2214
  elsif $2
2215
- len = $+.to_i
2216
- val = @str[@pos, len]
2217
- @pos += len
2218
- return Token.new(T_LITERAL8, val)
2215
+ literal_token($+, T_LITERAL8)
2219
2216
  elsif $3 && $7
2220
2217
  # greedily match ATOM, prefixed with NUMBER, NIL, or PLUS.
2221
2218
  return Token.new(T_ATOM, $3)
@@ -2243,10 +2240,7 @@ module Net
2243
2240
  elsif $15
2244
2241
  return Token.new(T_RBRA, $+)
2245
2242
  elsif $16
2246
- len = $+.to_i
2247
- val = @str[@pos, len]
2248
- @pos += len
2249
- return Token.new(T_LITERAL, val)
2243
+ literal_token($+)
2250
2244
  elsif $17
2251
2245
  return Token.new(T_PERCENT, $+)
2252
2246
  elsif $18
@@ -2272,10 +2266,7 @@ module Net
2272
2266
  elsif $4
2273
2267
  return Token.new(T_QUOTED, Patterns.unescape_quoted($+))
2274
2268
  elsif $5
2275
- len = $+.to_i
2276
- val = @str[@pos, len]
2277
- @pos += len
2278
- return Token.new(T_LITERAL, val)
2269
+ literal_token($+)
2279
2270
  elsif $6
2280
2271
  return Token.new(T_LPAR, $+)
2281
2272
  elsif $7
@@ -2290,6 +2281,15 @@ module Net
2290
2281
  else
2291
2282
  parse_error("invalid @lex_state - %s", @lex_state.inspect)
2292
2283
  end
2284
+ rescue DataFormatError => error
2285
+ parse_error error.message
2286
+ end
2287
+
2288
+ def literal_token(len, type = T_LITERAL)
2289
+ len = NumValidator.coerce_number64 len
2290
+ val = @str[@pos, len]
2291
+ @pos += len
2292
+ Token.new(type, val)
2293
2293
  end
2294
2294
 
2295
2295
  end
@@ -4,6 +4,8 @@ module Net
4
4
  class IMAP
5
5
  # See https://www.rfc-editor.org/rfc/rfc9051#section-2.2.2
6
6
  class ResponseReader # :nodoc:
7
+ include NumValidator
8
+
7
9
  attr_reader :client
8
10
 
9
11
  def initialize(client, sock)
@@ -46,7 +48,10 @@ module Net
46
48
  def line_done? = buff.end_with?(CRLF)
47
49
 
48
50
  def get_literal_size(buff)
49
- buff.end_with?("}\r\n") && buff.rindex(/\{(\d+)\}\r\n\z/n) && $1.to_i
51
+ buff.end_with?("}\r\n") && buff.rindex(/\{(\d+)\}\r\n\z/n) &&
52
+ coerce_number64($1)
53
+ rescue DataFormatError
54
+ raise DataFormatError, format("invalid response literal size (%s)", $1)
50
55
  end
51
56
 
52
57
  def read_line
data/lib/net/imap.rb CHANGED
@@ -813,7 +813,7 @@ module Net
813
813
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
814
814
  #
815
815
  class IMAP < Protocol
816
- VERSION = "0.6.4"
816
+ VERSION = "0.6.4.1"
817
817
 
818
818
  # Aliases for supported capabilities, to be used with the #enable command.
819
819
  ENABLE_ALIASES = {
@@ -1160,16 +1160,29 @@ module Net
1160
1160
  # imap.logout
1161
1161
  # imap.inspect #=> "#<Net::IMAP imap.example.net:993 TLS logout>"
1162
1162
  #
1163
+ # imap = Net::IMAP.new(hostname, ssl: false)
1164
+ # imap.inspect #=> "#<Net::IMAP imap.example.net:143 PLAINTEXT not_authenticated>"
1165
+ #
1166
+ # imap.starttls verify_mode: OpenSSL::SSL::VERIFY_NONE
1167
+ # imap.inspect #=> "#<Net::IMAP imap.example.net:993 TLS (NOT VERIFIED) not_authenticated>"
1168
+ #
1163
1169
  def inspect
1164
- tls_state = tls_verified? ? "TLS" :
1165
- ssl_ctx ? "TLS (NOT VERIFIED)" :
1166
- "PLAINTEXT"
1167
1170
  conn_state = disconnected? ? "disconnected" : connection_state.to_sym
1168
1171
  "#<%s:0x%08x %s:%s %s %s>" % [
1169
- self.class.name, __id__, host, port, tls_state, conn_state
1172
+ self.class.name, __id__, host, port, inspect_tls_state, conn_state
1170
1173
  ]
1171
1174
  end
1172
1175
 
1176
+ private def inspect_tls_state
1177
+ if tls_verified?
1178
+ "TLS"
1179
+ elsif ssl_ctx && @sock.kind_of?(OpenSSL::SSL::SSLSocket)
1180
+ "TLS (#{@sock.session ? "NOT VERIFIED" : "NOT ESTABLISHED"})"
1181
+ else
1182
+ "PLAINTEXT#{" (TLS NOT STARTED)" if ssl_ctx}"
1183
+ end
1184
+ end
1185
+
1173
1186
  # Returns true after the TLS negotiation has completed and the remote
1174
1187
  # hostname has been verified. Returns false when TLS has been established
1175
1188
  # but peer verification was disabled.
@@ -1177,22 +1190,24 @@ module Net
1177
1190
 
1178
1191
  # Disconnects from the server.
1179
1192
  #
1180
- # Waits for receiver thread to close before returning. Slow or stuck
1181
- # response handlers can cause #disconnect to hang until they complete.
1193
+ # Waits for receiver thread to close before returning, except when called
1194
+ # from inside the connection mutex such as from a response handler. Slow or
1195
+ # stuck response handlers can cause #disconnect to hang until they complete.
1182
1196
  #
1183
1197
  # Related: #logout, #logout!
1184
1198
  def disconnect
1185
1199
  in_logout_state = try_state_logout?
1186
1200
  return if disconnected?
1201
+ in_receiver_thread = Thread.current == @receiver_thread
1187
1202
  begin
1188
1203
  @sock.to_io.shutdown
1189
1204
  rescue Errno::ENOTCONN
1190
1205
  # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms.
1191
1206
  rescue Exception => e
1192
- @receiver_thread.raise(e)
1207
+ @receiver_thread.raise(e) unless in_receiver_thread
1193
1208
  end
1194
1209
  @sock.close
1195
- @receiver_thread.join
1210
+ @receiver_thread.join unless mon_owned? || in_receiver_thread
1196
1211
  raise e if e
1197
1212
  ensure
1198
1213
  # Try again after shutting down the receiver thread. With no reciever
@@ -2242,6 +2257,7 @@ module Net
2242
2257
  # provided as an array or a string.
2243
2258
  # See {"Argument translation"}[rdoc-ref:#search@Argument+translation]
2244
2259
  # and {"Search criteria"}[rdoc-ref:#search@Search+criteria], below.
2260
+ # <em>Please note</em> the warning for when +criteria+ is a String.
2245
2261
  #
2246
2262
  # +return+ options control what kind of information is returned about
2247
2263
  # messages matching the search +criteria+. Specifying +return+ should force
@@ -2652,7 +2668,8 @@ module Net
2652
2668
  # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
2653
2669
  # capability has been enabled.
2654
2670
  #
2655
- # See #search for documentation of parameters.
2671
+ # See #search for documentation of parameters. <em>Please note</em> the
2672
+ # warning for when +criteria+ is a String.
2656
2673
  #
2657
2674
  # ==== Capabilities
2658
2675
  #
@@ -2738,7 +2755,8 @@ module Net
2738
2755
  # {SequenceSet[...]}[rdoc-ref:SequenceSet@Creating+sequence+sets].
2739
2756
  # (For message sequence numbers, use #fetch instead.)
2740
2757
  #
2741
- # +attr+ behaves the same as with #fetch.
2758
+ # +attr+ behaves the same as with #fetch. <em>Please note</em> the #fetch
2759
+ # warning on the +attr+ argument.
2742
2760
  # >>>
2743
2761
  # *Note:* Servers _MUST_ implicitly include the +UID+ message data item as
2744
2762
  # part of any +FETCH+ response caused by a +UID+ command, regardless of
@@ -2950,8 +2968,10 @@ module Net
2950
2968
 
2951
2969
  # Sends a {SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3]
2952
2970
  # to search a mailbox for messages that match +search_keys+ and return an
2953
- # array of message sequence numbers, sorted by +sort_keys+. +search_keys+
2954
- # are interpreted the same as for #search.
2971
+ # array of message sequence numbers, sorted by +sort_keys+.
2972
+ #
2973
+ # +search_keys+ are interpreted the same as the +criteria+ argument for
2974
+ # #search. <em>Please note</em> the #search warning for String +criteria+.
2955
2975
  #
2956
2976
  #--
2957
2977
  # TODO: describe +sort_keys+
@@ -2976,8 +2996,10 @@ module Net
2976
2996
 
2977
2997
  # Sends a {UID SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3]
2978
2998
  # to search a mailbox for messages that match +search_keys+ and return an
2979
- # array of unique identifiers, sorted by +sort_keys+. +search_keys+ are
2980
- # interpreted the same as for #search.
2999
+ # array of unique identifiers, sorted by +sort_keys+.
3000
+ #
3001
+ # +search_keys+ are interpreted the same as the +criteria+ argument for
3002
+ # #search. <em>Please note</em> the #search warning for String +criteria+.
2981
3003
  #
2982
3004
  # Related: #sort, #search, #uid_search, #thread, #uid_thread
2983
3005
  #
@@ -2991,8 +3013,10 @@ module Net
2991
3013
 
2992
3014
  # Sends a {THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3]
2993
3015
  # to search a mailbox and return message sequence numbers in threaded
2994
- # format, as a ThreadMember tree. +search_keys+ are interpreted the same as
2995
- # for #search.
3016
+ # format, as a ThreadMember tree.
3017
+ #
3018
+ # +search_keys+ are interpreted the same as the +criteria+ argument for
3019
+ # #search. <em>Please note</em> the #search warning for String +criteria+.
2996
3020
  #
2997
3021
  # The supported algorithms are:
2998
3022
  #
@@ -3018,6 +3042,9 @@ module Net
3018
3042
  # Similar to #thread, but returns unique identifiers instead of
3019
3043
  # message sequence numbers.
3020
3044
  #
3045
+ # +search_keys+ are interpreted the same as the +criteria+ argument for
3046
+ # #search. <em>Please note</em> the #search warning for String +criteria+.
3047
+ #
3021
3048
  # Related: #thread, #search, #uid_search, #sort, #uid_sort
3022
3049
  #
3023
3050
  # ==== Capabilities
@@ -3127,10 +3154,11 @@ module Net
3127
3154
  capabilities = capabilities
3128
3155
  .flatten
3129
3156
  .map {|e| ENABLE_ALIASES[e] || e }
3157
+ .flat_map { _1.is_a?(String) && !_1.empty? ? _1.split(/ /, -1) : [_1] }
3130
3158
  .uniq
3131
- .join(' ')
3159
+ .map { Atom[_1] }
3132
3160
  synchronize do
3133
- send_command("ENABLE #{capabilities}")
3161
+ send_command("ENABLE", *capabilities)
3134
3162
  result = clear_responses("ENABLED").last || []
3135
3163
  @utf8_strings ||= result.include? "UTF8=ACCEPT"
3136
3164
  @utf8_strings ||= result.include? "IMAP4REV2"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-imap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.4
4
+ version: 0.6.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
@@ -131,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
131
  - !ruby/object:Gem::Version
132
132
  version: '0'
133
133
  requirements: []
134
- rubygems_version: 4.0.10
134
+ rubygems_version: 4.0.13
135
135
  specification_version: 4
136
136
  summary: Ruby client api for Internet Message Access Protocol
137
137
  test_files: []