net-imap 0.6.3 → 0.6.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: 4249dc5d175bd3ae3a3b3b79e0f368e674ec96045510a8b504e829feefb54f3d
4
- data.tar.gz: 6adbee15b0303b36ec1c574991d4bdbf518bcd5621c6b668f05df0bc201ba50a
3
+ metadata.gz: ffcec5e3cc42d7f72c63520110ec442f760c35472401de495001f420cfa059c5
4
+ data.tar.gz: aedd703997fc651cb6c67635a3d20a4159720c6e1bda41c07322f2bd67a627d6
5
5
  SHA512:
6
- metadata.gz: a20c230ac3977d9acc09bc964badc60acbedaca84246d1ce67787c78443cdea350ade6907e58b61a5c9f09bf3b12e5e57cd319ef82a096c3780f97dc7017ed31
7
- data.tar.gz: 68ab8b48664998bbf05d5367fea4e5b06e3ba3b2d4e48ba8ae026069060b23f7dafaf7ecf2681fc5aa4ff0f134e55d9399ca51456ff3a7ec718ba829629866b7
6
+ metadata.gz: 1fa95302f29607d152b991535a9d14cda5dae1a1fce06b1d831dd2b09f69b2fd845367e0ebed73dd4e79d04c064a801e59865e76947060a4811304728458f3c7
7
+ data.tar.gz: 2dc9a56758d6f370affc9540a8ca26b8ad866ca5ef0e5b9bc2c30ff6257e3461e5ed23173e1c5e98a2e95c1e0f0a844c96f9e34d41151e9196ab513ac6987d33
data/.document ADDED
@@ -0,0 +1,3 @@
1
+ *.rdoc
2
+ *.md
3
+ lib
data/.rdoc_options ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ charset: UTF-8
3
+ main_page: README.md
4
+ markup: rdoc
5
+ title: net-imap # rake task override's title to add the version number
6
+ op_dir: doc
7
+ # vim:ft=yaml
data/Gemfile CHANGED
@@ -11,7 +11,7 @@ gem "psych", ">= 5.3.0" # 5.2.5 for Data serialization, 5.3.0 for TruffleRuby
11
11
 
12
12
  gem "irb"
13
13
  gem "rake"
14
- gem "rdoc"
14
+ gem "rdoc", ">= 7.2.0"
15
15
  gem "test-unit"
16
16
  gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core"
17
17
 
data/README.md CHANGED
@@ -61,9 +61,9 @@ imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_i
61
61
  else
62
62
  imap.copy(message_id, "Mail/sent-apr03")
63
63
  imap.store(message_id, "+FLAGS", [:Deleted])
64
+ imap.expunge
64
65
  end
65
66
  end
