Support For Getting base-64 String as Byte Array (dotnet/corefx#41467)
authorAnton Landor <55381413+AntonLandor@users.noreply.github.com>
Tue, 12 Nov 2019 22:42:27 +0000 (23:42 +0100)
committerAhson Khan <ahson_ahmedk@yahoo.com>
Tue, 12 Nov 2019 22:42:27 +0000 (14:42 -0800)
* Implemented support for getting Base64 as an equivalent byte array

* New implementation for base-64 support as suggested in review. Added new test cases for it.

* Changed comment

in -> and

Co-Authored-By: Ahson Khan <ahkha@microsoft.com>
* Updated minimum length for TryGetBytesFromBase64 to match minimum length of base-64 string

Co-Authored-By: Günther Foidl <gue@korporal.at>
* Added old Convert.FromBase64String implementation for older platforms. arrayReturnToPool is now cleared before return. Added test cases for edge cases and full branch coverage. Changed magic number to predefined constant.

* Changed catch-all to only catch FormatException. Removed try-finally since it is most likely unnecessary. Now only clears the sliced buffer.

* Changed back to a try-finally solution for readability.

* Using JsonConstants.StackallocThreshold instead of magic number

Co-Authored-By: Ahson Khan <ahkha@microsoft.com>
* Added test to validate TryGetBytesFromBase64(...) gives the corrresulting bytes.

* Allocate to JsonConstants.StackallocThreshold when doing stackalloc

Co-Authored-By: Stephen Toub <stoub@microsoft.com>
Commit migrated from https://github.com/dotnet/corefx/commit/6a591b2d24869657094816418c46593337550a32

src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonString.cs
src/libraries/System.Text.Json/tests/JsonElementWithNodeParentTests.cs
src/libraries/System.Text.Json/tests/Resources/Strings.resx

index 07f6ccc..cfc7494 100644 (file)
@@ -495,9 +495,9 @@ namespace System.Text.Json
 
             var jsonNode = (JsonNode)_parent;
 
-            if (jsonNode is JsonString)
+            if (jsonNode is JsonString jsonString)
             {
-                throw new NotSupportedException();
+                return jsonString.TryGetBytesFromBase64(out value);
             }
 
             throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.String, jsonNode.ValueKind);
index 3025243..a896a82 100644 (file)
@@ -3,6 +3,8 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Globalization;
+using System.Buffers;
+using System.Diagnostics;
 
 namespace System.Text.Json
 {
@@ -208,5 +210,73 @@ namespace System.Text.Json
         ///   Returns <see cref="JsonValueKind.String"/>
         /// </summary>
         public override JsonValueKind ValueKind { get => JsonValueKind.String; }
+
+        /// <summary>
+        ///   Converts the text value of this instance, which should encode binary data as base-64 digits, to an equivalent 8-bit unsigned <see cref="byte"/> array.
+        ///   The return value indicates wether the conversion succeeded.
+        /// </summary>
+        /// <param name="value">
+        ///   When this method returns, contains the <see cref="byte"/> array equivalent of the text contained in this instance,
+        ///   if the conversion succeeded.
+        /// </param>
+        /// <returns>
+        ///   <see langword="true"/> if text was converted successfully; othwerwise returns <see langword="false"/>.
+        /// </returns>
+        internal bool TryGetBytesFromBase64(out byte[] value)
+        {
+            Debug.Assert(_value != null);
+
+            // Shortest length of a base-64 string is 4 characters.
+            if (_value.Length < 4)
+            {
+                value = default;
+                return false;
+            }
+
+#if BUILDING_INBOX_LIBRARY
+            // we decode string -> byte, so the resulting length will
+            // be /4 * 3 - padding. To be on the safe side, keep padding and slice later
+            int bufferSize = _value.Length / 4 * 3;
+
+            byte[] arrayToReturnToPool = null;
+            Span<byte> buffer = bufferSize <= JsonConstants.StackallocThreshold
+                ? stackalloc byte[JsonConstants.StackallocThreshold]
+                : arrayToReturnToPool = ArrayPool<byte>.Shared.Rent(bufferSize);
+            try
+            {
+                if (Convert.TryFromBase64String(_value, buffer, out int bytesWritten))
+                {
+                    buffer = buffer.Slice(0, bytesWritten);
+                    value = buffer.ToArray();
+                    return true;
+                }
+                else
+                {
+                    value = default;
+                    return false;
+                }
+            }
+            finally
+            {
+                if (arrayToReturnToPool != null)
+                {
+                    buffer.Clear();
+                    ArrayPool<byte>.Shared.Return(arrayToReturnToPool);
+                }
+            }
+
+#else
+            try
+            {
+                value = Convert.FromBase64String(_value);
+                return true;
+            }
+            catch (FormatException)
+            {
+                value = null;
+                return false;
+            }
+#endif
+        }
     }
 }
