Skip to content

Commit 9bec213

Browse files
committed
Fixed ImapTokenCache to properly equate non-ASCII tokens
1 parent d70f1bf commit 9bec213

File tree

1 file changed

+47
-64
lines changed

1 file changed

+47
-64
lines changed

MailKit/Net/Imap/ImapTokenCache.cs

Lines changed: 47 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626

2727
using System;
2828
using System.Text;
29-
using System.Collections.Generic;
3029
using System.Buffers;
3130
using System.Diagnostics;
31+
using System.Collections.Generic;
3232

3333
namespace MailKit.Net.Imap
3434
{
@@ -40,7 +40,7 @@ class ImapTokenCache
4040
readonly LinkedList<ImapTokenItem> list;
4141
readonly ImapTokenKey lookupKey;
4242
readonly Decoder[] decoders;
43-
readonly char[] chars;
43+
char[] charBuffer;
4444

4545
public ImapTokenCache ()
4646
{
@@ -54,13 +54,13 @@ public ImapTokenCache ()
5454
TextEncodings.Latin1.GetDecoder ()
5555
};
5656

57-
chars = new char[128];
57+
charBuffer = ArrayPool<char>.Shared.Rent (256);
5858
}
5959

6060
public ImapToken AddOrGet (ImapTokenType type, ByteArrayBuilder builder)
6161
{
6262
// lookupKey is a pre-allocated key used for lookups
63-
lookupKey.Init (decoders, chars, type, builder.GetBuffer (), builder.Length, out var decoder, out int charsNeeded);
63+
lookupKey.Init (decoders, ref charBuffer, type, builder.GetBuffer (), builder.Length, out int charsNeeded);
6464

6565
if (cache.TryGetValue (lookupKey, out var node)) {
6666
// move the node to the head of the list
@@ -71,26 +71,7 @@ public ImapToken AddOrGet (ImapTokenType type, ByteArrayBuilder builder)
7171
return node.Value.Token;
7272
}
7373

74-
string value;
75-
76-
if (charsNeeded <= chars.Length) {
77-
// If the number of needed chars is <= the length of our temp buffer, then it should all be contained.
78-
value = new string (chars, 0, charsNeeded);
79-
} else {
80-
var buffer = ArrayPool<char>.Shared.Rent (charsNeeded);
81-
try {
82-
// Note: This conversion should go flawlessly, so we'll just Debug.Assert() our expectations.
83-
decoder.Convert (builder.GetBuffer (), 0, builder.Length, buffer, 0, buffer.Length, true, out var bytesUsed, out var charsUsed, out var completed);
84-
Debug.Assert (bytesUsed == builder.Length);
85-
Debug.Assert (charsUsed == charsNeeded);
86-
Debug.Assert (completed);
87-
value = new string (buffer, 0, charsUsed);
88-
} finally {
89-
ArrayPool<char>.Shared.Return (buffer);
90-
decoder.Reset ();
91-
}
92-
}
93-
74+
var value = new string (charBuffer, 0, charsNeeded);
9475
var token = new ImapToken (type, value);
9576

9677
if (cache.Count >= capacity) {
@@ -100,10 +81,10 @@ public ImapToken AddOrGet (ImapTokenType type, ByteArrayBuilder builder)
10081
cache.Remove (node.Value.Key);
10182

10283
// re-use the node, item and key to avoid allocations
103-
node.Value.Key.Init (type, (string) token.Value);
84+
node.Value.Key.Init (type, value, lookupKey);
10485
node.Value.Token = token;
10586
} else {
106-
var key = new ImapTokenKey (type, (string) token.Value);
87+
var key = new ImapTokenKey (type, value, lookupKey);
10788
var item = new ImapTokenItem (key, token);
10889

10990
node = new LinkedListNode<ImapTokenItem> (item);
@@ -118,47 +99,50 @@ public ImapToken AddOrGet (ImapTokenType type, ByteArrayBuilder builder)
11899
class ImapTokenKey
119100
{
120101
ImapTokenType type;
121-
byte[] byteArrayKey;
102+
char[] charBuffer;
122103
string stringKey;
123-
int length;
124104
int hashCode;
105+
int length;
125106

126107
public ImapTokenKey ()
127108
{
128109
}
129110

130-
public ImapTokenKey (ImapTokenType type, string key)
111+
public ImapTokenKey (ImapTokenType type, string value, ImapTokenKey key)
131112
{
132-
Init (type, key);
113+
Init (type, value, key);
133114
}
134115

135-
public void Init (Decoder[] decoders, char[] chars, ImapTokenType type, byte[] key, int length, out Decoder correctDecoder, out int charsNeeded)
116+
public void Init (Decoder[] decoders, ref char[] charBuffer, ImapTokenType type, byte[] key, int length, out int charsNeeded)
136117
{
137118
this.type = type;
138-
this.byteArrayKey = key;
139-
this.stringKey = null;
140-
this.length = length;
141119

142120
var hash = new HashCode ();
143121
hash.Add ((int) type);
144122

145-
correctDecoder = null;
146123
charsNeeded = 0;
147124

125+
// Make sure the char buffer is at least as large as the key.
126+
if (charBuffer.Length < length) {
127+
ArrayPool<char>.Shared.Return (charBuffer);
128+
charBuffer = ArrayPool<char>.Shared.Rent (length);
129+
}
130+
148131
foreach (var decoder in decoders) {
149132
bool completed;
150133
int index = 0;
151134

152-
correctDecoder = decoder;
153-
154135
do {
155136
try {
156-
decoder.Convert (key, index, length - index, chars, 0, chars.Length, true, out var bytesUsed, out var charsUsed, out completed);
137+
decoder.Convert (key, index, length - index, charBuffer, charsNeeded, charBuffer.Length - charsNeeded, true, out var bytesUsed, out var charsUsed, out completed);
157138
charsNeeded += charsUsed;
158139
index += bytesUsed;
159140

160141
for (int i = 0; i < charsUsed; i++)
161-
hash.Add (chars[i]);
142+
hash.Add (charBuffer[i]);
143+
144+
if (completed)
145+
break;
162146
} catch (DecoderFallbackException) {
163147
// Restart the hash...
164148
hash = new HashCode ();
@@ -167,36 +151,39 @@ public void Init (Decoder[] decoders, char[] chars, ImapTokenType type, byte[] k
167151
charsNeeded = 0;
168152
break;
169153
}
170-
} while (!completed);
154+
155+
// The char buffer was not large enough to contain the full token. Resize it and try again.
156+
var newBuffer = ArrayPool<char>.Shared.Rent (charBuffer.Length + (length - index));
157+
charBuffer.AsSpan (0, charsNeeded).CopyTo (newBuffer);
158+
ArrayPool<char>.Shared.Return (charBuffer);
159+
charBuffer = newBuffer;
160+
} while (true);
171161

172162
decoder.Reset ();
173163

174164
if (completed)
175165
break;
176166
}
177167

168+
this.charBuffer = charBuffer;
169+
this.length = charsNeeded;
170+
178171
this.hashCode = hash.ToHashCode ();
179172
}
180173

181-
public void Init (ImapTokenType type, string key)
174+
public void Init (ImapTokenType type, string value, ImapTokenKey key)
182175
{
183176
this.type = type;
184-
this.byteArrayKey = null;
185-
this.stringKey = key;
186-
this.length = key.Length;
187-
188-
var hash = new HashCode ();
189-
hash.Add ((int) type);
190-
for (int i = 0; i < length; i++)
191-
hash.Add (key[i]);
192-
193-
this.hashCode = hash.ToHashCode ();
177+
this.charBuffer = null;
178+
this.stringKey = value;
179+
this.length = value.Length;
180+
this.hashCode = key.hashCode;
194181
}
195182

196-
static bool Equals (string str, byte[] bytes)
183+
static bool Equals (string str, char[] chars)
197184
{
198185
for (int i = 0; i < str.Length; i++) {
199-
if (str[i] != (char) bytes[i])
186+
if (str[i] != chars[i])
200187
return false;
201188
}
202189

@@ -208,22 +195,18 @@ static bool Equals (ImapTokenKey self, ImapTokenKey other)
208195
if (self.type != other.type || self.length != other.length)
209196
return false;
210197

198+
// Note: At most, only one of the ImapTokenKeys will use a charBuffer and that ImapTokenKey will be the lookup key.
211199
if (self.stringKey != null) {
212200
if (other.stringKey != null)
213201
return self.stringKey.Equals (other.stringKey, StringComparison.Ordinal);
214202

215-
return Equals (self.stringKey, other.byteArrayKey);
216-
}
217-
218-
if (other.stringKey != null)
219-
return Equals (other.stringKey, self.byteArrayKey);
203+
return Equals (self.stringKey, other.charBuffer);
204+
} else {
205+
// Note: 'self' MUST be the lookup key.
206+
Debug.Assert (self.charBuffer != null);
220207

221-
for (int i = 0; i < self.length; i++) {
222-
if (self.byteArrayKey[i] != other.byteArrayKey[i])
223-
return false;
208+
return Equals (other.stringKey, self.charBuffer);
224209
}
225-
226-
return true;
227210
}
228211

229212
public override bool Equals (object obj)
@@ -238,7 +221,7 @@ public override int GetHashCode ()
238221

239222
public override string ToString ()
240223
{
241-
return string.Format ("{0}: {1}", type, stringKey ?? Encoding.UTF8.GetString (byteArrayKey, 0, length));
224+
return string.Format ("{0}: {1}", type, stringKey ?? new string (charBuffer, 0, length));
242225
}
243226
}
244227

0 commit comments

Comments
 (0)