66
- imap.expunge
67
67
  ```
68
68
 
69
69
  ## Development
@@ -4,6 +4,8 @@ require "date"
4
4
 
5
5
  require_relative "errors"
6
6
 
7
+ # :enddoc:
8
+
7
9
  module Net
8
10
  class IMAP < Protocol
9
11
 
@@ -25,6 +27,7 @@ module Net
25
27
  end
26
28
  when Time, Date, DateTime
27
29
  when Symbol
30
+ Flag.validate(data)
28
31
  else
29
32
  data.validate
30
33
  end
@@ -45,7 +48,7 @@ module Net
45
48
  when Date
46
49
  send_date_data(data)
47
50
  when Symbol
48
- send_symbol_data(data)
51
+ Flag[data].send_data(self, tag)
49
52
  else
50
53
  data.send_data(self, tag)
51
54
  end
@@ -77,9 +80,23 @@ module Net
77
80
  put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"')
78
81
  end
79
82
 
80
- def send_literal(str, tag = nil)
83
+ def send_binary_literal(*, **) = send_literal(*, **, binary: true)
84
+
85
+ # `non_sync` is an optional tri-state flag:
86
+ # * `true` -> Force non-synchronizing `LITERAL+`/`LITERAL-` behavior.
87
+ # TODO: raise or warn when capabilities don't allow non_sync.
88
+ # * `false` -> Force normal synchronizing literal behavior.
89
+ # * `nil` -> (default) Currently behaves like `false` (will be dynamic).
90
+ def send_literal(str, tag = nil, binary: false, non_sync: nil)
81
91
  synchronize do
82
- put_string("{" + str.bytesize.to_s + "}" + CRLF)
92
+ non_sync = non_sync_literal?(str.bytesize) if non_sync.nil?
93
+ prefix = "~" if binary
94
+ plus = "+" if non_sync
95
+ put_string("#{prefix}{#{str.bytesize}#{plus}}\r\n")
96
+ if non_sync
97
+ put_string(str)
98
+ return
99
+ end
83
100
  @continued_command_tag = tag
84
101
  @continuation_request_exception = nil
85
102
  begin
@@ -94,6 +111,13 @@ module Net
94
111
  end
95
112
  end
96
113
 
114
+ 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-")))
119
+ end
120
+
97
121
  def send_number_data(num)
98
122
  put_string(num.to_s)
99
123
  end
@@ -115,11 +139,13 @@ module Net
115
139
  def send_date_data(date) put_string Net::IMAP.encode_date(date) end
116
140
  def send_time_data(time) put_string Net::IMAP.encode_time(time) end
117
141
 
118
- def send_symbol_data(symbol)
119
- put_string("\\" + symbol.to_s)
120
- end
121
-
122
142
  CommandData = Data.define(:data) do # :nodoc:
143
+ def self.validate(...)
144
+ data = new(...)
145
+ data.validate
146
+ data
147
+ end
148
+
123
149
  def send_data(imap, tag)
124
150
  raise NoMethodError, "#{self.class} must implement #{__method__}"
125
151
  end
@@ -128,15 +154,109 @@ module Net
128
154
  end
129
155
  end
130
156
 
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+.
162
+ #
163
+ # NOTE: The current implementation does not validate whether the connection
164
+ # currently supports UTF-8. Future versions may change.
165
+ #
166
+ # The string's bytes must be valid ASCII or valid UTF-8. The string's
167
+ # reported encoding is ignored, but the string is _not_ transcoded.
168
+ class RawText < CommandData # :nodoc:
169
+ def initialize(data:)
170
+ 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"))
177
+ end
178
+ super
179
+ validate
180
+ end
181
+
182
+ def validate
183
+ if data.include?("\0")
184
+ raise DataFormatError, "NULL byte must be binary literal encoded"
185
+ elsif !data.valid_encoding?
186
+ raise DataFormatError, "invalid UTF-8 must be literal encoded"
187
+ elsif /[\r\n]/.match?(data)
188
+ raise DataFormatError, "CR and LF bytes must be literal encoded"
189
+ end
190
+ end
191
+
192
+ def ascii_only? = data.ascii_only?
193
+
194
+ def send_data(imap, tag) = imap.__send__(:put_string, data)
195
+ end
196
+
131
197
  class RawData < CommandData # :nodoc:
132
- def send_data(imap, tag)
133
- imap.__send__(:put_string, data)
198
+ def initialize(data:)
199
+ data = split_parts(data)
200
+ super
201
+ validate
202
+ end
203
+
204
+ def send_data(imap, tag) = data.each do _1.send_data(imap, tag) end
205
+
206
+ def validate
207
+ return unless data.last in RawText(data: text)
208
+ if text.rindex(/~?\{[1-9]\d*\+?\}\z/n)
209
+ raise DataFormatError, "RawData cannot end with literal continuation"
210
+ end
211
+ end
212
+
213
+ private
214
+
215
+ def split_parts(data)
216
+ data = data.b # dups and ensures BINARY encoding
217
+ parts = []
218
+ while data.match(/(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n)
219
+ text, binary, bytesize, non_sync, data = $`, !!$1, $2, !!$3, $'
220
+ bytesize = NumValidator.coerce_number64 bytesize
221
+ parts << RawText[text] unless text.empty?
222
+ parts << extract_literal(data, binary:, bytesize:, non_sync:)
223
+ data.bytesplice(0, bytesize, "")
224
+ end
225
+ parts << RawText[data] unless data.empty?
226
+ parts
227
+ end
228
+
229
+ def extract_literal(data, binary:, bytesize:, non_sync:)
230
+ if data.bytesize < bytesize
231
+ raise DataFormatError, "Too few bytes in string for literal, " \
232
+ "expected: %s, remaining: %s" % [bytesize, data.bytesize]
233
+ end
234
+ literal = data.byteslice(0, bytesize)
235
+ (binary ? Literal8 : Literal).new(data: literal, non_sync:)
134
236
  end