index d8f3d82..aeca65a 100644 (file)
@@ -122,8 +122,22 @@ namespace System.Text.Json.Tests
         [Fact]
         public static void TestBytesFromBase64()
         {
-            Assert.Throws<NotSupportedException>(() => new JsonString().AsJsonElement().GetBytesFromBase64());
+            string valueString = "value";
+            string valueBase64String = "dmFsdWU=";
+
+            Assert.Equal(Encoding.UTF8.GetBytes(valueString), new JsonString(valueBase64String).AsJsonElement().GetBytesFromBase64());
+            Assert.Equal(Encoding.UTF8.GetBytes(SR.LoremIpsum40Words), new JsonString(SR.LoremIpsum40WordsBase64).AsJsonElement().GetBytesFromBase64());
+
+            Assert.Throws<FormatException>(() => new JsonString("Not base-64").AsJsonElement().GetBytesFromBase64());
+            Assert.Throws<FormatException>(() => new JsonString("abc").AsJsonElement().GetBytesFromBase64());
+            Assert.Throws<FormatException>(() => new JsonString("").AsJsonElement().GetBytesFromBase64());
+            Assert.Throws<FormatException>(() => new JsonString().AsJsonElement().GetBytesFromBase64());
+
             Assert.Throws<InvalidOperationException>(() => new JsonBoolean().AsJsonElement().GetBytesFromBase64());
+
+            Assert.True(new JsonString(valueBase64String).AsJsonElement().TryGetBytesFromBase64(out byte[] buffer));
+            Assert.Equal(Encoding.UTF8.GetBytes(valueString), buffer);
+            Assert.False(new JsonString().AsJsonElement().TryGetBytesFromBase64(out _));
         }
 
         [Fact]
index 6133f4a..b94fff3 100644 (file)
@@ -1,4 +1,5 @@
-<root>
+<?xml version="1.0" encoding="utf-8"?>
+<root>
   <!-- 
     Microsoft ResX Schema 
     
@@ -20271,4 +20272,10 @@ tiline\"another\" String\\"],"str":"\"\""}</value>
   <data name="BufferWriterAdvancedTooFar" xml:space="preserve">
     <value>Cannot advance past the end of the buffer, which has a size of {0}.</value>
   </data>
-</root>
\ No newline at end of file
+  <data name="LoremIpsum40Words" xml:space="preserve">
+    <value>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur viverra neque at erat laoreet, at sagittis arcu efficitur. Ut pulvinar eros nec odio cursus eleifend. Maecenas viverra elementum porttitor. Nullam mi velit, malesuada commodo tristique eu, mollis a velit. Morbi.</value>
+  </data>
+  <data name="LoremIpsum40WordsBase64" xml:space="preserve">
+    <value>TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3VyYWJpdHVyIHZpdmVycmEgbmVxdWUgYXQgZXJhdCBsYW9yZWV0LCBhdCBzYWdpdHRpcyBhcmN1IGVmZmljaXR1ci4gVXQgcHVsdmluYXIgZXJvcyBuZWMgb2RpbyBjdXJzdXMgZWxlaWZlbmQuIE1hZWNlbmFzIHZpdmVycmEgZWxlbWVudHVtIHBvcnR0aXRvci4gTnVsbGFtIG1pIHZlbGl0LCBtYWxlc3VhZGEgY29tbW9kbyB0cmlzdGlxdWUgZXUsIG1vbGxpcyBhIHZlbGl0LiBNb3JiaS4=</value>
+  </data>
+</root>