1package imapserver
2
3import (
4 "crypto/tls"
5 "encoding/base64"
6 "io"
7 mathrand "math/rand/v2"
8 "testing"
9 "time"
10)
11
12func TestCompress(t *testing.T) {
13 tc := start(t)
14 defer tc.close()
15
16 tc.client.Login("mjl@mox.example", password0)
17
18 tc.transactf("bad", "compress")
19 tc.transactf("bad", "compress bogus ")
20 tc.transactf("no", "compress bogus")
21
22 tc.client.CompressDeflate()
23 tc.transactf("no", "compress deflate") // Cannot have multiple.
24 tc.xcode("COMPRESSIONACTIVE")
25
26 tc.client.Select("inbox")
27 tc.transactf("ok", "append inbox (\\seen) {%d+}\r\n%s", len(exampleMsg), exampleMsg)
28 tc.transactf("ok", "noop")
29 tc.transactf("ok", "fetch 1 body.peek[1]")
30}
31
32func TestCompressStartTLS(t *testing.T) {
33 tc := start(t)
34 defer tc.close()
35
36 tc.client.Starttls(&tls.Config{InsecureSkipVerify: true})
37 tc.client.Login("mjl@mox.example", password0)
38 tc.client.CompressDeflate()
39 tc.client.Select("inbox")
40 tc.transactf("ok", "append inbox (\\seen) {%d+}\r\n%s", len(exampleMsg), exampleMsg)
41 tc.transactf("ok", "noop")
42 tc.transactf("ok", "fetch 1 body.peek[1]")
43
44 // Prevent client.Close from failing the test. The client first closes the
45 // compression stream, which causes the server to close the connection, after which
46 // the messages to close the TLS connection are written to a closed socket.
47 tc.client.SetPanic(false)
48}
49
50func TestCompressBreak(t *testing.T) {
51 // Close the client connection when the server is writing. That causes writes in
52 // the server to fail (panic), jumping out of the flate writer and leaving its
53 // state inconsistent. We must not call into the flate writer again because due to
54 // its broken internal state it may cause array out of bounds accesses.
55
56 tc := start(t)
57 defer tc.close()
58
59 msg := exampleMsg
60 // Add random data (so it is not compressible). Don't know why, but only
61 // reproducible with large writes. As if setting socket buffers had no effect.
62 buf := make([]byte, 64*1024)
63 _, err := io.ReadFull(mathrand.NewChaCha8([32]byte{}), buf)
64 tcheck(t, err, "read random")
65 text := base64.StdEncoding.EncodeToString(buf)
66 for len(text) > 0 {
67 n := min(78, len(text))
68 msg += text[:n] + "\r\n"
69 text = text[n:]
70 }
71
72 tc.client.Login("mjl@mox.example", password0)
73 tc.client.CompressDeflate()
74 tc.client.Select("inbox")
75 tc.transactf("ok", "append inbox (\\seen) {%d+}\r\n%s", len(msg), msg)
76 tc.transactf("ok", "noop")
77
78 // Write request. Close connection instead of reading data. Write will panic,
79 // coming through flate writer leaving its state inconsistent. Server must not try
80 // to Flush/Write again on flate writer or it may panic.
81 tc.client.Writelinef("x fetch 1 body.peek[1]")
82
83 // Close client connection and prevent cleanup from closing the client again.
84 time.Sleep(time.Second / 10)
85 tc.client = nil
86 tc.conn.Close() // Simulate client disappearing.
87}
88