From cf6b06b1d36d545e37b00bf1a6311b7fff33ff4e Mon Sep 17 00:00:00 2001 From: Kenneth Pouncey Date: Fri, 18 Sep 2020 19:28:02 +0200 Subject: [PATCH] [wasm][http] Add support for Blob URLs (#42111) * [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 * 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 --- .../System.Net.Http/src/Resources/Strings.resx | 3 + .../System.Net.Http/src/System.Net.Http.csproj | 2 + .../BrowserHttpHandler/HttpUtilities.Browser.cs | 22 +++ .../src/System/Net/Http/HttpClient.cs | 2 +- .../src/System/Net/Http/HttpRequestMessage.cs | 4 +- .../src/System/Net/Http/HttpUtilities.AnyOS.cs | 18 +++ .../src/System/Net/Http/HttpUtilities.cs | 5 +- .../UnitTests/System.Net.Http.Unit.Tests.csproj | 2 + .../InteropServices/JavaScript/HelperMarshal.cs | 13 ++ .../JavaScript/Http/HttpRequestMessageTest.cs | 172 ++++++++++++++++----- 10 files changed, 197 insertions(+), 46 deletions(-) create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/HttpUtilities.Browser.cs create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/HttpUtilities.AnyOS.cs diff --git a/src/libraries/System.Net.Http/src/Resources/Strings.resx b/src/libraries/System.Net.Http/src/Resources/Strings.resx index 33bedff..f9ad9e4 100644 --- a/src/libraries/System.Net.Http/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Http/src/Resources/Strings.resx @@ -204,6 +204,9 @@ Only 'http' and 'https' schemes are allowed. + + Only 'http', 'https', and 'blob' schemes are allowed. + Value '{0}' is not a valid Base64 string. Error: {1} diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 039686d..1bdcb88 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -175,6 +175,7 @@ + @@ -675,6 +676,7 @@ + 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 index 0000000..6e9b4b0 --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/HttpUtilities.Browser.cs @@ -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; + } +} diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs index 8e34a9f..bb03dee 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs @@ -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); } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs index 388bf1b..eaa59cd 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs @@ -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 index 0000000..afa2d7d --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpUtilities.AnyOS.cs @@ -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; + } +} diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpUtilities.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpUtilities.cs index 53edcaa..e31e930 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpUtilities.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpUtilities.cs @@ -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); diff --git a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj index 0af91c2..c4dfd64 100644 --- a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj @@ -252,6 +252,8 @@ Link="ProductionCode\System\Net\Http\SocketsHttpHandler\SystemProxyInfo.Unix.cs" /> + diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs index 192f11f..3c359f2 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs @@ -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; + } + } } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/Http/HttpRequestMessageTest.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/Http/HttpRequestMessageTest.cs index 80a8cd1..d3794ed 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/Http/HttpRequestMessageTest.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/Http/HttpRequestMessageTest.cs @@ -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 EnableStreamingResponse = new HttpRequestOptionsKey("WebAssemblyEnableStreamingResponse"); -#nullable enable +#nullable enable private HttpRequestOptionsKey> FetchOptions = new HttpRequestOptionsKey>("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(() => new HttpRequestMessage(null, "http://example.com")); + Assert.Throws(() => new HttpRequestMessage(null, uriData)); } [Fact] @@ -111,10 +118,12 @@ namespace System.Runtime.InteropServices.JavaScript.Http.Tests AssertExtensions.Throws("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(); 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: , 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(); } -- 2.7.4