net-imap 0.3.8 → 0.3.10

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: 1c0b89eb763616a2d446e7a145245fcc88906162cdb6e41e1f73b89ac54269fb
4
- data.tar.gz: aa19c272ab1142f1ebec8d40a7f56eeb348286188c154b9a4cfcd0819a2d4b18
3
+ metadata.gz: cb2a06dab230abb1358956fd53b2bdf4235a5443ab81d2e4d95df1d253fb4618
4
+ data.tar.gz: df7e4bf879551b5dfde3affca7c391d8fb2b2d6457247bb06567c4500e7c4a81
5
5
  SHA512:
6
- metadata.gz: 93819c8a2aa93f8e5e5737626981fadef9038e31afdec124f70ff6e29b4c233877031d64f3e19486e2976a713da40bed3e8cdf5d5939e953fa3b9cd864915ede
7
- data.tar.gz: f07545087c1d4c1faece211b902096782421fba4f5d81ceab07f8e4c5fed0f2e3918e4c4ba027231ab52038edf4b7dc3a0c4200bf0f1480addfa6042aaf7716e
6
+ metadata.gz: 180b80a10c10cb1bfc5267e665d256b7c551260cc7069ecebfeaa0673f1a09b552284f506e0b25e3dbd9bbb75a0d5bf9501c8428ba3a645ff163326cf2215a82
7
+ data.tar.gz: b7f47f69d736ba0586d910154d2a94981d12ddcf5db9f95b4ec9d4909d463405ee09c3073dc7060825d66e028edbe269138d41697faa99a09128750b13455f6c
data/Gemfile CHANGED
@@ -5,5 +5,5 @@ source "https://rubygems.org"
5
5
  gemspec
6
6
 
7
7
  gem "rake"
8
- gem "rdoc"
8
+ gem "rdoc", "<7.2" # incompatible with ruby 2.6
9
9
  gem "test-unit"
@@ -11,6 +11,40 @@ module Net
11
11
  class DataFormatError < Error
12
12
  end
13
13
 
14
+ # Error raised when the socket cannot be read, due to a configured limit.
15
+ class ResponseReadError < Error
16
+ end
17
+
18
+ # Error raised when a response is larger than IMAP#max_response_size.
19
+ class ResponseTooLargeError < ResponseReadError
20
+ attr_reader :bytes_read, :literal_size
21
+ attr_reader :max_response_size
22
+
23
+ def initialize(msg = nil, *args,
24
+ bytes_read: nil,
25
+ literal_size: nil,
26
+ max_response_size: nil,
27
+ **kwargs)
28
+ @bytes_read = bytes_read
29
+ @literal_size = literal_size
30
+ @max_response_size = max_response_size
31
+ msg ||= [
32
+ "Response size", response_size_msg, "exceeds max_response_size",
33
+ max_response_size && "(#{max_response_size}B)",
34
+ ].compact.join(" ")
35
+ return super(msg, *args) if kwargs.empty? # ruby 2.6 compatibility
36
+ super(msg, *args, **kwargs)
37
+ end
38
+
39
+ private
40
+
41
+ def response_size_msg
42
+ if bytes_read && literal_size
43
+ "(#{bytes_read}B read + #{literal_size}B literal)"
44
+ end
45
+ end
46
+ end
47
+
14
48
  # Error raised when a response from the server is non-parseable.
15
49
  class ResponseParseError < Error
16
50
  end
@@ -47,7 +81,27 @@ module Net
47
81
  class ByeResponseError < ResponseError
48
82
  end
49
83
 
84
+ # Error raised when the server sends an invalid response.
85
+ #
86
+ # This is different from UnknownResponseError: the response has been
87
+ # rejected. Although it may be parsable, the server is forbidden from
88
+ # sending it in the current context. The client should automatically
89
+ # disconnect, abruptly (without logout).
90
+ #
91
+ # Note that InvalidResponseError does not inherit from ResponseError: it
92
+ # can be raised before the response is fully parsed. A related
93
+ # ResponseParseError or ResponseError may be the #cause.
94
+ class InvalidResponseError < Error
95
+ end
96
+
50
97
  # Error raised upon an unknown response from the server.
98
+ #
99
+ # This is different from InvalidResponseError: the response may be a
100
+ # valid extension response and the server may be allowed to send it in
101
+ # this context, but Net::IMAP either does not know how to parse it or
102
+ # how to handle it. This could result from enabling unknown or
103
+ # unhandled extensions. The connection may still be usable,
104
+ # but—depending on context—it may be prudent to disconnect.
51
105
  class UnknownResponseError < ResponseError
