net-imap 0.4.10 → 0.4.20

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.
@@ -5,6 +5,9 @@ module Net
5
5
  autoload :FetchData, "#{__dir__}/fetch_data"
6
6
  autoload :SearchResult, "#{__dir__}/search_result"
7
7
  autoload :SequenceSet, "#{__dir__}/sequence_set"
8
+ autoload :UIDPlusData, "#{__dir__}/uidplus_data"
9
+ autoload :AppendUIDData, "#{__dir__}/uidplus_data"
10
+ autoload :CopyUIDData, "#{__dir__}/uidplus_data"
8
11
 
9
12
  # Net::IMAP::ContinuationRequest represents command continuation requests.
10
13
  #
@@ -58,7 +61,7 @@ module Net
58
61
 
59
62
  # Net::IMAP::IgnoredResponse represents intentionally ignored responses.
60
63
  #
61
- # This includes untagged response "NOOP" sent by eg. Zimbra to avoid
64
+ # This includes untagged response "NOOP" sent by e.g. Zimbra to avoid
62
65
  # some clients to close the connection.
63
66
  #
64
67
  # It matches no IMAP standard.
@@ -280,7 +283,7 @@ module Net
280
283
  # ==== +QRESYNC+ extension
281
284
  # See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
282
285
  # * +CLOSED+, returned when the currently selected mailbox is closed
283
- # implicity by selecting or examining another mailbox. #data is +nil+.
286
+ # implicitly by selecting or examining another mailbox. #data is +nil+.
284
287
  #
285
288
  # ==== +IMAP4rev2+ Response Codes
286
289
  # See {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051] {§7.1, "Server
@@ -324,60 +327,6 @@ module Net
324
327
  # code data can take.
325
328
  end
326
329
 
327
- # Net::IMAP::UIDPlusData represents the ResponseCode#data that accompanies
328
- # the +APPENDUID+ and +COPYUID+ response codes.
329
- #
330
- # See [[UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]].
331
- #
332
- # ==== Capability requirement
333
- #
334
- # The +UIDPLUS+ capability[rdoc-ref:Net::IMAP#capability] must be supported.
335
- # A server that supports +UIDPLUS+ should send a UIDPlusData object inside
336
- # every TaggedResponse returned by the append[rdoc-ref:Net::IMAP#append],
337
- # copy[rdoc-ref:Net::IMAP#copy], move[rdoc-ref:Net::IMAP#move], {uid
338
- # copy}[rdoc-ref:Net::IMAP#uid_copy], and {uid
339
- # move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the destination
340
- # mailbox reports +UIDNOTSTICKY+.
341
- #
342
- #--
343
- # TODO: support MULTIAPPEND
344
- #++
345
- #
346
- class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
347
- ##
348
- # method: uidvalidity
349
- # :call-seq: uidvalidity -> nonzero uint32
350
- #
351
- # The UIDVALIDITY of the destination mailbox.
352
-
353
- ##
354
- # method: source_uids
355
- # :call-seq: source_uids -> nil or an array of nonzero uint32
356
- #
357
- # The UIDs of the copied or moved messages.
358
- #
359
- # Note:: Returns +nil+ for Net::IMAP#append.
360
-
361
- ##
362
- # method: assigned_uids
363
- # :call-seq: assigned_uids -> an array of nonzero uint32
364
- #
365
- # The newly assigned UIDs of the copied, moved, or appended messages.
366
- #
367
- # Note:: This always returns an array, even when it contains only one UID.
368
-
369
- ##
370
- # :call-seq: uid_mapping -> nil or a hash
371
- #
372
- # Returns a hash mapping each source UID to the newly assigned destination
373
- # UID.
374
- #
375
- # Note:: Returns +nil+ for Net::IMAP#append.
376
- def uid_mapping
377
- source_uids&.zip(assigned_uids)&.to_h
378
- end
379
- end
380
-
381
330
  # Net::IMAP::MailboxList represents contents of the LIST response,
382
331
  # representing a single mailbox path.
383
332
  #
@@ -1045,7 +994,7 @@ module Net
1045
994
  # === Bug Analysis
1046
995
  #
1047
996
  # \IMAP body structures are parenthesized lists and assign their fields
1048
- # positionally, so missing fields change the intepretation of all
997
+ # positionally, so missing fields change the interpretation of all
1049
998
  # following fields. Additionally, different body types have a different
1050
999
  # number of required fields, followed by optional "extension" fields.
1051
1000
  #
@@ -1060,7 +1009,7 @@ module Net
1060
1009
  # Normally, +envelope+ and +md5+ are incompatible, but Net::IMAP leniently
1061
1010
  # allowed buggy servers to send +NIL+ for +envelope+. As a result, when a
1062
1011
  # server sent a <tt>message/rfc822</tt> part with +NIL+ for +md5+ and a
1063
- # non-<tt>NIL</tt> +dsp+, Net::IMAP mis-interpreted the
1012
+ # non-<tt>NIL</tt> +dsp+, Net::IMAP misinterpreted the
1064
1013
  # <tt>Content-Disposition</tt> as if it were a strange body type. In all
