1package imapserver
2
3import (
4 "testing"
5
6 "github.com/mjl-/mox/imapclient"
7)
8
9func TestReplace(t *testing.T) {
10 defer mockUIDValidity()()
11
12 tc := start(t)
13 defer tc.close()
14
15 tc2 := startNoSwitchboard(t)
16 defer tc2.closeNoWait()
17
18 tc.client.Login("mjl@mox.example", password0)
19 tc.client.Select("inbox")
20
21 // Append 3 messages, remove first. Leaves msgseq 1,2 with uid 2,3.
22 tc.client.Append("inbox", makeAppend(exampleMsg), makeAppend(exampleMsg), makeAppend(exampleMsg))
23 tc.client.StoreFlagsSet("1", true, `\deleted`)
24 tc.client.Expunge()
25
26 tc.transactf("no", "replace 2 expungebox {1}") // Mailbox no longer exists.
27 tc.xcode("TRYCREATE")
28
29 tc2.client.Login("mjl@mox.example", password0)
30 tc2.client.Select("inbox")
31
32 // Replace last message (msgseq 2, uid 3) in same mailbox.
33 tc.lastUntagged, tc.lastResult, tc.lastErr = tc.client.Replace("2", "INBOX", makeAppend(searchMsg))
34 tcheck(tc.t, tc.lastErr, "read imap response")
35 tc.xuntagged(
36 imapclient.UntaggedResult{Status: "OK", RespText: imapclient.RespText{Code: "APPENDUID", CodeArg: imapclient.CodeAppendUID{UIDValidity: 1, UIDs: xparseUIDRange("4")}, More: ""}},
37 imapclient.UntaggedExists(3),
38 imapclient.UntaggedExpunge(2),
39 )
40 tc.xcodeArg(imapclient.CodeHighestModSeq(8))
41
42 // Check that other client sees Exists and Expunge.
43 tc2.transactf("ok", "noop")
44 tc2.xuntagged(
45 imapclient.UntaggedExpunge(2),
46 imapclient.UntaggedExists(2),
47 imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(4), imapclient.FetchFlags(nil)}},
48 )
49
50 // Enable qresync, replace uid 2 (msgseq 1) to different mailbox, see that we get vanished instead of expunged.
51 tc.transactf("ok", "enable qresync")
52 tc.lastUntagged, tc.lastResult, tc.lastErr = tc.client.UIDReplace("2", "INBOX", makeAppend(searchMsg))
53 tcheck(tc.t, tc.lastErr, "read imap response")
54 tc.xuntagged(
55 imapclient.UntaggedResult{Status: "OK", RespText: imapclient.RespText{Code: "APPENDUID", CodeArg: imapclient.CodeAppendUID{UIDValidity: 1, UIDs: xparseUIDRange("5")}, More: ""}},
56 imapclient.UntaggedExists(3),
57 imapclient.UntaggedVanished{UIDs: xparseNumSet("2")},
58 )
59 tc.xcodeArg(imapclient.CodeHighestModSeq(9))
60
61 // Non-existent mailbox with non-synchronizing literal should consume the literal.
62 tc.transactf("no", "replace 1 bogusbox {1+}\r\nx")
63
64 // Leftover data.
65 tc.transactf("bad", "replace 1 inbox () {6+}\r\ntest\r\n ")
66}
67
68func TestReplaceBigNonsyncLit(t *testing.T) {
69 tc := start(t)
70 defer tc.close()
71
72 tc.client.Login("mjl@mox.example", password0)
73 tc.client.Select("inbox")
74
75 // Adding a message >1mb with non-sync literal to non-existent mailbox should abort entire connection.
76 tc.transactf("bad", "replace 12345 inbox {2000000+}")
77 tc.xuntagged(
78 imapclient.UntaggedBye{Code: "ALERT", More: "error condition and non-synchronizing literal too big"},
79 )
80 tc.xcode("TOOBIG")
81}
82
83func TestReplaceQuota(t *testing.T) {
84 // with quota limit
85 tc := startArgs(t, true, false, true, true, "limit")
86 defer tc.close()
87
88 tc.client.Login("limit@mox.example", password0)
89 tc.client.Select("inbox")
90 tc.client.Append("inbox", makeAppend("x"))
91
92 // Synchronizing literal, we get failure immediately.
93 tc.transactf("no", "replace 1 inbox {6}\r\n")
94 tc.xcode("OVERQUOTA")
95
96 // Synchronizing literal to non-existent mailbox, we get failure immediately.
97 tc.transactf("no", "replace 1 badbox {6}\r\n")
98 tc.xcode("TRYCREATE")
99
100 buf := make([]byte, 4000, 4002)
101 for i := range buf {
102 buf[i] = 'x'
103 }
104 buf = append(buf, "\r\n"...)
105
106 // Non-synchronizing literal. We get to write our data.
107 tc.client.Commandf("", "replace 1 inbox ~{4000+}")
108 _, err := tc.client.Write(buf)
109 tc.check(err, "write replace message")
110 tc.response("no")
111 tc.xcode("OVERQUOTA")
112
113 // Non-synchronizing literal to bad mailbox.
114 tc.client.Commandf("", "replace 1 badbox {4000+}")
115 _, err = tc.client.Write(buf)
116 tc.check(err, "write replace message")
117 tc.response("no")
118 tc.xcode("TRYCREATE")
119}
120
121func TestReplaceExpunged(t *testing.T) {
122 tc := start(t)
123 defer tc.close()
124
125 tc.client.Login("mjl@mox.example", password0)
126 tc.client.Select("inbox")
127 tc.client.Append("inbox", makeAppend(exampleMsg))
128
129 // We start the command, but don't write data yet.
130 tc.client.Commandf("", "replace 1 inbox {4000}")
131
132 // Get in with second client and remove the message we are replacing.
133 tc2 := startNoSwitchboard(t)
134 defer tc2.closeNoWait()
135 tc2.client.Login("mjl@mox.example", password0)
136 tc2.client.Select("inbox")
137 tc2.client.StoreFlagsSet("1", true, `\Deleted`)
138 tc2.client.Expunge()
139 tc2.client.Unselect()
140 tc2.client.Close()
141
142 // Now continue trying to replace the message. We should get an error and an expunge.
143 tc.readprefixline("+ ")
144 buf := make([]byte, 4000, 4002)
145 for i := range buf {
146 buf[i] = 'x'
147 }
148 buf = append(buf, "\r\n"...)
149 _, err := tc.client.Write(buf)
150 tc.check(err, "write replace message")
151 tc.response("no")
152 tc.xuntagged(
153 imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(1), imapclient.FetchFlags{`\Deleted`}}},
154 imapclient.UntaggedExpunge(1),
155 )
156}
157