224 lines
5.5 KiB
Go
224 lines
5.5 KiB
Go
// Package pop3 provides an implementation of the Post Office Protocol, Version
|
|
// 3 as defined in RFC 1939. Commands specified as optional are not
|
|
// implemented; however, this implementation may be trivially extended to
|
|
// support them.
|
|
|
|
package pop3
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// The POP3 client.
|
|
type Client struct {
|
|
conn net.Conn
|
|
bin *bufio.Reader
|
|
}
|
|
|
|
// Dial creates an unsecured connection to the POP3 server at the given address
|
|
// and returns the corresponding Client.
|
|
func Dial(addr string) (*Client, error) {
|
|
conn, err := net.Dial("tcp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NewClient(conn)
|
|
}
|
|
|
|
// DialTLS creates a TLS-secured connection to the POP3 server at the given
|
|
// address and returns the corresponding Client.
|
|
func DialTLS(addr string) (*Client, error) {
|
|
conn, err := tls.Dial("tcp", addr, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NewClient(conn)
|
|
}
|
|
|
|
// NewClient returns a new Client object using an existing connection.
|
|
func NewClient(conn net.Conn) (*Client, error) {
|
|
client := &Client{
|
|
bin: bufio.NewReader(conn),
|
|
conn: conn,
|
|
}
|
|
// send dud command, to read a line
|
|
_, err := client.Cmd("")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return client, nil
|
|
}
|
|
|
|
// Convenience function to synchronously run an arbitrary command and wait for
|
|
// output. The terminating CRLF must be included in the format string.
|
|
//
|
|
// Output sent after the first line must be retrieved via readLines.
|
|
func (c *Client) Cmd(format string, args ...interface{}) (string, error) {
|
|
fmt.Fprintf(c.conn, format, args...)
|
|
line, _, err := c.bin.ReadLine()
|
|
if err != nil { return "", err }
|
|
l := string(line)
|
|
if l[0:3] != "+OK" {
|
|
err = errors.New(l[5:])
|
|
}
|
|
if len(l) >= 4 {
|
|
return l[4:], err
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
func (c *Client) ReadLines() (lines []string, err error) {
|
|
lines = make([]string, 0)
|
|
l, _, err := c.bin.ReadLine()
|
|
line := string(l)
|
|
for err == nil && line != "." {
|
|
if len(line) > 0 && line[0] == '.' {
|
|
line = line[1:]
|
|
}
|
|
lines = append(lines, line)
|
|
l, _, err = c.bin.ReadLine()
|
|
line = string(l)
|
|
}
|
|
return
|
|
}
|
|
|
|
// User sends the given username to the server. Generally, there is no reason
|
|
// not to use the Auth convenience method.
|
|
func (c *Client) User(username string) (err error) {
|
|
_, err = c.Cmd("USER %s\r\n", username)
|
|
return
|
|
}
|
|
|
|
// Pass sends the given password to the server. The password is sent
|
|
// unencrypted unless the connection is already secured by TLS (via DialTLS or
|
|
// some other mechanism). Generally, there is no reason not to use the Auth
|
|
// convenience method.
|
|
func (c *Client) Pass(password string) (err error) {
|
|
_, err = c.Cmd("PASS %s\r\n", password)
|
|
return
|
|
}
|
|
|
|
// Auth sends the given username and password to the server, calling the User
|
|
// and Pass methods as appropriate.
|
|
func (c *Client) Auth(username, password string) (err error) {
|
|
err = c.User(username)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = c.Pass(password)
|
|
return
|
|
}
|
|
|
|
// Stat retrieves a drop listing for the current maildrop, consisting of the
|
|
// number of messages and the total size (in octets) of the maildrop.
|
|
// Information provided besides the number of messages and the size of the
|
|
// maildrop is ignored. In the event of an error, all returned numeric values
|
|
// will be 0.
|
|
func (c *Client) Stat() (count, size int, err error) {
|
|
l, err := c.Cmd("STAT\r\n")
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
parts := strings.Fields(l)
|
|
count, err = strconv.Atoi(parts[0])
|
|
if err != nil {
|
|
return 0, 0, errors.New("Invalid server response")
|
|
}
|
|
size, err = strconv.Atoi(parts[1])
|
|
if err != nil {
|
|
return 0, 0, errors.New("Invalid server response")
|
|
}
|
|
return
|
|
}
|
|
|
|
// List returns the size of the given message, if it exists. If the message
|
|
// does not exist, or another error is encountered, the returned size will be
|
|
// 0.
|
|
func (c *Client) List(msg int) (size int, err error) {
|
|
l, err := c.Cmd("LIST %d\r\n", msg)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
size, err = strconv.Atoi(strings.Fields(l)[1])
|
|
if err != nil {
|
|
return 0, errors.New("Invalid server response")
|
|
}
|
|
return size, nil
|
|
}
|
|
|
|
// ListAll returns a list of all messages and their sizes.
|
|
func (c *Client) ListAll() (msgs []int, sizes []int, err error) {
|
|
_, err = c.Cmd("LIST\r\n")
|
|
if err != nil {
|
|
return
|
|
}
|
|
lines, err := c.ReadLines()
|
|
if err != nil {
|
|
return
|
|
}
|
|
msgs = make([]int, len(lines), len(lines))
|
|
sizes = make([]int, len(lines), len(lines))
|
|
for i, l := range lines {
|
|
var m, s int
|
|
fs := strings.Fields(l)
|
|
m, err = strconv.Atoi(fs[0])
|
|
if err != nil {
|
|
return
|
|
}
|
|
s, err = strconv.Atoi(fs[1])
|
|
if err != nil {
|
|
return
|
|
}
|
|
msgs[i] = m
|
|
sizes[i] = s
|
|
}
|
|
return
|
|
}
|
|
|
|
// Retr downloads and returns the given message. The lines are separated by LF,
|
|
// whatever the server sent.
|
|
func (c *Client) Retr(msg int) (text string, err error) {
|
|
_, err = c.Cmd("RETR %d\r\n", msg)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
lines, err := c.ReadLines()
|
|
text = strings.Join(lines, "\n")
|
|
return
|
|
}
|
|
|
|
// Dele marks the given message as deleted.
|
|
func (c *Client) Dele(msg int) (err error) {
|
|
_, err = c.Cmd("DELE %d\r\n", msg)
|
|
return
|
|
}
|
|
|
|
// Noop does nothing, but will prolong the end of the connection if the server
|
|
// has a timeout set.
|
|
func (c *Client) Noop() (err error) {
|
|
_, err = c.Cmd("NOOP\r\n")
|
|
return
|
|
}
|
|
|
|
// Rset unmarks any messages marked for deletion previously in this session.
|
|
func (c *Client) Rset() (err error) {
|
|
_, err = c.Cmd("RSET\r\n")
|
|
return
|
|
}
|
|
|
|
// Quit sends the QUIT message to the POP3 server and closes the connection.
|
|
func (c *Client) Quit() error {
|
|
_, err := c.Cmd("QUIT\r\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.conn.Close()
|
|
return nil
|
|
}
|