├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── example └── main.go ├── go.mod └── pop3 ├── auth.go ├── auth_test.go ├── transaction.go └── transaction_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.17 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -v ./pop3 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Gökhan Özeloğlu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gop-3 [![GoDoc](https://godoc.org/github.com/gozeloglu/gop-3?status.svg)](https://godoc.org/github.com/gozeloglu/gop-3) [![Go Report Card](https://goreportcard.com/badge/github.com/gozeloglu/gop-3)](https://goreportcard.com/report/github.com/gozeloglu/gop-3) [![Release](https://img.shields.io/badge/Release-v0.2.0-blue)](https://github.com/gozeloglu/gop-3/releases/tag/v0.2.0) ![GitHub go.mod Go version (subdirectory of monorepo)](https://img.shields.io/github/go-mod/go-version/gozeloglu/gop-3?filename=go.mod) [![RFC 1939](https://img.shields.io/badge/Official%20Doc-RFC%201939-yellowgreen)](https://www.ietf.org/rfc/rfc1939.txt) ![LICENSE](https://img.shields.io/badge/license-MIT-green) 2 | 3 | ### Post Office Protocol - Version 3 (POP3) Go Client 4 | 5 | GOP-3 (Go + POP-3) is a POP-3 client for Go. It has experimental purpose and it is still under 6 | development. [RFC 1939](https://www.ietf.org/rfc/rfc1939.txt) document has been followed while developing package. 7 | 8 | #### Features - Commands 9 | 10 | * USER 11 | * PASS 12 | * STAT 13 | * LIST 14 | * DELE 15 | * RETR 16 | * NOOP 17 | * RSET 18 | * QUIT 19 | * TOP 20 | 21 | ### Installation 22 | 23 | You can download with the following command. 24 | 25 | ```shell 26 | go get github.com/gozeloglu/gop-3 27 | ``` 28 | 29 | ## Example 30 | 31 | ```go 32 | package main 33 | 34 | import ( 35 | "fmt" 36 | "github.com/gozeloglu/gop-3" 37 | "log" 38 | "os" 39 | ) 40 | 41 | func main() { 42 | pop, err := pop3.Connect("mail.pop3.com:110", nil, false) 43 | if err != nil { 44 | log.Fatalf(err.Error()) 45 | } 46 | 47 | fmt.Println(pop.GreetingMsg()) // Message starts with "+OK" 48 | fmt.Println(pop.IsAuthorized()) // true 49 | fmt.Println(pop.IsEncrypted()) // false 50 | 51 | // USER command 52 | username := os.Getenv("POP3_USER") // Read from env 53 | u, err := pop.User(username) 54 | if err != nil { 55 | log.Fatalf(err.Error()) 56 | } 57 | fmt.Println(u) 58 | 59 | // PASS command 60 | password := os.Getenv("POP3_PASSWORD") // Read from env 61 | pass, err := pop.Pass(password) 62 | if err != nil { 63 | log.Fatalf(err.Error()) 64 | } 65 | fmt.Println(pass) 66 | 67 | // STAT command 68 | stat, err := pop.Stat() 69 | if err != nil { 70 | log.Fatalf(err.Error()) 71 | } 72 | fmt.Println(stat) 73 | 74 | // LIST command 75 | l, _ := pop.List() 76 | if len(l) == 0 { 77 | fmt.Println(l) 78 | } 79 | 80 | // LIST command 81 | ll, _ := pop.List(1) 82 | fmt.Println(ll[0]) 83 | 84 | // TOP msgNum n 85 | top, _ := pop.Top(1, 10) 86 | fmt.Println(top) 87 | 88 | // DELE command 89 | dele, err := pop.Dele("1") 90 | if err != nil { 91 | log.Fatalf(err.Error()) 92 | } 93 | fmt.Println(dele) 94 | 95 | // RETR command 96 | retr, err := pop.Retr("1") 97 | if err != nil { 98 | log.Fatalf(err.Error()) 99 | } 100 | for _, m := range retr { 101 | fmt.Println(m) 102 | } 103 | 104 | // NOOP command 105 | noop, err := pop.Noop() 106 | if err != nil { 107 | log.Fatalf(err.Error()) 108 | } 109 | fmt.Println(noop) 110 | 111 | // RSET command 112 | rset, err := pop.Rset() 113 | if err != nil { 114 | log.Fatalf(err.Error()) 115 | } 116 | fmt.Println(rset) 117 | 118 | // QUIT state 119 | q, err := pop.Quit() 120 | if err != nil { 121 | log.Fatalf(err.Error()) 122 | } 123 | fmt.Println(q) // Prints: "QUIT" 124 | } 125 | ``` 126 | 127 | ### Run & Test 128 | 129 | **Note:** If you run the tests, you firstly need to have a GMail account that enables POP3 connections. Also, you have to 130 | save mail address and password in your local environment. 131 | 132 | If you make changes, make sure that all tests are passed. You can run the tests with the following command. 133 | 134 | ```shell 135 | go test pop3/* -v 136 | ``` 137 | 138 | If you want to run only one test, you can type the following command. 139 | 140 | ```shell 141 | go test pop3/* -v -run 142 | ``` 143 | 144 | Example: 145 | 146 | ```shell 147 | go test pop3/* -v -run TestStat 148 | ``` 149 | 150 | ### References 151 | 152 | * [RFC 1939 POP3](https://www.ietf.org/rfc/rfc1939.txt) 153 | 154 | ### LICENSE 155 | 156 | [MIT](https://github.com/gozeloglu/gop-3/blob/main/LICENSE) -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gozeloglu/gop-3/pop3" 6 | "log" 7 | "os" 8 | ) 9 | 10 | const ( 11 | userKey = "POP3_USER" 12 | passwordKey = "POP3_PASSWORD" 13 | ) 14 | 15 | func main() { 16 | pop, err := pop3.Connect("pop.gmail.com:995", nil, true) 17 | if err != nil { 18 | log.Fatalf(err.Error()) 19 | } 20 | fmt.Print(pop.GreetingMsg()) // Message starts with "+OK" 21 | fmt.Println(pop.IsAuthorized()) // true 22 | fmt.Println(pop.IsEncrypted()) // true 23 | 24 | username := os.Getenv(userKey) 25 | u, err := pop.User(username) // USER command 26 | if err != nil { 27 | log.Fatalf(err.Error()) 28 | } 29 | fmt.Println(u) // Starts with "+OK" 30 | 31 | password := os.Getenv(passwordKey) 32 | p, err := pop.Pass(password) // PASS command 33 | if err != nil { 34 | log.Fatalf(err.Error()) 35 | } 36 | fmt.Println(p) // Starts with "+OK" 37 | 38 | stat, _ := pop.Stat() // STAT command 39 | fmt.Println(stat) // Starts with "+OK". Returns total msg number and size. 40 | 41 | list, _ := pop.List() // LIST command 42 | fmt.Println(list) // Array of message number and size 43 | 44 | list, _ = pop.List(1) // LIST command 45 | fmt.Println(list) // 1st message size 46 | 47 | top, _ := pop.Top(1, 10) // TOP msgNum n 48 | fmt.Println(top) // Headers, blank line, and top 10 line of the message body in an array 49 | 50 | n, _ := pop.Noop() // NOOP command 51 | fmt.Println(n) // +OK 52 | 53 | r, _ := pop.Retr("1") // RETR 1 command 54 | fmt.Println(r) // array of lines. Starts with "+OK" if there is mail 55 | 56 | d, _ := pop.Dele("1") // DELE 1 command 57 | fmt.Println(d) // response message starts with "+OK" if successful 58 | 59 | rs, _ := pop.Rset() //RSET command 60 | fmt.Println(rs) // response message starts with "+OK" if successful 61 | 62 | q, _ := pop.Quit() // QUIT command 63 | fmt.Println(q) // response message starts with "+OK" if successful 64 | } 65 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gozeloglu/gop-3 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /pop3/auth.go: -------------------------------------------------------------------------------- 1 | package pop3 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net" 7 | "strings" 8 | ) 9 | 10 | // Client is POP3 client. Keeps the net.Conn, Addr of the POP3 11 | // server's address, GreetingMsg of the POP3 server when connection 12 | // established, and IsAuthorized boolean value when AUTHORIZATION 13 | // state completed. 14 | type Client struct { 15 | // Conn is connection for POP3 clients. 16 | Conn net.Conn 17 | 18 | // Addr is POP3 address. 19 | Addr string 20 | 21 | // greetingMsg keeps server response in AUTHORIZATION state. 22 | greetingMsg string 23 | 24 | // isAuthorized keeps status of AUTHORIZATION state. 25 | isAuthorized bool 26 | 27 | // isEncrypted stands for whether mail server encrypted with TLS. 28 | isEncrypted bool 29 | } 30 | 31 | const ( 32 | // ok is successful server response's prefix 33 | ok = "+OK" 34 | 35 | // e is unsuccessful server response's prefix 36 | e = "-ERR" 37 | ) 38 | 39 | // Connect create and make a connection with POP3 40 | // server. Takes only address of the POP3 server and 41 | // returns Client and error. 42 | // 43 | // addr string - POP3 mail server address. It contains 44 | // host and port number. 45 | // POP3 default port: 110 46 | // POP3 (with TLS) default port: 995 47 | // tlsConf *tls.Config - TLS configuration for POP3 servers. 48 | // You can pass if there is no configuration. 49 | // isEncryptedTLS bool - Indicates that POP3 server whether 50 | // is encrypted. You can pass false the server is not 51 | // encrypted. 52 | func Connect(addr string, tlsConf *tls.Config, isEncryptedTLS bool) (Client, error) { 53 | if isEncryptedTLS { 54 | return connectPOP3TLS(addr, tlsConf) 55 | } 56 | return connectPOP3(addr) 57 | } 58 | 59 | // connectPOP3TLS connects to given address and returns 60 | // a POP3 (encrypted with TLS) Client. This function is specialized 61 | // for TLS encrypted POP3 servers (995 port). 62 | // 63 | // addr string - POP3 server address. 64 | // config *tls.Config - TLS configuration for POP3 server. 65 | func connectPOP3TLS(addr string, config *tls.Config) (Client, error) { 66 | c := &Client{ 67 | isEncrypted: true, 68 | } 69 | 70 | tlsConn, err := tls.Dial("tcp", addr, config) 71 | if err != nil { 72 | return *c, err 73 | } 74 | c.Conn = tlsConn 75 | c.Addr = addr 76 | 77 | err = c.readGreetingMsg() 78 | if err != nil { 79 | return Client{}, err 80 | } 81 | return *c, nil 82 | } 83 | 84 | // connectPOP3 connects to given address and returns 85 | // a POP3 Client. This function is implementation of 86 | // Connect() function. Reads server's response sending 87 | // after connecting POP3 server. 88 | func connectPOP3(addr string) (Client, error) { 89 | c := &Client{} 90 | 91 | conn, err := net.Dial("tcp", addr) 92 | if err != nil { 93 | return *c, err 94 | } 95 | c.Conn = conn 96 | c.Addr = addr 97 | 98 | err = c.readGreetingMsg() 99 | if err != nil { 100 | return Client{}, err 101 | } 102 | 103 | return *c, nil 104 | } 105 | 106 | // readGreetingMsg reads the server response 107 | // in AUTHORIZATION step. It starts with "+OK" 108 | // string if it is successful. Response message 109 | // keeps in Client. 110 | // Returns error if reading or response message 111 | // fails. 112 | func (c *Client) readGreetingMsg() error { 113 | // buffer for reading server's response 114 | // message in AUTHORIZATION state. 115 | // Response message length may be up to 116 | // 512 characters. 117 | var buf [512]byte 118 | 119 | r, err := c.Conn.Read(buf[:]) 120 | if err != nil { 121 | return err 122 | } 123 | resp := string(buf[:r]) 124 | 125 | // If AUTHORIZATION state fails wrt greeting 126 | // message, returns an error. 127 | if !c.isAuth(resp) { 128 | e := "not authorized to POP3 server" 129 | return fmt.Errorf(e) 130 | } 131 | c.greetingMsg = resp 132 | c.isAuthorized = true 133 | 134 | return nil 135 | } 136 | 137 | // isAuth checks the greeting messages which comes 138 | // from the server after connecting to given address. 139 | // If the greeting message starts with "+OK" string, 140 | // we can make sure that, the server is POP3 server. 141 | // It returns bool value. 142 | // 143 | // greeting string - greeting message comes from the 144 | // server. 145 | func (c *Client) isAuth(greeting string) bool { 146 | return strings.HasPrefix(greeting, ok) 147 | } 148 | 149 | // Quit closes the POP3 connection with POP3 150 | // server. It just sends "QUIT" command and get 151 | // response from the server. The Quit function 152 | // returns server response and error. Server 153 | // response may start with "+OK" or "-ERR". 154 | func (c *Client) Quit() (string, error) { 155 | return c.quit() 156 | } 157 | 158 | // quit is implementation of the Quit() 159 | // function. Sends "QUIT\r\n" command to POP3 160 | // server. Closes Conn if server response 161 | // contains "+OK". 162 | func (c *Client) quit() (string, error) { 163 | err := c.sendQuitCmd() 164 | if err != nil { 165 | return "", err 166 | } 167 | 168 | qResp, err := c.readQuitResp() 169 | if err != nil { 170 | return "", err 171 | } 172 | 173 | if isQuit(qResp) { 174 | c.Conn.Close() 175 | c.changeClientState() 176 | } 177 | 178 | return qResp, nil 179 | } 180 | 181 | // isQuit checks the server response after 182 | // QUIT command. If it starts with "+OK" 183 | // string, it is closed successfully. It 184 | // returns boolean. 185 | // 186 | // resp string - response message retrieved 187 | // after QUIT command. 188 | func isQuit(resp string) bool { 189 | return strings.HasPrefix(resp, ok) 190 | } 191 | 192 | // sendQuitCmd sends the QUIT command to server 193 | // as a client. The command ends with CRLF(\r\n). 194 | // It indicates that command is terminated. 195 | // The function returns error if occurs while 196 | // sending command. 197 | func (c Client) sendQuitCmd() error { 198 | buf := []byte("QUIT\r\n") 199 | _, err := c.Conn.Write(buf) 200 | return err 201 | } 202 | 203 | // readQuitResp reads the response message that comes 204 | // from the server after sending QUIT command. If 205 | // QUIT is done successfully, the server sends a response 206 | // which starts with "+OK". Returns the response msg and 207 | // error if occurs. 208 | func (c *Client) readQuitResp() (string, error) { 209 | var buf [512]byte 210 | r, err := c.Conn.Read(buf[:]) 211 | if err != nil { 212 | return "", err 213 | } 214 | resp := string(buf[:r]) 215 | return resp, nil 216 | } 217 | 218 | // changeClientState changes the client's state 219 | // after Quit command. If the Quit command is 220 | // successful, Conn, Addr, GreetingMsg, IsAuthorized 221 | // variables changed to nil/empty strings. 222 | func (c *Client) changeClientState() { 223 | c.Conn = nil 224 | c.Addr = "" 225 | c.greetingMsg = "" 226 | c.isAuthorized = false 227 | } 228 | 229 | // GreetingMsg returns the greeting message which 230 | // server response when connected to mail server. 231 | // The message is returned in AUTHORIZATION state. 232 | func (c *Client) GreetingMsg() string { 233 | return c.greetingMsg 234 | } 235 | 236 | // IsAuthorized returns the information that 237 | // keeps the status of AUTHORIZATION state. 238 | func (c *Client) IsAuthorized() bool { 239 | return c.isAuthorized 240 | } 241 | 242 | // IsEncrypted returns the information whether 243 | // the server is encrypted with TLS. 244 | func (c *Client) IsEncrypted() bool { 245 | return c.isEncrypted 246 | } 247 | -------------------------------------------------------------------------------- /pop3/auth_test.go: -------------------------------------------------------------------------------- 1 | package pop3 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | var c = Client{} 9 | 10 | func TestIsAuth(t *testing.T) { 11 | resp := "+OK Hello POP3 Server" 12 | auth := c.isAuth(resp) 13 | 14 | if !auth { 15 | t.Errorf("Expected: %v, got: %v.", true, auth) 16 | } 17 | } 18 | 19 | func TestIsAuthFalse(t *testing.T) { 20 | resp := "-ERR Some problem" 21 | auth := c.isAuth(resp) 22 | 23 | if auth { 24 | t.Errorf("Expected: %v, got: %v.", false, auth) 25 | } 26 | } 27 | 28 | func TestConnect(t *testing.T) { 29 | addr := "mail.btopenworld.com:110" 30 | pop, err := Connect(addr, nil, false) 31 | 32 | if pop.Conn == nil { 33 | t.Errorf("c.Conn is nil.") 34 | } 35 | if err != nil { 36 | t.Errorf(err.Error()) 37 | } 38 | if !pop.IsAuthorized() { 39 | t.Errorf("Expected: %v, got: %v", true, pop.isAuthorized) 40 | } 41 | if pop.Addr != addr { 42 | t.Errorf("Expected: %s, got: %s", addr, pop.Addr) 43 | } 44 | } 45 | 46 | func TestConnectTLS(t *testing.T) { 47 | addr := "mail.btopenworld.com:995" 48 | pop, err := Connect(addr, nil, true) 49 | 50 | if pop.Conn == nil { 51 | t.Errorf("c.Conn is nil.") 52 | } 53 | if err != nil { 54 | t.Errorf(err.Error()) 55 | } 56 | if !pop.IsAuthorized() { 57 | t.Errorf("Expected: %v, got: %v", true, pop.IsAuthorized()) 58 | } 59 | if pop.Addr != addr { 60 | t.Errorf("Expected: %s, got: %s", addr, pop.Addr) 61 | } 62 | } 63 | 64 | func TestClient_Quit(t *testing.T) { 65 | addr := "mail.btopenworld.com:110" 66 | pop, err := Connect(addr, nil, false) 67 | 68 | if pop.Conn == nil { 69 | t.Errorf("c.Conn is nil.") 70 | } 71 | if err != nil { 72 | t.Errorf(err.Error()) 73 | } 74 | 75 | got, err := pop.Quit() 76 | if err != nil { 77 | t.Errorf(err.Error()) 78 | } 79 | if pop.IsAuthorized() != false { 80 | t.Errorf("Expected c.IsAuthorized %v, got: %v", false, pop.IsAuthorized()) 81 | } 82 | if !strings.Contains(got, ok) { 83 | t.Errorf("expected %s, got %s", ok, got) 84 | } 85 | } 86 | 87 | func TestClientTLS_Quit(t *testing.T) { 88 | addr := "mail.btopenworld.com:995" 89 | popTLS, err := Connect(addr, nil, true) 90 | 91 | if popTLS.Conn == nil { 92 | t.Errorf(err.Error()) 93 | } 94 | 95 | got, err := popTLS.Quit() 96 | if err != nil { 97 | t.Errorf(err.Error()) 98 | } 99 | if popTLS.IsAuthorized() != false { 100 | t.Errorf("expected popTLS.IsAuthorized: %v, got: %v", false, popTLS.IsAuthorized()) 101 | } 102 | if !strings.Contains(got, ok) { 103 | t.Errorf("expected: %s, got: %s", ok, got) 104 | } 105 | } 106 | 107 | func TestClient_IsEncrypted(t *testing.T) { 108 | pop, err := Connect("pop.gmail.com:995", nil, true) 109 | if err != nil { 110 | t.Errorf(err.Error()) 111 | } 112 | if !pop.IsEncrypted() { 113 | t.Errorf("expected: %v, got: %v", true, pop.IsEncrypted()) 114 | } 115 | } 116 | 117 | func TestClient_IsNotEncrypted(t *testing.T) { 118 | pop, err := Connect("mail.pop3.com:110", nil, false) 119 | if err != nil { 120 | t.Errorf(err.Error()) 121 | } 122 | if pop.IsEncrypted() { 123 | t.Errorf("expected: %v, got: %v", false, pop.IsEncrypted()) 124 | } 125 | } 126 | 127 | func TestClient_IsAuthorized(t *testing.T) { 128 | pop, err := Connect("mail.pop3.com:110", nil, false) 129 | if err != nil { 130 | t.Errorf(err.Error()) 131 | } 132 | if !pop.IsAuthorized() { 133 | t.Errorf("expected: %v, got: %v", true, pop.IsAuthorized()) 134 | } 135 | } 136 | 137 | func TestClient_GreetingMsg(t *testing.T) { 138 | pop, err := Connect("mail.pop3.com:110", nil, false) 139 | if err != nil { 140 | t.Errorf(err.Error()) 141 | } 142 | if !strings.HasPrefix(pop.GreetingMsg(), ok) { 143 | t.Errorf("expected: %s, got: %s", ok, pop.GreetingMsg()) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /pop3/transaction.go: -------------------------------------------------------------------------------- 1 | package pop3 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // sendCmd is the function that send command 10 | // without any argument. It ends with CRLF 11 | // (\r\n). It returns if something goes wrong 12 | // while sending cmd. 13 | func (c *Client) sendCmd(cmd string) error { 14 | buf := []byte(cmd + "\r\n") 15 | _, err := c.Conn.Write(buf) 16 | if err != nil { 17 | return err 18 | } 19 | return nil 20 | } 21 | 22 | // sendCmdWithArg function sends the POP3 command with 23 | // argument. It returns error if sending command 24 | // will be unsuccessful. 25 | // 26 | // cmd string - command that send will send 27 | // arg string - argument which command takes 28 | func (c Client) sendCmdWithArg(cmd string, arg string) error { 29 | buf := []byte(cmd + " " + arg + "\r\n") 30 | _, err := c.Conn.Write(buf[:]) 31 | if err != nil { 32 | return err 33 | } 34 | return nil 35 | } 36 | 37 | // readResp reads the command's response. 38 | // It allocates a byte array with size of 512 byte. 39 | // Read and store the response into buf array. 40 | // Finally, the byte array converts to string and 41 | // return. 42 | func (c *Client) readResp() (string, error) { 43 | var buf [512]byte 44 | r, err := c.Conn.Read(buf[:]) 45 | if err != nil { 46 | return "", err 47 | } 48 | 49 | resp := string(buf[:r]) 50 | return resp, nil 51 | } 52 | 53 | // readRespMultiLines reads the response that has multiple 54 | // lines until reaching ".\r\n" character set. Each 55 | // line is added to listResp array. 56 | func (c Client) readRespMultiLines() ([]string, error) { 57 | var buf [512]byte 58 | var listResp []string 59 | 60 | r, err := c.Conn.Read(buf[:]) 61 | if err != nil { 62 | return nil, err 63 | } 64 | resp := string(buf[:r]) 65 | listResp = append(listResp, strings.Split(resp, "\r\n")...) 66 | 67 | return listResp, nil 68 | } 69 | 70 | // Stat is a TRANSACTION state command. It 71 | // shows that how many mails are in the inbox 72 | // and size of the maildrop in octets. Stat 73 | // takes no parameters. The response string 74 | // starts with "+OK" and continues with the 75 | // number of messages and size of the maildrop. 76 | // separated by space. 77 | // Response type: 78 | // +OK xx yy 79 | // Example: 80 | // +OK 2 320 81 | func (c *Client) Stat() (string, error) { 82 | return c.stat() 83 | } 84 | 85 | // stat is implementation of the Stat function. 86 | // Sends command and receives response. Returns 87 | // string and error. string is the response of 88 | // the command and error is the unexpected 89 | // situations. 90 | func (c *Client) stat() (string, error) { 91 | err := c.sendCmd("STAT") 92 | if err != nil { 93 | return "", err 94 | } 95 | 96 | resp, err := c.readResp() 97 | if err != nil { 98 | return "", err 99 | } 100 | 101 | return resp, nil 102 | } 103 | 104 | // List returns the mail information. It can take argument 105 | // optionally. There might be 2 different usage. 106 | // Example-1: 107 | // C: LIST 108 | // S: +OK 2 messages (360 octets) 109 | // S: 1 160 110 | // S: 2 200 111 | // S: . 112 | // 113 | // Example-2: 114 | // C: LIST 1 115 | // S: +OK 2 160 116 | // C: LIST 2 117 | // S: +OK 2 200 118 | // C: LIST 3 119 | // S: -ERR no such message, only 2 messages in maildrop 120 | // 121 | // You do not need to pass any argument. The function 122 | // takes variadic parameter. 123 | // 124 | // msgNum ...int - variadic parameter. It indicates mail 125 | // number that we get. 126 | func (c *Client) List(mainNum ...int) ([]string, error) { 127 | // TODO Check the client is whether in TRANSACTION state 128 | return c.list(mainNum) 129 | } 130 | 131 | // list is the implementation of the List function. 132 | // It sends the LIST command and reads the response 133 | // that coming from the server. Returns the response 134 | // list and error. 135 | // 136 | // mailNum []int - mail numbers. 137 | func (c *Client) list(mailNum []int) ([]string, error) { 138 | var err error 139 | var msg string 140 | var msgList []string 141 | 142 | if len(mailNum) > 0 { 143 | err = c.sendCmdWithArg("LIST", strconv.Itoa(mailNum[0])) 144 | } else { 145 | err = c.sendCmd("LIST") 146 | } 147 | if err != nil { 148 | return msgList, err 149 | } 150 | 151 | if len(mailNum) == 0 { 152 | msgList, err = c.readRespMultiLines() 153 | } else { 154 | msg, err = c.readResp() 155 | msgList = append(msgList, msg) 156 | } 157 | return msgList, err 158 | } 159 | 160 | // Retr retrieves the mails from the inbox. It indicates 161 | // RETR command in POP-3 protocol. It takes mailNum which 162 | // stands for mail number. In return phase, if the mail 163 | // is retrieved successfully, there are multiple lines. 164 | // The first line starts with "+OK" and follows with total 165 | // message size. On the following line, the POP3 server 166 | // sends the entire message. Finally, command response 167 | // ends with ".\r\n". If retrieving mail fails, the server 168 | // returns "-ERR". The line starts with "-ERR". The function 169 | // returns string array and error. Error is returned for 170 | // unexpected situations like sending command or reading 171 | // response fails. The string array contains the multiple 172 | // line responses. 173 | // 174 | // mailNum string - mail-number. 175 | func (c *Client) Retr(mailNum string) ([]string, error) { 176 | return c.retr(mailNum) 177 | } 178 | 179 | // retr function is implementation of the Retr function. 180 | // It takes the mailNum. Firstly, sends RETR command and 181 | // reads the response which comes from the server. 182 | // 183 | // mailNum string - mail-number. 184 | func (c *Client) retr(mailNum string) ([]string, error) { 185 | // Send the RETR command 186 | err := c.sendCmdWithArg("RETR", mailNum) 187 | if err != nil { 188 | return nil, err 189 | } 190 | 191 | // Read the response 192 | retrResp, err := c.readRespMultiLines() 193 | if err != nil { 194 | return nil, err 195 | } 196 | return retrResp, nil 197 | } 198 | 199 | // Dele function deletes mail that is given as parameter. 200 | // DELE command takes mail number and returns 2 possible 201 | // message which are starts with "+OK" or "-ERR". POP3 202 | // server does not actually delete the mail until POP3 203 | // session enters the UPDATE state. 204 | // 205 | // mailNum string - mail number that will be deleted. 206 | func (c *Client) Dele(mailNum string) (string, error) { 207 | return c.dele(mailNum) 208 | } 209 | 210 | // dele function is the implementation of the Dele function. 211 | // It sends the command and reads the server response. 212 | // Response is a string. Error is returned if unexpected 213 | // situations happen like unsuccessful command send or 214 | // response read. 215 | // 216 | // mailNum string - mail number that will be deleted. 217 | func (c *Client) dele(mailNum string) (string, error) { 218 | // Send the DELE command. 219 | cmd := "DELE" 220 | err := c.sendCmdWithArg(cmd, mailNum) 221 | if err != nil { 222 | return "", err 223 | } 224 | 225 | // Read the DELE command's response. 226 | deleResp, err := c.readResp() 227 | if err != nil { 228 | return "", err 229 | } 230 | return deleResp, nil 231 | } 232 | 233 | // Noop is a command which does nothing. The POP3 234 | // server replies with a positive response. 235 | // Example: 236 | // C: NOOP 237 | // S: +OK 238 | // It takes no argument. 239 | func (c *Client) Noop() (string, error) { 240 | return c.noop() 241 | } 242 | 243 | // noop is implementation of the Noop function. 244 | func (c *Client) noop() (string, error) { 245 | err := c.sendCmd("NOOP") 246 | if err != nil { 247 | return "", err 248 | } 249 | noop, err := c.readResp() 250 | if err != nil { 251 | return "", err 252 | } 253 | return noop, nil 254 | } 255 | 256 | // Rset is a command which unmark if any message 257 | // is marked as deleted by the POP3 server. It takes 258 | // no argument. The POP3 server replies with positive 259 | // message as follows: 260 | // C: RSET 261 | // S: +OK maildrop has 2 messages. 262 | func (c *Client) Rset() (string, error) { 263 | return c.rset() 264 | } 265 | 266 | // rset is the implementation of the Rset function. 267 | // It sends command and reads response from server. 268 | // Returns string and error. String contains the 269 | // message comes from the server and error is 270 | // returned if something goes wrong while sending 271 | // command or reading response. 272 | func (c Client) rset() (string, error) { 273 | err := c.sendCmd("RSET") 274 | if err != nil { 275 | return "", err 276 | } 277 | 278 | resp, err := c.readResp() 279 | if err != nil { 280 | return "", err 281 | } 282 | 283 | return resp, nil 284 | } 285 | 286 | // User is the function that authenticates the user. 287 | // It takes username as a parameter and returns server 288 | // response and error if something goes wrong. Firstly, 289 | // the server checks the username and returns response 290 | // message to client. If the message starts with "+OK", 291 | // it means that the username is valid. If authentication 292 | // fails, the server may respond with negative status 293 | // indicator ("-ERR"). In such case, you may either 294 | // issue a new authentication command or may issue the 295 | // QUIT command. The server may return a positive response 296 | // even though no such mailbox exits. Also, the server 297 | // may return a negative status indicator even if the 298 | // username is exists because the mail server does not 299 | // permit plaintext password authentication. 300 | // Example: 301 | // C: USER testUser 302 | // S: -ERR no such mailbox 303 | // ... 304 | // C: USER validUser 305 | // S: +OK send PASS 306 | // 307 | // name string - username of the mailbox 308 | func (c *Client) User(name string) (string, error) { 309 | return c.user(name) 310 | } 311 | 312 | // user is the implementation of the User function. 313 | // It firstly sends the USER command and read response 314 | // comes from the server. It returns string and error. 315 | // String represents server's response message and 316 | // error returns when unexpected situations if something 317 | // goes wrong like reading response is failed. 318 | // 319 | // name string - username 320 | func (c *Client) user(name string) (string, error) { 321 | // Send USER command 322 | cmd := "USER" 323 | err := c.sendCmdWithArg(cmd, name) 324 | if err != nil { 325 | return "", err 326 | } 327 | 328 | // Read server response 329 | userResp, err := c.readResp() 330 | if err != nil { 331 | return "", nil 332 | } 333 | 334 | return userResp, nil 335 | } 336 | 337 | // Pass is the function that sends password to POP3 338 | // server. This function should be called after User 339 | // function (USER command). The function takes password 340 | // as a string and returns (string, error) pair. 341 | // Password is a plaintext string, and it should be 342 | // read from environment variable. Of course, you 343 | // can declare in code and pass it to function, but 344 | // it is not secure for privacy and security. In 345 | // return, string contains the server response regarding 346 | // result of the PASS command. Server response can 347 | // start with "+OK" and "-ERR". If the password is 348 | // true and authentication step is passed successfully, 349 | // the server returns a message starts with "+OK". If 350 | // the password is not true or the mail server does 351 | // not authenticate with some reasons such as security 352 | // level, the server returns a message contains 353 | // negative status indicator ("-ERR"). Remaining 354 | // message parts can be customized by the mail server. 355 | // It depends on the mail server. For example, GMail 356 | // returns "+OK Welcome" message. 357 | // Example: 358 | // C: USER username 359 | // S: +OK send PASS 360 | // C: PASS wrongPassword 361 | // S: -ERR Username and password not accepted. 362 | // ... 363 | // C: USER username 364 | // S: +OK send PASS 365 | // C: PASS rightPassword 366 | // S: +OK Welcome 367 | // 368 | // Note: Be sure that your mail server accepts less 369 | // secure apps. If not, give permission for less secure 370 | // apps. 371 | func (c *Client) Pass(password string) (string, error) { 372 | return c.pass(password) 373 | } 374 | 375 | // pass is implementation of the Pass function. It takes 376 | // password as an argument and sends the PASS command. 377 | // Then, reads the server's response. Password is a 378 | // plaintext string. The function returns string and 379 | // error. String contains the server message which either 380 | // starts with "+OK" or "-ERR". If something goes wrong 381 | // while sending command or reading response steps, the 382 | // error returns. 383 | func (c *Client) pass(password string) (string, error) { 384 | // Send PASS command 385 | cmd := "PASS" 386 | err := c.sendCmdWithArg(cmd, password) 387 | if err != nil { 388 | return "", err 389 | } 390 | 391 | // Read response message 392 | passResp, err := c.readResp() 393 | if err != nil { 394 | return "", err 395 | } 396 | 397 | return passResp, nil 398 | } 399 | 400 | // Top is a command which fetches message (msgNum) with n lines. To get messages 401 | // from mail server, you need to authenticate. The response starts with "+OK" 402 | // status indicator, and it follows the multiline mail. The server sends header 403 | // of the message, the blank line separating the headers from the body, and then 404 | // the number of lines of the message's body. If you request the message line 405 | // number greater than the number of lines in the body, the mail server sends 406 | // the entire message. 407 | // Possible responses: 408 | // +OK message follows 409 | // -ERR no such message 410 | // Example: 411 | // C: TOP 1 10 412 | // S: +OK message follows 413 | // S: 414 | // S: . 415 | // ... 416 | // C: TOP 100 10 417 | // S: -ERR no such message 418 | // 419 | // msgNum indicates message id starts from 1 and n is line of the message's body. 420 | func (c *Client) Top(msgNum, n int) ([]string, error) { 421 | return c.top(msgNum, n) 422 | } 423 | 424 | // top is the implementation function of the Top function. 425 | func (c *Client) top(msgNum, n int) ([]string, error) { 426 | if msgNum < 1 { 427 | return nil, fmt.Errorf("%s message number should be greater than 0", e) 428 | } 429 | if n < 0 { 430 | return nil, fmt.Errorf("%s line count cannot be negative", e) 431 | } 432 | cmd := "TOP" 433 | arg := fmt.Sprintf("%d %d", msgNum, n) 434 | err := c.sendCmdWithArg(cmd, arg) 435 | if err != nil { 436 | return nil, err 437 | } 438 | 439 | return c.readRespMultiLines() 440 | } 441 | -------------------------------------------------------------------------------- /pop3/transaction_test.go: -------------------------------------------------------------------------------- 1 | package pop3 2 | 3 | import ( 4 | "log" 5 | "math" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | const ( 13 | btAddr = "mail.btopenworld.com:110" 14 | btTLSAddr = "mail.btopenworld.com:995" 15 | gmailTLSAddr = "pop.gmail.com:995" 16 | userKey = "POP3_USER" 17 | passwordKey = "POP3_PASSWORD" 18 | ) 19 | 20 | func TestUserCmd(t *testing.T) { 21 | pop, err := Connect(btAddr, nil, false) 22 | if err != nil { 23 | t.Errorf(err.Error()) 24 | } 25 | 26 | u, err := pop.User("testUser") 27 | if err != nil { 28 | t.Errorf(err.Error()) 29 | } 30 | 31 | if !strings.HasPrefix(u, ok) { 32 | t.Errorf("expected: %s, got: %s", ok, u) 33 | } 34 | } 35 | 36 | func TestUserCmdWithTLS(t *testing.T) { 37 | pop, err := Connect(btTLSAddr, nil, true) 38 | if err != nil { 39 | t.Errorf(err.Error()) 40 | } 41 | 42 | u, err := pop.User("testUser") 43 | if err != nil { 44 | t.Errorf(err.Error()) 45 | } 46 | 47 | if !strings.HasPrefix(u, ok) { 48 | t.Errorf("expected: %s, got: %s", ok, u) 49 | } 50 | } 51 | 52 | func TestUserGMail(t *testing.T) { 53 | pop, err := Connect(gmailTLSAddr, nil, true) 54 | if err != nil { 55 | t.Errorf(err.Error()) 56 | } 57 | 58 | u, err := pop.User("testUser") 59 | if err != nil { 60 | t.Errorf(err.Error()) 61 | } 62 | 63 | if !strings.HasPrefix(u, ok) { 64 | t.Errorf("expected: %s, got: %s", ok, u) 65 | } 66 | } 67 | 68 | // You need to save GMail username and password 69 | // as environment variables. Environment variable 70 | // names should be "POP3_USER" and "POP3_PASSWORD". 71 | // NOTE: If you are working with GMail, you need to 72 | // change security level and give permission to 73 | // less secure apps. You can go to the following 74 | // link and give permission to less secure apps. 75 | // https://myaccount.google.com/lesssecureapps 76 | func TestPassCmd(t *testing.T) { 77 | pop, err := Connect(gmailTLSAddr, nil, true) 78 | if err != nil { 79 | t.Errorf(err.Error()) 80 | } 81 | 82 | // read username from env variable 83 | username := os.Getenv(userKey) 84 | u, err := pop.User(username) 85 | if err != nil { 86 | t.Errorf(err.Error()) 87 | } 88 | 89 | if !strings.HasPrefix(u, ok) { 90 | t.Errorf("expected: %s, got: %s", ok, u) 91 | } 92 | 93 | // read password from env variable 94 | password := os.Getenv(passwordKey) 95 | p, err := pop.Pass(password) 96 | if err != nil { 97 | t.Errorf(err.Error()) 98 | } 99 | 100 | if !strings.HasPrefix(p, ok) { 101 | t.Errorf("expected: %s, got: %s", ok, p) 102 | } 103 | } 104 | 105 | func TestStat(t *testing.T) { 106 | pop, err := Connect(gmailTLSAddr, nil, true) 107 | if err != nil { 108 | t.Errorf(err.Error()) 109 | } 110 | 111 | username := os.Getenv(userKey) 112 | u, err := pop.User(username) 113 | if err != nil { 114 | t.Errorf(err.Error()) 115 | } 116 | 117 | if !strings.HasPrefix(u, ok) { 118 | t.Errorf("expected: %s, got: %s", ok, u) 119 | } 120 | 121 | password := os.Getenv(passwordKey) 122 | p, err := pop.Pass(password) 123 | if err != nil { 124 | t.Errorf(err.Error()) 125 | } 126 | 127 | if !strings.HasPrefix(p, ok) { 128 | t.Errorf("expected: %s, got: %s", ok, p) 129 | } 130 | 131 | s, err := pop.Stat() 132 | if err != nil { 133 | t.Errorf(err.Error()) 134 | } 135 | 136 | if !strings.HasPrefix(s, ok) { 137 | t.Errorf("expected: %s, got: %s", ok, s) 138 | } 139 | } 140 | 141 | func TestStatUnauthorized(t *testing.T) { 142 | pop, err := Connect(gmailTLSAddr, nil, true) 143 | if err != nil { 144 | t.Errorf(err.Error()) 145 | } 146 | 147 | s, err := pop.Stat() 148 | if err != nil { 149 | t.Errorf(err.Error()) 150 | } 151 | 152 | if !strings.HasPrefix(s, e) { 153 | t.Errorf("expected: %s, got: %s", s, e) 154 | } 155 | } 156 | 157 | func TestStatErr(t *testing.T) { 158 | pop, err := Connect(btAddr, nil, false) 159 | if err != nil { 160 | t.Errorf(err.Error()) 161 | } 162 | 163 | s, err := pop.Stat() 164 | if err != nil { 165 | t.Errorf(err.Error()) 166 | } 167 | 168 | if !strings.HasPrefix(s, e) { 169 | t.Errorf("expected: %s, got: %s", e, s) 170 | } 171 | } 172 | 173 | func TestList(t *testing.T) { 174 | pop, err := Connect(gmailTLSAddr, nil, true) 175 | if err != nil { 176 | t.Errorf(err.Error()) 177 | } 178 | 179 | username := os.Getenv(userKey) 180 | u, err := pop.User(username) 181 | if err != nil { 182 | t.Errorf(err.Error()) 183 | } 184 | 185 | if !strings.HasPrefix(u, ok) { 186 | t.Errorf("expected: %s, got: %s", ok, u) 187 | } 188 | 189 | password := os.Getenv(passwordKey) 190 | p, err := pop.Pass(password) 191 | if err != nil { 192 | t.Errorf(err.Error()) 193 | } 194 | 195 | if !strings.HasPrefix(p, ok) { 196 | t.Errorf("expected: %s, got: %s", ok, p) 197 | } 198 | 199 | l, err := pop.List() 200 | if err != nil { 201 | t.Errorf(err.Error()) 202 | } 203 | 204 | if !strings.HasPrefix(l[0], ok) { 205 | t.Errorf("expected: %s, got: %s", ok, l[0]) 206 | } 207 | } 208 | 209 | func TestListUnauthorized(t *testing.T) { 210 | pop, err := Connect(gmailTLSAddr, nil, true) 211 | if err != nil { 212 | t.Errorf(err.Error()) 213 | } 214 | 215 | l, err := pop.List() 216 | if err != nil { 217 | t.Errorf(err.Error()) 218 | } 219 | 220 | if !strings.HasPrefix(l[0], e) { 221 | t.Errorf("expected:%s, got: %s", l, e) 222 | } 223 | } 224 | 225 | func TestListWithArg(t *testing.T) { 226 | pop, err := Connect(gmailTLSAddr, nil, true) 227 | if err != nil { 228 | t.Errorf(err.Error()) 229 | } 230 | 231 | username := os.Getenv(userKey) 232 | u, err := pop.User(username) 233 | if err != nil { 234 | t.Errorf(err.Error()) 235 | } 236 | 237 | if !strings.HasPrefix(u, ok) { 238 | t.Errorf("expected: %s, got: %s", ok, u) 239 | } 240 | 241 | password := os.Getenv(passwordKey) 242 | p, err := pop.Pass(password) 243 | if err != nil { 244 | t.Errorf(err.Error()) 245 | } 246 | 247 | if !strings.HasPrefix(p, ok) { 248 | t.Errorf("expected: %s, got: %s", ok, p) 249 | } 250 | 251 | l, err := pop.List(1) 252 | if err != nil { 253 | t.Errorf(err.Error()) 254 | } 255 | 256 | if !strings.HasPrefix(l[0], ok) { 257 | t.Errorf("expected: %s, got: %s", ok, l[0]) 258 | } 259 | 260 | if len(l) != 1 { 261 | t.Errorf("expected length: %v, l's length: %v", 1, len(l)) 262 | } 263 | } 264 | 265 | func TestListWithArgUnauthorized(t *testing.T) { 266 | pop, err := Connect(gmailTLSAddr, nil, true) 267 | if err != nil { 268 | t.Errorf(err.Error()) 269 | } 270 | 271 | l, err := pop.List(1) 272 | if err != nil { 273 | t.Errorf(err.Error()) 274 | } 275 | 276 | if !strings.HasPrefix(l[0], e) { 277 | t.Errorf("expected: %s, got: %s", e, l[0]) 278 | } 279 | } 280 | 281 | func TestNoop(t *testing.T) { 282 | pop, err := Connect(gmailTLSAddr, nil, true) 283 | if err != nil { 284 | t.Errorf(err.Error()) 285 | } 286 | 287 | n, err := pop.Noop() 288 | if err != nil { 289 | t.Errorf(err.Error()) 290 | } 291 | 292 | if !strings.HasPrefix(n, ok) { 293 | t.Errorf("expected: %s, got: %s", ok, n) 294 | } 295 | } 296 | 297 | func TestRetr(t *testing.T) { 298 | pop, err := Connect(gmailTLSAddr, nil, true) 299 | if err != nil { 300 | t.Errorf(err.Error()) 301 | } 302 | 303 | username := os.Getenv(userKey) 304 | u, err := pop.User(username) 305 | if err != nil { 306 | t.Errorf(err.Error()) 307 | } 308 | 309 | if !strings.HasPrefix(u, ok) { 310 | t.Errorf("expected: %s, got: %s", ok, u) 311 | } 312 | 313 | password := os.Getenv(passwordKey) 314 | p, err := pop.Pass(password) 315 | if err != nil { 316 | t.Errorf(err.Error()) 317 | } 318 | 319 | if !strings.HasPrefix(p, ok) { 320 | t.Errorf("expected: %s, got: %s", ok, p) 321 | } 322 | 323 | l, err := pop.List() 324 | if err != nil { 325 | t.Errorf(err.Error()) 326 | } 327 | 328 | mailNum := strings.Split(l[1], " ")[0] 329 | r, err := pop.Retr(mailNum) 330 | if err != nil { 331 | t.Errorf(err.Error()) 332 | } 333 | 334 | if !strings.HasPrefix(r[0], ok) { 335 | t.Errorf("expected prefix: %s, got %s message", ok, r[0]) 336 | } 337 | } 338 | 339 | func TestRetrFail(t *testing.T) { 340 | pop, err := Connect(gmailTLSAddr, nil, true) 341 | if err != nil { 342 | t.Errorf(err.Error()) 343 | } 344 | 345 | username := os.Getenv(userKey) 346 | u, err := pop.User(username) 347 | if err != nil { 348 | t.Errorf(err.Error()) 349 | } 350 | 351 | if !strings.HasPrefix(u, ok) { 352 | t.Errorf("expected: %s, got: %s", ok, u) 353 | } 354 | 355 | password := os.Getenv(passwordKey) 356 | p, err := pop.Pass(password) 357 | if err != nil { 358 | t.Errorf(err.Error()) 359 | } 360 | 361 | if !strings.HasPrefix(p, ok) { 362 | t.Errorf("expected: %s, got: %s", ok, p) 363 | } 364 | 365 | _, err = pop.List() 366 | if err != nil { 367 | t.Errorf(err.Error()) 368 | } 369 | 370 | r, err := pop.Retr(strconv.Itoa(math.MaxInt64 - 1)) 371 | if err != nil { 372 | t.Errorf(err.Error()) 373 | } 374 | 375 | if !strings.HasPrefix(r[0], e) { 376 | t.Errorf("expected prefix: %s, got %s message", ok, r[0]) 377 | } 378 | } 379 | 380 | func TestDele(t *testing.T) { 381 | pop, err := Connect(gmailTLSAddr, nil, true) 382 | if err != nil { 383 | t.Errorf(err.Error()) 384 | } 385 | 386 | username := os.Getenv(userKey) 387 | u, err := pop.User(username) 388 | if err != nil { 389 | t.Errorf(err.Error()) 390 | } 391 | 392 | if !strings.HasPrefix(u, ok) { 393 | t.Errorf("expected: %s, got: %s", ok, u) 394 | } 395 | 396 | password := os.Getenv(passwordKey) 397 | p, err := pop.Pass(password) 398 | if err != nil { 399 | t.Errorf(err.Error()) 400 | } 401 | 402 | if !strings.HasPrefix(p, ok) { 403 | t.Errorf("expected: %s, got: %s", ok, p) 404 | } 405 | 406 | d, err := pop.Dele("1") 407 | if err != nil { 408 | t.Errorf(err.Error()) 409 | } 410 | 411 | if !strings.HasPrefix(d, ok) { 412 | t.Errorf("expected prefix: %s, got: %s", ok, d) 413 | } 414 | } 415 | 416 | func TestDeleFail(t *testing.T) { 417 | pop, err := Connect(gmailTLSAddr, nil, true) 418 | if err != nil { 419 | t.Errorf(err.Error()) 420 | } 421 | 422 | username := os.Getenv(userKey) 423 | u, err := pop.User(username) 424 | if err != nil { 425 | t.Errorf(err.Error()) 426 | } 427 | 428 | if !strings.HasPrefix(u, ok) { 429 | t.Errorf("expected: %s, got: %s", ok, u) 430 | } 431 | 432 | password := os.Getenv(passwordKey) 433 | p, err := pop.Pass(password) 434 | if err != nil { 435 | t.Errorf(err.Error()) 436 | } 437 | 438 | if !strings.HasPrefix(p, ok) { 439 | t.Errorf("expected: %s, got: %s", ok, p) 440 | } 441 | 442 | d, err := pop.Dele(strconv.Itoa(math.MaxInt64 - 1)) 443 | if err != nil { 444 | t.Errorf(err.Error()) 445 | } 446 | 447 | if !strings.HasPrefix(d, e) { 448 | t.Errorf("expected prefix: %s, got: %s", e, d) 449 | } 450 | } 451 | 452 | func TestRset(t *testing.T) { 453 | pop, err := Connect(gmailTLSAddr, nil, true) 454 | if err != nil { 455 | t.Errorf(err.Error()) 456 | } 457 | 458 | username := os.Getenv(userKey) 459 | u, err := pop.User(username) 460 | if err != nil { 461 | t.Errorf(err.Error()) 462 | } 463 | 464 | if !strings.HasPrefix(u, ok) { 465 | t.Errorf("expected: %s, got: %s", ok, u) 466 | } 467 | 468 | password := os.Getenv(passwordKey) 469 | p, err := pop.Pass(password) 470 | if err != nil { 471 | t.Errorf(err.Error()) 472 | } 473 | 474 | if !strings.HasPrefix(p, ok) { 475 | t.Errorf("expected: %s, got: %s", ok, p) 476 | } 477 | 478 | d, err := pop.Dele("1") 479 | if err != nil { 480 | t.Errorf(err.Error()) 481 | } 482 | 483 | if !strings.HasPrefix(d, ok) { 484 | t.Errorf("expected prefix: %s, got: %s", ok, d) 485 | } 486 | 487 | r, err := pop.Rset() 488 | if err != nil { 489 | t.Errorf(err.Error()) 490 | } 491 | 492 | if !strings.HasPrefix(r, ok) { 493 | t.Errorf("expected prefix: %s, got: %s", ok, r) 494 | } 495 | } 496 | 497 | func TestTopNegativeMsgNum(t *testing.T) { 498 | exp := "-ERR message number should be greater than 0" 499 | pop, err := Connect(gmailTLSAddr, nil, true) 500 | if err != nil { 501 | t.Errorf(err.Error()) 502 | } 503 | log.Println("Connection established") 504 | 505 | msgNum := -1 506 | top, err := pop.Top(msgNum, 10) 507 | if top != nil { 508 | t.Errorf("need to be nil") 509 | } 510 | if !strings.HasPrefix(err.Error(), exp) { 511 | t.Errorf(err.Error()) 512 | } 513 | log.Println(err.Error()) 514 | 515 | quit, err := pop.Quit() 516 | if err != nil { 517 | t.Errorf(err.Error()) 518 | } 519 | if !strings.HasPrefix(quit, ok) { 520 | t.Errorf(quit) 521 | } 522 | log.Println("Connection closed") 523 | } 524 | 525 | func TestTopNegativeN(t *testing.T) { 526 | exp := "-ERR line count cannot be negative" 527 | pop, err := Connect(gmailTLSAddr, nil, true) 528 | if err != nil { 529 | t.Errorf(err.Error()) 530 | } 531 | log.Println("Connection established") 532 | 533 | n := -1 534 | top, err := pop.Top(1, n) 535 | if top != nil { 536 | t.Errorf("need to be nil") 537 | } 538 | if !strings.HasPrefix(err.Error(), exp) { 539 | t.Errorf(err.Error()) 540 | } 541 | log.Println(err.Error()) 542 | 543 | quit, err := pop.Quit() 544 | if err != nil { 545 | t.Errorf(err.Error()) 546 | } 547 | if !strings.HasPrefix(quit, ok) { 548 | t.Errorf(quit) 549 | } 550 | log.Println("Connection closed") 551 | } 552 | 553 | func TestTopNotLoggedIn(t *testing.T) { 554 | pop, err := Connect(gmailTLSAddr, nil, true) 555 | if err != nil { 556 | t.Errorf(err.Error()) 557 | } 558 | log.Println("Connection established") 559 | 560 | top, err := pop.Top(1, 20) 561 | if err != nil { 562 | t.Errorf("err need to be ") 563 | } 564 | if !strings.HasPrefix(top[0], e) { 565 | t.Errorf(top[0]) 566 | } 567 | log.Println(top[0]) 568 | log.Println(err) 569 | } 570 | 571 | func TestTopPass(t *testing.T) { 572 | pop, err := Connect(gmailTLSAddr, nil, true) 573 | if err != nil { 574 | t.Errorf(err.Error()) 575 | } 576 | log.Println("Connection established") 577 | 578 | username := os.Getenv(userKey) 579 | u, err := pop.User(username) 580 | if err != nil { 581 | t.Errorf(err.Error()) 582 | } 583 | if !strings.HasPrefix(u, ok) { 584 | t.Errorf("expected: %s, got: %s", ok, u) 585 | } 586 | 587 | password := os.Getenv(passwordKey) 588 | p, err := pop.Pass(password) 589 | if err != nil { 590 | t.Errorf(err.Error()) 591 | } 592 | if !strings.HasPrefix(p, ok) { 593 | t.Errorf("expected: %s, got: %s", ok, p) 594 | } 595 | 596 | top, err := pop.Top(1, 20) 597 | if err != nil { 598 | t.Errorf(err.Error()) 599 | } 600 | if !strings.HasPrefix(top[0], ok) { 601 | t.Errorf(top[0]) 602 | } 603 | log.Println(top[0]) 604 | 605 | quit, err := pop.Quit() 606 | if err != nil { 607 | t.Errorf(err.Error()) 608 | } 609 | log.Println(quit) 610 | log.Println("Connection closed") 611 | } 612 | --------------------------------------------------------------------------------