net-imap 0.3.4 → 0.4.1
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 +4 -4
- data/.github/workflows/pages.yml +46 -0
- data/.github/workflows/test.yml +12 -12
- data/Gemfile +1 -0
- data/README.md +15 -4
- data/Rakefile +0 -7
- data/benchmarks/generate_parser_benchmarks +52 -0
- data/benchmarks/parser.yml +578 -0
- data/benchmarks/stringprep.yml +1 -1
- data/lib/net/imap/authenticators.rb +26 -57
- data/lib/net/imap/command_data.rb +13 -6
- data/lib/net/imap/data_encoding.rb +3 -3
- data/lib/net/imap/deprecated_client_options.rb +139 -0
- data/lib/net/imap/response_data.rb +46 -41
- data/lib/net/imap/response_parser/parser_utils.rb +230 -0
- data/lib/net/imap/response_parser.rb +665 -627
- data/lib/net/imap/sasl/anonymous_authenticator.rb +68 -0
- data/lib/net/imap/sasl/authentication_exchange.rb +107 -0
- data/lib/net/imap/sasl/authenticators.rb +118 -0
- data/lib/net/imap/sasl/client_adapter.rb +72 -0
- data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +15 -9
- data/lib/net/imap/sasl/digest_md5_authenticator.rb +168 -0
- data/lib/net/imap/sasl/external_authenticator.rb +62 -0
- data/lib/net/imap/sasl/gs2_header.rb +80 -0
- data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +19 -14
- data/lib/net/imap/sasl/oauthbearer_authenticator.rb +164 -0
- data/lib/net/imap/sasl/plain_authenticator.rb +93 -0
- data/lib/net/imap/sasl/protocol_adapters.rb +45 -0
- data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
- data/lib/net/imap/sasl/scram_authenticator.rb +278 -0
- data/lib/net/imap/sasl/stringprep.rb +6 -66
- data/lib/net/imap/sasl/xoauth2_authenticator.rb +88 -0
- data/lib/net/imap/sasl.rb +144 -43
- data/lib/net/imap/sasl_adapter.rb +21 -0
- data/lib/net/imap/stringprep/nameprep.rb +70 -0
- data/lib/net/imap/stringprep/saslprep.rb +69 -0
- data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
- data/lib/net/imap/stringprep/tables.rb +146 -0
- data/lib/net/imap/stringprep/trace.rb +85 -0
- data/lib/net/imap/stringprep.rb +159 -0
- data/lib/net/imap.rb +976 -590
- data/net-imap.gemspec +2 -2
- data/rakelib/saslprep.rake +4 -4
- data/rakelib/string_prep_tables_generator.rb +82 -60
- metadata +31 -12
- data/lib/net/imap/authenticators/digest_md5.rb +0 -115
- data/lib/net/imap/authenticators/plain.rb +0 -41
- data/lib/net/imap/authenticators/xoauth2.rb +0 -20
- data/lib/net/imap/sasl/saslprep.rb +0 -55
- data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
- data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
@@ -1,68 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
3
|
+
# Backward compatible delegators from Net::IMAP to Net::IMAP::SASL.
|
4
4
|
module Net::IMAP::Authenticators
|
5
5
|
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# See PlainAuthenticator, XOauth2Authenticator, and DigestMD5Authenticator for
|
16
|
-
# examples.
|
17
|
-
def add_authenticator(auth_type, authenticator)
|
18
|
-
authenticators[auth_type] = authenticator
|
6
|
+
# Deprecated. Use Net::IMAP::SASL.add_authenticator instead.
|
7
|
+
def add_authenticator(...)
|
8
|
+
warn(
|
9
|
+
"%s.%s is deprecated. Use %s.%s instead." % [
|
10
|
+
Net::IMAP, __method__, Net::IMAP::SASL, __method__
|
11
|
+
],
|
12
|
+
uplevel: 1
|
13
|
+
)
|
14
|
+
Net::IMAP::SASL.add_authenticator(...)
|
19
15
|
end
|
20
16
|
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# [Note]
|
31
|
-
# This method is intended for internal use by connection protocol code only.
|
32
|
-
# Protocol client users should see refer to their client's documentation,
|
33
|
-
# e.g. Net::IMAP#authenticate for Net::IMAP.
|
34
|
-
#
|
35
|
-
# The call signatures documented for this method are recommendations for
|
36
|
-
# authenticator implementors. All arguments (other than +mechanism+) are
|
37
|
-
# forwarded to the registered authenticator's +#new+ (or +#call+) method, and
|
38
|
-
# each authenticator must document its own arguments.
|
39
|
-
#
|
40
|
-
# The returned object represents a single authentication exchange and <em>must
|
41
|
-
# not</em> be reused for multiple authentication attempts.
|
42
|
-
def authenticator(mechanism, *authargs, **properties, &callback)
|
43
|
-
authenticator = authenticators.fetch(mechanism.upcase) do
|
44
|
-
raise ArgumentError, 'unknown auth type - "%s"' % mechanism
|
45
|
-
end
|
46
|
-
if authenticator.respond_to?(:new)
|
47
|
-
authenticator.new(*authargs, **properties, &callback)
|
48
|
-
else
|
49
|
-
authenticator.call(*authargs, **properties, &callback)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def authenticators
|
56
|
-
@authenticators ||= {}
|
17
|
+
# Deprecated. Use Net::IMAP::SASL.authenticator instead.
|
18
|
+
def authenticator(...)
|
19
|
+
warn(
|
20
|
+
"%s.%s is deprecated. Use %s.%s instead." % [
|
21
|
+
Net::IMAP, __method__, Net::IMAP::SASL, __method__
|
22
|
+
],
|
23
|
+
uplevel: 1
|
24
|
+
)
|
25
|
+
Net::IMAP::SASL.authenticator(...)
|
57
26
|
end
|
58
27
|
|
28
|
+
Net::IMAP.extend self
|
59
29
|
end
|
60
30
|
|
61
|
-
|
31
|
+
class Net::IMAP
|
32
|
+
PlainAuthenticator = SASL::PlainAuthenticator # :nodoc:
|
33
|
+
deprecate_constant :PlainAuthenticator
|
62
34
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
require_relative "authenticators/cram_md5"
|
67
|
-
require_relative "authenticators/digest_md5"
|
68
|
-
require_relative "authenticators/xoauth2"
|
35
|
+
XOauth2Authenticator = SASL::XOAuth2Authenticator # :nodoc:
|
36
|
+
deprecate_constant :XOauth2Authenticator
|
37
|
+
end
|
@@ -52,13 +52,20 @@ module Net
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def send_string_data(str, tag = nil)
|
55
|
-
|
56
|
-
when ""
|
55
|
+
if str.empty?
|
57
56
|
put_string('""')
|
58
|
-
|
59
|
-
# literal
|
57
|
+
elsif str.match?(/[\r\n]/n)
|
58
|
+
# literal, because multiline
|
60
59
|
send_literal(str, tag)
|
61
|
-
|
60
|
+
elsif !str.ascii_only?
|
61
|
+
if @utf8_strings
|
62
|
+
# quoted string
|
63
|
+
send_quoted_string(str)
|
64
|
+
else
|
65
|
+
# literal, because of non-ASCII bytes
|
66
|
+
send_literal(str, tag)
|
67
|
+
end
|
68
|
+
elsif str.match?(/[(){ \x00-\x1f\x7f%*"\\]/n)
|
62
69
|
# quoted string
|
63
70
|
send_quoted_string(str)
|
64
71
|
else
|
@@ -67,7 +74,7 @@ module Net
|
|
67
74
|
end
|
68
75
|
|
69
76
|
def send_quoted_string(str)
|
70
|
-
put_string('"' + str.gsub(/["\\]
|
77
|
+
put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"')
|
71
78
|
end
|
72
79
|
|
73
80
|
def send_literal(str, tag = nil)
|
@@ -54,9 +54,9 @@ module Net
|
|
54
54
|
# Net::IMAP does _not_ automatically encode and decode
|
55
55
|
# mailbox names to and from UTF-7.
|
56
56
|
def self.decode_utf7(s)
|
57
|
-
return s.gsub(/&([
|
58
|
-
if $1
|
59
|
-
(
|
57
|
+
return s.gsub(/&([A-Za-z0-9+,]+)?-/n) {
|
58
|
+
if base64 = $1
|
59
|
+
(base64.tr(",", "/") + "===").unpack1("m").encode(Encoding::UTF_8, Encoding::UTF_16BE)
|
60
60
|
else
|
61
61
|
"&"
|
62
62
|
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP < Protocol
|
5
|
+
|
6
|
+
# This module handles deprecated arguments to various Net::IMAP methods.
|
7
|
+
module DeprecatedClientOptions
|
8
|
+
|
9
|
+
# :call-seq:
|
10
|
+
# Net::IMAP.new(host, **options) # standard keyword options
|
11
|
+
# Net::IMAP.new(host, options) # obsolete hash options
|
12
|
+
# Net::IMAP.new(host, port) # obsolete port argument
|
13
|
+
# Net::IMAP.new(host, port, usessl, certs = nil, verify = true) # deprecated SSL arguments
|
14
|
+
#
|
15
|
+
# Translates Net::IMAP.new arguments for backward compatibility.
|
16
|
+
#
|
17
|
+
# ==== Obsolete arguments
|
18
|
+
#
|
19
|
+
# Using obsolete arguments does not a warning. Obsolete arguments will be
|
20
|
+
# deprecated by a future release.
|
21
|
+
#
|
22
|
+
# If a second positional argument is given and it is a hash (or is
|
23
|
+
# convertable via +#to_hash+), it is converted to keyword arguments.
|
24
|
+
#
|
25
|
+
# # Obsolete:
|
26
|
+
# Net::IMAP.new("imap.example.com", options_hash)
|
27
|
+
# # Use instead:
|
28
|
+
# Net::IMAP.new("imap.example.com", **options_hash)
|
29
|
+
#
|
30
|
+
# If a second positional argument is given and it is not a hash, it is
|
31
|
+
# converted to the +port+ keyword argument.
|
32
|
+
# # Obsolete:
|
33
|
+
# Net::IMAP.new("imap.example.com", 114433)
|
34
|
+
# # Use instead:
|
35
|
+
# Net::IMAP.new("imap.example.com", port: 114433)
|
36
|
+
#
|
37
|
+
# ==== Deprecated arguments
|
38
|
+
#
|
39
|
+
# Using deprecated arguments prints a warning. Convert to keyword
|
40
|
+
# arguments to avoid the warning. Deprecated arguments will be removed in
|
41
|
+
# a future release.
|
42
|
+
#
|
43
|
+
# If +usessl+ is false, +certs+, and +verify+ are ignored. When it true,
|
44
|
+
# all three arguments are converted to the +ssl+ keyword argument.
|
45
|
+
# Without +certs+ or +verify+, it is converted to <tt>ssl: true</tt>.
|
46
|
+
# # DEPRECATED:
|
47
|
+
# Net::IMAP.new("imap.example.com", nil, true) # => prints a warning
|
48
|
+
# # Use instead:
|
49
|
+
# Net::IMAP.new("imap.example.com", ssl: true)
|
50
|
+
#
|
51
|
+
# When +certs+ is a path to a directory, it is converted to <tt>ca_path:
|
52
|
+
# certs</tt>.
|
53
|
+
# # DEPRECATED:
|
54
|
+
# Net::IMAP.new("imap.example.com", nil, true, "/path/to/certs") # => prints a warning
|
55
|
+
# # Use instead:
|
56
|
+
# Net::IMAP.new("imap.example.com", ssl: {ca_path: "/path/to/certs"})
|
57
|
+
#
|
58
|
+
# When +certs+ is a path to a file, it is converted to <tt>ca_file:
|
59
|
+
# certs</tt>.
|
60
|
+
# # DEPRECATED:
|
61
|
+
# Net::IMAP.new("imap.example.com", nil, true, "/path/to/cert.pem") # => prints a warning
|
62
|
+
# # Use instead:
|
63
|
+
# Net::IMAP.new("imap.example.com", ssl: {ca_file: "/path/to/cert.pem"})
|
64
|
+
#
|
65
|
+
# When +verify+ is +false+, it is converted to <tt>verify_mode:
|
66
|
+
# OpenSSL::SSL::VERIFY_NONE</tt>.
|
67
|
+
# # DEPRECATED:
|
68
|
+
# Net::IMAP.new("imap.example.com", nil, true, nil, false) # => prints a warning
|
69
|
+
# # Use instead:
|
70
|
+
# Net::IMAP.new("imap.example.com", ssl: {verify_mode: OpenSSL::SSL::VERIFY_NONE})
|
71
|
+
#
|
72
|
+
def initialize(host, port_or_options = nil, *deprecated, **options)
|
73
|
+
if port_or_options.nil? && deprecated.empty?
|
74
|
+
super host, **options
|
75
|
+
elsif options.any?
|
76
|
+
# Net::IMAP.new(host, *__invalid__, **options)
|
77
|
+
raise ArgumentError, "Do not combine deprecated and keyword arguments"
|
78
|
+
elsif port_or_options.respond_to?(:to_hash) and deprecated.any?
|
79
|
+
# Net::IMAP.new(host, options, *__invalid__)
|
80
|
+
raise ArgumentError, "Do not use deprecated SSL params with options hash"
|
81
|
+
elsif port_or_options.respond_to?(:to_hash)
|
82
|
+
super host, **Hash.try_convert(port_or_options)
|
83
|
+
elsif deprecated.empty?
|
84
|
+
super host, port: port_or_options
|
85
|
+
elsif deprecated.shift
|
86
|
+
warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1
|
87
|
+
super host, port: port_or_options, ssl: create_ssl_params(*deprecated)
|
88
|
+
else
|
89
|
+
warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1
|
90
|
+
super host, port: port_or_options, ssl: false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# :call-seq:
|
95
|
+
# starttls(**options) # standard
|
96
|
+
# starttls(options = {}) # obsolete
|
97
|
+
# starttls(certs = nil, verify = true) # deprecated
|
98
|
+
#
|
99
|
+
# Translates Net::IMAP#starttls arguments for backward compatibility.
|
100
|
+
#
|
101
|
+
# Support for +certs+ and +verify+ will be dropped in a future release.
|
102
|
+
#
|
103
|
+
# See ::new for interpretation of +certs+ and +verify+.
|
104
|
+
def starttls(*deprecated, **options)
|
105
|
+
if deprecated.empty?
|
106
|
+
super(**options)
|
107
|
+
elsif options.any?
|
108
|
+
# starttls(*__invalid__, **options)
|
109
|
+
raise ArgumentError, "Do not combine deprecated and keyword options"
|
110
|
+
elsif deprecated.first.respond_to?(:to_hash) && deprecated.length > 1
|
111
|
+
# starttls(*__invalid__, **options)
|
112
|
+
raise ArgumentError, "Do not use deprecated verify param with options hash"
|
113
|
+
elsif deprecated.first.respond_to?(:to_hash)
|
114
|
+
super(**Hash.try_convert(deprecated.first))
|
115
|
+
else
|
116
|
+
warn "DEPRECATED: Call Net::IMAP#starttls with keyword options", uplevel: 1
|
117
|
+
super(**create_ssl_params(*deprecated))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def create_ssl_params(certs = nil, verify = true)
|
124
|
+
params = {}
|
125
|
+
if certs
|
126
|
+
if File.file?(certs)
|
127
|
+
params[:ca_file] = certs
|
128
|
+
elsif File.directory?(certs)
|
129
|
+
params[:ca_path] = certs
|
130
|
+
end
|
131
|
+
end
|
132
|
+
params[:verify_mode] =
|
133
|
+
verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
134
|
+
params
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -891,13 +891,6 @@ module Net
|
|
891
891
|
# should use BodyTypeBasic.
|
892
892
|
# BodyTypeMultipart:: for <tt>multipart/*</tt> parts
|
893
893
|
#
|
894
|
-
# ==== Deprecated BodyStructure classes
|
895
|
-
# The following classes represent invalid server responses or parser bugs:
|
896
|
-
# BodyTypeExtension:: parser bug: used for <tt>message/*</tt> where
|
897
|
-
# BodyTypeBasic should have been used.
|
898
|
-
# BodyTypeAttachment:: server bug: some servers sometimes return the
|
899
|
-
# "Content-Disposition: attachment" data where the
|
900
|
-
# entire body structure for a message part is expected.
|
901
894
|
module BodyStructure
|
902
895
|
end
|
903
896
|
|
@@ -914,6 +907,7 @@ module Net
|
|
914
907
|
:param, :content_id,
|
915
908
|
:description, :encoding, :size,
|
916
909
|
:md5, :disposition, :language,
|
910
|
+
:location,
|
917
911
|
:extension)
|
918
912
|
include BodyStructure
|
919
913
|
|
@@ -1049,6 +1043,7 @@ module Net
|
|
1049
1043
|
:description, :encoding, :size,
|
1050
1044
|
:lines,
|
1051
1045
|
:md5, :disposition, :language,
|
1046
|
+
:location,
|
1052
1047
|
:extension)
|
1053
1048
|
include BodyStructure
|
1054
1049
|
|
@@ -1094,6 +1089,7 @@ module Net
|
|
1094
1089
|
:description, :encoding, :size,
|
1095
1090
|
:envelope, :body, :lines,
|
1096
1091
|
:md5, :disposition, :language,
|
1092
|
+
:location,
|
1097
1093
|
:extension)
|
1098
1094
|
include BodyStructure
|
1099
1095
|
|
@@ -1126,36 +1122,41 @@ module Net
|
|
1126
1122
|
end
|
1127
1123
|
end
|
1128
1124
|
|
1129
|
-
#
|
1130
|
-
# BodyTypeAttachment represents a <tt>body-fld-dsp</tt> that is
|
1131
|
-
# incorrectly in a position where the IMAP4rev1 grammar expects a nested
|
1132
|
-
# +body+ structure.
|
1125
|
+
# BodyTypeAttachment is not used and will be removed in an upcoming release.
|
1133
1126
|
#
|
1134
|
-
#
|
1135
|
-
#
|
1136
|
-
#
|
1137
|
-
#
|
1138
|
-
#
|
1139
|
-
#
|
1140
|
-
#
|
1141
|
-
#
|
1142
|
-
#
|
1143
|
-
#
|
1144
|
-
#
|
1145
|
-
#
|
1146
|
-
#
|
1147
|
-
#
|
1148
|
-
#
|
1149
|
-
#
|
1150
|
-
#
|
1151
|
-
#
|
1152
|
-
#
|
1153
|
-
#
|
1154
|
-
#
|
1127
|
+
# === Bug Analysis
|
1128
|
+
#
|
1129
|
+
# \IMAP body structures are parenthesized lists and assign their fields
|
1130
|
+
# positionally, so missing fields change the intepretation of all
|
1131
|
+
# following fields. Additionally, different body types have a different
|
1132
|
+
# number of required fields, followed by optional "extension" fields.
|
1133
|
+
#
|
1134
|
+
# BodyTypeAttachment was previously returned when a "message/rfc822" part,
|
1135
|
+
# which should be sent as <tt>body-type-msg</tt> with ten required fields,
|
1136
|
+
# was actually sent as a <tt>body-type-basic</tt> with _seven_ required
|
1137
|
+
# fields.
|
1138
|
+
#
|
1139
|
+
# basic => type, subtype, param, id, desc, enc, octets, md5=nil, dsp=nil, lang=nil, loc=nil, *ext
|
1140
|
+
# msg => type, subtype, param, id, desc, enc, octets, envelope, body, lines, md5=nil, ...
|
1141
|
+
#
|
1142
|
+
# Normally, +envelope+ and +md5+ are incompatible, but Net::IMAP leniently
|
1143
|
+
# allowed buggy servers to send +NIL+ for +envelope+. As a result, when a
|
1144
|
+
# server sent a <tt>message/rfc822</tt> part with +NIL+ for +md5+ and a
|
1145
|
+
# non-<tt>NIL</tt> +dsp+, Net::IMAP mis-interpreted the
|
1146
|
+
# <tt>Content-Disposition</tt> as if it were a strange body type. In all
|
1147
|
+
# reported cases, the <tt>Content-Disposition</tt> was "attachment", so
|
1148
|
+
# BodyTypeAttachment was created as the workaround.
|
1149
|
+
#
|
1150
|
+
# === Current behavior
|
1151
|
+
#
|
1152
|
+
# When interpreted strictly, +envelope+ and +md5+ are incompatible. So the
|
1153
|
+
# current parsing algorithm peeks ahead after it has recieved the seventh
|
1154
|
+
# body field. If the next token is not the start of an +envelope+, we assume
|
1155
|
+
# the server has incorrectly sent us a <tt>body-type-basic</tt> and return
|
1156
|
+
# BodyTypeBasic. As a result, what was previously BodyTypeMessage#body =>
|
1157
|
+
# BodyTypeAttachment is now BodyTypeBasic#disposition => ContentDisposition.
|
1155
1158
|
#
|
1156
1159
|
class BodyTypeAttachment < Struct.new(:dsp_type, :_unused_, :param)
|
1157
|
-
include BodyStructure
|
1158
|
-
|
1159
1160
|
# *invalid for BodyTypeAttachment*
|
1160
1161
|
def media_type
|
1161
1162
|
warn(<<~WARN, uplevel: 1)
|
@@ -1190,11 +1191,14 @@ module Net
|
|
1190
1191
|
end
|
1191
1192
|
end
|
1192
1193
|
|
1194
|
+
deprecate_constant :BodyTypeAttachment
|
1195
|
+
|
1193
1196
|
# Net::IMAP::BodyTypeMultipart represents body structures of messages and
|
1194
1197
|
# message parts, when <tt>Content-Type</tt> is <tt>multipart/*</tt>.
|
1195
1198
|
class BodyTypeMultipart < Struct.new(:media_type, :subtype,
|
1196
1199
|
:parts,
|
1197
1200
|
:param, :disposition, :language,
|
1201
|
+
:location,
|
1198
1202
|
:extension)
|
1199
1203
|
include BodyStructure
|
1200
1204
|
|
@@ -1265,23 +1269,24 @@ module Net
|
|
1265
1269
|
end
|
1266
1270
|
end
|
1267
1271
|
|
1268
|
-
# ===
|
1272
|
+
# === Obsolete
|
1273
|
+
# BodyTypeExtension is not used and will be removed in an upcoming release.
|
1274
|
+
#
|
1269
1275
|
# >>>
|
1270
|
-
# BodyTypeExtension
|
1276
|
+
# BodyTypeExtension was (incorrectly) used for <tt>message/*</tt> parts
|
1271
1277
|
# (besides <tt>message/rfc822</tt>, which correctly uses BodyTypeMessage).
|
1272
1278
|
#
|
1273
|
-
#
|
1274
|
-
#
|
1275
|
-
# * BodyTypeBasic for any other <tt>message/*</tt>
|
1279
|
+
# Net::IMAP now (correctly) parses all message types (other than
|
1280
|
+
# <tt>message/rfc822</tt> or <tt>message/global</tt>) as BodyTypeBasic.
|
1276
1281
|
class BodyTypeExtension < Struct.new(:media_type, :subtype,
|
1277
1282
|
:params, :content_id,
|
1278
1283
|
:description, :encoding, :size)
|
1279
|
-
include BodyStructure
|
1280
|
-
|
1281
1284
|
def multipart?
|
1282
1285
|
return false
|
1283
1286
|
end
|
1284
1287
|
end
|
1285
1288
|
|
1289
|
+
deprecate_constant :BodyTypeExtension
|
1290
|
+
|
1286
1291
|
end
|
1287
1292
|
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP < Protocol
|
5
|
+
class ResponseParser
|
6
|
+
# basic utility methods for parsing.
|
7
|
+
#
|
8
|
+
# (internal API, subject to change)
|
9
|
+
module ParserUtils # :nodoc:
|
10
|
+
|
11
|
+
module Generator
|
12
|
+
|
13
|
+
LOOKAHEAD = "(@token ||= next_token)"
|
14
|
+
SHIFT_TOKEN = "(@token = nil)"
|
15
|
+
|
16
|
+
# we can skip lexer for single character matches, as a shortcut
|
17
|
+
def def_char_matchers(name, char, token)
|
18
|
+
match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name
|
19
|
+
char = char.dump
|
20
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
21
|
+
# frozen_string_literal: true
|
22
|
+
|
23
|
+
# force use of #next_token; no string peeking
|
24
|
+
def lookahead_#{name}?
|
25
|
+
#{LOOKAHEAD}&.symbol == #{token}
|
26
|
+
end
|
27
|
+
|
28
|
+
# use token or string peek
|
29
|
+
def peek_#{name}?
|
30
|
+
@token ? @token.symbol == #{token} : @str[@pos] == #{char}
|
31
|
+
end
|
32
|
+
|
33
|
+
# like accept(token_symbols); returns token or nil
|
34
|
+
def #{name}?
|
35
|
+
if @token&.symbol == #{token}
|
36
|
+
#{SHIFT_TOKEN}
|
37
|
+
#{char}
|
38
|
+
elsif !@token && @str[@pos] == #{char}
|
39
|
+
@pos += 1
|
40
|
+
#{char}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# like match(token_symbols); returns token or raises parse_error
|
45
|
+
def #{match_name}
|
46
|
+
if @token&.symbol == #{token}
|
47
|
+
#{SHIFT_TOKEN}
|
48
|
+
#{char}
|
49
|
+
elsif !@token && @str[@pos] == #{char}
|
50
|
+
@pos += 1
|
51
|
+
#{char}
|
52
|
+
else
|
53
|
+
parse_error("unexpected %s (expected %p)",
|
54
|
+
@token&.symbol || @str[@pos].inspect, #{char})
|
55
|
+
end
|
56
|
+
end
|
57
|
+
RUBY
|
58
|
+
end
|
59
|
+
|
60
|
+
# TODO: move coersion to the token.value method?
|
61
|
+
def def_token_matchers(name, *token_symbols, coerce: nil, send: nil)
|
62
|
+
match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name
|
63
|
+
|
64
|
+
if token_symbols.size == 1
|
65
|
+
token = token_symbols.first
|
66
|
+
matcher = "token&.symbol == %p" % [token]
|
67
|
+
desc = token
|
68
|
+
else
|
69
|
+
matcher = "%p.include? token&.symbol" % [token_symbols]
|
70
|
+
desc = token_symbols.join(" or ")
|
71
|
+
end
|
72
|
+
|
73
|
+
value = "(token.value)"
|
74
|
+
value = coerce.to_s + value if coerce
|
75
|
+
value = [value, send].join(".") if send
|
76
|
+
|
77
|
+
raise_parse_error = <<~RUBY
|
78
|
+
parse_error("unexpected %s (expected #{desc})", token&.symbol)
|
79
|
+
RUBY
|
80
|
+
|
81
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
82
|
+
# frozen_string_literal: true
|
83
|
+
|
84
|
+
# lookahead version of match, returning the value
|
85
|
+
def lookahead_#{name}!
|
86
|
+
token = #{LOOKAHEAD}
|
87
|
+
if #{matcher}
|
88
|
+
#{value}
|
89
|
+
else
|
90
|
+
#{raise_parse_error}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def #{name}?
|
95
|
+
token = #{LOOKAHEAD}
|
96
|
+
if #{matcher}
|
97
|
+
#{SHIFT_TOKEN}
|
98
|
+
#{value}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def #{match_name}
|
103
|
+
token = #{LOOKAHEAD}
|
104
|
+
if #{matcher}
|
105
|
+
#{SHIFT_TOKEN}
|
106
|
+
#{value}
|
107
|
+
else
|
108
|
+
#{raise_parse_error}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
RUBY
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# TODO: after checking the lookahead, use a regexp for remaining chars.
|
120
|
+
# That way a loop isn't needed.
|
121
|
+
def combine_adjacent(*tokens)
|
122
|
+
result = "".b
|
123
|
+
while token = accept(*tokens)
|
124
|
+
result << token.value
|
125
|
+
end
|
126
|
+
if result.empty?
|
127
|
+
parse_error('unexpected token %s (expected %s)',
|
128
|
+
lookahead.symbol, tokens.join(" or "))
|
129
|
+
end
|
130
|
+
result
|
131
|
+
end
|
132
|
+
|
133
|
+
def match(*args)
|
134
|
+
token = lookahead
|
135
|
+
unless args.include?(token.symbol)
|
136
|
+
parse_error('unexpected token %s (expected %s)',
|
137
|
+
token.symbol.id2name,
|
138
|
+
args.collect {|i| i.id2name}.join(" or "))
|
139
|
+
end
|
140
|
+
shift_token
|
141
|
+
token
|
142
|
+
end
|
143
|
+
|
144
|
+
# like match, but does not raise error on failure.
|
145
|
+
#
|
146
|
+
# returns and shifts token on successful match
|
147
|
+
# returns nil and leaves @token unshifted on no match
|
148
|
+
def accept(*args)
|
149
|
+
token = lookahead
|
150
|
+
if args.include?(token.symbol)
|
151
|
+
shift_token
|
152
|
+
token
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# To be used conditionally:
|
157
|
+
# assert_no_lookahead if Net::IMAP.debug
|
158
|
+
def assert_no_lookahead
|
159
|
+
@token.nil? or
|
160
|
+
parse_error("assertion failed: expected @token.nil?, actual %s: %p",
|
161
|
+
@token.symbol, @token.value)
|
162
|
+
end
|
163
|
+
|
164
|
+
# like accept, without consuming the token
|
165
|
+
def lookahead?(*symbols)
|
166
|
+
@token if symbols.include?((@token ||= next_token)&.symbol)
|
167
|
+
end
|
168
|
+
|
169
|
+
def lookahead
|
170
|
+
@token ||= next_token
|
171
|
+
end
|
172
|
+
|
173
|
+
def peek_str?(str)
|
174
|
+
assert_no_lookahead if Net::IMAP.debug
|
175
|
+
@str[@pos, str.length] == str
|
176
|
+
end
|
177
|
+
|
178
|
+
def peek_re(re)
|
179
|
+
assert_no_lookahead if Net::IMAP.debug
|
180
|
+
re.match(@str, @pos)
|
181
|
+
end
|
182
|
+
|
183
|
+
def accept_re(re)
|
184
|
+
assert_no_lookahead if Net::IMAP.debug
|
185
|
+
re.match(@str, @pos) and @pos = $~.end(0)
|
186
|
+
$~
|
187
|
+
end
|
188
|
+
|
189
|
+
def match_re(re, name)
|
190
|
+
assert_no_lookahead if Net::IMAP.debug
|
191
|
+
if re.match(@str, @pos)
|
192
|
+
@pos = $~.end(0)
|
193
|
+
$~
|
194
|
+
else
|
195
|
+
parse_error("invalid #{name}")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def shift_token
|
200
|
+
@token = nil
|
201
|
+
end
|
202
|
+
|
203
|
+
def parse_error(fmt, *args)
|
204
|
+
msg = format(fmt, *args)
|
205
|
+
if IMAP.debug
|
206
|
+
local_path = File.dirname(__dir__)
|
207
|
+
tok = @token ? "%s: %p" % [@token.symbol, @token.value] : "nil"
|
208
|
+
warn "%s %s: %s" % [self.class, __method__, msg]
|
209
|
+
warn " tokenized : %s" % [@str[...@pos].dump]
|
210
|
+
warn " remaining : %s" % [@str[@pos..].dump]
|
211
|
+
warn " @lex_state: %s" % [@lex_state]
|
212
|
+
warn " @pos : %d" % [@pos]
|
213
|
+
warn " @token : %s" % [tok]
|
214
|
+
caller_locations(1..20).each_with_index do |cloc, idx|
|
215
|
+
next unless cloc.path&.start_with?(local_path)
|
216
|
+
warn " caller[%2d]: %-30s (%s:%d)" % [
|
217
|
+
idx,
|
218
|
+
cloc.base_label,
|
219
|
+
File.basename(cloc.path, ".rb"),
|
220
|
+
cloc.lineno
|
221
|
+
]
|
222
|
+
end
|
223
|
+
end
|
224
|
+
raise ResponseParseError, msg
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|