[wasm][http] Add support for Blob URLs (#42111)
authorKenneth Pouncey <kjpou@pt.lu>
Fri, 18 Sep 2020 17:28:02 +0000 (19:28 +0200)
committerGitHub <noreply@github.com>
Fri, 18 Sep 2020 17:28:02 +0000 (12:28 -0500)
* [wasm][http] Add support for Blob URLs

* Modify the string to read `Only 'http' 'https' and 'blob' schemes are allowed.` for browser

* Add more tests

* Add blob Uri marshal test

* Address review comments to add comments within code about intentions.

* Multiple review comments.

- Split Fact into theory instead of specific test for blob.
- Split large test into multiple tests as well as mark it Theory where appropriate.

* Rename

* Add message to other validations as well

* Change name

* Update src/libraries/System.Net.Http/src/Resources/Strings.resx

Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Create partial classes for HttpUtilities to replace the proliferating use of TARGETS_BROWSER in the sources.

* Define and call `HttpUtilities.InvalidUriMessage` to provide the invalid message to be thrown.

* Fix CI Build

* Rename modules to follow current standards

Co-authored-by: Stephen Toub <stoub@microsoft.com>
src/libraries/System.Net.Http/src/Resources/Strings.resx
src/libraries/System.Net.Http/src/System.Net.Http.csproj
src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/HttpUtilities.Browser.cs [new file with mode: 0644]
src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs
src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs
src/libraries/System.Net.Http/src/System/Net/Http/HttpUtilities.AnyOS.cs [new file with mode: 0644]
src/libraries/System.Net.Http/src/System/Net/Http/HttpUtilities.cs
src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj
src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/Http/HttpRequestMessageTest.cs

index 33bedff..f9ad9e4 100644 (file)
   <data name="net_http_client_http_baseaddress_required" xml:space="preserve">
     <value>Only 'http' and 'https' schemes are allowed.</value>
   </data>
+  <data name="net_http_client_http_browser_baseaddress_required" xml:space="preserve">
+    <value>Only 'http', 'https', and 'blob' schemes are allowed.</value>
+  </data>
   <data name="net_http_parser_invalid_base64_string" xml:space="preserve">
     <value>Value '{0}' is not a valid Base64 string. Error: {1}</value>
   </data>
index 039686d..1bdcb88 100644 (file)
     <Compile Include="System\Net\Http\SocketsHttpHandler\RedirectHandler.cs" />
     <Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpConnectionContext.cs" />
     <Compile Include="System\Net\Http\SocketsHttpHandler\SocketsHttpHandler.cs" />
+    <Compile Include="System\Net\Http\HttpUtilities.AnyOS.cs" />
     <Compile Include="System\Net\Http\SocketsHttpHandler\SystemProxyInfo.cs" />
     <Compile Include="$(CommonPath)System\Net\NTAuthentication.Common.cs"
              Link="Common\System\Net\NTAuthentication.Common.cs" />
     <Compile Include="System\Net\Http\BrowserHttpHandler\SystemProxyInfo.Browser.cs" />
     <Compile Include="System\Net\Http\BrowserHttpHandler\SocketsHttpHandler.cs" />
     <Compile Include="System\Net\Http\BrowserHttpHandler\BrowserHttpHandler.cs" />
+    <Compile Include="System\Net\Http\BrowserHttpHandler\HttpUtilities.Browser.cs" />
     <Compile Include="$(CommonPath)System\Net\Http\HttpHandlerDefaults.cs"
              Link="Common\System\Net\Http\HttpHandlerDefaults.cs" />
   </ItemGroup>
diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/HttpUtilities.Browser.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/HttpUtilities.Browser.cs
new file mode 100644 (file)
index 0000000..6e9b4b0
--- /dev/null
@@ -0,0 +1,22 @@
+// 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.Threading.Tasks;
+using System.Threading;
+
+namespace System.Net.Http
+{
+    internal static partial class HttpUtilities
+    {
+        internal static bool IsSupportedNonSecureScheme(string scheme) =>
+            string.Equals(scheme, "http", StringComparison.OrdinalIgnoreCase)
+            || IsBlobScheme(scheme)
+            || IsNonSecureWebSocketScheme(scheme);
+
+        internal static bool IsBlobScheme(string scheme) =>
+            string.Equals(scheme, "blob", StringComparison.OrdinalIgnoreCase);
+
+        internal static string InvalidUriMessage => SR.net_http_client_http_browser_baseaddress_required;
+    }
+}
index 8e34a9f..bb03dee 100644 (file)
@@ -945,7 +945,7 @@ namespace System.Net.Http
 
             if (!HttpUtilities.IsHttpUri(baseAddress))
             {
-                throw new ArgumentException(SR.net_http_client_http_baseaddress_required, parameterName);
+                throw new ArgumentException(HttpUtilities.InvalidUriMessage, parameterName);
             }
         }
 