135
237
  end
136
238
 
137
239
  class Atom < CommandData # :nodoc:
240
+ def initialize(**)
241
+ super
242
+ validate
243
+ end
244
+
245
+ def validate
246
+ data.to_s.ascii_only? \
247
+ or raise DataFormatError, "#{self.class} must be ASCII only"
248
+ data.match?(ResponseParser::Patterns::ATOM_SPECIALS) \
249
+ and raise DataFormatError, "#{self.class} must not contain atom-specials"
250
+ end
251
+
252
+ def send_data(imap, tag)
253
+ imap.__send__(:put_string, data.to_s)
254
+ end
255
+ end
256
+
257
+ class Flag < Atom # :nodoc:
138
258
  def send_data(imap, tag)
139
- imap.__send__(:put_string, data)
259
+ imap.__send__(:put_string, "\\#{data}")
140
260
  end
141
261
  end
142
262
 
@@ -146,9 +266,39 @@ module Net
146
266
  end
147
267
  end
148
268
 
149
- class Literal < CommandData # :nodoc:
269
+ class Literal < Data.define(:data, :non_sync) # :nodoc:
270
+ def self.validate(...)
271
+ data = new(...)
272
+ data.validate
273
+ data
274
+ end
275
+
276
+ def initialize(data:, non_sync: nil)
277
+ data = -String(data.to_str).b or
278
+ raise DataFormatError, "#{self.class} expects string input"
279
+ super
280
+ validate
281
+ end
282
+
283
+ def bytesize = data.bytesize
284
+
285
+ def validate
286
+ if data.include?("\0")
287
+ raise DataFormatError, "NULL byte not allowed in #{self.class}. " \
288
+ "Use #{Literal8} or a null-safe encoding."
289
+ end
290
+ end
291
+
292
+ def send_data(imap, tag)
293
+ imap.__send__(:send_literal, data, tag, non_sync:)
294
+ end
295
+ end
296
+
297
+ class Literal8 < Literal # :nodoc:
298
+ def validate = nil # all bytes are okay
299
+
150
300
  def send_data(imap, tag)
151
- imap.__send__(:send_literal, data, tag)
301
+ imap.__send__(:send_binary_literal, data, tag, non_sync:)
152
302
  end
153
303
  end
154
304
 
@@ -221,6 +371,14 @@ module Net
221
371
 
222
372
  module_function
223
373
 
374
+ def literal_or_literal8(input, name: "argument")
375
+ return input if input in Literal | Literal8
376
+ data = String.try_convert(input) \
377
+ or raise TypeError, "expected #{name} to be String, got #{input.class}"
378
+ type = data.include?("\0") ? Literal8 : Literal
379
+ type.new(data:)
380
+ end
381
+
224
382
  # Allows symbols in addition to strings
225
383
  def valid_string?(str)
226
384
  str.is_a?(Symbol) || str.respond_to?(:to_str)
@@ -281,6 +281,40 @@ module Net
281
281
  0.5r => true,
282
282
  }
283
283
 
