net-imap 0.4.7 → 0.4.8

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.

Potentially problematic release.


This version of net-imap might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac68288133676e52f1e07e714debeb5b5f07f79f1990efe3f1342f0a643a4df1
4
- data.tar.gz: 2588adb3518535fc451a06396133cbcf948d097dc26366486f698296a16bc0a6
3
+ metadata.gz: b90ea893e6f831943d2d0412ff068c7a6603ce3bf723900ea7401a9e045f724b
4
+ data.tar.gz: '087c861278ce56a7369780aa27d77e56ed6d94907a4116a3a4fd9707f327d57a'
5
5
  SHA512:
6
- metadata.gz: 31726eb30d3e24ce39a6cc5ad6ea5fbf13f9fd43f0f621d32b9e9f50b0fbcee5b240d163b384efa03df19257c9772f333e02c96a11e7f40b5a01899879838b6d
7
- data.tar.gz: 83ba9341159c2916f673f378b449d5743daa3f68ec7490c9e03b6b619dd1fe455a8a5fe50dc8afc4e138b66c941e696d345dbf88058ffbe99e4b2ae3f44f4d74
6
+ metadata.gz: 5fa157a8887d5ca2b97b75756b5414d5973667299ae3dbdb340b432eae05cfdb2994969ea2dab68b595f2eae66bd50d19fb814196fb991af1bfc285dfa5b3d6f
7
+ data.tar.gz: 7b173943ff66f97b5b22795752a34f7338228e6ef5a4dca43b293f5e3153f90d34b7039fd24c76399b1f460e77ffe422dd3691466b0952e3631f2406bab70854
@@ -27,7 +27,7 @@ jobs:
27
27
  bundler-cache: true
28
28
  - name: Setup Pages
29
29
  id: pages
30
- uses: actions/configure-pages@v3
30
+ uses: actions/configure-pages@v4
31
31
  - name: Build with RDoc
32
32
  run: bundle exec rake rdoc
33
33
  - name: Upload artifact
@@ -43,4 +43,4 @@ jobs:
43
43
  steps:
44
44
  - name: Deploy to GitHub Pages
45
45
  id: deployment
46
- uses: actions/deploy-pages@v2
46
+ uses: actions/deploy-pages@v3
@@ -3,6 +3,7 @@
3
3
  module Net
4
4
  class IMAP < Protocol
5
5
  autoload :FetchData, "#{__dir__}/fetch_data"
6
+ autoload :SearchResult, "#{__dir__}/search_result"
6
7
  autoload :SequenceSet, "#{__dir__}/sequence_set"
7
8
 
8
9
  # Net::IMAP::ContinuationRequest represents command continuation requests.
@@ -205,6 +206,7 @@ module Net
205
206
  # defines them. When unknown response code data is encountered, #data
206
207
  # will return an unparsed string.
207
208
  #
209
+ # ==== +IMAP4rev1+ Response Codes
208
210
  # See [IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501]] {§7.1, "Server
209
211
  # Responses - Status
210
212
  # Responses"}[https://www.rfc-editor.org/rfc/rfc3501#section-7.1] for full
@@ -228,13 +230,32 @@ module Net
228
230
  # {§2.3.1.1, "Unique Identifier (UID) Message
229
231
  # Attribute}[https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1].
230
232
  # * +UIDVALIDITY+, #data is an Integer, the UID validity value of the
231
- # mailbox See [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501]],
233
+ # mailbox. See [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501]],
232
234
  # {§2.3.1.1, "Unique Identifier (UID) Message
233
235
  # Attribute}[https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1].
234
236
  # * +UNSEEN+, #data is an Integer, the number of messages which do not have
235
237
  # the <tt>\Seen</tt> flag set.
