net-imap 0.1.1 → 0.2.0
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 +4 -4
- data/.github/workflows/test.yml +6 -0
- data/lib/net/imap.rb +456 -68
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a804edc85533bfda64ba246d9c90163e9b3ae7de6dcd55cfb22e9788ffc0674b
|
|
4
|
+
data.tar.gz: 2c5481cc5def65f616575f484a56024805023cf223e8e184e39b0049ab16d15c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4292263bd4719686c854b85677e639b5ca6092ad3b44de29384cda759cfe754934d1a8f1ef04a5624b3f726ea9e875ed4c9991247a0d43eb80930c38f9978033
|
|
7
|
+
data.tar.gz: 2a8a211591e2da4b295b14564d7166664fdc695c3823bcff86b80ca3b45619a493482a5a5d1ee8bf621c4a7a89fc877d6a7717821412e8053ba613e6202dd2a6
|
data/.github/workflows/test.yml
CHANGED
|
@@ -9,7 +9,13 @@ jobs:
|
|
|
9
9
|
matrix:
|
|
10
10
|
ruby: [ 2.7, 2.6, 2.5, head ]
|
|
11
11
|
os: [ ubuntu-latest, macos-latest ]
|
|
12
|
+
experimental: [false]
|
|
13
|
+
include:
|
|
14
|
+
- ruby: 2.6
|
|
15
|
+
os: ubuntu-latest
|
|
16
|
+
experimental: true
|
|
12
17
|
runs-on: ${{ matrix.os }}
|
|
18
|
+
continue-on-error: ${{ matrix.experimental }}
|
|
13
19
|
steps:
|
|
14
20
|
- uses: actions/checkout@master
|
|
15
21
|
- name: Set up Ruby
|
data/lib/net/imap.rb
CHANGED
|
@@ -201,7 +201,7 @@ module Net
|
|
|
201
201
|
# Unicode", RFC 2152, May 1997.
|
|
202
202
|
#
|
|
203
203
|
class IMAP < Protocol
|
|
204
|
-
VERSION = "0.
|
|
204
|
+
VERSION = "0.2.0"
|
|
205
205
|
|
|
206
206
|
include MonitorMixin
|
|
207
207
|
if defined?(OpenSSL::SSL)
|
|
@@ -304,6 +304,16 @@ module Net
|
|
|
304
304
|
@@authenticators[auth_type] = authenticator
|
|
305
305
|
end
|
|
306
306
|
|
|
307
|
+
# Builds an authenticator for Net::IMAP#authenticate.
|
|
308
|
+
def self.authenticator(auth_type, *args)
|
|
309
|
+
auth_type = auth_type.upcase
|
|
310
|
+
unless @@authenticators.has_key?(auth_type)
|
|
311
|
+
raise ArgumentError,
|
|
312
|
+
format('unknown auth type - "%s"', auth_type)
|
|
313
|
+
end
|
|
314
|
+
@@authenticators[auth_type].new(*args)
|
|
315
|
+
end
|
|
316
|
+
|
|
307
317
|
# The default port for IMAP connections, port 143
|
|
308
318
|
def self.default_port
|
|
309
319
|
return PORT
|
|
@@ -365,6 +375,30 @@ module Net
|
|
|
365
375
|
end
|
|
366
376
|
end
|
|
367
377
|
|
|
378
|
+
# Sends an ID command, and returns a hash of the server's
|
|
379
|
+
# response, or nil if the server does not identify itself.
|
|
380
|
+
#
|
|
381
|
+
# Note that the user should first check if the server supports the ID
|
|
382
|
+
# capability. For example:
|
|
383
|
+
#
|
|
384
|
+
# capabilities = imap.capability
|
|
385
|
+
# if capabilities.include?("ID")
|
|
386
|
+
# id = imap.id(
|
|
387
|
+
# name: "my IMAP client (ruby)",
|
|
388
|
+
# version: MyIMAP::VERSION,
|
|
389
|
+
# "support-url": "mailto:bugs@example.com",
|
|
390
|
+
# os: RbConfig::CONFIG["host_os"],
|
|
391
|
+
# )
|
|
392
|
+
# end
|
|
393
|
+
#
|
|
394
|
+
# See RFC 2971, Section 3.3, for defined fields.
|
|
395
|
+
def id(client_id=nil)
|
|
396
|
+
synchronize do
|
|
397
|
+
send_command("ID", ClientID.new(client_id))
|
|
398
|
+
@responses.delete("ID")&.last
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
368
402
|
# Sends a NOOP command to the server. It does nothing.
|
|
369
403
|
def noop
|
|
370
404
|
send_command("NOOP")
|
|
@@ -408,7 +442,7 @@ module Net
|
|
|
408
442
|
# the form "AUTH=LOGIN" or "AUTH=CRAM-MD5".
|
|
409
443
|
#
|
|
410
444
|
# Authentication is done using the appropriate authenticator object:
|
|
411
|
-
# see
|
|
445
|
+
# see +add_authenticator+ for more information on plugging in your own
|
|
412
446
|
# authenticator.
|
|
413
447
|
#
|
|
414
448
|
# For example:
|
|
@@ -417,12 +451,7 @@ module Net
|
|
|
417
451
|
#
|
|
418
452
|
# A Net::IMAP::NoResponseError is raised if authentication fails.
|
|
419
453
|
def authenticate(auth_type, *args)
|
|
420
|
-
|
|
421
|
-
unless @@authenticators.has_key?(auth_type)
|
|
422
|
-
raise ArgumentError,
|
|
423
|
-
format('unknown auth type - "%s"', auth_type)
|
|
424
|
-
end
|
|
425
|
-
authenticator = @@authenticators[auth_type].new(*args)
|
|
454
|
+
authenticator = self.class.authenticator(auth_type, *args)
|
|
426
455
|
send_command("AUTHENTICATE", auth_type) do |resp|
|
|
427
456
|
if resp.instance_of?(ContinuationRequest)
|
|
428
457
|
data = authenticator.process(resp.data.text.unpack("m")[0])
|
|
@@ -552,6 +581,60 @@ module Net
|
|
|
552
581
|
end
|
|
553
582
|
end
|
|
554
583
|
|
|
584
|
+
# Sends a NAMESPACE command [RFC2342] and returns the namespaces that are
|
|
585
|
+
# available. The NAMESPACE command allows a client to discover the prefixes
|
|
586
|
+
# of namespaces used by a server for personal mailboxes, other users'
|
|
587
|
+
# mailboxes, and shared mailboxes.
|
|
588
|
+
#
|
|
589
|
+
# This extension predates IMAP4rev1 (RFC3501), so most IMAP servers support
|
|
590
|
+
# it. Many popular IMAP servers are configured with the default personal
|
|
591
|
+
# namespaces as `("" "/")`: no prefix and "/" hierarchy delimiter. In that
|
|
592
|
+
# common case, the naive client may not have any trouble naming mailboxes.
|
|
593
|
+
#
|
|
594
|
+
# But many servers are configured with the default personal namespace as
|
|
595
|
+
# e.g. `("INBOX." ".")`, placing all personal folders under INBOX, with "."
|
|
596
|
+
# as the hierarchy delimiter. If the client does not check for this, but
|
|
597
|
+
# naively assumes it can use the same folder names for all servers, then
|
|
598
|
+
# folder creation (and listing, moving, etc) can lead to errors.
|
|
599
|
+
#
|
|
600
|
+
# From RFC2342:
|
|
601
|
+
#
|
|
602
|
+
# Although typically a server will support only a single Personal
|
|
603
|
+
# Namespace, and a single Other User's Namespace, circumstances exist
|
|
604
|
+
# where there MAY be multiples of these, and a client MUST be prepared
|
|
605
|
+
# for them. If a client is configured such that it is required to create
|
|
606
|
+
# a certain mailbox, there can be circumstances where it is unclear which
|
|
607
|
+
# Personal Namespaces it should create the mailbox in. In these
|
|
608
|
+
# situations a client SHOULD let the user select which namespaces to
|
|
609
|
+
# create the mailbox in.
|
|
610
|
+
#
|
|
611
|
+
# The user of this method should first check if the server supports the
|
|
612
|
+
# NAMESPACE capability. The return value is a +Net::IMAP::Namespaces+
|
|
613
|
+
# object which has +personal+, +other+, and +shared+ fields, each an array
|
|
614
|
+
# of +Net::IMAP::Namespace+ objects. These arrays will be empty when the
|
|
615
|
+
# server responds with nil.
|
|
616
|
+
#
|
|
617
|
+
# For example:
|
|
618
|
+
#
|
|
619
|
+
# capabilities = imap.capability
|
|
620
|
+
# if capabilities.include?("NAMESPACE")
|
|
621
|
+
# namespaces = imap.namespace
|
|
622
|
+
# if namespace = namespaces.personal.first
|
|
623
|
+
# prefix = namespace.prefix # e.g. "" or "INBOX."
|
|
624
|
+
# delim = namespace.delim # e.g. "/" or "."
|
|
625
|
+
# # personal folders should use the prefix and delimiter
|
|
626
|
+
# imap.create(prefix + "foo")
|
|
627
|
+
# imap.create(prefix + "bar")
|
|
628
|
+
# imap.create(prefix + %w[path to my folder].join(delim))
|
|
629
|
+
# end
|
|
630
|
+
# end
|
|
631
|
+
def namespace
|
|
632
|
+
synchronize do
|
|
633
|
+
send_command("NAMESPACE")
|
|
634
|
+
return @responses.delete("NAMESPACE")[-1]
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
|
|
555
638
|
# Sends a XLIST command, and returns a subset of names from
|
|
556
639
|
# the complete set of all names available to the client.
|
|
557
640
|
# +refname+ provides a context (for instance, a base directory
|
|
@@ -1656,6 +1739,74 @@ module Net
|
|
|
1656
1739
|
end
|
|
1657
1740
|
end
|
|
1658
1741
|
|
|
1742
|
+
class ClientID # :nodoc:
|
|
1743
|
+
|
|
1744
|
+
def send_data(imap, tag)
|
|
1745
|
+
imap.__send__(:send_data, format_internal(@data), tag)
|
|
1746
|
+
end
|
|
1747
|
+
|
|
1748
|
+
def validate
|
|
1749
|
+
validate_internal(@data)
|
|
1750
|
+
end
|
|
1751
|
+
|
|
1752
|
+
private
|
|
1753
|
+
|
|
1754
|
+
def initialize(data)
|
|
1755
|
+
@data = data
|
|
1756
|
+
end
|
|
1757
|
+
|
|
1758
|
+
def validate_internal(client_id)
|
|
1759
|
+
client_id.to_h.each do |k,v|
|
|
1760
|
+
unless StringFormatter.valid_string?(k)
|
|
1761
|
+
raise DataFormatError, client_id.inspect
|
|
1762
|
+
end
|
|
1763
|
+
end
|
|
1764
|
+
rescue NoMethodError, TypeError # to_h failed
|
|
1765
|
+
raise DataFormatError, client_id.inspect
|
|
1766
|
+
end
|
|
1767
|
+
|
|
1768
|
+
def format_internal(client_id)
|
|
1769
|
+
return nil if client_id.nil?
|
|
1770
|
+
client_id.to_h.flat_map {|k,v|
|
|
1771
|
+
[StringFormatter.string(k), StringFormatter.nstring(v)]
|
|
1772
|
+
}
|
|
1773
|
+
end
|
|
1774
|
+
|
|
1775
|
+
end
|
|
1776
|
+
|
|
1777
|
+
module StringFormatter
|
|
1778
|
+
|
|
1779
|
+
LITERAL_REGEX = /[\x80-\xff\r\n]/n
|
|
1780
|
+
|
|
1781
|
+
module_function
|
|
1782
|
+
|
|
1783
|
+
# Allows symbols in addition to strings
|
|
1784
|
+
def valid_string?(str)
|
|
1785
|
+
str.is_a?(Symbol) || str.respond_to?(:to_str)
|
|
1786
|
+
end
|
|
1787
|
+
|
|
1788
|
+
# Allows nil, symbols, and strings
|
|
1789
|
+
def valid_nstring?(str)
|
|
1790
|
+
str.nil? || valid_string?(str)
|
|
1791
|
+
end
|
|
1792
|
+
|
|
1793
|
+
# coerces using +to_s+
|
|
1794
|
+
def string(str)
|
|
1795
|
+
str = str.to_s
|
|
1796
|
+
if str =~ LITERAL_REGEX
|
|
1797
|
+
Literal.new(str)
|
|
1798
|
+
else
|
|
1799
|
+
QuotedString.new(str)
|
|
1800
|
+
end
|
|
1801
|
+
end
|
|
1802
|
+
|
|
1803
|
+
# coerces non-nil using +to_s+
|
|
1804
|
+
def nstring(str)
|
|
1805
|
+
str.nil? ? nil : string(str)
|
|
1806
|
+
end
|
|
1807
|
+
|
|
1808
|
+
end
|
|
1809
|
+
|
|
1659
1810
|
# Common validators of number and nz_number types
|
|
1660
1811
|
module NumValidator # :nodoc
|
|
1661
1812
|
class << self
|
|
@@ -1747,6 +1898,18 @@ module Net
|
|
|
1747
1898
|
# raw_data:: Returns the raw data string.
|
|
1748
1899
|
UntaggedResponse = Struct.new(:name, :data, :raw_data)
|
|
1749
1900
|
|
|
1901
|
+
# Net::IMAP::IgnoredResponse represents intentionaly ignored responses.
|
|
1902
|
+
#
|
|
1903
|
+
# This includes untagged response "NOOP" sent by eg. Zimbra to avoid some
|
|
1904
|
+
# clients to close the connection.
|
|
1905
|
+
#
|
|
1906
|
+
# It matches no IMAP standard.
|
|
1907
|
+
#
|
|
1908
|
+
# ==== Fields:
|
|
1909
|
+
#
|
|
1910
|
+
# raw_data:: Returns the raw data string.
|
|
1911
|
+
IgnoredResponse = Struct.new(:raw_data)
|
|
1912
|
+
|
|
1750
1913
|
# Net::IMAP::TaggedResponse represents tagged responses.
|
|
1751
1914
|
#
|
|
1752
1915
|
# The server completion result response indicates the success or
|
|
@@ -1774,8 +1937,7 @@ module Net
|
|
|
1774
1937
|
# Net::IMAP::ResponseText represents texts of responses.
|
|
1775
1938
|
# The text may be prefixed by the response code.
|
|
1776
1939
|
#
|
|
1777
|
-
# resp_text ::= ["["
|
|
1778
|
-
# ;; text SHOULD NOT begin with "[" or "="
|
|
1940
|
+
# resp_text ::= ["[" resp-text-code "]" SP] text
|
|
1779
1941
|
#
|
|
1780
1942
|
# ==== Fields:
|
|
1781
1943
|
#
|
|
@@ -1787,12 +1949,15 @@ module Net
|
|
|
1787
1949
|
|
|
1788
1950
|
# Net::IMAP::ResponseCode represents response codes.
|
|
1789
1951
|
#
|
|
1790
|
-
# resp_text_code ::= "ALERT" /
|
|
1791
|
-
# "
|
|
1952
|
+
# resp_text_code ::= "ALERT" /
|
|
1953
|
+
# "BADCHARSET" [SP "(" astring *(SP astring) ")" ] /
|
|
1954
|
+
# capability_data / "PARSE" /
|
|
1955
|
+
# "PERMANENTFLAGS" SP "("
|
|
1956
|
+
# [flag_perm *(SP flag_perm)] ")" /
|
|
1792
1957
|
# "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
|
|
1793
|
-
# "UIDVALIDITY"
|
|
1794
|
-
# "UNSEEN"
|
|
1795
|
-
# atom [
|
|
1958
|
+
# "UIDNEXT" SP nz_number / "UIDVALIDITY" SP nz_number /
|
|
1959
|
+
# "UNSEEN" SP nz_number /
|
|
1960
|
+
# atom [SP 1*<any TEXT-CHAR except "]">]
|
|
1796
1961
|
#
|
|
1797
1962
|
# ==== Fields:
|
|
1798
1963
|
#
|
|
@@ -1872,6 +2037,39 @@ module Net
|
|
|
1872
2037
|
#
|
|
1873
2038
|
MailboxACLItem = Struct.new(:user, :rights, :mailbox)
|
|
1874
2039
|
|
|
2040
|
+
# Net::IMAP::Namespace represents a single [RFC-2342] namespace.
|
|
2041
|
+
#
|
|
2042
|
+
# Namespace = nil / "(" 1*( "(" string SP (<"> QUOTED_CHAR <"> /
|
|
2043
|
+
# nil) *(Namespace_Response_Extension) ")" ) ")"
|
|
2044
|
+
#
|
|
2045
|
+
# Namespace_Response_Extension = SP string SP "(" string *(SP string)
|
|
2046
|
+
# ")"
|
|
2047
|
+
#
|
|
2048
|
+
# ==== Fields:
|
|
2049
|
+
#
|
|
2050
|
+
# prefix:: Returns the namespace prefix string.
|
|
2051
|
+
# delim:: Returns nil or the hierarchy delimiter character.
|
|
2052
|
+
# extensions:: Returns a hash of extension names to extension flag arrays.
|
|
2053
|
+
#
|
|
2054
|
+
Namespace = Struct.new(:prefix, :delim, :extensions)
|
|
2055
|
+
|
|
2056
|
+
# Net::IMAP::Namespaces represents the response from [RFC-2342] NAMESPACE.
|
|
2057
|
+
#
|
|
2058
|
+
# Namespace_Response = "*" SP "NAMESPACE" SP Namespace SP Namespace SP
|
|
2059
|
+
# Namespace
|
|
2060
|
+
#
|
|
2061
|
+
# ; The first Namespace is the Personal Namespace(s)
|
|
2062
|
+
# ; The second Namespace is the Other Users' Namespace(s)
|
|
2063
|
+
# ; The third Namespace is the Shared Namespace(s)
|
|
2064
|
+
#
|
|
2065
|
+
# ==== Fields:
|
|
2066
|
+
#
|
|
2067
|
+
# personal:: Returns an array of Personal Net::IMAP::Namespace objects.
|
|
2068
|
+
# other:: Returns an array of Other Users' Net::IMAP::Namespace objects.
|
|
2069
|
+
# shared:: Returns an array of Shared Net::IMAP::Namespace objects.
|
|
2070
|
+
#
|
|
2071
|
+
Namespaces = Struct.new(:personal, :other, :shared)
|
|
2072
|
+
|
|
1875
2073
|
# Net::IMAP::StatusData represents the contents of the STATUS response.
|
|
1876
2074
|
#
|
|
1877
2075
|
# ==== Fields:
|
|
@@ -2291,8 +2489,12 @@ module Net
|
|
|
2291
2489
|
return response_cond
|
|
2292
2490
|
when /\A(?:FLAGS)\z/ni
|
|
2293
2491
|
return flags_response
|
|
2492
|
+
when /\A(?:ID)\z/ni
|
|
2493
|
+
return id_response
|
|
2294
2494
|
when /\A(?:LIST|LSUB|XLIST)\z/ni
|
|
2295
2495
|
return list_response
|
|
2496
|
+
when /\A(?:NAMESPACE)\z/ni
|
|
2497
|
+
return namespace_response
|
|
2296
2498
|
when /\A(?:QUOTA)\z/ni
|
|
2297
2499
|
return getquota_response
|
|
2298
2500
|
when /\A(?:QUOTAROOT)\z/ni
|
|
@@ -2307,6 +2509,8 @@ module Net
|
|
|
2307
2509
|
return status_response
|
|
2308
2510
|
when /\A(?:CAPABILITY)\z/ni
|
|
2309
2511
|
return capability_response
|
|
2512
|
+
when /\A(?:NOOP)\z/ni
|
|
2513
|
+
return ignored_response
|
|
2310
2514
|
else
|
|
2311
2515
|
return text_response
|
|
2312
2516
|
end
|
|
@@ -2316,7 +2520,7 @@ module Net
|
|
|
2316
2520
|
end
|
|
2317
2521
|
|
|
2318
2522
|
def response_tagged
|
|
2319
|
-
tag =
|
|
2523
|
+
tag = astring_chars
|
|
2320
2524
|
match(T_SPACE)
|
|
2321
2525
|
token = match(T_ATOM)
|
|
2322
2526
|
name = token.value.upcase
|
|
@@ -2876,14 +3080,18 @@ module Net
|
|
|
2876
3080
|
return name, modseq
|
|
2877
3081
|
end
|
|
2878
3082
|
|
|
3083
|
+
def ignored_response
|
|
3084
|
+
while lookahead.symbol != T_CRLF
|
|
3085
|
+
shift_token
|
|
3086
|
+
end
|
|
3087
|
+
return IgnoredResponse.new(@str)
|
|
3088
|
+
end
|
|
3089
|
+
|
|
2879
3090
|
def text_response
|
|
2880
3091
|
token = match(T_ATOM)
|
|
2881
3092
|
name = token.value.upcase
|
|
2882
3093
|
match(T_SPACE)
|
|
2883
|
-
|
|
2884
|
-
token = match(T_TEXT)
|
|
2885
|
-
@lex_state = EXPR_BEG
|
|
2886
|
-
return UntaggedResponse.new(name, token.value)
|
|
3094
|
+
return UntaggedResponse.new(name, text)
|
|
2887
3095
|
end
|
|
2888
3096
|
|
|
2889
3097
|
def flags_response
|
|
@@ -3114,11 +3322,15 @@ module Net
|
|
|
3114
3322
|
token = match(T_ATOM)
|
|
3115
3323
|
name = token.value.upcase
|
|
3116
3324
|
match(T_SPACE)
|
|
3325
|
+
UntaggedResponse.new(name, capability_data, @str)
|
|
3326
|
+
end
|
|
3327
|
+
|
|
3328
|
+
def capability_data
|
|
3117
3329
|
data = []
|
|
3118
3330
|
while true
|
|
3119
3331
|
token = lookahead
|
|
3120
3332
|
case token.symbol
|
|
3121
|
-
when T_CRLF
|
|
3333
|
+
when T_CRLF, T_RBRA
|
|
3122
3334
|
break
|
|
3123
3335
|
when T_SPACE
|
|
3124
3336
|
shift_token
|
|
@@ -3126,30 +3338,142 @@ module Net
|
|
|
3126
3338
|
end
|
|
3127
3339
|
data.push(atom.upcase)
|
|
3128
3340
|
end
|
|
3341
|
+
data
|
|
3342
|
+
end
|
|
3343
|
+
|
|
3344
|
+
def id_response
|
|
3345
|
+
token = match(T_ATOM)
|
|
3346
|
+
name = token.value.upcase
|
|
3347
|
+
match(T_SPACE)
|
|
3348
|
+
token = match(T_LPAR, T_NIL)
|
|
3349
|
+
if token.symbol == T_NIL
|
|
3350
|
+
return UntaggedResponse.new(name, nil, @str)
|
|
3351
|
+
else
|
|
3352
|
+
data = {}
|
|
3353
|
+
while true
|
|
3354
|
+
token = lookahead
|
|
3355
|
+
case token.symbol
|
|
3356
|
+
when T_RPAR
|
|
3357
|
+
shift_token
|
|
3358
|
+
break
|
|
3359
|
+
when T_SPACE
|
|
3360
|
+
shift_token
|
|
3361
|
+
next
|
|
3362
|
+
else
|
|
3363
|
+
key = string
|
|
3364
|
+
match(T_SPACE)
|
|
3365
|
+
val = nstring
|
|
3366
|
+
data[key] = val
|
|
3367
|
+
end
|
|
3368
|
+
end
|
|
3369
|
+
return UntaggedResponse.new(name, data, @str)
|
|
3370
|
+
end
|
|
3371
|
+
end
|
|
3372
|
+
|
|
3373
|
+
def namespace_response
|
|
3374
|
+
@lex_state = EXPR_DATA
|
|
3375
|
+
token = lookahead
|
|
3376
|
+
token = match(T_ATOM)
|
|
3377
|
+
name = token.value.upcase
|
|
3378
|
+
match(T_SPACE)
|
|
3379
|
+
personal = namespaces
|
|
3380
|
+
match(T_SPACE)
|
|
3381
|
+
other = namespaces
|
|
3382
|
+
match(T_SPACE)
|
|
3383
|
+
shared = namespaces
|
|
3384
|
+
@lex_state = EXPR_BEG
|
|
3385
|
+
data = Namespaces.new(personal, other, shared)
|
|
3129
3386
|
return UntaggedResponse.new(name, data, @str)
|
|
3130
3387
|
end
|
|
3131
3388
|
|
|
3132
|
-
def
|
|
3133
|
-
@lex_state = EXPR_RTEXT
|
|
3389
|
+
def namespaces
|
|
3134
3390
|
token = lookahead
|
|
3135
|
-
|
|
3136
|
-
|
|
3391
|
+
# empty () is not allowed, so nil is functionally identical to empty.
|
|
3392
|
+
data = []
|
|
3393
|
+
if token.symbol == T_NIL
|
|
3394
|
+
shift_token
|
|
3137
3395
|
else
|
|
3138
|
-
|
|
3396
|
+
match(T_LPAR)
|
|
3397
|
+
loop do
|
|
3398
|
+
data << namespace
|
|
3399
|
+
break unless lookahead.symbol == T_SPACE
|
|
3400
|
+
shift_token
|
|
3401
|
+
end
|
|
3402
|
+
match(T_RPAR)
|
|
3403
|
+
end
|
|
3404
|
+
data
|
|
3405
|
+
end
|
|
3406
|
+
|
|
3407
|
+
def namespace
|
|
3408
|
+
match(T_LPAR)
|
|
3409
|
+
prefix = match(T_QUOTED, T_LITERAL).value
|
|
3410
|
+
match(T_SPACE)
|
|
3411
|
+
delimiter = string
|
|
3412
|
+
extensions = namespace_response_extensions
|
|
3413
|
+
match(T_RPAR)
|
|
3414
|
+
Namespace.new(prefix, delimiter, extensions)
|
|
3415
|
+
end
|
|
3416
|
+
|
|
3417
|
+
def namespace_response_extensions
|
|
3418
|
+
data = {}
|
|
3419
|
+
token = lookahead
|
|
3420
|
+
if token.symbol == T_SPACE
|
|
3421
|
+
shift_token
|
|
3422
|
+
name = match(T_QUOTED, T_LITERAL).value
|
|
3423
|
+
data[name] ||= []
|
|
3424
|
+
match(T_SPACE)
|
|
3425
|
+
match(T_LPAR)
|
|
3426
|
+
loop do
|
|
3427
|
+
data[name].push match(T_QUOTED, T_LITERAL).value
|
|
3428
|
+
break unless lookahead.symbol == T_SPACE
|
|
3429
|
+
shift_token
|
|
3430
|
+
end
|
|
3431
|
+
match(T_RPAR)
|
|
3432
|
+
end
|
|
3433
|
+
data
|
|
3434
|
+
end
|
|
3435
|
+
|
|
3436
|
+
# text = 1*TEXT-CHAR
|
|
3437
|
+
# TEXT-CHAR = <any CHAR except CR and LF>
|
|
3438
|
+
def text
|
|
3439
|
+
match(T_TEXT, lex_state: EXPR_TEXT).value
|
|
3440
|
+
end
|
|
3441
|
+
|
|
3442
|
+
# resp-text = ["[" resp-text-code "]" SP] text
|
|
3443
|
+
def resp_text
|
|
3444
|
+
token = match(T_LBRA, T_TEXT, lex_state: EXPR_RTEXT)
|
|
3445
|
+
case token.symbol
|
|
3446
|
+
when T_LBRA
|
|
3447
|
+
code = resp_text_code
|
|
3448
|
+
match(T_RBRA)
|
|
3449
|
+
accept_space # violating RFC
|
|
3450
|
+
ResponseText.new(code, text)
|
|
3451
|
+
when T_TEXT
|
|
3452
|
+
ResponseText.new(nil, token.value)
|
|
3139
3453
|
end
|
|
3140
|
-
token = match(T_TEXT)
|
|
3141
|
-
@lex_state = EXPR_BEG
|
|
3142
|
-
return ResponseText.new(code, token.value)
|
|
3143
3454
|
end
|
|
3144
3455
|
|
|
3456
|
+
# See https://www.rfc-editor.org/errata/rfc3501
|
|
3457
|
+
#
|
|
3458
|
+
# resp-text-code = "ALERT" /
|
|
3459
|
+
# "BADCHARSET" [SP "(" charset *(SP charset) ")" ] /
|
|
3460
|
+
# capability-data / "PARSE" /
|
|
3461
|
+
# "PERMANENTFLAGS" SP "("
|
|
3462
|
+
# [flag-perm *(SP flag-perm)] ")" /
|
|
3463
|
+
# "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
|
|
3464
|
+
# "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
|
|
3465
|
+
# "UNSEEN" SP nz-number /
|
|
3466
|
+
# atom [SP 1*<any TEXT-CHAR except "]">]
|
|
3145
3467
|
def resp_text_code
|
|
3146
|
-
@lex_state = EXPR_BEG
|
|
3147
|
-
match(T_LBRA)
|
|
3148
3468
|
token = match(T_ATOM)
|
|
3149
3469
|
name = token.value.upcase
|
|
3150
3470
|
case name
|
|
3151
3471
|
when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n
|
|
3152
3472
|
result = ResponseCode.new(name, nil)
|
|
3473
|
+
when /\A(?:BADCHARSET)\z/n
|
|
3474
|
+
result = ResponseCode.new(name, charset_list)
|
|
3475
|
+
when /\A(?:CAPABILITY)\z/ni
|
|
3476
|
+
result = ResponseCode.new(name, capability_data)
|
|
3153
3477
|
when /\A(?:PERMANENTFLAGS)\z/n
|
|
3154
3478
|
match(T_SPACE)
|
|
3155
3479
|
result = ResponseCode.new(name, flag_list)
|
|
@@ -3160,19 +3484,28 @@ module Net
|
|
|
3160
3484
|
token = lookahead
|
|
3161
3485
|
if token.symbol == T_SPACE
|
|
3162
3486
|
shift_token
|
|
3163
|
-
|
|
3164
|
-
token = match(T_TEXT)
|
|
3165
|
-
@lex_state = EXPR_BEG
|
|
3487
|
+
token = match(T_TEXT, lex_state: EXPR_CTEXT)
|
|
3166
3488
|
result = ResponseCode.new(name, token.value)
|
|
3167
3489
|
else
|
|
3168
3490
|
result = ResponseCode.new(name, nil)
|
|
3169
3491
|
end
|
|
3170
3492
|
end
|
|
3171
|
-
match(T_RBRA)
|
|
3172
|
-
@lex_state = EXPR_RTEXT
|
|
3173
3493
|
return result
|
|
3174
3494
|
end
|
|
3175
3495
|
|
|
3496
|
+
def charset_list
|
|
3497
|
+
result = []
|
|
3498
|
+
if accept(T_SPACE)
|
|
3499
|
+
match(T_LPAR)
|
|
3500
|
+
result << charset
|
|
3501
|
+
while accept(T_SPACE)
|
|
3502
|
+
result << charset
|
|
3503
|
+
end
|
|
3504
|
+
match(T_RPAR)
|
|
3505
|
+
end
|
|
3506
|
+
result
|
|
3507
|
+
end
|
|
3508
|
+
|
|
3176
3509
|
def address_list
|
|
3177
3510
|
token = lookahead
|
|
3178
3511
|
if token.symbol == T_NIL
|
|
@@ -3269,7 +3602,7 @@ module Net
|
|
|
3269
3602
|
if string_token?(token)
|
|
3270
3603
|
return string
|
|
3271
3604
|
else
|
|
3272
|
-
return
|
|
3605
|
+
return astring_chars
|
|
3273
3606
|
end
|
|
3274
3607
|
end
|
|
3275
3608
|
|
|
@@ -3299,34 +3632,49 @@ module Net
|
|
|
3299
3632
|
return token.value.upcase
|
|
3300
3633
|
end
|
|
3301
3634
|
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
while true
|
|
3305
|
-
token = lookahead
|
|
3306
|
-
if atom_token?(token)
|
|
3307
|
-
result.concat(token.value)
|
|
3308
|
-
shift_token
|
|
3309
|
-
else
|
|
3310
|
-
if result.empty?
|
|
3311
|
-
parse_error("unexpected token %s", token.symbol)
|
|
3312
|
-
else
|
|
3313
|
-
return result
|
|
3314
|
-
end
|
|
3315
|
-
end
|
|
3316
|
-
end
|
|
3317
|
-
end
|
|
3318
|
-
|
|
3635
|
+
# atom = 1*ATOM-CHAR
|
|
3636
|
+
# ATOM-CHAR = <any CHAR except atom-specials>
|
|
3319
3637
|
ATOM_TOKENS = [
|
|
3320
3638
|
T_ATOM,
|
|
3321
3639
|
T_NUMBER,
|
|
3322
3640
|
T_NIL,
|
|
3323
3641
|
T_LBRA,
|
|
3324
|
-
T_RBRA,
|
|
3325
3642
|
T_PLUS
|
|
3326
3643
|
]
|
|
3327
3644
|
|
|
3328
|
-
def
|
|
3329
|
-
|
|
3645
|
+
def atom
|
|
3646
|
+
-combine_adjacent(*ATOM_TOKENS)
|
|
3647
|
+
end
|
|
3648
|
+
|
|
3649
|
+
# ASTRING-CHAR = ATOM-CHAR / resp-specials
|
|
3650
|
+
# resp-specials = "]"
|
|
3651
|
+
ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA]
|
|
3652
|
+
|
|
3653
|
+
def astring_chars
|
|
3654
|
+
combine_adjacent(*ASTRING_CHARS_TOKENS)
|
|
3655
|
+
end
|
|
3656
|
+
|
|
3657
|
+
def combine_adjacent(*tokens)
|
|
3658
|
+
result = "".b
|
|
3659
|
+
while token = accept(*tokens)
|
|
3660
|
+
result << token.value
|
|
3661
|
+
end
|
|
3662
|
+
if result.empty?
|
|
3663
|
+
parse_error('unexpected token %s (expected %s)',
|
|
3664
|
+
lookahead.symbol, args.join(" or "))
|
|
3665
|
+
end
|
|
3666
|
+
result
|
|
3667
|
+
end
|
|
3668
|
+
|
|
3669
|
+
# See https://www.rfc-editor.org/errata/rfc3501
|
|
3670
|
+
#
|
|
3671
|
+
# charset = atom / quoted
|
|
3672
|
+
def charset
|
|
3673
|
+
if token = accept(T_QUOTED)
|
|
3674
|
+
token.value
|
|
3675
|
+
else
|
|
3676
|
+
atom
|
|
3677
|
+
end
|
|
3330
3678
|
end
|
|
3331
3679
|
|
|
3332
3680
|
def number
|
|
@@ -3344,22 +3692,62 @@ module Net
|
|
|
3344
3692
|
return nil
|
|
3345
3693
|
end
|
|
3346
3694
|
|
|
3347
|
-
|
|
3695
|
+
SPACES_REGEXP = /\G */n
|
|
3696
|
+
|
|
3697
|
+
# This advances @pos directly so it's safe before changing @lex_state.
|
|
3698
|
+
def accept_space
|
|
3699
|
+
if @token
|
|
3700
|
+
shift_token if @token.symbol == T_SPACE
|
|
3701
|
+
elsif @str[@pos] == " "
|
|
3702
|
+
@pos += 1
|
|
3703
|
+
end
|
|
3704
|
+
end
|
|
3705
|
+
|
|
3706
|
+
# The RFC is very strict about this and usually we should be too.
|
|
3707
|
+
# But skipping spaces is usually a safe workaround for buggy servers.
|
|
3708
|
+
#
|
|
3709
|
+
# This advances @pos directly so it's safe before changing @lex_state.
|
|
3710
|
+
def accept_spaces
|
|
3711
|
+
shift_token if @token&.symbol == T_SPACE
|
|
3712
|
+
if @str.index(SPACES_REGEXP, @pos)
|
|
3713
|
+
@pos = $~.end(0)
|
|
3714
|
+
end
|
|
3715
|
+
end
|
|
3716
|
+
|
|
3717
|
+
def match(*args, lex_state: @lex_state)
|
|
3718
|
+
if @token && lex_state != @lex_state
|
|
3719
|
+
parse_error("invalid lex_state change to %s with unconsumed token",
|
|
3720
|
+
lex_state)
|
|
3721
|
+
end
|
|
3722
|
+
begin
|
|
3723
|
+
@lex_state, original_lex_state = lex_state, @lex_state
|
|
3724
|
+
token = lookahead
|
|
3725
|
+
unless args.include?(token.symbol)
|
|
3726
|
+
parse_error('unexpected token %s (expected %s)',
|
|
3727
|
+
token.symbol.id2name,
|
|
3728
|
+
args.collect {|i| i.id2name}.join(" or "))
|
|
3729
|
+
end
|
|
3730
|
+
shift_token
|
|
3731
|
+
return token
|
|
3732
|
+
ensure
|
|
3733
|
+
@lex_state = original_lex_state
|
|
3734
|
+
end
|
|
3735
|
+
end
|
|
3736
|
+
|
|
3737
|
+
# like match, but does not raise error on failure.
|
|
3738
|
+
#
|
|
3739
|
+
# returns and shifts token on successful match
|
|
3740
|
+
# returns nil and leaves @token unshifted on no match
|
|
3741
|
+
def accept(*args)
|
|
3348
3742
|
token = lookahead
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
args.collect {|i| i.id2name}.join(" or "))
|
|
3743
|
+
if args.include?(token.symbol)
|
|
3744
|
+
shift_token
|
|
3745
|
+
token
|
|
3353
3746
|
end
|
|
3354
|
-
shift_token
|
|
3355
|
-
return token
|
|
3356
3747
|
end
|
|
3357
3748
|
|
|
3358
3749
|
def lookahead
|
|
3359
|
-
|
|
3360
|
-
@token = next_token
|
|
3361
|
-
end
|
|
3362
|
-
return @token
|
|
3750
|
+
@token ||= next_token
|
|
3363
3751
|
end
|
|
3364
3752
|
|
|
3365
3753
|
def shift_token
|
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.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shugo Maeda
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-03-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: net-protocol
|
|
@@ -92,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
92
92
|
- !ruby/object:Gem::Version
|
|
93
93
|
version: '0'
|
|
94
94
|
requirements: []
|
|
95
|
-
rubygems_version: 3.
|
|
95
|
+
rubygems_version: 3.3.0.dev
|
|
96
96
|
signing_key:
|
|
97
97
|
specification_version: 4
|
|
98
98
|
summary: Ruby client api for Internet Message Access Protocol
|