From 89b758478eef37ad4cbb5e18dce4089e9e44fd74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 23 Nov 2022 01:14:36 +0000 Subject: [PATCH] Work in HttpUtility class (#370) ***NO_CI*** --- .runsettings | 28 +-- Tests/HttpUnitTests/HttpUnitTests.nfproj | 30 +-- Tests/HttpUnitTests/HttpUtilityTest.cs | 118 ++++++++++ Tests/HttpUnitTests/packages.config | 2 - Tests/HttpUnitTests/packages.lock.json | 12 - .../Http/System.Net.HttpUtility.cs | 215 +++++++++++------- 6 files changed, 277 insertions(+), 128 deletions(-) create mode 100644 Tests/HttpUnitTests/HttpUtilityTest.cs diff --git a/.runsettings b/.runsettings index e82b99e..d2930fe 100644 --- a/.runsettings +++ b/.runsettings @@ -1,15 +1,17 @@ - - - 1 - .\TestResults - 120000 - net48 - x64 - - - None - False - - \ No newline at end of file + + + 1 + .\TestResults + + 60000 + + net48 + x64 + + + Detailed + False + + diff --git a/Tests/HttpUnitTests/HttpUnitTests.nfproj b/Tests/HttpUnitTests/HttpUnitTests.nfproj index 1dcaa8c..f619dac 100644 --- a/Tests/HttpUnitTests/HttpUnitTests.nfproj +++ b/Tests/HttpUnitTests/HttpUnitTests.nfproj @@ -26,6 +26,7 @@ + @@ -39,41 +40,28 @@ - + + + + ..\..\packages\nanoFramework.CoreLibrary.1.12.0\lib\mscorlib.dll - True - - - ..\..\packages\nanoFramework.System.Collections.1.4.0\lib\nanoFramework.System.Collections.dll - True - + ..\..\packages\nanoFramework.System.Text.1.2.22\lib\nanoFramework.System.Text.dll - True - + ..\..\packages\nanoFramework.TestFramework.2.0.60\lib\nanoFramework.TestFramework.dll - True - + ..\..\packages\nanoFramework.TestFramework.2.0.60\lib\nanoFramework.UnitTestLauncher.exe - True - + ..\..\packages\nanoFramework.System.IO.Streams.1.1.27\lib\System.IO.Streams.dll - True - - - ..\..\packages\nanoFramework.System.Threading.1.1.8\lib\System.Threading.dll - True - - - diff --git a/Tests/HttpUnitTests/HttpUtilityTest.cs b/Tests/HttpUnitTests/HttpUtilityTest.cs new file mode 100644 index 0000000..9eb8e76 --- /dev/null +++ b/Tests/HttpUnitTests/HttpUtilityTest.cs @@ -0,0 +1,118 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +using nanoFramework.TestFramework; +using System; +using System.IO; +using System.Net.Http; +using System.Reflection; +using System.Text; +using System.Web; + +namespace HttpUnitTests +{ + [TestClass] + public class HttpUtilityTest + { + + [TestMethod] + public void UrlDecodeNoThrow() + { + string str = "../../&param2=%CURRREV%"; + + Assert.Equal(str, HttpUtility.UrlDecode(str)); + } + + [TestMethod] + public void UrlEncodeTest() + { + for (char c = char.MinValue; c < char.MaxValue; c++) + { + byte[] bIn; + bIn = Encoding.UTF8.GetBytes(c.ToString()); + MemoryStream expected = new MemoryStream(); + MemoryStream expUnicode = new MemoryStream(); + + // build expected result for UrlEncode + for (int i = 0; i < bIn.Length; i++) + { + UrlEncodeChar((char)bIn[i], expected, false); + } + + // build expected result for UrlEncodeUnicode + UrlEncodeChar(c, expUnicode, true); + + byte[] bOut = expected.ToArray(); + + Assert.Equal( + Encoding.UTF8.GetString(bOut, 0, bOut.Length), + HttpUtility.UrlEncode(c.ToString()), + $"Expecting UrlEncode of '{c}' ({(int)c}) as [{Encoding.UTF8.GetString(bOut, 0, bOut.Length)}] got {HttpUtility.UrlEncode(c.ToString())}"); + } + } + + static void UrlEncodeChar(char c, Stream result, bool isUnicode) + { + if (c > 255) + { + int idx; + int i = (int)c; + + result.WriteByte((byte)'%'); + result.WriteByte((byte)'u'); + idx = i >> 12; + result.WriteByte((byte)hexChars[idx]); + idx = (i >> 8) & 0x0F; + result.WriteByte((byte)hexChars[idx]); + idx = (i >> 4) & 0x0F; + result.WriteByte((byte)hexChars[idx]); + idx = i & 0x0F; + result.WriteByte((byte)hexChars[idx]); + return; + } + + if (c > ' ' && notEncoded.IndexOf(c) != -1) + { + result.WriteByte((byte)c); + return; + } + if (c == ' ') + { + result.WriteByte((byte)'+'); + return; + } + if ((c < '0') || + (c < 'A' && c > '9') || + (c > 'Z' && c < 'a') || + (c > 'z')) + { + if (isUnicode && c > 127) + { + result.WriteByte((byte)'%'); + result.WriteByte((byte)'u'); + result.WriteByte((byte)'0'); + result.WriteByte((byte)'0'); + } + else + { + result.WriteByte((byte)'%'); + } + + int idx = ((int)c) >> 4; + result.WriteByte((byte)hexChars[idx]); + idx = c & 0x0F; + result.WriteByte((byte)hexChars[idx]); + } + else + { + result.WriteByte((byte)c); + } + } + + static char[] hexChars = "0123456789ABCDEF".ToCharArray(); + const string notEncoded = "!()*-._"; + } +} diff --git a/Tests/HttpUnitTests/packages.config b/Tests/HttpUnitTests/packages.config index 8ca66f8..11f9424 100644 --- a/Tests/HttpUnitTests/packages.config +++ b/Tests/HttpUnitTests/packages.config @@ -1,9 +1,7 @@  - - \ No newline at end of file diff --git a/Tests/HttpUnitTests/packages.lock.json b/Tests/HttpUnitTests/packages.lock.json index 2cd88a4..ce6a574 100644 --- a/Tests/HttpUnitTests/packages.lock.json +++ b/Tests/HttpUnitTests/packages.lock.json @@ -8,12 +8,6 @@ "resolved": "1.12.0", "contentHash": "qQrFNXmJiStMC4VXk5cVMOJp23/qlT9FW5i9i+igwQVwraQTtvpkam8yK1hj992jqrbjoCIFZP4Hw9E8H0pB7w==" }, - "nanoFramework.System.Collections": { - "type": "Direct", - "requested": "[1.4.0, 1.4.0]", - "resolved": "1.4.0", - "contentHash": "/yFwxtCFzi+24NuyxcwlH1YyBGOxRX4oHGLwVmFbgbvOyx3ny/Mwyk2YjHTzmTSgUg9C2XxPF+EkXWwCOAkytw==" - }, "nanoFramework.System.IO.Streams": { "type": "Direct", "requested": "[1.1.27, 1.1.27]", @@ -26,12 +20,6 @@ "resolved": "1.2.22", "contentHash": "vLvU0II3oJfajQ8MgNm8aCkaQ2JhjznzruwksOorbMJf86zLRbA5NUeg9X/KjbAE5pIalitUOqtNLKorYTbYGg==" }, - "nanoFramework.System.Threading": { - "type": "Direct", - "requested": "[1.1.8, 1.1.8]", - "resolved": "1.1.8", - "contentHash": "oES5GN3KHoDzifRNr06WM7P9NaQf+kDmIYkr1ETR2awmERHz4sRpECduGEatwyo1vMhMvZY/KoBcEpAyKNbDgQ==" - }, "nanoFramework.TestFramework": { "type": "Direct", "requested": "[2.0.60, 2.0.60]", diff --git a/nanoFramework.System.Net.Http/Http/System.Net.HttpUtility.cs b/nanoFramework.System.Net.Http/Http/System.Net.HttpUtility.cs index 4bf5760..9e27e17 100644 --- a/nanoFramework.System.Net.Http/Http/System.Net.HttpUtility.cs +++ b/nanoFramework.System.Net.Http/Http/System.Net.HttpUtility.cs @@ -4,21 +4,20 @@ // See LICENSE file in the project root for full license information. // -using System; using System.Text; namespace System.Web { /// - /// Utilities to encode and decode url + /// Utilities to encode and decode URLs. /// public class HttpUtility { /// - /// Encode an URL using UTF8 + /// Encodes a URL string. /// - /// The URL string to encode - /// The encoded string URL + /// The text to encode. + /// An encoded string. public static string UrlEncode(string str) { if ((str == null) || (str == string.Empty)) @@ -30,12 +29,14 @@ public static string UrlEncode(string str) } /// - /// Encode an URL + /// Encodes a URL string using the specified encoding object. /// - /// The URL string to encode - /// The Encoding object that specifies the encoding scheme - /// The encoded string URL - public static string UrlEncode(string str, Encoding e) + /// The text to encode. + /// The object that specifies the encoding scheme. + /// An encoded string. + public static string UrlEncode( + string str, + Encoding e) { if ((str == null) || (str == string.Empty)) { @@ -46,63 +47,81 @@ public static string UrlEncode(string str, Encoding e) } /// - /// Encode an URL + /// Converts a byte array into a URL-encoded string, starting at the specified position in the array and continuing for the specified number of bytes. /// - /// The array of bytes to encode - /// The position in the byte array at which to begin encoding - /// The number of bytes to encode - /// The encoded string URL - public static string UrlEncode(byte[] bytes, int offset, int count) + /// The array of bytes to encode. + /// The position in the byte array at which to begin encoding. + /// The number of bytes to encode. + /// An encoded string. + public static string UrlEncode( + byte[] bytes, + int offset, + int count) { - return new string(Encoding.UTF8.GetChars(UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false))); + return new string(Encoding.UTF8.GetChars(UrlEncodeBytesToBytesInternal(bytes, offset, count, false))); } /// - /// Encode an URL + /// Converts a byte array into an encoded URL string. /// - /// The array of bytes to encode - /// The encoded string URLL + /// The array of bytes to encode. + /// An encoded string. public static string UrlEncode(byte[] bytes) => UrlEncode(bytes, 0, bytes.Length); /// - /// Encode an URL into a byte array + /// Converts a string into a URL-encoded array of bytes using the specified encoding object. /// - /// The URL string to encode - /// - /// The encoded byte array + /// The string to encode. + /// The that specifies the encoding scheme. + /// An encoded array of bytes. public static byte[] UrlEncodeToBytes(string str, Encoding e) { if (str == null) { return null; } + var bytes = e.GetBytes(str); + return UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false); } /// - /// Encode a byte array URL into a byte array + /// Converts a URL-encoded array of bytes into a decoded array of bytes. /// /// The array of bytes to encode - /// The encoded byte array + /// A decoded array of bytes. public static byte[] UrlEncodeToBytes(byte[] bytes) => UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false); /// - /// Encode a byte array URL into a byte array + /// Converts an array of bytes into a URL-encoded array of bytes, starting at the specified position in the array and continuing for the specified number of bytes. /// /// The array of bytes to encode /// The position in the byte array at which to begin encoding /// The number of bytes to encode - /// The encoded byte array - public static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count) => UrlEncodeBytesToBytesInternal(bytes, offset, count, false); + /// An encoded array of bytes. + public static byte[] UrlEncodeToBytes( + byte[] bytes, + int offset, + int count) => UrlEncodeBytesToBytesInternal( + bytes, + offset, + count, + false); - private static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue) + private static byte[] UrlEncodeBytesToBytesInternal( + byte[] bytes, + int offset, + int count, + bool alwaysCreateReturnValue) { var num = 0; var num2 = 0; + for (var i = 0; i < count; i++) { var ch = (char)bytes[offset + i]; + if (ch == ' ') { num++; @@ -112,17 +131,20 @@ private static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, in num2++; } } - if ((!alwaysCreateReturnValue && (num == 0)) && - (num2 == 0)) + if ((!alwaysCreateReturnValue && (num == 0)) + && (num2 == 0)) { return bytes; } + var buffer = new byte[count + (num2 * 2)]; var num4 = 0; + for (var j = 0; j < count; j++) { var num6 = bytes[offset + j]; var ch2 = (char)num6; + if (IsSafe(ch2)) { buffer[num4++] = num6; @@ -138,6 +160,7 @@ private static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, in buffer[num4++] = (byte)IntToHex(num6 & 15); } } + return buffer; } @@ -153,11 +176,12 @@ private static char IntToHex(int n) private static bool IsSafe(char ch) { - if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) || - ((ch >= '0') && (ch <= '9'))) + if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) + || ((ch >= '0') && (ch <= '9'))) { return true; } + switch (ch) { case '(': @@ -173,27 +197,28 @@ private static bool IsSafe(char ch) } /// - /// Decode an UTF8 encoded URL + /// Converts a string that has been encoded for transmission in a URL into a decoded string. /// - /// The encoded URL + /// The string to decode./param> /// The decoded URL - public static string UrlDecode(string url) + public static string UrlDecode(string str) { - if ((url == null) || (url == string.Empty)) + if (string.IsNullOrEmpty(str)) { return string.Empty; } - var data = Encoding.UTF8.GetBytes(url); + var data = Encoding.UTF8.GetBytes(str); + return new string(Encoding.UTF8.GetChars(UrlDecodeToBytes(data, 0, data.Length))); } /// - /// Decode an URL using the specified encoding + /// Converts a URL-encoded string into a decoded string, using the specified encoding object. /// - /// The string to encode - /// The Encoding object that specifies the encoding scheme - /// The encoded string + /// The string to decode. + /// The that specifies the decoding scheme. + /// A decoded string. public static string UrlDecode(string str, Encoding e) { if ((str == null) || (str == string.Empty)) @@ -206,49 +231,67 @@ public static string UrlDecode(string str, Encoding e) } /// - /// Decode an encoded URL + /// Converts a URL-encoded byte array into a decoded string using the specified encoding object, starting at the specified position in the array, and continuing for the specified number of bytes. /// - /// The array of bytes to decode + /// The array of bytes to decode. /// The position in the byte to begin decoding - /// The number of bytes to decode - /// The Encoding object that specifies the encoding scheme - /// The decoded URL - public static string UrlDecode(byte[] bytes, int offset, int count, System.Text.Encoding e) => new string(e.GetChars(UrlDecodeToBytes(bytes, 0, bytes.Length))); + /// The number of bytes to decode. + /// The object that specifies the encoding scheme. + /// A decoded string. + public static string UrlDecode( + byte[] bytes, + int offset, + int count, + Encoding e) => new(e.GetChars(UrlDecodeToBytes( + bytes, + offset, + count))); /// - /// Decode an encoded URL + /// Converts a URL-encoded byte array into a decoded string using the specified decoding object. /// - /// The array of bytes to decode - /// The Encoding object that specifies the encoding scheme - /// The decoded URL - public static string UrlDecode(byte[] bytes, System.Text.Encoding e) => new string(e.GetChars(UrlDecodeToBytes(bytes, 0, bytes.Length))); + /// The array of bytes to decode. + /// The object that specifies the encoding scheme. + /// A decoded string. + public static string UrlDecode( + byte[] bytes, + Encoding e) => new string(e.GetChars(UrlDecodeToBytes( + bytes, + 0, + bytes.Length))); /// - /// Decode bytes array to byte array + /// Converts a URL-encoded array of bytes into a decoded array of bytes, starting at the specified position in the array and continuing for the specified number of bytes. /// - /// The array of bytes to decode - /// The position in the byte array at which to begin decoding - /// The number of bytes to decode - /// - public static byte[] UrlDecodeToBytes(byte[] bytes, int offset, int count) + /// The array of bytes to decode. + /// The position in the byte array at which to begin decoding. + /// The number of bytes to decode. + /// A decoded array of bytes. + public static byte[] UrlDecodeToBytes( + byte[] bytes, + int offset, + int count) { var length = 0; var sourceArray = new byte[count]; + for (var i = 0; i < count; i++) { var index = offset + i; var num4 = bytes[index]; + if (num4 == 0x2b) { num4 = 0x20; } - else if ((num4 == 0x25) && - (i < (count - 2))) + else if ((num4 == 0x25) + && (i < (count - 2))) { var num5 = HexToInt((char)bytes[index + 1]); var num6 = HexToInt((char)bytes[index + 2]); - if ((num5 >= 0) && - (num6 >= 0)) + + if ((num5 >= 0) + && (num6 >= 0)) { num4 = (byte)((num5 << 4) | num6); i += 2; @@ -256,46 +299,55 @@ public static byte[] UrlDecodeToBytes(byte[] bytes, int offset, int count) } sourceArray[length++] = num4; } + if (length < sourceArray.Length) { var destinationArray = new byte[length]; Array.Copy(sourceArray, destinationArray, length); sourceArray = destinationArray; } + return sourceArray; } /// - /// Decode bytes array to byte array + /// Converts a URL-encoded string into a decoded array of bytes using the specified decoding object. /// - /// The string to encode - /// The Encoding object that specifies the encoding scheme - /// - public static byte[] UrlDecodeToBytes(string str, System.Text.Encoding e) + /// The string to encode. + /// The object that specifies the encoding scheme. + /// A decoded array of bytes. + public static byte[] UrlDecodeToBytes( + string str, + Encoding e) { var data = e.GetBytes(str); return UrlDecodeToBytes(data, 0, data.Length); } /// - /// Decode bytes array to byte array in UTF8 + /// Converts a URL-encoded string into a decoded array of bytes. /// - /// The string to encode - /// - public static byte[] UrlDecodeToBytes(string str) => UrlDecodeToBytes(str, Encoding.UTF8); + /// The string to decode. + /// A decoded array of bytes. + public static byte[] UrlDecodeToBytes(string str) => UrlDecodeToBytes( + str, + Encoding.UTF8); /// - /// Decode bytes array to byte array + /// Converts a URL-encoded array of bytes into a decoded array of bytes. /// - /// The array of bytes to decode - /// - public static byte[] UrlDecodeToBytes(byte[] bytes) => UrlDecodeToBytes(bytes, 0, bytes.Length); + /// The array of bytes to decode. + /// A decoded array of bytes. + public static byte[] UrlDecodeToBytes(byte[] bytes) => UrlDecodeToBytes( + bytes, + 0, + bytes.Length); /// - /// Get the int value of a char + /// Get the int value of a char. /// - /// a char - /// The int value of the char + /// The to convert. + /// The value of the . public static int HexToInt(char h) { if ((h >= '0') && @@ -303,16 +355,19 @@ public static int HexToInt(char h) { return (h - '0'); } + if ((h >= 'a') && (h <= 'f')) { return ((h - 'a') + 10); } + if ((h >= 'A') && (h <= 'F')) { return ((h - 'A') + 10); } + return -1; } }