236
- #
237
- # See RFC5530[https://www.rfc-editor.org/rfc/rfc5530], "IMAP Response
238
+ # <em>DEPRECATED by IMAP4rev2.</em>
239
+ #
240
+ # ==== +BINARY+ extension
241
+ # See {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516].
242
+ # * +UNKNOWN-CTE+, with a tagged +NO+ response, when the server does not
243
+ # known how to decode a CTE (content-transfer-encoding). #data is +nil+.
244
+ # See IMAP#fetch.
245
+ #
246
+ # ==== +UIDPLUS+ extension
247
+ # See {[RFC4315 §3]}[https://www.rfc-editor.org/rfc/rfc4315#section-3].
248
+ # * +APPENDUID+, #data is UIDPlusData. See IMAP#append.
249
+ # * +COPYUID+, #data is UIDPlusData. See IMAP#copy.
250
+ # * +UIDNOTSTICKY+, #data is +nil+. See IMAP#select.
251
+ #
252
+ # ==== +SEARCHRES+ extension
253
+ # See {[RFC5182]}[https://www.rfc-editor.org/rfc/rfc5182].
254
+ # * +NOTSAVED+, with a tagged +NO+ response, when the search result variable
255
+ # is not saved. #data is +nil+.
256
+ #
257
+ # ==== +RFC5530+ Response Codes
258
+ # See {[RFC5530]}[https://www.rfc-editor.org/rfc/rfc5530], "IMAP Response
238
259
  # Codes" for the definition of the following response codes, which are all
239
260
  # machine-readable annotations for the human-readable ResponseText#text, and
240
261
  # have +nil+ #data of their own:
@@ -256,9 +277,34 @@ module Net
256
277
  # * +ALREADYEXISTS+
257
278
  # * +NONEXISTENT+
258
279
  #
259
- # Other supported \IMAP extension response codes:
260
- # * +OBJECTID+ {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html#section-7]
261
- # * +MAILBOXID+, #data will be a string
280
+ # ==== +QRESYNC+ extension
281
+ # See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
282
+ # * +CLOSED+, returned when the currently selected mailbox is closed
283
+ # implicity by selecting or examining another mailbox. #data is +nil+.
284
+ #
285
+ # ==== +IMAP4rev2+ Response Codes
286
+ # See {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051] {§7.1, "Server
287
+ # Responses - Status
288
+ # Responses"}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1] for full
289
+ # descriptions of IMAP4rev2 response codes. IMAP4rev2 includes all of the
290
+ # response codes listed above (except "UNSEEN") and adds the following:
291
+ # * +HASCHILDREN+, with a tagged +NO+ response, when a mailbox delete failed
292
+ # because the server doesn't allow deletion of mailboxes with children.
293
+ # #data is +nil+.
294
+ #
295
+ # ==== +CONDSTORE+ extension
296
+ # See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
297
+ # * +NOMODSEQ+, when selecting a mailbox that does not support
298
+ # mod-sequences. #data is +nil+. See IMAP#select.
299
+ # * +HIGHESTMODSEQ+, #data is an Integer, the highest mod-sequence value of
300
+ # all messages in the mailbox. See IMAP#select.
301
+ # * +MODIFIED+, #data is a SequenceSet, the messages that have been modified
302
+ # since the +UNCHANGEDSINCE+ mod-sequence given to +STORE+ or <tt>UID
303
+ # STORE</tt>.
304
+ #
305
+ # ==== +OBJECTID+ extension
306
+ # See {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
307
+ # * +MAILBOXID+, #data is a string
262
308
  #
263
309
  class ResponseCode < Struct.new(:name, :data)
264
310
  ##
@@ -728,6 +774,19 @@ module Net
728
774
  #
729
775
  # An array of Net::IMAP::ThreadMember objects for mail items that are
730
776
  # children of this in the thread.
777
+
778
+ # Returns a SequenceSet containing #seqno and all #children's seqno,
779
+ # recursively.
780
+ def to_sequence_set
781
+ SequenceSet.new all_seqnos
782
+ end
783
+
784
+ protected
785
+
786
+ def all_seqnos(node = self)
787
+ [node.seqno].concat node.children.flat_map { _1.all_seqnos }
788
+ end
789
+
731
790
  end
732
791
 
733
792
  # Net::IMAP::BodyStructure is included by all of the structs that can be
