summaryrefslogtreecommitdiff
path: root/lib/net/imap
diff options
context:
space:
mode:
Diffstat (limited to 'lib/net/imap')
-rw-r--r--lib/net/imap/authenticators.rb44
-rw-r--r--lib/net/imap/authenticators/cram_md5.rb49
-rw-r--r--lib/net/imap/authenticators/digest_md5.rb111
-rw-r--r--lib/net/imap/authenticators/login.rb43
-rw-r--r--lib/net/imap/authenticators/plain.rb41
-rw-r--r--lib/net/imap/command_data.rb301
-rw-r--r--lib/net/imap/data_encoding.rb47
-rw-r--r--lib/net/imap/flags.rb76
-rw-r--r--lib/net/imap/net-imap.gemspec37
-rw-r--r--lib/net/imap/response_data.rb527
-rw-r--r--lib/net/imap/response_parser.rb1530
11 files changed, 0 insertions, 2806 deletions
diff --git a/lib/net/imap/authenticators.rb b/lib/net/imap/authenticators.rb
deleted file mode 100644
index b5dded35a5..0000000000
--- a/lib/net/imap/authenticators.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-# Registry for SASL authenticators used by Net::IMAP.
-module Net::IMAP::Authenticators
-
- # Adds an authenticator for use with Net::IMAP#authenticate. +auth_type+ is the
- # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
- # supported by +authenticator+ (for instance, "+PLAIN+"). The +authenticator+
- # is an object which defines a +#process+ method to handle authentication with
- # the server. See Net::IMAP::PlainAuthenticator, Net::IMAP::LoginAuthenticator,
- # Net::IMAP::CramMD5Authenticator, and Net::IMAP::DigestMD5Authenticator for
- # examples.
- #
- # If +auth_type+ refers to an existing authenticator, it will be
- # replaced by the new one.
- def add_authenticator(auth_type, authenticator)
- authenticators[auth_type] = authenticator
- end
-
- # Builds an authenticator for Net::IMAP#authenticate. +args+ will be passed
- # directly to the chosen authenticator's +#initialize+.
- def authenticator(auth_type, *args)
- auth_type = auth_type.upcase
- unless authenticators.has_key?(auth_type)
- raise ArgumentError,
- format('unknown auth type - "%s"', auth_type)
- end
- authenticators[auth_type].new(*args)
- end
-
- private
-
- def authenticators
- @authenticators ||= {}
- end
-
-end
-
-Net::IMAP.extend Net::IMAP::Authenticators
-
-require_relative "authenticators/login"
-require_relative "authenticators/plain"
-require_relative "authenticators/cram_md5"
-require_relative "authenticators/digest_md5"
diff --git a/lib/net/imap/authenticators/cram_md5.rb b/lib/net/imap/authenticators/cram_md5.rb
deleted file mode 100644
index 0930c5ac34..0000000000
--- a/lib/net/imap/authenticators/cram_md5.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-require "digest/md5"
-
-# Authenticator for the "+CRAM-MD5+" SASL mechanism, specified in
-# RFC2195[https://tools.ietf.org/html/rfc2195]. See Net::IMAP#authenticate.
-#
-# == Deprecated
-#
-# +CRAM-MD5+ is obsolete and insecure. It is included for compatibility with
-# existing servers.
-# {draft-ietf-sasl-crammd5-to-historic}[https://tools.ietf.org/html/draft-ietf-sasl-crammd5-to-historic-00.html]
-# recommends using +SCRAM-*+ or +PLAIN+ protected by TLS instead.
-#
-# Additionally, RFC8314[https://tools.ietf.org/html/rfc8314] discourage the use
-# of cleartext and recommends TLS version 1.2 or greater be used for all
-# traffic. With TLS +CRAM-MD5+ is okay, but so is +PLAIN+
-class Net::IMAP::CramMD5Authenticator
- def process(challenge)
- digest = hmac_md5(challenge, @password)
- return @user + " " + digest
- end
-
- private
-
- def initialize(user, password)
- @user = user
- @password = password
- end
-
- def hmac_md5(text, key)
- if key.length > 64
- key = Digest::MD5.digest(key)
- end
-
- k_ipad = key + "\0" * (64 - key.length)
- k_opad = key + "\0" * (64 - key.length)
- for i in 0..63
- k_ipad[i] = (k_ipad[i].ord ^ 0x36).chr
- k_opad[i] = (k_opad[i].ord ^ 0x5c).chr
- end
-
- digest = Digest::MD5.digest(k_ipad + text)
-
- return Digest::MD5.hexdigest(k_opad + digest)
- end
-
- Net::IMAP.add_authenticator "PLAIN", self
-end
diff --git a/lib/net/imap/authenticators/digest_md5.rb b/lib/net/imap/authenticators/digest_md5.rb
deleted file mode 100644
index 19e1a460c8..0000000000
--- a/lib/net/imap/authenticators/digest_md5.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-# frozen_string_literal: true
-
-require "digest/md5"
-require "strscan"
-
-# Net::IMAP authenticator for the "`DIGEST-MD5`" SASL mechanism type, specified
-# in RFC2831(https://tools.ietf.org/html/rfc2831). See Net::IMAP#authenticate.
-#
-# == Deprecated
-#
-# "+DIGEST-MD5+" has been deprecated by
-# {RFC6331}[https://tools.ietf.org/html/rfc6331] and should not be relied on for
-# security. It is included for compatibility with existing servers.
-class Net::IMAP::DigestMD5Authenticator
- def process(challenge)
- case @stage
- when STAGE_ONE
- @stage = STAGE_TWO
- sparams = {}
- c = StringScanner.new(challenge)
- while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]+|\\.)*"|[^,]+)\s*/)
- k, v = c[1], c[2]
- if v =~ /^"(.*)"$/
- v = $1
- if v =~ /,/
- v = v.split(',')
- end
- end
- sparams[k] = v
- end
-
- raise DataFormatError, "Bad Challenge: '#{challenge}'" unless c.rest.size == 0
- raise Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth")
-
- response = {
- :nonce => sparams['nonce'],
- :username => @user,
- :realm => sparams['realm'],
- :cnonce => Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand, Process.pid.to_s]),
- :'digest-uri' => 'imap/' + sparams['realm'],
- :qop => 'auth',
- :maxbuf => 65535,
- :nc => "%08d" % nc(sparams['nonce']),
- :charset => sparams['charset'],
- }
-
- response[:authzid] = @authname unless @authname.nil?
-
- # now, the real thing
- a0 = Digest::MD5.digest( [ response.values_at(:username, :realm), @password ].join(':') )
-
- a1 = [ a0, response.values_at(:nonce,:cnonce) ].join(':')
- a1 << ':' + response[:authzid] unless response[:authzid].nil?
-
- a2 = "AUTHENTICATE:" + response[:'digest-uri']
- a2 << ":00000000000000000000000000000000" if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/
-
- response[:response] = Digest::MD5.hexdigest(
- [
- Digest::MD5.hexdigest(a1),
- response.values_at(:nonce, :nc, :cnonce, :qop),
- Digest::MD5.hexdigest(a2)
- ].join(':')
- )
-
- return response.keys.map {|key| qdval(key.to_s, response[key]) }.join(',')
- when STAGE_TWO
- @stage = nil
- # if at the second stage, return an empty string
- if challenge =~ /rspauth=/
- return ''
- else
- raise ResponseParseError, challenge
- end
- else
- raise ResponseParseError, challenge
- end
- end
-
- def initialize(user, password, authname = nil)
- @user, @password, @authname = user, password, authname
- @nc, @stage = {}, STAGE_ONE
- end
-
- private
-
- STAGE_ONE = :stage_one
- STAGE_TWO = :stage_two
-
- def nc(nonce)
- if @nc.has_key? nonce
- @nc[nonce] = @nc[nonce] + 1
- else
- @nc[nonce] = 1
- end
- return @nc[nonce]
- end
-
- # some responses need quoting
- def qdval(k, v)
- return if k.nil? or v.nil?
- if %w"username authzid realm nonce cnonce digest-uri qop".include? k
- v.gsub!(/([\\"])/, "\\\1")
- return '%s="%s"' % [k, v]
- else
- return '%s=%s' % [k, v]
- end
- end
-
- Net::IMAP.add_authenticator "DIGEST-MD5", self
-end
diff --git a/lib/net/imap/authenticators/login.rb b/lib/net/imap/authenticators/login.rb
deleted file mode 100644
index e1afebc323..0000000000
--- a/lib/net/imap/authenticators/login.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-# Authenticator for the "+LOGIN+" SASL mechanism. See Net::IMAP#authenticate.
-#
-# +LOGIN+ authentication sends the password in cleartext.
-# RFC3501[https://tools.ietf.org/html/rfc3501] encourages servers to disable
-# cleartext authentication until after TLS has been negotiated.
-# RFC8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2 or
-# greater be used for all traffic, and deprecate cleartext access ASAP. +LOGIN+
-# can be secured by TLS encryption.
-#
-# == Deprecated
-#
-# The {SASL mechanisms
-# registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
-# marks "LOGIN" as obsoleted in favor of "PLAIN". It is included here for
-# compatibility with existing servers. See
-# {draft-murchison-sasl-login}[https://www.iana.org/go/draft-murchison-sasl-login]
-# for both specification and deprecation.
-class Net::IMAP::LoginAuthenticator
- def process(data)
- case @state
- when STATE_USER
- @state = STATE_PASSWORD
- return @user
- when STATE_PASSWORD
- return @password
- end
- end
-
- private
-
- STATE_USER = :USER
- STATE_PASSWORD = :PASSWORD
-
- def initialize(user, password)
- @user = user
- @password = password
- @state = STATE_USER
- end
-
- Net::IMAP.add_authenticator "LOGIN", self
-end
diff --git a/lib/net/imap/authenticators/plain.rb b/lib/net/imap/authenticators/plain.rb
deleted file mode 100644
index a9d46c920e..0000000000
--- a/lib/net/imap/authenticators/plain.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-# Authenticator for the "+PLAIN+" SASL mechanism, specified in
-# RFC4616[https://tools.ietf.org/html/rfc4616]. See Net::IMAP#authenticate.
-#
-# +PLAIN+ authentication sends the password in cleartext.
-# RFC3501[https://tools.ietf.org/html/rfc3501] encourages servers to disable
-# cleartext authentication until after TLS has been negotiated.
-# RFC8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2 or
-# greater be used for all traffic, and deprecate cleartext access ASAP. +PLAIN+
-# can be secured by TLS encryption.
-class Net::IMAP::PlainAuthenticator
-
- def process(data)
- return "#@authzid\0#@username\0#@password"
- end
-
- # :nodoc:
- NULL = -"\0".b
-
- private
-
- # +username+ is the authentication identity, the identity whose +password+ is
- # used. +username+ is referred to as +authcid+ by
- # RFC4616[https://tools.ietf.org/html/rfc4616].
- #
- # +authzid+ is the authorization identity (identity to act as). It can
- # usually be left blank. When +authzid+ is left blank (nil or empty string)
- # the server will derive an identity from the credentials and use that as the
- # authorization identity.
- def initialize(username, password, authzid: nil)
- raise ArgumentError, "username contains NULL" if username&.include?(NULL)
- raise ArgumentError, "password contains NULL" if password&.include?(NULL)
- raise ArgumentError, "authzid contains NULL" if authzid&.include?(NULL)
- @username = username
- @password = password
- @authzid = authzid
- end
-
- Net::IMAP.add_authenticator "PLAIN", self
-end
diff --git a/lib/net/imap/command_data.rb b/lib/net/imap/command_data.rb
deleted file mode 100644
index d52390c933..0000000000
--- a/lib/net/imap/command_data.rb
+++ /dev/null
@@ -1,301 +0,0 @@
-# frozen_string_literal: true
-
-module Net
- class IMAP < Protocol
-
- private
-
- def validate_data(data)
- case data
- when nil
- when String
- when Integer
- NumValidator.ensure_number(data)
- when Array
- if data[0] == 'CHANGEDSINCE'
- NumValidator.ensure_mod_sequence_value(data[1])
- else
- data.each do |i|
- validate_data(i)
- end
- end
- when Time
- when Symbol
- else
- data.validate
- end
- end
-
- def send_data(data, tag = nil)
- case data
- when nil
- put_string("NIL")
- when String
- send_string_data(data, tag)
- when Integer
- send_number_data(data)
- when Array
- send_list_data(data, tag)
- when Time
- send_time_data(data)
- when Symbol
- send_symbol_data(data)
- else
- data.send_data(self, tag)
- end
- end
-
- def send_string_data(str, tag = nil)
- case str
- when ""
- put_string('""')
- when /[\x80-\xff\r\n]/n
- # literal
- send_literal(str, tag)
- when /[(){ \x00-\x1f\x7f%*"\\]/n
- # quoted string
- send_quoted_string(str)
- else
- put_string(str)
- end
- end
-
- def send_quoted_string(str)
- put_string('"' + str.gsub(/["\\]/n, "\\\\\\&") + '"')
- end
-
- def send_literal(str, tag = nil)
- synchronize do
- put_string("{" + str.bytesize.to_s + "}" + CRLF)
- @continued_command_tag = tag
- @continuation_request_exception = nil
- begin
- @continuation_request_arrival.wait
- e = @continuation_request_exception || @exception
- raise e if e
- put_string(str)
- ensure
- @continued_command_tag = nil
- @continuation_request_exception = nil
- end
- end
- end
-
- def send_number_data(num)
- put_string(num.to_s)
- end
-
- def send_list_data(list, tag = nil)
- put_string("(")
- first = true
- list.each do |i|
- if first
- first = false
- else
- put_string(" ")
- end
- send_data(i, tag)
- end
- put_string(")")
- end
-
- DATE_MONTH = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
-
- def send_time_data(time)
- t = time.dup.gmtime
- s = format('"%2d-%3s-%4d %02d:%02d:%02d +0000"',
- t.day, DATE_MONTH[t.month - 1], t.year,
- t.hour, t.min, t.sec)
- put_string(s)
- end
-
- def send_symbol_data(symbol)
- put_string("\\" + symbol.to_s)
- end
-
- class RawData # :nodoc:
- def send_data(imap, tag)
- imap.__send__(:put_string, @data)
- end
-
- def validate
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class Atom # :nodoc:
- def send_data(imap, tag)
- imap.__send__(:put_string, @data)
- end
-
- def validate
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class QuotedString # :nodoc:
- def send_data(imap, tag)
- imap.__send__(:send_quoted_string, @data)
- end
-
- def validate
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class Literal # :nodoc:
- def send_data(imap, tag)
- imap.__send__(:send_literal, @data, tag)
- end
-
- def validate
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class MessageSet # :nodoc:
- def send_data(imap, tag)
- imap.__send__(:put_string, format_internal(@data))
- end
-
- def validate
- validate_internal(@data)
- end
-
- private
-
- def initialize(data)
- @data = data
- end
-
- def format_internal(data)
- case data
- when "*"
- return data
- when Integer
- if data == -1
- return "*"
- else
- return data.to_s
- end
- when Range
- return format_internal(data.first) +
- ":" + format_internal(data.last)
- when Array
- return data.collect {|i| format_internal(i)}.join(",")
- when ThreadMember
- return data.seqno.to_s +
- ":" + data.children.collect {|i| format_internal(i).join(",")}
- end
- end
-
- def validate_internal(data)
- case data
- when "*"
- when Integer
- NumValidator.ensure_nz_number(data)
- when Range
- when Array
- data.each do |i|
- validate_internal(i)
- end
- when ThreadMember
- data.children.each do |i|
- validate_internal(i)
- end
- else
- raise DataFormatError, data.inspect
- end
- end
- end
-
- class ClientID # :nodoc:
-
- def send_data(imap, tag)
- imap.__send__(:send_data, format_internal(@data), tag)
- end
-
- def validate
- validate_internal(@data)
- end
-
- private
-
- def initialize(data)
- @data = data
- end
-
- def validate_internal(client_id)
- client_id.to_h.each do |k,v|
- unless StringFormatter.valid_string?(k)
- raise DataFormatError, client_id.inspect
- end
- end
- rescue NoMethodError, TypeError # to_h failed
- raise DataFormatError, client_id.inspect
- end
-
- def format_internal(client_id)
- return nil if client_id.nil?
- client_id.to_h.flat_map {|k,v|
- [StringFormatter.string(k), StringFormatter.nstring(v)]
- }
- end
-
- end
-
- module StringFormatter
-
- LITERAL_REGEX = /[\x80-\xff\r\n]/n
-
- module_function
-
- # Allows symbols in addition to strings
- def valid_string?(str)
- str.is_a?(Symbol) || str.respond_to?(:to_str)
- end
-
- # Allows nil, symbols, and strings
- def valid_nstring?(str)
- str.nil? || valid_string?(str)
- end
-
- # coerces using +to_s+
- def string(str)
- str = str.to_s
- if str =~ LITERAL_REGEX
- Literal.new(str)
- else
- QuotedString.new(str)
- end
- end
-
- # coerces non-nil using +to_s+
- def nstring(str)
- str.nil? ? nil : string(str)
- end
-
- end
-
- end
-end
diff --git a/lib/net/imap/data_encoding.rb b/lib/net/imap/data_encoding.rb
deleted file mode 100644
index d8449f582c..0000000000
--- a/lib/net/imap/data_encoding.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-module Net
- class IMAP < Protocol
-
- # Decode a string from modified UTF-7 format to UTF-8.
- #
- # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a
- # slightly modified version of this to encode mailbox names
- # containing non-ASCII characters; see [IMAP] section 5.1.3.
- #
- # Net::IMAP does _not_ automatically encode and decode
- # mailbox names to and from UTF-7.
- def self.decode_utf7(s)
- return s.gsub(/&([^-]+)?-/n) {
- if $1
- ($1.tr(",", "/") + "===").unpack1("m").encode(Encoding::UTF_8, Encoding::UTF_16BE)
- else
- "&"
- end
- }
- end
-
- # Encode a string from UTF-8 format to modified UTF-7.
- def self.encode_utf7(s)
- return s.gsub(/(&)|[^\x20-\x7e]+/) {
- if $1
- "&-"
- else
- base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
- "&" + base64.delete("=").tr("/", ",") + "-"
- end
- }.force_encoding("ASCII-8BIT")
- end
-
- # Formats +time+ as an IMAP-style date.
- def self.format_date(time)
- return time.strftime('%d-%b-%Y')
- end
-
- # Formats +time+ as an IMAP-style date-time.
- def self.format_datetime(time)
- return time.strftime('%d-%b-%Y %H:%M %z')
- end
-
- end
-end
diff --git a/lib/net/imap/flags.rb b/lib/net/imap/flags.rb
deleted file mode 100644
index b3d98c09b2..0000000000
--- a/lib/net/imap/flags.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-
-module Net
- class IMAP < Protocol
-
- # :category: Message Flags
- #
- # Flag indicating a message has been seen.
- SEEN = :Seen
-
- # :category: Message Flags
- #
- # Flag indicating a message has been answered.
- ANSWERED = :Answered
-
- # :category: Message Flags
- #
- # Flag indicating a message has been flagged for special or urgent
- # attention.
- FLAGGED = :Flagged
-
- # :category: Message Flags
- #
- # Flag indicating a message has been marked for deletion. This
- # will occur when the mailbox is closed or expunged.
- DELETED = :Deleted
-
- # :category: Message Flags
- #
- # Flag indicating a message is only a draft or work-in-progress version.
- DRAFT = :Draft
-
- # :category: Message Flags
- #
- # Flag indicating that the message is "recent," meaning that this
- # session is the first session in which the client has been notified
- # of this message.
- RECENT = :Recent
-
- # :category: Mailbox Flags
- #
- # Flag indicating that a mailbox context name cannot contain
- # children.
- NOINFERIORS = :Noinferiors
-
- # :category: Mailbox Flags
- #
- # Flag indicating that a mailbox is not selected.
- NOSELECT = :Noselect
-
- # :category: Mailbox Flags
- #
- # Flag indicating that a mailbox has been marked "interesting" by
- # the server; this commonly indicates that the mailbox contains
- # new messages.
- MARKED = :Marked
-
- # :category: Mailbox Flags
- #
- # Flag indicating that the mailbox does not contains new messages.
- UNMARKED = :Unmarked
-
- @@max_flag_count = 10000
-
- # Returns the max number of flags interned to symbols.
- def self.max_flag_count
- return @@max_flag_count
- end
-
- # Sets the max number of flags interned to symbols.
- def self.max_flag_count=(count)
- @@max_flag_count = count
- end
-
- end
-end
diff --git a/lib/net/imap/net-imap.gemspec b/lib/net/imap/net-imap.gemspec
deleted file mode 100644
index 9a6e0acf2d..0000000000
--- a/lib/net/imap/net-imap.gemspec
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-name = File.basename(__FILE__, ".gemspec")
-version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir|
- break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
- /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
- end rescue nil
-end
-
-Gem::Specification.new do |spec|
- spec.name = name
- spec.version = version
- spec.authors = ["Shugo Maeda"]
- spec.email = ["shugo@ruby-lang.org"]
-
- spec.summary = %q{Ruby client api for Internet Message Access Protocol}
- spec.description = %q{Ruby client api for Internet Message Access Protocol}
- spec.homepage = "https://github.com/ruby/net-imap"
- spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
- spec.licenses = ["Ruby", "BSD-2-Clause"]
-
- spec.metadata["homepage_uri"] = spec.homepage
- spec.metadata["source_code_uri"] = spec.homepage
-
- # Specify which files should be added to the gem when it is released.
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
- `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
- end
- spec.bindir = "exe"
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
- spec.require_paths = ["lib"]
-
- spec.add_dependency "net-protocol"
- spec.add_dependency "digest"
- spec.add_dependency "strscan"
-end
diff --git a/lib/net/imap/response_data.rb b/lib/net/imap/response_data.rb
deleted file mode 100644
index f0175286fe..0000000000
--- a/lib/net/imap/response_data.rb
+++ /dev/null
@@ -1,527 +0,0 @@
-# frozen_string_literal: true
-
-module Net
- class IMAP < Protocol
-
- # Net::IMAP::ContinuationRequest represents command continuation requests.
- #
- # The command continuation request response is indicated by a "+" token
- # instead of a tag. This form of response indicates that the server is
- # ready to accept the continuation of a command from the client. The
- # remainder of this response is a line of text.
- #
- # continue_req ::= "+" SPACE (resp_text / base64)
- #
- # ==== Fields:
- #
- # data:: Returns the data (Net::IMAP::ResponseText).
- #
- # raw_data:: Returns the raw data string.
- class ContinuationRequest < Struct.new(:data, :raw_data)
- end
-
- # Net::IMAP::UntaggedResponse represents untagged responses.
- #
- # Data transmitted by the server to the client and status responses
- # that do not indicate command completion are prefixed with the token
- # "*", and are called untagged responses.
- #
- # response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye /
- # mailbox_data / message_data / capability_data)
- #
- # ==== Fields:
- #
- # name:: Returns the name, such as "FLAGS", "LIST", or "FETCH".
- #
- # data:: Returns the data such as an array of flag symbols,
- # a ((<Net::IMAP::MailboxList>)) object.
- #
- # raw_data:: Returns the raw data string.
- class UntaggedResponse < Struct.new(:name, :data, :raw_data)
- end
-
- # Net::IMAP::IgnoredResponse represents intentionally ignored responses.
- #
- # This includes untagged response "NOOP" sent by eg. Zimbra to avoid some
- # clients to close the connection.
- #
- # It matches no IMAP standard.
- #
- # ==== Fields:
- #
- # raw_data:: Returns the raw data string.
- class IgnoredResponse < Struct.new(:raw_data)
- end
-
- # Net::IMAP::TaggedResponse represents tagged responses.
- #
- # The server completion result response indicates the success or
- # failure of the operation. It is tagged with the same tag as the
- # client command which began the operation.
- #
- # response_tagged ::= tag SPACE resp_cond_state CRLF
- #
- # tag ::= 1*<any ATOM_CHAR except "+">
- #
- # resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
- #
- # ==== Fields:
- #
- # tag:: Returns the tag.
- #
- # name:: Returns the name, one of "OK", "NO", or "BAD".
- #
- # data:: Returns the data. See ((<Net::IMAP::ResponseText>)).
- #
- # raw_data:: Returns the raw data string.
- #
- class TaggedResponse < Struct.new(:tag, :name, :data, :raw_data)
- end
-
- # Net::IMAP::ResponseText represents texts of responses.
- # The text may be prefixed by the response code.
- #
- # resp_text ::= ["[" resp-text-code "]" SP] text
- #
- # ==== Fields:
- #
- # code:: Returns the response code. See ((<Net::IMAP::ResponseCode>)).
- #
- # text:: Returns the text.
- #
- class ResponseText < Struct.new(:code, :text)
- end
-
- # Net::IMAP::ResponseCode represents response codes.
- #
- # resp_text_code ::= "ALERT" /
- # "BADCHARSET" [SP "(" astring *(SP astring) ")" ] /
- # capability_data / "PARSE" /
- # "PERMANENTFLAGS" SP "("
- # [flag_perm *(SP flag_perm)] ")" /
- # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
- # "UIDNEXT" SP nz_number / "UIDVALIDITY" SP nz_number /
- # "UNSEEN" SP nz_number /
- # atom [SP 1*<any TEXT-CHAR except "]">]
- #
- # ==== Fields:
- #
- # name:: Returns the name, such as "ALERT", "PERMANENTFLAGS", or "UIDVALIDITY".
- #
- # data:: Returns the data, if it exists.
- #
- class ResponseCode < Struct.new(:name, :data)
- end
-
- # Net::IMAP::MailboxList represents contents of the LIST response.
- #
- # mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
- # "\Noselect" / "\Unmarked" / flag_extension) ")"
- # SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
- #
- # ==== Fields:
- #
- # attr:: Returns the name attributes. Each name attribute is a symbol
- # capitalized by String#capitalize, such as :Noselect (not :NoSelect).
- #
- # delim:: Returns the hierarchy delimiter.
- #
- # name:: Returns the mailbox name.
- #
- class MailboxList < Struct.new(:attr, :delim, :name)
- end
-
- # Net::IMAP::MailboxQuota represents contents of GETQUOTA response.
- # This object can also be a response to GETQUOTAROOT. In the syntax
- # specification below, the delimiter used with the "#" construct is a
- # single space (SPACE).
- #
- # quota_list ::= "(" #quota_resource ")"
- #
- # quota_resource ::= atom SPACE number SPACE number
- #
- # quota_response ::= "QUOTA" SPACE astring SPACE quota_list
- #
- # ==== Fields:
- #
- # mailbox:: The mailbox with the associated quota.
- #
- # usage:: Current storage usage of the mailbox.
- #
- # quota:: Quota limit imposed on the mailbox.
- #
- class MailboxQuota < Struct.new(:mailbox, :usage, :quota)
- end
-
- # Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT
- # response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.)
- #
- # quotaroot_response ::= "QUOTAROOT" SPACE astring *(SPACE astring)
- #
- # ==== Fields:
- #
- # mailbox:: The mailbox with the associated quota.
- #
- # quotaroots:: Zero or more quotaroots that affect the quota on the
- # specified mailbox.
- #
- class MailboxQuotaRoot < Struct.new(:mailbox, :quotaroots)
- end
-
- # Net::IMAP::MailboxACLItem represents the response from GETACL.
- #
- # acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE rights)
- #
- # identifier ::= astring
- #
- # rights ::= astring
- #
- # ==== Fields:
- #
- # user:: Login name that has certain rights to the mailbox
- # that was specified with the getacl command.
- #
- # rights:: The access rights the indicated user has to the
- # mailbox.
- #
- class MailboxACLItem < Struct.new(:user, :rights, :mailbox)
- end
-
- # Net::IMAP::Namespace represents a single [RFC-2342] namespace.
- #
- # Namespace = nil / "(" 1*( "(" string SP (<"> QUOTED_CHAR <"> /
- # nil) *(Namespace_Response_Extension) ")" ) ")"
- #
- # Namespace_Response_Extension = SP string SP "(" string *(SP string)
- # ")"
- #
- # ==== Fields:
- #
- # prefix:: Returns the namespace prefix string.
- # delim:: Returns nil or the hierarchy delimiter character.
- # extensions:: Returns a hash of extension names to extension flag arrays.
- #
- class Namespace < Struct.new(:prefix, :delim, :extensions)
- end
-
- # Net::IMAP::Namespaces represents the response from [RFC-2342] NAMESPACE.
- #
- # Namespace_Response = "*" SP "NAMESPACE" SP Namespace SP Namespace SP
- # Namespace
- #
- # ; The first Namespace is the Personal Namespace(s)
- # ; The second Namespace is the Other Users' Namespace(s)
- # ; The third Namespace is the Shared Namespace(s)
- #
- # ==== Fields:
- #
- # personal:: Returns an array of Personal Net::IMAP::Namespace objects.
- # other:: Returns an array of Other Users' Net::IMAP::Namespace objects.
- # shared:: Returns an array of Shared Net::IMAP::Namespace objects.
- #
- class Namespaces < Struct.new(:personal, :other, :shared)
- end
-
- # Net::IMAP::StatusData represents the contents of the STATUS response.
- #
- # ==== Fields:
- #
- # mailbox:: Returns the mailbox name.
- #
- # attr:: Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT",
- # "UIDVALIDITY", "UNSEEN". Each value is a number.
- #
- class StatusData < Struct.new(:mailbox, :attr)
- end
-
- # Net::IMAP::FetchData represents the contents of the FETCH response.
- #
- # ==== Fields:
- #
- # seqno:: Returns the message sequence number.
- # (Note: not the unique identifier, even for the UID command response.)
- #
- # attr:: Returns a hash. Each key is a data item name, and each value is
- # its value.
- #
- # The current data items are:
- #
- # [BODY]
- # A form of BODYSTRUCTURE without extension data.
- # [BODY[<section>]<<origin_octet>>]
- # A string expressing the body contents of the specified section.
- # [BODYSTRUCTURE]
- # An object that describes the [MIME-IMB] body structure of a message.
- # See Net::IMAP::BodyTypeBasic, Net::IMAP::BodyTypeText,
- # Net::IMAP::BodyTypeMessage, Net::IMAP::BodyTypeMultipart.
- # [ENVELOPE]
- # A Net::IMAP::Envelope object that describes the envelope
- # structure of a message.
- # [FLAGS]
- # A array of flag symbols that are set for this message. Flag symbols
- # are capitalized by String#capitalize.
- # [INTERNALDATE]
- # A string representing the internal date of the message.
- # [RFC822]
- # Equivalent to +BODY[]+.
- # [RFC822.HEADER]
- # Equivalent to +BODY.PEEK[HEADER]+.
- # [RFC822.SIZE]
- # A number expressing the [RFC-822] size of the message.
- # [RFC822.TEXT]
- # Equivalent to +BODY[TEXT]+.
- # [UID]
- # A number expressing the unique identifier of the message.
- #
- class FetchData < Struct.new(:seqno, :attr)
- end
-
- # Net::IMAP::Envelope represents envelope structures of messages.
- #
- # ==== Fields:
- #
- # date:: Returns a string that represents the date.
- #
- # subject:: Returns a string that represents the subject.
- #
- # from:: Returns an array of Net::IMAP::Address that represents the from.
- #
- # sender:: Returns an array of Net::IMAP::Address that represents the sender.
- #
- # reply_to:: Returns an array of Net::IMAP::Address that represents the reply-to.
- #
- # to:: Returns an array of Net::IMAP::Address that represents the to.
- #
- # cc:: Returns an array of Net::IMAP::Address that represents the cc.
- #
- # bcc:: Returns an array of Net::IMAP::Address that represents the bcc.
- #
- # in_reply_to:: Returns a string that represents the in-reply-to.
- #
- # message_id:: Returns a string that represents the message-id.
- #
- class Envelope < Struct.new(:date, :subject, :from, :sender, :reply_to,
- :to, :cc, :bcc, :in_reply_to, :message_id)
- end
-
- #
- # Net::IMAP::Address represents electronic mail addresses.
- #
- # ==== Fields:
- #
- # name:: Returns the phrase from [RFC-822] mailbox.
- #
- # route:: Returns the route from [RFC-822] route-addr.
- #
- # mailbox:: nil indicates end of [RFC-822] group.
- # If non-nil and host is nil, returns [RFC-822] group name.
- # Otherwise, returns [RFC-822] local-part.
- #
- # host:: nil indicates [RFC-822] group syntax.
- # Otherwise, returns [RFC-822] domain name.
- #
- class Address < Struct.new(:name, :route, :mailbox, :host)
- end
-
- #
- # Net::IMAP::ContentDisposition represents Content-Disposition fields.
- #
- # ==== Fields:
- #
- # dsp_type:: Returns the disposition type.
- #
- # param:: Returns a hash that represents parameters of the Content-Disposition
- # field.
- #
- class ContentDisposition < Struct.new(:dsp_type, :param)
- end
-
- # Net::IMAP::ThreadMember represents a thread-node returned
- # by Net::IMAP#thread.
- #
- # ==== Fields:
- #
- # seqno:: The sequence number of this message.
- #
- # children:: An array of Net::IMAP::ThreadMember objects for mail
- # items that are children of this in the thread.
- #
- class ThreadMember < Struct.new(:seqno, :children)
- end
-
- # Net::IMAP::BodyTypeBasic represents basic body structures of messages.
- #
- # ==== Fields:
- #
- # media_type:: Returns the content media type name as defined in [MIME-IMB].
- #
- # subtype:: Returns the content subtype name as defined in [MIME-IMB].
- #
- # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
- #
- # content_id:: Returns a string giving the content id as defined in [MIME-IMB].
- #
- # description:: Returns a string giving the content description as defined in
- # [MIME-IMB].
- #
- # encoding:: Returns a string giving the content transfer encoding as defined in
- # [MIME-IMB].
- #
- # size:: Returns a number giving the size of the body in octets.
- #
- # md5:: Returns a string giving the body MD5 value as defined in [MD5].
- #
- # disposition:: Returns a Net::IMAP::ContentDisposition object giving
- # the content disposition.
- #
- # language:: Returns a string or an array of strings giving the body
- # language value as defined in [LANGUAGE-TAGS].
- #
- # extension:: Returns extension data.
- #
- # multipart?:: Returns false.
- #
- class BodyTypeBasic < Struct.new(:media_type, :subtype,
- :param, :content_id,
- :description, :encoding, :size,
- :md5, :disposition, :language,
- :extension)
- def multipart?
- return false
- end
-
- # Obsolete: use +subtype+ instead. Calling this will
- # generate a warning message to +stderr+, then return
- # the value of +subtype+.
- def media_subtype
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
- return subtype
- end
- end
-
- # Net::IMAP::BodyTypeText represents TEXT body structures of messages.
- #
- # ==== Fields:
- #
- # lines:: Returns the size of the body in text lines.
- #
- # And Net::IMAP::BodyTypeText has all fields of Net::IMAP::BodyTypeBasic.
- #
- class BodyTypeText < Struct.new(:media_type, :subtype,
- :param, :content_id,
- :description, :encoding, :size,
- :lines,
- :md5, :disposition, :language,
- :extension)
- def multipart?
- return false
- end
-
- # Obsolete: use +subtype+ instead. Calling this will
- # generate a warning message to +stderr+, then return
- # the value of +subtype+.
- def media_subtype
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
- return subtype
- end
- end
-
- # Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages.
- #
- # ==== Fields:
- #
- # envelope:: Returns a Net::IMAP::Envelope giving the envelope structure.
- #
- # body:: Returns an object giving the body structure.
- #
- # And Net::IMAP::BodyTypeMessage has all methods of Net::IMAP::BodyTypeText.
- #
- class BodyTypeMessage < Struct.new(:media_type, :subtype,
- :param, :content_id,
- :description, :encoding, :size,
- :envelope, :body, :lines,
- :md5, :disposition, :language,
- :extension)
- def multipart?
- return false
- end
-
- # Obsolete: use +subtype+ instead. Calling this will
- # generate a warning message to +stderr+, then return
- # the value of +subtype+.
- def media_subtype
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
- return subtype
- end
- end
-
- # Net::IMAP::BodyTypeAttachment represents attachment body structures
- # of messages.
- #
- # ==== Fields:
- #
- # media_type:: Returns the content media type name.
- #
- # subtype:: Returns +nil+.
- #
- # param:: Returns a hash that represents parameters.
- #
- # multipart?:: Returns false.
- #
- class BodyTypeAttachment < Struct.new(:media_type, :subtype,
- :param)
- def multipart?
- return false
- end
- end
-
- # Net::IMAP::BodyTypeMultipart represents multipart body structures
- # of messages.
- #
- # ==== Fields:
- #
- # media_type:: Returns the content media type name as defined in [MIME-IMB].
- #
- # subtype:: Returns the content subtype name as defined in [MIME-IMB].
- #
- # parts:: Returns multiple parts.
- #
- # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
- #
- # disposition:: Returns a Net::IMAP::ContentDisposition object giving
- # the content disposition.
- #
- # language:: Returns a string or an array of strings giving the body
- # language value as defined in [LANGUAGE-TAGS].
- #
- # extension:: Returns extension data.
- #
- # multipart?:: Returns true.
- #
- class BodyTypeMultipart < Struct.new(:media_type, :subtype,
- :parts,
- :param, :disposition, :language,
- :extension)
- def multipart?
- return true
- end
-
- # Obsolete: use +subtype+ instead. Calling this will
- # generate a warning message to +stderr+, then return
- # the value of +subtype+.
- def media_subtype
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
- return subtype
- end
- end
-
- class BodyTypeExtension < Struct.new(:media_type, :subtype,
- :params, :content_id,
- :description, :encoding, :size)
- def multipart?
- return false
- end
- end
-
- end
-end
diff --git a/lib/net/imap/response_parser.rb b/lib/net/imap/response_parser.rb
deleted file mode 100644
index 1e051327c6..0000000000
--- a/lib/net/imap/response_parser.rb
+++ /dev/null
@@ -1,1530 +0,0 @@
-# frozen_string_literal: true
-
-module Net
- class IMAP < Protocol
-
- class ResponseParser # :nodoc:
- def initialize
- @str = nil
- @pos = nil
- @lex_state = nil
- @token = nil
- @flag_symbols = {}
- end
-
- def parse(str)
- @str = str
- @pos = 0
- @lex_state = EXPR_BEG
- @token = nil
- return response
- end
-
- private
-
- EXPR_BEG = :EXPR_BEG
- EXPR_DATA = :EXPR_DATA
- EXPR_TEXT = :EXPR_TEXT
- EXPR_RTEXT = :EXPR_RTEXT
- EXPR_CTEXT = :EXPR_CTEXT
-
- T_SPACE = :SPACE
- T_NIL = :NIL
- T_NUMBER = :NUMBER
- T_ATOM = :ATOM
- T_QUOTED = :QUOTED
- T_LPAR = :LPAR
- T_RPAR = :RPAR
- T_BSLASH = :BSLASH
- T_STAR = :STAR
- T_LBRA = :LBRA
- T_RBRA = :RBRA
- T_LITERAL = :LITERAL
- T_PLUS = :PLUS
- T_PERCENT = :PERCENT
- T_CRLF = :CRLF
- T_EOF = :EOF
- T_TEXT = :TEXT
-
- BEG_REGEXP = /\G(?:\
-(?# 1: SPACE )( +)|\
-(?# 2: NIL )(NIL)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
-(?# 3: NUMBER )(\d+)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
-(?# 4: ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+]+)|\
-(?# 5: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
-(?# 6: LPAR )(\()|\
-(?# 7: RPAR )(\))|\
-(?# 8: BSLASH )(\\)|\
-(?# 9: STAR )(\*)|\
-(?# 10: LBRA )(\[)|\
-(?# 11: RBRA )(\])|\
-(?# 12: LITERAL )\{(\d+)\}\r\n|\
-(?# 13: PLUS )(\+)|\
-(?# 14: PERCENT )(%)|\
-(?# 15: CRLF )(\r\n)|\
-(?# 16: EOF )(\z))/ni
-
- DATA_REGEXP = /\G(?:\
-(?# 1: SPACE )( )|\
-(?# 2: NIL )(NIL)|\
-(?# 3: NUMBER )(\d+)|\
-(?# 4: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
-(?# 5: LITERAL )\{(\d+)\}\r\n|\
-(?# 6: LPAR )(\()|\
-(?# 7: RPAR )(\)))/ni
-
- TEXT_REGEXP = /\G(?:\
-(?# 1: TEXT )([^\x00\r\n]*))/ni
-
- RTEXT_REGEXP = /\G(?:\
-(?# 1: LBRA )(\[)|\
-(?# 2: TEXT )([^\x00\r\n]*))/ni
-
- CTEXT_REGEXP = /\G(?:\
-(?# 1: TEXT )([^\x00\r\n\]]*))/ni
-
- Token = Struct.new(:symbol, :value)
-
- def response
- token = lookahead
- case token.symbol
- when T_PLUS
- result = continue_req
- when T_STAR
- result = response_untagged
- else
- result = response_tagged
- end
- while lookahead.symbol == T_SPACE
- # Ignore trailing space for Microsoft Exchange Server
- shift_token
- end
- match(T_CRLF)
- match(T_EOF)
- return result
- end
-
- def continue_req
- match(T_PLUS)
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- return ContinuationRequest.new(resp_text, @str)
- else
- return ContinuationRequest.new(ResponseText.new(nil, ""), @str)
- end
- end
-
- def response_untagged
- match(T_STAR)
- match(T_SPACE)
- token = lookahead
- if token.symbol == T_NUMBER
- return numeric_response
- elsif token.symbol == T_ATOM
- case token.value
- when /\A(?:OK|NO|BAD|BYE|PREAUTH)\z/ni
- return response_cond
- when /\A(?:FLAGS)\z/ni
- return flags_response
- when /\A(?:ID)\z/ni
- return id_response
- when /\A(?:LIST|LSUB|XLIST)\z/ni
- return list_response
- when /\A(?:NAMESPACE)\z/ni
- return namespace_response
- when /\A(?:QUOTA)\z/ni
- return getquota_response
- when /\A(?:QUOTAROOT)\z/ni
- return getquotaroot_response
- when /\A(?:ACL)\z/ni
- return getacl_response
- when /\A(?:SEARCH|SORT)\z/ni
- return search_response
- when /\A(?:THREAD)\z/ni
- return thread_response
- when /\A(?:STATUS)\z/ni
- return status_response
- when /\A(?:CAPABILITY)\z/ni
- return capability_response
- when /\A(?:NOOP)\z/ni
- return ignored_response
- else
- return text_response
- end
- else
- parse_error("unexpected token %s", token.symbol)
- end
- end
-
- def response_tagged
- tag = astring_chars
- match(T_SPACE)
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return TaggedResponse.new(tag, name, resp_text, @str)
- end
-
- def response_cond
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return UntaggedResponse.new(name, resp_text, @str)
- end
-
- def numeric_response
- n = number
- match(T_SPACE)
- token = match(T_ATOM)
- name = token.value.upcase
- case name
- when "EXISTS", "RECENT", "EXPUNGE"
- return UntaggedResponse.new(name, n, @str)
- when "FETCH"
- shift_token
- match(T_SPACE)
- data = FetchData.new(n, msg_att(n))
- return UntaggedResponse.new(name, data, @str)
- end
- end
-
- def msg_att(n)
- match(T_LPAR)
- attr = {}
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- break
- when T_SPACE
- shift_token
- next
- end
- case token.value
- when /\A(?:ENVELOPE)\z/ni
- name, val = envelope_data
- when /\A(?:FLAGS)\z/ni
- name, val = flags_data
- when /\A(?:INTERNALDATE)\z/ni
- name, val = internaldate_data
- when /\A(?:RFC822(?:\.HEADER|\.TEXT)?)\z/ni
- name, val = rfc822_text
- when /\A(?:RFC822\.SIZE)\z/ni
- name, val = rfc822_size
- when /\A(?:BODY(?:STRUCTURE)?)\z/ni
- name, val = body_data
- when /\A(?:UID)\z/ni
- name, val = uid_data
- when /\A(?:MODSEQ)\z/ni
- name, val = modseq_data
- else
- parse_error("unknown attribute `%s' for {%d}", token.value, n)
- end
- attr[name] = val
- end
- return attr
- end
-
- def envelope_data
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return name, envelope
- end
-
- def envelope
- @lex_state = EXPR_DATA
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- result = nil
- else
- match(T_LPAR)
- date = nstring
- match(T_SPACE)
- subject = nstring
- match(T_SPACE)
- from = address_list
- match(T_SPACE)
- sender = address_list
- match(T_SPACE)
- reply_to = address_list
- match(T_SPACE)
- to = address_list
- match(T_SPACE)
- cc = address_list
- match(T_SPACE)
- bcc = address_list
- match(T_SPACE)
- in_reply_to = nstring
- match(T_SPACE)
- message_id = nstring
- match(T_RPAR)
- result = Envelope.new(date, subject, from, sender, reply_to,
- to, cc, bcc, in_reply_to, message_id)
- end
- @lex_state = EXPR_BEG
- return result
- end
-
- def flags_data
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return name, flag_list
- end
-
- def internaldate_data
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- token = match(T_QUOTED)
- return name, token.value
- end
-
- def rfc822_text
- token = match(T_ATOM)
- name = token.value.upcase
- token = lookahead
- if token.symbol == T_LBRA
- shift_token
- match(T_RBRA)
- end
- match(T_SPACE)
- return name, nstring
- end
-
- def rfc822_size
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return name, number
- end
-
- def body_data
- token = match(T_ATOM)
- name = token.value.upcase
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- return name, body
- end
- name.concat(section)
- token = lookahead
- if token.symbol == T_ATOM
- name.concat(token.value)
- shift_token
- end
- match(T_SPACE)
- data = nstring
- return name, data
- end
-
- def body
- @lex_state = EXPR_DATA
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- result = nil
- else
- match(T_LPAR)
- token = lookahead
- if token.symbol == T_LPAR
- result = body_type_mpart
- else
- result = body_type_1part
- end
- match(T_RPAR)
- end
- @lex_state = EXPR_BEG
- return result
- end
-
- def body_type_1part
- token = lookahead
- case token.value
- when /\A(?:TEXT)\z/ni
- return body_type_text
- when /\A(?:MESSAGE)\z/ni
- return body_type_msg
- when /\A(?:ATTACHMENT)\z/ni
- return body_type_attachment
- when /\A(?:MIXED)\z/ni
- return body_type_mixed
- else
- return body_type_basic
- end
- end
-
- def body_type_basic
- mtype, msubtype = media_type
- token = lookahead
- if token.symbol == T_RPAR
- return BodyTypeBasic.new(mtype, msubtype)
- end
- match(T_SPACE)
- param, content_id, desc, enc, size = body_fields
- md5, disposition, language, extension = body_ext_1part
- return BodyTypeBasic.new(mtype, msubtype,
- param, content_id,
- desc, enc, size,
- md5, disposition, language, extension)
- end
-
- def body_type_text
- mtype, msubtype = media_type
- match(T_SPACE)
- param, content_id, desc, enc, size = body_fields
- match(T_SPACE)
- lines = number
- md5, disposition, language, extension = body_ext_1part
- return BodyTypeText.new(mtype, msubtype,
- param, content_id,
- desc, enc, size,
- lines,
- md5, disposition, language, extension)
- end
-
- def body_type_msg
- mtype, msubtype = media_type
- match(T_SPACE)
- param, content_id, desc, enc, size = body_fields
-
- token = lookahead
- if token.symbol == T_RPAR
- # If this is not message/rfc822, we shouldn't apply the RFC822
- # spec to it. We should handle anything other than
- # message/rfc822 using multipart extension data [rfc3501] (i.e.
- # the data itself won't be returned, we would have to retrieve it
- # with BODYSTRUCTURE instead of with BODY
-
- # Also, sometimes a message/rfc822 is included as a large
- # attachment instead of having all of the other details
- # (e.g. attaching a .eml file to an email)
- if msubtype == "RFC822"
- return BodyTypeMessage.new(mtype, msubtype, param, content_id,
- desc, enc, size, nil, nil, nil, nil,
- nil, nil, nil)
- else
- return BodyTypeExtension.new(mtype, msubtype,
- param, content_id,
- desc, enc, size)
- end
- end
-
- match(T_SPACE)
- env = envelope
- match(T_SPACE)
- b = body
- match(T_SPACE)
- lines = number
- md5, disposition, language, extension = body_ext_1part
- return BodyTypeMessage.new(mtype, msubtype,
- param, content_id,
- desc, enc, size,
- env, b, lines,
- md5, disposition, language, extension)
- end
-
- def body_type_attachment
- mtype = case_insensitive_string
- match(T_SPACE)
- param = body_fld_param
- return BodyTypeAttachment.new(mtype, nil, param)
- end
-
- def body_type_mixed
- mtype = "MULTIPART"
- msubtype = case_insensitive_string
- param, disposition, language, extension = body_ext_mpart
- return BodyTypeBasic.new(mtype, msubtype, param, nil, nil, nil, nil, nil, disposition, language, extension)
- end
-
- def body_type_mpart
- parts = []
- while true
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- break
- end
- parts.push(body)
- end
- mtype = "MULTIPART"
- msubtype = case_insensitive_string
- param, disposition, language, extension = body_ext_mpart
- return BodyTypeMultipart.new(mtype, msubtype, parts,
- param, disposition, language,
- extension)
- end
-
- def media_type
- mtype = case_insensitive_string
- token = lookahead
- if token.symbol != T_SPACE
- return mtype, nil
- end
- match(T_SPACE)
- msubtype = case_insensitive_string
- return mtype, msubtype
- end
-
- def body_fields
- param = body_fld_param
- match(T_SPACE)
- content_id = nstring
- match(T_SPACE)
- desc = nstring
- match(T_SPACE)
- enc = case_insensitive_string
- match(T_SPACE)
- size = number
- return param, content_id, desc, enc, size
- end
-
- def body_fld_param
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- end
- match(T_LPAR)
- param = {}
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- break
- when T_SPACE
- shift_token
- end
- name = case_insensitive_string
- match(T_SPACE)
- val = string
- param[name] = val
- end
- return param
- end
-
- def body_ext_1part
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return nil
- end
- md5 = nstring
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return md5
- end
- disposition = body_fld_dsp
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return md5, disposition
- end
- language = body_fld_lang
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return md5, disposition, language
- end
-
- extension = body_extensions
- return md5, disposition, language, extension
- end
-
- def body_ext_mpart
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return nil
- end
- param = body_fld_param
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return param
- end
- disposition = body_fld_dsp
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return param, disposition
- end
- language = body_fld_lang
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return param, disposition, language
- end
-
- extension = body_extensions
- return param, disposition, language, extension
- end
-
- def body_fld_dsp
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- end
- match(T_LPAR)
- dsp_type = case_insensitive_string
- match(T_SPACE)
- param = body_fld_param
- match(T_RPAR)
- return ContentDisposition.new(dsp_type, param)
- end
-
- def body_fld_lang
- token = lookahead
- if token.symbol == T_LPAR
- shift_token
- result = []
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- return result
- when T_SPACE
- shift_token
- end
- result.push(case_insensitive_string)
- end
- else
- lang = nstring
- if lang
- return lang.upcase
- else
- return lang
- end
- end
- end
-
- def body_extensions
- result = []
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- return result
- when T_SPACE
- shift_token
- end
- result.push(body_extension)
- end
- end
-
- def body_extension
- token = lookahead
- case token.symbol
- when T_LPAR
- shift_token
- result = body_extensions
- match(T_RPAR)
- return result
- when T_NUMBER
- return number
- else
- return nstring
- end
- end
-
- def section
- str = String.new
- token = match(T_LBRA)
- str.concat(token.value)
- token = match(T_ATOM, T_NUMBER, T_RBRA)
- if token.symbol == T_RBRA
- str.concat(token.value)
- return str
- end
- str.concat(token.value)
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- str.concat(token.value)
- token = match(T_LPAR)
- str.concat(token.value)
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- str.concat(token.value)
- shift_token
- break
- when T_SPACE
- shift_token
- str.concat(token.value)
- end
- str.concat(format_string(astring))
- end
- end
- token = match(T_RBRA)
- str.concat(token.value)
- return str
- end
-
- def format_string(str)
- case str
- when ""
- return '""'
- when /[\x80-\xff\r\n]/n
- # literal
- return "{" + str.bytesize.to_s + "}" + CRLF + str
- when /[(){ \x00-\x1f\x7f%*"\\]/n
- # quoted string
- return '"' + str.gsub(/["\\]/n, "\\\\\\&") + '"'
- else
- # atom
- return str
- end
- end
-
- def uid_data
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return name, number
- end
-
- def modseq_data
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- match(T_LPAR)
- modseq = number
- match(T_RPAR)
- return name, modseq
- end
-
- def ignored_response
- while lookahead.symbol != T_CRLF
- shift_token
- end
- return IgnoredResponse.new(@str)
- end
-
- def text_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return UntaggedResponse.new(name, text)
- end
-
- def flags_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return UntaggedResponse.new(name, flag_list, @str)
- end
-
- def list_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return UntaggedResponse.new(name, mailbox_list, @str)
- end
-
- def mailbox_list
- attr = flag_list
- match(T_SPACE)
- token = match(T_QUOTED, T_NIL)
- if token.symbol == T_NIL
- delim = nil
- else
- delim = token.value
- end
- match(T_SPACE)
- name = astring
- return MailboxList.new(attr, delim, name)
- end
-
- def getquota_response
- # If quota never established, get back
- # `NO Quota root does not exist'.
- # If quota removed, get `()' after the
- # folder spec with no mention of `STORAGE'.
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- mailbox = astring
- match(T_SPACE)
- match(T_LPAR)
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- data = MailboxQuota.new(mailbox, nil, nil)
- return UntaggedResponse.new(name, data, @str)
- when T_ATOM
- shift_token
- match(T_SPACE)
- token = match(T_NUMBER)
- usage = token.value
- match(T_SPACE)
- token = match(T_NUMBER)
- quota = token.value
- match(T_RPAR)
- data = MailboxQuota.new(mailbox, usage, quota)
- return UntaggedResponse.new(name, data, @str)
- else
- parse_error("unexpected token %s", token.symbol)
- end
- end
-
- def getquotaroot_response
- # Similar to getquota, but only admin can use getquota.
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- mailbox = astring
- quotaroots = []
- while true
- token = lookahead
- break unless token.symbol == T_SPACE
- shift_token
- quotaroots.push(astring)
- end
- data = MailboxQuotaRoot.new(mailbox, quotaroots)
- return UntaggedResponse.new(name, data, @str)
- end
-
- def getacl_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- mailbox = astring
- data = []
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- while true
- token = lookahead
- case token.symbol
- when T_CRLF
- break
- when T_SPACE
- shift_token
- end
- user = astring
- match(T_SPACE)
- rights = astring
- data.push(MailboxACLItem.new(user, rights, mailbox))
- end
- end
- return UntaggedResponse.new(name, data, @str)
- end
-
- def search_response
- token = match(T_ATOM)
- name = token.value.upcase
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- data = []
- while true
- token = lookahead
- case token.symbol
- when T_CRLF
- break
- when T_SPACE
- shift_token
- when T_NUMBER
- data.push(number)
- when T_LPAR
- # TODO: include the MODSEQ value in a response
- shift_token
- match(T_ATOM)
- match(T_SPACE)
- match(T_NUMBER)
- match(T_RPAR)
- end
- end
- else
- data = []
- end
- return UntaggedResponse.new(name, data, @str)
- end
-
- def thread_response
- token = match(T_ATOM)
- name = token.value.upcase
- token = lookahead
-
- if token.symbol == T_SPACE
- threads = []
-
- while true
- shift_token
- token = lookahead
-
- case token.symbol
- when T_LPAR
- threads << thread_branch(token)
- when T_CRLF
- break
- end
- end
- else
- # no member
- threads = []
- end
-
- return UntaggedResponse.new(name, threads, @str)
- end
-
- def thread_branch(token)
- rootmember = nil
- lastmember = nil
-
- while true
- shift_token # ignore first T_LPAR
- token = lookahead
-
- case token.symbol
- when T_NUMBER
- # new member
- newmember = ThreadMember.new(number, [])
- if rootmember.nil?
- rootmember = newmember
- else
- lastmember.children << newmember
- end
- lastmember = newmember
- when T_SPACE
- # do nothing
- when T_LPAR
- if rootmember.nil?
- # dummy member
- lastmember = rootmember = ThreadMember.new(nil, [])
- end
-
- lastmember.children << thread_branch(token)
- when T_RPAR
- break
- end
- end
-
- return rootmember
- end
-
- def status_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- mailbox = astring
- match(T_SPACE)
- match(T_LPAR)
- attr = {}
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- break
- when T_SPACE
- shift_token
- end
- token = match(T_ATOM)
- key = token.value.upcase
- match(T_SPACE)
- val = number
- attr[key] = val
- end
- data = StatusData.new(mailbox, attr)
- return UntaggedResponse.new(name, data, @str)
- end
-
- def capability_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- UntaggedResponse.new(name, capability_data, @str)
- end
-
- def capability_data
- data = []
- while true
- token = lookahead
- case token.symbol
- when T_CRLF, T_RBRA
- break
- when T_SPACE
- shift_token
- next
- end
- data.push(atom.upcase)
- end
- data
- end
-
- def id_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- token = match(T_LPAR, T_NIL)
- if token.symbol == T_NIL
- return UntaggedResponse.new(name, nil, @str)
- else
- data = {}
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- break
- when T_SPACE
- shift_token
- next
- else
- key = string
- match(T_SPACE)
- val = nstring
- data[key] = val
- end
- end
- return UntaggedResponse.new(name, data, @str)
- end
- end
-
- def namespace_response
- @lex_state = EXPR_DATA
- token = lookahead
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- personal = namespaces
- match(T_SPACE)
- other = namespaces
- match(T_SPACE)
- shared = namespaces
- @lex_state = EXPR_BEG
- data = Namespaces.new(personal, other, shared)
- return UntaggedResponse.new(name, data, @str)
- end
-
- def namespaces
- token = lookahead
- # empty () is not allowed, so nil is functionally identical to empty.
- data = []
- if token.symbol == T_NIL
- shift_token
- else
- match(T_LPAR)
- loop do
- data << namespace
- break unless lookahead.symbol == T_SPACE
- shift_token
- end
- match(T_RPAR)
- end
- data
- end
-
- def namespace
- match(T_LPAR)
- prefix = match(T_QUOTED, T_LITERAL).value
- match(T_SPACE)
- delimiter = string
- extensions = namespace_response_extensions
- match(T_RPAR)
- Namespace.new(prefix, delimiter, extensions)
- end
-
- def namespace_response_extensions
- data = {}
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- name = match(T_QUOTED, T_LITERAL).value
- data[name] ||= []
- match(T_SPACE)
- match(T_LPAR)
- loop do
- data[name].push match(T_QUOTED, T_LITERAL).value
- break unless lookahead.symbol == T_SPACE
- shift_token
- end
- match(T_RPAR)
- end
- data
- end
-
- # text = 1*TEXT-CHAR
- # TEXT-CHAR = <any CHAR except CR and LF>
- def text
- match(T_TEXT, lex_state: EXPR_TEXT).value
- end
-
- # resp-text = ["[" resp-text-code "]" SP] text
- def resp_text
- token = match(T_LBRA, T_TEXT, lex_state: EXPR_RTEXT)
- case token.symbol
- when T_LBRA
- code = resp_text_code
- match(T_RBRA)
- accept_space # violating RFC
- ResponseText.new(code, text)
- when T_TEXT
- ResponseText.new(nil, token.value)
- end
- end
-
- # See https://www.rfc-editor.org/errata/rfc3501
- #
- # resp-text-code = "ALERT" /
- # "BADCHARSET" [SP "(" charset *(SP charset) ")" ] /
- # capability-data / "PARSE" /
- # "PERMANENTFLAGS" SP "("
- # [flag-perm *(SP flag-perm)] ")" /
- # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
- # "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
- # "UNSEEN" SP nz-number /
- # atom [SP 1*<any TEXT-CHAR except "]">]
- def resp_text_code
- token = match(T_ATOM)
- name = token.value.upcase
- case name
- when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n
- result = ResponseCode.new(name, nil)
- when /\A(?:BADCHARSET)\z/n
- result = ResponseCode.new(name, charset_list)
- when /\A(?:CAPABILITY)\z/ni
- result = ResponseCode.new(name, capability_data)
- when /\A(?:PERMANENTFLAGS)\z/n
- match(T_SPACE)
- result = ResponseCode.new(name, flag_list)
- when /\A(?:UIDVALIDITY|UIDNEXT|UNSEEN)\z/n
- match(T_SPACE)
- result = ResponseCode.new(name, number)
- else
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- token = match(T_TEXT, lex_state: EXPR_CTEXT)
- result = ResponseCode.new(name, token.value)
- else
- result = ResponseCode.new(name, nil)
- end
- end
- return result
- end
-
- def charset_list
- result = []
- if accept(T_SPACE)
- match(T_LPAR)
- result << charset
- while accept(T_SPACE)
- result << charset
- end
- match(T_RPAR)
- end
- result
- end
-
- def address_list
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- else
- result = []
- match(T_LPAR)
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- break
- when T_SPACE
- shift_token
- end
- result.push(address)
- end
- return result
- end
- end
-
- ADDRESS_REGEXP = /\G\
-(?# 1: NAME )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
-(?# 2: ROUTE )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
-(?# 3: MAILBOX )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
-(?# 4: HOST )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)")\
-\)/ni
-
- def address
- match(T_LPAR)
- if @str.index(ADDRESS_REGEXP, @pos)
- # address does not include literal.
- @pos = $~.end(0)
- name = $1
- route = $2
- mailbox = $3
- host = $4
- for s in [name, route, mailbox, host]
- if s
- s.gsub!(/\\(["\\])/n, "\\1")
- end
- end
- else
- name = nstring
- match(T_SPACE)
- route = nstring
- match(T_SPACE)
- mailbox = nstring
- match(T_SPACE)
- host = nstring
- match(T_RPAR)
- end
- return Address.new(name, route, mailbox, host)
- end
-
- FLAG_REGEXP = /\
-(?# FLAG )\\([^\x80-\xff(){ \x00-\x1f\x7f%"\\]+)|\
-(?# ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\]+)/n
-
- def flag_list
- if @str.index(/\(([^)]*)\)/ni, @pos)
- @pos = $~.end(0)
- return $1.scan(FLAG_REGEXP).collect { |flag, atom|
- if atom
- atom
- else
- symbol = flag.capitalize.intern
- @flag_symbols[symbol] = true
- if @flag_symbols.length > IMAP.max_flag_count
- raise FlagCountError, "number of flag symbols exceeded"
- end
- symbol
- end
- }
- else
- parse_error("invalid flag list")
- end
- end
-
- def nstring
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- else
- return string
- end
- end
-
- def astring
- token = lookahead
- if string_token?(token)
- return string
- else
- return astring_chars
- end
- end
-
- def string
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- end
- token = match(T_QUOTED, T_LITERAL)
- return token.value
- end
-
- STRING_TOKENS = [T_QUOTED, T_LITERAL, T_NIL]
-
- def string_token?(token)
- return STRING_TOKENS.include?(token.symbol)
- end
-
- def case_insensitive_string
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- end
- token = match(T_QUOTED, T_LITERAL)
- return token.value.upcase
- end
-
- # atom = 1*ATOM-CHAR
- # ATOM-CHAR = <any CHAR except atom-specials>
- ATOM_TOKENS = [
- T_ATOM,
- T_NUMBER,
- T_NIL,
- T_LBRA,
- T_PLUS
- ]
-
- def atom
- -combine_adjacent(*ATOM_TOKENS)
- end
-
- # ASTRING-CHAR = ATOM-CHAR / resp-specials
- # resp-specials = "]"
- ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA]
-
- def astring_chars
- combine_adjacent(*ASTRING_CHARS_TOKENS)
- end
-
- def combine_adjacent(*tokens)
- result = "".b
- while token = accept(*tokens)
- result << token.value
- end
- if result.empty?
- parse_error('unexpected token %s (expected %s)',
- lookahead.symbol, args.join(" or "))
- end
- result
- end
-
- # See https://www.rfc-editor.org/errata/rfc3501
- #
- # charset = atom / quoted
- def charset
- if token = accept(T_QUOTED)
- token.value
- else
- atom
- end
- end
-
- def number
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- end
- token = match(T_NUMBER)
- return token.value.to_i
- end
-
- def nil_atom
- match(T_NIL)
- return nil
- end
-
- SPACES_REGEXP = /\G */n
-
- # This advances @pos directly so it's safe before changing @lex_state.
- def accept_space
- if @token
- shift_token if @token.symbol == T_SPACE
- elsif @str[@pos] == " "
- @pos += 1
- end
- end
-
- # The RFC is very strict about this and usually we should be too.
- # But skipping spaces is usually a safe workaround for buggy servers.
- #
- # This advances @pos directly so it's safe before changing @lex_state.
- def accept_spaces
- shift_token if @token&.symbol == T_SPACE
- if @str.index(SPACES_REGEXP, @pos)
- @pos = $~.end(0)
- end
- end
-
- def match(*args, lex_state: @lex_state)
- if @token && lex_state != @lex_state
- parse_error("invalid lex_state change to %s with unconsumed token",
- lex_state)
- end
- begin
- @lex_state, original_lex_state = lex_state, @lex_state
- token = lookahead
- unless args.include?(token.symbol)
- parse_error('unexpected token %s (expected %s)',
- token.symbol.id2name,
- args.collect {|i| i.id2name}.join(" or "))
- end
- shift_token
- return token
- ensure
- @lex_state = original_lex_state
- end
- end
-
- # like match, but does not raise error on failure.
- #
- # returns and shifts token on successful match
- # returns nil and leaves @token unshifted on no match
- def accept(*args)
- token = lookahead
- if args.include?(token.symbol)
- shift_token
- token
- end
- end
-
- def lookahead
- @token ||= next_token
- end
-
- def shift_token
- @token = nil
- end
-
- def next_token
- case @lex_state
- when EXPR_BEG
- if @str.index(BEG_REGEXP, @pos)
- @pos = $~.end(0)
- if $1
- return Token.new(T_SPACE, $+)
- elsif $2
- return Token.new(T_NIL, $+)
- elsif $3
- return Token.new(T_NUMBER, $+)
- elsif $4
- return Token.new(T_ATOM, $+)
- elsif $5
- return Token.new(T_QUOTED,
- $+.gsub(/\\(["\\])/n, "\\1"))
- elsif $6
- return Token.new(T_LPAR, $+)
- elsif $7
- return Token.new(T_RPAR, $+)
- elsif $8
- return Token.new(T_BSLASH, $+)
- elsif $9
- return Token.new(T_STAR, $+)
- elsif $10
- return Token.new(T_LBRA, $+)
- elsif $11
- return Token.new(T_RBRA, $+)
- elsif $12
- len = $+.to_i
- val = @str[@pos, len]
- @pos += len
- return Token.new(T_LITERAL, val)
- elsif $13
- return Token.new(T_PLUS, $+)
- elsif $14
- return Token.new(T_PERCENT, $+)
- elsif $15
- return Token.new(T_CRLF, $+)
- elsif $16
- return Token.new(T_EOF, $+)
- else
- parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid")
- end
- else
- @str.index(/\S*/n, @pos)
- parse_error("unknown token - %s", $&.dump)
- end
- when EXPR_DATA
- if @str.index(DATA_REGEXP, @pos)
- @pos = $~.end(0)
- if $1
- return Token.new(T_SPACE, $+)
- elsif $2
- return Token.new(T_NIL, $+)
- elsif $3
- return Token.new(T_NUMBER, $+)
- elsif $4
- return Token.new(T_QUOTED,
- $+.gsub(/\\(["\\])/n, "\\1"))
- elsif $5
- len = $+.to_i
- val = @str[@pos, len]
- @pos += len
- return Token.new(T_LITERAL, val)
- elsif $6
- return Token.new(T_LPAR, $+)
- elsif $7
- return Token.new(T_RPAR, $+)
- else
- parse_error("[Net::IMAP BUG] DATA_REGEXP is invalid")
- end
- else
- @str.index(/\S*/n, @pos)
- parse_error("unknown token - %s", $&.dump)
- end
- when EXPR_TEXT
- if @str.index(TEXT_REGEXP, @pos)
- @pos = $~.end(0)
- if $1
- return Token.new(T_TEXT, $+)
- else
- parse_error("[Net::IMAP BUG] TEXT_REGEXP is invalid")
- end
- else
- @str.index(/\S*/n, @pos)
- parse_error("unknown token - %s", $&.dump)
- end
- when EXPR_RTEXT
- if @str.index(RTEXT_REGEXP, @pos)
- @pos = $~.end(0)
- if $1
- return Token.new(T_LBRA, $+)
- elsif $2
- return Token.new(T_TEXT, $+)
- else
- parse_error("[Net::IMAP BUG] RTEXT_REGEXP is invalid")
- end
- else
- @str.index(/\S*/n, @pos)
- parse_error("unknown token - %s", $&.dump)
- end
- when EXPR_CTEXT
- if @str.index(CTEXT_REGEXP, @pos)
- @pos = $~.end(0)
- if $1
- return Token.new(T_TEXT, $+)
- else
- parse_error("[Net::IMAP BUG] CTEXT_REGEXP is invalid")
- end
- else
- @str.index(/\S*/n, @pos) #/
- parse_error("unknown token - %s", $&.dump)
- end
- else
- parse_error("invalid @lex_state - %s", @lex_state.inspect)
- end
- end
-
- def parse_error(fmt, *args)
- if IMAP.debug
- $stderr.printf("@str: %s\n", @str.dump)
- $stderr.printf("@pos: %d\n", @pos)
- $stderr.printf("@lex_state: %s\n", @lex_state)
- if @token
- $stderr.printf("@token.symbol: %s\n", @token.symbol)
- $stderr.printf("@token.value: %s\n", @token.value.inspect)
- end
- end
- raise ResponseParseError, format(fmt, *args)
- end
- end
-
- end
-
-end