2Package imapclient provides an IMAP4 client, primarily for testing the IMAP4 server.
4Commands can be sent to the server free-form, but responses are parsed strictly.
5Behaviour that may not be required by the IMAP4 specification may be expected by
11- Try to keep the parsing method names and the types similar to the ABNF names in the RFCs.
13- todo: have mode for imap4rev1 vs imap4rev2, refusing what is not allowed. we are accepting too much now.
14- todo: stricter parsing. xnonspace() and xword() should be replaced by proper parsers.
26// Conn is an IMAP connection to a server.
32 record bool // If true, bytes read are added to recordBuf. recorded() resets.
36 CapAvailable map[Capability]struct{} // Capabilities available at server, from CAPABILITY command or response code.
37 CapEnabled map[Capability]struct{} // Capabilities enabled through ENABLE command.
40// Error is a parse or other protocol error.
41type Error struct{ err error }
43func (e Error) Error() string {
47func (e Error) Unwrap() error {
51// New creates a new client on conn.
53// If xpanic is true, functions that would return an error instead panic. For parse
54// errors, the resulting stack traces show typically show what was being parsed.
56// The initial untagged greeting response is read and must be "OK".
57func New(conn net.Conn, xpanic bool) (client *Conn, rerr error) {
60 r: bufio.NewReader(conn),
62 CapAvailable: map[Capability]struct{}{},
63 CapEnabled: map[Capability]struct{}{},
66 defer c.recover(&rerr)
69 c.xerrorf("expected untagged *, got %q", tag)
73 switch x := ut.(type) {
76 c.xerrorf("greeting, got status %q, expected OK", x.Status)
80 c.xerrorf("greeting: unexpected preauth")
82 c.xerrorf("greeting: server sent bye")
84 c.xerrorf("unexpected untagged %v", ut)
89func (c *Conn) recover(rerr *error) {
105func (c *Conn) xerrorf(format string, args ...any) {
106 panic(Error{fmt.Errorf(format, args...)})
109func (c *Conn) xcheckf(err error, format string, args ...any) {
111 c.xerrorf("%s: %w", fmt.Sprintf(format, args...), err)
115func (c *Conn) xcheck(err error) {
121// TLSConnectionState returns the TLS connection state if the connection uses TLS.
122func (c *Conn) TLSConnectionState() *tls.ConnectionState {
123 if conn, ok := c.conn.(*tls.Conn); ok {
124 cs := conn.ConnectionState()
130// Commandf writes a free-form IMAP command to the server.
131// If tag is empty, a next unique tag is assigned.
132func (c *Conn) Commandf(tag string, format string, args ...any) (rerr error) {
133 defer c.recover(&rerr)
140 _, err := fmt.Fprintf(c.conn, "%s %s\r\n", tag, fmt.Sprintf(format, args...))
141 c.xcheckf(err, "write command")
145func (c *Conn) nextTag() string {
147 return fmt.Sprintf("x%03d", c.tagGen)
150// Response reads from the IMAP server until a tagged response line is found.
151// The tag must be the same as the tag for the last written command.
152// Result holds the status of the command. The caller must check if this the status is OK.
153func (c *Conn) Response() (untagged []Untagged, result Result, rerr error) {
154 defer c.recover(&rerr)
160 untagged = append(untagged, c.xuntagged())
164 if tag != c.LastTag {
165 c.xerrorf("got tag %q, expected %q", tag, c.LastTag)
168 status := c.xstatus()
170 result = c.xresult(status)
176// ReadUntagged reads a single untagged response line.
177// Useful for reading lines from IDLE.
178func (c *Conn) ReadUntagged() (untagged Untagged, rerr error) {
179 defer c.recover(&rerr)
183 c.xerrorf("got tag %q, expected untagged", tag)
190// Readline reads a line, including CRLF.
191// Used with IDLE and synchronous literals.
192func (c *Conn) Readline() (line string, rerr error) {
193 defer c.recover(&rerr)
195 line, err := c.r.ReadString('\n')
196 c.xcheckf(err, "read line")
200// ReadContinuation reads a line. If it is a continuation, i.e. starts with a +, it
201// is returned without leading "+ " and without trailing crlf. Otherwise, a command
202// response is returned. A successfully read continuation can return an empty line.
203// Callers should check rerr and result.Status being empty to check if a
204// continuation was read.
205func (c *Conn) ReadContinuation() (line string, untagged []Untagged, result Result, rerr error) {
207 untagged, result, rerr = c.Response()
208 c.xcheckf(rerr, "reading non-continuation response")
209 c.xerrorf("response status %q, expected OK", result.Status)
212 line, err := c.Readline()
213 c.xcheckf(err, "read line")
214 line = strings.TrimSuffix(line, "\r\n")
218// Writelinef writes the formatted format and args as a single line, adding CRLF.
219// Used with IDLE and synchronous literals.
220func (c *Conn) Writelinef(format string, args ...any) (rerr error) {
221 defer c.recover(&rerr)
223 s := fmt.Sprintf(format, args...)
224 _, err := fmt.Fprintf(c.conn, "%s\r\n", s)
225 c.xcheckf(err, "writeline")
229// Write writes directly to the connection. Write errors do take the connections
230// panic mode into account, i.e. Write can panic.
231func (c *Conn) Write(buf []byte) (n int, rerr error) {
232 defer c.recover(&rerr)
234 n, rerr = c.conn.Write(buf)
235 c.xcheckf(rerr, "write")
239// WriteSyncLiteral first writes the synchronous literal size, then read the
240// continuation "+" and finally writes the data.
241func (c *Conn) WriteSyncLiteral(s string) (rerr error) {
242 defer c.recover(&rerr)
244 _, err := fmt.Fprintf(c.conn, "{%d}\r\n", len(s))
245 c.xcheckf(err, "write sync literal size")
246 line, err := c.Readline()
247 c.xcheckf(err, "read line")
248 if !strings.HasPrefix(line, "+") {
249 c.xerrorf("no continuation received for sync literal")
251 _, err = c.conn.Write([]byte(s))
252 c.xcheckf(err, "write literal data")
256// Transactf writes format and args as an IMAP command, using Commandf with an
257// empty tag. I.e. format must not contain a tag. Transactf then reads a response
258// using ReadResponse and checks the result status is OK.
259func (c *Conn) Transactf(format string, args ...any) (untagged []Untagged, result Result, rerr error) {
260 defer c.recover(&rerr)
262 err := c.Commandf("", format, args...)
264 return nil, Result{}, err
266 return c.ResponseOK()
269func (c *Conn) ResponseOK() (untagged []Untagged, result Result, rerr error) {
270 untagged, result, rerr = c.Response()
272 return nil, Result{}, rerr
274 if result.Status != OK {
275 c.xerrorf("response status %q, expected OK", result.Status)
277 return untagged, result, rerr
280func (c *Conn) xgetUntagged(l []Untagged, dst any) {
282 c.xerrorf("got %d untagged, expected 1: %v", len(l), l)
285 gotv := reflect.ValueOf(got)
286 dstv := reflect.ValueOf(dst)
287 if gotv.Type() != dstv.Type().Elem() {
288 c.xerrorf("got %v, expected %v", gotv.Type(), dstv.Type().Elem())
290 dstv.Elem().Set(gotv)
293// Close closes the connection without writing anything to the server.
294// You may want to call Logout. Closing a connection with a mailbox with deleted
295// message not yet expunged will not expunge those messages.
296func (c *Conn) Close() error {