index 388bf1b..eaa59cd 100644 (file)
@@ -103,7 +103,7 @@ namespace System.Net.Http
             {
                 if ((value != null) && (value.IsAbsoluteUri) && (!HttpUtilities.IsHttpUri(value)))
                 {
-                    throw new ArgumentException(SR.net_http_client_http_baseaddress_required, nameof(value));
+                    throw new ArgumentException(HttpUtilities.InvalidUriMessage, nameof(value));
                 }
                 CheckDisposed();
 
@@ -189,7 +189,7 @@ namespace System.Net.Http
             }
             if ((requestUri != null) && (requestUri.IsAbsoluteUri) && (!HttpUtilities.IsHttpUri(requestUri)))
             {
-                throw new ArgumentException(SR.net_http_client_http_baseaddress_required, nameof(requestUri));
+                throw new ArgumentException(HttpUtilities.InvalidUriMessage, nameof(requestUri));
             }
 
             _method = method;
diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpUtilities.AnyOS.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpUtilities.AnyOS.cs
new file mode 100644 (file)
index 0000000..afa2d7d
--- /dev/null
@@ -0,0 +1,18 @@
+// 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.Threading.Tasks;
+using System.Threading;
+
+namespace System.Net.Http
+{
+    internal static partial class HttpUtilities
+    {
+        internal static bool IsSupportedNonSecureScheme(string scheme) =>
+            string.Equals(scheme, "http", StringComparison.OrdinalIgnoreCase)
+            || IsNonSecureWebSocketScheme(scheme);
+
+        internal static string InvalidUriMessage => SR.net_http_client_http_baseaddress_required;
+    }
+}
index 53edcaa..e31e930 100644 (file)
@@ -7,7 +7,7 @@ using System.Threading;
 
 namespace System.Net.Http
 {
-    internal static class HttpUtilities
+    internal static partial class HttpUtilities
     {
         internal static Version DefaultRequestVersion => HttpVersion.Version11;
 
@@ -25,9 +25,6 @@ namespace System.Net.Http
             IsSupportedNonSecureScheme(scheme) ||
             IsSupportedSecureScheme(scheme);
 
-        internal static bool IsSupportedNonSecureScheme(string scheme) =>
-            string.Equals(scheme, "http", StringComparison.OrdinalIgnoreCase) || IsNonSecureWebSocketScheme(scheme);
-
         internal static bool IsSupportedSecureScheme(string scheme) =>
             string.Equals(scheme, "https", StringComparison.OrdinalIgnoreCase) || IsSecureWebSocketScheme(scheme);
 
index 0af91c2..c4dfd64 100644 (file)
              Link="ProductionCode\System\Net\Http\SocketsHttpHandler\SystemProxyInfo.Unix.cs" />
     <Compile Include="..\..\src\System\Net\Http\SocketsHttpHandler\SystemProxyInfo.Windows.cs" Condition=" '$(TargetsWindows)' == 'true' and '$(TargetFramework)' == '$(NetCoreAppCurrent)'"
              Link="ProductionCode\System\Net\Http\SocketsHttpHandler\SystemProxyInfo.Windows.cs" />
+    <Compile Include="..\..\src\System\Net\Http\HttpUtilities.AnyOS.cs"
+             Link="ProductionCode\System\Net\Http\HttpUtilities.AnyOS.cs" />
     <Compile Include="$(CommonPath)System\Net\Http\HttpHandlerDefaults.cs"
              Link="ProductionCode\System\Net\Http\HttpHandlerDefaults.cs" />
     <Compile Include="DigestAuthenticationTests.cs" />
index 192f11f..3c359f2 100644 (file)
@@ -332,5 +332,18 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         {
             _minValue = (int)_mathMinFunction.Apply(null, new object[] { 5, 6, 2, 3, 7 });
         }
+
+        internal static Uri _blobURL;
+        public static void SetBlobUrl(string blobUrl)
+        {
+            _blobURL = new Uri(blobUrl);
+        }
+
+        internal static Uri _blobURI;
+        public static void SetBlobAsUri(Uri blobUri)
+        {
+            _blobURI = blobUri;
+        }
+
     }
 }
