From 864b7979f1b2499b7271169eb92b2328c82cee33 Mon Sep 17 00:00:00 2001 From: nick evans Date: Fri, 11 Nov 2022 11:36:05 -0500 Subject: [PATCH 01/17] =?UTF-8?q?=F0=9F=93=9A=20Update=20Net::IMAP=20docs,?= =?UTF-8?q?=20formatting=20and=20more...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Escape some text that shouldn't be linked, e.g. IMAP. * Link some text that should be linked: * removing "+" to let classes link * Remove `Net::IMAP::` prefix from response data class names. The redundant prefix breaks up the reading flow of the text. Examples will still use fully-qualified names, and readers should generally be able to see that the classes/modules are in Net::IMAP, from context. * Add headings and convert text to headings. * Reduce headings inside method docs to h5 (call-seq renders like h4). * Add or "+" to code or verbatim text. * To match the tweaks to rdoc's darkfish template, convert definition lists to "note" vs "label" style lists for table vs lists. * Add ">>>" with to highlight important "aside" notices. * Add :nodoc: to ResponseErrors. --- lib/net/imap.rb | 121 +++++++++++++++++++++++++++--------------------- 1 file changed, 69 insertions(+), 52 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 95c1b207..f1eae089 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -23,12 +23,14 @@ module Net - # - # Net::IMAP implements Internet Message Access Protocol (IMAP) client + # Net::IMAP implements Internet Message Access Protocol (\IMAP) client # functionality. The protocol is described in - # [IMAP[https://tools.ietf.org/html/rfc3501]]. + # [IMAP4rev1[https://tools.ietf.org/html/rfc3501]]. + #-- + # TODO: and [IMAP4rev2[https://tools.ietf.org/html/rfc9051]]. + #++ # - # == IMAP Overview + # == \IMAP Overview # # An \IMAP client connects to a server, and then authenticates # itself using either #authenticate or #login. Having @@ -41,12 +43,14 @@ module Net # within a hierarchy of directories. # # To work on the messages within a mailbox, the client must - # first select that mailbox, using either #select or (for - # read-only access) #examine. Once the client has successfully - # selected a mailbox, they enter _selected_ state, and that + # first select that mailbox, using either #select or #examine + # (for read-only access). Once the client has successfully + # selected a mailbox, they enter the "_selected_" state, and that # mailbox becomes the _current_ mailbox, on which mail-item # related commands implicitly operate. # + # === Sequence numbers and UIDs + # # Messages have two sorts of identifiers: message sequence # numbers and UIDs. # @@ -62,7 +66,7 @@ module Net # the existing message is deleted. UIDs are required to # be assigned in ascending (but not necessarily sequential) # order within a mailbox; this means that if a non-IMAP client - # rearranges the order of mailitems within a mailbox, the + # rearranges the order of mail items within a mailbox, the # UIDs have to be reassigned. An \IMAP client thus cannot # rearrange message orders. # @@ -108,7 +112,7 @@ module Net # # == Errors # - # An IMAP server can send three different types of responses to indicate + # An \IMAP server can send three different types of responses to indicate # failure: # # NO:: the attempted command could not be successfully completed. For @@ -116,7 +120,7 @@ module Net # the selected mailbox does not exist; etc. # # BAD:: the request from the client does not follow the server's - # understanding of the IMAP protocol. This includes attempting + # understanding of the \IMAP protocol. This includes attempting # commands from the wrong client state; for instance, attempting # to perform a SEARCH command without having SELECTed a current # mailbox. It can also signal an internal server @@ -340,10 +344,12 @@ class IMAP < Protocol include SSL end - # Returns an initial greeting response from the server. + # Returns the initial greeting the server, an UntaggedResponse. attr_reader :greeting - # Returns recorded untagged responses. For example: + # Returns recorded untagged responses. + # + # For example: # # imap.select("inbox") # p imap.responses["EXISTS"][-1] @@ -421,14 +427,17 @@ def disconnected? # Sends a CAPABILITY command, and returns an array of # capabilities that the server supports. Each capability - # is a string. See [IMAP] for a list of possible - # capabilities. - # - # Note that the Net::IMAP class does not modify its - # behaviour according to the capabilities of the server; - # it is up to the user of the class to ensure that - # a certain capability is supported by a server before - # using it. + # is a string. + # See the {IANA IMAP capabilities registry}[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml] + # for a list of possible capabilities and their RFCs. + # + # >>> + # *Note* that the Net::IMAP class does not modify its + # behaviour according to the capabilities of the server; + # it is up to the user of the class to ensure that + # a certain capability is supported by a server before + # using it. + # def capability synchronize do send_command("CAPABILITY") @@ -492,10 +501,10 @@ def starttls(options = {}, verify = true) # supports the following mechanisms: # # PLAIN:: Login using cleartext user and password. Secure with TLS. - # See Net::IMAP::PlainAuthenticator. + # See PlainAuthenticator. # CRAM-MD5:: DEPRECATED: Use PLAIN (or DIGEST-MD5) with TLS. # DIGEST-MD5:: DEPRECATED by RFC6331. Must be secured using TLS. - # See Net::IMAP::DigestMD5Authenticator. + # See DigestMD5Authenticator. # LOGIN:: DEPRECATED: Use PLAIN. # # Most mechanisms require two args: authentication identity (e.g. username) @@ -518,7 +527,7 @@ def starttls(options = {}, verify = true) # # A Net::IMAP::NoResponseError is raised if authentication fails. # - # See +Net::IMAP::Authenticators+ for more information on plugging in your + # See Net::IMAP::Authenticators for more information on plugging in your # own authenticator. def authenticate(auth_type, *args) authenticator = self.class.authenticator(auth_type, *args) @@ -555,7 +564,7 @@ def login(user, password) # A Net::IMAP::NoResponseError is raised if the mailbox does not # exist or is for some reason non-selectable. # - # ==== Capabilities + # ===== Capabilities # # If [UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]] is supported, # the server may return an untagged "NO" response with a "UIDNOTSTICKY" @@ -644,7 +653,7 @@ def unsubscribe(mailbox) # which mailboxes to match. If +mailbox+ is empty, the root # name of +refname+ and the hierarchy delimiter are returned. # - # The return value is an array of +Net::IMAP::MailboxList+. For example: + # The return value is an array of MailboxList. For example: # # imap.create("foo/bar") # imap.create("foo/baz") @@ -688,9 +697,9 @@ def list(refname, mailbox) # 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+ + # NAMESPACE #capability. The return value is a Namespaces # object which has +personal+, +other+, and +shared+ fields, each an array - # of +Net::IMAP::Namespace+ objects. These arrays will be empty when the + # of Namespace objects. These arrays will be empty when the # server responds with nil. # # For example: @@ -733,7 +742,7 @@ def namespace # The XLIST command is like the LIST command except that the flags # returned refer to the function of the folder/mailbox, e.g. :Sent # - # The return value is an array of +Net::IMAP::MailboxList+. For example: + # The return value is an array of MailboxList objects. For example: # # imap.create("foo/bar") # imap.create("foo/baz") @@ -751,7 +760,7 @@ def xlist(refname, mailbox) # Sends the GETQUOTAROOT command along with the specified +mailbox+. # This command is generally available to both admin and user. # If this mailbox exists, it returns an array containing objects of type - # Net::IMAP::MailboxQuotaRoot and Net::IMAP::MailboxQuota. + # MailboxQuotaRoot and MailboxQuota. # # The QUOTA extension is described in [QUOTA[https://tools.ietf.org/html/rfc2087]] def getquotaroot(mailbox) @@ -766,7 +775,7 @@ def getquotaroot(mailbox) # Sends the GETQUOTA command along with specified +mailbox+. # If this mailbox exists, then an array containing a - # Net::IMAP::MailboxQuota object is returned. This + # MailboxQuota object is returned. This # command is generally only available to server admin. # # The QUOTA extension is described in [QUOTA[https://tools.ietf.org/html/rfc2087]] @@ -807,7 +816,7 @@ def setacl(mailbox, user, rights) # Send the GETACL command along with a specified +mailbox+. # If this mailbox exists, an array containing objects of - # Net::IMAP::MailboxACLItem will be returned. + # MailboxACLItem will be returned. # # The ACL extension is described in [ACL[https://tools.ietf.org/html/rfc4314]] def getacl(mailbox) @@ -822,7 +831,7 @@ def getacl(mailbox) # "subscribed." +refname+ and +mailbox+ are interpreted as # for #list. # - # The return value is an array of +Net::IMAP::MailboxList+. + # The return value is an array of MailboxList objects. def lsub(refname, mailbox) synchronize do send_command("LSUB", refname, mailbox) @@ -858,6 +867,7 @@ def status(mailbox, attr) # flags initially passed to the new message. The optional # +date_time+ argument specifies the creation time to assign to the # new message; it defaults to the current time. + # # For example: # # imap.append("inbox", <\\Deleted flag set and a UID that is # included in +uid_set+. # # By using UID EXPUNGE instead of EXPUNGE when resynchronizing with # the server, the client can ensure that it does not inadvertantly - # remove any messages that have been marked as \\Deleted by other + # remove any messages that have been marked as \\Deleted by other # clients between the time that the client was last connected and # the time the client resynchronizes. # - # Note:: Although the command takes a +uid_set+ for its argument, the + # *Note:* + # >>> + # Although the command takes a +uid_set+ for its argument, the # server still returns regular EXPUNGE responses, which contain # a sequence number. These will be deleted from # #responses and this method returns them as an array of # sequence number integers. # - # ==== Capability requirement + # ===== Capability requirement # # +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] must be # supported by the server. @@ -962,12 +974,17 @@ def uid_expunge(uid_set) # match the given searching criteria, and returns message sequence # numbers. +keys+ can either be a string holding the entire # search string, or a single-dimension array of search keywords and - # arguments. The following are some common search criteria; - # see [IMAP] section 6.4.4 for a full list. + # arguments. + # + # ===== Search criteria + # + # The following are some common search criteria; + # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]] + # for a full list: # - # :: a set of message sequence numbers. ',' indicates - # an interval, ':' indicates a range. For instance, - # '2,10:12,15' means "2,10,11,12,15". + # :: a set of message sequence numbers. "," indicates + # an interval, "+:+" indicates a range. For instance, + # "2,10:12,15" means "2,10,11,12,15". # # BEFORE :: messages with an internal date strictly before # . The date argument has a format similar @@ -994,10 +1011,11 @@ def uid_expunge(uid_set) # # TO :: messages with in their TO field. # - # For example: + # ===== For example: # # p imap.search(["SUBJECT", "hello", "NOT", "NEW"]) # #=> [1, 6, 7, 8] + # def search(keys, charset = nil) return search_internal("SEARCH", keys, charset) end @@ -1020,9 +1038,9 @@ def uid_search(keys, charset = nil) # equivalent to 1..5. # # +attr+ is a list of attributes to fetch; see the documentation - # for Net::IMAP::FetchData for a list of valid attributes. + # for FetchData for a list of valid attributes. # - # The return value is an array of Net::IMAP::FetchData or nil + # The return value is an array of FetchData or nil # (instead of an empty array) if there is no matching message. # # For example: @@ -1059,7 +1077,7 @@ def uid_fetch(set, attr, mod = nil) # with the provided one, '+FLAGS' will add the provided flags, # and '-FLAGS' will remove them. +flags+ is a list of flags. # - # The return value is an array of Net::IMAP::FetchData. For example: + # The return value is an array of FetchData. For example: # # p imap.store(6..8, "+FLAGS", [:Deleted]) # #=> [#[:Seen, :Deleted]}>, \\ @@ -1079,7 +1097,7 @@ def uid_store(set, attr, flags) # a number, an array of numbers, or a Range object. The number is # a message sequence number. # - # ==== Capabilities + # ===== Capabilities # # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is # supported, the server's response should include a +COPYUID+ response code @@ -1091,7 +1109,7 @@ def copy(set, mailbox) # Similar to #copy, but +set+ contains unique identifiers. # - # ==== Capabilities + # ===== Capabilities # # +UIDPLUS+ affects #uid_copy the same way it affects #copy. def uid_copy(set, mailbox) @@ -1103,7 +1121,7 @@ def uid_copy(set, mailbox) # a number, an array of numbers, or a Range object. The number is # a message sequence number. # - # ==== Capabilities requirements + # ===== Capabilities requirements # # +MOVE+ [RFC6851[https://tools.ietf.org/html/rfc6851]] must be supported by # the server. @@ -1119,7 +1137,7 @@ def move(set, mailbox) # Similar to #move, but +set+ contains unique identifiers. # - # ==== Capabilities requirements + # ===== Capabilities requirements # # Same as #move: +MOVE+ [RFC6851[https://tools.ietf.org/html/rfc6851]] must # be supported by the server. +UIDPLUS+ also affects #uid_move the same way @@ -1171,8 +1189,7 @@ def remove_response_handler(handler) end # Similar to #search, but returns message sequence numbers in threaded - # format, as a Net::IMAP::ThreadMember tree. The supported algorithms - # are: + # format, as a ThreadMember tree. The supported algorithms are: # # ORDEREDSUBJECT:: split into single-level threads according to subject, # ordered by date. From c115a95a7159dc6036a4dae9feeaf27619b9ea0e Mon Sep 17 00:00:00 2001 From: nick evans Date: Fri, 11 Nov 2022 21:38:16 -0500 Subject: [PATCH 02/17] =?UTF-8?q?=F0=9F=93=9A=20Update=20capabilities=20do?= =?UTF-8?q?cumentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Document capabilities at the class-level, linking to #capability for details. * Add "Basic IMAP4rev1 capabilities" and "Using IMAP4rev1 extensions" sections to the #capability rdoc. * Add relevant capabilities to the rdoc for every command extension. * Update existing capability docs to the same consistent format. * Add TODO comment for unsupported LIST-EXTENDED --- lib/net/imap.rb | 186 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 163 insertions(+), 23 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index f1eae089..c4440007 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -70,6 +70,15 @@ module Net # UIDs have to be reassigned. An \IMAP client thus cannot # rearrange message orders. # + # === Server capabilities and protocol extensions + # + # Net::IMAP does not modify its behavior according to server + # #capability. Users of the class must check for required capabilities before + # issuing commands. Special care should be taken to follow all #capability + # requirements for #starttls, #login, and #authenticate. + # + # See the #capability method for more information. + # # == Examples of Usage # # === List sender and subject of all recent messages in the default mailbox @@ -428,16 +437,55 @@ def disconnected? # Sends a CAPABILITY command, and returns an array of # capabilities that the server supports. Each capability # is a string. - # See the {IANA IMAP capabilities registry}[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml] - # for a list of possible capabilities and their RFCs. + # + # See the {IANA IMAP4 capabilities + # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list + # of all standard capabilities, and their reference RFCs. # # >>> - # *Note* that the Net::IMAP class does not modify its + # *Note* that Net::IMAP does not currently modify its # behaviour according to the capabilities of the server; # it is up to the user of the class to ensure that # a certain capability is supported by a server before # using it. # + # Capability requirements—other than +IMAP4rev1+—are listed in the + # documentation for each command method. + # + # ===== Basic IMAP4rev1 capabilities + # + # All IMAP4rev1 servers must include +IMAP4rev1+ in their capabilities list. + # All IMAP4rev1 servers must _implement_ the +STARTTLS+, + # AUTH=PLAIN, and +LOGINDISABLED+ capabilities, and clients must + # respect their presence or absence. See the capabilites requirements on + # #starttls, #login, and #authenticate. + # + # ===== Using IMAP4rev1 extensions + # + # IMAP4rev1 servers must not activate incompatible behavior until an + # explicit client action invokes a capability, e.g. sending a command or + # command argument specific to that capability. Extensions with backward + # compatible behavior, such as response codes or mailbox attributes, may + # be sent at any time. + # + # Invoking capabilities which are unknown to Net::IMAP may cause unexpected + # behavior and errors, for example ResponseParseError is raised when unknown + # response syntax is received. Invoking commands or command parameters that + # are unsupported by the server may raise NoResponseError, BadResponseError, + # or cause other unexpected behavior. + # + # ===== Caching +CAPABILITY+ responses + # + # Servers may send their capability list, unsolicited, using the + # +CAPABILITY+ response code or an untagged +CAPABILITY+ response. These + # responses can be retrieved and cached using #responses or + # #add_response_handler. + # + # But cached capabilities _must_ be discarded after #starttls, #login, or + # #authenticate. The OK TaggedResponse to #login and #authenticate may + # include +CAPABILITY+ response code data, but the TaggedResponse for + # #starttls is sent clear-text and cannot be trusted. + # def capability synchronize do send_command("CAPABILITY") @@ -462,6 +510,11 @@ def capability # end # # See [ID[https://tools.ietf.org/html/rfc2971]] for field definitions. + # + # ===== Capabilities + # + # The server's capabilities must include +ID+ + # [RFC2971[https://tools.ietf.org/html/rfc2971]] def id(client_id=nil) synchronize do send_command("ID", ClientID.new(client_id)) @@ -481,6 +534,20 @@ def logout end # Sends a STARTTLS command to start TLS session. + # Sends a STARTTLS command to start a TLS session. + # + # ===== Capability + # + # The server's capabilities must include +STARTTLS+. + # + # Server capabilities may change after #starttls, #login, and #authenticate. + # Cached capabilities _must_ be invalidated after this method completes. + # + # The TaggedResponse to #starttls is sent clear-text, so the server must + # *not* send capabilities in the #starttls response and clients must + # not use them if they are sent. Servers will generally send an + # unsolicited untagged response immeditely _after_ #starttls completes. + # def starttls(options = {}, verify = true) send_command("STARTTLS") do |resp| if resp.kind_of?(TaggedResponse) && resp.name == "OK" @@ -529,6 +596,20 @@ def starttls(options = {}, verify = true) # # See Net::IMAP::Authenticators for more information on plugging in your # own authenticator. + # + # ==== Capabilities + # + # Clients MUST NOT attempt to #authenticate or #login when +LOGINDISABLED+ + # is listed with the capabilities. + # + # Clients MUST NOT attempt to authenticate with a mechanism unless + # "AUTH=#{mechanism}" for that mechanism is a server capability. + # + # Server capabilities may change after #starttls, #login, and #authenticate. + # Cached capabilities _must_ be invalidated after this method completes. + # The TaggedResponse to #authenticate may include updated capabilities in + # its ResponseCode. + # def authenticate(auth_type, *args) authenticator = self.class.authenticator(auth_type, *args) send_command("AUTHENTICATE", auth_type) do |resp| @@ -547,6 +628,16 @@ def authenticate(auth_type, *args) # of "LOGIN", #login does *not* use the login authenticator. # # A Net::IMAP::NoResponseError is raised if authentication fails. + # + # ==== Capabilities + # Clients MUST NOT attempt to #authenticate or #login when +LOGINDISABLED+ + # is listed with the capabilities. + # + # Server capabilities may change after #starttls, #login, and #authenticate. + # Cached capabilities _must_ be invalidated after this method completes. + # The TaggedResponse to #login may include updated capabilities in its + # ResponseCode. + # def login(user, password) send_command("LOGIN", user, password) end @@ -661,6 +752,10 @@ def unsubscribe(mailbox) # #=> [#, \\ # #, \\ # #] + # + #-- + # TODO: support LIST-EXTENDED extension [RFC5258]. Needed for IMAP4rev2. + #++ def list(refname, mailbox) synchronize do send_command("LIST", refname, mailbox) @@ -717,7 +812,10 @@ def list(refname, mailbox) # end # end # - # The NAMESPACE extension is described in [NAMESPACE[https://tools.ietf.org/html/rfc2342]] + # ===== Capabilities + # + # The server's capabilities must include +NAMESPACE+ + # [RFC2342[https://tools.ietf.org/html/rfc2342]] def namespace synchronize do send_command("NAMESPACE") @@ -750,6 +848,16 @@ def namespace # #=> [#, \\ # #, \\ # #] + # + # ===== Capabilities + # + # The server's capabilities must include +XLIST+, + # a deprecated Gmail extension (replaced by +SPECIAL-USE+). + #-- + # TODO: Net::IMAP doesn't yet have full SPECIAL-USE support. Supporting + # servers MAY return SPECIAL-USE attributes, but are not *required* to + # unless the SPECIAL-USE return option is supplied. + #++ def xlist(refname, mailbox) synchronize do send_command("XLIST", refname, mailbox) @@ -762,7 +870,10 @@ def xlist(refname, mailbox) # If this mailbox exists, it returns an array containing objects of type # MailboxQuotaRoot and MailboxQuota. # - # The QUOTA extension is described in [QUOTA[https://tools.ietf.org/html/rfc2087]] + # ===== Capabilities + # + # The server's capabilities must include +QUOTA+ + # [RFC2087[https://tools.ietf.org/html/rfc2087]]. def getquotaroot(mailbox) synchronize do send_command("GETQUOTAROOT", mailbox) @@ -778,7 +889,10 @@ def getquotaroot(mailbox) # MailboxQuota object is returned. This # command is generally only available to server admin. # - # The QUOTA extension is described in [QUOTA[https://tools.ietf.org/html/rfc2087]] + # ===== Capabilities + # + # The server's capabilities must include +QUOTA+ + # [RFC2087[https://tools.ietf.org/html/rfc2087]]. def getquota(mailbox) synchronize do send_command("GETQUOTA", mailbox) @@ -791,7 +905,10 @@ def getquota(mailbox) # mailbox. Typically one needs to be logged in as a server admin # for this to work. # - # The QUOTA extension is described in [QUOTA[https://tools.ietf.org/html/rfc2087]] + # ===== Capabilities + # + # The server's capabilities must include +QUOTA+ + # [RFC2087[https://tools.ietf.org/html/rfc2087]]. def setquota(mailbox, quota) if quota.nil? data = '()' @@ -805,7 +922,10 @@ def setquota(mailbox, quota) # +rights+ that user is to have on that mailbox. If +rights+ is nil, # then that user will be stripped of any rights to that mailbox. # - # The ACL extension is described in [ACL[https://tools.ietf.org/html/rfc4314]] + # ===== Capabilities + # + # The server's capabilities must include +ACL+ + # [RFC4314[https://tools.ietf.org/html/rfc4314]]. def setacl(mailbox, user, rights) if rights.nil? send_command("SETACL", mailbox, user, "") @@ -818,7 +938,10 @@ def setacl(mailbox, user, rights) # If this mailbox exists, an array containing objects of # MailboxACLItem will be returned. # - # The ACL extension is described in [ACL[https://tools.ietf.org/html/rfc4314]] + # ===== Capabilities + # + # The server's capabilities must include +ACL+ + # [RFC4314[https://tools.ietf.org/html/rfc4314]]. def getacl(mailbox) synchronize do send_command("GETACL", mailbox) @@ -959,10 +1082,10 @@ def expunge # #responses and this method returns them as an array of # sequence number integers. # - # ===== Capability requirement + # ===== Capabilities # - # +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] must be - # supported by the server. + # The server's capabilities must include +UIDPLUS+ + # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]]. def uid_expunge(uid_set) synchronize do send_command("UID EXPUNGE", MessageSet.new(uid_set)) @@ -1121,10 +1244,10 @@ def uid_copy(set, mailbox) # a number, an array of numbers, or a Range object. The number is # a message sequence number. # - # ===== Capabilities requirements + # ===== Capabilities # - # +MOVE+ [RFC6851[https://tools.ietf.org/html/rfc6851]] must be supported by - # the server. + # The server's capabilities must include +MOVE+ + # [RFC6851[https://tools.ietf.org/html/rfc6851]]. # # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is # also supported, the server's response should include a +COPYUID+ response @@ -1137,11 +1260,11 @@ def move(set, mailbox) # Similar to #move, but +set+ contains unique identifiers. # - # ===== Capabilities requirements + # ===== Capabilities # - # Same as #move: +MOVE+ [RFC6851[https://tools.ietf.org/html/rfc6851]] must - # be supported by the server. +UIDPLUS+ also affects #uid_move the same way - # it affects #move. + # Same as #move: The server's capabilities must include +MOVE+ + # [RFC6851[https://tools.ietf.org/html/rfc6851]]. +UIDPLUS+ also affects + # #uid_move the same way it affects #move. def uid_move(set, mailbox) copy_internal("UID MOVE", set, mailbox) end @@ -1154,14 +1277,20 @@ def uid_move(set, mailbox) # p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII") # #=> [6, 7, 8, 1] # - # The SORT extension is described in [SORT[https://tools.ietf.org/html/rfc5256]]. + # ===== Capabilities + # + # The server's capabilities must include +SORT+ + # [RFC5256[https://tools.ietf.org/html/rfc5256]]. def sort(sort_keys, search_keys, charset) return sort_internal("SORT", sort_keys, search_keys, charset) end # Similar to #sort, but returns an array of unique identifiers. # - # The SORT extension is described in [SORT[https://tools.ietf.org/html/rfc5256]]. + # ===== Capabilities + # + # The server's capabilities must include +SORT+ + # [RFC5256[https://tools.ietf.org/html/rfc5256]]. def uid_sort(sort_keys, search_keys, charset) return sort_internal("UID SORT", sort_keys, search_keys, charset) end @@ -1199,7 +1328,10 @@ def remove_response_handler(handler) # Unlike #search, +charset+ is a required argument. US-ASCII # and UTF-8 are sample values. # - # The THREAD extension is described in [THREAD[https://tools.ietf.org/html/rfc5256]]. + # ===== Capabilities + # + # The server's capabilities must include +THREAD+ + # [RFC5256[https://tools.ietf.org/html/rfc5256]]. def thread(algorithm, search_keys, charset) return thread_internal("THREAD", algorithm, search_keys, charset) end @@ -1207,7 +1339,10 @@ def thread(algorithm, search_keys, charset) # Similar to #thread, but returns unique identifiers instead of # message sequence numbers. # - # The THREAD extension is described in [THREAD[https://tools.ietf.org/html/rfc5256]]. + # ===== Capabilities + # + # The server's capabilities must include +THREAD+ + # [RFC5256[https://tools.ietf.org/html/rfc5256]]. def uid_thread(algorithm, search_keys, charset) return thread_internal("UID THREAD", algorithm, search_keys, charset) end @@ -1226,6 +1361,11 @@ def uid_thread(algorithm, search_keys, charset) # ... # end # end + # + # ===== Capabilities + # + # The server's capabilities must include +IDLE+ + # [RFC2177[https://tools.ietf.org/html/rfc2177]]. def idle(timeout = nil, &response_handler) raise LocalJumpError, "no block given" unless response_handler From c2b35283e9b092c3a23be847ee7af38092a8231c Mon Sep 17 00:00:00 2001 From: nick evans Date: Sun, 13 Nov 2022 00:53:03 -0500 Subject: [PATCH 03/17] =?UTF-8?q?=F0=9F=93=9A=20Document=20the=20RFC=20spe?= =?UTF-8?q?cification=20for=20each=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every IMAP command method now links to the RFC section that defines it. --- lib/net/imap.rb | 282 ++++++++++++++++++++++++++++-------------------- 1 file changed, 163 insertions(+), 119 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index c4440007..450b6841 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -434,9 +434,9 @@ def disconnected? return @sock.closed? end - # Sends a CAPABILITY command, and returns an array of - # capabilities that the server supports. Each capability - # is a string. + # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1] + # and returns an array of capabilities that the server supports. Each + # capability is a string. # # See the {IANA IMAP4 capabilities # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list @@ -493,8 +493,9 @@ def capability end end - # Sends an ID command, and returns a hash of the server's - # response, or nil if the server does not identify itself. + # Sends an {ID command [RFC2971 §3.1]}[https://www.rfc-editor.org/rfc/rfc2971#section-3.1] + # 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: @@ -522,19 +523,21 @@ def id(client_id=nil) end end - # Sends a NOOP command to the server. It does nothing. + # Sends a {NOOP command [IMAP4rev1 §6.1.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.2] + # to the server. def noop send_command("NOOP") end - # Sends a LOGOUT command to inform the server that the client is - # done with the connection. + # Sends a {LOGOUT command [IMAP4rev1 §6.1.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.3] + # to inform the command to inform the server that the client is done with + # the connection. def logout send_command("LOGOUT") end - # Sends a STARTTLS command to start TLS session. - # Sends a STARTTLS command to start a TLS session. + # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1] + # to start a TLS session. # # ===== Capability # @@ -562,9 +565,11 @@ def starttls(options = {}, verify = true) end end - # Sends an AUTHENTICATE command to authenticate the client. - # The +auth_type+ parameter is a string that represents - # the authentication mechanism to be used. Currently Net::IMAP + # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2] + # to authenticate the client. + # + # The +auth_type+ parameter is a string that + # represents the authentication mechanism to be used. Currently Net::IMAP # supports the following mechanisms: # # PLAIN:: Login using cleartext user and password. Secure with TLS. @@ -622,10 +627,10 @@ def authenticate(auth_type, *args) end end - # Sends a LOGIN command to identify the client and carries - # the plaintext +password+ authenticating this +user+. Note - # that, unlike calling #authenticate with an +auth_type+ - # of "LOGIN", #login does *not* use the login authenticator. + # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3] + # to identify the client and carries the plaintext +password+ authenticating + # this +user+. Note that, unlike calling #authenticate with an +auth_type+ + # of "LOGIN", #login does *not* use the LoginAuthenticator. # # A Net::IMAP::NoResponseError is raised if authentication fails. # @@ -642,8 +647,8 @@ def login(user, password) send_command("LOGIN", user, password) end - # Sends a SELECT command to select a +mailbox+ so that messages - # in the +mailbox+ can be accessed. + # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1] + # to select a +mailbox+ so that messages in the +mailbox+ can be accessed. # # After you have selected a mailbox, you may retrieve the number of items in # that mailbox from imap.responses["EXISTS"][-1], and the number of @@ -669,9 +674,10 @@ def select(mailbox) end end - # Sends a EXAMINE command to select a +mailbox+ so that messages - # in the +mailbox+ can be accessed. Behaves the same as #select, - # except that the selected +mailbox+ is identified as read-only. + # Sends a {EXAMINE command [IMAP4rev1 §6.3.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.2] + # to select a +mailbox+ so that messages in the +mailbox+ can be accessed. + # Behaves the same as #select, except that the selected +mailbox+ is + # identified as read-only. # # A Net::IMAP::NoResponseError is raised if the mailbox does not # exist or is for some reason non-examinable. @@ -682,7 +688,8 @@ def examine(mailbox) end end - # Sends a CREATE command to create a new +mailbox+. + # Sends a {CREATE command [IMAP4rev1 §6.3.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.3] + # to create a new +mailbox+. # # A Net::IMAP::NoResponseError is raised if a mailbox with that name # cannot be created. @@ -690,7 +697,8 @@ def create(mailbox) send_command("CREATE", mailbox) end - # Sends a DELETE command to remove the +mailbox+. + # Sends a {DELETE command [IMAP4rev1 §6.3.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.4] + # to remove the +mailbox+. # # A Net::IMAP::NoResponseError is raised if a mailbox with that name # cannot be deleted, either because it does not exist or because the @@ -699,8 +707,8 @@ def delete(mailbox) send_command("DELETE", mailbox) end - # Sends a RENAME command to change the name of the +mailbox+ to - # +newname+. + # Sends a {RENAME command [IMAP4rev1 §6.3.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.5] + # to change the name of the +mailbox+ to +newname+. # # A Net::IMAP::NoResponseError is raised if a mailbox with the # name +mailbox+ cannot be renamed to +newname+ for whatever @@ -710,9 +718,9 @@ def rename(mailbox, newname) send_command("RENAME", mailbox, newname) end - # Sends a SUBSCRIBE command to add the specified +mailbox+ name to - # the server's set of "active" or "subscribed" mailboxes as returned - # by #lsub. + # Sends a {SUBSCRIBE command [IMAP4rev1 §6.3.6]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.6] + # to add the specified +mailbox+ name to the server's set of "active" or + # "subscribed" mailboxes as returned by #lsub. # # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be # subscribed to; for instance, because it does not exist. @@ -720,8 +728,9 @@ def subscribe(mailbox) send_command("SUBSCRIBE", mailbox) end - # Sends a UNSUBSCRIBE command to remove the specified +mailbox+ name - # from the server's set of "active" or "subscribed" mailboxes. + # Sends an {UNSUBSCRIBE command [IMAP4rev1 §6.3.7]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.7] + # to remove the specified +mailbox+ name from the server's set of "active" + # or "subscribed" mailboxes. # # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be # unsubscribed from; for instance, because the client is not currently @@ -730,15 +739,15 @@ def unsubscribe(mailbox) send_command("UNSUBSCRIBE", mailbox) end - # Sends a LIST 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 - # in a directory-based mailbox hierarchy). +mailbox+ specifies - # a mailbox or (via wildcards) mailboxes under that context. - # Two wildcards may be used in +mailbox+: '*', which matches - # all characters *including* the hierarchy delimiter (for instance, - # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%', - # which matches all characters *except* the hierarchy delimiter. + # Sends a {LIST command [IMAP4rev1 §6.3.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.8] + # 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 in a directory-based mailbox hierarchy). +mailbox+ specifies a + # mailbox or (via wildcards) mailboxes under that context. Two wildcards + # may be used in +mailbox+: '*', which matches all characters *including* + # the hierarchy delimiter (for instance, '/' on a UNIX-hosted + # directory-based mailbox hierarchy); and '%', which matches all characters + # *except* the hierarchy delimiter. # # If +refname+ is empty, +mailbox+ is used directly to determine # which mailboxes to match. If +mailbox+ is empty, the root @@ -763,10 +772,10 @@ def list(refname, mailbox) end end - # Sends a NAMESPACE command 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. + # Sends a {NAMESPACE command [RFC2342 §5]}[https://www.rfc-editor.org/rfc/rfc2342#section-5] + # 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. # # The NAMESPACE extension predates [IMAP4rev1[https://tools.ietf.org/html/rfc2501]], # so most IMAP servers support it. Many popular IMAP servers are configured @@ -865,10 +874,10 @@ def xlist(refname, mailbox) end end - # Sends the GETQUOTAROOT command along with the specified +mailbox+. - # This command is generally available to both admin and user. - # If this mailbox exists, it returns an array containing objects of type - # MailboxQuotaRoot and MailboxQuota. + # Sends a {GETQUOTAROOT command [RFC2087 §4.3]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.3] + # along with the specified +mailbox+. This command is generally available + # to both admin and user. If this mailbox exists, it returns an array + # containing objects of type MailboxQuotaRoot and MailboxQuota. # # ===== Capabilities # @@ -884,10 +893,10 @@ def getquotaroot(mailbox) end end - # Sends the GETQUOTA command along with specified +mailbox+. - # If this mailbox exists, then an array containing a - # MailboxQuota object is returned. This - # command is generally only available to server admin. + # Sends a {GETQUOTA command [RFC2087 §4.2]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.2] + # along with specified +mailbox+. If this mailbox exists, then an array + # containing a MailboxQuota object is returned. This command is generally + # only available to server admin. # # ===== Capabilities # @@ -900,10 +909,10 @@ def getquota(mailbox) end end - # Sends a SETQUOTA command along with the specified +mailbox+ and - # +quota+. If +quota+ is nil, then +quota+ will be unset for that - # mailbox. Typically one needs to be logged in as a server admin - # for this to work. + # Sends a {SETQUOTA command [RFC2087 §4.1]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.1] + # along with the specified +mailbox+ and +quota+. If +quota+ is nil, then + # +quota+ will be unset for that mailbox. Typically one needs to be logged + # in as a server admin for this to work. # # ===== Capabilities # @@ -918,9 +927,10 @@ def setquota(mailbox, quota) send_command("SETQUOTA", mailbox, RawData.new(data)) end - # Sends the SETACL command along with +mailbox+, +user+ and the - # +rights+ that user is to have on that mailbox. If +rights+ is nil, - # then that user will be stripped of any rights to that mailbox. + # Sends a {SETACL command [RFC4314 §3.1]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.1] + # along with +mailbox+, +user+ and the +rights+ that user is to have on that + # mailbox. If +rights+ is nil, then that user will be stripped of any + # rights to that mailbox. # # ===== Capabilities # @@ -934,9 +944,9 @@ def setacl(mailbox, user, rights) end end - # Send the GETACL command along with a specified +mailbox+. - # If this mailbox exists, an array containing objects of - # MailboxACLItem will be returned. + # Sends a {GETACL command [RFC4314 §3.3]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.3] + # along with a specified +mailbox+. If this mailbox exists, an array + # containing objects of MailboxACLItem will be returned. # # ===== Capabilities # @@ -949,10 +959,10 @@ def getacl(mailbox) end end - # Sends a LSUB command, and returns a subset of names from the set - # of names that the user has declared as being "active" or - # "subscribed." +refname+ and +mailbox+ are interpreted as - # for #list. + # Sends a {LSUB command [IMAP4rev1 §6.3.9]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.9] + # and returns a subset of names from the set of names that the user has + # declared as being "active" or "subscribed." +refname+ and +mailbox+ are + # interpreted as for #list. # # The return value is an array of MailboxList objects. def lsub(refname, mailbox) @@ -962,9 +972,10 @@ def lsub(refname, mailbox) end end - # Sends a STATUS command, and returns the status of the indicated - # +mailbox+. +attr+ is a list of one or more attributes whose - # statuses are to be requested. Supported attributes include: + # Sends a {STATUS commands [IMAP4rev1 §6.3.10]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.10] + # and returns the status of the indicated +mailbox+. +attr+ is a list of one + # or more attributes whose statuses are to be requested. Supported + # attributes include: # # MESSAGES:: the number of messages in the mailbox. # RECENT:: the number of recent messages in the mailbox. @@ -985,10 +996,10 @@ def status(mailbox, attr) end end - # Sends a APPEND command to append the +message+ to the end of - # the +mailbox+. The optional +flags+ argument is an array of - # flags initially passed to the new message. The optional - # +date_time+ argument specifies the creation time to assign to the + # Sends an {APPEND command [IMAP4rev1 §6.3.11]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.11] + # to append the +message+ to the end of the +mailbox+. The optional +flags+ + # argument is an array of flags initially passed to the new message. The + # optional +date_time+ argument specifies the creation time to assign to the # new message; it defaults to the current time. # # For example: @@ -1025,26 +1036,27 @@ def append(mailbox, message, flags = nil, date_time = nil) send_command("APPEND", mailbox, *args) end - # Sends a CHECK command to request a checkpoint of the currently - # selected mailbox. This performs implementation-specific - # housekeeping; for instance, reconciling the mailbox's - # in-memory and on-disk state. + # Sends a {CHECK command [IMAP4rev1 §6.4.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.1] + # to request a checkpoint of the currently selected mailbox. This performs + # implementation-specific housekeeping; for instance, reconciling the + # mailbox's in-memory and on-disk state. def check send_command("CHECK") end - # Sends a CLOSE command to close the currently selected mailbox. - # The CLOSE command permanently removes from the mailbox all - # messages that have the \Deleted flag set. + # Sends a {CLOSE command [IMAP4rev1 §6.4.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.2] + # to close the currently selected mailbox. The CLOSE command permanently + # removes from the mailbox all messages that have the \\Deleted + # flag set. def close send_command("CLOSE") end - # Sends an {UNSELECT command [IMAP4rev2 - # §6.4.2]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.2] to free the - # session resources for a mailbox and return to the "_authenticated_" state. - # This is the same as #close, except that \\Deleted messages are - # not removed from the mailbox. + # Sends an {UNSELECT command [RFC3691 §2]}[https://www.rfc-editor.org/rfc/rfc3691#section-3] + # {[IMAP4rev2 §6.4.2]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.2] + # to free the session resources for a mailbox and return to the + # "_authenticated_" state. This is the same as #close, except that + # \\Deleted messages are not removed from the mailbox. # # ===== Capabilities # @@ -1054,6 +1066,7 @@ def unselect send_command("UNSELECT") end + # Sends an {EXPUNGE command [IMAP4rev1 §6.4.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.3] # Sends a EXPUNGE command to permanently remove from the currently # selected mailbox all messages that have the \Deleted flag set. def expunge @@ -1063,10 +1076,10 @@ def expunge end end - # Similar to #expunge, but takes a set of unique identifiers as - # argument. Sends a UID EXPUNGE command to permanently remove all - # messages that have both the \\Deleted flag set and a UID that is - # included in +uid_set+. + # Sends a {UID EXPUNGE command [RFC4315 §2.1]}[https://www.rfc-editor.org/rfc/rfc4315#section-2.1] + # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9] + # to permanently remove all messages that have both the \\Deleted + # flag set and a UID that is included in +uid_set+. # # By using UID EXPUNGE instead of EXPUNGE when resynchronizing with # the server, the client can ensure that it does not inadvertantly @@ -1093,11 +1106,11 @@ def uid_expunge(uid_set) end end - # Sends a SEARCH command to search the mailbox for messages that - # match the given searching criteria, and returns message sequence - # numbers. +keys+ can either be a string holding the entire - # search string, or a single-dimension array of search keywords and - # arguments. + # Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4] + # to search the mailbox for messages that match the given searching + # criteria, and returns message sequence numbers. +keys+ can either be a + # string holding the entire search string, or a single-dimension array of + # search keywords and arguments. # # ===== Search criteria # @@ -1143,13 +1156,17 @@ def search(keys, charset = nil) return search_internal("SEARCH", keys, charset) end - # Similar to #search, but returns unique identifiers. + # Sends a {UID SEARCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] + # to search the mailbox for messages that match the given searching + # criteria, and returns unique identifiers (UIDs). + # + # See #search for documentation of search criteria. def uid_search(keys, charset = nil) return search_internal("UID SEARCH", keys, charset) end - # Sends a FETCH command to retrieve data associated with a message - # in the mailbox. + # Sends a {FETCH command [IMAP4rev1 §6.4.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.5] + # to retrieve data associated with a message in the mailbox. # # The +set+ parameter is a number or a range between two numbers, # or an array of those. The number is a message sequence number, @@ -1187,18 +1204,22 @@ def fetch(set, attr, mod = nil) return fetch_internal("FETCH", set, attr, mod) end - # Similar to #fetch, but +set+ contains unique identifiers. + # Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] + # to retrieve data associated with a message in the mailbox. + # + # Similar to #fetch, but the +set+ parameter contains unique identifiers + # instead of message sequence numbers. def uid_fetch(set, attr, mod = nil) return fetch_internal("UID FETCH", set, attr, mod) end - # Sends a STORE command to alter data associated with messages - # in the mailbox, in particular their flags. The +set+ parameter - # is a number, an array of numbers, or a Range object. Each number - # is a message sequence number. +attr+ is the name of a data item - # to store: 'FLAGS' will replace the message's flag list - # with the provided one, '+FLAGS' will add the provided flags, - # and '-FLAGS' will remove them. +flags+ is a list of flags. + # Sends a {STORE command [IMAP4rev1 §6.4.6]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.6] + # to alter data associated with messages in the mailbox, in particular their + # flags. The +set+ parameter is a number, an array of numbers, or a Range + # object. Each number is a message sequence number. +attr+ is the name of a + # data item to store: 'FLAGS' will replace the message's flag list with the + # provided one, '+FLAGS' will add the provided flags, and '-FLAGS' will + # remove them. +flags+ is a list of flags. # # The return value is an array of FetchData. For example: # @@ -1210,15 +1231,20 @@ def store(set, attr, flags) return store_internal("STORE", set, attr, flags) end - # Similar to #store, but +set+ contains unique identifiers. + # Sends a {UID STORE command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] + # to alter data associated with messages in the mailbox, in particular their + # flags. + # + # Similar to #store, but +set+ contains unique identifiers instead of + # message sequence numbers. def uid_store(set, attr, flags) return store_internal("UID STORE", set, attr, flags) end - # Sends a COPY command to copy the specified message(s) to the end - # of the specified destination +mailbox+. The +set+ parameter is - # a number, an array of numbers, or a Range object. The number is - # a message sequence number. + # Sends a {COPY command [IMAP4rev1 §6.4.7]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.7] + # to copy the specified message(s) to the end of the specified destination + # +mailbox+. The +set+ parameter is a number, an array of numbers, or a + # Range object. The number is a message sequence number. # # ===== Capabilities # @@ -1230,6 +1256,10 @@ def copy(set, mailbox) copy_internal("COPY", set, mailbox) end + # Sends a {UID COPY command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] + # to copy the specified message(s) to the end of the specified destination + # +mailbox+. + # # Similar to #copy, but +set+ contains unique identifiers. # # ===== Capabilities @@ -1239,10 +1269,11 @@ def uid_copy(set, mailbox) copy_internal("UID COPY", set, mailbox) end - # Sends a MOVE command to move the specified message(s) to the end - # of the specified destination +mailbox+. The +set+ parameter is - # a number, an array of numbers, or a Range object. The number is - # a message sequence number. + # Sends a {MOVE command [RFC6851 §3.1]}[https://www.rfc-editor.org/rfc/rfc6851#section-3.1] + # {[IMAP4rev2 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.8] + # to move the specified message(s) to the end of the specified destination + # +mailbox+. The +set+ parameter is a number, an array of numbers, or a + # Range object. The number is a message sequence number. # # ===== Capabilities # @@ -1258,6 +1289,11 @@ def move(set, mailbox) copy_internal("MOVE", set, mailbox) end + # Sends a {UID MOVE command [RFC6851 §3.2]}[https://www.rfc-editor.org/rfc/rfc6851#section-3.2] + # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9] + # to move the specified message(s) to the end of the specified destination + # +mailbox+. + # # Similar to #move, but +set+ contains unique identifiers. # # ===== Capabilities @@ -1269,8 +1305,11 @@ def uid_move(set, mailbox) copy_internal("UID MOVE", set, mailbox) end - # Sends a SORT command to sort messages in the mailbox. - # Returns an array of message sequence numbers. For example: + # Sends a {SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] + # to sort messages in the mailbox. Returns an array of message sequence + # numbers. + # + # ===== For example: # # p imap.sort(["FROM"], ["ALL"], "US-ASCII") # #=> [1, 2, 3, 5, 6, 7, 8, 4, 9] @@ -1285,7 +1324,8 @@ def sort(sort_keys, search_keys, charset) return sort_internal("SORT", sort_keys, search_keys, charset) end - # Similar to #sort, but returns an array of unique identifiers. + # Sends a {UID SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] + # to sort messages in the mailbox. Returns an array of unique identifiers. # # ===== Capabilities # @@ -1317,6 +1357,7 @@ def remove_response_handler(handler) @response_handlers.delete(handler) end + # Sends a {THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] # Similar to #search, but returns message sequence numbers in threaded # format, as a ThreadMember tree. The supported algorithms are: # @@ -1336,6 +1377,7 @@ def thread(algorithm, search_keys, charset) return thread_internal("THREAD", algorithm, search_keys, charset) end + # Sends a {UID THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] # Similar to #thread, but returns unique identifiers instead of # message sequence numbers. # @@ -1347,8 +1389,10 @@ def uid_thread(algorithm, search_keys, charset) return thread_internal("UID THREAD", algorithm, search_keys, charset) end - # Sends an IDLE command that waits for notifications of new or expunged - # messages. Yields responses from the server during the IDLE. + # Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3] + # {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13] + # that waits for notifications of new or expunged messages. Yields + # responses from the server during the IDLE. # # Use #idle_done to leave IDLE. # From 3c90cec4da4a9a6bb719d1be859ece57c219d66d Mon Sep 17 00:00:00 2001 From: nick evans Date: Thu, 15 Dec 2022 09:03:55 -0500 Subject: [PATCH 04/17] =?UTF-8?q?=F0=9F=93=9A=20Add=20"Related:"=20rdoc=20?= =?UTF-8?q?for=20most=20Net::IMAP=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are a lot of IMAP commands! This makes it easier to navigate between them. Hopefully this makes it easier to learn the IMAP4rev1 protocol and its extensions, as well. --- lib/net/imap.rb | 96 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 450b6841..8510c234 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -407,6 +407,8 @@ class << self end # Disconnects from the server. + # + # Related: #logout def disconnect return if disconnected? begin @@ -430,6 +432,8 @@ def disconnect end # Returns true if disconnected from the server. + # + # Related: #logout, #disconnect def disconnected? return @sock.closed? end @@ -525,6 +529,8 @@ def id(client_id=nil) # Sends a {NOOP command [IMAP4rev1 §6.1.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.2] # to the server. + # + # Related: #idle, #check def noop send_command("NOOP") end @@ -532,6 +538,8 @@ def noop # Sends a {LOGOUT command [IMAP4rev1 §6.1.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.3] # to inform the command to inform the server that the client is done with # the connection. + # + # Related: #disconnect def logout send_command("LOGOUT") end @@ -539,6 +547,8 @@ def logout # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1] # to start a TLS session. # + # Related: Net::IMAP.new, #login, #authenticate + # # ===== Capability # # The server's capabilities must include +STARTTLS+. @@ -602,6 +612,8 @@ def starttls(options = {}, verify = true) # See Net::IMAP::Authenticators for more information on plugging in your # own authenticator. # + # Related: #login, #starttls + # # ==== Capabilities # # Clients MUST NOT attempt to #authenticate or #login when +LOGINDISABLED+ @@ -634,6 +646,8 @@ def authenticate(auth_type, *args) # # A Net::IMAP::NoResponseError is raised if authentication fails. # + # Related: #authenticate, #starttls + # # ==== Capabilities # Clients MUST NOT attempt to #authenticate or #login when +LOGINDISABLED+ # is listed with the capabilities. @@ -660,6 +674,8 @@ def login(user, password) # A Net::IMAP::NoResponseError is raised if the mailbox does not # exist or is for some reason non-selectable. # + # Related: #examine + # # ===== Capabilities # # If [UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]] is supported, @@ -681,6 +697,8 @@ def select(mailbox) # # A Net::IMAP::NoResponseError is raised if the mailbox does not # exist or is for some reason non-examinable. + # + # Related: #select def examine(mailbox) synchronize do @responses.clear @@ -693,6 +711,8 @@ def examine(mailbox) # # A Net::IMAP::NoResponseError is raised if a mailbox with that name # cannot be created. + # + # Related: #rename, #delete def create(mailbox) send_command("CREATE", mailbox) end @@ -703,6 +723,8 @@ def create(mailbox) # A Net::IMAP::NoResponseError is raised if a mailbox with that name # cannot be deleted, either because it does not exist or because the # client does not have permission to delete it. + # + # Related: #create, #rename def delete(mailbox) send_command("DELETE", mailbox) end @@ -714,6 +736,8 @@ def delete(mailbox) # name +mailbox+ cannot be renamed to +newname+ for whatever # reason; for instance, because +mailbox+ does not exist, or # because there is already a mailbox with the name +newname+. + # + # Related: #create, #delete def rename(mailbox, newname) send_command("RENAME", mailbox, newname) end @@ -724,6 +748,8 @@ def rename(mailbox, newname) # # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be # subscribed to; for instance, because it does not exist. + # + # Related: #unsubscribe, #lsub, #list def subscribe(mailbox) send_command("SUBSCRIBE", mailbox) end @@ -735,6 +761,8 @@ def subscribe(mailbox) # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be # unsubscribed from; for instance, because the client is not currently # subscribed to it. + # + # Related: #subscribe, #lsub, #list def unsubscribe(mailbox) send_command("UNSUBSCRIBE", mailbox) end @@ -753,7 +781,11 @@ def unsubscribe(mailbox) # which mailboxes to match. If +mailbox+ is empty, the root # name of +refname+ and the hierarchy delimiter are returned. # - # The return value is an array of MailboxList. For example: + # The return value is an array of MailboxList. + # + # Related: #lsub, MailboxList + # + # ===== For example: # # imap.create("foo/bar") # imap.create("foo/baz") @@ -806,7 +838,9 @@ def list(refname, mailbox) # of Namespace objects. These arrays will be empty when the # server responds with nil. # - # For example: + # Related: #list, Namespaces, Namespace + # + # ===== For example: # # capabilities = imap.capability # if capabilities.include?("NAMESPACE") @@ -858,6 +892,8 @@ def namespace # #, \\ # #] # + # Related: #list, MailboxList + # # ===== Capabilities # # The server's capabilities must include +XLIST+, @@ -879,6 +915,8 @@ def xlist(refname, mailbox) # to both admin and user. If this mailbox exists, it returns an array # containing objects of type MailboxQuotaRoot and MailboxQuota. # + # Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota + # # ===== Capabilities # # The server's capabilities must include +QUOTA+ @@ -898,6 +936,8 @@ def getquotaroot(mailbox) # containing a MailboxQuota object is returned. This command is generally # only available to server admin. # + # Related: #getquotaroot, #setquota, MailboxQuota + # # ===== Capabilities # # The server's capabilities must include +QUOTA+ @@ -914,6 +954,8 @@ def getquota(mailbox) # +quota+ will be unset for that mailbox. Typically one needs to be logged # in as a server admin for this to work. # + # Related: #getquota, #getquotaroot + # # ===== Capabilities # # The server's capabilities must include +QUOTA+ @@ -932,6 +974,8 @@ def setquota(mailbox, quota) # mailbox. If +rights+ is nil, then that user will be stripped of any # rights to that mailbox. # + # Related: #getacl + # # ===== Capabilities # # The server's capabilities must include +ACL+ @@ -948,6 +992,8 @@ def setacl(mailbox, user, rights) # along with a specified +mailbox+. If this mailbox exists, an array # containing objects of MailboxACLItem will be returned. # + # Related: #setacl, MailboxACLItem + # # ===== Capabilities # # The server's capabilities must include +ACL+ @@ -965,6 +1011,8 @@ def getacl(mailbox) # interpreted as for #list. # # The return value is an array of MailboxList objects. + # + # Related: #subscribe, #unsubscribe, #list, MailboxList def lsub(refname, mailbox) synchronize do send_command("LSUB", refname, mailbox) @@ -1040,6 +1088,8 @@ def append(mailbox, message, flags = nil, date_time = nil) # to request a checkpoint of the currently selected mailbox. This performs # implementation-specific housekeeping; for instance, reconciling the # mailbox's in-memory and on-disk state. + # + # Related: #idle, #noop def check send_command("CHECK") end @@ -1048,6 +1098,8 @@ def check # to close the currently selected mailbox. The CLOSE command permanently # removes from the mailbox all messages that have the \\Deleted # flag set. + # + # Related: #unselect def close send_command("CLOSE") end @@ -1058,6 +1110,8 @@ def close # "_authenticated_" state. This is the same as #close, except that # \\Deleted messages are not removed from the mailbox. # + # Related: #close + # # ===== Capabilities # # The server's capabilities must include +UNSELECT+ @@ -1069,6 +1123,8 @@ def unselect # Sends an {EXPUNGE command [IMAP4rev1 §6.4.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.3] # Sends a EXPUNGE command to permanently remove from the currently # selected mailbox all messages that have the \Deleted flag set. + # + # Related: #uid_expunge def expunge synchronize do send_command("EXPUNGE") @@ -1095,6 +1151,8 @@ def expunge # #responses and this method returns them as an array of # sequence number integers. # + # Related: #expunge + # # ===== Capabilities # # The server's capabilities must include +UIDPLUS+ @@ -1112,6 +1170,8 @@ def uid_expunge(uid_set) # string holding the entire search string, or a single-dimension array of # search keywords and arguments. # + # Related: #uid_search + # # ===== Search criteria # # The following are some common search criteria; @@ -1183,7 +1243,9 @@ def uid_search(keys, charset = nil) # The return value is an array of FetchData or nil # (instead of an empty array) if there is no matching message. # - # For example: + # Related: #uid_search, FetchData + # + # ===== For example: # # p imap.fetch(6..8, "UID") # #=> [#98}>, \\ @@ -1209,6 +1271,8 @@ def fetch(set, attr, mod = nil) # # Similar to #fetch, but the +set+ parameter contains unique identifiers # instead of message sequence numbers. + # + # Related: #fetch, FetchData def uid_fetch(set, attr, mod = nil) return fetch_internal("UID FETCH", set, attr, mod) end @@ -1221,7 +1285,11 @@ def uid_fetch(set, attr, mod = nil) # provided one, '+FLAGS' will add the provided flags, and '-FLAGS' will # remove them. +flags+ is a list of flags. # - # The return value is an array of FetchData. For example: + # The return value is an array of FetchData + # + # Related: #uid_store + # + # ===== For example: # # p imap.store(6..8, "+FLAGS", [:Deleted]) # #=> [#[:Seen, :Deleted]}>, \\ @@ -1237,6 +1305,8 @@ def store(set, attr, flags) # # Similar to #store, but +set+ contains unique identifiers instead of # message sequence numbers. + # + # Related: #store def uid_store(set, attr, flags) return store_internal("UID STORE", set, attr, flags) end @@ -1246,6 +1316,8 @@ def uid_store(set, attr, flags) # +mailbox+. The +set+ parameter is a number, an array of numbers, or a # Range object. The number is a message sequence number. # + # Related: #uid_copy + # # ===== Capabilities # # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is @@ -1275,6 +1347,8 @@ def uid_copy(set, mailbox) # +mailbox+. The +set+ parameter is a number, an array of numbers, or a # Range object. The number is a message sequence number. # + # Related: #uid_move + # # ===== Capabilities # # The server's capabilities must include +MOVE+ @@ -1296,6 +1370,8 @@ def move(set, mailbox) # # Similar to #move, but +set+ contains unique identifiers. # + # Related: #move + # # ===== Capabilities # # Same as #move: The server's capabilities must include +MOVE+ @@ -1309,6 +1385,8 @@ def uid_move(set, mailbox) # to sort messages in the mailbox. Returns an array of message sequence # numbers. # + # Related: #uid_sort, #search, #uid_search, #thread, #uid_thread + # # ===== For example: # # p imap.sort(["FROM"], ["ALL"], "US-ASCII") @@ -1327,6 +1405,8 @@ def sort(sort_keys, search_keys, charset) # Sends a {UID SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] # to sort messages in the mailbox. Returns an array of unique identifiers. # + # Related: #sort, #search, #uid_search, #thread, #uid_thread + # # ===== Capabilities # # The server's capabilities must include +SORT+ @@ -1369,6 +1449,8 @@ def remove_response_handler(handler) # Unlike #search, +charset+ is a required argument. US-ASCII # and UTF-8 are sample values. # + # Related: #uid_thread, #search, #uid_search, #sort, #uid_sort + # # ===== Capabilities # # The server's capabilities must include +THREAD+ @@ -1381,6 +1463,8 @@ def thread(algorithm, search_keys, charset) # Similar to #thread, but returns unique identifiers instead of # message sequence numbers. # + # Related: #thread, #search, #uid_search, #sort, #uid_sort + # # ===== Capabilities # # The server's capabilities must include +THREAD+ @@ -1406,6 +1490,8 @@ def uid_thread(algorithm, search_keys, charset) # end # end # + # Related: #idle_done, #noop, #check + # # ===== Capabilities # # The server's capabilities must include +IDLE+ @@ -1440,6 +1526,8 @@ def idle(timeout = nil, &response_handler) end # Leaves IDLE. + # + # Related: #idle def idle_done synchronize do if @idle_done_cond.nil? From b0e91f70b8e678a7ebd5e94324fe1b62a8dbdf2f Mon Sep 17 00:00:00 2001 From: nick evans Date: Fri, 11 Nov 2022 11:40:28 -0500 Subject: [PATCH 05/17] =?UTF-8?q?=F0=9F=93=9A=20Document=20UIDPLUS=20comma?= =?UTF-8?q?nds,=20response=20codes,=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Link to UIDPlusData for commands that can return it: `#append`, `#copy`, and `#move` * Update `#uid_expunge` to avoid "UID set", which has slightly different semantics from "sequence set of UIDs". --- lib/net/imap.rb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 8510c234..bb895515 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -1067,9 +1067,10 @@ def status(mailbox, attr) # ===== Capabilities # # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is - # supported, the server's response should include a +APPENDUID+ response - # code with the UIDVALIDITY of the destination mailbox and the assigned UID - # of the appended message. + # supported and the destination supports persistent UIDs, the server's + # response should include an +APPENDUID+ response code with UIDPlusData. + # This will report the UIDVALIDITY of the destination mailbox and the + # assigned UID of the appended message. # #-- # TODO: add MULTIAPPEND support @@ -1145,7 +1146,7 @@ def expunge # # *Note:* # >>> - # Although the command takes a +uid_set+ for its argument, the + # Although the command takes a set of UIDs for its argument, the # server still returns regular EXPUNGE responses, which contain # a sequence number. These will be deleted from # #responses and this method returns them as an array of @@ -1322,8 +1323,9 @@ def uid_store(set, attr, flags) # # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is # supported, the server's response should include a +COPYUID+ response code - # with the UIDVALIDITY of the destination mailbox, the UID set of the source - # messages, and the assigned UID set of the moved messages. + # with UIDPlusData. This will report the UIDVALIDITY of the destination + # mailbox, the UID set of the source messages, and the assigned UID set of + # the moved messages. def copy(set, mailbox) copy_internal("COPY", set, mailbox) end @@ -1355,9 +1357,10 @@ def uid_copy(set, mailbox) # [RFC6851[https://tools.ietf.org/html/rfc6851]]. # # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is - # also supported, the server's response should include a +COPYUID+ response - # code with the UIDVALIDITY of the destination mailbox, the UID set of the - # source messages, and the assigned UID set of the moved messages. + # supported, the server's response should include a +COPYUID+ response code + # with UIDPlusData. This will report the UIDVALIDITY of the destination + # mailbox, the UID set of the source messages, and the assigned UID set of + # the moved messages. # def move(set, mailbox) copy_internal("MOVE", set, mailbox) From be253f386f3f9fe4aea345911d860712e8187037 Mon Sep 17 00:00:00 2001 From: nick evans Date: Mon, 19 Dec 2022 10:29:38 -0500 Subject: [PATCH 06/17] =?UTF-8?q?=F0=9F=93=9A=20Document=20EXPUNGE,=20fetc?= =?UTF-8?q?h/store/search,=20idle/noop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add paragraph to class-level docs, about expunge messages. * Add explanation to #noop docs, explaining EXPUNGE responses * Update #uid_expunge docs with method link instead of command name. --- lib/net/imap.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index bb895515..5a7f5f4a 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -61,6 +61,13 @@ module Net # are expunged from the mailbox, remaining messages have their # sequence numbers "shuffled down" to fill the gaps. # + # To avoid sequence number race conditions, servers must not expunge messages + # when no command is in progress, nor when responding to #fetch, #store, or + # #search. Expunges _may_ be sent during any other command, including + # #uid_fetch, #uid_store, and #uid_search. The #noop and #idle commands are + # both useful for this side-effect: they allow the server to send all mailbox + # updates, including expunges. + # # UIDs, on the other hand, are permanently guaranteed not to # identify another message within the same mailbox, even if # the existing message is deleted. UIDs are required to @@ -530,6 +537,14 @@ def id(client_id=nil) # Sends a {NOOP command [IMAP4rev1 §6.1.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.2] # to the server. # + # This allows the server to send unsolicited untagged EXPUNGE #responses, + # but does not execute any client request. \IMAP servers are permitted to + # send unsolicited untagged responses at any time, except for `EXPUNGE`. + # + # * +EXPUNGE+ can only be sent while a command is in progress. + # * +EXPUNGE+ must _not_ be sent during #fetch, #store, or #search. + # * +EXPUNGE+ may be sent during #uid_fetch, #uid_store, or #uid_search. + # # Related: #idle, #check def noop send_command("NOOP") @@ -1138,7 +1153,7 @@ def expunge # to permanently remove all messages that have both the \\Deleted # flag set and a UID that is included in +uid_set+. # - # By using UID EXPUNGE instead of EXPUNGE when resynchronizing with + # By using #uid_expunge instead of #expunge when resynchronizing with # the server, the client can ensure that it does not inadvertantly # remove any messages that have been marked as \\Deleted by other # clients between the time that the client was last connected and From 200757e480c786010b009acb81000eb7e45ae4b7 Mon Sep 17 00:00:00 2001 From: nick evans Date: Sun, 13 Nov 2022 00:59:18 -0500 Subject: [PATCH 07/17] =?UTF-8?q?=F0=9F=93=9A=20Update=20#starttls=20docum?= =?UTF-8?q?entation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/net/imap.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 5a7f5f4a..dcd5b789 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -562,6 +562,18 @@ def logout # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1] # to start a TLS session. # + # Any +options+ are forwarded to OpenSSL::SSL::SSLContext#set_params. + # + # This method returns after TLS negotiation and hostname verification are + # both successful. Any error indicates that the connection has not been + # secured. + # + # *Note:* + # >>> + # Any #response_handlers added before STARTTLS should be aware that the + # TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation. + # TLS negotiation starts immediately after that response. + # # Related: Net::IMAP.new, #login, #authenticate # # ===== Capability From 534333609118c347dd574eb2e1c42e9209c2d80c Mon Sep 17 00:00:00 2001 From: "nicholas a. evans" Date: Mon, 7 Nov 2022 21:27:25 -0500 Subject: [PATCH 08/17] =?UTF-8?q?=F0=9F=93=9A=20Update=20docs=20for=20#aut?= =?UTF-8?q?henticate,=20#login,=20etc...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documentation on these methods is meant to complement a new SASL::Authenticator base class and new documentation on each of the individual authenticator classes. See #78 and #82. That base class is added in another PR (#78), but the documentation for these methods can be updated without it. Also, somehow I misremembered `LOGINDISABLED`: it only applies to `LOGIN`, not `AUTHENTICATE`! This fixes that error. --- lib/net/imap.rb | 121 +++++++++++++++++++++------------ lib/net/imap/authenticators.rb | 40 ++++++++--- 2 files changed, 109 insertions(+), 52 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index dcd5b789..ca058227 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -602,49 +602,55 @@ def starttls(options = {}, verify = true) end end + # :call-seq: + # authenticate(mechanism, ...) -> ok_resp + # authenticate(mech, *creds, **props) {|prop, auth| val } -> ok_resp + # authenticate(mechanism, authnid, credentials, authzid=nil) -> ok_resp + # authenticate(mechanism, **properties) -> ok_resp + # authenticate(mechanism) {|propname, authctx| prop_value } -> ok_resp + # # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2] - # to authenticate the client. - # - # The +auth_type+ parameter is a string that - # represents the authentication mechanism to be used. Currently Net::IMAP - # supports the following mechanisms: - # - # PLAIN:: Login using cleartext user and password. Secure with TLS. - # See PlainAuthenticator. - # CRAM-MD5:: DEPRECATED: Use PLAIN (or DIGEST-MD5) with TLS. - # DIGEST-MD5:: DEPRECATED by RFC6331. Must be secured using TLS. - # See DigestMD5Authenticator. - # LOGIN:: DEPRECATED: Use PLAIN. - # - # Most mechanisms require two args: authentication identity (e.g. username) - # and credentials (e.g. a password). But each mechanism requires and allows - # different arguments; please consult the documentation for the specific - # mechanisms you are using. Several obsolete mechanisms are available - # for backwards compatibility. Using deprecated mechanisms will issue - # warnings. - # - # Servers do not support all mechanisms and clients must not attempt to use - # a mechanism unless "AUTH=#{mechanism}" is listed as a #capability. - # Clients must not attempt to authenticate or #login when +LOGINDISABLED+ is - # listed with the capabilities. Server capabilities, especially auth - # mechanisms, do change after calling #starttls so they need to be checked - # again. + # to authenticate the client. If successful, the connection enters the + # "_authenticated_" state. # - # For example: + # +mechanism+ is the name of the \SASL authentication mechanism to be used. + # All other arguments are forwarded to the authenticator for the requested + # mechanism. The listed call signatures are suggestions. The + # documentation for each individual mechanism must be consulted for its + # specific parameters. # - # imap.authenticate('PLAIN', user, password) + # An exception Net::IMAP::NoResponseError is raised if authentication fails. # - # A Net::IMAP::NoResponseError is raised if authentication fails. + # Related: #login, #starttls # - # See Net::IMAP::Authenticators for more information on plugging in your - # own authenticator. + # ==== Supported SASL Mechanisms # - # Related: #login, #starttls + # +PLAIN+:: See PlainAuthenticator. + # Login using clear-text username and password. # - # ==== Capabilities + # +XOAUTH2+:: See XOauth2Authenticator. + # Login using a username and OAuth2 access token. + # Non-standard and obsoleted by +OAUTHBEARER+, but widely + # supported. + # + # >>> + # *Deprecated:* Obsolete mechanisms are available for backwards + # compatibility. # - # Clients MUST NOT attempt to #authenticate or #login when +LOGINDISABLED+ - # is listed with the capabilities. + # For +DIGEST-MD5+ see DigestMD5Authenticator. + # + # For +LOGIN+, see LoginAuthenticator. + # + # For +CRAM-MD5+, see CramMD5Authenticator. + # + # Using a deprecated mechanism will print a warning. + # + # See Net::IMAP::Authenticators for information on plugging in + # authenticators for other mechanisms. See the {SASL mechanism + # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] + # for information on these and other SASL mechanisms. + # + # ===== Capabilities # # Clients MUST NOT attempt to authenticate with a mechanism unless # "AUTH=#{mechanism}" for that mechanism is a server capability. @@ -654,9 +660,37 @@ def starttls(options = {}, verify = true) # The TaggedResponse to #authenticate may include updated capabilities in # its ResponseCode. # - def authenticate(auth_type, *args) - authenticator = self.class.authenticator(auth_type, *args) - send_command("AUTHENTICATE", auth_type) do |resp| + # ===== Example + # If the authenticators ignore unhandled keyword arguments, the same config + # can be used for multiple mechanisms: + # + # password = nil # saved locally, so we don't ask more than once + # accesstok = nil # saved locally... + # creds = { + # authcid: username, + # password: proc { password ||= ui.prompt_for_password }, + # oauth2_token: proc { accesstok ||= kms.fresh_access_token }, + # } + # capa = imap.capability + # if capa.include? "AUTH=OAUTHBEARER" + # imap.authenticate "OAUTHBEARER", **creds # authcid, oauth2_token + # elsif capa.include? "AUTH=XOAUTH2" + # imap.authenticate "XOAUTH2", **creds # authcid, oauth2_token + # elsif capa.include? "AUTH=SCRAM-SHA-256" + # imap.authenticate "SCRAM-SHA-256", **creds # authcid, password + # elsif capa.include? "AUTH=PLAIN" + # imap.authenticate "PLAIN", **creds # authcid, password + # elsif capa.include? "AUTH=DIGEST-MD5" + # imap.authenticate "DIGEST-MD5", **creds # authcid, password + # elsif capa.include? "LOGINDISABLED" + # raise "the server has disabled login" + # else + # imap.login username, password + # end + # + def authenticate(mechanism, *args, **props, &cb) + authenticator = self.class.authenticator(mechanism, *args, **props, &cb) + send_command("AUTHENTICATE", mechanism) do |resp| if resp.instance_of?(ContinuationRequest) data = authenticator.process(resp.data.text.unpack("m")[0]) s = [data].pack("m0") @@ -668,16 +702,19 @@ def authenticate(auth_type, *args) # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3] # to identify the client and carries the plaintext +password+ authenticating - # this +user+. Note that, unlike calling #authenticate with an +auth_type+ - # of "LOGIN", #login does *not* use the LoginAuthenticator. + # this +user+. If successful, the connection enters the "_authenticated_" + # state. + # + # Using #authenticate is generally preferred over #login. The LOGIN command + # is not the same as #authenticate with the "LOGIN" +mechanism+. # # A Net::IMAP::NoResponseError is raised if authentication fails. # # Related: #authenticate, #starttls # # ==== Capabilities - # Clients MUST NOT attempt to #authenticate or #login when +LOGINDISABLED+ - # is listed with the capabilities. + # Clients MUST NOT call #login if +LOGINDISABLED+ is listed with the + # capabilities. # # Server capabilities may change after #starttls, #login, and #authenticate. # Cached capabilities _must_ be invalidated after this method completes. diff --git a/lib/net/imap/authenticators.rb b/lib/net/imap/authenticators.rb index d6f5ff69..44f781e5 100644 --- a/lib/net/imap/authenticators.rb +++ b/lib/net/imap/authenticators.rb @@ -3,22 +3,42 @@ # 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 + # Adds an authenticator for Net::IMAP#authenticate to use. +mechanism+ 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. + # implemented by +authenticator+ (for instance, "PLAIN"). + # + # The +authenticator+ must respond to +#new+ (or #call), receiving the + # authenticator configuration and return a configured authentication session. + # The authenticator session must respond to +#process+, receiving the server's + # challenge and returning the client's response. # - # If +auth_type+ refers to an existing authenticator, it will be - # replaced by the new one. + # See PlainAuthenticator, XOauth2Authenticator, and DigestMD5Authenticator for + # examples. 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+. + # :call-seq: + # authenticator(mechanism, ...) -> authenticator + # authenticator(mech, *creds, **props) {|prop, auth| val } -> authenticator + # authenticator(mechanism, authnid, creds, authzid=nil) -> authenticator + # authenticator(mechanism, **properties) -> authenticator + # authenticator(mechanism) {|propname, authctx| value } -> authenticator + # + # Builds a new authentication session context for +mechanism+. + # + # [Note] + # This method is intended for internal use by connection protocol code only. + # Protocol client users should see refer to their client's documentation, + # e.g. Net::IMAP#authenticate for Net::IMAP. + # + # The call signatures documented for this method are recommendations for + # authenticator implementors. All arguments (other than +mechanism+) are + # forwarded to the registered authenticator's +#new+ (or +#call+) method, and + # each authenticator must document its own arguments. + # + # The returned object represents a single authentication exchange and must + # not be reused for multiple authentication attempts. def authenticator(mechanism, *authargs, **properties, &callback) authenticator = authenticators.fetch(mechanism.upcase) do raise ArgumentError, 'unknown auth type - "%s"' % mechanism From c76e9d602ecb588254fa2bb1dffe302c8c98b66e Mon Sep 17 00:00:00 2001 From: nick evans Date: Mon, 19 Dec 2022 09:47:52 -0500 Subject: [PATCH 09/17] =?UTF-8?q?=F0=9F=93=9A=20Update=20#uid=5Ffetch=20do?= =?UTF-8?q?cumentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a note about the implicit UID message data item. --- lib/net/imap.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index ca058227..7d276bc4 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -1337,6 +1337,11 @@ def fetch(set, attr, mod = nil) # Similar to #fetch, but the +set+ parameter contains unique identifiers # instead of message sequence numbers. # + # >>> + # *Note:* Servers _MUST_ implicitly include the +UID+ message data item as + # part of any +FETCH+ response caused by a +UID+ command, regardless of + # whether a +UID+ was specified as a message data item to the +FETCH+. + # # Related: #fetch, FetchData def uid_fetch(set, attr, mod = nil) return fetch_internal("UID FETCH", set, attr, mod) From 32dfda9e83f90977e4d80603f31f00f6afce08dd Mon Sep 17 00:00:00 2001 From: nick evans Date: Sun, 13 Nov 2022 01:01:54 -0500 Subject: [PATCH 10/17] =?UTF-8?q?=F0=9F=93=9A=20Update=20#search=20documen?= =?UTF-8?q?tation,=20search=20criteria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/net/imap.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 7d276bc4..48e076dd 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -1239,17 +1239,23 @@ def uid_expunge(uid_set) # # ===== Search criteria # - # The following are some common search criteria; - # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]] - # for a full list: + # For a full list of search criteria, + # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]], + # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]], + # in addition to documentation for + # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]] + # reported by #capability which may define additional search filters, e.g: + # +CONDSTORE+, +WITHIN+, +FILTERS+, SEARCH=FUZZY, +OBJECTID+, or + # +SAVEDATE+. The following are some common search criteria: # # :: a set of message sequence numbers. "," indicates # an interval, "+:+" indicates a range. For instance, # "2,10:12,15" means "2,10,11,12,15". # # BEFORE :: messages with an internal date strictly before - # . The date argument has a format similar - # to 8-Aug-2002. + # . The date argument has a format similar + # to 8-Aug-2002, and can be formatted using + # Net::IMAP.format_date. # # BODY :: messages that contain within their body. # From 0963ef10ca1d7aad2601080d3f392d302b46e095 Mon Sep 17 00:00:00 2001 From: nick evans Date: Mon, 19 Dec 2022 09:45:30 -0500 Subject: [PATCH 11/17] =?UTF-8?q?=F0=9F=93=9A=20Update=20#namespace=20docu?= =?UTF-8?q?mentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes it just a little bit simpler. --- lib/net/imap.rb | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 48e076dd..3066e1e0 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -873,35 +873,31 @@ def list(refname, mailbox) # allows a client to discover the prefixes of namespaces used by a server # for personal mailboxes, other users' mailboxes, and shared mailboxes. # - # The NAMESPACE extension predates [IMAP4rev1[https://tools.ietf.org/html/rfc2501]], - # 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. + # The return value is a Namespaces object which has +personal+, +other+, and + # +shared+ fields, each an array of Namespace objects. These arrays will be + # empty when the server responds with +nil+. # + # Many \IMAP servers are configured with the default personal namespaces as + # ("" "/"): no prefix and the "+/+" 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. + # 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 + # 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 + # 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 Namespaces - # object which has +personal+, +other+, and +shared+ fields, each an array - # of Namespace objects. These arrays will be empty when the - # server responds with nil. - # # Related: #list, Namespaces, Namespace # # ===== For example: @@ -922,7 +918,7 @@ def list(refname, mailbox) # ===== Capabilities # # The server's capabilities must include +NAMESPACE+ - # [RFC2342[https://tools.ietf.org/html/rfc2342]] + # [RFC2342[https://tools.ietf.org/html/rfc2342]]. def namespace synchronize do send_command("NAMESPACE") From 5717f630b7518a350b404e32cd10181eb748ad0f Mon Sep 17 00:00:00 2001 From: nick evans Date: Mon, 19 Dec 2022 09:51:34 -0500 Subject: [PATCH 12/17] =?UTF-8?q?=F0=9F=93=9A=20Update=20docs=20for=20#sor?= =?UTF-8?q?t,=20#uid=5Fsort,=20&=20#thread=20[=F0=9F=9A=A7=20sort=5Fkeys]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/net/imap.rb | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 3066e1e0..0e23619e 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -1456,8 +1456,13 @@ def uid_move(set, mailbox) end # Sends a {SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] - # to sort messages in the mailbox. Returns an array of message sequence - # numbers. + # to search a mailbox for messages that match +search_keys+ and return an + # array of message sequence numbers, sorted by +sort_keys+. +search_keys+ + # are interpreted the same as for #search. + # + #-- + # TODO: describe +sort_keys+ + #++ # # Related: #uid_sort, #search, #uid_search, #thread, #uid_thread # @@ -1477,7 +1482,9 @@ def sort(sort_keys, search_keys, charset) end # Sends a {UID SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] - # to sort messages in the mailbox. Returns an array of unique identifiers. + # to search a mailbox for messages that match +search_keys+ and return an + # array of unique identifiers, sorted by +sort_keys+. +search_keys+ are + # interpreted the same as for #search. # # Related: #sort, #search, #uid_search, #thread, #uid_thread # @@ -1512,8 +1519,11 @@ def remove_response_handler(handler) end # Sends a {THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] - # Similar to #search, but returns message sequence numbers in threaded - # format, as a ThreadMember tree. The supported algorithms are: + # to search a mailbox and return message sequence numbers in threaded + # format, as a ThreadMember tree. +search_keys+ are interpreted the same as + # for #search. + # + # The supported algorithms are: # # ORDEREDSUBJECT:: split into single-level threads according to subject, # ordered by date. From 4c9bf8190faa230115f19c9853de30a436d841dc Mon Sep 17 00:00:00 2001 From: nick evans Date: Fri, 11 Nov 2022 16:32:25 -0500 Subject: [PATCH 13/17] =?UTF-8?q?=F0=9F=93=9A=20Document=20"What's=20here?= =?UTF-8?q?=3F"=20for=20Net::IMAP=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This "What's here?" section is based on the new documentation style for many core ruby classes. It isn't purely a list of methods; paragraphs of explanatory text are interspersed. *Many* "TODO" comments are added too, hidden from rdoc by `--` and `++`. --- lib/net/imap.rb | 349 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 349 insertions(+) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 0e23619e..92a82d4c 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -170,6 +170,355 @@ module Net # between UTF-8 and UTF-16), and Net::IMAP::ResponseParseError is # thrown if a server response is non-parseable. # + # == What's here? + # + # * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods] + # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands] + # * {...for any state}[rdoc-ref:Net::IMAP@IMAP+commands+for+any+state] + # * {...for the "not authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Not+Authenticated-22+state] + # * {...for the "authenticated" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Authenticated-22+state] + # * {...for the "selected" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Selected-22+state] + # * {...for the "logout" state}[rdoc-ref:Net::IMAP@IMAP+commands+for+the+-22Logout-22+state] + # * {Supported IMAP extensions}[rdoc-ref:Net::IMAP@Supported+IMAP+extensions] + # * {Handling server responses}[rdoc-ref:Net::IMAP@Handling+server+responses] + # + # === Connection control methods + # + # - Net::IMAP.new: A new client connects immediately and waits for a + # successful server greeting before returning the new client object. + # - #starttls: Asks the server to upgrade a clear-text connection to use TLS. + # - #logout: Tells the server to end the session. Enters the "_logout_" state. + # - #disconnect: Disconnects the connection (without sending #logout first). + # - #disconnected?: True if the connection has been closed. + # + # === Core \IMAP commands + # + # The following commands are defined either by + # the [IMAP4rev1[https://tools.ietf.org/html/rfc3501]] base specification, or + # by one of the following extensions: + # [IDLE[https://tools.ietf.org/html/rfc2177]], + # [NAMESPACE[https://tools.ietf.org/html/rfc2342]], + # [UNSELECT[https://tools.ietf.org/html/rfc3691]], + #-- + # TODO: [ENABLE[https://tools.ietf.org/html/rfc5161]], + # TODO: [LIST-EXTENDED[https://tools.ietf.org/html/rfc5258]], + # TODO: [LIST-STATUS[https://tools.ietf.org/html/rfc5819]], + #++ + # [MOVE[https://tools.ietf.org/html/rfc6851]]. + # These extensions are widely supported by modern IMAP4rev1 servers and have + # all been integrated into [IMAP4rev2[https://tools.ietf.org/html/rfc9051]]. + # Note: Net::IMAP doesn't fully support IMAP4rev2 yet. + # + #-- + # TODO: When IMAP4rev2 is supported, add the following to the each of the + # appropriate commands below. + # Note:: CHECK has been removed from IMAP4rev2. + # Note:: LSUB is obsoleted by +LIST-EXTENDED and has been removed from IMAP4rev2. + # Some arguments require the +LIST-EXTENDED+ or +IMAP4rev2+ capability. + # Requires either the +ENABLE+ or +IMAP4rev2+ capability. + # Requires either the +NAMESPACE+ or +IMAP4rev2+ capability. + # Requires either the +IDLE+ or +IMAP4rev2+ capability. + # Requires either the +UNSELECT+ or +IMAP4rev2+ capability. + # Requires either the +UIDPLUS+ or +IMAP4rev2+ capability. + # Requires either the +MOVE+ or +IMAP4rev2+ capability. + #++ + # + # ==== \IMAP commands for any state + # + # - #capability: Returns the server's capabilities as an array of strings. + # + # Capabilities may change after #starttls, #authenticate, or #login + # and cached capabilities must be reloaded. + # - #noop: Allows the server to send unsolicited untagged #responses. + # - #logout: Tells the server to end the session. Enters the "_logout_" state. + # + # ==== \IMAP commands for the "Not Authenticated" state + # + # In addition to the universal commands, the following commands are valid in + # the "not authenticated" state: + # + # - #starttls: Upgrades a clear-text connection to use TLS. + # + # Requires the +STARTTLS+ capability. + # - #authenticate: Identifies the client to the server using a {SASL + # mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]. + # Enters the "_authenticated_" state. + # + # Requires the AUTH=#{mechanism} capability for the chosen + # mechanism. + # - #login: Identifies the client to the server using a plain text password. + # Using #authenticate is generally preferred. Enters the "_authenticated_" + # state. + # + # The +LOGINDISABLED+ capability must NOT be listed. + # + # ==== \IMAP commands for the "Authenticated" state + # + # In addition to the universal commands, the following commands are valid in + # the "_authenticated_" state: + # + #-- + # - #enable: Not implemented by Net::IMAP, yet. + # + # Requires the +ENABLE+ capability. + #++ + # - #select: Open a mailbox and enter the "_selected_" state. + # - #examine: Open a mailbox read-only, and enter the "_selected_" state. + # - #create: Creates a new mailbox. + # - #delete: Permanently remove a mailbox. + # - #rename: Change the name of a mailbox. + # - #subscribe: Adds a mailbox to the "subscribed" set. + # - #unsubscribe: Removes a mailbox from the "subscribed" set. + # - #list: Returns names and attributes of mailboxes matching a given pattern. + # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters. + # + # Requires the +NAMESPACE+ capability. + # - #status: Returns mailbox information, e.g. message count, unseen message + # count, +UIDVALIDITY+ and +UIDNEXT+. + # - #append: Appends a message to the end of a mailbox. + # - #idle: Allows the server to send updates to the client, without the client + # needing to poll using #noop. + # + # Requires the +IDLE+ capability. + # - #lsub: Lists mailboxes the user has declared "active" or "subscribed". + #-- + # Replaced by LIST-EXTENDED and removed from + # +IMAP4rev2+. However, Net::IMAP hasn't implemented + # LIST-EXTENDED _yet_. + #++ + # + # ==== \IMAP commands for the "Selected" state + # + # In addition to the universal commands and the "authenticated" commands, the + # following commands are valid in the "_selected_" state: + # + # - #close: Closes the mailbox and returns to the "_authenticated_" state, + # expunging deleted messages, unless the mailbox was opened as read-only. + # - #unselect: Closes the mailbox and returns to the "_authenticated_" state, + # without expunging any messages. + # + # Requires the +UNSELECT+ capability. + # - #expunge: Permanently removes messages which have the Deleted flag set. + # - #uid_expunge: Restricts #expunge to only remove the specified UIDs. + # + # Requires the +UIDPLUS+ capability. + # - #search, #uid_search: Returns sequence numbers or UIDs of messages that + # match the given searching criteria. + # - #fetch, #uid_fetch: Returns data associated with a set of messages, + # specified by sequence number or UID. + # - #store, #uid_store: Alters a message's flags. + # - #copy, #uid_copy: Copies the specified messages to the end of the + # specified destination mailbox. + # - #move, #uid_move: Moves the specified messages to the end of the + # specified destination mailbox, expunging them from the current mailbox. + # + # Requires the +MOVE+ capability. + # - #check: Mostly obsolete. Can be replaced with #noop or #idle. + #-- + # Removed from IMAP4rev2. + #++ + # + # ==== \IMAP commands for the "Logout" state + # + # No \IMAP commands are valid in the +logout+ state. If the socket is still + # open, Net::IMAP will close it after receiving server confirmation. + # Exceptions will be raised by \IMAP commands that have already started and + # are waiting for a response, as well as any that are called after logout. + # + # === Supported \IMAP extensions + # + # ==== RFC9051: +IMAP4rev2+ + # + # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is not supported + # yet, Net::IMAP supports several extensions that have been folded into + # it: +IDLE+, +MOVE+, +NAMESPACE+, +UIDPLUS+, and +UNSELECT+. + #-- + # TODO: RFC4466, ABNF extensions (automatic support for other extensions) + # TODO: +ESEARCH+, ExtendedSearchData + # TODO: +SEARCHRES+, + # TODO: +ENABLE+, + # TODO: +SASL-IR+, + # TODO: +LIST-EXTENDED+, + # TODO: +LIST-STATUS+, + # TODO: +LITERAL-+, + # TODO: +BINARY+ (only the FETCH side) + # TODO: +SPECIAL-USE+ + # implicitly supported, but we can do better: Response codes: RFC5530, etc + # implicitly supported, but we can do better: STATUS=SIZE + # implicitly supported, but we can do better: STATUS DELETED + #++ + # Commands for these extensions are included with the {Core IMAP + # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above. Other supported + # extensons are listed below. + # + # ==== RFC2087: +QUOTA+ + # - #getquota: returns the resource usage and limits for a quota root + # - #getquotaroot: returns the list of quota roots for a mailbox, as well as + # their resource usage and limits. + # - #setquota: sets the resource limits for a given quota root. + # + # ==== RFC2177: +IDLE+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also + # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. + # - #idle: Allows the server to send updates to the client, without the client + # needing to poll using #noop. + # + # ==== RFC2342: +NAMESPACE+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also + # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. + # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters. + # + # ==== RFC2971: +ID+ + # - #id: exchanges client and server implementation information. + # + #-- + # ==== RFC3502: +MULTIAPPEND+ + # TODO... + #++ + # + #-- + # ==== RFC3516: +BINARY+ + # TODO... + #++ + # + # ==== RFC3691: +UNSELECT+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also + # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. + # - #unselect: Closes the mailbox and returns to the "_authenticated_" state, + # without expunging any messages. + # + # ==== RFC4314: +ACL+ + # - #getacl: lists the authenticated user's access rights to a mailbox. + # - #setacl: sets the access rights for a user on a mailbox + #-- + # TODO: #deleteacl, #listrights, #myrights + #++ + # - *_Note:_* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet. + # + # ==== RFC4315: +UIDPLUS+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also + # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. + # - #uid_expunge: Restricts #expunge to only remove the specified UIDs. + # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode + # - Updates #append with the +APPENDUID+ ResponseCode + # - Updates #copy, #move with the +COPYUID+ ResponseCode + # + #-- + # ==== RFC4466: Collected Extensions to IMAP4 ABNF + # TODO... + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this RFC updates + # the protocol to enable new optional parameters to many commands: #select, + # #examine, #create, #rename, #fetch, #uid_fetch, #store, #uid_store, #search, + # #uid_search, and #append. However, specific parameters are not defined. + # Extensions to these commands use this syntax whenever possible. Net::IMAP + # may be partially compatible with extensions to these commands, even without + # any explicit support. + #++ + # + #-- + # ==== RFC4731 +ESEARCH+ + # TODO... + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]. + # - Updates #search, #uid_search to accept result options: +MIN+, +MAX+, + # +ALL+, +COUNT+, and to return ExtendedSearchData. + #++ + # + #-- + # ==== RFC4959: +SASL-IR+ + # TODO... + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]. + # - Updates #authenticate to reduce round-trips for supporting mechanisms. + #++ + # + #-- + # ==== RFC4978: COMPRESS=DEFLATE + # TODO... + #++ + # + #-- + # ==== RFC5182 +SEARCHRES+ + # TODO... + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]. + # - Updates #search, #uid_search with the +SAVE+ result option. + # - Updates #copy, #uid_copy, #fetch, #uid_fetch, #move, #uid_move, #search, + # #uid_search, #store, #uid_store, and #uid_expunge with ability to + # reference the saved result of a previous #search or #uid_search command. + #++ + # + # ==== RFC5256: +SORT+ + # - #sort, #uid_sort: An alternate version of #search or #uid_search which + # sorts the results by specified keys. + # ==== RFC5256: +THREAD+ + # - #thread, #uid_thread: An alternate version of #search or #uid_search, + # which arranges the results into ordered groups or threads according to a + # chosen algorithm. + # + #-- + # ==== RFC5258 +LIST-EXTENDED+ + # TODO... + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], this updates the + # protocol with new optional parameters to the #list command, adding a few of + # its own. Net::IMAP may be forward-compatible with future #list extensions, + # even without any explicit support. + # - Updates #list to accept selection options: +SUBSCRIBED+, +REMOTE+, and + # +RECURSIVEMATCH+, and return options: +SUBSCRIBED+ and +CHILDREN+. + #++ + # + #-- + # ==== RFC5819 +LIST-STATUS+ + # TODO... + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]. + # - Updates #list with +STATUS+ return option. + #++ + # + # ==== +XLIST+ (non-standard, deprecated) + # - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses. + # + #-- + # ==== RFC6154 +SPECIAL-USE+ + # TODO... + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]. + # - Updates #list with the +SPECIAL-USE+ selection and return options. + #++ + # + # ==== RFC6851: +MOVE+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051], so it is also + # listed with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. + # - #move, #uid_move: Moves the specified messages to the end of the + # specified destination mailbox, expunging them from the current mailbox. + # + #-- + # ==== RFC6855: UTF8=ACCEPT + # TODO... + # ==== RFC6855: UTF8=ONLY + # TODO... + #++ + # + #-- + # ==== RFC7888: LITERAL+, +LITERAL-+ + # TODO... + # ==== RFC7162: +QRESYNC+ + # TODO... + # ==== RFC7162: +CONDSTORE+ + # TODO... + # ==== RFC8474: +OBJECTID+ + # TODO... + # ==== RFC9208: +QUOTA+ + # TODO... + #++ + # + # === Handling server responses + # + # - #greeting: The server's initial untagged response, which can indicate a + # pre-authenticated connection. + # - #responses: The untagged responses, as a hash. Keys are the untagged + # response type (e.g. "OK", "FETCH", "FLAGS") and response code (e.g. + # "ALERT", "UIDVALIDITY", "UIDNEXT", "TRYCREATE", etc). Values are arrays + # of UntaggedResponse or ResponseCode. + # - #add_response_handler: Add a block to be called inside the receiver thread + # with every server response. + # - #remove_response_handler: Remove a previously added response handler. + # # # == References #-- From fb82ed0235c396f6dfa86b9a230fe3c7e98f1de6 Mon Sep 17 00:00:00 2001 From: nick evans Date: Mon, 19 Dec 2022 09:52:06 -0500 Subject: [PATCH 14/17] =?UTF-8?q?=F0=9F=8E=A8=20Move=20response=20handlers?= =?UTF-8?q?=20after=20IMAP=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/net/imap.rb | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 92a82d4c..89b0fcb5 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -1845,28 +1845,6 @@ def uid_sort(sort_keys, search_keys, charset) return sort_internal("UID SORT", sort_keys, search_keys, charset) end - # Adds a response handler. For example, to detect when - # the server sends a new EXISTS response (which normally - # indicates new messages being added to the mailbox), - # add the following handler after selecting the - # mailbox: - # - # imap.add_response_handler { |resp| - # if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "EXISTS" - # puts "Mailbox now has #{resp.data} messages" - # end - # } - # - def add_response_handler(handler = nil, &block) - raise ArgumentError, "two Procs are passed" if handler && block - @response_handlers.push(block || handler) - end - - # Removes the response handler. - def remove_response_handler(handler) - @response_handlers.delete(handler) - end - # Sends a {THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] # to search a mailbox and return message sequence numbers in threaded # format, as a ThreadMember tree. +search_keys+ are interpreted the same as @@ -1970,6 +1948,28 @@ def idle_done end end + # Adds a response handler. For example, to detect when + # the server sends a new EXISTS response (which normally + # indicates new messages being added to the mailbox), + # add the following handler after selecting the + # mailbox: + # + # imap.add_response_handler { |resp| + # if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "EXISTS" + # puts "Mailbox now has #{resp.data} messages" + # end + # } + # + def add_response_handler(handler = nil, &block) + raise ArgumentError, "two Procs are passed" if handler && block + @response_handlers.push(block || handler) + end + + # Removes the response handler. + def remove_response_handler(handler) + @response_handlers.delete(handler) + end + private CRLF = "\r\n" # :nodoc: From 17072495f27c66d772dccb71423bf80bbcaed94a Mon Sep 17 00:00:00 2001 From: nick evans Date: Tue, 22 Nov 2022 12:27:37 -0500 Subject: [PATCH 15/17] =?UTF-8?q?=F0=9F=93=9A=20Fix=20constants=20rdoc=20s?= =?UTF-8?q?ection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without resetting to the default section, code that's loaded later may be documented in the wrong place --- lib/net/imap/flags.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/net/imap/flags.rb b/lib/net/imap/flags.rb index 68bb8b81..88091d01 100644 --- a/lib/net/imap/flags.rb +++ b/lib/net/imap/flags.rb @@ -257,5 +257,6 @@ class IMAP < Protocol # special use is likely not to be supported. TRASH = :Trash + # :section: end end From bd04eb58525c1ea8f06c098c1db71ce68d4f0eea Mon Sep 17 00:00:00 2001 From: nick evans Date: Wed, 21 Dec 2022 17:58:36 -0500 Subject: [PATCH 16/17] =?UTF-8?q?=F0=9F=93=9A=20Update=20StringPrep=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/net/imap/sasl/stringprep.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/net/imap/sasl/stringprep.rb b/lib/net/imap/sasl/stringprep.rb index 32d25557..b352b368 100644 --- a/lib/net/imap/sasl/stringprep.rb +++ b/lib/net/imap/sasl/stringprep.rb @@ -26,6 +26,9 @@ def self.[](table) # # Also checks bidirectional characters, when bidi: true, which may # raise a BidiStringError. + # + # +profile+ is an optional string which will be added to any exception that + # is raised (it does not affect behavior). def check_prohibited!(string, *tables, bidi: false, profile: nil) tables = TABLE_TITLES.keys.grep(/^C/) if tables.empty? tables |= %w[C.8] if bidi @@ -47,10 +50,11 @@ def check_prohibited!(string, *tables, bidi: false, profile: nil) # RandALCat character MUST be the last character of the string. # # This is usually combined with #check_prohibited!, so table "C.8" is only - # checked when +c_8: true+. + # checked when c_8: true. # # Raises either ProhibitedCodepoint or BidiStringError unless all - # requirements are met. + # requirements are met. +profile+ is an optional string which will be + # added to any exception that is raised (it does not affect behavior). def check_bidi!(string, c_8: false, profile: nil) check_prohibited!(string, "C.8", profile: profile) if c_8 if BIDI_FAILS_REQ2.match?(string) From 7ce966c69630869ad6ee3c783eb2f05d7e440b84 Mon Sep 17 00:00:00 2001 From: nicholas evans Date: Wed, 21 Dec 2022 21:13:01 -0500 Subject: [PATCH 17/17] =?UTF-8?q?=F0=9F=93=9A=20Add=20UTF-8=20RFC=20to=20t?= =?UTF-8?q?he=20references=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rakelib/rfcs.rake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rakelib/rfcs.rake b/rakelib/rfcs.rake index e22dee8c..d1b9bb7c 100644 --- a/rakelib/rfcs.rake +++ b/rakelib/rfcs.rake @@ -43,7 +43,8 @@ RFCS = { 2046 => "[MIME-IMT]: MIME Part Two: Media Types", 2047 => "[MIME-HDRS]: MIME Part Three: Header Extensions for Non-ASCII Text", 2183 => "[DISPOSITION]: The Content-Disposition Header", - 2231 => "MIME Parameter Value and Encoded Word Extensions: Character Sets, Languages, and Continuations", + 2231 => "MIME Parameter Value and Encoded Word Extensions: " \ + "Character Sets, Languages, and Continuations", 2557 => "[LOCATION]: MIME Encapsulation of Aggregate Documents", 2978 => "[CHARSET]: IANA Charset Registration Procedures, BCP 19", 3282 => "[LANGUAGE-TAGS]: Content Language Headers", @@ -146,6 +147,7 @@ RFCS = { 9208 => "IMAP QUOTA, QUOTA=, QUOTASET", # etc... + 3629 => "UTF8", 6857 => "Post-Delivery Message Downgrading for I18n Email Messages", }.freeze