Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,62 @@ private static byte[] GetContentByteArray(IEnumerable<KeyValuePair<string?, stri
ArgumentNullException.ThrowIfNull(nameValueCollection);

// Encode and concatenate data
StringBuilder builder = new StringBuilder();
var builder = new ValueStringBuilder(stackalloc char[256]);

foreach (KeyValuePair<string?, string?> pair in nameValueCollection)
{
if (builder.Length > 0)
{
builder.Append('&');
}

builder.Append(Encode(pair.Key));
Encode(ref builder, pair.Key);
builder.Append('=');
builder.Append(Encode(pair.Value));
Encode(ref builder, pair.Value);
}

return HttpRuleParser.DefaultHttpEncoding.GetBytes(builder.ToString());
// EscapeDataString will always return an ASCII string and DefaultHttpEncoding is Latin1,
// so we know the output byte size will be the same as the builder length.
byte[] bytes = new byte[builder.Length];
HttpRuleParser.DefaultHttpEncoding.GetBytes(builder.AsSpan(), bytes);
builder.Dispose();
return bytes;
}

private static string Encode(string? data)
private static void Encode(ref ValueStringBuilder builder, string? data)
{
if (string.IsNullOrEmpty(data))
if (!string.IsNullOrEmpty(data))
{
return string.Empty;
int charsWritten;
while (!Uri.TryEscapeDataString(data, builder.RawChars.Slice(builder.Length), out charsWritten))
{
builder.EnsureCapacity(builder.Capacity + 1);
}

// Escape spaces as '+'.
if (data.Contains(' '))
{
ReadOnlySpan<char> escapedChars = builder.RawChars.Slice(builder.Length, charsWritten);

while (true)
{
int indexOfEscapedSpace = escapedChars.IndexOf("%20", StringComparison.Ordinal);
if (indexOfEscapedSpace < 0)
{
builder.Append(escapedChars);
break;
}

builder.Append(escapedChars.Slice(0, indexOfEscapedSpace));
builder.Append('+');
escapedChars = escapedChars.Slice(indexOfEscapedSpace + 3); // Skip "%20"
}
}
else
{
builder.Length += charsWritten;
}
}
// Escape spaces as '+'.
return Uri.EscapeDataString(data).Replace("%20", "+");
}

protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,16 +170,9 @@ private HttpEnvironmentProxy(Uri? httpProxy, Uri? httpsProxy, string? bypassList
int separatorIndex = value.LastIndexOf('@');
if (separatorIndex != -1)
{
string auth = value.Substring(0, separatorIndex);

// The User and password may or may not be URL encoded.
// Curl seems to accept both. To match that,
// we do opportunistic decode and we use original string if it fails.
try
{
auth = Uri.UnescapeDataString(auth);
}
catch { };
// Curl seems to accept both. To match that, we also decode the value.
string auth = Uri.UnescapeDataString(value.AsSpan(0, separatorIndex));

value = value.Substring(separatorIndex + 1);
separatorIndex = auth.IndexOf(':');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,17 +495,17 @@ internal FtpWebRequest(Uri uri)
NetworkCredential? networkCredential = null;
_uri = uri;
_methodInfo = FtpMethodInfo.GetMethodInfo(WebRequestMethods.Ftp.DownloadFile);
if (!string.IsNullOrEmpty(_uri.UserInfo))

if (_uri.UserInfo is { Length: > 0 } userInfo)
{
string userInfo = _uri.UserInfo;
string username = userInfo;
string password = "";
int index = userInfo.IndexOf(':');
if (index != -1)
{
username = Uri.UnescapeDataString(userInfo.Substring(0, index));
username = Uri.UnescapeDataString(userInfo.AsSpan(0, index));
index++; // skip ':'
password = Uri.UnescapeDataString(userInfo.Substring(index));
password = Uri.UnescapeDataString(userInfo.AsSpan(index));
}
networkCredential = new NetworkCredential(username, password);
}
Expand Down
134 changes: 124 additions & 10 deletions src/libraries/System.Private.Uri/src/System/UriExt.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

namespace System
Expand Down Expand Up @@ -552,28 +554,122 @@ internal unsafe bool InternalIsWellFormedOriginalString()
return true;
}

/// <summary>Converts a string to its unescaped representation.</summary>
/// <param name="stringToUnescape">The string to unescape.</param>
/// <returns>The unescaped representation of <paramref name="stringToUnescape"/>.</returns>
public static string UnescapeDataString(string stringToUnescape)
{
ArgumentNullException.ThrowIfNull(stringToUnescape);

if (stringToUnescape.Length == 0)
return string.Empty;
return UnescapeDataString(stringToUnescape, stringToUnescape);
}

int position = stringToUnescape.IndexOf('%');
if (position == -1)
return stringToUnescape;
/// <summary>Converts a span to its unescaped representation.</summary>
/// <param name="charsToUnescape">The span to unescape.</param>
/// <returns>The unescaped representation of <paramref name="charsToUnescape"/>.</returns>
public static string UnescapeDataString(ReadOnlySpan<char> charsToUnescape)
{
return UnescapeDataString(charsToUnescape, backingString: null);
}

private static string UnescapeDataString(ReadOnlySpan<char> charsToUnescape, string? backingString = null)
{
Debug.Assert(backingString is null || backingString.Length == charsToUnescape.Length);

int indexOfFirstToUnescape = charsToUnescape.IndexOf('%');
if (indexOfFirstToUnescape < 0)
{
// Nothing to unescape, just return the original value.
return backingString ?? charsToUnescape.ToString();
}

var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
vsb.EnsureCapacity(stringToUnescape.Length);

vsb.Append(stringToUnescape.AsSpan(0, position));
// We may throw for very large inputs (when growing the ValueStringBuilder).
vsb.EnsureCapacity(charsToUnescape.Length - indexOfFirstToUnescape);

UriHelper.UnescapeString(
stringToUnescape, position, stringToUnescape.Length, ref vsb,
charsToUnescape.Slice(indexOfFirstToUnescape), ref vsb,
c_DummyChar, c_DummyChar, c_DummyChar,
UnescapeMode.Unescape | UnescapeMode.UnescapeAll,
syntax: null, isQuery: false);

return vsb.ToString();
string result = string.Concat(charsToUnescape.Slice(0, indexOfFirstToUnescape), vsb.AsSpan());
vsb.Dispose();
return result;
}

/// <summary>Attempts to convert a span to its unescaped representation.</summary>
/// <param name="charsToUnescape">The span to unescape.</param>
/// <param name="destination">The output span that contains the unescaped result of the operation.</param>
/// <param name="charsWritten">When this method returns, contains the number of chars that were written into <paramref name="destination"/>.</param>
/// <returns><see langword="true"/> if the <paramref name="destination"/> was large enough to hold the entire result; otherwise, <see langword="false"/>.</returns>
public static bool TryUnescapeDataString(ReadOnlySpan<char> charsToUnescape, Span<char> destination, out int charsWritten)
{
int indexOfFirstToUnescape = charsToUnescape.IndexOf('%');
if (indexOfFirstToUnescape < 0)
{
// Nothing to unescape, just copy the original chars.
if (charsToUnescape.TryCopyTo(destination))
{
charsWritten = charsToUnescape.Length;
return true;
}

charsWritten = 0;
return false;
}

// We may throw for very large inputs (when growing the ValueStringBuilder).
scoped ValueStringBuilder vsb;

// If the input and destination buffers overlap, we must take care not to overwrite parts of the input before we've processed it.
// If the buffers start at the same location, we can still use the destination as the output length is strictly <= input length.
bool overlapped = charsToUnescape.Overlaps(destination) &&
!Unsafe.AreSame(ref MemoryMarshal.GetReference(charsToUnescape), ref MemoryMarshal.GetReference(destination));

if (overlapped)
{
vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
vsb.EnsureCapacity(charsToUnescape.Length - indexOfFirstToUnescape);
}
else
{
vsb = new ValueStringBuilder(destination.Slice(indexOfFirstToUnescape));
}

UriHelper.UnescapeString(
charsToUnescape.Slice(indexOfFirstToUnescape), ref vsb,
c_DummyChar, c_DummyChar, c_DummyChar,
UnescapeMode.Unescape | UnescapeMode.UnescapeAll,
syntax: null, isQuery: false);

int newLength = indexOfFirstToUnescape + vsb.Length;
Debug.Assert(newLength <= charsToUnescape.Length);

if (destination.Length >= newLength)
{
charsToUnescape.Slice(0, indexOfFirstToUnescape).CopyTo(destination);

if (overlapped)
{
vsb.AsSpan().CopyTo(destination.Slice(indexOfFirstToUnescape));
vsb.Dispose();
}
else
{
// We are expecting the builder not to grow if the original span was large enough.
// This means that we MUST NOT over allocate anywhere in UnescapeString (e.g. append and then decrease the length).
Debug.Assert(vsb.RawChars.Overlaps(destination));
}

charsWritten = newLength;
return true;
}

vsb.Dispose();
charsWritten = 0;
return false;
}

// Where stringToEscape is intended to be a completely unescaped URI string.
Expand All @@ -584,9 +680,27 @@ public static string EscapeUriString(string stringToEscape) =>

// Where stringToEscape is intended to be URI data, but not an entire URI.
// This method will escape any character that is not an unreserved character, including percent signs.

/// <summary>Converts a string to its escaped representation.</summary>
/// <param name="stringToEscape">The string to escape.</param>
/// <returns>The escaped representation of <paramref name="stringToEscape"/>.</returns>
public static string EscapeDataString(string stringToEscape) =>
UriHelper.EscapeString(stringToEscape, checkExistingEscaped: false, UriHelper.Unreserved);

/// <summary>Converts a span to its escaped representation.</summary>
/// <param name="charsToEscape">The span to escape.</param>
/// <returns>The escaped representation of <paramref name="charsToEscape"/>.</returns>
public static string EscapeDataString(ReadOnlySpan<char> charsToEscape) =>
UriHelper.EscapeString(charsToEscape, checkExistingEscaped: false, UriHelper.Unreserved, backingString: null);

/// <summary>Attempts to convert a span to its escaped representation.</summary>
/// <param name="charsToEscape">The span to escape.</param>
/// <param name="destination">The output span that contains the escaped result of the operation.</param>
/// <param name="charsWritten">When this method returns, contains the number of chars that were written into <paramref name="destination"/>.</param>
/// <returns><see langword="true"/> if the <paramref name="destination"/> was large enough to hold the entire result; otherwise, <see langword="false"/>.</returns>
public static bool TryEscapeDataString(ReadOnlySpan<char> charsToEscape, Span<char> destination, out int charsWritten) =>
UriHelper.TryEscapeDataString(charsToEscape, destination, out charsWritten);

//
// Cleans up the specified component according to Iri rules
// a) Chars allowed by iri in a component are unescaped if found escaped
Expand Down
Loading