package imapclient_test import ( "crypto/tls" "io" "net" "os" "sync" "testing" "github.com/emersion/go-imap/v2" "github.com/emersion/go-imap/v2/imapclient" "github.com/emersion/go-imap/v2/imapserver" "github.com/emersion/go-imap/v2/imapserver/imapmemserver" ) const ( testUsername = "test-user" testPassword = "test-password" ) const simpleRawMessage = `MIME-Version: 1.0 Message-Id: <191101702316132@example.com> Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset=utf-8 This is my letter!` var rsaCertPEM = `-----BEGIN CERTIFICATE----- MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI 5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2 +tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK 5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/ WkBKOclmOV2xlTVuPw== -----END CERTIFICATE----- ` var rsaKeyPEM = `-----BEGIN RSA PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDoZtrm0dXV0Aqi 4Bpc7f95sNRTiu/AJSD8I1onY9PnEsPg3VVxvytsVJbYdcqr4w99V3AgpH/UNzMS gAZ/8lZBNbsSDOVesJ3euVqMRfYPvd9pYl6QPRRpSDPm+2tNdn3QFAvta9EgJ3sW URnoU85w+W6aLI2bNSq3AaE771p3VbkGolpEjo9h+i42TBHo1rhPNKPkGupR8/QX AOLMpInRdeaHyDwb2a3DE5I3dG7VAVzrVfJ6W6Q84YoFX+rpEE2SVM17SAjy6xQy VjKgLvK2mk0xbtfa+h0B6VK7bmODHZqeP18NVm6HsBcXn7iclLgAC3SfWU1jucZK x1lqzw9tAgMBAAECggEABWzxS1Y2wckblnXY57Z+sl6YdmLV+gxj2r8Qib7g4ZIk lIlWR1OJNfw7kU4eryib4fc6nOh6O4AWZyYqAK6tqNQSS/eVG0LQTLTTEldHyVJL dvBe+MsUQOj4nTndZW+QvFzbcm2D8lY5n2nBSxU5ypVoKZ1EqQzytFcLZpTN7d89 EPj0qDyrV4NZlWAwL1AygCwnlwhMQjXEalVF1ylXwU3QzyZ/6MgvF6d3SSUlh+sq XefuyigXw484cQQgbzopv6niMOmGP3of+yV4JQqUSb3IDmmT68XjGd2Dkxl4iPki 6ZwXf3CCi+c+i/zVEcufgZ3SLf8D99kUGE7v7fZ6AQKBgQD1ZX3RAla9hIhxCf+O 3D+I1j2LMrdjAh0ZKKqwMR4JnHX3mjQI6LwqIctPWTU8wYFECSh9klEclSdCa64s uI/GNpcqPXejd0cAAdqHEEeG5sHMDt0oFSurL4lyud0GtZvwlzLuwEweuDtvT9cJ Wfvl86uyO36IW8JdvUprYDctrQKBgQDycZ697qutBieZlGkHpnYWUAeImVA878sJ w44NuXHvMxBPz+lbJGAg8Cn8fcxNAPqHIraK+kx3po8cZGQywKHUWsxi23ozHoxo +bGqeQb9U661TnfdDspIXia+xilZt3mm5BPzOUuRqlh4Y9SOBpSWRmEhyw76w4ZP OPxjWYAgwQKBgA/FehSYxeJgRjSdo+MWnK66tjHgDJE8bYpUZsP0JC4R9DL5oiaA brd2fI6Y+SbyeNBallObt8LSgzdtnEAbjIH8uDJqyOmknNePRvAvR6mP4xyuR+Bv m+Lgp0DMWTw5J9CKpydZDItc49T/mJ5tPhdFVd+am0NAQnmr1MCZ6nHxAoGABS3Y LkaC9FdFUUqSU8+Chkd/YbOkuyiENdkvl6t2e52jo5DVc1T7mLiIrRQi4SI8N9bN /3oJWCT+uaSLX2ouCtNFunblzWHBrhxnZzTeqVq4SLc8aESAnbslKL4i8/+vYZlN s8xtiNcSvL+lMsOBORSXzpj/4Ot8WwTkn1qyGgECgYBKNTypzAHeLE6yVadFp3nQ Ckq9yzvP/ib05rvgbvrne00YeOxqJ9gtTrzgh7koqJyX1L4NwdkEza4ilDWpucn0 xiUZS4SoaJq6ZvcBYS62Yr1t8n09iG47YL8ibgtmH3L+svaotvpVxVK+d7BLevA/ ZboOWVe3icTy64BT3OQhmg== -----END RSA PRIVATE KEY----- ` func newMemClientServerPair(t *testing.T) (net.Conn, io.Closer) { memServer := imapmemserver.New() user := imapmemserver.NewUser(testUsername, testPassword) user.Create("INBOX", nil) memServer.AddUser(user) cert, err := tls.X509KeyPair([]byte(rsaCertPEM), []byte(rsaKeyPEM)) if err != nil { t.Fatalf("tls.X509KeyPair() = %v", err) } server := imapserver.New(&imapserver.Options{ NewSession: func(conn *imapserver.Conn) (imapserver.Session, *imapserver.GreetingData, error) { return memServer.NewSession(), nil, nil }, TLSConfig: &tls.Config{ Certificates: []tls.Certificate{cert}, }, InsecureAuth: true, Caps: imap.CapSet{ imap.CapIMAP4rev1: {}, imap.CapIMAP4rev2: {}, }, }) ln, err := net.Listen("tcp", "localhost:0") if err != nil { t.Fatalf("net.Listen() = %v", err) } go func() { if err := server.Serve(ln); err != nil { t.Errorf("Serve() = %v", err) } }() conn, err := net.Dial("tcp", ln.Addr().String()) if err != nil { t.Fatalf("net.Dial() = %v", err) } return conn, server } func newClientServerPair(t *testing.T, initialState imap.ConnState) (*imapclient.Client, io.Closer) { var useDovecot bool switch os.Getenv("GOIMAP_TEST_DOVECOT") { case "0", "": // ok case "1": useDovecot = true default: t.Fatalf("invalid GOIMAP_TEST_DOVECOT env var") } var ( conn net.Conn server io.Closer ) if useDovecot { if initialState < imap.ConnStateAuthenticated { t.Skip("Dovecot connections are pre-authenticated") } conn, server = newDovecotClientServerPair(t) } else { conn, server = newMemClientServerPair(t) } var debugWriter swapWriter debugWriter.Swap(io.Discard) var options imapclient.Options if testing.Verbose() { options.DebugWriter = &debugWriter } client := imapclient.New(conn, &options) if initialState >= imap.ConnStateAuthenticated { // Dovecot connections are pre-authenticated if !useDovecot { if err := client.Login(testUsername, testPassword).Wait(); err != nil { t.Fatalf("Login().Wait() = %v", err) } } appendCmd := client.Append("INBOX", int64(len(simpleRawMessage)), nil) appendCmd.Write([]byte(simpleRawMessage)) appendCmd.Close() if _, err := appendCmd.Wait(); err != nil { t.Fatalf("AppendCommand.Wait() = %v", err) } } if initialState >= imap.ConnStateSelected { if _, err := client.Select("INBOX", nil).Wait(); err != nil { t.Fatalf("Select().Wait() = %v", err) } } // Turn on debug logs after we're done initializing the test debugWriter.Swap(os.Stderr) return client, server } // swapWriter is an io.Writer which can be swapped at runtime. type swapWriter struct { w io.Writer mutex sync.Mutex } func (sw *swapWriter) Write(b []byte) (int, error) { sw.mutex.Lock() w := sw.w sw.mutex.Unlock() return w.Write(b) } func (sw *swapWriter) Swap(w io.Writer) { sw.mutex.Lock() sw.w = w sw.mutex.Unlock() } func TestLogin(t *testing.T) { client, server := newClientServerPair(t, imap.ConnStateNotAuthenticated) defer client.Close() defer server.Close() if err := client.Login(testUsername, testPassword).Wait(); err != nil { t.Errorf("Login().Wait() = %v", err) } } func TestLogout(t *testing.T) { client, server := newClientServerPair(t, imap.ConnStateAuthenticated) defer server.Close() if _, ok := server.(*dovecotServer); ok { t.Skip("Dovecot connections don't reply to LOGOUT") } if err := client.Logout().Wait(); err != nil { t.Errorf("Logout().Wait() = %v", err) } if err := client.Close(); err != nil { t.Errorf("Close() = %v", err) } } // https://github.com/emersion/go-imap/issues/562 func TestFetch_invalid(t *testing.T) { client, server := newClientServerPair(t, imap.ConnStateSelected) defer client.Close() defer server.Close() _, err := client.Fetch(imap.UIDSet(nil), nil).Collect() if err == nil { t.Fatalf("UIDFetch().Collect() = %v", err) } } func TestFetch_closeUnreadBody(t *testing.T) { client, server := newClientServerPair(t, imap.ConnStateSelected) defer client.Close() defer server.Close() fetchCmd := client.Fetch(imap.SeqSetNum(1), &imap.FetchOptions{ BodySection: []*imap.FetchItemBodySection{ { Specifier: imap.PartSpecifierNone, Peek: true, }, }, }) if err := fetchCmd.Close(); err != nil { t.Fatalf("UIDFetch().Close() = %v", err) } } func TestWaitGreeting_eof(t *testing.T) { // bad server: connected but without greeting clientConn, serverConn := net.Pipe() client := imapclient.New(clientConn, nil) defer client.Close() if err := serverConn.Close(); err != nil { t.Fatalf("serverConn.Close() = %v", err) } if err := client.WaitGreeting(); err == nil { t.Fatalf("WaitGreeting() should fail") } }