Skip to content

Commit da0fc0a

Browse files
authored
Redo ReadNullTerminatedString (#3937)
For safety with non-ASCII chars. Fixes #3660
1 parent 876c800 commit da0fc0a

2 files changed

Lines changed: 58 additions & 26 deletions

File tree

src/Npgsql/Internal/NpgsqlReadBuffer.cs

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -572,39 +572,69 @@ public ValueTask<string> ReadNullTerminatedString(bool async, CancellationToken
572572
/// </summary>
573573
ValueTask<string> ReadNullTerminatedString(Encoding encoding, bool async, CancellationToken cancellationToken = default)
574574
{
575-
return ReadFromBuffer(this, encoding, out var s)
576-
? new ValueTask<string>(s)
577-
: ReadLong(this, async, encoding, s);
578-
579-
static bool ReadFromBuffer(NpgsqlReadBuffer buffer, Encoding encoding, out string s)
575+
for (var i = ReadPosition; i < FilledBytes; i++)
580576
{
581-
var start = buffer.ReadPosition;
582-
while (buffer.ReadPosition < buffer.FilledBytes)
577+
if (Buffer[i] == 0)
583578
{
584-
if (buffer.Buffer[buffer.ReadPosition++] == 0)
585-
{
586-
s = encoding.GetString(buffer.Buffer, start, buffer.ReadPosition - start - 1);
587-
return true;
588-
}
579+
var byteLen = i - ReadPosition;
580+
var result = new ValueTask<string>(encoding.GetString(Buffer, ReadPosition, byteLen));
581+
ReadPosition += byteLen + 1;
582+
return result;
589583
}
590-
591-
s = encoding.GetString(buffer.Buffer, start, buffer.ReadPosition - start);
592-
return false;
593584
}
594585

595-
static async ValueTask<string> ReadLong(NpgsqlReadBuffer buffer, bool async, Encoding encoding, string s)
586+
return ReadLong(async);
587+
588+
async ValueTask<string> ReadLong(bool async)
596589
{
597-
var builder = new StringBuilder(s);
598-
bool complete;
599-
do
590+
var chunkSize = FilledBytes - ReadPosition;
591+
var tempBuf = ArrayPool<byte>.Shared.Rent(chunkSize + 1024);
592+
593+
try
600594
{
601-
await buffer.ReadMore(async);
602-
complete = ReadFromBuffer(buffer, encoding, out s);
603-
builder.Append(s);
604-
}
605-
while (!complete);
595+
bool foundTerminator;
596+
var byteLen = chunkSize;
597+
Array.Copy(Buffer, ReadPosition, tempBuf, 0, chunkSize);
598+
ReadPosition += chunkSize;
599+
600+
do
601+
{
602+
await ReadMore(async);
603+
Debug.Assert(ReadPosition == 0);
606604

607-
return builder.ToString();
605+
foundTerminator = false;
606+
int i;
607+
for (i = 0; i < FilledBytes; i++)
608+
{
609+
if (Buffer[i] == 0)
610+
{
611+
foundTerminator = true;
612+
break;
613+
}
614+
}
615+
616+
if (byteLen + i > tempBuf.Length)
617+
{
618+
var newTempBuf = ArrayPool<byte>.Shared.Rent(
619+
foundTerminator ? byteLen + i : byteLen + i + 1024);
620+
621+
Array.Copy(tempBuf, 0, newTempBuf, 0, byteLen);
622+
ArrayPool<byte>.Shared.Return(tempBuf);
623+
tempBuf = newTempBuf;
624+
}
625+
626+
Array.Copy(Buffer, 0, tempBuf, byteLen, i);
627+
byteLen += i;
628+
ReadPosition = i;
629+
} while (!foundTerminator);
630+
631+
ReadPosition++;
632+
return encoding.GetString(tempBuf, 0, byteLen);
633+
}
634+
finally
635+
{
636+
ArrayPool<byte>.Shared.Return(tempBuf);
637+
}
608638
}
609639
}
610640

test/Npgsql.Tests/ReadBufferTests.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.IO;
3-
using System.Text;
43
using System.Threading;
54
using System.Threading.Tasks;
65
using Npgsql.Internal;
@@ -61,6 +60,8 @@ public void ReadNullTerminatedString_buffered_only()
6160
.Write(PGUtil.UTF8Encoding.GetBytes(new string("bar")))
6261
.WriteByte(0);
6362

63+
ReadBuffer.Ensure(1);
64+
6465
Assert.That(ReadBuffer.ReadNullTerminatedString(), Is.EqualTo("foo"));
6566
Assert.That(ReadBuffer.ReadNullTerminatedString(), Is.EqualTo("bar"));
6667
}
@@ -69,6 +70,7 @@ public void ReadNullTerminatedString_buffered_only()
6970
public async Task ReadNullTerminatedString_with_io()
7071
{
7172
Writer.Write(PGUtil.UTF8Encoding.GetBytes(new string("Chunked ")));
73+
ReadBuffer.Ensure(1);
7274
var task = ReadBuffer.ReadNullTerminatedString(async: true);
7375
Assert.That(!task.IsCompleted);
7476

0 commit comments

Comments
 (0)