284
+ # The maximum bytesize for sending non-synchronizing literals, when the
285
+ # server supports them. To disable non-synchronizing literals, set the
286
+ # value to +-1+.
287
+ #
288
+ # Non-synchronizing literals are only sent when the server's
289
+ # capabilities[rdoc-ref:IMAP#capabilities] have been
290
+ # cached[rdoc-ref:IMAP#capabilities_cached?] and include either
291
+ # <tt>LITERAL+</tt> [RFC7888[https://www.rfc-editor.org/rfc/rfc7888]],
292
+ # <tt>LITERAL-</tt> [RFC7888[https://www.rfc-editor.org/rfc/rfc7888]], or
293
+ # +IMAP4rev2+ [RFC9051[https://www.rfc-editor.org/rfc/rfc9051]].
294
+ #
295
+ # For <tt>LITERAL+</tt>, this value is the only limit on whether a literal
296
+ # value is sent as non-synchronizing literals. For <tt>LITERAL-</tt> and
297
+ # <tt>IMAP4rev2</tt>, non-synchronizing literals must also be smaller than
298
+ # +4096+ bytes.
299
+ #
300
+ # Non-synchronizing literals avoid the latency of waiting for the server
301
+ # to allow continuation. However, if a client sends a non-synchronizing
302
+ # literal that is too large for the server, the server may need to close
303
+ # the connection. Because <tt>LITERAL+</tt> does not directly indicate
304
+ # the server's limits, it's best to avoid sending very large
305
+ # non-synchronized literals.
306
+ #
307
+ # ==== Versioned Defaults
308
+ #
309
+ # max_non_synchronizing_literal <em>was added in +v0.6.4+.</em>
310
+ #
311
+ # * original: +-1+ (_never_ send non-synchronizing literals)
312
+ # * +0.6+: 16 KiB
313
+ attr_accessor :max_non_synchronizing_literal, type: Integer?, defaults: {
314
+ 0.0r => -1,
315
+ 0.6r => 16 << 16, # 16 KiB
316
+ }
317
+
284
318
  # The maximum allowed server response size. When +nil+, there is no limit
285
319
  # on response size.
286
320
  #
@@ -174,6 +174,22 @@ module Net
174
174
  0 < num && num <= 0xffff_ffff
175
175
  end
176
176
 
177
+ # Check if argument is a valid 'number64' according to RFC 9051
178
+ # number64 = 1*DIGIT
179
+ # ; Unsigned 63-bit integer
180
+ # ; (0 <= n <= 9,223,372,036,854,775,807)
181
+ def valid_number64?(num)
182
+ 0 <= num && num <= 0x7fff_ffff_ffff_ffff
183
+ end
184
+
185
+ # Check if argument is a valid 'number64' according to RFC 9051
186
+ # nz-number64 = digit-nz *DIGIT
187
+ # ; Unsigned 63-bit integer
188
+ # ; (0 < n <= 9,223,372,036,854,775,807)
189
+ def valid_nz_number64?(num)
190
+ 0 < num && num <= 0x7fff_ffff_ffff_ffff
191
+ end
192
+
177
193
  # Check if argument is a valid 'mod-sequence-value' according to RFC 4551
178
194
  # mod-sequence-value = 1*DIGIT
179
195
  # ; Positive unsigned 64-bit integer
@@ -203,6 +219,20 @@ module Net
203
219
  "nz-number must be non-zero unsigned 32-bit integer: #{num}"
204
220
  end
205
221
 
222
+ # Ensure argument is 'number64' or raise DataFormatError
223
+ def ensure_number64(num)
224
+ return num if valid_number64?(num)
225
+ raise DataFormatError,
226
+ "number64 must be unsigned 63-bit integer: #{num}"
227
+ end
228
+
229
+ # Ensure argument is 'nz-number64' or raise DataFormatError
230
+ def ensure_nz_number64(num)
231
+ return num if valid_nz_number64?(num)
232
+ raise DataFormatError,
233
+ "nz-number64 must be non-zero unsigned 63-bit integer: #{num}"
234
+ end
235
+
206
236
  # Ensure argument is 'mod-sequence-value' or raise DataFormatError
207
237
  def ensure_mod_sequence_value(num)
208
238
  return num if valid_mod_sequence_value?(num)
@@ -237,6 +267,26 @@ module Net
237
267
  end
238
268
  end
239
269
 
270
+ # Like #ensure_number64, but usable with numeric String input.
271
+ def coerce_number64(num)
272
+ case num
273
+ when Integer then ensure_number64 num
274
+ when NUMBER_RE then ensure_number64 Integer num
275
+ else
276
+ raise DataFormatError, "%p is not a valid number64" % [num]
277
+ end
278
+ end
279
+
280
+ # Like #ensure_nz_number64, but usable with numeric String input.
281
+ def coerce_nz_number64(num)
282
+ case num
283
+ when Integer then ensure_nz_number64 num
284
+ when NUMBER_RE then ensure_nz_number64 Integer num
285
+ else
286
+ raise DataFormatError, "%p is not a valid nz-number64" % [num]
287
+ end
288
+ end
289
+
240
290
  # Like #ensure_mod_sequence_value, but usable with numeric String input.
