Skip to content

Commit da876e9

Browse files
Optimize HttpUtility.UrlEncodeToBytes for (string, Encoding) overload.
1 parent 49c10ed commit da876e9

File tree

2 files changed

+61
-52
lines changed

2 files changed

+61
-52
lines changed

src/libraries/System.Web.HttpUtility/src/System/Web/HttpUtility.cs

+2-11
Original file line numberDiff line numberDiff line change
@@ -216,19 +216,10 @@ public static NameValueCollection ParseQueryString(string query, Encoding encodi
216216
public static byte[]? UrlDecodeToBytes(byte[]? bytes) => bytes == null ? null : HttpEncoder.UrlDecode(bytes.AsSpan(0, bytes.Length));
217217

218218
[return: NotNullIfNotNull(nameof(str))]
219-
public static byte[]? UrlEncodeToBytes(string? str, Encoding e)
220-
{
221-
if (str == null)
222-
{
223-
return null;
224-
}
225-
226-
byte[] bytes = e.GetBytes(str);
227-
return HttpEncoder.UrlEncode(bytes, 0, bytes.Length, alwaysCreateNewReturnValue: false);
228-
}
219+
public static byte[]? UrlEncodeToBytes(string? str, Encoding e) => str == null ? null : HttpEncoder.UrlEncode(str, e);
229220

230221
[return: NotNullIfNotNull(nameof(bytes))]
231-
public static byte[]? UrlEncodeToBytes(byte[]? bytes, int offset, int count) => HttpEncoder.UrlEncode(bytes, offset, count, alwaysCreateNewReturnValue: true);
222+
public static byte[]? UrlEncodeToBytes(byte[]? bytes, int offset, int count) => HttpEncoder.UrlEncode(bytes, offset, count);
232223

233224
[Obsolete("This method produces non-standards-compliant output and has interoperability issues. The preferred alternative is UrlEncode(String).")]
234225
[return: NotNullIfNotNull(nameof(str))]

src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs

+59-41
Original file line numberDiff line numberDiff line change
@@ -401,64 +401,37 @@ internal static string UrlDecode(ReadOnlySpan<char> value, Encoding encoding)
401401
}
402402

403403
[return: NotNullIfNotNull(nameof(bytes))]
404-
internal static byte[]? UrlEncode(byte[]? bytes, int offset, int count, bool alwaysCreateNewReturnValue)
405-
{
406-
byte[]? encoded = UrlEncode(bytes, offset, count);
407-
408-
return (alwaysCreateNewReturnValue && (encoded != null) && (encoded == bytes))
409-
? (byte[])encoded.Clone()
410-
: encoded;
411-
}
412-
413-
[return: NotNullIfNotNull(nameof(bytes))]
414-
private static byte[]? UrlEncode(byte[]? bytes, int offset, int count)
404+
internal static byte[]? UrlEncode(byte[]? bytes, int offset, int count)
415405
{
416406
if (!ValidateUrlEncodingParameters(bytes, offset, count))
417407
{
418408
return null;
419409
}
420410

421-
int cSpaces = 0;
422-
int cUnsafe = 0;
423-
424-
// count them first
425-
for (int i = 0; i < count; i++)
426-
{
427-
char ch = (char)bytes[offset + i];
428-
429-
if (ch == ' ')
430-
{
431-
cSpaces++;
432-
}
433-
else if (!HttpEncoderUtility.IsUrlSafeChar(ch))
434-
{
435-
cUnsafe++;
436-
}
437-
}
411+
return UrlEncode(bytes.AsSpan(offset, count));
412+
}
438413

414+
private static byte[] UrlEncode(ReadOnlySpan<byte> bytes)
415+
{
439416
// nothing to expand?
440-
if (cSpaces == 0 && cUnsafe == 0)
417+
if (!IsExpandingNeeded(bytes, out int cUnsafe))
441418
{
442-
// DevDiv 912606: respect "offset" and "count"
443-
if (0 == offset && bytes.Length == count)
444-
{
445-
return bytes;
446-
}
447-
else
448-
{
449-
byte[] subarray = new byte[count];
450-
Buffer.BlockCopy(bytes, offset, subarray, 0, count);
451-
return subarray;
452-
}
419+
return bytes.ToArray();
453420
}
454421

422+
return UrlEncode(bytes, cUnsafe);
423+
}
424+
425+
private static byte[] UrlEncode(ReadOnlySpan<byte> bytes, int cUnsafe)
426+
{
427+
int count = bytes.Length;
455428
// expand not 'safe' characters into %XX, spaces to +s
456429
byte[] expandedBytes = new byte[count + cUnsafe * 2];
457430
int pos = 0;
458431

459432
for (int i = 0; i < count; i++)
460433
{
461-
byte b = bytes[offset + i];
434+
byte b = bytes[i];
462435
char ch = (char)b;
463436

464437
if (HttpEncoderUtility.IsUrlSafeChar(ch))
@@ -480,6 +453,51 @@ internal static string UrlDecode(ReadOnlySpan<char> value, Encoding encoding)
480453
return expandedBytes;
481454
}
482455

456+
private static bool IsExpandingNeeded(ReadOnlySpan<byte> bytes, out int cUnsafe)
457+
{
458+
cUnsafe = 0;
459+
460+
int cSpaces = 0;
461+
int count = bytes.Length;
462+
for (int i = 0; i < count; i++)
463+
{
464+
char ch = (char)bytes[i];
465+
466+
if (ch == ' ')
467+
{
468+
cSpaces++;
469+
}
470+
else if (!HttpEncoderUtility.IsUrlSafeChar(ch))
471+
{
472+
cUnsafe++;
473+
}
474+
}
475+
476+
return cSpaces != 0 || cUnsafe != 0;
477+
}
478+
479+
internal static byte[] UrlEncode(string str, Encoding e)
480+
{
481+
const int StackallocThreshold = 512;
482+
483+
if (e.GetMaxByteCount(str.Length) <= StackallocThreshold)
484+
{
485+
Span<byte> byteSpan = stackalloc byte[StackallocThreshold];
486+
int encodedBytes = e.GetBytes(str, byteSpan);
487+
488+
return UrlEncode(byteSpan.Slice(0, encodedBytes));
489+
}
490+
491+
byte[] bytes = e.GetBytes(str);
492+
if (!IsExpandingNeeded(bytes, out int cUnsafe))
493+
{
494+
// return encoded byte[] if nothing to expand
495+
return bytes;
496+
}
497+
498+
return UrlEncode(bytes.AsSpan(0, bytes.Length), cUnsafe);
499+
}
500+
483501
// Helper to encode the non-ASCII url characters only
484502
private static string UrlEncodeNonAscii(string str, Encoding e)
485503
{

0 commit comments

Comments
 (0)