@@ -231,6 +231,10 @@ module Net
231
231
  FLAG_PERM_LIST = /\G\((#{FLAG_PERM}(?:#{SP}#{FLAG_PERM})*|)\)/ni
232
232
  MBX_LIST_FLAGS = /\G (#{MBX_FLAG }(?:#{SP}#{MBX_FLAG })*) /nix
233
233
 
234
+ # Gmail allows SP and "]" in flags.......
235
+ QUIRKY_FLAG = Regexp.union(/\\?#{ASTRING_CHARS}/n, "\\*")
236
+ QUIRKY_FLAGS_LIST = /\G\(( [^)]* )\)/nx
237
+
234
238
  # RFC3501:
235
239
  # QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
236
240
  # "\" quoted-specials
@@ -464,7 +468,7 @@ module Net
464
468
  def sequence_set
465
469
  str = combine_adjacent(*SEQUENCE_SET_TOKENS)
466
470
  if Patterns::SEQUENCE_SET_STR.match?(str)
467
- SequenceSet.new(str)
471
+ SequenceSet[str]
468
472
  else
469
473
  parse_error("unexpected atom %p, expected sequence-set", str)
470
474
  end
@@ -1366,7 +1370,7 @@ module Net
1366
1370
  MailboxList.new(attr, delim, name)
1367
1371
  end
1368
1372
 
1369
- def getquota_response
1373
+ def quota_response
1370
1374
  # If quota never established, get back
1371
1375
  # `NO Quota root does not exist'.
1372
1376
  # If quota removed, get `()' after the
@@ -1399,7 +1403,7 @@ module Net
1399
1403
  end
1400
1404
  end
1401
1405
 
1402
- def getquotaroot_response
1406
+ def quotaroot_response
1403
1407
  # Similar to getquota, but only admin can use getquota.
1404
1408
  token = match(T_ATOM)
1405
1409
  name = token.value.upcase
@@ -1463,9 +1467,10 @@ module Net
1463
1467
  while _ = SP? && nz_number? do data << _ end
1464
1468
  if lpar?
1465
1469
  label("MODSEQ"); SP!
1466
- mod_sequence_value
1470
+ modseq = mod_sequence_value
1467
1471
  rpar
1468
1472
  end
1473
+ data = SearchResult.new(data, modseq: modseq)
1469
1474
  UntaggedResponse.new(name, data, @str)
1470
1475
  end
1471
1476
  alias sort_data mailbox_data__search
@@ -1590,6 +1595,7 @@ module Net
1590
1595
  when "UIDVALIDITY" then nz_number # RFC3501, RFC9051
1591
1596
  when "RECENT" then number # RFC3501 (obsolete)
1592
1597
  when "SIZE" then number64 # RFC8483, RFC9051
1598
+ when "HIGHESTMODSEQ" then mod_sequence_valzer # RFC7162
1593
1599
  when "MAILBOXID" then parens__objectid # RFC8474
1594
1600
  else
1595
1601
  number? || ExtensionData.new(tagged_ext_val)
@@ -1796,6 +1802,8 @@ module Net
1796
1802
  # resp-text-code =/ "HIGHESTMODSEQ" SP mod-sequence-value /
1797
1803
  # "NOMODSEQ" /
1798
1804
  # "MODIFIED" SP sequence-set
1805
+ # RFC7162 (QRESYNC):
1806
+ # resp-text-code =/ "CLOSED"
1799
1807
  #
1800
1808
  # RFC8474: OBJECTID
1801
1809
  # resp-text-code =/ "MAILBOXID" SP "(" objectid ")"
@@ -1817,7 +1825,9 @@ module Net
1817
1825
  "EXPUNGEISSUED", "CORRUPTION", "SERVERBUG", "CLIENTBUG", "CANNOT",
1818
1826
  "LIMIT", "OVERQUOTA", "ALREADYEXISTS", "NONEXISTENT", "CLOSED",
1819
1827
  "NOTSAVED", "UIDNOTSTICKY", "UNKNOWN-CTE", "HASCHILDREN"
1820
- when "NOMODSEQ" # CONDSTORE
1828
+ when "NOMODSEQ" then nil # CONDSTORE
1829
+ when "HIGHESTMODSEQ" then SP!; mod_sequence_value # CONDSTORE
1830
+ when "MODIFIED" then SP!; sequence_set # CONDSTORE
1821
1831
  when "MAILBOXID" then SP!; parens__objectid # RFC8474: OBJECTID
1822
1832
  else
1823
1833
  SP? and text_chars_except_rbra
@@ -1901,22 +1911,35 @@ module Net
1901
1911
 
1902
1912
  # flag-list = "(" [flag *(SP flag)] ")"
1903
1913
  def flag_list
1904
- match_re(Patterns::FLAG_LIST, "flag-list")[1]
1905
- .split(nil)
1906
- .map! { _1.start_with?("\\") ? _1[1..].capitalize.to_sym : _1 }
1914
+ if (match = accept_re(Patterns::FLAG_LIST))
1915
+ match[1].split(nil)
1916
+ .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 }
1917
+ else
1918
+ quirky__flag_list "flags-list"
1919
+ end
1907
1920
  end
1908
1921
 
1909
1922
  # "(" [flag-perm *(SP flag-perm)] ")"
1910
1923
  def flag_perm__list
1911
- match_re(Patterns::FLAG_PERM_LIST, "PERMANENTFLAGS flag-perm list")[1]
1912
- .split(nil)
1913
- .map! { _1.start_with?("\\") ? _1[1..].capitalize.to_sym : _1 }
1924
+ if (match = accept_re(Patterns::FLAG_PERM_LIST))
1925
+ match[1].split(nil)
1926
+ .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 }
1927
+ else
1928
+ quirky__flag_list "PERMANENTFLAGS flag-perm list"
1929
+ end
1930
+ end
1931
+
1932
+ def quirky__flag_list(name)
1933
+ match_re(Patterns::QUIRKY_FLAGS_LIST, "quirks mode #{name}")[1]
1934
+ .scan(Patterns::QUIRKY_FLAG)
1935
+ .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 }
1914
1936
  end