241
291
  def coerce_mod_sequence_value(num)
242
292
  case num
@@ -172,18 +172,11 @@ module Net
172
172
  ]
173
173
  end
174
174
  if parser_backtrace
175
- backtrace_locations&.each_with_index do |loc, idx|
176
- next if loc.base_label.include? "parse_error"
177
- break if loc.base_label == "parse"
178
- if loc.label.include?("#") # => Class#method, since ruby 3.4
179
- next unless loc.label&.include?(parser_class.name)
180
- else
181
- next unless loc.path&.include?("net/imap/response_parser")
182
- end
175
+ normalized_parser_backtrace.each do |idx, path, lineno, label, base_label|
183
176
  msg << "\n %s: %s (%s:%d)" % [
184
177
  hl["%{key}caller[%{/key}%{idx}%%2d%{/idx}%{key}]%{/key}"] % idx,
185
- hl["%{label}%%-30s%{/label}"] % loc.base_label,
186
- File.basename(loc.path, ".rb"), loc.lineno
178
+ hl["%{label}%%-30s%{/label}"] % base_label,
179
+ File.basename(path, ".rb"), lineno
187
180
  ]
188
181
  end
189
182
  end
@@ -198,12 +191,56 @@ module Net
198
191
  def processed_string = string && pos && string[...pos]
199
192
  def remaining_string = string && pos && string[pos..]
200
193
 
194
+ # Returns true when all attributes are equal, except for #backtrace and
195
+ # #backtrace_locations which are replaced with #parser_methods. This
196
+ # allows deserialized errors to be compared.
197
+ def ==(other)
198
+ return false if self.class != other.class
199
+ methods = parser_methods
200
+ other_methods = other.parser_methods
201
+ message == other.message &&
202
+ methods == other_methods &&
203
+ string == other.string &&
204
+ pos == other.pos &&
205
+ lex_state == other.lex_state &&
206
+ token == other.token
207
+ end
208
+
209
+ # Lists the methods (from #backtrace_locations or #backtrace) called on
210
+ # parser_class (since ruby 3.4) or which have "net/imap/response_parser"
211
+ # in the path (before ruby 3.4). Most parser method names are based on
212
+ # rules in the IMAP grammar.
213
+ def parser_methods = normalized_parser_backtrace.map(&:last)
214
+
201
215
  private
202
216
 
