Skip to content

Commit d550e79

Browse files
Optimize HttpUtility.UrlEncodeToBytes for (string, Encoding) overload.
1 parent 03bc51f commit d550e79

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
@@ -199,19 +199,10 @@ public static NameValueCollection ParseQueryString(string query, Encoding encodi
199199
public static byte[]? UrlDecodeToBytes(byte[]? bytes) => bytes == null ? null : UrlDecodeToBytes(bytes, 0, bytes.Length);
200200

201201
[return: NotNullIfNotNull(nameof(str))]
202-
public static byte[]? UrlEncodeToBytes(string? str, Encoding e)
203-
{
204-
if (str == null)
205-
{
206-
return null;
207-
}
208-
209-
byte[] bytes = e.GetBytes(str);
210-
return HttpEncoder.UrlEncode(bytes, 0, bytes.Length, alwaysCreateNewReturnValue: false);
211-
}
202+
public static byte[]? UrlEncodeToBytes(string? str, Encoding e) => str == null ? null : HttpEncoder.UrlEncode(str, e);
212203

213204
[return: NotNullIfNotNull(nameof(bytes))]
214-
public static byte[]? UrlEncodeToBytes(byte[]? bytes, int offset, int count) => HttpEncoder.UrlEncode(bytes, offset, count, alwaysCreateNewReturnValue: true);
205+
public static byte[]? UrlEncodeToBytes(byte[]? bytes, int offset, int count) => HttpEncoder.UrlEncode(bytes, offset, count);
215206

216207
[Obsolete("This method produces non-standards-compliant output and has interoperability issues. The preferred alternative is UrlEncode(String).")]
217208
[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
@@ -386,64 +386,37 @@ internal static string JavaScriptStringEncode(string? value)
386386
}
387387

388388
[return: NotNullIfNotNull(nameof(bytes))]
389-
internal static byte[]? UrlEncode(byte[]? bytes, int offset, int count, bool alwaysCreateNewReturnValue)
390-
{
391-
byte[]? encoded = UrlEncode(bytes, offset, count);
392-
393-
return (alwaysCreateNewReturnValue && (encoded != null) && (encoded == bytes))
394-
? (byte[])encoded.Clone()
395-
: encoded;
396-
}
397-
398-
[return: NotNullIfNotNull(nameof(bytes))]
399-
private static byte[]? UrlEncode(byte[]? bytes, int offset, int count)
389+
internal static byte[]? UrlEncode(byte[]? bytes, int offset, int count)
400390
{
401391
if (!ValidateUrlEncodingParameters(bytes, offset, count))
402392
{
403393
return null;
404394
}
405395

406-
int cSpaces = 0;
407-
int cUnsafe = 0;
408-
409-
// count them first
410-
for (int i = 0; i < count; i++)
411-
{
412-
char ch = (char)bytes[offset + i];
413-
414-
if (ch == ' ')
415-
{
416-
cSpaces++;
417-
}
418-
else if (!HttpEncoderUtility.IsUrlSafeChar(ch))
419-
{
420-
cUnsafe++;
421-
}
422-
}
396+
return UrlEncode(bytes.AsSpan(offset, count));
397+
}
423398

399+
private static byte[] UrlEncode(ReadOnlySpan<byte> bytes)
400+
{
424401
// nothing to expand?
425-
if (cSpaces == 0 && cUnsafe == 0)
402+
if (!IsExpandingNeeded(bytes, out int cUnsafe))
426403
{
427-
// DevDiv 912606: respect "offset" and "count"
428-
if (0 == offset && bytes.Length == count)
429-
{
430-
return bytes;
431-
}
432-
else
433-
{
434-
byte[] subarray = new byte[count];
435-
Buffer.BlockCopy(bytes, offset, subarray, 0, count);
436-
return subarray;
437-
}
404+
return bytes.ToArray();
438405
}
439406

407+
return UrlEncode(bytes, cUnsafe);
408+
}
409+
410+
private static byte[] UrlEncode(ReadOnlySpan<byte> bytes, int cUnsafe)
411+
{
412+
int count = bytes.Length;
440413
// expand not 'safe' characters into %XX, spaces to +s
441414
byte[] expandedBytes = new byte[count + cUnsafe * 2];
442415
int pos = 0;
443416

444417
for (int i = 0; i < count; i++)
445418
{
446-
byte b = bytes[offset + i];
419+
byte b = bytes[i];
447420
char ch = (char)b;
448421

449422
if (HttpEncoderUtility.IsUrlSafeChar(ch))
@@ -465,6 +438,51 @@ internal static string JavaScriptStringEncode(string? value)
465438
return expandedBytes;
466439
}
467440

441+
private static bool IsExpandingNeeded(ReadOnlySpan<byte> bytes, out int cUnsafe)
442+
{
443+
cUnsafe = 0;
444+
445+
int cSpaces = 0;
446+
int count = bytes.Length;
447+
for (int i = 0; i < count; i++)
448+
{
449+
char ch = (char)bytes[i];
450+
451+
if (ch == ' ')
452+
{
453+
cSpaces++;
454+
}
455+
else if (!HttpEncoderUtility.IsUrlSafeChar(ch))
456+
{
457+
cUnsafe++;
458+
}
459+
}
460+
461+
return cSpaces != 0 || cUnsafe != 0;
462+
}
463+
464+
internal static byte[] UrlEncode(string str, Encoding e)
465+
{
466+
const int StackallocThreshold = 512;
467+
468+
if (e.GetMaxByteCount(str.Length) <= StackallocThreshold)
469+
{
470+
Span<byte> byteSpan = stackalloc byte[StackallocThreshold];
471+
int encodedBytes = e.GetBytes(str, byteSpan);
472+
473+
return UrlEncode(byteSpan.Slice(0, encodedBytes));
474+
}
475+
476+
byte[] bytes = e.GetBytes(str);
477+
if (!IsExpandingNeeded(bytes, out int cUnsafe))
478+
{
479+
// return encoded byte[] if nothing to expand
480+
return bytes;
481+
}
482+
483+
return UrlEncode(bytes.AsSpan(0, bytes.Length), cUnsafe);
484+
}
485+
468486
// Helper to encode the non-ASCII url characters only
469487
private static string UrlEncodeNonAscii(string str, Encoding e)
470488
{

0 commit comments

Comments
 (0)