// This is copied from MAUI repo to be used from WebAuthenticator // https://github.com/dotnet/maui/blob/main/src/Essentials/src/Types/Shared/WebUtils.shared.cs #if IOS using System; using System.Collections.Generic; using System.Diagnostics; namespace Bit.Core.Utilities.MAUI { static class WebUtils { internal static IDictionary ParseQueryString(Uri uri) { var parameters = new Dictionary(StringComparer.Ordinal); if (uri == null) return parameters; // Note: Uri.Query starts with a '?' if (!string.IsNullOrEmpty(uri.Query)) UnpackParameters(uri.Query.AsSpan(1), parameters); // Note: Uri.Fragment starts with a '#' if (!string.IsNullOrEmpty(uri.Fragment)) UnpackParameters(uri.Fragment.AsSpan(1), parameters); return parameters; } // The following method is a port of the logic found in https://source.dot.net/#Microsoft.AspNetCore.WebUtilities/src/Shared/QueryStringEnumerable.cs // but refactored such that it: // // 1. avoids the IEnumerable overhead that isn't needed (the ASP.NET logic was clearly designed that way to offer a public API whereas we don't need that) // 2. avoids the use of unsafe code static void UnpackParameters(ReadOnlySpan query, Dictionary parameters) { while (!query.IsEmpty) { int delimeterIndex = query.IndexOf('&'); ReadOnlySpan segment; if (delimeterIndex >= 0) { segment = query.Slice(0, delimeterIndex); query = query.Slice(delimeterIndex + 1); } else { segment = query; query = default; } // If it's nonempty, emit it if (!segment.IsEmpty) { var equalIndex = segment.IndexOf('='); string name, value; if (equalIndex >= 0) { name = segment.Slice(0, equalIndex).ToString(); var span = segment.Slice(equalIndex + 1); var chars = new char[span.Length]; for (int i = 0; i < span.Length; i++) chars[i] = span[i] == '+' ? ' ' : span[i]; value = new string(chars); } else { name = segment.ToString(); value = string.Empty; } name = Uri.UnescapeDataString(name); parameters[name] = Uri.UnescapeDataString(value); } } } internal static Uri EscapeUri(Uri uri) { if (uri == null) throw new ArgumentNullException(nameof(uri)); var idn = new global::System.Globalization.IdnMapping(); return new Uri(uri.Scheme + "://" + idn.GetAscii(uri.Authority) + uri.PathAndQuery + uri.Fragment); } internal static bool CanHandleCallback(Uri expectedUrl, Uri callbackUrl) { if (!callbackUrl.Scheme.Equals(expectedUrl.Scheme, StringComparison.OrdinalIgnoreCase)) return false; if (!string.IsNullOrEmpty(expectedUrl.Host)) { if (!callbackUrl.Host.Equals(expectedUrl.Host, StringComparison.OrdinalIgnoreCase)) return false; } return true; } #if __IOS__ || __TVOS__ || __MACOS__ internal static Foundation.NSUrl GetNativeUrl(Uri uri) { try { return new Foundation.NSUrl(uri.OriginalString); } catch (Exception ex) { Debug.WriteLine($"Unable to create NSUrl from Original string, trying Absolute URI: {ex.Message}"); return new Foundation.NSUrl(uri.AbsoluteUri); } } #endif } } #endif