-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathpop3.go
223 lines (204 loc) · 5.51 KB
/
pop3.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// 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
}