├── go.mod
├── .gitignore
├── go.sum
├── main.go
├── README.md
└── pkg
├── PopMail.go
├── parseMIMEmail.go
└── pop3.go
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/hktalent/gopop3
2 |
3 | go 1.18
4 |
5 | require github.com/emersion/go-message v0.15.0
6 |
7 | require github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | popmails_linux_amd64
7 | popmails_macos_amd64
8 | lists.txt
9 | # Folders
10 | _obj
11 | _test
12 |
13 | # Architecture specific extensions/prefixes
14 | *.[568vq]
15 | [568vq].out
16 |
17 | *.cgo1.go
18 | *.cgo2.c
19 | _cgo_defun.c
20 | _cgo_gotypes.go
21 | _cgo_export.*
22 |
23 | _testmain.go
24 |
25 | *.exe
26 | *.test
27 |
28 | #
29 | *.out
30 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/emersion/go-message v0.15.0 h1:urgKGqt2JAc9NFJcgncQcohHdiYb803YTH9OQwHBHIY=
2 | github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
3 | github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
4 | github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
5 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
6 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
7 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/hktalent/gopop3/pkg"
6 | "io/ioutil"
7 | "log"
8 | "os"
9 | "strings"
10 | "sync"
11 | )
12 |
13 | func main() {
14 | fmt.Println("How use?\n./popmails lists.txt xxxUser 50 pop3.xxx.com\n")
15 | // os.Args = []string{"", "lists.txt", "xxx", "50", "mail.xx.com"}
16 | Wg := sync.WaitGroup{}
17 | if data, err := ioutil.ReadFile(os.Args[1]); nil != err {
18 | log.Printf("read %s is error: %v\n", os.Args[1], err)
19 | return
20 | } else {
21 | a := strings.Split(string(data), "\n")
22 | for _, x := range a {
23 | j := strings.Split(x, "\t")
24 | if 2 <= len(j) {
25 | x := pkg.GetPopMail(j[0], j[1], os.Args[4], &Wg)
26 | Wg.Add(1)
27 | go x.PopAllMails()
28 | }
29 | }
30 | }
31 | Wg.Wait()
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://twitter.com/intent/follow?screen_name=Hktalent3135773) [](https://twitter.com/intent/follow?screen_name=Hktalent3135773) [](https://github.com/hktalent/)
2 |
3 | # go4hacker-mails
4 |
5 | Bulk email collection and retrieval,Assist penetration and redness teams in searching for sensitive information in emails for multiple accounts, such as admin, VPN, and password information. All searches are completed in memory without landing, thus avoiding their own risks
6 | 帮助渗透、红对团队在若干账号的邮件中搜索敏感信息,例如admin、vpn账号及密码信息,所有的搜索在内存中完成,不落地,从而规避自己的风险
7 |
8 | ## Usage
9 | searchText eg:
10 | ```
11 | password
12 | passwd
13 | admin
14 | vpn
15 | ```
16 | 50 50 indicates that the output will be searched to 50 bytes after the text is found
17 |
18 | go4hacker-mails_macos_amd64 lists.txt searchText 50 MailServer
19 | eg:
20 | popmails lists.txt password 50 pop3.seclover.com
21 |
22 | #### out results save to file:
23 | SMResults.txt
24 |
25 | #### lists.txt
26 | format: user[tab]pswd
27 | ```
28 | user1 pswd1
29 | user2 pswd2
30 | user3@xx.com pswd1
31 | username1 pswd1
32 | username2 pswd2
33 | username3 pswd3
34 | ```
35 |
36 |
37 | ## 💖Star
38 | [](https://starchart.cc/hktalent/go4hacker-mails)
39 |
40 | # Donation
41 | | Wechat Pay | AliPay | Paypal | BTC Pay |BCH Pay |
42 | | --- | --- | --- | --- | --- |
43 | |
|
|[paypal](https://www.paypal.me/pwned2019) **miracletalent@gmail.com**|
|
|
44 |
45 |
--------------------------------------------------------------------------------
/pkg/PopMail.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "hash/fnv"
7 | "io"
8 | "log"
9 | "mime"
10 | "regexp"
11 | "strings"
12 | "sync"
13 | )
14 |
15 | type PopMail struct {
16 | User string
17 | Pswd string
18 | Server string
19 | Wg *sync.WaitGroup
20 | }
21 |
22 | func GetStr(r *io.Reader) string {
23 | buf := new(strings.Builder)
24 | n, err := io.Copy(buf, *r)
25 | if err != nil || 0 == n {
26 | //log.Println("GetStr ", err)
27 | return ""
28 | }
29 | return buf.String()
30 | }
31 |
32 | func hash(s string) uint32 {
33 | h := fnv.New32a()
34 | h.Write([]byte(s))
35 | return h.Sum32()
36 | }
37 |
38 | var rg01 = regexp.MustCompile(`=\?UTF-8\?B\?(.*?)\?=`)
39 |
40 | func DecodeBase64(s string) string {
41 | if s2, err := base64.StdEncoding.DecodeString(s); nil == err {
42 | return string(s2)
43 | } else {
44 | log.Println(err)
45 | }
46 | return s
47 | }
48 |
49 | func DecodeTitle(s string) string {
50 | a := rg01.FindAllStringSubmatch(s, -1)
51 | var a1 []string
52 | for i, _ := range a {
53 | if 2 == len(a[i]) {
54 | a1 = append(a1, DecodeBase64(a[i][1]))
55 | }
56 | }
57 | //log.Println(a)
58 | return strings.Join(a1, " ")
59 | }
60 | func GetPopMail(u, p, s string, Wg *sync.WaitGroup) *PopMail {
61 | //os.MkdirAll(s, os.ModePerm)
62 | if -1 == strings.Index(u, "@") {
63 | a := strings.Split(s, ".")[1:]
64 | u = u + "@" + strings.Join(a, ".")
65 | }
66 |
67 | return &PopMail{User: u, Pswd: p, Server: s, Wg: Wg}
68 | }
69 |
70 | func (r *PopMail) WriteFile(id int, args ...string) {
71 | s := fmt.Sprintf("%d %s", id, strings.Join(args, "\n"))
72 | szFileName := fmt.Sprintf("%s/%d.txt", r.Server, hash(s))
73 | SaveFile(szFileName, []byte(s))
74 | }
75 | func (r *PopMail) PopAllMails() {
76 | defer r.Wg.Done()
77 | p := New(Opt{
78 | Host: r.Server,
79 | Port: 995,
80 | TLSEnabled: true,
81 | //Port: 110,
82 | //TLSEnabled: false,
83 | })
84 |
85 | // Create a new connection. POP3 connections are stateful and should end
86 | // with a Quit() once the opreations are done.
87 | c, err := p.NewConn()
88 | if err != nil {
89 | log.Printf("%v", err)
90 | return
91 | }
92 | defer c.Quit()
93 |
94 | // Authenticate.
95 | if err := c.Auth(r.User, r.Pswd); err != nil {
96 | log.Printf("%v", err)
97 | return
98 | }
99 | // Print the total number of messages and their size.
100 | count, size, _ := c.Stat()
101 | fmt.Println("total messages=", count, "size=", size)
102 |
103 | // Pull the list of all message IDs and their sizes.
104 | msgs, _ := c.List(0)
105 | for _, m := range msgs {
106 | fmt.Sprintln("id=", m.ID, "size=", m.Size)
107 | }
108 |
109 | // Pull all messages on the server. Message IDs go from 1 to N.
110 | for id := 1; id <= count; id++ {
111 | m, _ := c.Retr(id)
112 | //s1 := m.Header.Get("subject")
113 | dec := new(mime.WordDecoder)
114 | from, _ := dec.DecodeHeader(m.Header.Get("From"))
115 | to, _ := dec.DecodeHeader(m.Header.Get("To"))
116 | subject, _ := dec.DecodeHeader(m.Header.Get("Subject"))
117 |
118 | mediaType, params, err := mime.ParseMediaType(m.Header.Get("Content-Type"))
119 | if err != nil {
120 | log.Printf("%v", err)
121 | return
122 | }
123 | if strings.HasPrefix(mediaType, "multipart/") {
124 | r.ParsePart(m.Body, params["boundary"], 1, r.Server)
125 | }
126 | // DecodeTitle(s1)
127 | r.WriteFile(id, from, to, subject)
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/pkg/parseMIMEmail.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | import (
4 | "bytes"
5 | "encoding/base64"
6 | "fmt"
7 | "io"
8 | "io/ioutil"
9 | "log"
10 | "mime"
11 | "mime/multipart"
12 | "mime/quotedprintable"
13 | "os"
14 | "regexp"
15 | "strconv"
16 | "strings"
17 | )
18 |
19 | var r02 = regexp.MustCompile(`^-+=`)
20 |
21 | func SaveFile(s string, data []byte) {
22 | // lists.txt 20
23 | //if 3 < len(os.Args) {
24 | // ioutil.WriteFile(s, data, 0644)
25 | //}
26 | }
27 |
28 | // BuildFileName builds a file name for a MIME part, using information extracted from
29 | // the part itself, as well as a radix and an index given as parameters.
30 | func BuildFileName(part *multipart.Part, radix string, index int) (filename string) {
31 | // 1st try to get the true file name if there is one in Content-Disposition
32 | filename = part.FileName()
33 | if strings.HasSuffix(filename, "=?") {
34 | filename = DecodeTitle(filename)
35 | }
36 | if len(filename) > 0 {
37 | return
38 | }
39 | // If no defaut filename defined, try to build one of the following format :
40 | // "radix-index.ext" where extension is comuputed from the Content-Type of the part
41 | mediaType, _, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
42 | if err == nil {
43 | mime_type, e := mime.ExtensionsByType(mediaType)
44 | if e == nil {
45 | //if strings.HasSuffix(radix, "=?") {
46 | // radix = DecodeTitle(radix)
47 | //} else {
48 | radix = r02.ReplaceAllString(radix, "")
49 | //}
50 | var s09 string
51 | if 0 == len(mime_type) {
52 | s09 = ""
53 | } else {
54 | s09 = mime_type[len(mime_type)-1]
55 | }
56 | return fmt.Sprintf("%s-%d%s", radix, index, s09)
57 | }
58 | }
59 | return
60 |
61 | }
62 |
63 | func DoFileName(s string) string {
64 | if strings.HasPrefix(s, "=?") {
65 | s = DecodeTitle(s)
66 | } else if strings.HasSuffix(s, "?=") {
67 | s = DecodeTitle("=?UTF-8?B?" + s)
68 | }
69 | return s
70 | }
71 |
72 | // 追加到文件中
73 | func AppendFile(szFile, szOut string) {
74 | f, err := os.OpenFile(szFile,
75 | os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
76 | if err != nil {
77 | log.Println(err)
78 | return
79 | }
80 | defer f.Close()
81 | if _, err := f.WriteString(szOut + "\n"); err != nil {
82 | log.Println(err)
83 | }
84 | }
85 |
86 | func (r *PopMail) SearchText(data []byte) {
87 | if 2 < len(os.Args) {
88 | s := string(data)
89 | s1 := os.Args[2]
90 | j := strings.Index(s, s1)
91 | if -1 < j {
92 | x01 := 200
93 | if 3 < len(os.Args) {
94 | if m, err := strconv.Atoi(os.Args[3]); nil == err {
95 | x01 = m
96 | }
97 | }
98 | n := j + len(s1) + x01
99 | if n > len(s) {
100 | n = len(s)
101 | }
102 | s001 := "[" + r.User + "] " + s[j:n] + "\n===================\n"
103 | AppendFile("SMResults.txt", s001)
104 | fmt.Printf("found in: %s\n==========\n", s001)
105 | }
106 | }
107 | }
108 |
109 | // WitePart decodes the data of MIME part and writes it to the file filename.
110 | func (r *PopMail) WritePart(part *multipart.Part, filename string, path string) {
111 | // Read the data for this MIME part
112 | part_data, err := ioutil.ReadAll(part)
113 | if err != nil || 0 == len(part_data) {
114 | //log.Println("Error reading MIME part data -", err)
115 | return
116 | }
117 | content_transfer_encoding := strings.ToUpper(part.Header.Get("Content-Transfer-Encoding"))
118 | switch {
119 | case strings.Compare(content_transfer_encoding, "BASE64") == 0:
120 | decoded_content, err := base64.StdEncoding.DecodeString(string(part_data))
121 | if err != nil {
122 | //log.Println("Error decoding base64 -", err)
123 | } else {
124 | r.SearchText(decoded_content)
125 | SaveFile(path+"/"+DoFileName(filename), decoded_content)
126 | }
127 |
128 | case strings.Compare(content_transfer_encoding, "QUOTED-PRINTABLE") == 0:
129 | decoded_content, err := ioutil.ReadAll(quotedprintable.NewReader(bytes.NewReader(part_data)))
130 | if err != nil {
131 | //log.Println("Error decoding quoted-printable -", err)
132 | } else {
133 | r.SearchText(decoded_content)
134 | SaveFile(path+"/"+DoFileName(filename), decoded_content)
135 | }
136 | default:
137 | r.SearchText(part_data)
138 | SaveFile(path+"/"+DoFileName(filename), part_data)
139 | }
140 | }
141 |
142 | // ParsePart parses the MIME part from mime_data, each part being separated by
143 | // boundary. If one of the part read is itself a multipart MIME part, the
144 | // function calls itself to recursively parse all the parts. The parts read
145 | // are decoded and written to separate files, named uppon their Content-Descrption
146 | // (or boundary if no Content-Description available) with the appropriate
147 | // file extension. Index is incremented at each recursive level and is used in
148 | // building the filename where the part is written, as to ensure all filenames
149 | // are distinct.
150 | func (r *PopMail) ParsePart(mime_data io.Reader, boundary string, index int, path string) {
151 | // Instantiate a new io.Reader dedicated to MIME multipart parsing
152 | // using multipart.NewReader()
153 | reader := multipart.NewReader(mime_data, boundary)
154 | if reader == nil {
155 | return
156 | }
157 | //fmt.Println(strings.Repeat(" ", 2*(index-1)), ">>>>>>>>>>>>> ", boundary)
158 | // Go through each of the MIME part of the message Body with NextPart(),
159 | // and read the content of the MIME part with ioutil.ReadAll()
160 | for {
161 | new_part, err := reader.NextPart()
162 | if err == io.EOF {
163 | break
164 | }
165 | if err != nil {
166 | break
167 | }
168 | //for key, value := range new_part.Header {
169 | // fmt.Printf("%s Key: (%+v) - %d Value: (%#v)\n", strings.Repeat(" ", 2*(index-1)), key, len(value), value)
170 | //}
171 | //fmt.Println(strings.Repeat(" ", 2*(index-1)), "------------")
172 | mediaType, params, err := mime.ParseMediaType(new_part.Header.Get("Content-Type"))
173 | if err == nil && strings.HasPrefix(mediaType, "multipart/") {
174 | r.ParsePart(new_part, params["boundary"], index+1, path)
175 | } else {
176 | filename := BuildFileName(new_part, boundary, 1)
177 | r.WritePart(new_part, filename, path)
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/pkg/pop3.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "crypto/tls"
7 | "errors"
8 | "fmt"
9 | "net"
10 | "strconv"
11 | "strings"
12 | "time"
13 |
14 | "github.com/emersion/go-message"
15 | )
16 |
17 | // Client implements a Client e-mail client.
18 | type Client struct {
19 | opt Opt
20 | }
21 |
22 | // Conn is a stateful connection with the POP3 server/
23 | type Conn struct {
24 | conn net.Conn
25 | r *bufio.Reader
26 | w *bufio.Writer
27 | }
28 |
29 | // Opt represents the client configuration.
30 | type Opt struct {
31 | Host string `json:"host"`
32 | Port int `json:"port"`
33 |
34 | // Default is 3 seconds.
35 | DialTimeout time.Duration `json:"dial_timeout"`
36 |
37 | TLSEnabled bool `json:"tls_enabled"`
38 | TLSSkipVerify bool `json:"tls_skip_verify"`
39 | }
40 |
41 | // MessageID contains the ID and size of an individual message.
42 | type MessageID struct {
43 | // ID is the numerical index (non-unique) of the message.
44 | ID int
45 | Size int
46 |
47 | // UID is only present if the response is to the UIDL command.
48 | UID string
49 | }
50 |
51 | var (
52 | lineBreak = []byte("\r\n")
53 |
54 | respOK = []byte("+OK") // `+OK` without additional info
55 | respOKInfo = []byte("+OK ") // `+OK `
56 | respErr = []byte("-ERR") // `-ERR` without additional info
57 | respErrInfo = []byte("-ERR ") // `-ERR `
58 | )
59 |
60 | // New returns a new client object using an existing connection.
61 | func New(opt Opt) *Client {
62 | if opt.DialTimeout < time.Millisecond {
63 | opt.DialTimeout = time.Second * 3
64 | }
65 |
66 | return &Client{
67 | opt: opt,
68 | }
69 | }
70 |
71 | // NewConn creates and returns live POP3 server connection.
72 | func (c *Client) NewConn() (*Conn, error) {
73 | var (
74 | addr = fmt.Sprintf("%s:%d", c.opt.Host, c.opt.Port)
75 | )
76 |
77 | conn, err := net.DialTimeout("tcp", addr, c.opt.DialTimeout)
78 | if err != nil {
79 | return nil, err
80 | }
81 |
82 | // No TLS.
83 | if c.opt.TLSEnabled {
84 | // Skip TLS host verification.
85 | //tlsCfg := tls.Config{}
86 | tlsCfg := tls.Config{
87 | Renegotiation: tls.RenegotiateOnceAsClient,
88 | InsecureSkipVerify: true,
89 | MinVersion: tls.VersionTLS10,
90 | }
91 | if c.opt.TLSSkipVerify {
92 | tlsCfg.InsecureSkipVerify = c.opt.TLSSkipVerify
93 | } else {
94 | tlsCfg.ServerName = c.opt.Host // strings.Split(c.opt.Host,":")[0]
95 | }
96 |
97 | conn = tls.Client(conn, &tlsCfg)
98 | }
99 |
100 | pCon := &Conn{
101 | conn: conn,
102 | r: bufio.NewReader(conn),
103 | w: bufio.NewWriter(conn),
104 | }
105 |
106 | // Verify the connection by reading the welcome +OK greeting.
107 | if _, err := pCon.ReadOne(); err != nil {
108 | return nil, err
109 | }
110 |
111 | return pCon, nil
112 | }
113 |
114 | // Send sends a POP3 command to the server. The given comand is suffixed with "\r\n".
115 | func (c *Conn) Send(b string) error {
116 | if _, err := c.w.WriteString(b + "\r\n"); err != nil {
117 | return err
118 | }
119 | return c.w.Flush()
120 | }
121 |
122 | // Cmd sends a command to the server. POP3 responses are either single line or multi-line.
123 | // The first line always with -ERR in case of an error or +OK in case of a successful operation.
124 | // OK+ is always followed by a response on the same line which is either the actual response data
125 | // in case of single line responses, or a help message followed by multiple lines of actual response
126 | // data in case of multiline responses.
127 | // See https://www.shellhacks.com/retrieve-email-pop3-server-command-line/ for examples.
128 | func (c *Conn) Cmd(cmd string, isMulti bool, args ...interface{}) (*bytes.Buffer, error) {
129 | var cmdLine string
130 |
131 | // Repeat a %v to format each arg.
132 | if len(args) > 0 {
133 | format := " " + strings.TrimRight(strings.Repeat("%v ", len(args)), " ")
134 |
135 | // CMD arg1 argn ...\r\n
136 | cmdLine = fmt.Sprintf(cmd+format, args...)
137 | } else {
138 | cmdLine = cmd
139 | }
140 |
141 | if err := c.Send(cmdLine); err != nil {
142 | return nil, err
143 | }
144 |
145 | // Read the first line of response to get the +OK/-ERR status.
146 | b, err := c.ReadOne()
147 | if err != nil {
148 | return nil, err
149 | }
150 |
151 | // Single line response.
152 | if !isMulti {
153 | return bytes.NewBuffer(b), err
154 | }
155 |
156 | buf, err := c.ReadAll()
157 | return buf, err
158 | }
159 |
160 | // ReadOne reads a single line response from the conn.
161 | func (c *Conn) ReadOne() ([]byte, error) {
162 | b, _, err := c.r.ReadLine()
163 | if err != nil {
164 | return nil, err
165 | }
166 |
167 | r, err := parseResp(b)
168 | return r, err
169 | }
170 |
171 | // ReadAll reads all lines from the connection until the POP3 multiline terminator "." is encountered
172 | // and returns a bytes.Buffer of all the read lines.
173 | func (c *Conn) ReadAll() (*bytes.Buffer, error) {
174 | buf := &bytes.Buffer{}
175 |
176 | for {
177 | b, _, err := c.r.ReadLine()
178 | if err != nil {
179 | return nil, err
180 | }
181 |
182 | // "." indicates the end of a multi-line response.
183 | if bytes.Equal(b, []byte(".")) {
184 | break
185 | }
186 |
187 | if _, err := buf.Write(b); err != nil {
188 | return nil, err
189 | }
190 | if _, err := buf.Write(lineBreak); err != nil {
191 | return nil, err
192 | }
193 | }
194 |
195 | return buf, nil
196 | }
197 |
198 | // Auth authenticates the given credentials with the server.
199 | func (c *Conn) Auth(user, password string) error {
200 | if err := c.User(user); err != nil {
201 | return err
202 | }
203 |
204 | if err := c.Pass(password); err != nil {
205 | return err
206 | }
207 |
208 | // Issue a NOOP to force the server to respond to the auth.
209 | // Couresy: github.com/TheCreeper/go-pop3
210 | return c.Noop()
211 | }
212 |
213 | // User sends the username to the server.
214 | func (c *Conn) User(s string) error {
215 | _, err := c.Cmd("USER", false, s)
216 | return err
217 | }
218 |
219 | // Pass sends the password to the server.
220 | func (c *Conn) Pass(s string) error {
221 | _, err := c.Cmd("PASS", false, s)
222 | return err
223 | }
224 |
225 | // Stat returns the number of messages and their total size in bytes in the inbox.
226 | func (c *Conn) Stat() (int, int, error) {
227 | b, err := c.Cmd("STAT", false)
228 | if err != nil {
229 | return 0, 0, err
230 | }
231 |
232 | // count size
233 | f := bytes.Fields(b.Bytes())
234 |
235 | // Total number of messages.
236 | count, err := strconv.Atoi(string(f[0]))
237 | if err != nil {
238 | return 0, 0, err
239 | }
240 | if count == 0 {
241 | return 0, 0, nil
242 | }
243 |
244 | // Total size of all messages in bytes.
245 | size, err := strconv.Atoi(string(f[1]))
246 | if err != nil {
247 | return 0, 0, err
248 | }
249 |
250 | return count, size, nil
251 | }
252 |
253 | // List returns a list of (message ID, message Size) pairs.
254 | // If the optional msgID > 0, then only that particular message is listed.
255 | // The message IDs are sequential, 1 to N.
256 | func (c *Conn) List(msgID int) ([]MessageID, error) {
257 | var (
258 | buf *bytes.Buffer
259 | err error
260 | )
261 |
262 | if msgID <= 0 {
263 | // Multiline response listing all messages.
264 | buf, err = c.Cmd("LIST", true)
265 | } else {
266 | // Single line response listing one message.
267 | buf, err = c.Cmd("LIST", false, msgID)
268 | }
269 | if err != nil {
270 | return nil, err
271 | }
272 |
273 | var (
274 | out []MessageID
275 | lines = bytes.Split(buf.Bytes(), lineBreak)
276 | )
277 |
278 | for _, l := range lines {
279 | // id size
280 | f := bytes.Fields(l)
281 | if len(f) == 0 {
282 | break
283 | }
284 |
285 | id, err := strconv.Atoi(string(f[0]))
286 | if err != nil {
287 | return nil, err
288 | }
289 |
290 | size, err := strconv.Atoi(string(f[1]))
291 | if err != nil {
292 | return nil, err
293 | }
294 |
295 | out = append(out, MessageID{ID: id, Size: size})
296 | }
297 |
298 | return out, nil
299 | }
300 |
301 | // Uidl returns a list of (message ID, message UID) pairs. If the optional msgID
302 | // is > 0, then only that particular message is listed. It works like Top() but only works on
303 | // servers that support the UIDL command. Messages size field is not available in the UIDL response.
304 | func (c *Conn) Uidl(msgID int) ([]MessageID, error) {
305 | var (
306 | buf *bytes.Buffer
307 | err error
308 | )
309 |
310 | if msgID <= 0 {
311 | // Multiline response listing all messages.
312 | buf, err = c.Cmd("UIDL", true)
313 | } else {
314 | // Single line response listing one message.
315 | buf, err = c.Cmd("UIDL", false, msgID)
316 | }
317 | if err != nil {
318 | return nil, err
319 | }
320 |
321 | var (
322 | out []MessageID
323 | lines = bytes.Split(buf.Bytes(), lineBreak)
324 | )
325 |
326 | for _, l := range lines {
327 | // id size
328 | f := bytes.Fields(l)
329 | if len(f) == 0 {
330 | break
331 | }
332 |
333 | id, err := strconv.Atoi(string(f[0]))
334 | if err != nil {
335 | return nil, err
336 | }
337 |
338 | out = append(out, MessageID{ID: id, UID: string(f[1])})
339 | }
340 |
341 | return out, nil
342 | }
343 |
344 | // Retr downloads a message by the given msgID, parses it and returns it as a
345 | // emersion/go-message.message.Entity object.
346 | func (c *Conn) Retr(msgID int) (*message.Entity, error) {
347 | b, err := c.Cmd("RETR", true, msgID)
348 | if err != nil {
349 | return nil, err
350 | }
351 |
352 | m, err := message.Read(b)
353 | if err != nil {
354 | if !message.IsUnknownCharset(err) {
355 | return nil, err
356 | }
357 | }
358 |
359 | return m, nil
360 | }
361 |
362 | // RetrRaw downloads a message by the given msgID and returns the raw []byte
363 | // of the entire message.
364 | func (c *Conn) RetrRaw(msgID int) (*bytes.Buffer, error) {
365 | b, err := c.Cmd("RETR", true, msgID)
366 | return b, err
367 | }
368 |
369 | // Top retrieves a message by its ID with full headers and numLines lines of the body.
370 | func (c *Conn) Top(msgID int, numLines int) (*message.Entity, error) {
371 | b, err := c.Cmd("TOP", true, msgID, numLines)
372 | if err != nil {
373 | return nil, err
374 | }
375 |
376 | m, err := message.Read(b)
377 | if err != nil {
378 | return nil, err
379 | }
380 |
381 | return m, nil
382 | }
383 |
384 | // Dele deletes one or more messages. The server only executes the
385 | // deletions after a successful Quit().
386 | func (c *Conn) Dele(msgID ...int) error {
387 | for _, id := range msgID {
388 | _, err := c.Cmd("DELE", false, id)
389 | if err != nil {
390 | return err
391 | }
392 | }
393 | return nil
394 | }
395 |
396 | // Rset clears the messages marked for deletion in the current session.
397 | func (c *Conn) Rset() error {
398 | _, err := c.Cmd("RSET", false)
399 | return err
400 | }
401 |
402 | // Noop issues a do-nothing NOOP command to the server. This is useful for
403 | // prolonging open connections.
404 | func (c *Conn) Noop() error {
405 | _, err := c.Cmd("NOOP", false)
406 | return err
407 | }
408 |
409 | // Quit sends the QUIT command to server and gracefully closes the connection.
410 | // Message deletions (DELE command) are only excuted by the server on a graceful
411 | // quit and close.
412 | func (c *Conn) Quit() error {
413 | if _, err := c.Cmd("QUIT", false); err != nil {
414 | return err
415 | }
416 | return c.conn.Close()
417 | }
418 |
419 | // parseResp checks if the response is an error that starts with `-ERR`
420 | // and returns an error with the message that succeeds the error indicator.
421 | // For success `+OK` messages, it returns the remaining response bytes.
422 | func parseResp(b []byte) ([]byte, error) {
423 | if len(b) == 0 {
424 | return nil, nil
425 | }
426 |
427 | if bytes.Equal(b, respOK) {
428 | return nil, nil
429 | } else if bytes.HasPrefix(b, respOKInfo) {
430 | return bytes.TrimPrefix(b, respOKInfo), nil
431 | } else if bytes.Equal(b, respErr) {
432 | return nil, errors.New("unknown error (no info specified in response)")
433 | } else if bytes.HasPrefix(b, respErrInfo) {
434 | return nil, errors.New(string(bytes.TrimPrefix(b, respErrInfo)))
435 | } else {
436 | return nil, fmt.Errorf("unknown response: %s. Neither -ERR, nor +OK", string(b))
437 | }
438 | }
439 |
--------------------------------------------------------------------------------