ruby-changes:65928
From: Hiroshi <ko1@a...>
Date: Thu, 22 Apr 2021 14:41:52 +0900 (JST)
Subject: [ruby-changes:65928] 674760316c (master): Merge net-imap-0.2.0
https://git.ruby-lang.org/ruby.git/commit/?id=674760316c From 674760316ce5b68aa182c1b3b25665de250341b3 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA <hsbt@r...> Date: Thu, 22 Apr 2021 14:35:52 +0900 Subject: Merge net-imap-0.2.0 --- lib/net/imap.rb | 524 +++++++++++++++++++++++++---- test/net/imap/test_imap.rb | 69 +++- test/net/imap/test_imap_response_parser.rb | 78 +++++ 3 files changed, 593 insertions(+), 78 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 36920c4..ff9dff4 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -201,7 +201,7 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L201 # Unicode", RFC 2152, May 1997. # class IMAP < Protocol - VERSION = "0.1.1" + VERSION = "0.2.0" include MonitorMixin if defined?(OpenSSL::SSL) @@ -304,6 +304,16 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L304 @@authenticators[auth_type] = authenticator end + # Builds an authenticator for Net::IMAP#authenticate. + def self.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 + # The default port for IMAP connections, port 143 def self.default_port return PORT @@ -365,6 +375,30 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L375 end end + # Sends an ID command, and returns a hash of the server's + # response, or nil if the server does not identify itself. + # + # Note that the user should first check if the server supports the ID + # capability. For example: + # + # capabilities = imap.capability + # if capabilities.include?("ID") + # id = imap.id( + # name: "my IMAP client (ruby)", + # version: MyIMAP::VERSION, + # "support-url": "mailto:bugs@e...", + # os: RbConfig::CONFIG["host_os"], + # ) + # end + # + # See RFC 2971, Section 3.3, for defined fields. + def id(client_id=nil) + synchronize do + send_command("ID", ClientID.new(client_id)) + @responses.delete("ID")&.last + end + end + # Sends a NOOP command to the server. It does nothing. def noop send_command("NOOP") @@ -408,7 +442,7 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L442 # the form "AUTH=LOGIN" or "AUTH=CRAM-MD5". # # Authentication is done using the appropriate authenticator object: - # see @@authenticators for more information on plugging in your own + # see +add_authenticator+ for more information on plugging in your own # authenticator. # # For example: @@ -417,12 +451,7 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L451 # # A Net::IMAP::NoResponseError is raised if authentication fails. def authenticate(auth_type, *args) - auth_type = auth_type.upcase - unless @@authenticators.has_key?(auth_type) - raise ArgumentError, - format('unknown auth type - "%s"', auth_type) - end - authenticator = @@authenticators[auth_type].new(*args) + authenticator = self.class.authenticator(auth_type, *args) send_command("AUTHENTICATE", auth_type) do |resp| if resp.instance_of?(ContinuationRequest) data = authenticator.process(resp.data.text.unpack("m")[0]) @@ -552,6 +581,60 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L581 end end + # Sends a NAMESPACE command [RFC2342] and returns the namespaces that are + # available. The NAMESPACE command allows a client to discover the prefixes + # of namespaces used by a server for personal mailboxes, other users' + # mailboxes, and shared mailboxes. + # + # This extension predates IMAP4rev1 (RFC3501), so most IMAP servers support + # it. Many popular IMAP servers are configured with the default personal + # namespaces as `("" "/")`: no prefix and "/" hierarchy delimiter. In that + # common case, the naive client may not have any trouble naming mailboxes. + # + # But many servers are configured with the default personal namespace as + # e.g. `("INBOX." ".")`, placing all personal folders under INBOX, with "." + # as the hierarchy delimiter. If the client does not check for this, but + # naively assumes it can use the same folder names for all servers, then + # folder creation (and listing, moving, etc) can lead to errors. + # + # From RFC2342: + # + # Although typically a server will support only a single Personal + # Namespace, and a single Other User's Namespace, circumstances exist + # where there MAY be multiples of these, and a client MUST be prepared + # for them. If a client is configured such that it is required to create + # a certain mailbox, there can be circumstances where it is unclear which + # Personal Namespaces it should create the mailbox in. In these + # situations a client SHOULD let the user select which namespaces to + # create the mailbox in. + # + # The user of this method should first check if the server supports the + # NAMESPACE capability. The return value is a +Net::IMAP::Namespaces+ + # object which has +personal+, +other+, and +shared+ fields, each an array + # of +Net::IMAP::Namespace+ objects. These arrays will be empty when the + # server responds with nil. + # + # For example: + # + # capabilities = imap.capability + # if capabilities.include?("NAMESPACE") + # namespaces = imap.namespace + # if namespace = namespaces.personal.first + # prefix = namespace.prefix # e.g. "" or "INBOX." + # delim = namespace.delim # e.g. "/" or "." + # # personal folders should use the prefix and delimiter + # imap.create(prefix + "foo") + # imap.create(prefix + "bar") + # imap.create(prefix + %w[path to my folder].join(delim)) + # end + # end + def namespace + synchronize do + send_command("NAMESPACE") + return @responses.delete("NAMESPACE")[-1] + end + end + # Sends a XLIST command, and returns a subset of names from # the complete set of all names available to the client. # +refname+ provides a context (for instance, a base directory @@ -1656,6 +1739,74 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L1739 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 + # Common validators of number and nz_number types module NumValidator # :nodoc class << self @@ -1747,6 +1898,18 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L1898 # raw_data:: Returns the raw data string. UntaggedResponse = Struct.new(:name, :data, :raw_data) + # Net::IMAP::IgnoredResponse represents intentionaly 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. + IgnoredResponse = Struct.new(:raw_data) + # Net::IMAP::TaggedResponse represents tagged responses. # # The server completion result response indicates the success or @@ -1774,8 +1937,7 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L1937 # Net::IMAP::ResponseText represents texts of responses. # The text may be prefixed by the response code. # - # resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text) - # ;; text SHOULD NOT begin with "[" or "=" + # resp_text ::= ["[" resp-text-code "]" SP] text # # ==== Fields: # @@ -1787,12 +1949,15 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L1949 # Net::IMAP::ResponseCode represents response codes. # - # resp_text_code ::= "ALERT" / "PARSE" / - # "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" / + # resp_text_code ::= "ALERT" / + # "BADCHARSET" [SP "(" astring *(SP astring) ")" ] / + # capability_data / "PARSE" / + # (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/