From d6d8ced6454a043058c5c216ec606cd430b6193a Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 10 Feb 2023 20:39:54 +0100 Subject: [PATCH 01/13] Tweak AndroidMessageHandler behavior for WCF support Context: https://github.com/xamarin/xamarin-android/issues/7230 Context: https://github.com/dotnet/runtime/issues/80935 When a WCF application receives compressed content that is automatically decompressed in `AndroidMessageHandler`, it will most of the time fail to properly read the response, cutting reading of the decompressed content short. The reason for this is that when `AndroidMessageHandler` creates a wrapper decompression stream, it does not update the `Content-Length` to match the length of the decoded content, because it doesn't have a way to know what the length is without first reading the stream to the end, and that might prevent the end user to read the content. Additionally, I think the `Content-Length` header should reflect the **original** content length, for the end user to be able to interpret the response as it was sent. WCF, on the other hand, looks at the `Content-Length` header and, if found, it takes its value and reads only this many bytes from the content stream and no more - it will almost always result in short reads and failure to correctly interpret the response. I implemented this workaround which makes `AndroidMessageHandler` behave the same way as other handlers implemented in the BCL. What they do in this situation, is to remove the `Content-Length` header, making WCF read stream to the end. Additionally, the clients remove the compressed content encoding identifier from the `Content-Encoding` header. As a bonus, this commit also adds support for decompression of responses compressed with the `Brotli` compression (using the `br` encoding ID in the `Content-Encoding` header) --- .../AndroidMessageHandler.cs | 154 ++++++++++++++---- 1 file changed, 122 insertions(+), 32 deletions(-) diff --git a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs index 8971220b68b..7d42441c66e 100644 --- a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs +++ b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs @@ -69,11 +69,46 @@ sealed class RequestRedirectionState public bool MethodChanged; } + /// + /// Some requests require modification to the set of headers returned from the native client. + /// However, the headers collection in it is immutable, so we need to perform the adjustments + /// in CopyHeaders. This class describes the necessary operations. + /// + sealed class ContentState + { + public bool? RemoveContentLengthHeader; + + /// + /// If this is `true`, then `NewContentEncodingHeaderValue` is entirely ignored + /// + public bool? RemoveContentEncodingHeader; + + /// + /// New 'Content-Encoding' header value. Ignored if not null and empty. + /// + public List? NewContentEncodingHeaderValue; + + /// + /// Reset the class to values that indicate there's no action to take. MUST be + /// called BEFORE any of the class members are assigned values and AFTER the state + /// modification is applied + /// + public void Reset () + { + RemoveContentEncodingHeader = null; + RemoveContentLengthHeader = null; + NewContentEncodingHeaderValue = null; + } + } + internal const string LOG_APP = "monodroid-net"; const string GZIP_ENCODING = "gzip"; const string DEFLATE_ENCODING = "deflate"; + const string BROTLI_ENCODING = "br"; const string IDENTITY_ENCODING = "identity"; + const string ContentEncodingHeaderName = "Content-Encoding"; + const string ContentLengthHeaderName = "Content-Length"; static readonly IDictionary headerSeparators = new Dictionary { ["User-Agent"] = " ", @@ -82,9 +117,9 @@ sealed class RequestRedirectionState static readonly HashSet known_content_headers = new HashSet (StringComparer.OrdinalIgnoreCase) { "Allow", "Content-Disposition", - "Content-Encoding", + ContentEncodingHeaderName, "Content-Language", - "Content-Length", + ContentLengthHeaderName, "Content-Location", "Content-MD5", "Content-Range", @@ -571,6 +606,7 @@ internal Task WriteRequestContentToOutputInternal (HttpRequestMessage request, H CancellationTokenRegistration cancelRegistration = default (CancellationTokenRegistration); HttpStatusCode statusCode = HttpStatusCode.OK; Uri? connectionUri = null; + var contentState = new ContentState (); try { cancelRegistration = cancellationToken.Register (() => { @@ -608,13 +644,13 @@ internal Task WriteRequestContentToOutputInternal (HttpRequestMessage request, H if (!IsErrorStatusCode (statusCode)) { if (Logger.LogNet) Logger.Log (LogLevel.Info, LOG_APP, $"Reading..."); - ret.Content = GetContent (httpConnection, httpConnection.InputStream!); + ret.Content = GetContent (httpConnection, httpConnection.InputStream!, contentState); } else { if (Logger.LogNet) Logger.Log (LogLevel.Info, LOG_APP, $"Status code is {statusCode}, reading..."); // For 400 >= response code <= 599 the Java client throws the FileNotFound exception when attempting to read from the input stream. // Instead we try to read the error stream and return an empty string if the error stream isn't readable. - ret.Content = GetErrorContent (httpConnection, new StringContent (String.Empty, Encoding.ASCII)); + ret.Content = GetErrorContent (httpConnection, new StringContent (String.Empty, Encoding.ASCII), contentState); } bool disposeRet; @@ -633,7 +669,7 @@ internal Task WriteRequestContentToOutputInternal (HttpRequestMessage request, H } } - CopyHeaders (httpConnection, ret); + CopyHeaders (httpConnection, ret, contentState); ParseCookies (ret, connectionUri); if (disposeRet) { @@ -661,8 +697,8 @@ internal Task WriteRequestContentToOutputInternal (HttpRequestMessage request, H // We return the body of the response too, but the Java client will throw // a FileNotFound exception if we attempt to access the input stream. // Instead we try to read the error stream and return an default message if the error stream isn't readable. - ret.Content = GetErrorContent (httpConnection, new StringContent ("Unauthorized", Encoding.ASCII)); - CopyHeaders (httpConnection, ret); + ret.Content = GetErrorContent (httpConnection, new StringContent ("Unauthorized", Encoding.ASCII), contentState); + CopyHeaders (httpConnection, ret, contentState); if (ret.Headers.WwwAuthenticate != null) { ProxyAuthenticationRequested = false; @@ -676,7 +712,7 @@ internal Task WriteRequestContentToOutputInternal (HttpRequestMessage request, H return ret; } - CopyHeaders (httpConnection, ret); + CopyHeaders (httpConnection, ret, contentState); ParseCookies (ret, connectionUri); if (Logger.LogNet) @@ -684,29 +720,57 @@ internal Task WriteRequestContentToOutputInternal (HttpRequestMessage request, H return ret; } - HttpContent GetErrorContent (HttpURLConnection httpConnection, HttpContent fallbackContent) + HttpContent GetErrorContent (HttpURLConnection httpConnection, HttpContent fallbackContent, ContentState contentState) { var contentStream = httpConnection.ErrorStream; if (contentStream != null) { - return GetContent (httpConnection, contentStream); + return GetContent (httpConnection, contentStream, contentState); } return fallbackContent; } - HttpContent GetContent (URLConnection httpConnection, Stream contentStream) + Stream GetDecompressionWrapper (URLConnection httpConnection, Stream inputStream, ContentState contentState) { - Stream inputStream = new BufferedStream (contentStream); - if (decompress_here) { - var encodings = httpConnection.ContentEncoding?.Split (','); - if (encodings != null) { - if (encodings.Contains (GZIP_ENCODING, StringComparer.OrdinalIgnoreCase)) - inputStream = new GZipStream (inputStream, CompressionMode.Decompress); - else if (encodings.Contains (DEFLATE_ENCODING, StringComparer.OrdinalIgnoreCase)) - inputStream = new DeflateStream (inputStream, CompressionMode.Decompress); + contentState.Reset (); + if (!decompress_here || String.IsNullOrEmpty (httpConnection.ContentEncoding)) { + return inputStream; + } + + var encodings = new HashSet (httpConnection.ContentEncoding?.Split (','), StringComparer.OrdinalIgnoreCase); + Stream? ret = null; + string? supportedEncoding = null; + if (encodings.Contains (GZIP_ENCODING)) { + supportedEncoding = GZIP_ENCODING; + ret = new GZipStream (inputStream, CompressionMode.Decompress); + } else if (encodings.Contains (DEFLATE_ENCODING)) { + supportedEncoding = DEFLATE_ENCODING; + ret = new DeflateStream (inputStream, CompressionMode.Decompress); + } +#if NETCOREAPP + else if (encodings.Contains (BROTLI_ENCODING)) { + supportedEncoding = BROTLI_ENCODING; + ret = new BrotliStream (inputStream, CompressionMode.Decompress); + } +#endif + if (!String.IsNullOrEmpty (supportedEncoding)) { + contentState.RemoveContentLengthHeader = true; + + encodings.Remove (supportedEncoding!); + if (encodings.Count == 0) { + contentState.RemoveContentEncodingHeader = true; + } else { + contentState.NewContentEncodingHeaderValue = new List (encodings); } } + + return ret ?? inputStream; + } + + HttpContent GetContent (URLConnection httpConnection, Stream contentStream, ContentState contentState) + { + Stream inputStream = GetDecompressionWrapper (httpConnection, new BufferedStream (contentStream), contentState); return new StreamContent (inputStream); } @@ -881,9 +945,13 @@ void ParseCookies (AndroidHttpResponseMessage ret, Uri connectionUri) } } - void CopyHeaders (HttpURLConnection httpConnection, HttpResponseMessage response) + void CopyHeaders (HttpURLConnection httpConnection, HttpResponseMessage response, ContentState contentState) { var headers = httpConnection.HeaderFields; + bool removeContentLength = contentState.RemoveContentLengthHeader ?? false; + bool removeContentEncoding = contentState.RemoveContentEncodingHeader ?? false; + bool setNewContentEncodingValue = !removeContentEncoding && contentState.NewContentEncodingHeaderValue != null && contentState.NewContentEncodingHeaderValue.Count > 0; + foreach (var key in headers!.Keys) { if (key == null) // First header entry has null key, it corresponds to the response message continue; @@ -895,8 +963,25 @@ void CopyHeaders (HttpURLConnection httpConnection, HttpResponseMessage response } else { item_headers = response.Headers; } - item_headers.TryAddWithoutValidation (key, headers [key]); + + IEnumerable values = headers [key]; + if (removeContentLength && String.Compare (ContentLengthHeaderName, key, StringComparison.OrdinalIgnoreCase) == 0) { + removeContentLength = false; + continue; + } + + if ((removeContentEncoding || setNewContentEncodingValue) && String.Compare (ContentEncodingHeaderName, key, StringComparison.OrdinalIgnoreCase) == 0) { + if (removeContentEncoding) { + removeContentEncoding = false; + continue; + } + + setNewContentEncodingValue = false; + values = contentState.NewContentEncodingHeaderValue!; + } + item_headers.TryAddWithoutValidation (key, values); } + contentState.Reset (); } /// @@ -1006,19 +1091,24 @@ void AppendEncoding (string encoding, ref List ? list) List ? accept_encoding = null; decompress_here = false; - if ((AutomaticDecompression & DecompressionMethods.GZip) != 0) { - AppendEncoding (GZIP_ENCODING, ref accept_encoding); - decompress_here = true; - } - - if ((AutomaticDecompression & DecompressionMethods.Deflate) != 0) { - AppendEncoding (DEFLATE_ENCODING, ref accept_encoding); - decompress_here = true; - } - if (AutomaticDecompression == DecompressionMethods.None) { - accept_encoding?.Clear (); AppendEncoding (IDENTITY_ENCODING, ref accept_encoding); // Turns off compression for the Java client + } else { + if ((AutomaticDecompression & DecompressionMethods.GZip) != 0) { + AppendEncoding (GZIP_ENCODING, ref accept_encoding); + decompress_here = true; + } + + if ((AutomaticDecompression & DecompressionMethods.Deflate) != 0) { + AppendEncoding (DEFLATE_ENCODING, ref accept_encoding); + decompress_here = true; + } +#if NETCOREAPP + if ((AutomaticDecompression & DecompressionMethods.Brotli) != 0) { + AppendEncoding (BROTLI_ENCODING, ref accept_encoding); + decompress_here = true; + } +#endif } if (accept_encoding?.Count > 0) From cf3ec47b985915fb145292ea364c536225b030a2 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 13 Feb 2023 15:59:42 +0100 Subject: [PATCH 02/13] Add test for compression methods --- .../AndroidMessageHandlerTests.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index c15b8292c36..bf8474e6b2d 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -19,6 +19,22 @@ protected override HttpMessageHandler CreateHandler () return new AndroidMessageHandler (); } + // We can't test `deflate` for now because it's broken in the BCL for https://httpbin.org/deflate (S.I.Compression.DeflateStream doesn't recognize the compression + // method used by the server) + [Test] + public async Task Decompression ([Values ("gzip", "brotli")] urlPath) + { + var handler = new AndroidMessageHandler { + AutomaticDecompression = DecompressionMethods.All + }; + + var client = new HttpClient (handler); + string response = await client.GetStringAsync ($"https://httpbin.org/{urlPath}"); + + Assert.IsTrue (response.Length > 0, "Response was empty"); + Assert.IsTrue (response.Contains ($"\"{urlPath}\"", StringComparison.OrdinalIgnoreCase), $"\"{urlPath}\" should have been in the response JSON"); + } + [Test] public async Task ServerCertificateCustomValidationCallback_ApproveRequest () { From af2d3606e38552e48b23b894eba2b01acef4bd78 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 13 Feb 2023 17:08:15 +0100 Subject: [PATCH 03/13] Expand the decompression test + fix build --- .../AndroidMessageHandlerTests.cs | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index bf8474e6b2d..8eb0c2b2cab 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Net.Http; using System.Net.Security; using System.Security.Cryptography.X509Certificates; @@ -21,19 +22,44 @@ protected override HttpMessageHandler CreateHandler () // We can't test `deflate` for now because it's broken in the BCL for https://httpbin.org/deflate (S.I.Compression.DeflateStream doesn't recognize the compression // method used by the server) + static readonly object[] DecompressionSource = new object[] { + new object[] { + "gzip", // urlPath + "gzip", // encoding + }, + + new object[] { + "brotli", // urlPath + "br", // encoding + }, + }; + +#if NETCOREAPP [Test] - public async Task Decompression ([Values ("gzip", "brotli")] urlPath) + [TestCaseSource (nameof (DecompressionSource))] + public async Task Decompression (string urlPath, string encoding) { var handler = new AndroidMessageHandler { AutomaticDecompression = DecompressionMethods.All }; var client = new HttpClient (handler); - string response = await client.GetStringAsync ($"https://httpbin.org/{urlPath}"); + HttpResponseMessage response = await client.GetAsync ($"https://httpbin.org/{urlPath}"); - Assert.IsTrue (response.Length > 0, "Response was empty"); - Assert.IsTrue (response.Contains ($"\"{urlPath}\"", StringComparison.OrdinalIgnoreCase), $"\"{urlPath}\" should have been in the response JSON"); + Assert.IsNull (response.Content.Headers.ContentLength, "Content-Length header should have been removed"); + + foreach (string enc in response.Content.Headers.ContentEncoding) { + if (String.Compare (enc, encoding, StringComparison.Ordinal) == 0) { + Assert.Fail ($"Encoding '{encoding}' should have been removed from the Content-Encoding header"); + } + } + + string responseBody = await response.ReadAsStringAsync (); + + Assert.IsTrue (responseBody.Length > 0, "Response was empty"); + Assert.IsTrue (responseBody.Contains ($"\"{urlPath}\"", StringComparison.OrdinalIgnoreCase), $"\"{urlPath}\" should have been in the response JSON"); } +#endif [Test] public async Task ServerCertificateCustomValidationCallback_ApproveRequest () From 570d0d5dcf852da84b424bf5eca50d3ba51dfac7 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 13 Feb 2023 17:18:04 +0100 Subject: [PATCH 04/13] use `#if NET` instead --- .../Xamarin.Android.Net/AndroidMessageHandlerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index 8eb0c2b2cab..11e8a12515f 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -34,7 +34,7 @@ protected override HttpMessageHandler CreateHandler () }, }; -#if NETCOREAPP +#if NET [Test] [TestCaseSource (nameof (DecompressionSource))] public async Task Decompression (string urlPath, string encoding) From f6cf2c5d125072a1d6bc7b2c2feb7bc2cb44b5a1 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 13 Feb 2023 21:56:32 +0100 Subject: [PATCH 05/13] Update apkdesc --- .../BuildReleaseArm64XFormsDotNet.apkdesc | 115 +++++++++--------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index afcd83a9d39..c87d2d87170 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -5,136 +5,139 @@ "Size": 3568 }, "assemblies/_Microsoft.Android.Resource.Designer.dll": { - "Size": 1942 + "Size": 1940 }, "assemblies/FormsViewGroup.dll": { "Size": 7313 }, "assemblies/Java.Interop.dll": { - "Size": 66786 + "Size": 66792 }, "assemblies/Mono.Android.dll": { - "Size": 444703 + "Size": 445418 }, "assemblies/Mono.Android.Runtime.dll": { - "Size": 5895 + "Size": 5825 }, "assemblies/mscorlib.dll": { - "Size": 3857 + "Size": 3850 }, "assemblies/netstandard.dll": { - "Size": 5567 + "Size": 5562 }, "assemblies/rc.bin": { "Size": 1182 }, "assemblies/System.Collections.Concurrent.dll": { - "Size": 10529 + "Size": 10520 }, "assemblies/System.Collections.dll": { - "Size": 15258 + "Size": 15249 }, "assemblies/System.Collections.NonGeneric.dll": { - "Size": 7487 + "Size": 7479 }, "assemblies/System.ComponentModel.dll": { - "Size": 1964 + "Size": 1958 }, "assemblies/System.ComponentModel.Primitives.dll": { - "Size": 2579 + "Size": 2573 }, "assemblies/System.ComponentModel.TypeConverter.dll": { - "Size": 6069 + "Size": 6066 }, "assemblies/System.Console.dll": { - "Size": 6610 + "Size": 6601 }, "assemblies/System.Core.dll": { - "Size": 1982 + "Size": 1975 }, "assemblies/System.Diagnostics.TraceSource.dll": { - "Size": 6580 + "Size": 6576 }, "assemblies/System.dll": { - "Size": 2338 + "Size": 2330 }, "assemblies/System.Drawing.dll": { - "Size": 2023 + "Size": 2016 }, "assemblies/System.Drawing.Primitives.dll": { - "Size": 11996 + "Size": 11990 + }, + "assemblies/System.IO.Compression.Brotli.dll": { + "Size": 11852 }, "assemblies/System.IO.Compression.dll": { - "Size": 16847 + "Size": 16839 }, "assemblies/System.IO.IsolatedStorage.dll": { - "Size": 9957 + "Size": 9952 }, "assemblies/System.Linq.dll": { - "Size": 19441 + "Size": 19432 }, "assemblies/System.Linq.Expressions.dll": { - "Size": 164128 + "Size": 164124 }, "assemblies/System.Net.Http.dll": { - "Size": 65982 + "Size": 66104 }, "assemblies/System.Net.Primitives.dll": { - "Size": 22441 + "Size": 22437 }, "assemblies/System.Net.Requests.dll": { - "Size": 3620 + "Size": 3613 }, "assemblies/System.ObjectModel.dll": { - "Size": 8144 + "Size": 8139 }, "assemblies/System.Private.CoreLib.dll": { - "Size": 808708 + "Size": 810677 }, "assemblies/System.Private.DataContractSerialization.dll": { - "Size": 192356 + "Size": 192352 }, "assemblies/System.Private.Uri.dll": { - "Size": 42878 + "Size": 42872 }, "assemblies/System.Private.Xml.dll": { - "Size": 216193 + "Size": 216186 }, "assemblies/System.Private.Xml.Linq.dll": { - "Size": 16662 + "Size": 16658 }, "assemblies/System.Runtime.dll": { - "Size": 2763 + "Size": 2756 }, "assemblies/System.Runtime.InteropServices.dll": { - "Size": 3753 + "Size": 3743 }, "assemblies/System.Runtime.Serialization.dll": { - "Size": 1943 + "Size": 1936 }, "assemblies/System.Runtime.Serialization.Formatters.dll": { - "Size": 2509 + "Size": 2501 }, "assemblies/System.Runtime.Serialization.Primitives.dll": { - "Size": 3791 + "Size": 3785 }, "assemblies/System.Security.Cryptography.dll": { - "Size": 7766 + "Size": 7767 }, "assemblies/System.Text.RegularExpressions.dll": { - "Size": 156677 + "Size": 157070 }, "assemblies/System.Xml.dll": { - "Size": 1833 + "Size": 1826 }, "assemblies/System.Xml.Linq.dll": { - "Size": 1856 + "Size": 1849 }, "assemblies/UnnamedProject.dll": { "Size": 5294 }, "assemblies/Xamarin.AndroidX.Activity.dll": { - "Size": 5870 + "Size": 5867 }, "assemblies/Xamarin.AndroidX.AppCompat.AppCompatResources.dll": { "Size": 6117 @@ -146,7 +149,7 @@ "Size": 6592 }, "assemblies/Xamarin.AndroidX.CoordinatorLayout.dll": { - "Size": 16404 + "Size": 16408 }, "assemblies/Xamarin.AndroidX.Core.dll": { "Size": 96738 @@ -158,16 +161,16 @@ "Size": 39951 }, "assemblies/Xamarin.AndroidX.Legacy.Support.Core.UI.dll": { - "Size": 5923 + "Size": 5924 }, "assemblies/Xamarin.AndroidX.Lifecycle.Common.dll": { "Size": 6391 }, "assemblies/Xamarin.AndroidX.Lifecycle.LiveData.Core.dll": { - "Size": 6459 + "Size": 6462 }, "assemblies/Xamarin.AndroidX.Lifecycle.ViewModel.dll": { - "Size": 3067 + "Size": 3068 }, "assemblies/Xamarin.AndroidX.Loader.dll": { "Size": 12473 @@ -176,7 +179,7 @@ "Size": 84804 }, "assemblies/Xamarin.AndroidX.SavedState.dll": { - "Size": 4866 + "Size": 4870 }, "assemblies/Xamarin.AndroidX.SwipeRefreshLayout.dll": { "Size": 10389 @@ -188,10 +191,10 @@ "Size": 528450 }, "assemblies/Xamarin.Forms.Platform.Android.dll": { - "Size": 337828 + "Size": 337831 }, "assemblies/Xamarin.Forms.Platform.dll": { - "Size": 11084 + "Size": 11080 }, "assemblies/Xamarin.Forms.Xaml.dll": { "Size": 60774 @@ -200,16 +203,16 @@ "Size": 40158 }, "classes.dex": { - "Size": 3141008 + "Size": 3127152 }, "lib/arm64-v8a/libmono-component-marshal-ilgen.so": { "Size": 93552 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 379320 + "Size": 379008 }, "lib/arm64-v8a/libmonosgen-2.0.so": { - "Size": 3090760 + "Size": 3093016 }, "lib/arm64-v8a/libSystem.IO.Compression.Native.so": { "Size": 723840 @@ -218,10 +221,10 @@ "Size": 94328 }, "lib/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so": { - "Size": 155056 + "Size": 155136 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 333720 + "Size": 333840 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -335,13 +338,13 @@ "Size": 1213 }, "META-INF/BNDLTOOL.SF": { - "Size": 79326 + "Size": 79441 }, "META-INF/com.google.android.material_material.version": { "Size": 10 }, "META-INF/MANIFEST.MF": { - "Size": 79199 + "Size": 79314 }, "META-INF/proguard/androidx-annotations.pro": { "Size": 339 @@ -1976,5 +1979,5 @@ "Size": 341228 } }, - "PackageSize": 7828228 + "PackageSize": 7820125 } \ No newline at end of file From 3cd503a0c1a10fb2e67682ce5829bd32d9d1b9f8 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 17 Feb 2023 19:43:04 +0100 Subject: [PATCH 06/13] Fix build --- .../Xamarin.Android.Net/AndroidMessageHandlerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index 11e8a12515f..51222e50e41 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -54,7 +54,7 @@ public async Task Decompression (string urlPath, string encoding) } } - string responseBody = await response.ReadAsStringAsync (); + string responseBody = await response.Content.ReadAsStringAsync (); Assert.IsTrue (responseBody.Length > 0, "Response was empty"); Assert.IsTrue (responseBody.Contains ($"\"{urlPath}\"", StringComparison.OrdinalIgnoreCase), $"\"{urlPath}\" should have been in the response JSON"); From b2fd01e212a125e63c1ac46cb7ffc0e1855bc073 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 24 Feb 2023 16:39:04 +0100 Subject: [PATCH 07/13] Revert "Bump to dotnet/installer/main@d25a3bb 8.0.100-preview.2.23105.6 (#7769)" This reverts commit 6cd0d38989178e1c3e5e1d71505b4aef26a1739b. --- eng/Version.Details.xml | 18 +++++++++--------- eng/Versions.props | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 0533ad38c2d..33b4096ba6d 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,20 +1,20 @@ - + https://github.com/dotnet/installer - f05478ba9a4026e95306d3f8de59c70cfc0e60ce + dec120944450abb58bc07a2fcdae2f4383bfd6bf - - https://github.com/dotnet/runtime - fe4760cf04dee615948848955978e6d7430982a7 + + https://github.com/dotnet/linker + c790896f128957acd2999208f44f09ae1e826c8c - + https://github.com/dotnet/runtime - fe4760cf04dee615948848955978e6d7430982a7 + 9529803ae29c2804880c6bd8ca710b8c037cb498 - + https://github.com/dotnet/emsdk - fd5a0d1b19bf0a96f6b4423150921542b0b9a90d + 0fe864fc71191ff4ee18e59ef0af2929ca367a11 diff --git a/eng/Versions.props b/eng/Versions.props index febf67713f4..926781e3a0a 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,13 +1,13 @@ - 8.0.100-preview.2.23107.13 - 8.0.0-preview.2.23106.6 - 8.0.0-preview.2.23106.6 + 8.0.100-alpha.1.23080.11 + 8.0.100-1.23067.1 + 8.0.0-alpha.1.23080.2 7.0.0-beta.22103.1 7.0.0-beta.22103.1 - 8.0.0-preview.2.23081.1 - $(MicrosoftNETWorkloadEmscriptenCurrentManifest80100preview2Version) + 8.0.0-alpha.1.23077.4 + $(MicrosoftNETWorkloadEmscriptenCurrentManifest80100alpha1Version) 7.0.100-rc.1.22410.7 From 1c1dfce830b42f40aaa361d197da606f37e28d4f Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 24 Feb 2023 19:55:53 +0100 Subject: [PATCH 08/13] Cannot check whether Content-Length header was removed HttpClient adds it back, reflecting the uncompressed content length --- .../Xamarin.Android.Net/AndroidMessageHandlerTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index 51222e50e41..c4d3e86aa41 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -46,8 +46,6 @@ public async Task Decompression (string urlPath, string encoding) var client = new HttpClient (handler); HttpResponseMessage response = await client.GetAsync ($"https://httpbin.org/{urlPath}"); - Assert.IsNull (response.Content.Headers.ContentLength, "Content-Length header should have been removed"); - foreach (string enc in response.Content.Headers.ContentEncoding) { if (String.Compare (enc, encoding, StringComparison.Ordinal) == 0) { Assert.Fail ($"Encoding '{encoding}' should have been removed from the Content-Encoding header"); From e86954b48ae2ddb03d4dbf5cca50b77e1fe2efb1 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 27 Feb 2023 09:58:26 +0100 Subject: [PATCH 09/13] Update test --- .../Xamarin.Android.Net/AndroidMessageHandlerTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index c4d3e86aa41..88c1802a636 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -55,6 +55,7 @@ public async Task Decompression (string urlPath, string encoding) string responseBody = await response.Content.ReadAsStringAsync (); Assert.IsTrue (responseBody.Length > 0, "Response was empty"); + Assert.AreEqual (response.Content.Headers.ContentLength, response.Length, "Retrieved data length is different than the one specified in the Content-Length header"); Assert.IsTrue (responseBody.Contains ($"\"{urlPath}\"", StringComparison.OrdinalIgnoreCase), $"\"{urlPath}\" should have been in the response JSON"); } #endif From ddcfb045c394c9cad30ffc016edab2273f5d7cec Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 27 Feb 2023 11:06:37 +0100 Subject: [PATCH 10/13] Doh --- .../Xamarin.Android.Net/AndroidMessageHandlerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index 88c1802a636..20f70805327 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -55,7 +55,7 @@ public async Task Decompression (string urlPath, string encoding) string responseBody = await response.Content.ReadAsStringAsync (); Assert.IsTrue (responseBody.Length > 0, "Response was empty"); - Assert.AreEqual (response.Content.Headers.ContentLength, response.Length, "Retrieved data length is different than the one specified in the Content-Length header"); + Assert.AreEqual (response.Content.Headers.ContentLength, responseBody.Length, "Retrieved data length is different than the one specified in the Content-Length header"); Assert.IsTrue (responseBody.Contains ($"\"{urlPath}\"", StringComparison.OrdinalIgnoreCase), $"\"{urlPath}\" should have been in the response JSON"); } #endif From c0105500babdb64366c9601cc44f01b4f0beae8f Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 27 Feb 2023 15:44:09 +0100 Subject: [PATCH 11/13] Adjust the test parameters to match responses from httpbin.org --- .../Xamarin.Android.Net/AndroidMessageHandlerTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index 20f70805327..f58157a961a 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -26,18 +26,20 @@ protected override HttpMessageHandler CreateHandler () new object[] { "gzip", // urlPath "gzip", // encoding + "gzipped", // jsonFieldName }, new object[] { "brotli", // urlPath "br", // encoding + "brotli", // jsonFieldName }, }; #if NET [Test] [TestCaseSource (nameof (DecompressionSource))] - public async Task Decompression (string urlPath, string encoding) + public async Task Decompression (string urlPath, string encoding, string jsonFieldName) { var handler = new AndroidMessageHandler { AutomaticDecompression = DecompressionMethods.All @@ -56,7 +58,7 @@ public async Task Decompression (string urlPath, string encoding) Assert.IsTrue (responseBody.Length > 0, "Response was empty"); Assert.AreEqual (response.Content.Headers.ContentLength, responseBody.Length, "Retrieved data length is different than the one specified in the Content-Length header"); - Assert.IsTrue (responseBody.Contains ($"\"{urlPath}\"", StringComparison.OrdinalIgnoreCase), $"\"{urlPath}\" should have been in the response JSON"); + Assert.IsTrue (responseBody.Contains ($"\"{jsonFieldName}\"", StringComparison.OrdinalIgnoreCase), $"\"{jsonFieldName}\" should have been in the response JSON"); } #endif From 7b2e1728295d476f23d832e48640082c9f424aa0 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 28 Feb 2023 16:53:30 +0100 Subject: [PATCH 12/13] Tweak the test to retry up to 5 times and dump the retrieved content --- .../AndroidMessageHandlerTests.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index f58157a961a..c240ac94ccb 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -39,7 +39,20 @@ protected override HttpMessageHandler CreateHandler () #if NET [Test] [TestCaseSource (nameof (DecompressionSource))] + [Retry (5)] public async Task Decompression (string urlPath, string encoding, string jsonFieldName) + { + // Catch all the exceptions and warn about them or otherwise [Retry] above won't work + try { + DoDecompression (urlPath, encoding, jsonFieldName); + } catch (Exception ex) { + Assert.Warn ("Unexpected exception thrown"); + Assert.Warn (ex.ToString ()); + Assert.Fail ("Exception should have not been thrown"); + } + } + + void DoDecompression (string urlPath, string encoding, string jsonFieldName) { var handler = new AndroidMessageHandler { AutomaticDecompression = DecompressionMethods.All @@ -48,6 +61,14 @@ public async Task Decompression (string urlPath, string encoding, string jsonFie var client = new HttpClient (handler); HttpResponseMessage response = await client.GetAsync ($"https://httpbin.org/{urlPath}"); + // Failing on error codes other than 2xx will make NUnit retry the test up to the number of times specified in the + // [Retry] attribute above. This may or may not the desired effect if httpbin.org is throttling the requests, thus + // we will sleep a short while before failing the test + if (!response.IsSuccessStatusCode) { + System.Threading.Thread.Sleep (1000); + Assert.Fail ($"Request ended with a failure error code: {response.StatusCode}"); + } + foreach (string enc in response.Content.Headers.ContentEncoding) { if (String.Compare (enc, encoding, StringComparison.Ordinal) == 0) { Assert.Fail ($"Encoding '{encoding}' should have been removed from the Content-Encoding header"); @@ -56,6 +77,10 @@ public async Task Decompression (string urlPath, string encoding, string jsonFie string responseBody = await response.Content.ReadAsStringAsync (); + Assert.Warn ("-- Retrieved JSON start"); + Assert.Warn (responseBody); + Assert.Warn ("-- Retrieved JSON end"); + Assert.IsTrue (responseBody.Length > 0, "Response was empty"); Assert.AreEqual (response.Content.Headers.ContentLength, responseBody.Length, "Retrieved data length is different than the one specified in the Content-Length header"); Assert.IsTrue (responseBody.Contains ($"\"{jsonFieldName}\"", StringComparison.OrdinalIgnoreCase), $"\"{jsonFieldName}\" should have been in the response JSON"); From 36d0eb466206d906b01f26decce2c42fa244079f Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 28 Feb 2023 19:05:44 +0100 Subject: [PATCH 13/13] Update apkdesc --- .../BuildReleaseArm64XFormsDotNet.apkdesc | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index b3fc11fd0e4..6902b06169c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -11,13 +11,13 @@ "Size": 7313 }, "assemblies/Java.Interop.dll": { - "Size": 66563 + "Size": 66562 }, "assemblies/Mono.Android.dll": { - "Size": 444617 + "Size": 444972 }, "assemblies/Mono.Android.Runtime.dll": { - "Size": 5897 + "Size": 5822 }, "assemblies/mscorlib.dll": { "Size": 3866 @@ -64,6 +64,9 @@ "assemblies/System.Drawing.Primitives.dll": { "Size": 12010 }, + "assemblies/System.IO.Compression.Brotli.dll": { + "Size": 11871 + }, "assemblies/System.IO.Compression.dll": { "Size": 16858 }, @@ -89,7 +92,7 @@ "Size": 8154 }, "assemblies/System.Private.CoreLib.dll": { - "Size": 814216 + "Size": 814322 }, "assemblies/System.Private.DataContractSerialization.dll": { "Size": 192370 @@ -131,7 +134,7 @@ "Size": 1864 }, "assemblies/UnnamedProject.dll": { - "Size": 5294 + "Size": 5286 }, "assemblies/Xamarin.AndroidX.Activity.dll": { "Size": 5867 @@ -206,7 +209,7 @@ "Size": 93552 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 379152 + "Size": 380656 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3106808 @@ -221,7 +224,7 @@ "Size": 154904 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 333760 + "Size": 333840 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -335,13 +338,13 @@ "Size": 1213 }, "META-INF/BNDLTOOL.SF": { - "Size": 79326 + "Size": 79441 }, "META-INF/com.google.android.material_material.version": { "Size": 10 }, "META-INF/MANIFEST.MF": { - "Size": 79199 + "Size": 79314 }, "META-INF/proguard/androidx-annotations.pro": { "Size": 339 @@ -1976,5 +1979,5 @@ "Size": 341228 } }, - "PackageSize": 7820036 + "PackageSize": 7832413 } \ No newline at end of file