index 80a8cd1..d3794ed 100644 (file)
@@ -6,6 +6,7 @@ using System.IO;
 using System.Net.Http.Headers;
 using System.Net.Http;
 using System.Net;
+using System.Runtime.InteropServices.JavaScript.Tests;
 using System.Threading.Tasks;
 
 using Xunit;
@@ -17,7 +18,7 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
     {
         private readonly Version _expectedRequestMessageVersion = HttpVersion.Version11;
         private HttpRequestOptionsKey<bool> EnableStreamingResponse = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse");
-#nullable enable        
+#nullable enable
         private HttpRequestOptionsKey<IDictionary<string, object?>> FetchOptions = new HttpRequestOptionsKey<IDictionary<string, object?>>("WebAssemblyFetchOptions");
 #nullable disable
 
@@ -42,15 +43,17 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
             Assert.Equal(new Uri("/relative", UriKind.Relative), rm.RequestUri);
         }
 
-        [Fact]
-        public void Ctor_AbsoluteStringUri_CorrectValues()
+        [Theory]
+        [InlineData("http://host/absolute/")]
+        [InlineData("blob:http://host/absolute/")]
+        public void Ctor_AbsoluteStringUri_CorrectValues(string uri)
         {
-            var rm = new HttpRequestMessage(HttpMethod.Post, "http://host/absolute/");
+            var rm = new HttpRequestMessage(HttpMethod.Post, uri);
 
             Assert.Equal(HttpMethod.Post, rm.Method);
             Assert.Equal(_expectedRequestMessageVersion, rm.Version);
             Assert.Null(rm.Content);
-            Assert.Equal(new Uri("http://host/absolute/"), rm.RequestUri);
+            Assert.Equal(new Uri(uri), rm.RequestUri);
         }
 
         [Fact]
@@ -76,10 +79,12 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
             Assert.Equal(uri, rm.RequestUri);
         }
 
-        [Fact]
-        public void Ctor_AbsoluteUri_CorrectValues()
+        [Theory]
+        [InlineData("http://host/absolute/")]
+        [InlineData("blob:http://host/absolute/")]
+        public void Ctor_AbsoluteUri_CorrectValues(string uriData)
         {
-            var uri = new Uri("http://host/absolute/");
+            var uri = new Uri(uriData);
             var rm = new HttpRequestMessage(HttpMethod.Post, uri);
 
             Assert.Equal(HttpMethod.Post, rm.Method);
@@ -99,10 +104,12 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
             Assert.Null(rm.Content);
         }
 
-        [Fact]
-        public void Ctor_NullMethod_ThrowsArgumentNullException()
+        [Theory]
+        [InlineData("http://example.com")]
+        [InlineData("blob:http://example.com")]
+        public void Ctor_NullMethod_ThrowsArgumentNullException(string uriData)
         {
-            Assert.Throws<ArgumentNullException>(() => new HttpRequestMessage(null, "http://example.com"));
+            Assert.Throws<ArgumentNullException>(() => new HttpRequestMessage(null, uriData));
         }
 
         [Fact]