1915
1937
 
1916
1938
  # See Patterns::MBX_LIST_FLAGS
1917
1939
  def mbx_list_flags
1918
1940
  match_re(Patterns::MBX_LIST_FLAGS, "mbx-list-flags")[1]
1919
- .split(nil).map! { _1[1..].capitalize.to_sym }
1941
+ .split(nil)
1942
+ .map! { _1.delete_prefix!("\\"); _1.capitalize.to_sym }
1920
1943
  end
1921
1944
 
1922
1945
  # See https://developers.google.com/gmail/imap/imap-extensions
@@ -1949,6 +1972,10 @@ module Net
1949
1972
  # ;; Per-message mod-sequence.
1950
1973
  alias permsg_modsequence mod_sequence_value
1951
1974
 
1975
+ # RFC7162:
1976
+ # mod-sequence-valzer = "0" / mod-sequence-value
1977
+ alias mod_sequence_valzer number64
1978
+
1952
1979
  def parens__modseq; lpar; _ = permsg_modsequence; rpar; _ end
1953
1980
 
1954
1981
  # RFC8474:
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+
6
+ # An array of sequence numbers returned by Net::IMAP#search, or unique
7
+ # identifiers returned by Net::IMAP#uid_search.
8
+ #
9
+ # For backward compatibility, SearchResult inherits from Array.
10
+ class SearchResult < Array
11
+
12
+ # Returns a frozen SearchResult populated with the given +seq_nums+.
13
+ #
14
+ # Net::IMAP::SearchResult[1, 3, 5, modseq: 9]
15
+ # # => Net::IMAP::SearchResult[1, 3, 5, modseq: 9]
16
+ def self.[](*seq_nums, modseq: nil)
17
+ new(seq_nums, modseq: modseq)
18
+ end
19
+
20
+ # A modification sequence number, as described by the +CONDSTORE+
21
+ # extension in {[RFC7162
22
+ # §3.1.6]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.6].
23
+ attr_reader :modseq
24
+
25
+ # Returns a frozen SearchResult populated with the given +seq_nums+.
26
+ #
27
+ # Net::IMAP::SearchResult.new([1, 3, 5], modseq: 9)
28
+ # # => Net::IMAP::SearchResult[1, 3, 5, modseq: 9]
29
+ def initialize(seq_nums, modseq: nil)
30
+ super(seq_nums.to_ary.map { Integer _1 })
31
+ @modseq = Integer modseq if modseq
32
+ freeze
33
+ end
34
+
35
+ # Returns a frozen copy of +other+.
36
+ def initialize_copy(other); super; freeze end
37
+
38
+ # Returns whether +other+ is a SearchResult with the same values and the
39
+ # same #modseq. The order of numbers is irrelevant.
40
+ #
41
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
42
+ # Net::IMAP::SearchResult[123, 456, modseq: 789]
43
+ # # => true
44
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
45
+ # Net::IMAP::SearchResult[456, 123, modseq: 789]
46
+ # # => true
47
+ #
48
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
49
+ # Net::IMAP::SearchResult[987, 654, modseq: 789]
50
+ # # => false
51
+ # Net::IMAP::SearchResult[123, 456, modseq: 789] ==
52
+ # Net::IMAP::SearchResult[1, 2, 3, modseq: 9999]
53
+ # # => false
54
+ #
55
+ # SearchResult can be compared directly with Array, if #modseq is nil and
56
+ # the array is sorted.
57
+ #
58
+ # Net::IMAP::SearchResult[9, 8, 6, 4, 1] == [1, 4, 6, 8, 9] # => true
59
+ # Net::IMAP::SearchResult[3, 5, 7, modseq: 99] == [3, 5, 7] # => false
60
+ #
61
+ # Note that Array#== does require matching order and ignores #modseq.
62
+ #
63
+ # [9, 8, 6, 4, 1] == Net::IMAP::SearchResult[1, 4, 6, 8, 9] # => false
64
+ # [3, 5, 7] == Net::IMAP::SearchResult[3, 5, 7, modseq: 99] # => true
65
+ #
66
+ def ==(other)
67
+ (modseq ?
68
+ other.is_a?(self.class) && modseq == other.modseq :
69
+ other.is_a?(Array)) &&
70
+ size == other.size &&
71
+ sort == other.sort
72
+ end
73
+
74
+ # Hash equality. Unlike #==, order will be taken into account.
75
+ def hash
76
+ return super if modseq.nil?
77
+ [super, self.class, modseq].hash
78
+ end
79
+
80
+ # Hash equality. Unlike #==, order will be taken into account.
81
+ def eql?(other)
82
+ return super if modseq.nil?
83
+ self.class == other.class && hash == other.hash
84
+ end
85
+
86
+ # Returns a string that represents the SearchResult.
87
+ #
88
+ # Net::IMAP::SearchResult[123, 456, 789].inspect
89
+ # # => "[123, 456, 789]"
90
+ #
91
+ # Net::IMAP::SearchResult[543, 210, 678, modseq: 2048].inspect
92
+ # # => "Net::IMAP::SearchResult[543, 210, 678, modseq: 2048]"
93
+ #
94
+ def inspect
95
+ return super if modseq.nil?
96
+ "%s[%s, modseq: %p]" % [self.class, join(", "), modseq]
97
+ end
98
+
99
+ # Returns a string that follows the formal \IMAP syntax.
100
+ #
101
+ # data = Net::IMAP::SearchResult[2, 8, 32, 128, 256, 512]
102
+ # data.to_s # => "* SEARCH 2 8 32 128 256 512"
103
+ # data.to_s("SEARCH") # => "* SEARCH 2 8 32 128 256 512"
104
+ # data.to_s("SORT") # => "* SORT 2 8 32 128 256 512"
105
+ # data.to_s(nil) # => "2 8 32 128 256 512"
106
+ #
107
+ # data = Net::IMAP::SearchResult[1, 3, 16, 1024, modseq: 2048].to_s
108
+ # data.to_s # => "* SEARCH 1 3 16 1024 (MODSEQ 2048)"
109
+ # data.to_s("SORT") # => "* SORT 1 3 16 1024 (MODSEQ 2048)"
110
+ # data.to_s # => "1 3 16 1024 (MODSEQ 2048)"
111
+ #
112
+ def to_s(type = "SEARCH")
113
+ str = +""
114
+ str << "* %s " % [type.to_str] unless type.nil?
115
+ str << join(" ")
116
+ str << " (MODSEQ %d)" % [modseq] if modseq
117
+ -str
118
+ end
119
+
120
+ # Converts the SearchResult into a SequenceSet.
121
+ #
122
+ # Net::IMAP::SearchResult[9, 1, 2, 4, 10, 12, 3, modseq: 123_456]
123
+ # .to_sequence_set
124
+ # # => Net::IMAP::SequenceSet["1:4,9:10,12"]
125
+ def to_sequence_set; SequenceSet[*self] end
126
+
127
+ def pretty_print(pp)
128
+ return super if modseq.nil?
129
+ pp.text self.class.name + "["
130
+ pp.group_sub do
131
+ pp.nest(2) do
132
+ pp.breakable ""
133
+ each do |num|
134
+ pp.pp num
135
+ pp.text ","
136
+ pp.fill_breakable
137
+ end
138
+ pp.breakable ""
139
+ pp.text "modseq: "
140
+ pp.pp modseq
141
+ end
142
+ pp.breakable ""
143
+ pp.text "]"
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ end
150
+ end