1065
1014
  # reported cases, the <tt>Content-Disposition</tt> was "attachment", so
1066
1015
  # BodyTypeAttachment was created as the workaround.
@@ -1068,7 +1017,7 @@ module Net
1068
1017
  # === Current behavior
1069
1018
  #
1070
1019
  # When interpreted strictly, +envelope+ and +md5+ are incompatible. So the
1071
- # current parsing algorithm peeks ahead after it has recieved the seventh
1020
+ # current parsing algorithm peeks ahead after it has received the seventh
1072
1021
  # body field. If the next token is not the start of an +envelope+, we assume
1073
1022
  # the server has incorrectly sent us a <tt>body-type-basic</tt> and return
1074
1023
  # BodyTypeBasic. As a result, what was previously BodyTypeMessage#body =>
@@ -154,7 +154,7 @@ module Net
154
154
  end
155
155
 
156
156
  # To be used conditionally:
157
- # assert_no_lookahead if Net::IMAP.debug
157
+ # assert_no_lookahead if config.debug?
158
158
  def assert_no_lookahead
159
159
  @token.nil? or
160
160
  parse_error("assertion failed: expected @token.nil?, actual %s: %p",
@@ -181,23 +181,23 @@ module Net
181
181
  end
182
182
 
183
183
  def peek_str?(str)
184
- assert_no_lookahead if Net::IMAP.debug
184
+ assert_no_lookahead if config.debug?
185
185
  @str[@pos, str.length] == str
186
186
  end
187
187
 
188
188
  def peek_re(re)
189
- assert_no_lookahead if Net::IMAP.debug
189
+ assert_no_lookahead if config.debug?
190
190
  re.match(@str, @pos)
191
191
  end
192
192
 
193
193
  def accept_re(re)
194
- assert_no_lookahead if Net::IMAP.debug
194
+ assert_no_lookahead if config.debug?
195
195
  re.match(@str, @pos) and @pos = $~.end(0)
196
196
  $~
197
197
  end
198
198
 
199
199
  def match_re(re, name)
200
- assert_no_lookahead if Net::IMAP.debug
200
+ assert_no_lookahead if config.debug?
201
201
  if re.match(@str, @pos)
202
202
  @pos = $~.end(0)
203
203
  $~
@@ -212,7 +212,7 @@ module Net
212
212
 
213
213
  def parse_error(fmt, *args)
214
214
  msg = format(fmt, *args)
215
- if IMAP.debug
215
+ if config.debug?
216
216
  local_path = File.dirname(__dir__)
217
217
  tok = @token ? "%s: %p" % [@token.symbol, @token.value] : "nil"
218
218
  warn "%s %s: %s" % [self.class, __method__, msg]
@@ -11,12 +11,19 @@ module Net
11
11
  include ParserUtils
12
12
  extend ParserUtils::Generator
13
13
 
14
- # :call-seq: Net::IMAP::ResponseParser.new -> Net::IMAP::ResponseParser
15
- def initialize
14
+ attr_reader :config
15
+
16
+ # Creates a new ResponseParser.
17
+ #
18
+ # When +config+ is frozen or global, the parser #config inherits from it.
19
+ # Otherwise, +config+ will be used directly.
20
+ def initialize(config: Config.global)
16
21
  @str = nil
17
22
  @pos = nil
18
23
  @lex_state = nil
19
24
  @token = nil
25
+ @config = Config[config]
26
+ @config = @config.new if @config == Config.global || @config.frozen?
20
27
  end
21
28
 
22
29
  # :call-seq:
@@ -1155,6 +1162,7 @@ module Net
1155
1162
  # RFC3501, RFC9051:
1156
1163
  # body-fld-param = "(" string SP string *(SP string SP string) ")" / nil
1157
1164
  def body_fld_param
1165
+ quirky_SP? # See comments on test_bodystructure_extra_space
1158
1166
  return if NIL?
1159
1167
  param = {}
1160
1168
  lpar
@@ -1335,7 +1343,8 @@ module Net
1335
1343
  assert_no_lookahead
1336
1344
  start = @pos
1337
1345
  astring
1338
- @str[start...@pos - 1]
1346
+ end_pos = @token ? @pos - 1 : @pos
1347
+ @str[start...end_pos]
1339
1348
  end
1340
1349
 
1341
1350
  # mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list /
@@ -1858,11 +1867,10 @@ module Net
1858
1867
  #
1859
1868
  # n.b, uniqueid ⊂ uid-set. To avoid inconsistent return types, we always
1860
1869
  # match uid_set even if that returns a single-member array.
1861
- #
1862
1870
  def resp_code_apnd__data
1863
1871
  validity = number; SP!
1864
1872
  dst_uids = uid_set # uniqueid ⊂ uid-set
1865
- UIDPlusData.new(validity, nil, dst_uids)
1873
+ AppendUID(validity, dst_uids)
1866
1874
  end
1867
1875
 
1868
1876
  # already matched: "COPYUID"
@@ -1872,7 +1880,25 @@ module Net
1872
1880
  validity = number; SP!
1873
1881
  src_uids = uid_set; SP!
1874
1882
  dst_uids = uid_set
1875
- UIDPlusData.new(validity, src_uids, dst_uids)
1883
+ CopyUID(validity, src_uids, dst_uids)
1884
+ end
1885
+
1886
+ def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
1887
+ def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end
1888
+
1889
+ # TODO: remove this code in the v0.6.0 release
1890
+ def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids)
1891
+ return unless config.parser_use_deprecated_uidplus_data
1892
+ compact_uid_sets = [src_uids, dst_uids].compact
1893
+ count = compact_uid_sets.map { _1.count_with_duplicates }.max
1894
+ max = config.parser_max_deprecated_uidplus_data_size
1895
+ if count <= max
1896
+ src_uids &&= src_uids.each_ordered_number.to_a
1897
+ dst_uids = dst_uids.each_ordered_number.to_a
1898
+ UIDPlusData.new(validity, src_uids, dst_uids)
1899
+ elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size
1900
+ parse_error("uid-set is too large: %d > %d", count, max)
1901
+ end
1876
1902
  end
