Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Fix netcore WebClient.UploadValuesAsync() UrlEncode behavior (#18933)
Browse files Browse the repository at this point in the history
* fix netcore Webclient UploadValuesAsync UrlEncode behavior

* Address PR feedback
  • Loading branch information
Caesar Chen authored and stephentoub committed Apr 27, 2017
1 parent e881f97 commit ee6845a
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 4 deletions.
99 changes: 97 additions & 2 deletions src/System.Net.WebClient/src/System/Net/WebClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -588,9 +588,9 @@ private byte[] GetValuesToUpload(NameValueCollection data)
foreach (string name in data.AllKeys)
{
values.Append(delimiter);
values.Append(WebUtility.UrlEncode(name));
values.Append(UrlEncode(name));
values.Append('=');
values.Append(WebUtility.UrlEncode(data[name]));
values.Append(UrlEncode(data[name]));
delimiter = "&";
}

Expand Down Expand Up @@ -1176,6 +1176,101 @@ private string MapToDefaultMethod(Uri address)
"STOR" :
"POST";
}

private static string UrlEncode(string str)
{
if (str == null)
return null;
byte[] bytes = Encoding.UTF8.GetBytes(str);
return Encoding.ASCII.GetString(UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false));
}

private static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue)
{
int cSpaces = 0;
int cUnsafe = 0;

// Count them first.
for (int i = 0; i < count; i++)
{
char ch = (char) bytes[offset + i];

if (ch == ' ')
{
cSpaces++;
}
else if (!IsSafe(ch))
{
cUnsafe++;
}
}

// If nothing to expand.
if (!alwaysCreateReturnValue && cSpaces == 0 && cUnsafe == 0)
return bytes;

// Expand not 'safe' characters into %XX, spaces to +.
byte[] expandedBytes = new byte[count + cUnsafe * 2];
int pos = 0;

for (int i = 0; i < count; i++)
{
byte b = bytes[offset+i];
char ch = (char) b;

if (IsSafe(ch))
{
expandedBytes[pos++] = b;
}
else if (ch == ' ')
{
expandedBytes[pos++] = (byte) '+';
}
else
{
expandedBytes[pos++] = (byte) '%';
expandedBytes[pos++] = (byte) IntToHex((b >> 4) & 0xf);
expandedBytes[pos++] = (byte) IntToHex(b & 0x0f);
}
}

return expandedBytes;
}

private static char IntToHex(int n)
{
Debug.Assert(n < 0x10);

if (n <= 9)
{
return(char)(n + (int)'0');
}

return(char)(n - 10 + (int)'a');
}

private static bool IsSafe(char ch)
{
if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9')
{
return true;
}

switch (ch)
{
case '-':
case '_':
case '.':
case '!':
case '*':
case '\'':
case '(':
case ')':
return true;
}

return false;
}

private void InvokeOperationCompleted(AsyncOperation asyncOp, SendOrPostCallback callback, AsyncCompletedEventArgs eventArgs)
{
Expand Down
10 changes: 8 additions & 2 deletions src/System.Net.WebClient/tests/WebClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,13 @@ public abstract class WebClientTestBase
"The Slings and Arrows of outrageous Fortune," +
"Or to take Arms against a Sea of troubles," +
"And by opposing end them:";

const string ExpectedTextAfterUrlEncode =
"To+be%2c+or+not+to+be%2c+that+is+the+question%3a" +
"Whether+'tis+Nobler+in+the+mind+to+suffer" +
"The+Slings+and+Arrows+of+outrageous+Fortune%2c" +
"Or+to+take+Arms+against+a+Sea+of+troubles%2c" +
"And+by+opposing+end+them%3a";

protected abstract bool IsAsync { get; }

Expand Down Expand Up @@ -688,15 +695,14 @@ public async Task UploadString_Success(Uri echoServer)
Assert.Contains(ExpectedText, result);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "dotnet/corefx #18674")] // Difference in behavior.
[OuterLoop("Networking test talking to remote server: issue #11345")]
[Theory]
[MemberData(nameof(EchoServers))]
public async Task UploadValues_Success(Uri echoServer)
{
var wc = new WebClient();
byte[] result = await UploadValuesAsync(wc, echoServer.ToString(), new NameValueCollection() { { "Data", ExpectedText } });
Assert.Contains(WebUtility.UrlEncode(ExpectedText), Encoding.UTF8.GetString(result));
Assert.Contains(ExpectedTextAfterUrlEncode, Encoding.UTF8.GetString(result));
}
}

Expand Down

0 comments on commit ee6845a

Please sign in to comment.