217
+ def normalized_parser_backtrace
218
+ normalize_backtrace
219
+ .take_while {|_, _, _, _, base_label| base_label != "parse" }
220
+ .reject {|_, _, _, _, base_label| base_label.nil? }
221
+ .reject {|_, _, _, _, base_label| base_label.include? "parse_error" }
222
+ .select {|_, path, _, label, _|
223
+ if label.include?("#") # => Class#method, since ruby 3.4
224
+ label.include?(parser_class.name)
225
+ else
226
+ path.include?("net/imap/response_parser")
227
+ end
228
+ }
229
+ end
230
+
231
+ def normalize_backtrace
232
+ (backtrace_locations&.each_with_index&.map {|loc, idx|
233
+ [idx, loc.path, loc.lineno, loc.label, loc.base_label]
234
+ } || backtrace&.each_with_index&.map {|bt, idx|
235
+ [idx, *bt.match(/\A(\S+):(\d+):in [`'](.*?([\w]+[?!]?))'\z/)&.captures]
236
+ } || [])
237
+ end
238
+
203
239
  def default_highlight_from_env
204
240
  (ENV["FORCE_COLOR"] || "") !~ /\A(?:0|)\z/ ||
205
241
  (ENV["TERM"] || "") !~ /\A(?:dumb|unknown|)\z/i
206
242
  end
243
+
207
244
  end
208
245
 
209
246
  # Superclass of all errors used to encapsulate "fail" responses
@@ -75,9 +75,18 @@ module Net
75
75
  #
76
76
  # Net::IMAP::UnparsedData represents data for unknown response types or
77
77
  # unknown extensions to response types without a well-defined extension
78
- # grammar.
78
+ # grammar. UnparsedData represents the portion of the response which the
79
+ # parser has skipped over, without attempting to parse it.
79
80
  #
80
- # See also: UnparsedNumericResponseData, ExtensionData, IgnoredResponse
81
+ # parser = Net::IMAP::ResponseParser.new
82
+ # response = parser.parse "* X-UNKNOWN-TYPE can't parse this\r\n"
83
+ # response => Net::IMAP::UntaggedResponse(
84
+ # name: "X-UNKNOWN-TYPE",
85
+ # data: Net::IMAP::UnparsedData(unparsed_data: "can't parse this"),
86
+ # )
87
+ #
88
+ # See also: UnparsedNumericResponseData, ExtensionData, IgnoredResponse,
89
+ # InvalidParseData.
81
90
  class UnparsedData < Struct.new(:unparsed_data)
82
91
  ##
83
92
  # method: unparsed_data
@@ -86,6 +95,61 @@ module Net
86
95
  # The unparsed data
87
96
  end
88
97
 
98
+ # **Note:** This represents an intentionally _unstable_ API. Where
99
+ # instances of this class are returned, future releases may return a
100
+ # different (incompatible) object <em>without deprecation or warning</em>.
101
+ #
102
+ # When the response parser encounters a recoverable error,
103
+ # Net::IMAP::InvalidParseData represents that portion of the response which
104
+ # could not be parsed, allowing the parser to parse the remainder of the
105
+ # response. InvalidParseData is always associated with a ResponseParseError
106
+ # which has been rescued.
107
+ #
108
+ # This could be caused by a malformed server response, by a bug in
109
+ # Net::IMAP::ResponseParser, or by an unsupported extension to the response
110
+ # syntax. For example, if a server supports +UIDPLUS+, but sends an invalid
111
+ # +COPYUID+ response code:
112
+ #
113
+ # parser = Net::IMAP::ResponseParser.new
114
+ # parsed = parser.parse "* OK [COPYUID 701 ] copied one message\r\n"
115
+ # parsed => {
116
+ # data: Net::IMAP::ResponseText(
117
+ # code: Net::IMAP::ResponseCode(
118
+ # name: "COPYUID",
119
+ # data: Net::IMAP::InvalidParseData(
120
+ # parse_error: Net::IMAP::ResponseParseError,
121
+ # unparsed_data: "701 ",
122
+ # parsed_data: nil,
123
+ # )
124
+ # )
125
+ # )
126
+ # }
127
+ #
128
+ # In this example, although <tt>[COPYUID 701 ]</tt> uses valid syntax for a
129
+ # _generic_ ResponseCode, it is _invalid_ syntax for a +COPYUID+ response
130
+ # code.
131
+ #
132
+ # See also: UnparsedData, ExtensionData
133
+ class InvalidParseData < Data.define(:parse_error, :unparsed_data, :parsed_data)
134
+ ##
135
+ # method: parse_error
136
+ # :call-seq: parse_error -> ResponseParseError
137
+ #
138
+ # Returns the rescued ResponseParseError.
139
+
140
+ ##
141
+ # method: unparsed_data
142
+ # :call-seq: unparsed_data -> string
143
+ #
144
+ # Returns the raw string which was skipped over by the parser.
145
+
146
+ ##
147
+ # method: parsed_data
148
+ #
149
+ # May return a partial parse result for unparsed_data, which had already
150
+ # been parsed before the parse_error.
151
+ end
152
+
89
153
  # **Note:** This represents an intentionally _unstable_ API. Where
90
154
  # instances of this class are returned, future releases may return a
91
155
  # different (incompatible) object <em>without deprecation or warning</em>.
@@ -93,7 +157,17 @@ module Net
93
157
  # Net::IMAP::UnparsedNumericResponseData represents data for unhandled
94
158
  # response types with a numeric prefix. See the documentation for #number.
95
159
  #
96
- # See also: UnparsedData, ExtensionData, IgnoredResponse
160
+ # parser = Net::IMAP::ResponseParser.new
161
+ # response = parser.parse "* 123 X-UNKNOWN-TYPE can't parse this\r\n"
162
+ # response => Net::IMAP::UntaggedResponse(
163
+ # name: "X-UNKNOWN-TYPE",
164
+ # data: Net::IMAP::UnparsedNumericData(
165
+ # number: 123,
166
+ # unparsed_data: "can't parse this"
167
+ # ),
168
+ # )
169
+ #
170
+ # See also: UnparsedData, ExtensionData, IgnoredResponse, InvalidParseData
97
171
  class UnparsedNumericResponseData < Struct.new(:number, :unparsed_data)
98
172
  ##
99
173
  # method: number
@@ -306,6 +380,14 @@ module Net
306
380
  # because the server doesn't allow deletion of mailboxes with children.
307
381
  # #data is +nil+.
308
382
  #
383
+ # === <tt>QUOTA=RES-*</tt> response codes
384
+ # See {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208.html#section-4.3].
385
+ # * +OVERQUOTA+ (also in RFC5530[https://www.rfc-editor.org/rfc/rfc5530]),
386
+ # with a tagged +NO+ response to an +APPEND+/+COPY+/+MOVE+ command when
387
+ # the command would put the target mailbox over any quota, and with an
388
+ # untagged +NO+ when a mailbox exceeds a soft quota (which may be caused
389
+ # be external events). #data is +nil+.
390
+ #
309
391
  # === +CONDSTORE+ extension
310
392
  # See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
311
393
  # * +NOMODSEQ+, when selecting a mailbox that does not support
@@ -324,9 +406,10 @@ module Net
324
406
  #
325
407
  # Response codes are backwards compatible: Servers are allowed to send new
326
408
  # response codes even if the client has not enabled the extension that
327
- # defines them. When Net::IMAP does not know how to parse response
328
- # code text, #data returns the unparsed string.
329
- #
409
+ # defines them. When ResponseParser does not know how to parse the response
410
+ # code data, #data may return the unparsed string, ExtensionData, or
411
+ # UnparsedData. When ResponseParser attempts but fails to parse the
412
+ # response code data, #data returns InvalidParseData.
330
413
  class ResponseCode < Struct.new(:name, :data)
331
414
  ##
332
415
  # method: name
@@ -341,8 +424,13 @@ module Net
341
424
  #
342
425
  # Returns the parsed response code data, e.g: an array of capabilities
343
426
  # strings, an array of character set strings, a list of permanent flags,
344
- # an Integer, etc. The response #code determines what form the response
345
- # code data can take.
427
+ # an Integer, etc. The response #name determines what form the response
428
+ # code #data can take.
429
+ #
430
+ # When ResponseParser does not know how to parse the response code data,
431
+ # #data may return the unparsed string, ExtensionData, or UnparsedData.
432
+ # When ResponseParser attempts but fails to parse the response code data,
433
+ # #data returns InvalidParseData.
346
434
  end
347
435
 
348
436
  # MailboxList represents the data of an untagged +LIST+ response, for a
@@ -383,14 +471,23 @@ module Net
383
471
  # and MailboxQuota objects.
384
472
  #
385
473
  # == Required capability
474
+ #
386
475
  # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
387
- # capability.
476
+ # or <tt>QUOTA=RES-STORAGE</tt>
477
+ # [RFC9208[https://www.rfc-editor.org/rfc/rfc9208]] capability.
388
478
  class MailboxQuota < Struct.new(:mailbox, :usage, :quota)
389
479
  ##
390
480
  # method: mailbox
391
481
  # :call-seq: mailbox -> string
392
482
  #
393
- # The mailbox with the associated quota.
483
+ # The quota root with the associated quota.
484
+ #
485
+ # NOTE: this was mistakenly named "mailbox". But the quota root's name may
486
+ # differ from the mailbox. A single quota root may cover multiple
487
+ # mailboxes, and a single mailbox may be governed by multiple quota roots.
488
+
489
+ # The quota root with the associated quota.
490
+ alias quota_root mailbox
394
491
 
395
492
  ##
396
493
  # method: usage
@@ -402,7 +499,7 @@ module Net
402
499
  # method: quota
403
500
  # :call-seq: quota -> Integer
404
501
  #
405
- # Quota limit imposed on the mailbox.
502
+ # Storage limit imposed on the mailbox.
406
503
  #
407
504
  end
408
505