52
106
  end
53
107
 
@@ -1382,7 +1382,7 @@ module Net
1382
1382
  when T_NUMBER then [Integer(token.value)]
1383
1383
  when T_ATOM
1384
1384
  entries = uid_set__ranges(token.value)
1385
- if (count = entries.sum(&:count)) > MAX_UID_SET_SIZE
1385
+ if (count = entries.sum(&:size)) > MAX_UID_SET_SIZE
1386
1386
  parse_error("uid-set is too large: %d > 10k", count)
1387
1387
  end
1388
1388
  entries.flat_map(&:to_a)
@@ -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
data/lib/net/imap.rb CHANGED
@@ -45,10 +45,16 @@ module Net
45
45
  # To work on the messages within a mailbox, the client must
46
46
  # first select that mailbox, using either #select or #examine
47
47
  # (for read-only access). Once the client has successfully
48
- # selected a mailbox, they enter the "_selected_" state, and that
48
+ # selected a mailbox, they enter the +selected+ state, and that
49
49
  # mailbox becomes the _current_ mailbox, on which mail-item
50
50
  # related commands implicitly operate.
51
51
  #
52
+ # === Connection state
53
+ #
54
+ # Once an IMAP connection is established, the connection is in one of four
55
+ # states: <tt>not authenticated</tt>, +authenticated+, +selected+, and
56
+ # +logout+. Most commands are valid only in certain states.
57
+ #
52
58
  # === Sequence numbers and UIDs
53
59
  #
54
60
  # Messages have two sorts of identifiers: message sequence
@@ -126,6 +132,41 @@ module Net
126
132
  #
127
133
  # This script invokes the FETCH command and the SEARCH command concurrently.
128
134
  #