@@ -111,10 +118,12 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
             AssertExtensions.Throws<ArgumentException>("requestUri", () => new HttpRequestMessage(HttpMethod.Put, "ftp://example.com"));
         }
 
-        [Fact]
-        public void Dispose_DisposeObject_ContentGetsDisposedAndSettersWillThrowButGettersStillWork()
+        [Theory]
+        [InlineData("http://example.com")]
+        [InlineData("blob:http://example.com")]
+        public void Dispose_DisposeObject_ContentGetsDisposedAndSettersWillThrowButGettersStillWork(string uriData)
         {
-            var rm = new HttpRequestMessage(HttpMethod.Get, "http://example.com");
+            var rm = new HttpRequestMessage(HttpMethod.Get, uriData);
             var content = new MockContent();
             rm.Content = content;
             Assert.False(content.IsDisposed);
@@ -130,18 +139,20 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
 
             // Property getters should still work after disposing.
             Assert.Equal(HttpMethod.Get, rm.Method);
-            Assert.Equal(new Uri("http://example.com"), rm.RequestUri);
+            Assert.Equal(new Uri(uriData), rm.RequestUri);
             Assert.Equal(_expectedRequestMessageVersion, rm.Version);
             Assert.Equal(content, rm.Content);
         }
 
-        [Fact]
-        public void Properties_SetOptionsAndGetTheirValue_MatchingValues()
+        [Theory]
+        [InlineData("https://example.com")]
+        [InlineData("blob:https://example.com")]
+        public void Properties_SetOptionsAndGetTheirValue_MatchingValues(string uriData)
         {
             var rm = new HttpRequestMessage();
 
             var content = new MockContent();
-            var uri = new Uri("https://example.com");
+            var uri = new Uri(uriData);
             var version = new Version(1, 0);
             var method = new HttpMethod("custom");
 
@@ -160,13 +171,15 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
         }
 
 #nullable enable
