Documentation
¶
Overview ¶
Package imap is a pure-Go (CGO-free) reimplementation of the deterministic, interpreter-independent core of Ruby's Net::IMAP (MRI 4.0.5, net-imap 0.6.2): the IMAP4rev1 command builder and the response-grammar parser. The socket and TLS transport is a seam the host (go-embedded-ruby / rbgo) supplies; this package never opens a connection.
What it is — and isn't ¶
Building a tagged command line (atom / quoted-string / literal argument encoding, sequence-sets, fetch-att lists) and parsing the IMAP response grammar (tagged / untagged / continuation responses, parenthesised lists, literals, NIL, numbers, quoted strings, the FETCH attribute and ENVELOPE grammars, resp-text-codes) is fully deterministic and needs no interpreter, so it lives here as pure Go. Reading bytes off a TLS socket — including reading exactly N bytes for a literal — is the host's job; the Reader takes a line-reader and a literal-reader callback, and the host wires those to its own connection.
Index ¶
- Constants
- Variables
- func DecodeUTF7(s string) string
- func EncodeUTF7(s string) string
- func FormatDate(t time.Time) string
- func FormatDatetime(t time.Time) string
- func IdleDone() string
- func MessageSet(nums ...int64) (string, error)
- func SASLCramMD5(username, password, challenge string) string
- func SASLEncode(raw string) string
- func SASLLoginPassword(password string) string
- func SASLLoginUser(username string) string
- func SASLPlain(authzid, username, password string) string
- func SASLXOAuth2(username, token string) string
- type Address
- type AppendUIDData
- type Argument
- type Atom
- type BodyStructure
- type Builder
- func (b *Builder) Append(mailbox string, flags []Flag, dt *Date, message string) (Command, error)
- func (b *Builder) Authenticate(mechanism string) (Command, error)
- func (b *Builder) Capability() (Command, error)
- func (b *Builder) Check() (Command, error)
- func (b *Builder) Close() (Command, error)
- func (b *Builder) Command(cmd string, args ...Argument) (Command, error)
- func (b *Builder) Copy(set *SequenceSet, mailbox string) (Command, error)
- func (b *Builder) Create(mailbox string) (Command, error)
- func (b *Builder) Delete(mailbox string) (Command, error)
- func (b *Builder) Examine(mailbox string) (Command, error)
- func (b *Builder) Expunge() (Command, error)
- func (b *Builder) Fetch(set *SequenceSet, atts ...string) (Command, error)
- func (b *Builder) Idle() (Command, error)
- func (b *Builder) List(refName, mailbox string) (Command, error)
- func (b *Builder) Login(user, password string) (Command, error)
- func (b *Builder) Logout() (Command, error)
- func (b *Builder) Lsub(refName, mailbox string) (Command, error)
- func (b *Builder) NextTag() string
- func (b *Builder) Noop() (Command, error)
- func (b *Builder) Rename(oldName, newName string) (Command, error)
- func (b *Builder) Search(keys ...Argument) (Command, error)
- func (b *Builder) Select(mailbox string) (Command, error)
- func (b *Builder) StartTLS() (Command, error)
- func (b *Builder) Status(mailbox string, attrs ...string) (Command, error)
- func (b *Builder) Store(set *SequenceSet, item string, flags []Flag) (Command, error)
- func (b *Builder) Subscribe(mailbox string) (Command, error)
- func (b *Builder) UIDCopy(set *SequenceSet, mailbox string) (Command, error)
- func (b *Builder) UIDFetch(set *SequenceSet, atts ...string) (Command, error)
- func (b *Builder) UIDSearch(keys ...Argument) (Command, error)
- func (b *Builder) UIDStore(set *SequenceSet, item string, flags []Flag) (Command, error)
- func (b *Builder) Unselect() (Command, error)
- func (b *Builder) Unsubscribe(mailbox string) (Command, error)
- type Command
- type ContinuationRequest
- type CopyUIDData
- type Date
- type Envelope
- type FetchData
- type Flag
- type Literal
- type LiteralSegment
- type MailboxList
- type OrderedAttr
- type OrderedInts
- type QuotedString
- type RawData
- type ReadLineFunc
- type ReadLiteralFunc
- type Reader
- type Response
- type ResponseCode
- type ResponseText
- type SeqRange
- type SequenceSet
- type StatusData
- type TaggedResponse
- type UntaggedResponse
Constants ¶
const CRLF = "\r\n"
CRLF is the IMAP line terminator.
const Star = int64(0)
Star is the sequence value "*" (the largest message in the mailbox).
Variables ¶
var ErrInvalidData = errors.New("imap: invalid command data")
ErrInvalidData is returned by command builders for an argument outside the set of types IMAP commands can serialise.
var ErrParse = errors.New("imap: parse error")
ErrParse is returned (wrapped) when a response buffer does not match the IMAP response grammar.
Functions ¶
func DecodeUTF7 ¶
DecodeUTF7 decodes a modified-UTF-7 mailbox name to UTF-8, mirroring Net::IMAP.decode_utf7. "&-" decodes to a literal "&"; "&…-" decodes its modified-base64 payload as UTF-16BE; everything else passes through.
func EncodeUTF7 ¶
EncodeUTF7 encodes a UTF-8 string to a modified-UTF-7 mailbox name, mirroring Net::IMAP.encode_utf7. Printable ASCII (0x20–0x7e) passes through, with a bare "&" doubled to "&-"; runs of other characters are shifted into a "&…-" block of modified base64 over their UTF-16BE code units.
func FormatDate ¶
FormatDate mirrors Net::IMAP.format_date: an unquoted `DD-Mon-YYYY` (e.g. "30-Jun-2026"), used for the SEARCH date keys.
func FormatDatetime ¶
FormatDatetime mirrors Net::IMAP.format_datetime: an unquoted `DD-Mon-YYYY HH:MM +ZZZZ` (minute precision, no seconds), distinct from the APPEND date-time which carries seconds. It uses the time's own zone offset.
func IdleDone ¶
func IdleDone() string
IdleDone returns the `DONE\r\n` line that terminates an IDLE (it is untagged and carries no tag, mirroring Net::IMAP#idle_done's put_string("DONE\r\n")).
func MessageSet ¶
MessageSet renders a list of message sequence numbers / UIDs as the IMAP sequence-set string, mirroring Net::IMAP::MessageSet#to_s (it is a thin alias over SequenceSet for the FETCH/STORE/COPY set argument). 0 means "*". It returns ErrInvalidData for an out-of-range value.
func SASLCramMD5 ¶
SASLCramMD5 returns the raw CRAM-MD5 response (RFC 2195) for the given decoded server challenge: "username SP hex(HMAC-MD5(challenge, password))". The caller base64-decodes the server challenge first, then base64-encodes this result with SASLEncode.
func SASLEncode ¶
SASLEncode base64-encodes a raw SASL response for the wire, mirroring the strict (no-newline) base64 Net::IMAP writes after a continuation request.
func SASLLoginPassword ¶
SASLLoginPassword returns the raw LOGIN second response: the password verbatim. Encode with SASLEncode for the wire.
func SASLLoginUser ¶
SASLLoginUser returns the raw LOGIN first response: the username verbatim (Net::IMAP::SASL::LoginAuthenticator's first process result). Encode with SASLEncode for the wire.
func SASLPlain ¶
SASLPlain returns the raw PLAIN initial response (RFC 4616): the octets authzid NUL authcid NUL passwd. With an empty authzid (the common case) this is "\0user\0pass". Encode with SASLEncode for the wire.
func SASLXOAuth2 ¶
SASLXOAuth2 returns the raw XOAUTH2 initial response: the octets "user=USER\x01auth=Bearer TOKEN\x01\x01". Encode with SASLEncode for the wire.
Types ¶
type Address ¶
Address is one address from an ENVELOPE. It mirrors Net::IMAP::Address (members: name, route, mailbox, host). A field is "" where the wire form was NIL.
type AppendUIDData ¶
AppendUIDData is the data of an [APPENDUID uidvalidity uid] response code, mirroring Net::IMAP::AppendUIDData (members: uidvalidity, assigned_uids). AssignedUIDs is the sequence-set string the server reported (e.g. "3955").
type Argument ¶
type Argument = any
Argument is a value usable as an IMAP command argument. The accepted dynamic types mirror MRI's send_data dispatch:
nil -> NIL
string -> atom / quoted-string / literal (per send_string_data)
int / int64 -> a number
[]Argument -> a parenthesised list
time.Time -> a quoted date-time ("DD-Mon-YYYY HH:MM:SS +ZZZZ")
Date -> a quoted date (DD-Mon-YYYY)
Flag -> a backslash-prefixed atom (\Seen)
Atom -> a raw atom, emitted verbatim
QuotedString -> always a quoted string
Literal -> always a literal
RawData -> emitted verbatim with no quoting
*SequenceSet -> the coalesced sequence-set form (1:3,7)
type Atom ¶
type Atom string
Atom is a command argument emitted verbatim as an atom (Net::IMAP::Atom).
type BodyStructure ¶
type BodyStructure struct {
// Multipart is true when the body is a multipart/* (a parenthesised list of
// nested parts followed by the subtype).
Multipart bool
// Parts holds the nested parts of a multipart body (nil for singlepart).
Parts []*BodyStructure
// MultipartSubtype is the subtype of a multipart body ("MIXED", "ALTERNATIVE").
MultipartSubtype string
// Singlepart fields (the body-type-1part grammar):
MediaType string // body-fld-type, e.g. "TEXT" / "IMAGE" / "MESSAGE"
Subtype string // body-fld-subtype, e.g. "PLAIN"
Params *OrderedAttr
ContentID string
Description string
Encoding string
Size int64
Lines int64 // text body line count (TEXT subtype only); -1 if absent
// Extension holds any remaining body-ext-* items (md5, disposition,
// language, location, and the per-type extension data) as a generic list of
// parsed values, in wire order.
Extension []any
}
BodyStructure is the parsed BODY / BODYSTRUCTURE of a message part.
Boundary versus MRI ¶
MRI builds a tower of typed structs (BodyTypeText, BodyTypeBasic, BodyTypeMessage, BodyTypeMultipart, …) and assigns each body-fld-* slot a named member. This package instead returns one uniform, fully-recursive tree: every field of the body grammar is captured (so no information is lost), but the dispatch to a specific Ruby struct class — and the field-name mapping of the per-type extension data — is left to the host (rbgo), which knows the Ruby class registry. Parts is non-nil only for a multipart body; for a singlepart body the leading nstring/number fields populate the named slots and Extension holds the remaining body-ext-* items verbatim as a generic list.
This is the one documented-partial attribute: the parse is complete and loss-free, but it is not mapped to MRI's BodyType* struct names here.
func (*BodyStructure) BodyType ¶
func (b *BodyStructure) BodyType() string
BodyType returns the name of the MRI Net::IMAP struct class this body maps to, driving the same dispatch ResponseParser does: "BodyTypeMultipart" for a multipart; "BodyTypeMessage" for a MESSAGE/RFC822 part; "BodyTypeText" for a TEXT/* part; otherwise "BodyTypeBasic". rbgo uses this to instantiate the right Ruby class over the loss-free field tree.
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder assembles tagged command lines with monotonic tags, mirroring Net::IMAP#generate_tag ("<prefix><4-digit>"). The zero value is not usable; use NewBuilder.
func NewBuilder ¶
NewBuilder returns a Builder issuing tags "<prefix>0001", "<prefix>0002", … MRI's default prefix is "RUBY".
func (*Builder) Append ¶
Append builds `TAG APPEND mailbox [(flags)] [datetime] {n}\r\n<message>`. When flags is non-empty it is emitted as a parenthesised flag list; when dt is non-nil it is emitted as a quoted date-time; the message is always a literal.
func (*Builder) Authenticate ¶
Authenticate builds `TAG AUTHENTICATE mech` for the named SASL mechanism (emitted as a raw atom, e.g. PLAIN, LOGIN, CRAM-MD5, XOAUTH2). The continuation exchange (base64 challenge/response) is driven by the host using the SASL encoders in sasl.go.
func (*Builder) Capability ¶
Capability builds `TAG CAPABILITY`.
func (*Builder) Command ¶
Command builds a tagged command line: TAG SP CMD (SP arg)* CRLF, encoding each argument per MRI's send_data. It returns ErrInvalidData for an unsupported argument type or an out-of-range integer.
func (*Builder) Copy ¶
func (b *Builder) Copy(set *SequenceSet, mailbox string) (Command, error)
Copy builds `TAG COPY set mailbox`.
func (*Builder) Fetch ¶
func (b *Builder) Fetch(set *SequenceSet, atts ...string) (Command, error)
Fetch builds `TAG FETCH set (att …)`. set is the sequence set; atts is the fetch attribute list, each emitted as a raw atom (FLAGS, UID, BODY[TEXT], …).
func (*Builder) Idle ¶
Idle builds `TAG IDLE` (RFC 2177). The host writes Bytes, waits for the "+ " continuation, then later writes IdleDone() to end the idle.
func (*Builder) NextTag ¶
NextTag returns the next tag and advances the counter (Net::IMAP#generate_tag).
func (*Builder) Search ¶
Search builds `TAG SEARCH key …` from raw search-key tokens (e.g. "FROM", "smith", "SINCE", "1-Feb-2026"). Each token is emitted as a raw atom unless it is already an Argument (a QuotedString, Literal, *SequenceSet, …).
func (*Builder) Status ¶
Status builds `TAG STATUS mailbox (ATTR …)` from the requested status items. Each item is emitted as a raw atom (MESSAGES, UIDNEXT, …).
func (*Builder) Store ¶
Store builds `TAG STORE set item value`. item is e.g. "+FLAGS" or "FLAGS"; flags is the list of flags, emitted as a parenthesised flag list.
func (*Builder) UIDCopy ¶
func (b *Builder) UIDCopy(set *SequenceSet, mailbox string) (Command, error)
UIDCopy builds `TAG UID COPY set mailbox`.
func (*Builder) UIDFetch ¶
func (b *Builder) UIDFetch(set *SequenceSet, atts ...string) (Command, error)
UIDFetch builds `TAG UID FETCH set (att …)`.
type Command ¶
type Command struct {
Tag string
Bytes string
Literals []LiteralSegment
}
Command is a fully-built tagged command ready to write to the socket. Bytes is the complete octet sequence ("TAG CMD args\r\n"); Tag is the tag the host must match against the eventual tagged response. When the command carries one or more literals, Bytes stops just after the first `{n}\r\n`; the remaining segments live in Literals and the host writes each after receiving a continuation request. For literal-free commands Literals is empty and Bytes is the whole line.
type ContinuationRequest ¶
type ContinuationRequest struct {
Data *ResponseText
RawData string
}
ContinuationRequest is a command continuation request (`+ …`). It mirrors Net::IMAP::ContinuationRequest (members: data, raw_data).
type CopyUIDData ¶
CopyUIDData is the data of a [COPYUID uidvalidity src dst] response code, mirroring Net::IMAP::CopyUIDData (members: uidvalidity, source_uids, assigned_uids). The two UID sets are the sequence-set strings the server sent.
type Envelope ¶
type Envelope struct {
Date string
Subject string
From []Address
Sender []Address
ReplyTo []Address
To []Address
Cc []Address
Bcc []Address
InReplyTo string
MessageID string
}
Envelope is the parsed ENVELOPE fetch attribute. It mirrors Net::IMAP::Envelope (members: date, subject, from, sender, reply_to, to, cc, bcc, in_reply_to, message_id). String members are "" where the wire form was NIL; address-list members are nil where the wire form was NIL.
type FetchData ¶
type FetchData struct {
Seqno int64
Attr *OrderedAttr
}
FetchData is the data of a FETCH response. It mirrors Net::IMAP::FetchData (members: seqno, attr). Attr maps each fetch attribute name to its parsed value (see ParseResponse for the per-attribute value types), preserving the order the server sent them.
type Flag ¶
type Flag string
Flag is a system or keyword flag. System flags are reported by their bare name without the leading backslash and capitalised like Ruby's symbol (`\Seen` -> Flag("Seen")); the wildcard `\*` is Flag("*"). Keyword flags (no backslash) are reported verbatim.
type Literal ¶
type Literal string
Literal is a command argument always emitted as a literal (Net::IMAP::Literal): `{n}\r\n` followed by the bytes (the host sends the bytes after the server's continuation request).
type LiteralSegment ¶
LiteralSegment is the literal payload plus the bytes that follow it (up to and including the next literal's `{n}\r\n`, or the trailing CRLF). The host writes Data after a continuation request, then writes Tail.
type MailboxList ¶
type MailboxList struct {
Attr []Flag
Delim string // hierarchy delimiter, or "" for NIL
Name string
}
MailboxList is the data of a LIST or LSUB response. It mirrors Net::IMAP::MailboxList (members: attr, delim, name).
type OrderedAttr ¶
type OrderedAttr struct {
// contains filtered or unexported fields
}
OrderedAttr is an insertion-ordered string-keyed map of FETCH attributes, mirroring the Ruby Hash that Net::IMAP::FetchData#attr returns (key order is the server's order).
func (*OrderedAttr) Get ¶
func (o *OrderedAttr) Get(key string) (any, bool)
Get returns the value for key and whether it was present.
func (*OrderedAttr) Keys ¶
func (o *OrderedAttr) Keys() []string
Keys returns the attribute names in insertion order.
func (*OrderedAttr) Set ¶
func (o *OrderedAttr) Set(key string, val any)
Set inserts or replaces key. A repeated key keeps its original position.
type OrderedInts ¶
type OrderedInts struct {
// contains filtered or unexported fields
}
OrderedInts is an insertion-ordered string-keyed map of integers, used for the STATUS attr hash.
func (*OrderedInts) Get ¶
func (o *OrderedInts) Get(key string) (int64, bool)
Get returns the value for key and whether it was present.
func (*OrderedInts) Keys ¶
func (o *OrderedInts) Keys() []string
Keys returns the item names in insertion order.
func (*OrderedInts) Set ¶
func (o *OrderedInts) Set(key string, val int64)
Set inserts or replaces key, keeping an existing key's position.
type QuotedString ¶
type QuotedString string
QuotedString is a command argument always emitted as a quoted string (Net::IMAP::QuotedString).
type RawData ¶
type RawData string
RawData is a command argument emitted verbatim with no quoting (Net::IMAP::RawData).
type ReadLineFunc ¶
ReadLineFunc reads the next CRLF-terminated line from the connection, including the trailing CRLF. It returns the line and nil on success, or a non-nil error (e.g. io.EOF) at end of stream. The host wires this to its buffered socket reader.
type ReadLiteralFunc ¶
ReadLiteralFunc reads exactly n bytes of literal payload from the connection. The host wires this to a full read of n octets off the socket.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader assembles complete response buffers from a line reader and a literal reader, then parses them. It is the parser-side seam: the host supplies the two transport callbacks and Reader does the IMAP framing (detecting a trailing `{n}\r\n`, reading n literal bytes, and continuing until the line is complete).
func NewReader ¶
func NewReader(readLine ReadLineFunc, readLiteral ReadLiteralFunc) *Reader
NewReader returns a Reader driven by the host's line and literal readers.
func (*Reader) ReadBuffer ¶
ReadBuffer assembles one complete response buffer: it reads a line, and while that line ends with a `{n}\r\n` literal marker it reads n literal bytes plus the next line, splicing them into the buffer (Net::IMAP::ResponseReader's read_response_buffer). The returned buffer is suitable for ParseResponse.
func (*Reader) ReadResponse ¶
ReadResponse assembles a complete buffer and parses it.
type Response ¶
type Response interface {
// contains filtered or unexported methods
}
Response is the common interface of the three response shapes ParseResponse returns: *TaggedResponse, *UntaggedResponse and *ContinuationRequest.
func ParseResponse ¶
ParseResponse parses one complete response buffer — a single line, with any IMAP literals already spliced in as raw bytes (the Reader produces exactly such a buffer). It returns a *TaggedResponse, *UntaggedResponse or *ContinuationRequest, mirroring Net::IMAP::ResponseParser#parse.
The returned RawData field carries buf verbatim. ParseResponse is byte-faithful to MRI for the IMAP4rev1 response grammar (status responses, EXISTS/RECENT/ EXPUNGE, FLAGS, LIST/LSUB, STATUS, SEARCH, CAPABILITY, FETCH including ENVELOPE / FLAGS / INTERNALDATE / RFC822* / UID / BODY[…] and the resp-text-codes). BODYSTRUCTURE is returned as a *BodyStructure tree (see its doc for the boundary versus MRI's BodyType* struct tower).
type ResponseCode ¶
ResponseCode is a bracketed response code such as [ALERT], [UIDVALIDITY 1234] or [PERMANENTFLAGS (\Deleted \Seen \*)]. It mirrors Net::IMAP::ResponseCode (members: name, data). Data is nil, an int64, a string, a []int64, or a []Flag depending on the code.
type ResponseText ¶
type ResponseText struct {
Code *ResponseCode
Text string
}
ResponseText is the human-readable text of a status response, with an optional bracketed [resp-text-code]. It mirrors Net::IMAP::ResponseText (members: code, text).
type SeqRange ¶
type SeqRange struct{ Lo, Hi int64 }
SeqRange is a closed range [Lo, Hi] for NewSequenceSet. Use Star for "*".
type SequenceSet ¶
type SequenceSet struct {
// contains filtered or unexported fields
}
SequenceSet is a set of message sequence numbers or UIDs. It coalesces sorted values into ranges the way Net::IMAP::SequenceSet#valid_string does ([3,1,2] -> "1:3"). A value of 0 stands for "*" (the largest in the mailbox).
func NewSequenceSet ¶
func NewSequenceSet(items ...any) (*SequenceSet, error)
NewSequenceSet builds a coalesced sequence set from individual numbers and ranges. Numbers are int / int64 (0 meaning "*"); ranges are SeqRange. It returns ErrInvalidData for an unsupported element or a number below 0.
func (*SequenceSet) String ¶
func (s *SequenceSet) String() string
String renders the set as the IMAP sequence-set form, e.g. "1:3,7,9:*".
type StatusData ¶
type StatusData struct {
Mailbox string
Attr *OrderedInts
}
StatusData is the data of a STATUS response. It mirrors Net::IMAP::StatusData (members: mailbox, attr). Attr maps each requested status item (MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN, …) to its integer value, preserving the order the server sent them.
type TaggedResponse ¶
type TaggedResponse struct {
Tag string
Name string
Data *ResponseText
RawData string
}
TaggedResponse is a tagged status response (`a001 OK …`). It mirrors Net::IMAP::TaggedResponse (members: tag, name, data, raw_data).
type UntaggedResponse ¶
UntaggedResponse is an untagged response (`* …`). It mirrors Net::IMAP::UntaggedResponse (members: name, data, raw_data). Data's concrete type depends on Name: int64 for EXISTS/RECENT/EXPUNGE, *ResponseText for OK/NO/BAD/BYE/PREAUTH, []Flag for FLAGS, *MailboxList for LIST/LSUB, *StatusData for STATUS, []int64 for SEARCH, []string for CAPABILITY, and *FetchData for FETCH.
