[前][次][番号順一覧][スレッド一覧]

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/

[前][次][番号順一覧][スレッド一覧]