198 lines
4.4 KiB
Go
198 lines
4.4 KiB
Go
package imap
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"net"
|
|
)
|
|
|
|
// A connection state.
|
|
// See RFC 3501 section 3.
|
|
type ConnState int
|
|
|
|
const (
|
|
// In the connecting state, the server has not yet sent a greeting and no
|
|
// command can be issued.
|
|
ConnectingState = 0
|
|
|
|
// In the not authenticated state, the client MUST supply
|
|
// authentication credentials before most commands will be
|
|
// permitted. This state is entered when a connection starts
|
|
// unless the connection has been pre-authenticated.
|
|
NotAuthenticatedState ConnState = 1 << 0
|
|
|
|
// In the authenticated state, the client is authenticated and MUST
|
|
// select a mailbox to access before commands that affect messages
|
|
// will be permitted. This state is entered when a
|
|
// pre-authenticated connection starts, when acceptable
|
|
// authentication credentials have been provided, after an error in
|
|
// selecting a mailbox, or after a successful CLOSE command.
|
|
AuthenticatedState = 1 << 1
|
|
|
|
// In a selected state, a mailbox has been selected to access.
|
|
// This state is entered when a mailbox has been successfully
|
|
// selected.
|
|
SelectedState = AuthenticatedState + 1<<2
|
|
|
|
// In the logout state, the connection is being terminated. This
|
|
// state can be entered as a result of a client request (via the
|
|
// LOGOUT command) or by unilateral action on the part of either
|
|
// the client or server.
|
|
LogoutState = 1 << 3
|
|
|
|
// ConnectedState is either NotAuthenticatedState, AuthenticatedState or
|
|
// SelectedState.
|
|
ConnectedState = NotAuthenticatedState | AuthenticatedState | SelectedState
|
|
)
|
|
|
|
// A function that upgrades a connection.
|
|
//
|
|
// This should only be used by libraries implementing an IMAP extension (e.g.
|
|
// COMPRESS).
|
|
type ConnUpgrader func(conn net.Conn) (net.Conn, error)
|
|
|
|
type debugWriter struct {
|
|
io.Writer
|
|
|
|
local io.Writer
|
|
remote io.Writer
|
|
}
|
|
|
|
// NewDebugWriter creates a new io.Writer that will write local network activity
|
|
// to local and remote network activity to remote.
|
|
func NewDebugWriter(local, remote io.Writer) io.Writer {
|
|
return &debugWriter{Writer: local, local: local, remote: remote}
|
|
}
|
|
|
|
type multiFlusher struct {
|
|
flushers []flusher
|
|
}
|
|
|
|
func (mf *multiFlusher) Flush() error {
|
|
for _, f := range mf.flushers {
|
|
if err := f.Flush(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newMultiFlusher(flushers ...flusher) flusher {
|
|
return &multiFlusher{flushers}
|
|
}
|
|
|
|
// An IMAP connection.
|
|
type Conn struct {
|
|
net.Conn
|
|
*Reader
|
|
*Writer
|
|
|
|
br *bufio.Reader
|
|
bw *bufio.Writer
|
|
|
|
waits chan struct{}
|
|
|
|
// Print all commands and responses to this io.Writer.
|
|
debug io.Writer
|
|
}
|
|
|
|
// NewConn creates a new IMAP connection.
|
|
func NewConn(conn net.Conn, r *Reader, w *Writer) *Conn {
|
|
c := &Conn{Conn: conn, Reader: r, Writer: w}
|
|
|
|
c.init()
|
|
return c
|
|
}
|
|
|
|
func (c *Conn) init() {
|
|
r := io.Reader(c.Conn)
|
|
w := io.Writer(c.Conn)
|
|
|
|
if c.debug != nil {
|
|
localDebug, remoteDebug := c.debug, c.debug
|
|
if debug, ok := c.debug.(*debugWriter); ok {
|
|
localDebug, remoteDebug = debug.local, debug.remote
|
|
}
|
|
|
|
if localDebug != nil {
|
|
w = io.MultiWriter(c.Conn, localDebug)
|
|
}
|
|
if remoteDebug != nil {
|
|
r = io.TeeReader(c.Conn, remoteDebug)
|
|
}
|
|
}
|
|
|
|
if c.br == nil {
|
|
c.br = bufio.NewReader(r)
|
|
c.Reader.reader = c.br
|
|
} else {
|
|
c.br.Reset(r)
|
|
}
|
|
|
|
if c.bw == nil {
|
|
c.bw = bufio.NewWriter(w)
|
|
c.Writer.Writer = c.bw
|
|
} else {
|
|
c.bw.Reset(w)
|
|
}
|
|
|
|
if f, ok := c.Conn.(flusher); ok {
|
|
c.Writer.Writer = struct {
|
|
io.Writer
|
|
flusher
|
|
}{
|
|
c.bw,
|
|
newMultiFlusher(c.bw, f),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write implements io.Writer.
|
|
func (c *Conn) Write(b []byte) (n int, err error) {
|
|
return c.Writer.Write(b)
|
|
}
|
|
|
|
// Flush writes any buffered data to the underlying connection.
|
|
func (c *Conn) Flush() error {
|
|
if err := c.Writer.Flush(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
|
|
// tunnel.
|
|
func (c *Conn) Upgrade(upgrader ConnUpgrader) error {
|
|
// Flush all buffered data
|
|
if err := c.Flush(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Block reads and writes during the upgrading process
|
|
c.waits = make(chan struct{})
|
|
defer close(c.waits)
|
|
|
|
upgraded, err := upgrader(c.Conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.Conn = upgraded
|
|
c.init()
|
|
return nil
|
|
}
|
|
|
|
// Wait waits for the connection to be ready for reads and writes.
|
|
func (c *Conn) Wait() {
|
|
if c.waits != nil {
|
|
<-c.waits
|
|
}
|
|
}
|
|
|
|
// SetDebug defines an io.Writer to which all network activity will be logged.
|
|
// If nil is provided, network activity will not be logged.
|
|
func (c *Conn) SetDebug(w io.Writer) {
|
|
c.debug = w
|
|
c.init()
|
|
}
|