diff --git a/lib/net/imap/response_data.rb b/lib/net/imap/response_data.rb index bac4339a..c8bd7fa5 100644 --- a/lib/net/imap/response_data.rb +++ b/lib/net/imap/response_data.rb @@ -10,34 +10,47 @@ class IMAP < Protocol # ready to accept the continuation of a command from the client. The # remainder of this response is a line of text. # - # continue_req ::= "+" SPACE (resp_text / base64) - # - # ==== Fields: - # - # data:: Returns the data (Net::IMAP::ResponseText). - # - # raw_data:: Returns the raw data string. class ContinuationRequest < Struct.new(:data, :raw_data) + ## + # method: data + # :call-seq: data -> ResponseText + # + # Returns a ResponseText object + + ## + # method: raw_data + # :call-seq: raw_data -> string + # + # the raw response data end # Net::IMAP::UntaggedResponse represents untagged responses. # # Data transmitted by the server to the client and status responses # that do not indicate command completion are prefixed with the token - # "*", and are called untagged responses. - # - # response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye / - # mailbox_data / message_data / capability_data) - # - # ==== Fields: - # - # name:: Returns the name, such as "FLAGS", "LIST", or "FETCH". - # - # data:: Returns the data such as an array of flag symbols, - # a (()) object. + # "*", and are called untagged responses. # - # raw_data:: Returns the raw data string. class UntaggedResponse < Struct.new(:name, :data, :raw_data) + ## + # method: name + # :call-seq: name -> string + # + # The uppercase response name, e.g. "FLAGS", "LIST", "FETCH", etc. + + ## + # method: data + # :call-seq: data -> object or nil + # + # The parsed response data, e.g: an array of flag symbols, an array of + # capabilities strings, a ResponseText object, a MailboxList object, a + # FetchData object, a Namespaces object, etc. The response #name + # determines what form the data can take. + + ## + # method: raw_data + # :call-seq: raw_data -> string + # + # The raw response data. end # Net::IMAP::IgnoredResponse represents intentionally ignored responses. @@ -47,10 +60,12 @@ class UntaggedResponse < Struct.new(:name, :data, :raw_data) # # It matches no IMAP standard. # - # ==== Fields: - # - # raw_data:: Returns the raw data string. class IgnoredResponse < Struct.new(:raw_data) + ## + # method: raw_data + # :call-seq: raw_data -> string + # + # The raw response data. end # Net::IMAP::TaggedResponse represents tagged responses. @@ -59,58 +74,145 @@ class IgnoredResponse < Struct.new(:raw_data) # failure of the operation. It is tagged with the same tag as the # client command which began the operation. # - # response_tagged ::= tag SPACE resp_cond_state CRLF - # - # tag ::= 1* - # - # resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text - # - # ==== Fields: - # - # tag:: Returns the tag. - # - # name:: Returns the name, one of "OK", "NO", or "BAD". - # - # data:: Returns the data. See (()). - # - # raw_data:: Returns the raw data string. - # class TaggedResponse < Struct.new(:tag, :name, :data, :raw_data) + ## + # method: tag + # :call-seq: tag -> string + # + # Returns the command tag + + ## + # method: name + # :call-seq: name -> string + # + # Returns the name, one of "OK", "NO", or "BAD". + + ## + # method: data + # :call-seq: data -> ResponseText + # + # Returns a ResponseText object + + ## + # method: raw_data + # :call-seq: raw_data -> string + # + # The raw response data. end # Net::IMAP::ResponseText represents texts of responses. - # The text may be prefixed by the response code. # - # resp_text ::= ["[" resp-text-code "]" SP] text - # - # ==== Fields: - # - # code:: Returns the response code. See (()). - # - # text:: Returns the text. + # The text may be prefixed by a ResponseCode. # + # ResponseText is returned from TaggedResponse#data, or from + # UntaggedResponse#data when the response type is a "condition" ("OK", "NO", + # "BAD", "PREAUTH", or "BYE"). class ResponseText < Struct.new(:code, :text) + ## + # method: code + # :call-seq: code -> ResponseCode or nil + # + # Returns a ResponseCode, if the response contains one + + ## + # method: text + # :call-seq: text -> string + # + # Returns the response text, not including any response code end - # Net::IMAP::ResponseCode represents response codes. - # - # resp_text_code ::= "ALERT" / - # "BADCHARSET" [SP "(" astring *(SP astring) ")" ] / - # capability_data / "PARSE" / - # "PERMANENTFLAGS" SP "(" - # [flag_perm *(SP flag_perm)] ")" / - # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / - # "UIDNEXT" SP nz_number / "UIDVALIDITY" SP nz_number / - # "UNSEEN" SP nz_number / - # atom [SP 1*] - # - # ==== Fields: - # - # name:: Returns the name, such as "ALERT", "PERMANENTFLAGS", or "UIDVALIDITY". - # - # data:: Returns the data, if it exists. + # Net::IMAP::ResponseCode represents response codes. Response codes can be + # retrieved from ResponseText#code and can be included in any "condition" + # response: any TaggedResponse and UntaggedResponse when the response type + # is a "condition" ("OK", "NO", "BAD", "PREAUTH", or "BYE"). + # + # Some response codes come with additional data which will be parsed by + # Net::IMAP. Others return +nil+ for #data, but are used as a + # machine-readable annotation for the human-readable ResponseText#text in + # the same response. When Net::IMAP does not know how to parse response + # code text, #data returns the unparsed string. + # + # Untagged response code #data is pushed directly onto Net::IMAP#responses, + # keyed by #name, unless it is removed by the command that generated it. + # Use Net::IMAP#add_response_handler to view tagged response codes for + # command methods that do not return their TaggedResponse. + # + # \IMAP extensions may define new codes and the data that comes with them. + # The IANA {IMAP Response + # Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml] + # registry has links to specifications for all standard response codes. + # Response codes are backwards compatible: Servers are allowed to send new + # response codes even if the client has not enabled the extension that + # defines them. When unknown response code data is encountered, #data + # will return an unparsed string. + # + # See [IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501]] {§7.1, "Server + # Responses - Status + # Responses"}[https://www.rfc-editor.org/rfc/rfc3501#section-7.1] for full + # definitions of the basic set of IMAP4rev1 response codes: + # * +ALERT+, the ResponseText#text contains a special alert that MUST be + # brought to the user's attention. + # * +BADCHARSET+, #data will be an array of charset strings, or +nil+. + # * +CAPABILITY+, #data will be an array of capability strings. + # * +PARSE+, the ResponseText#text presents an error parsing a message's + # \[RFC5322] or [MIME-IMB] headers. + # * +PERMANENTFLAGS+, followed by an array of flags. System flags will be + # symbols, and keyword flags will be strings. See + # rdoc-ref:Net::IMAP@System+flags + # * +READ-ONLY+, the mailbox was selected read-only, or changed to read-only + # * +READ-WRITE+, the mailbox was selected read-write, or changed to + # read-write + # * +TRYCREATE+, when #append or #copy fail because the target mailbox + # doesn't exist. + # * +UIDNEXT+, #data is an Integer, the next UID value of the mailbox. See + # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501]], + # {§2.3.1.1, "Unique Identifier (UID) Message + # Attribute}[https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1]. + # * +UIDVALIDITY+, #data is an Integer, the UID validity value of the + # mailbox See [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501]], + # {§2.3.1.1, "Unique Identifier (UID) Message + # Attribute}[https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1]. + # * +UNSEEN+, #data is an Integer, the number of messages which do not have + # the \Seen flag set. + # + # See RFC5530[https://www.rfc-editor.org/rfc/rfc5530], "IMAP Response + # Codes" for the definition of the following response codes, which are all + # machine-readable annotations for the human-readable ResponseText#text, and + # have +nil+ #data of their own: + # * +UNAVAILABLE+ + # * +AUTHENTICATIONFAILED+ + # * +AUTHORIZATIONFAILED+ + # * +EXPIRED+ + # * +PRIVACYREQUIRED+ + # * +CONTACTADMIN+ + # * +NOPERM+ + # * +INUSE+ + # * +EXPUNGEISSUED+ + # * +CORRUPTION+ + # * +SERVERBUG+ + # * +CLIENTBUG+ + # * +CANNOT+ + # * +LIMIT+ + # * +OVERQUOTA+ + # * +ALREADYEXISTS+ + # * +NONEXISTENT+ # class ResponseCode < Struct.new(:name, :data) + ## + # method: name + # :call-seq: name -> string + # + # Returns the response code name, such as "ALERT", "PERMANENTFLAGS", or + # "UIDVALIDITY". + + ## + # method: data + # :call-seq: data -> object or nil + # + # Returns the parsed response code data, e.g: an array of capabilities + # strings, an array of character set strings, a list of permanent flags, + # an Integer, etc. The response #code determines what form the response + # code data can take. end # Net::IMAP::UIDPlusData represents the ResponseCode#data that accompanies @@ -167,22 +269,36 @@ def uid_mapping end end - # Net::IMAP::MailboxList represents contents of the LIST response. + # Net::IMAP::MailboxList represents contents of the LIST response, + # representing a single mailbox path. # - # mailbox_list ::= "(" #("\Marked" / "\Noinferiors" / - # "\Noselect" / "\Unmarked" / flag_extension) ")" - # SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox - # - # ==== Fields: - # - # attr:: Returns the name attributes. Each name attribute is a symbol - # capitalized by String#capitalize, such as :Noselect (not :NoSelect). - # - # delim:: Returns the hierarchy delimiter. - # - # name:: Returns the mailbox name. + # Net::IMAP#list returns an array of MailboxList objects. # class MailboxList < Struct.new(:attr, :delim, :name) + ## + # method: attr + # :call-seq: attr -> array of Symbols + # + # Returns the name attributes. Each name attribute is a symbol capitalized + # by String#capitalize, such as :Noselect (not :NoSelect). For the + # semantics of each attribute, see: + # * rdoc-ref:Net::IMAP@Basic+Mailbox+Attributes + # * rdoc-ref:Net::IMAP@Mailbox+role+Attributes + # * Net::IMAP@SPECIAL-USE + # * The IANA {IMAP Mailbox Name Attributes + # registry}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml] + + ## + # method: delim + # :call-seq: delim -> single character string + # + # Returns the hierarchy delimiter for the mailbox path. + + ## + # method: name + # :call-seq: name -> string + # + # Returns the mailbox name. end # Net::IMAP::MailboxQuota represents contents of GETQUOTA response. @@ -190,174 +306,359 @@ class MailboxList < Struct.new(:attr, :delim, :name) # specification below, the delimiter used with the "#" construct is a # single space (SPACE). # - # quota_list ::= "(" #quota_resource ")" + # Net:IMAP#getquota returns an array of MailboxQuota objects. # - # quota_resource ::= atom SPACE number SPACE number - # - # quota_response ::= "QUOTA" SPACE astring SPACE quota_list - # - # ==== Fields: - # - # mailbox:: The mailbox with the associated quota. - # - # usage:: Current storage usage of the mailbox. - # - # quota:: Quota limit imposed on the mailbox. + # Net::IMAP#getquotaroot returns an array containing both MailboxQuotaRoot + # and MailboxQuota objects. # class MailboxQuota < Struct.new(:mailbox, :usage, :quota) + ## + # method: mailbox + # :call-seq: mailbox -> string + # + # The mailbox with the associated quota. + + ## + # method: usage + # :call-seq: usage -> Integer + # + # Current storage usage of the mailbox. + + ## + # method: quota + # :call-seq: quota -> Integer + # + # Quota limit imposed on the mailbox. + # end # Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT # response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.) # - # quotaroot_response ::= "QUOTAROOT" SPACE astring *(SPACE astring) - # - # ==== Fields: - # - # mailbox:: The mailbox with the associated quota. - # - # quotaroots:: Zero or more quotaroots that affect the quota on the - # specified mailbox. + # Net::IMAP#getquotaroot returns an array containing both MailboxQuotaRoot + # and MailboxQuota objects. # class MailboxQuotaRoot < Struct.new(:mailbox, :quotaroots) + ## + # method: mailbox + # :call-seq: mailbox -> string + # + # The mailbox with the associated quota. + + ## + # method: mailbox + # :call-seq: quotaroots -> array of strings + # + # Zero or more quotaroots that affect the quota on the specified mailbox. end # Net::IMAP::MailboxACLItem represents the response from GETACL. # - # acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE rights) - # - # identifier ::= astring - # - # rights ::= astring - # - # ==== Fields: - # - # user:: Login name that has certain rights to the mailbox - # that was specified with the getacl command. - # - # rights:: The access rights the indicated user has to the - # mailbox. + # Net::IMAP#getacl returns an array of MailboxACLItem objects. # + # ==== Required capability + # +ACL+ - described in [ACL[https://tools.ietf.org/html/rfc4314]] class MailboxACLItem < Struct.new(:user, :rights, :mailbox) + ## + # method: mailbox + # :call-seq: mailbox -> string + # + # The mailbox to which the indicated #user has the specified #rights. + + ## + # method: user + # :call-seq: user -> string + # + # Login name that has certain #rights to the #mailbox that was specified + # with the getacl command. + + ## + # method: rights + # :call-seq: rights -> string + # + # The access rights the indicated #user has to the #mailbox. end - # Net::IMAP::Namespace represents a single [RFC-2342] namespace. + # Net::IMAP::Namespace represents a single namespace contained inside a + # NAMESPACE response. # - # Namespace = nil / "(" 1*( "(" string SP (<"> QUOTED_CHAR <"> / - # nil) *(Namespace_Response_Extension) ")" ) ")" - # - # Namespace_Response_Extension = SP string SP "(" string *(SP string) - # ")" - # - # ==== Fields: - # - # prefix:: Returns the namespace prefix string. - # delim:: Returns nil or the hierarchy delimiter character. - # extensions:: Returns a hash of extension names to extension flag arrays. + # Returned by Net::IMAP#namespace, contained inside a Namespaces object. # class Namespace < Struct.new(:prefix, :delim, :extensions) + ## + # method: prefix + # :call-seq: prefix -> string + # + # Returns the namespace prefix string. + + ## + # method: delim + # :call-seq: delim -> single character string or nil + # + # Returns a hierarchy delimiter character, if it exists. + + ## + # method: extensions + # :call-seq: extensions -> Hash[String, Array[String]] + # + # A hash of parameters mapped to arrays of strings, for extensibility. + # Extension parameter semantics would be defined by the extension. end - # Net::IMAP::Namespaces represents the response from [RFC-2342] NAMESPACE. - # - # Namespace_Response = "*" SP "NAMESPACE" SP Namespace SP Namespace SP - # Namespace + # Net::IMAP::Namespaces represents a +NAMESPACE+ server response, which + # contains lists of #personal, #shared, and #other namespaces. # - # ; The first Namespace is the Personal Namespace(s) - # ; The second Namespace is the Other Users' Namespace(s) - # ; The third Namespace is the Shared Namespace(s) - # - # ==== Fields: - # - # personal:: Returns an array of Personal Net::IMAP::Namespace objects. - # other:: Returns an array of Other Users' Net::IMAP::Namespace objects. - # shared:: Returns an array of Shared Net::IMAP::Namespace objects. + # Net::IMAP#namespace returns a Namespaces object. # class Namespaces < Struct.new(:personal, :other, :shared) + ## + # method: personal + # :call-seq: personal -> array of Namespace + # + # Returns an array of Personal Namespace objects. + + ## + # method: other + # :call-seq: other -> array of Namespace + # + # Returns an array of Other Users' Namespace objects. + + ## + # method: shared + # :call-seq: shared -> array of Namespace + # + # Returns an array of Shared Namespace objects. end # Net::IMAP::StatusData represents the contents of the STATUS response. # - # ==== Fields: - # - # mailbox:: Returns the mailbox name. - # - # attr:: Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT", - # "UIDVALIDITY", "UNSEEN". Each value is a number. - # + # Net::IMAP#status returns the contents of #attr. class StatusData < Struct.new(:mailbox, :attr) + ## + # method: mailbox + # :call-seq: mailbox -> string + # + # The mailbox name. + + ## + # method: attr + # :call-seq: attr -> Hash[String, Integer] + # + # A hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT", + # "UIDVALIDITY", "UNSEEN". Each value is a number. end - # Net::IMAP::FetchData represents the contents of the FETCH response. - # - # ==== Fields: - # - # seqno:: Returns the message sequence number. - # (Note: not the unique identifier, even for the UID command response.) - # - # attr:: Returns a hash. Each key is a data item name, and each value is - # its value. - # - # The current data items are: - # - # [BODY] - # A form of BODYSTRUCTURE without extension data. - # [BODY[
]<>] - # A string expressing the body contents of the specified section. - # [BODYSTRUCTURE] - # An object that describes the [MIME-IMB] body structure of a message. - # See Net::IMAP::BodyTypeBasic, Net::IMAP::BodyTypeText, - # Net::IMAP::BodyTypeMessage, Net::IMAP::BodyTypeMultipart. - # [ENVELOPE] - # A Net::IMAP::Envelope object that describes the envelope - # structure of a message. - # [FLAGS] - # A array of flag symbols that are set for this message. Flag symbols - # are capitalized by String#capitalize. - # [INTERNALDATE] - # A string representing the internal date of the message. - # [RFC822] - # Equivalent to +BODY[]+. - # [RFC822.HEADER] - # Equivalent to +BODY.PEEK[HEADER]+. - # [RFC822.SIZE] - # A number expressing the [RFC-822] size of the message. - # [RFC822.TEXT] - # Equivalent to +BODY[TEXT]+. - # [UID] - # A number expressing the unique identifier of the message. + # Net::IMAP::FetchData represents the contents of a FETCH response. + # + # Net::IMAP#fetch and Net::IMAP#uid_fetch both return an array of + # FetchData objects. + # + # === Fetch attributes + # + #-- + # TODO: merge branch with accessor methods for each type of attr. Then + # move nearly all of the +attr+ documentation onto the appropriate + # accessor methods. + #++ + # + # Each key of the #attr hash is the data item name for the fetched value. + # Each data item represents a message attribute, part of one, or an + # interpretation of one. #seqno is not a message attribute. Most message + # attributes are static and must never change for a given [server, + # account, mailbox, UIDVALIDITY, UID] tuple. A few message attributes + # can be dynamically changed, e.g. using the {STORE + # command}[rdoc-ref:Net::IMAP#store]. # # See {[IMAP4rev1] §7.4.2}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2] # and {[IMAP4rev2] §7.5.2}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2] # for full description of the standard fetch response data items, and # Net::IMAP@Message+envelope+and+body+structure for other relevant RFCs. # - class FetchData < Struct.new(:seqno, :attr) - end - - # Net::IMAP::Envelope represents envelope structures of messages. - # - # ==== Fields: + # ==== Static fetch data items + # + # The static data items + # defined by [IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html]] are: + # + # ["UID"] + # A number expressing the unique identifier of the message. + # + # ["BODY[]", "BODY[]<#{offset}>"] + # The [RFC5322[https://tools.ietf.org/html/rfc5322]] expression of the + # entire message, as a string. + # + # If +offset+ is specified, this returned string is a substring of the + # entire contents, starting at that origin octet. This means that + # BODY[]<0> MAY be truncated, but BODY[] is NEVER + # truncated. + # + # Messages can be parsed using the "mail" gem. + # + # [Note] + # When fetching BODY.PEEK[#{specifier}], the data will be + # returned in BODY[#{specifier}], without the +PEEK+. This is + # true for all of the BODY[...] attribute forms. + # + # ["BODY[HEADER]", "BODY[HEADER]<#{offset}>"] + # The [RFC5322[https://tools.ietf.org/html/rfc5322]] header of the + # message. + # + # Message headers can be parsed using the "mail" gem. + # + # ["BODY[HEADER.FIELDS (#{fields.join(" ")})]",] + # ["BODY[HEADER.FIELDS (#{fields.join(" ")})]<#{offset}>"] + # When field names are given, the subset contains only the header fields + # that matches one of the names in the list. The field names are based + # on what was requested, not on what was returned. + # + # ["BODY[HEADER.FIELDS.NOT (#{fields.join(" ")})]",] + # ["BODY[HEADER.FIELDS.NOT (#{fields.join(" ")})]<#{offset}>"] + # When the HEADER.FIELDS.NOT is used, the subset is all of the + # fields that do not match any names in the list. + # + # ["BODY[TEXT]", "BODY[TEXT]<#{offset}>"] + # The text body of the message, omitting + # the [RFC5322[https://tools.ietf.org/html/rfc5322]] header. + # + # ["BODY[#{part}]", "BODY[#{part}]<#{offset}>"] + # The text of a particular body section, if it was fetched. + # + # Multiple part specifiers will be joined with ".". Numeric + # part specifiers refer to the MIME part number, counting up from +1+. + # Messages that don't use MIME, or MIME messages that are not multipart + # and don't hold an encapsulated message, only have a part +1+. + # + # 8-bit textual data is permitted if + # a [CHARSET[https://tools.ietf.org/html/rfc2978]] identifier is part of + # the body parameter parenthesized list for this section. See + # BodyTypeBasic. + # + # MESSAGE/RFC822 or MESSAGE/GLOBAL message, or a subset of the header, if + # it was fetched. + # + # ["BODY[#{part}.HEADER]",] + # ["BODY[#{part}.HEADER]<#{offset}>",] + # ["BODY[#{part}.HEADER.FIELDS.NOT (#{fields.join(" ")})]",] + # ["BODY[#{part}.HEADER.FIELDS.NOT (#{fields.join(" ")})]<#{offset}>",] + # ["BODY[#{part}.TEXT]",] + # ["BODY[#{part}.TEXT]<#{offset}>",] + # ["BODY[#{part}.MIME]",] + # ["BODY[#{part}.MIME]<#{offset}>"] + # +HEADER+, HEADER.FIELDS, HEADER.FIELDS.NOT, and + # TEXT can be prefixed by numeric part specifiers, if it refers + # to a part of type message/rfc822 or message/global. + # + # +MIME+ refers to the [MIME-IMB[https://tools.ietf.org/html/rfc2045]] + # header for this part. + # + # ["BODY"] + # A form of +BODYSTRUCTURE+, without any extension data. + # + # ["BODYSTRUCTURE"] + # Returns a BodyStructure object that describes + # the [MIME-IMB[https://tools.ietf.org/html/rfc2045]] body structure of + # a message, if it was fetched. + # + # ["ENVELOPE"] + # An Envelope object that describes the envelope structure of a message. + # See the documentation for Envelope for a description of the envelope + # structure attributes. + # + # ["INTERNALDATE"] + # The internal date and time of the message on the server. This is not + # the date and time in + # the [RFC5322[https://tools.ietf.org/html/rfc5322]] header, but rather + # a date and time which reflects when the message was received. + # + # ["RFC822.SIZE"] + # A number expressing the [RFC5322[https://tools.ietf.org/html/rfc5322]] + # size of the message. + # + # [Note] + # \IMAP was originally developed for the older RFC-822 standard, and + # as a consequence several fetch items in \IMAP incorporate "RFC822" + # in their name. With the exception of +RFC822.SIZE+, there are more + # modern replacements; for example, the modern version of + # +RFC822.HEADER+ is BODY.PEEK[HEADER]. In all cases, + # "RFC822" should be interpreted as a reference to the + # updated [RFC5322[https://tools.ietf.org/html/rfc5322]] standard. + # + # ["RFC822"] + # Semantically equivalent to BODY[]. + # ["RFC822.HEADER"] + # Semantically equivalent to BODY[HEADER]. + # ["RFC822.TEXT"] + # Semantically equivalent to BODY[TEXT]. + # + # [Note:] + # >>> + # Additional static fields are defined in \IMAP extensions and + # [IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]], but + # Net::IMAP can't parse them yet. # - # date:: Returns a string that represents the date. - # - # subject:: Returns a string that represents the subject. + #-- + # "BINARY[#{section_binary}]<#{offset}>":: TODO... + # "BINARY.SIZE[#{sectionbinary}]":: TODO... + # "EMAILID":: TODO... + # "THREADID":: TODO... + # "SAVEDATE":: TODO... + #++ # - # from:: Returns an array of Net::IMAP::Address that represents the from. + # ==== Dynamic message attributes + # The only dynamic item defined + # by [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]] is: + # ["FLAGS"] + # An array of flags that are set for this message. System flags are + # symbols that have been capitalized by String#capitalize. Keyword + # flags are strings and their case is not changed. # - # sender:: Returns an array of Net::IMAP::Address that represents the sender. + # \IMAP extensions define new dynamic fields, e.g.: # - # reply_to:: Returns an array of Net::IMAP::Address that represents the reply-to. + # ["MODSEQ"] + # The modification sequence number associated with this IMAP message. # - # to:: Returns an array of Net::IMAP::Address that represents the to. + # Requires the [CONDSTORE[https://tools.ietf.org/html/rfc7162]] + # server {capability}[rdoc-ref:Net::IMAP#capability]. # - # cc:: Returns an array of Net::IMAP::Address that represents the cc. + # [Note:] + # >>> + # Additional dynamic fields are defined in \IMAP extensions, but + # Net::IMAP can't parse them yet. # - # bcc:: Returns an array of Net::IMAP::Address that represents the bcc. + #-- + # "ANNOTATE":: TODO... + # "PREVIEW":: TODO... + #++ # - # in_reply_to:: Returns a string that represents the in-reply-to. + class FetchData < Struct.new(:seqno, :attr) + ## + # method: seqno + # :call-seq: seqno -> Integer + # + # The message sequence number. + # + # [Note] + # This is never the unique identifier (UID), not even for the + # Net::IMAP#uid_fetch result. If it was returned, the UID is available + # from attr["UID"]. + + ## + # method: attr + # :call-seq: attr -> hash + # + # A hash. Each key is specifies a message attribute, and the value is the + # corresponding data item. + # + # See rdoc-ref:FetchData@Fetch+attributes for descriptions of possible + # values. + end + + # Net::IMAP::Envelope represents envelope structures of messages. # - # message_id:: Returns a string that represents the message-id. + # [Note] + # When the #sender and #reply_to fields are absent or empty, they will + # return the same value as #from. Also, fields may return values that are + # invalid for well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # messages when the message is malformed or a draft message. # # See [{IMAP4rev1 §7.4.2}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2]] # and [{IMAP4rev2 §7.5.2}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2]] @@ -366,84 +667,243 @@ class FetchData < Struct.new(:seqno, :attr) # class Envelope < Struct.new(:date, :subject, :from, :sender, :reply_to, :to, :cc, :bcc, :in_reply_to, :message_id) + ## + # method: date + # call-seq: date -> string + # + # Returns a string that represents the +Date+ header. + # + # [Note] + # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # message, the #date field must not be +nil+. However it can be +nil+ + # for a malformed or draft message. + + ## + # method: subject + # call-seq: subject -> string or nil + # + # Returns a string that represents the +Subject+ header, if it is present. + # + # [Note] + # Servers should return +nil+ when the header is absent and an empty + # string when it is present but empty. Some servers may return a +nil+ + # envelope member in the "present but empty" case. Clients should treat + # +nil+ and empty string as identical. + + ## + # method: from + # call-seq: from -> array of Net::IMAP::Address or nil + # + # Returns an array of Address that represents the +From+ header. + # + # If the +From+ header is absent, or is present but empty, the server + # returns +nil+ for this envelope field. + # + # [Note] + # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # message, the #from field must not be +nil+. However it can be +nil+ + # for a malformed or draft message. + + ## + # method: sender + # call-seq: sender -> array of Net::IMAP::Address or nil + # + # Returns an array of Address that represents the +Sender+ header. + # + # [Note] + # If the Sender header is absent, or is present but empty, the + # server sets this field to be the same value as #from. Therefore, in a + # well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] message, + # the #sender envelope field must not be +nil+. However it can be + # +nil+ for a malformed or draft message. + + ## + # method: reply_to + # call-seq: reply_to -> array of Net::IMAP::Address or nil + # + # Returns an array of Address that represents the Reply-To + # header. + # + # [Note] + # If the Reply-To header is absent, or is present but empty, + # the server sets this field to be the same value as #from. Therefore, + # in a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # message, the #reply_to envelope field must not be +nil+. However it + # can be +nil+ for a malformed or draft message. + + ## + # method: to + # call-seq: to -> array of Net::IMAP::Address + # + # Returns an array of Address that represents the +To+ header. + + ## + # method: cc + # call-seq: cc -> array of Net::IMAP::Address + # + # Returns an array of Address that represents the +Cc+ header. + + ## + # method: bcc + # call-seq: bcc -> array of Net::IMAP::Address + # + # Returns an array of Address that represents the +Bcc+ header. + + ## + # method: in_reply_to + # call-seq: in_reply_to -> string + # + # Returns a string that represents the In-Reply-To header. + # + # [Note] + # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # message, the #in_reply_to field, if present, must not be empty. But + # it can still return an empty string for malformed messages. + # + # Servers should return +nil+ when the header is absent and an empty + # string when it is present but empty. Some servers may return a +nil+ + # envelope member in the "present but empty" case. Clients should treat + # +nil+ and empty string as identical. + + ## + # method: message_id + # call-seq: message_id -> string + # + # Returns a string that represents the Message-ID. + # + # [Note] + # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # message, the #message_id field, if present, must not be empty. But it + # can still return an empty string for malformed messages. + # + # Servers should return +nil+ when the header is absent and an empty + # string when it is present but empty. Some servers may return a +nil+ + # envelope member in the "present but empty" case. Clients should treat + # +nil+ and empty string as identical. end - # - # Net::IMAP::Address represents electronic mail addresses. - # - # ==== Fields: - # - # name:: Returns the phrase from [RFC-822] mailbox. - # - # route:: Returns the route from [RFC-822] route-addr. - # - # mailbox:: nil indicates end of [RFC-822] group. - # If non-nil and host is nil, returns [RFC-822] group name. - # Otherwise, returns [RFC-822] local-part. - # - # host:: nil indicates [RFC-822] group syntax. - # Otherwise, returns [RFC-822] domain name. - # + # Net::IMAP::Address represents an electronic mail address, which has been + # parsed into its component parts by the server. Address objects are + # returned within Envelope fields. + # + # === Group syntax + # + # When the #host field is +nil+, this is a special form of address structure + # that indicates the [RFC5322[https://tools.ietf.org/html/rfc5322]] group + # syntax. If the #mailbox name field is also +nil+, this is an end-of-group + # marker (semicolon in RFC-822 syntax). If the #mailbox name field is + # non-+NIL+, this is the start of a group marker, and the mailbox #name + # field holds the group name phrase. class Address < Struct.new(:name, :route, :mailbox, :host) + ## + # method: name + # :call-seq: name -> string or nil + # + # Returns the [RFC5322[https://tools.ietf.org/html/rfc5322]] address + # +display-name+ (or the mailbox +phrase+ in the RFC-822 grammar). + + ## + # method: route + # :call-seq: route -> string or nil + # + # Returns the route from RFC-822 route-addr. + # + # Note:: Generating this obsolete route addressing syntax is not allowed + # by [RFC5322[https://tools.ietf.org/html/rfc5322]]. However, + # addresses with this syntax must still be accepted and parsed. + + ## + # method: mailbox + # :call-seq: mailbox -> string or nil + # + # Returns the [RFC5322[https://tools.ietf.org/html/rfc5322]] address + # +local-part+, if #host is not +nil+. + # + # When #host is +nil+, this returns + # an [RFC5322[https://tools.ietf.org/html/rfc5322]] group name and a +nil+ + # mailbox indicates the end of a group. + + ## + # method: host + # :call-seq: host -> string or nil + # + # Returns the [RFC5322[https://tools.ietf.org/html/rfc5322]] addr-spec + # +domain+ name. + # + # +nil+ indicates [RFC5322[https://tools.ietf.org/html/rfc5322]] group + # syntax. end - # # Net::IMAP::ContentDisposition represents Content-Disposition fields. # - # ==== Fields: - # - # dsp_type:: Returns the disposition type. - # - # param:: Returns a hash that represents parameters of the Content-Disposition - # field. - # class ContentDisposition < Struct.new(:dsp_type, :param) + ## + # method: dsp_type + # :call-seq: dsp_type -> string + # + # Returns the content disposition type, as defined by + # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. + + ## + # method: param + # :call-seq: param -> hash + # + # Returns a hash representing parameters of the Content-Disposition + # field, as defined by [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. end # Net::IMAP::ThreadMember represents a thread-node returned # by Net::IMAP#thread. # - # ==== Fields: - # - # seqno:: The sequence number of this message. - # - # children:: An array of Net::IMAP::ThreadMember objects for mail - # items that are children of this in the thread. - # class ThreadMember < Struct.new(:seqno, :children) + ## + # method: seqno + # :call-seq: seqno -> Integer + # + # The message sequence number. + + ## + # method: children + # :call-seq: children -> array of ThreadMember + # + # An array of Net::IMAP::ThreadMember objects for mail items that are + # children of this in the thread. end - # Net::IMAP::BodyTypeBasic represents basic body structures of messages. - # - # ==== Fields: - # - # media_type:: Returns the content media type name as defined in [MIME-IMB]. - # - # subtype:: Returns the content subtype name as defined in [MIME-IMB]. - # - # param:: Returns a hash that represents parameters as defined in [MIME-IMB]. - # - # content_id:: Returns a string giving the content id as defined in [MIME-IMB]. - # - # description:: Returns a string giving the content description as defined in - # [MIME-IMB]. - # - # encoding:: Returns a string giving the content transfer encoding as defined in - # [MIME-IMB]. + # Net::IMAP::BodyStructure is included by all of the structs that can be + # returned from a "BODYSTRUCTURE" or "BODY" + # FetchData#attr value. Although these classes don't share a base class, + # this module can be used to pattern match all of them. # - # size:: Returns a number giving the size of the body in octets. - # - # md5:: Returns a string giving the body MD5 value as defined in [MD5]. - # - # disposition:: Returns a Net::IMAP::ContentDisposition object giving - # the content disposition. - # - # language:: Returns a string or an array of strings giving the body - # language value as defined in [LANGUAGE-TAGS]. - # - # extension:: Returns extension data. + # See {[IMAP4rev1] §7.4.2}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2] + # and {[IMAP4rev2] §7.5.2}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2-4.9] + # for full description of all +BODYSTRUCTURE+ fields, and also + # Net::IMAP@Message+envelope+and+body+structure for other relevant RFCs. # - # multipart?:: Returns false. + # === Classes that include BodyStructure + # BodyTypeBasic:: Represents any message parts that are not handled by + # BodyTypeText, BodyTypeMessage, or BodyTypeMultipart. + # BodyTypeText:: Used by text/* parts. Contains all of the + # BodyTypeBasic fields. + # BodyTypeMessage:: Used by message/rfc822 and + # message/global parts. Contains all of the + # BodyTypeBasic fields. Other message/* types + # should use BodyTypeBasic. + # BodyTypeMultipart:: for multipart/* parts + # + # ==== Deprecated BodyStructure classes + # The following classes represent invalid server responses or parser bugs: + # BodyTypeExtension:: parser bug: used for message/* where + # BodyTypeBasic should have been used. + # BodyTypeAttachment:: server bug: some servers sometimes return the + # "Content-Disposition: attachment" data where the + # entire body structure for a message part is expected. + module BodyStructure + end + + # Net::IMAP::BodyTypeBasic represents basic body structures of messages and + # message parts, unless they have a Content-Type that is handled by + # BodyTypeText, BodyTypeMessage, or BodyTypeMultipart. # # See {[IMAP4rev1] §7.4.2}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2] # and {[IMAP4rev2] §7.5.2}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2-4.9] @@ -455,26 +915,134 @@ class BodyTypeBasic < Struct.new(:media_type, :subtype, :description, :encoding, :size, :md5, :disposition, :language, :extension) + include BodyStructure + + ## + # method: media_type + # :call-seq: media_type -> string + # + # The top-level media type as defined in + # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. + + ## + # method: subtype + # :call-seq: subtype -> string + # + # The media subtype name as defined in + # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. + + ## + # method: param + # :call-seq: param -> string + # + # Returns a hash that represents parameters as defined in + # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. + + ## + # method: content_id + # :call-seq: content_id -> string + # + # Returns a string giving the content id as defined + # in [MIME-IMB[https://tools.ietf.org/html/rfc2045]] + # {§7}[https://tools.ietf.org/html/rfc2045#section-7]. + + ## + # method: description + # :call-seq: description -> string + # + # Returns a string giving the content description as defined + # in [MIME-IMB[https://tools.ietf.org/html/rfc2045]] + # {§8}[https://tools.ietf.org/html/rfc2045#section-8]. + + ## + # method: encoding + # :call-seq: encoding -> string + # + # Returns a string giving the content transfer encoding as defined + # in [MIME-IMB[https://tools.ietf.org/html/rfc2045]] + # {§6}[https://tools.ietf.org/html/rfc2045#section-6]. + + ## + # method: size + # :call-seq: size -> integer + # + # Returns a number giving the size of the body in octets. + + ## + # method: md5 + # :call-seq: md5 -> string + # + # Returns a string giving the body MD5 value as defined in + # [MD5[https://tools.ietf.org/html/rfc1864]]. + + ## + # method: disposition + # :call-seq: disposition -> ContentDisposition + # + # Returns a ContentDisposition object giving the content + # disposition, as defined by + # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. + + ## + # method: language + # :call-seq: language -> string + # + # Returns a string or an array of strings giving the body + # language value as defined in + # [LANGUAGE-TAGS[https://www.rfc-editor.org/info/rfc3282]]. + + #-- + ## + # method: location + # :call-seq: location -> string + # + # A string list giving the body content URI as defined in + # [LOCATION[https://www.rfc-editor.org/info/rfc2557]]. + #++ + + ## + # method: extension + # :call-seq: extension -> string + # + # Returns extension data. The +BODYSTRUCTURE+ fetch attribute + # contains extension data, but +BODY+ does not. + + ## + # :call-seq: multipart? -> false + # + # BodyTypeBasic is not used for multipart MIME parts. def multipart? return false end - # Obsolete: use +subtype+ instead. Calling this will - # generate a warning message to +stderr+, then return - # the value of +subtype+. + # :call-seq: media_subtype -> subtype + # + # >>> + # [Obsolete] + # Use +subtype+ instead. Calling this will generate a warning message + # to +stderr+, then return the value of +subtype+. + #-- + # TODO: why not just keep this as an alias? Would "media_subtype" be used + # for something else? + #++ def media_subtype warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1) return subtype end end - # Net::IMAP::BodyTypeText represents TEXT body structures of messages. - # - # ==== Fields: - # - # lines:: Returns the size of the body in text lines. - # - # And Net::IMAP::BodyTypeText has all fields of Net::IMAP::BodyTypeBasic. + # Net::IMAP::BodyTypeText represents the body structures of messages and + # message parts, when Content-Type is text/*. + # + # BodyTypeText contains all of the fields of BodyTypeBasic. See + # BodyTypeBasic for documentation of the following: + # * {media_type}[rdoc-ref:BodyTypeBasic#media_type] + # * subtype[rdoc-ref:BodyTypeBasic#subtype] + # * param[rdoc-ref:BodyTypeBasic#param] + # * {content_id}[rdoc-ref:BodyTypeBasic#content_id] + # * description[rdoc-ref:BodyTypeBasic#description] + # * encoding[rdoc-ref:BodyTypeBasic#encoding] + # * size[rdoc-ref:BodyTypeBasic#size] # class BodyTypeText < Struct.new(:media_type, :subtype, :param, :content_id, @@ -482,6 +1050,18 @@ class BodyTypeText < Struct.new(:media_type, :subtype, :lines, :md5, :disposition, :language, :extension) + include BodyStructure + + ## + # method: lines + # :call-seq: lines -> Integer + # + # Returns the size of the body in text lines. + + ## + # :call-seq: multipart? -> false + # + # BodyTypeText is not used for multipart MIME parts. def multipart? return false end @@ -495,15 +1075,19 @@ def media_subtype end end - # Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages. - # - # ==== Fields: - # - # envelope:: Returns a Net::IMAP::Envelope giving the envelope structure. - # - # body:: Returns an object giving the body structure. - # - # And Net::IMAP::BodyTypeMessage has all methods of Net::IMAP::BodyTypeText. + # Net::IMAP::BodyTypeMessage represents the body structures of messages and + # message parts, when Content-Type is message/rfc822 or + # message/global. + # + # BodyTypeMessage contains all of the fields of BodyTypeBasic. See + # BodyTypeBasic for documentation of the following fields: + # * {media_type}[rdoc-ref:BodyTypeBasic#media_type] + # * subtype[rdoc-ref:BodyTypeBasic#subtype] + # * param[rdoc-ref:BodyTypeBasic#param] + # * {content_id}[rdoc-ref:BodyTypeBasic#content_id] + # * description[rdoc-ref:BodyTypeBasic#description] + # * encoding[rdoc-ref:BodyTypeBasic#encoding] + # * size[rdoc-ref:BodyTypeBasic#size] # class BodyTypeMessage < Struct.new(:media_type, :subtype, :param, :content_id, @@ -511,6 +1095,24 @@ class BodyTypeMessage < Struct.new(:media_type, :subtype, :envelope, :body, :lines, :md5, :disposition, :language, :extension) + include BodyStructure + + ## + # method: envelope + # :call-seq: envelope -> Envelope + # + # Returns a Net::IMAP::Envelope giving the envelope structure. + + ## + # method: body + # :call-seq: body -> BodyStructure + # + # Returns a Net::IMAP::BodyStructure for the message's body structure. + + ## + # :call-seq: multipart? -> false + # + # BodyTypeMessage is not used for multipart MIME parts. def multipart? return false end @@ -524,57 +1126,136 @@ def media_subtype end end - # Net::IMAP::BodyTypeAttachment represents attachment body structures - # of messages. - # - # ==== Fields: - # - # media_type:: Returns the content media type name. - # - # subtype:: Returns +nil+. - # - # param:: Returns a hash that represents parameters. - # - # multipart?:: Returns false. - # - class BodyTypeAttachment < Struct.new(:media_type, :subtype, - :param) + # === WARNING + # BodyTypeAttachment represents a body-fld-dsp that is + # incorrectly in a position where the IMAP4rev1 grammar expects a nested + # +body+ structure. + # + # >>> + # \IMAP body structures are parenthesized lists and assign their fields + # positionally, so missing fields change the intepretation of all + # following fields. Buggy \IMAP servers sometimes leave fields missing + # rather than empty, which inevitably confuses parsers. + # BodyTypeAttachment was an attempt to parse a common type of buggy body + # structure without crashing. + # + # Currently, when Net::IMAP::ResponseParser sees "attachment" as the first + # entry in a body-type-1part, which is where the MIME type should + # be, it uses BodyTypeAttachment to capture the rest. "attachment" is not + # a valid MIME type, but _is_ a common Content-Disposition. What + # might have happened was that buggy server could not parse the message + # (which might have been incorrectly formatted) and output a + # body-type-dsp where a Net::IMAP::ResponseParser expected to see + # a +body+. + # + # A future release will replace this, probably with a ContentDisposition + # nested inside another body structure object, maybe BodyTypeBasic, or + # perhaps a new body structure class that represents any unparsable body + # structure. + # + class BodyTypeAttachment < Struct.new(:dsp_type, :_unused_, :param) + include BodyStructure + + # *invalid for BodyTypeAttachment* + def media_type + warn(<<~WARN, uplevel: 1) + BodyTypeAttachment#media_type is obsolete. Use dsp_type instead. + WARN + dsp_type + end + + # *invalid for BodyTypeAttachment* + def subtype + warn("BodyTypeAttachment#subtype is obsolete.\n", uplevel: 1) + nil + end + + ## + # method: dsp_type + # :call-seq: dsp_type -> string + # + # Returns the content disposition type, as defined by + # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. + + ## + # method: param + # :call-seq: param -> hash + # + # Returns a hash representing parameters of the Content-Disposition + # field, as defined by [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. + + ## def multipart? return false end end - # Net::IMAP::BodyTypeMultipart represents multipart body structures - # of messages. - # - # ==== Fields: - # - # media_type:: Returns the content media type name as defined in [MIME-IMB]. - # - # subtype:: Returns the content subtype name as defined in [MIME-IMB]. - # - # parts:: Returns multiple parts. - # - # param:: Returns a hash that represents parameters as defined in [MIME-IMB]. - # - # disposition:: Returns a Net::IMAP::ContentDisposition object giving - # the content disposition. - # - # language:: Returns a string or an array of strings giving the body - # language value as defined in [LANGUAGE-TAGS]. - # - # extension:: Returns extension data. - # - # multipart?:: Returns true. - # + # Net::IMAP::BodyTypeMultipart represents body structures of messages and + # message parts, when Content-Type is multipart/*. class BodyTypeMultipart < Struct.new(:media_type, :subtype, :parts, :param, :disposition, :language, :extension) + include BodyStructure + + ## + # method: media_type + # call-seq: media_type -> "multipart" + # + # BodyTypeMultipart is only used with multipart/* media types. + + ## + # method: subtype + # call-seq: subtype -> string + # + # Returns the content subtype name + # as defined in [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. + + ## + # method: parts + # call-seq: parts -> array of BodyStructure objects + # + # Returns an array with a BodyStructure object for each part contained in + # this part. + + ## + # method: param + # call-seq: param -> hash + # + # Returns a hash that represents parameters + # as defined in [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. + + ## + # method: disposition + # call-seq: disposition -> ContentDisposition + # + # Returns a Net::IMAP::ContentDisposition object giving the content + # disposition. + + ## + # method: language + # :call-seq: language -> string + # + # Returns a string or an array of strings giving the body + # language value as defined in + # [LANGUAGE-TAGS[https://www.rfc-editor.org/info/rfc3282]]. + + ## + # method: extension + # call-seq: extension -> array + # + # Returns extension data as an array of numbers strings, and nested + # arrays (of numbers, strings, etc). + + ## + # :call-seq: multipart? -> true + # + # BodyTypeMultipart is used for multipart MIME parts. def multipart? return true end + ## # Obsolete: use +subtype+ instead. Calling this will # generate a warning message to +stderr+, then return # the value of +subtype+. @@ -584,9 +1265,19 @@ def media_subtype end end + # === WARNING: + # >>> + # BodyTypeExtension is (incorrectly) used for message/* parts + # (besides message/rfc822, which correctly uses BodyTypeMessage). + # + # A future release will replace this class with: + # * BodyTypeMessage for message/rfc822 and message/global + # * BodyTypeBasic for any other message/* class BodyTypeExtension < Struct.new(:media_type, :subtype, :params, :content_id, :description, :encoding, :size) + include BodyStructure + def multipart? return false end