1877
1903
 
1878
1904
  ADDRESS_REGEXP = /\G
@@ -1998,15 +2024,9 @@ module Net
1998
2024
  # uniqueid = nz-number
1999
2025
  # ; Strictly ascending
2000
2026
  def uid_set
2001
- token = match(T_NUMBER, T_ATOM)
2002
- case token.symbol
2003
- when T_NUMBER then [Integer(token.value)]
2004
- when T_ATOM
2005
- token.value.split(",").flat_map {|range|
2006
- range = range.split(":").map {|uniqueid| Integer(uniqueid) }
2007
- range.size == 1 ? range : Range.new(range.min, range.max).to_a
2008
- }
2009
- end
2027
+ set = sequence_set
2028
+ parse_error("uid-set cannot contain '*'") if set.include_star?
2029
+ set
2010
2030
  end
2011
2031
 
2012
2032
  def nil_atom
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ # See https://www.rfc-editor.org/rfc/rfc9051#section-2.2.2
6
+ class ResponseReader # :nodoc:
7
+ attr_reader :client
8
+
9
+ def initialize(client, sock)
10
+ @client, @sock = client, sock
11
+ end
12
+
13
+ def read_response_buffer
14
+ @buff = String.new
15
+ catch :eof do
16
+ while true
17
+ read_line
18
+ break unless (@literal_size = get_literal_size)
19
+ read_literal
20
+ end
21
+ end
22
+ buff
23
+ ensure
24
+ @buff = nil
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :buff, :literal_size
30
+
31
+ def bytes_read; buff.bytesize end
32
+ def empty?; buff.empty? end
33
+ def done?; line_done? && !get_literal_size end
34
+ def line_done?; buff.end_with?(CRLF) end
35
+ def get_literal_size; /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i end
36
+
37
+ def read_line
38
+ buff << (@sock.gets(CRLF, read_limit) or throw :eof)
39
+ max_response_remaining! unless line_done?
40
+ end
41
+
42
+ def read_literal
43
+ # check before allocating memory for literal
44
+ max_response_remaining!
45
+ literal = String.new(capacity: literal_size)
46
+ buff << (@sock.read(read_limit(literal_size), literal) or throw :eof)
47
+ ensure
48
+ @literal_size = nil
49
+ end
50
+
51
+ def read_limit(limit = nil)
52
+ [limit, max_response_remaining!].compact.min
53
+ end
54
+
55
+ def max_response_size; client.max_response_size end
56
+ def max_response_remaining; max_response_size &.- bytes_read end
57
+ def response_too_large?; max_response_size &.< min_response_size end
58
+ def min_response_size; bytes_read + min_response_remaining end
59
+
60
+ def min_response_remaining
61
+ empty? ? 3 : done? ? 0 : (literal_size || 0) + 2
62
+ end
63
+
64
+ def max_response_remaining!
65
+ return max_response_remaining unless response_too_large?
66
+ raise ResponseTooLargeError.new(
67
+ max_response_size: max_response_size,
68
+ bytes_read: bytes_read,
69
+ literal_size: literal_size,
70
+ )
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -9,7 +9,7 @@ module Net
9
9
  # Net::IMAP#authenticate.
10
10
  #
11
11
  # The EXTERNAL mechanism requests that the server use client credentials
12
- # established external to SASL, for example by TLS certificate or IPsec.
12
+ # established external to SASL, for example by TLS certificate or IPSec.
13
13
  class ExternalAuthenticator
14
14
 
15
15
  # Authorization identity: an identity to act as or on behalf of. The
data/lib/net/imap/sasl.rb CHANGED
@@ -37,7 +37,7 @@ module Net
37
37
  # See ExternalAuthenticator.
38
38
  #
39
39
  # Authenticates using already established credentials, such as a TLS
40
- # certificate or IPsec.
40
+ # certificate or IPSec.
41
41
  #
42
42
  # +OAUTHBEARER+::
43
43
  # See OAuthBearerAuthenticator.