135
+ # When running multiple commands, care must be taken to avoid ambiguity. For
136
+ # example, SEARCH responses are ambiguous about which command they are
137
+ # responding to, so search commands should not run simultaneously, unless the
138
+ # server supports +ESEARCH+ {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731] or
139
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]. See {RFC9051
140
+ # §5.5}[https://www.rfc-editor.org/rfc/rfc9051.html#section-5.5] for
141
+ # other examples of command sequences which should not be pipelined.
142
+ #
143
+ # == Unbounded memory use
144
+ #
145
+ # Net::IMAP reads server responses in a separate receiver thread per client.
146
+ # Unhandled response data is saved to #responses, and response_handlers run
147
+ # inside the receiver thread. See the list of methods for {handling server
148
+ # responses}[rdoc-ref:Net::IMAP@Handling+server+responses], below.
149
+ #
150
+ # Because the receiver thread continuously reads and saves new responses, some
151
+ # scenarios must be careful to avoid unbounded memory use:
152
+ #
153
+ # * Commands such as #list or #fetch can have an enormous number of responses.
154
+ # * Commands such as #fetch can result in an enormous size per response.
155
+ # * Long-lived connections will gradually accumulate unsolicited server
156
+ # responses, especially +EXISTS+, +FETCH+, and +EXPUNGE+ responses.
157
+ # * A buggy or untrusted server could send inappropriate responses, which
158
+ # could be very numerous, very large, and very rapid.
159
+ #
160
+ # Use paginated or limited versions of commands whenever possible.
161
+ #
162
+ # Use #max_response_size to impose a limit on incoming server responses
163
+ # as they are being read. <em>This is especially important for untrusted
164
+ # servers.</em>
165
+ #
166
+ # Use #add_response_handler to handle responses after each one is received.
167
+ # Use the +response_handlers+ argument to ::new to assign response handlers
168
+ # before the receiver thread is started.
169
+ #
129
170
  # == Errors
130
171
  #
131
172
  # An \IMAP server can send three different types of responses to indicate
@@ -187,7 +228,7 @@ module Net
187
228
  # - Net::IMAP.new: A new client connects immediately and waits for a
188
229
  # successful server greeting before returning the new client object.
189
230
  # - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
190
- # - #logout: Tells the server to end the session. Enters the "_logout_" state.
231
+ # - #logout: Tells the server to end the session. Enters the +logout+ state.
191
232
  # - #disconnect: Disconnects the connection (without sending #logout first).
192
233
  # - #disconnected?: True if the connection has been closed.
193
234
  #
@@ -230,40 +271,39 @@ module Net
230
271
  # <em>Capabilities may change after</em> #starttls, #authenticate, or #login
231
272
  # <em>and cached capabilities must be reloaded.</em>
232
273
  # - #noop: Allows the server to send unsolicited untagged #responses.
233
- # - #logout: Tells the server to end the session. Enters the "_logout_" state.
274
+ # - #logout: Tells the server to end the session. Enters the +logout+ state.
234
275
  #
235
276
  # ==== \IMAP commands for the "Not Authenticated" state
236
277
  #
237
- # In addition to the universal commands, the following commands are valid in
238
- # the "<em>not authenticated</em>" state:
278
+ # In addition to the commands for any state, the following commands are valid
279
+ # in the +not_authenticated+ state:
239
280
  #
240
281
  # - #starttls: Upgrades a clear-text connection to use TLS.
241
282
  #
242
283
  # <em>Requires the +STARTTLS+ capability.</em>
243
- # - #authenticate: Identifies the client to the server using a {SASL
244
- # mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml].
245
- # Enters the "_authenticated_" state.
284
+ # - #authenticate: Identifies the client to the server using the given {SASL
285
+ # mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
286
+ # and credentials. Enters the +authenticated+ state.
246
287
  #
247
288
  # <em>Requires the <tt>AUTH=#{mechanism}</tt> capability for the chosen
248
289
  # mechanism.</em>
249
290
  # - #login: Identifies the client to the server using a plain text password.
250
- # Using #authenticate is generally preferred. Enters the "_authenticated_"
251
- # state.
291
+ # Using #authenticate is preferred. Enters the +authenticated+ state.
252
292
  #
253
293
  # <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
254
294
  #
255
295
  # ==== \IMAP commands for the "Authenticated" state
256
296
  #
257
- # In addition to the universal commands, the following commands are valid in
258
- # the "_authenticated_" state:
297
+ # In addition to the commands for any state, the following commands are valid
298
+ # in the +authenticated+ state:
259
299
  #
260
300
  #--
261
301
  # - #enable: <em>Not implemented by Net::IMAP, yet.</em>
262
302
  #
263
303
  # <em>Requires the +ENABLE+ capability.</em>
264
304
  #++
265
- # - #select: Open a mailbox and enter the "_selected_" state.
266
- # - #examine: Open a mailbox read-only, and enter the "_selected_" state.
305
+ # - #select: Open a mailbox and enter the +selected+ state.
306
+ # - #examine: Open a mailbox read-only, and enter the +selected+ state.
267
307
  # - #create: Creates a new mailbox.
268
308
  # - #delete: Permanently remove a mailbox.
269
309
  # - #rename: Change the name of a mailbox.
@@ -289,12 +329,12 @@ module Net
289
329
  #
290
330
  # ==== \IMAP commands for the "Selected" state
291
331
  #
292
- # In addition to the universal commands and the "authenticated" commands, the
293
- # following commands are valid in the "_selected_" state:
332
+ # In addition to the commands for any state and the +authenticated+
333
+ # commands, the following commands are valid in the +selected+ state:
294
334
  #
295
- # - #close: Closes the mailbox and returns to the "_authenticated_" state,
335
+ # - #close: Closes the mailbox and returns to the +authenticated+ state,
296
336
  # expunging deleted messages, unless the mailbox was opened as read-only.
297
- # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
337
+ # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
298
338
  # without expunging any messages.
299
339
  #
300
340
  # <em>Requires the +UNSELECT+ capability.</em>
@@ -384,7 +424,7 @@ module Net
384
424
  # ==== RFC3691: +UNSELECT+
385
425
  # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also
386
426
  # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
387
- # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
427
+ # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
388
428
  # without expunging any messages.
389
429
  #
390
430
  # ==== RFC4314: +ACL+
@@ -699,7 +739,9 @@ module Net
699
739
  # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
700
740
  #
701
741
  class IMAP < Protocol
702
- VERSION = "0.3.8"
742
+ VERSION = "0.3.10"
743
+
744
+ autoload :ResponseReader, File.expand_path("imap/response_reader", __dir__)
703
745
 
704
746
  include MonitorMixin
705
747
  if defined?(OpenSSL::SSL)
@@ -734,6 +776,40 @@ module Net
734
776
  # Seconds to wait until an IDLE response is received.
735
777
  attr_reader :idle_response_timeout
736
778
 
779
+ # The maximum allowed server response size. When +nil+, there is no limit
780
+ # on response size.
781
+ #
782
+ # The default value is _unlimited_ (after +v0.5.8+, the default is 512 MiB).
783
+ # A _much_ lower value should be used with untrusted servers (for example,
784
+ # when connecting to a user-provided hostname). When using a lower limit,
785
+ # message bodies should be fetched in chunks rather than all at once.
786
+ #
787
+ # <em>Please Note:</em> this only limits the size per response. It does
788
+ # not prevent a flood of individual responses and it does not limit how
789
+ # many unhandled responses may be stored on the responses hash. See
790
+ # Net::IMAP@Unbounded+memory+use.
791
+ #
792
+ # Socket reads are limited to the maximum remaining bytes for the current
793
+ # response: max_response_size minus the bytes that have already been read.
794
+ # When the limit is reached, or reading a +literal+ _would_ go over the
795
+ # limit, ResponseTooLargeError is raised and the connection is closed.
796
+ # See also #socket_read_limit.
797
+ #
798
+ # Note that changes will not take effect immediately, because the receiver
799
+ # thread may already be waiting for the next response using the previous
800
+ # value. Net::IMAP#noop can force a response and enforce the new setting
801
+ # immediately.
802
+ #
803
+ # ==== Versioned Defaults
804
+ #
805
+ # Net::IMAP#max_response_size <em>was added in +v0.2.5+ and +v0.3.9+ as an
806
+ # attr_accessor, and in +v0.4.20+ and +v0.5.7+ as a delegator to a config
807
+ # attribute.</em>
808
+ #
809
+ # * original: +nil+ <em>(no limit)</em>
810
+ # * +0.5+: 512 MiB
811
+ attr_accessor :max_response_size
812
+
737
813
  attr_accessor :client_thread # :nodoc:
738
814
 
739
815
  # Returns the debug mode.
@@ -938,8 +1014,11 @@ module Net
938
1014
  # unsolicited untagged response immeditely _after_ #starttls completes.
939
1015
  #
940
1016
  def starttls(options = {}, verify = true)
941
- send_command("STARTTLS") do |resp|
1017
+ handled = false
1018
+ error = nil
1019
+ ok = send_command("STARTTLS") do |resp|
942
1020
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
1021
+ handled = true
943
1022
  begin
944
1023
  # for backward compatibility
945
1024
  certs = options.to_str
@@ -948,7 +1027,21 @@ module Net
948
1027
  end
949
1028
  start_tls_session(options)
950
1029
  end
1030
+ rescue Exception => error
1031
+ raise # note that the error backtrace is in the receiver_thread
1032
+ end
1033
+ if error
1034
+ disconnect
1035
+ raise error
951
1036
  end
1037
+ unless handled
1038
+ disconnect
1039
+ raise InvalidResponseError,
1040
+ "STARTTLS handler was bypassed, although server responded %p" % [
1041
+ ok.raw_data.chomp
1042
+ ]
1043
+ end
1044
+ ok
952
1045
  end
953
1046
 
954
1047
  # :call-seq:
@@ -1960,6 +2053,11 @@ module Net
1960
2053
  # end
1961
2054
  # }
1962
2055
  #
2056
+ # Response handlers can also be added when the client is created before the
2057
+ # receiver thread is started, by the +response_handlers+ argument to ::new.
2058
+ # This ensures every server response is handled, including the #greeting.
2059
+ #
2060
+ # Related: #remove_response_handler, #response_handlers
1963
2061
  def add_response_handler(handler = nil, &block)
1964
2062
  raise ArgumentError, "two Procs are passed" if handler && block
1965
2063
  @response_handlers.push(block || handler)
@@ -1995,6 +2093,13 @@ module Net
1995
2093
  # OpenSSL::SSL::SSLContext#set_params as parameters.
1996
2094
  # open_timeout:: Seconds to wait until a connection is opened
1997
2095
  # idle_response_timeout:: Seconds to wait until an IDLE response is received
2096
+ # response_handlers:: A list of response handlers to be added before the
2097
+ # receiver thread is started. This ensures every server
2098
+ # response is handled, including the #greeting. Note
2099
+ # that the greeting is handled in the current thread,
2100
+ # but all other responses are handled in the receiver
2101
+ # thread.
2102
+ # max_response_size:: See #max_response_size.
1998
2103
  #
1999
2104
  # The most common errors are:
2000
2105
  #
@@ -2025,8 +2130,10 @@ module Net
2025
2130
  @tagno = 0
2026
2131
  @open_timeout = options[:open_timeout] || 30
2027
2132
  @idle_response_timeout = options[:idle_response_timeout] || 5
2133
+ @max_response_size = options[:max_response_size]
2028
2134
  @parser = ResponseParser.new
2029
2135
  @sock = tcp_socket(@host, @port)
2136
+ @reader = ResponseReader.new(self, @sock)
2030
2137
  begin
2031
2138
  if options[:ssl]
2032
2139
  start_tls_session(options[:ssl])
@@ -2037,6 +2144,7 @@ module Net
2037
2144
  @responses = Hash.new([].freeze)
2038
2145
  @tagged_responses = {}
2039
2146
  @response_handlers = []
2147
+ options[:response_handlers]&.each do |h| add_response_handler(h) end
2040
2148
  @tagged_response_arrival = new_cond
2041
2149
  @continued_command_tag = nil
2042
2150
  @continuation_request_arrival = new_cond
@@ -2053,6 +2161,7 @@ module Net
2053
2161
  if @greeting.name == "BYE"
2054
2162
  raise ByeResponseError, @greeting
2055
2163
  end
2164
+ @response_handlers.each do |handler| handler.call(@greeting) end
2056
2165
 
2057
2166
  @client_thread = Thread.current
2058
2167
  @receiver_thread = Thread.start {
@@ -2176,25 +2285,14 @@ module Net
2176
2285
  end
2177
2286
 
2178
2287
  def get_response
2179
- buff = String.new
2180
- while true
2181
- s = @sock.gets(CRLF)
2182
- break unless s
2183
- buff.concat(s)
2184
- if /\{(\d+)\}\r\n/n =~ s
2185
- s = @sock.read($1.to_i)
2186
- buff.concat(s)
2187
- else
2188
- break
2189
- end
2190
- end
2288
+ buff = @reader.read_response_buffer
2191
2289
  return nil if buff.length == 0
2192
- if @@debug
2193
- $stderr.print(buff.gsub(/^/n, "S: "))
2194
- end
2195
- return @parser.parse(buff)
2290
+ $stderr.print(buff.gsub(/^/n, "S: ")) if @@debug
2291
+ @parser.parse(buff)
2196
2292
  end
2197
2293
 
2294
+ #############################
2295
+
2198
2296
  def record_response(name, data)
2199
2297
  unless @responses.has_key?(name)
2200
2298
  @responses[name] = []
@@ -2213,6 +2311,7 @@ module Net
2213
2311
  put_string(" ")
2214
2312
  send_data(i, tag)
2215
2313
  end
2314
+ guard_against_tagged_response_skipping_handler!(tag)
2216
2315
  put_string(CRLF)
2217
2316
  if cmd == "LOGOUT"
2218
2317
  @logout_command_tag = tag
@@ -2228,6 +2327,17 @@ module Net
2228
2327
  end
2229
2328
  end
2230
2329
  end
2330
+ rescue InvalidResponseError
2331
+ disconnect
2332
+ raise
2333
+ end
2334
+
2335
+ def guard_against_tagged_response_skipping_handler!(tag)
2336
+ return unless (resp = @tagged_responses[tag])&.name&.upcase == "OK"
2337
+ raise(InvalidResponseError,
2338
+ "Server sent tagged 'OK' before command was finished: %p. " \
2339
+ "This could indicate a malicious server or client-side " \
2340
+ "command injection. Disconnecting." % [resp.raw_data.chomp])
2231
2341
  end
2232
2342
 
2233
2343
  def generate_tag
@@ -2372,6 +2482,7 @@ module Net
2372
2482
  context.verify_callback = VerifyCallbackProc
2373
2483
  end
2374
2484
  @sock = SSLSocket.new(@sock, context)
2485
+ @reader = ResponseReader.new(self, @sock)
2375
2486
  @sock.sync_close = true
2376
2487
  @sock.hostname = @host if @sock.respond_to? :hostname=
2377
2488
  ssl_socket_connect(@sock, @open_timeout)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-imap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.8
4
+ version: 0.3.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
8
8
  - nicholas a. evans
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-07 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-protocol
@@ -97,6 +97,7 @@ files:
97
97
  - lib/net/imap/flags.rb
98
98
  - lib/net/imap/response_data.rb
99
99
  - lib/net/imap/response_parser.rb
100
+ - lib/net/imap/response_reader.rb
100
101
  - lib/net/imap/sasl.rb
101
102
  - lib/net/imap/sasl/saslprep.rb
102
103
  - lib/net/imap/sasl/saslprep_tables.rb
@@ -128,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
129
  - !ruby/object:Gem::Version
129
130
  version: '0'
130
131
  requirements: []
131
- rubygems_version: 3.6.2
132
+ rubygems_version: 4.0.3
132
133
  specification_version: 4
133
134
  summary: Ruby client api for Internet Message Access Protocol
134
135
  test_files: []