This is the v1 version, had the v2 before.
This commit is contained in:
284
conn.go
Normal file
284
conn.go
Normal file
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user