-        [Fact]
-        public void Properties_SetOptionsAndGetTheirValue_Set_FetchOptions()
+        [Theory]
+        [InlineData("https://example.com")]
+        [InlineData("blob:https://example.com")]
+        public void Properties_SetOptionsAndGetTheirValue_Set_FetchOptions(string uriData)
         {
             var rm = new HttpRequestMessage();
 
             var content = new MockContent();
-            var uri = new Uri("https://example.com");
+            var uri = new Uri(uriData);
             var version = new Version(1, 0);
             var method = new HttpMethod("custom");
 
@@ -174,7 +187,7 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
             rm.Method = method;
             rm.RequestUri = uri;
             rm.Version = version;
-            
+
             var fetchme = new Dictionary<string, object?>();
             fetchme.Add("hic", null);
             fetchme.Add("sunt", 4444);
@@ -202,13 +215,15 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
 #nullable disable
 
 #nullable enable
-        [Fact]
-        public void Properties_SetOptionsAndGetTheirValue_NotSet_FetchOptions()
+        [Theory]
+        [InlineData("https://example.com")]
+        [InlineData("blob:https://example.com")]
+        public void Properties_SetOptionsAndGetTheirValue_NotSet_FetchOptions(string uriData)
         {
             var rm = new HttpRequestMessage();
 
             var content = new MockContent();
-            var uri = new Uri("https://example.com");
+            var uri = new Uri(uriData);
             var version = new Version(1, 0);
             var method = new HttpMethod("custom");
 
@@ -229,14 +244,15 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
             Assert.Null(fetchOptionsValue);
         }
 #nullable disable
-
-        [Fact]
-        public void Properties_SetOptionsAndGetTheirValue_Set_EnableStreamingResponse()
+        [Theory]
+        [InlineData("https://example.com")]
+        [InlineData("blob:https://example.com")]
+        public void Properties_SetOptionsAndGetTheirValue_Set_EnableStreamingResponse(string uriData)
         {
             var rm = new HttpRequestMessage();
 
             var content = new MockContent();
-            var uri = new Uri("https://example.com");
+            var uri = new Uri(uriData);
             var version = new Version(1, 0);
             var method = new HttpMethod("custom");
 
@@ -244,7 +260,7 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
             rm.Method = method;
             rm.RequestUri = uri;
             rm.Version = version;
-            
+
             rm.Options.Set(EnableStreamingResponse, true);
 
             Assert.Equal(content, rm.Content);
@@ -259,13 +275,15 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
             Assert.True(streamingEnabledValue);
         }
 
-        [Fact]
-        public void Properties_SetOptionsAndGetTheirValue_NotSet_EnableStreamingResponse()
+        [Theory]
+        [InlineData("https://example.com")]
+        [InlineData("blob:https://example.com")]
+        public void Properties_SetOptionsAndGetTheirValue_NotSet_EnableStreamingResponse(string uriData)
         {
             var rm = new HttpRequestMessage();
 
             var content = new MockContent();
-            var uri = new Uri("https://example.com");
+            var uri = new Uri(uriData);
             var version = new Version(1, 0);
             var method = new HttpMethod("custom");
 
@@ -273,7 +291,7 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
             rm.Method = method;
             rm.RequestUri = uri;
             rm.Version = version;
-            
+
             Assert.Equal(content, rm.Content);
             Assert.Equal(uri, rm.RequestUri);
             Assert.Equal(method, rm.Method);
@@ -286,6 +304,7 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
             Assert.False(streamingEnabledValue);
         }
 
+
         [Fact]
         public void RequestUri_SetNonHttpUri_ThrowsArgumentException()
         {
@@ -308,7 +327,7 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
         }
 
         [Fact]
-        public void ToString_DefaultAndNonDefaultInstance_DumpAllFields()
+        public void ToString_DefaultInstance_DumpAllFields()
         {
             var rm = new HttpRequestMessage();
             string expected =
@@ -316,27 +335,45 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
                     _expectedRequestMessageVersion.ToString(2) +
                     $", Content: <null>, Headers:{Environment.NewLine}{{{Environment.NewLine}}}";
             Assert.Equal(expected, rm.ToString());
+        }
 
+        [Theory]
+        [InlineData("http://a.com/")]
+        [InlineData("blob:http://a.com/")]
+        public void ToString_NonDefaultInstanceWithNoCustomHeaders_DumpAllFields(string uriData)
+        {
+            var rm = new HttpRequestMessage();
             rm.Method = HttpMethod.Put;
-            rm.RequestUri = new Uri("http://a.com/");
+            rm.RequestUri = new Uri(uriData);
             rm.Version = new Version(1, 0);
             rm.Content = new StringContent("content");
 
             // Note that there is no Content-Length header: The reason is that the value for Content-Length header
             // doesn't get set by StringContent..ctor, but only if someone actually accesses the ContentLength property.
             Assert.Equal(
-                "Method: PUT, RequestUri: 'http://a.com/', Version: 1.0, Content: " + typeof(StringContent).ToString() + ", Headers:" + Environment.NewLine +
+                $"Method: PUT, RequestUri: '{uriData}', Version: 1.0, Content: " + typeof(StringContent).ToString() + ", Headers:" + Environment.NewLine +
                 $"{{{Environment.NewLine}" +
                 "  Content-Type: text/plain; charset=utf-8" + Environment.NewLine +
                 "}", rm.ToString());
+        }
 
+        [Theory]
+        [InlineData("http://a.com/")]
+        [InlineData("blob:http://a.com/")]
+        public void ToString_NonDefaultInstanceWithCustomHeaders_DumpAllFields(string uriData)
+        {
+            var rm = new HttpRequestMessage();
+            rm.Method = HttpMethod.Put;
+            rm.RequestUri = new Uri(uriData);
+            rm.Version = new Version(1, 0);
+            rm.Content = new StringContent("content");
             rm.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain", 0.2));
             rm.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/xml", 0.1));
             rm.Headers.Add("Custom-Request-Header", "value1");
             rm.Content.Headers.Add("Custom-Content-Header", "value2");
 
             Assert.Equal(
-                "Method: PUT, RequestUri: 'http://a.com/', Version: 1.0, Content: " + typeof(StringContent).ToString() + ", Headers:" + Environment.NewLine +
+                $"Method: PUT, RequestUri: '{uriData}', Version: 1.0, Content: " + typeof(StringContent).ToString() + ", Headers:" + Environment.NewLine +
                 "{" + Environment.NewLine +
                 "  Accept: text/plain; q=0.2" + Environment.NewLine +
                 "  Accept: text/xml; q=0.1" + Environment.NewLine +
@@ -346,6 +383,63 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
                 "}", rm.ToString());
         }
 
+        [Fact]
+        public void BlobStringUri_Marshal_CorrectValues()
+        {
+            Runtime.InvokeJS(@"
+                function typedArrayToURL(typedArray, mimeType) {
+                    // URL.createObjectURL does not work outside of browser but since this was actual
+                    // test code from https://developer.mozilla.org/en-US/docs/Web/API/Blob
+                    // left it in to show what this should do if the test code were to actually run
+                    //return URL.createObjectURL(new Blob([typedArray.buffer], {type: mimeType}))
+                    return 'blob:https://mdn.mozillademos.org/ca45b575-6348-4d3e-908a-3dbf3d146ea7';
+                }
+                const bytes = new Uint8Array(59);
+                for(let i = 0; i < 59; i++) {
+                    bytes[i] = 32 + i;
+                }
+                const url = typedArrayToURL(bytes, 'text/plain');
+                // Calls method with string that will be converted to a valid Uri
+                // within the method
+                App.call_test_method  (""SetBlobUrl"", [ url ]);
+            ");
+
+            var rm = new HttpRequestMessage(HttpMethod.Post, HelperMarshal._blobURL);
+
+            Assert.Equal(HttpMethod.Post, rm.Method);
+            Assert.Equal(_expectedRequestMessageVersion, rm.Version);
+            Assert.Null(rm.Content);
+            Assert.Equal(new Uri("blob:https://mdn.mozillademos.org/ca45b575-6348-4d3e-908a-3dbf3d146ea7"), rm.RequestUri);
+        }
+
+        [Fact]
+        public void BlobUri_Marshal_CorrectValues()
+        {
+            Runtime.InvokeJS(@"
+                function typedArrayToURL(typedArray, mimeType) {
+                    // URL.createObjectURL does not work outside of browser but since this was actual
+                    // test code from https://developer.mozilla.org/en-US/docs/Web/API/Blob
+                    // left it in to show what this should do if the test code were to actually run
+                    //return URL.createObjectURL(new Blob([typedArray.buffer], {type: mimeType}))
+                    return 'blob:https://mdn.mozillademos.org/ca45b575-6348-4d3e-908a-3dbf3d146ea7';
+                }
+                const bytes = new Uint8Array(59);
+                for(let i = 0; i < 59; i++) {
+                    bytes[i] = 32 + i;
+                }
+                const url = typedArrayToURL(bytes, 'text/plain');
+                // Calls method with string that will be marshaled as valid URI
+                App.call_test_method  (""SetBlobAsUri"", [ url ]);
+            ");
+
+            var rm = new HttpRequestMessage(HttpMethod.Post, HelperMarshal._blobURI);
+
+            Assert.Equal(HttpMethod.Post, rm.Method);
+            Assert.Equal(_expectedRequestMessageVersion, rm.Version);
+            Assert.Null(rm.Content);
+            Assert.Equal(new Uri("blob:https://mdn.mozillademos.org/ca45b575-6348-4d3e-908a-3dbf3d146ea7"), rm.RequestUri);
+        }
+
         #region Helper methods
 
         private class MockContent : HttpContent
@@ -360,7 +454,7 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests
 #nullable enable
             protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
             {
-#nullable disable                
+#nullable disable
                 throw new NotImplementedException();
             }