diff options
Diffstat (limited to 'vendor/github.com/emersion/go-imap/conn.go')
-rw-r--r-- | vendor/github.com/emersion/go-imap/conn.go | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/vendor/github.com/emersion/go-imap/conn.go b/vendor/github.com/emersion/go-imap/conn.go new file mode 100644 index 0000000..09ce633 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/conn.go @@ -0,0 +1,284 @@ +package imap + +import ( + "bufio" + "crypto/tls" + "io" + "net" + "sync" +) + +// 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 Waiter struct { + start sync.WaitGroup + end sync.WaitGroup + finished bool +} + +func NewWaiter() *Waiter { + w := &Waiter{finished: false} + w.start.Add(1) + w.end.Add(1) + return w +} + +func (w *Waiter) Wait() { + if !w.finished { + // Signal that we are ready for upgrade to continue. + w.start.Done() + // Wait for upgrade to finish. + w.end.Wait() + w.finished = true + } +} + +func (w *Waiter) WaitReady() { + if !w.finished { + // Wait for reader/writer goroutine to be ready for upgrade. + w.start.Wait() + } +} + +func (w *Waiter) Close() { + if !w.finished { + // Upgrade is finished, close chanel to release reader/writer + w.end.Done() + } +} + +type LockedWriter struct { + lock sync.Mutex + writer io.Writer +} + +// NewLockedWriter - goroutine safe writer. +func NewLockedWriter(w io.Writer) io.Writer { + return &LockedWriter{writer: w} +} + +func (w *LockedWriter) Write(b []byte) (int, error) { + w.lock.Lock() + defer w.lock.Unlock() + return w.writer.Write(b) +} + +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} +} + +// Underlying connection state information. +type ConnInfo struct { + RemoteAddr net.Addr + LocalAddr net.Addr + + // nil if connection is not using TLS. + TLS *tls.ConnectionState +} + +// An IMAP connection. +type Conn struct { + net.Conn + *Reader + *Writer + + br *bufio.Reader + bw *bufio.Writer + + waiter *Waiter + + // 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) createWaiter() *Waiter { + // create new waiter each time. + w := NewWaiter() + c.waiter = w + return w +} + +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 local and remote are the same, then we need a LockedWriter. + if localDebug == remoteDebug { + localDebug = NewLockedWriter(localDebug) + remoteDebug = localDebug + } + + 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), + } + } +} + +func (c *Conn) Info() *ConnInfo { + info := &ConnInfo{ + RemoteAddr: c.RemoteAddr(), + LocalAddr: c.LocalAddr(), + } + + tlsConn, ok := c.Conn.(*tls.Conn) + if ok { + state := tlsConn.ConnectionState() + info.TLS = &state + } + + return info +} + +// 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 { + return c.Writer.Flush() +} + +// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted +// tunnel. +func (c *Conn) Upgrade(upgrader ConnUpgrader) error { + // Block reads and writes during the upgrading process + w := c.createWaiter() + defer w.Close() + + upgraded, err := upgrader(c.Conn) + if err != nil { + return err + } + + c.Conn = upgraded + c.init() + return nil +} + +// Called by reader/writer goroutines to wait for Upgrade to finish +func (c *Conn) Wait() { + c.waiter.Wait() +} + +// Called by Upgrader to wait for reader/writer goroutines to be ready for +// upgrade. +func (c *Conn) WaitReady() { + c.waiter.WaitReady() +} + +// 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() +} |