< Summary

Line coverage
9%
Covered lines: 299
Uncovered lines: 2962
Coverable lines: 3261
Total lines: 5852
Line coverage: 9.1%
Branch coverage
6%
Covered branches: 134
Total branches: 2140
Branch coverage: 6.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: .cctor()100%11100%
File 1: .ctor(...)100%1137.5%
File 1: DebugSetLeftCtor()100%11100%
File 1: AssertInvariants()40.9%222281.25%
File 1: DebugAssertInCtor()100%11100%
File 1: InterlockedSetFlags(...)0%220%
File 1: IriParsingStatic(...)50%22100%
File 1: NotAny(...)100%110%
File 1: InFact(...)100%11100%
File 1: StaticNotAny(...)100%11100%
File 1: StaticInFact(...)100%110%
File 1: EnsureUriInfo()50%44100%
File 1: EnsureParseRemaining()0%220%
File 1: EnsureHostString(...)0%660%
File 1: .ctor(...)0%220%
File 1: .ctor(...)0%220%
File 1: .ctor(...)100%110%
File 1: .ctor(...)100%110%
File 1: .ctor(...)0%220%
File 1: .ctor(...)0%440%
File 1: System.Runtime.Serialization.ISerializable.GetObjectData(...)100%110%
File 1: GetObjectData(...)0%220%
File 1: CreateUri(...)0%12120%
File 1: .ctor(...)0%12120%
File 1: GetCombinedString(...)0%16160%
File 1: GetException(...)0%12120%
File 1: StaticIsFile(...)100%110%
File 1: GetLocalPath()0%40400%
File 1: CheckHostName(...)34.61%262645.83%
File 1: GetLeftPart(...)0%11110%
File 1: HexEscape(...)100%110%
File 1: HexUnescape(...)0%660%
File 1: IsHexEncoding(...)0%660%
File 1: CheckSchemeName(...)0%440%
File 1: IsHexDigit(...)100%110%
File 1: FromHex(...)0%220%
File 1: GetHashCode()0%880%
File 1: ToString()0%660%
File 1: TryFormat(...)0%22220%
File 1: System.ISpanFormattable.TryFormat(...)100%110%
File 1: System.IFormattable.ToString(...)100%110%
File 1: op_Equality(...)0%660%
File 1: op_Inequality(...)0%660%
File 1: Equals(...)0%14140%
File 1: Equals(...)0%62620%
File 1: MakeRelativeUri(...)0%16160%
File 1: CheckForColonInFirstPathSegment(...)0%220%
File 1: InternalEscapeString(...)0%220%
File 1: ParseScheme(...)37.5%8848%
File 1: ParseMinimal()0%440%
File 1: PrivateParseMinimal()21.42%12612629.57%
File 1: CreateUriInfo(...)27.63%767637.39%
File 1: CreateHostString()0%34340%
File 1: CreateHostStringHelper(...)0%22220%
File 1: GetHostViaCustomSyntax()0%28280%
File 1: GetParts(...)100%110%
File 1: GetEscapedParts(...)0%16160%
File 1: GetUnescapedParts(...)0%14140%
File 1: RecreateParts(...)0%220%
File 1: TryRecreateParts(...)0%440%
File 1: RecreateParts(...)0%80800%
File 1: FormatQueryOrFragment(System.ReadOnlySpan`1<System.Char>,System.Text.ValueStringBuilder&,System.UInt16,System.UriFormat,System.Boolean)0%16160%
File 1: GetUriPartsFromUserString(...)0%1011010%
File 1: ParseRemaining()0%1241240%
File 1: ParseRemaining_RecreateNormalizedString()0%24240%
File 1: EscapeUnescapeIri(...)100%110%
File 1: ParseSchemeCheckImplicitFile(...)0%56560%
File 1: CheckSchemeSyntax(...)0%46460%
File 1: CheckAuthorityHelper(...)23.37%15415417.88%
File 1: CheckCanonical(...)0%1221220%
File 1: GetCanonicalPath(...)0%76760%
File 1: UnescapePathSlashesAndDots(...)0%14140%
File 1: Compress(...)100%110%
File 1: Compress(...)100%110%
File 1: CombineUri(...)0%88880%
File 1: PathDifference(...)0%26260%
File 1: MakeRelative(...)0%10100%
File 1: Canonicalize()100%110%
File 1: Parse()100%110%
File 1: Escape()100%110%
File 1: Unescape(...)100%110%
File 1: EscapeString(...)0%220%
File 1: CheckSecurity()100%110%
File 1: IsReservedCharacter(...)0%16160%
File 1: IsExcludedCharacter(...)0%28280%
File 1: IsBadFileSystemCharacter(...)0%28280%
File 2: CreateThis(...)0%220%
File 2: TryCreateThis(...)25.8%626228.91%
File 2: .cctor()100%11100%
File 2: CheckForUnicodeOrEscapedUnreserved(...)7.14%141420%
File 2: System.IParsable<System.Uri>.Parse(...)100%110%
File 2: System.IParsable<System.Uri>.TryParse(...)100%110%
File 2: TryCreate(...)100%11100%
File 2: TryCreate(...)100%110%
File 2: TryCreate(...)0%440%
File 2: TryCreate(...)0%16160%
File 2: GetComponents(...)0%440%
File 2: InternalGetComponents(...)0%12120%
File 2: Compare(...)0%14140%
File 2: IsWellFormedOriginalString()0%440%
File 2: IsWellFormedUriString(...)0%220%
File 2: InternalIsWellFormedOriginalString()0%52520%
File 2: UnescapeDataString(...)100%110%
File 2: UnescapeDataString(...)100%110%
File 2: UnescapeDataString(...)0%660%
File 2: TryUnescapeDataString(...)0%14140%
File 2: EscapeUriString(...)100%110%
File 2: EscapeDataString(...)100%110%
File 2: EscapeDataString(...)100%110%
File 2: TryEscapeDataString(...)100%110%
File 2: .ctor()100%11100%
File 2: CreateHelper(...)33.33%6653.84%
File 2: ResolveHelper(...)0%52520%
File 2: GetRelativeSerializationString(...)0%880%
File 2: GetComponentsHelper(...)0%32320%
File 2: IsBaseOf(...)0%440%
File 2: IsBaseOfHelper(...)0%16160%
File 2: CreateThisFromUri(...)0%14140%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.Uri/src/System/Uri.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Collections.Generic;
 6using System.ComponentModel;
 7using System.Diagnostics;
 8using System.Diagnostics.CodeAnalysis;
 9using System.Globalization;
 10using System.Net;
 11using System.Runtime.CompilerServices;
 12using System.Runtime.Serialization;
 13using System.Text;
 14using System.Threading;
 15
 16namespace System
 17{
 18    [Serializable]
 19    [TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
 20    public partial class Uri : ISpanFormattable, IEquatable<Uri>, IParsable<Uri>, ISerializable
 21    {
 122        public static readonly string UriSchemeFile = UriParser.FileUri.SchemeName;
 123        public static readonly string UriSchemeFtp = UriParser.FtpUri.SchemeName;
 124        public static readonly string UriSchemeSftp = "sftp";
 125        public static readonly string UriSchemeFtps = "ftps";
 126        public static readonly string UriSchemeGopher = UriParser.GopherUri.SchemeName;
 127        public static readonly string UriSchemeHttp = UriParser.HttpUri.SchemeName;
 128        public static readonly string UriSchemeHttps = UriParser.HttpsUri.SchemeName;
 129        public static readonly string UriSchemeWs = UriParser.WsUri.SchemeName;
 130        public static readonly string UriSchemeWss = UriParser.WssUri.SchemeName;
 131        public static readonly string UriSchemeMailto = UriParser.MailToUri.SchemeName;
 132        public static readonly string UriSchemeNews = UriParser.NewsUri.SchemeName;
 133        public static readonly string UriSchemeNntp = UriParser.NntpUri.SchemeName;
 134        public static readonly string UriSchemeSsh = "ssh";
 135        public static readonly string UriSchemeTelnet = UriParser.TelnetUri.SchemeName;
 136        public static readonly string UriSchemeNetTcp = UriParser.NetTcpUri.SchemeName;
 137        public static readonly string UriSchemeNetPipe = UriParser.NetPipeUri.SchemeName;
 38        /// <summary>Specifies that the URI is a data URI.</summary>
 139        public static readonly string UriSchemeData = "data";
 140        public static readonly string SchemeDelimiter = "://";
 41
 42        private const int SchemeLengthLimit = 1024;
 43
 44        internal const int StackallocThreshold = 512;
 45
 46        // untouched user string unless string has unicode chars and iriparsing is enabled
 47        // or idn is on and we have unicode host or idn host
 48        // In that case, this string is normalized, stripped of bidi chars, and validated
 49        // with char limits
 50        private string _string;
 51
 52        // untouched user string if string has unicode with iri on or unicode/idn host with idn on
 66153        private string _originalUnicodeString = null!; // initialized in ctor via helper
 54
 66155        internal UriParser _syntax = null!;   // Initialized in ctor via helper. This is a whole Uri syntax, not only th
 56
 57        internal Flags _flags;
 66158        private UriInfo _info = null!;
 59
 60        [Flags]
 61        internal enum Flags : ulong
 62        {
 63            Zero = 0,
 64
 65            SchemeNotCanonical = 1UL << 0,
 66            UserNotCanonical = 1UL << 1,
 67            HostNotCanonical = 1UL << 2,
 68            PortNotCanonical = 1UL << 3,
 69            PathNotCanonical = 1UL << 4,
 70            QueryNotCanonical = 1UL << 5,
 71            FragmentNotCanonical = 1UL << 6,
 72            CannotDisplayCanonical = SchemeNotCanonical | UserNotCanonical | HostNotCanonical | PortNotCanonical | PathN
 73
 74            E_UserNotCanonical = 1UL << 7,
 75            E_HostNotCanonical = 1UL << 8,
 76            E_PortNotCanonical = 1UL << 9,
 77            E_PathNotCanonical = 1UL << 10,
 78            E_QueryNotCanonical = 1UL << 11,
 79            E_FragmentNotCanonical = 1UL << 12,
 80            E_CannotDisplayCanonical = E_UserNotCanonical | E_HostNotCanonical | E_PortNotCanonical | E_PathNotCanonical
 81
 82            ShouldBeCompressed = 1UL << 13,
 83            FirstSlashAbsent = 1UL << 14,
 84            BackslashInPath = 1UL << 15,
 85
 86            IndexMask = 0xFFFFFFFF, // 32 bits
 87
 88            HostTypeMask = 7 * (1UL << 32),
 89            HostNotParsed = 0,
 90            IPv6HostType = 1 * (1UL << 32),
 91            IPv4HostType = 2 * (1UL << 32),
 92            DnsHostType = 3 * (1UL << 32),
 93            UncHostType = 4 * (1UL << 32),
 94            BasicHostType = 5 * (1UL << 32),
 95            UnusedHostType = 6 * (1UL << 32),
 96            UnknownHostType = 7 * (1UL << 32),
 97
 98            UserEscaped = 1UL << 35,
 99            AuthorityFound = 1UL << 36,
 100            HasUserInfo = 1UL << 37,
 101            LoopbackHost = 1UL << 38,
 102            NotDefaultPort = 1UL << 39,
 103
 104            UserDrivenParsing = 1UL << 40,
 105            CanonicalDnsHost = 1UL << 41,
 106            ErrorOrParsingRecursion = 1UL << 42,    // Used to signal a default parser error and also to confirm Port
 107                                                    // and Host values in case of a custom user Parser
 108            DosPath = 1UL << 43,
 109            UncPath = 1UL << 44,
 110            ImplicitFile = 1UL << 45,
 111            MinimalUriInfoSet = 1UL << 46,
 112            AllUriInfoSet = 1UL << 47,
 113            IdnHost = 1UL << 48,
 114            HasUnicode = 1UL << 49,
 115
 116            // Is this component Iri canonical
 117            UserIriCanonical = 1UL << 50,
 118            PathIriCanonical = 1UL << 51,
 119            QueryIriCanonical = 1UL << 52,
 120            FragmentIriCanonical = 1UL << 53,
 121            IriCanonical = UserIriCanonical | PathIriCanonical | QueryIriCanonical | FragmentIriCanonical,
 122            UnixPath = 1UL << 54,
 123
 124            /// <summary>
 125            /// Disables any validation/normalization past the authority. Fragments will always be empty. GetComponents 
 126            /// </summary>
 127            DisablePathAndQueryCanonicalization = 1UL << 55,
 128
 129            /// <summary>
 130            /// Used to ensure that InitializeAndValidate is only called once per Uri instance and only from an override
 131            /// </summary>
 132            CustomParser_ParseMinimalAlreadyCalled = 1UL << 56,
 133
 134            /// <summary>
 135            /// Used for asserting that certain methods are only called from the constructor to validate thread-safety a
 136            /// </summary>
 137            Debug_LeftConstructor = 1UL << 57,
 138
 139            /// <summary>
 140            /// Indicates that there is an authority, but the scheme does not use "//" to delimit it (e.g. "http:\\host"
 141            /// </summary>
 142            SchemeNotCanonical_NoTrailingSlashes = 1UL << 58,
 143
 144            /// <summary>
 145            /// Disables recognizing absolute file paths as file URIs during initial scheme parsing.
 146            /// </summary>
 147            DisableImplicitFilePaths = 1UL << 59,
 148        }
 149
 150        [Conditional("DEBUG")]
 151        private void DebugSetLeftCtor()
 661152        {
 661153            DebugAssertInCtor();
 154
 661155            _flags |= Flags.Debug_LeftConstructor;
 156
 661157            AssertInvariants();
 661158        }
 159
 160        [Conditional("DEBUG")]
 161        private void AssertInvariants()
 1322162        {
 1322163            Debug.Assert(InFact(Flags.MinimalUriInfoSet) == (_info is not null));
 164
 1322165            if (_info is UriInfo info)
 661166            {
 661167                Debug.Assert(IsAbsoluteUri);
 168
 661169                Offset offset = info.Offset;
 170
 661171                Debug.Assert(offset.Scheme >= 0);
 661172                Debug.Assert(offset.User >= 0 && offset.User >= offset.Scheme);
 661173                Debug.Assert(offset.Host >= 0 && offset.Host >= offset.User);
 661174                Debug.Assert(offset.Path >= 0);
 661175                Debug.Assert(offset.End >= 0 && offset.End >= offset.Path);
 176
 661177                if (InFact(Flags.AllUriInfoSet))
 0178                {
 0179                    Debug.Assert(offset.Path >= offset.Host);
 0180                    Debug.Assert(offset.Query >= 0 && offset.Query >= offset.Path);
 0181                    Debug.Assert(offset.Fragment >= 0 && offset.Fragment >= offset.Query);
 0182                    Debug.Assert(offset.End >= offset.Fragment && offset.End <= _string.Length);
 0183                }
 184                else
 661185                {
 186                    // If we have non-ASCII, we're about to continue with ParseRemaining.
 187                    // Between CreateUriInfo and the rest of ParseRemaining, the Path offset is pointing into the origin
 188                    // while the Host offset is poining into the new _string. As such the Host offset may temporarily be
 661189                    if (!InFact(Flags.HasUnicode))
 661190                    {
 661191                        Debug.Assert(offset.Path >= offset.Host);
 661192                    }
 193
 194                    // We can't check that Query and Fragment offsets are 0 here as a different thread may have called
 195                    // ParseRemaining after we've checked InFact(Flags.AllUriInfoSet).
 661196                }
 661197            }
 198            else
 661199            {
 661200                if (IsAbsoluteUri && IriParsing)
 661201                {
 661202                    Debug.Assert(Ascii.IsValid(_string));
 661203                }
 661204            }
 1322205        }
 206
 207        [Conditional("DEBUG")]
 208        internal void DebugAssertInCtor()
 1983209        {
 1983210            Debug.Assert((_flags & Flags.Debug_LeftConstructor) == 0);
 1983211        }
 212
 213        private sealed class UriInfo
 214        {
 215            public Offset Offset;
 216            public string? String;
 217            public string? Host;
 218            public string? IdnHost;
 219            public string? PathAndQuery;
 220
 221            public MoreInfo? _moreInfo;
 222            public MoreInfo MoreInfo
 223            {
 224                get
 0225                {
 0226                    if (_moreInfo is null)
 0227                    {
 0228                        Interlocked.CompareExchange(ref _moreInfo, new MoreInfo(), null);
 0229                    }
 0230                    return _moreInfo;
 0231                }
 232            }
 233        };
 234
 235        private struct Offset
 236        {
 237            public int Scheme;
 238            public int User;
 239            public int Host;
 240            public ushort PortValue;
 241            public int Path;
 242            public int Query;
 243            public int Fragment;
 244            public int End;
 245        };
 246
 247        private sealed class MoreInfo
 248        {
 249            public string? Path;
 250            public string? Query;
 251            public string? Fragment;
 252            public string? AbsoluteUri;
 253            public string? RemoteUrl;
 254
 255            /// <summary>
 256            /// Only IP v6 may need this
 257            /// </summary>
 258            public string? ScopeId;
 259        };
 260
 261        private void InterlockedSetFlags(Flags flags)
 0262        {
 0263            Debug.Assert(_syntax != null);
 264
 0265            if (_syntax.IsSimple)
 0266            {
 267                // For built-in (simple) parsers, it is safe to do an Interlocked update here
 0268                Debug.Assert(sizeof(Flags) == sizeof(ulong));
 0269                Interlocked.Or(ref _flags, flags);
 0270            }
 271            else
 0272            {
 273                // Custom parsers still use a lock in CreateHostString and perform non-atomic flags updates
 274                // We have to take the lock to ensure flags access synchronization if CreateHostString and ParseRemainin
 0275                lock (_info)
 0276                {
 0277                    _flags |= flags;
 0278                }
 0279            }
 0280        }
 281
 282        private bool IsImplicitFile
 283        {
 1983284            get { return (_flags & Flags.ImplicitFile) != 0; }
 285        }
 286
 287        private bool IsUncOrDosPath
 288        {
 0289            get { return (_flags & (Flags.UncPath | Flags.DosPath)) != 0; }
 290        }
 291
 292        private bool IsDosPath
 293        {
 0294            get { return (_flags & Flags.DosPath) != 0; }
 295        }
 296
 297        private bool IsUncPath
 298        {
 0299            get { return (_flags & Flags.UncPath) != 0; }
 300        }
 301
 302        private bool IsUnixPath
 303        {
 0304            get { return (_flags & Flags.UnixPath) != 0; }
 305        }
 306
 307        private Flags HostType
 308        {
 1983309            get { return _flags & Flags.HostTypeMask; }
 310        }
 311
 312        private UriParser Syntax
 313        {
 314            get
 0315            {
 0316                return _syntax;
 0317            }
 318        }
 319
 320        private bool IsNotAbsoluteUri
 321        {
 1983322            get { return _syntax is null; }
 323        }
 324
 325        //
 326        // Checks if Iri parsing is allowed by the syntax & by config
 327        //
 1322328        private bool IriParsing => IriParsingStatic(_syntax);
 329
 330        internal static bool IriParsingStatic(UriParser? syntax)
 1322331        {
 1322332            return syntax is null || syntax.InFact(UriSyntaxFlags.AllowIriParsing);
 1322333        }
 334
 0335        internal bool DisablePathAndQueryCanonicalization => (_flags & Flags.DisablePathAndQueryCanonicalization) != 0;
 336
 337        internal bool UserDrivenParsing
 338        {
 339            get
 661340            {
 661341                return (_flags & Flags.UserDrivenParsing) != 0;
 661342            }
 343        }
 344
 345        private int SecuredPathIndex
 346        {
 347            get
 0348            {
 349                // This is one more trouble with a Dos Path.
 350                // This property gets "safe" first path slash that is not the first if path = c:\
 0351                if (IsDosPath)
 0352                {
 0353                    char ch = _string[_info.Offset.Path];
 0354                    return (ch == '/' || ch == '\\') ? 3 : 2;
 355                }
 0356                return 0;
 0357            }
 358        }
 359
 360        private bool NotAny(Flags flags)
 0361        {
 0362            return (_flags & flags) == 0;
 0363        }
 364
 365        private bool InFact(Flags flags)
 3305366        {
 3305367            return (_flags & flags) != 0;
 3305368        }
 369
 370        private static bool StaticNotAny(Flags allFlags, Flags checkFlags)
 123371        {
 123372            return (allFlags & checkFlags) == 0;
 123373        }
 374
 375        private static bool StaticInFact(Flags allFlags, Flags checkFlags)
 0376        {
 0377            return (allFlags & checkFlags) != 0;
 0378        }
 379
 380        [MemberNotNull(nameof(_info))]
 381        private UriInfo EnsureUriInfo()
 661382        {
 661383            Flags cF = _flags;
 661384            if ((cF & Flags.MinimalUriInfoSet) == 0)
 661385            {
 661386                CreateUriInfo(cF);
 661387                AssertInvariants();
 661388            }
 661389            Debug.Assert(_info != null && (_flags & Flags.MinimalUriInfoSet) != 0);
 661390            return _info;
 661391        }
 392
 393        private void EnsureParseRemaining()
 0394        {
 0395            if ((_flags & Flags.AllUriInfoSet) == 0)
 0396            {
 0397                ParseRemaining();
 0398                AssertInvariants();
 0399            }
 0400        }
 401
 402        [MemberNotNull(nameof(_info))]
 403        private void EnsureHostString(bool allowDnsOptimization)
 0404        {
 0405            UriInfo info = EnsureUriInfo();
 406
 0407            if (info.Host is null)
 0408            {
 0409                if (allowDnsOptimization && InFact(Flags.CanonicalDnsHost))
 0410                {
 411                    /* Optimization for a canonical DNS name
 412                    *  ATTN: the host string won't be created,
 413                    *  Hence ALL _info.Host callers first call EnsureHostString(false)
 414                    *  For example IsLoopBack property is one of such callers.
 415                    */
 0416                    return;
 417                }
 0418                CreateHostString();
 0419            }
 0420        }
 421
 422        //
 423        // Uri(string)
 424        //
 425        //  We expect to create a Uri from a display name - e.g. that was typed by
 426        //  a user, or that was copied & pasted from a document. That is, we do not
 427        //  expect already encoded URI to be supplied.
 428        //
 0429        public Uri([StringSyntax(StringSyntaxAttribute.Uri)] string uriString)
 0430        {
 0431            ArgumentNullException.ThrowIfNull(uriString);
 432
 0433            CreateThis(uriString, Flags.Zero, UriKind.Absolute);
 0434        }
 435
 436        //
 437        // Uri(string, bool)
 438        //
 439        //  Uri constructor. Assumes that input string is canonically escaped
 440        //
 441        [Obsolete("This constructor has been deprecated. Use Uri(string) instead.")]
 0442        public Uri([StringSyntax(StringSyntaxAttribute.Uri)] string uriString, bool dontEscape)
 0443        {
 0444            ArgumentNullException.ThrowIfNull(uriString);
 445
 0446            CreateThis(uriString, dontEscape ? Flags.UserEscaped : Flags.Zero, UriKind.Absolute);
 0447        }
 448
 449        //
 450        // Uri(Uri, string, bool)
 451        //
 452        //  Uri combinatorial constructor. Do not perform character escaping if
 453        //  DontEscape is true
 454        //
 455        [Obsolete("This constructor has been deprecated. Use Uri(Uri, string) instead.")]
 0456        public Uri(Uri baseUri, string? relativeUri, bool dontEscape)
 0457        {
 0458            ArgumentNullException.ThrowIfNull(baseUri);
 459
 0460            if (!baseUri.IsAbsoluteUri)
 0461                throw new ArgumentOutOfRangeException(nameof(baseUri));
 462
 0463            CreateUri(baseUri, relativeUri, dontEscape);
 0464        }
 465
 466        //
 467        // Uri(string, UriKind);
 468        //
 0469        public Uri([StringSyntax(StringSyntaxAttribute.Uri, nameof(uriKind))] string uriString, UriKind uriKind)
 0470        {
 0471            ArgumentNullException.ThrowIfNull(uriString);
 472
 0473            CreateThis(uriString, Flags.Zero, uriKind);
 0474        }
 475
 476        /// <summary>
 477        /// Initializes a new instance of the <see cref="Uri"/> class with the specified URI and additional <see cref="U
 478        /// </summary>
 479        /// <param name="uriString">A string that identifies the resource to be represented by the <see cref="Uri"/> ins
 480        /// <param name="creationOptions">Options that control how the <seealso cref="Uri"/> is created and behaves.</pa
 0481        public Uri([StringSyntax(StringSyntaxAttribute.Uri)] string uriString, in UriCreationOptions creationOptions)
 0482        {
 0483            ArgumentNullException.ThrowIfNull(uriString);
 484
 0485            CreateThis(uriString, creationOptions._flags, UriKind.Absolute);
 0486        }
 487
 488        //
 489        // Uri(Uri, string)
 490        //
 491        //  Construct a new Uri from a base and relative URI. The relative URI may
 492        //  also be an absolute URI, in which case the resultant URI is constructed
 493        //  entirely from it
 494        //
 0495        public Uri(Uri baseUri, string? relativeUri)
 0496        {
 0497            ArgumentNullException.ThrowIfNull(baseUri);
 498
 0499            if (!baseUri.IsAbsoluteUri)
 0500                throw new ArgumentOutOfRangeException(nameof(baseUri));
 501
 0502            CreateUri(baseUri, relativeUri, false);
 0503        }
 504
 505        //
 506        // Uri(SerializationInfo, StreamingContext)
 507        //
 508        // ISerializable constructor
 509        //
 510        [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlForma
 511        [EditorBrowsable(EditorBrowsableState.Never)]
 0512        protected Uri(SerializationInfo serializationInfo, StreamingContext streamingContext)
 0513        {
 0514            string? uriString = serializationInfo.GetString("AbsoluteUri"); // Do not rename (binary serialization)
 515
 0516            if (uriString!.Length != 0)
 0517            {
 0518                CreateThis(uriString, Flags.Zero, UriKind.Absolute);
 0519                return;
 520            }
 521
 0522            uriString = serializationInfo.GetString("RelativeUri");  // Do not rename (binary serialization)
 0523            if (uriString is null)
 0524                throw new ArgumentException(SR.Format(SR.InvalidNullArgument, "RelativeUri"), nameof(serializationInfo))
 525
 0526            CreateThis(uriString, Flags.Zero, UriKind.Relative);
 0527        }
 528
 529        //
 530        // ISerializable method
 531        //
 532        /// <internalonly/>
 533        void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
 0534        {
 0535            GetObjectData(serializationInfo, streamingContext);
 0536        }
 537
 538        //
 539        // FxCop: provide some way for derived classes to access GetObjectData even if the derived class
 540        // explicitly re-inherits ISerializable.
 541        //
 542        protected void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
 0543        {
 544
 0545            if (IsAbsoluteUri)
 0546                serializationInfo.AddValue("AbsoluteUri", GetParts(UriComponents.SerializationInfoString, UriFormat.UriE
 547            else
 0548            {
 0549                serializationInfo.AddValue("AbsoluteUri", string.Empty); // Do not rename (binary serialization)
 0550                serializationInfo.AddValue("RelativeUri", GetParts(UriComponents.SerializationInfoString, UriFormat.UriE
 0551            }
 0552        }
 553
 554        [MemberNotNull(nameof(_string))]
 555        private void CreateUri(Uri baseUri, string? relativeUri, bool dontEscape)
 0556        {
 0557            DebugAssertInCtor();
 558
 559            // Parse relativeUri and populate Uri internal data.
 0560            CreateThis(relativeUri, dontEscape ? Flags.UserEscaped : Flags.Zero, UriKind.RelativeOrAbsolute);
 561
 0562            if (baseUri.Syntax!.IsSimple)
 0563            {
 564                // Resolve Uris if possible OR get merged Uri String to re-parse below
 0565                Uri? uriResult = ResolveHelper(baseUri, this, ref relativeUri, ref dontEscape);
 566
 567                // If resolved into a Uri then we build from that Uri
 0568                if (uriResult != null)
 0569                {
 0570                    if (!ReferenceEquals(this, uriResult))
 0571                        CreateThisFromUri(uriResult);
 572
 0573                    return;
 574                }
 0575            }
 576            else
 0577            {
 0578                dontEscape = false;
 0579                relativeUri = baseUri.Syntax.InternalResolve(baseUri, this, out UriFormatException? e);
 0580                if (e != null)
 0581                    throw e;
 0582            }
 583
 0584            _flags = Flags.Zero;
 0585            _info = null!;
 0586            _syntax = null!;
 0587            _originalUnicodeString = null!;
 588            // If not resolved, we reparse modified Uri string and populate Uri internal data.
 0589            CreateThis(relativeUri, dontEscape ? Flags.UserEscaped : Flags.Zero, UriKind.Absolute);
 0590        }
 591
 592        //
 593        // Uri(Uri , Uri )
 594        // Note: a static Create() method should be used by users, not this .ctor
 595        //
 0596        public Uri(Uri baseUri, Uri relativeUri)
 0597        {
 0598            ArgumentNullException.ThrowIfNull(baseUri);
 0599            ArgumentNullException.ThrowIfNull(relativeUri);
 600
 0601            if (!baseUri.IsAbsoluteUri)
 0602                throw new ArgumentOutOfRangeException(nameof(baseUri));
 603
 0604            CreateThisFromUri(relativeUri);
 605
 0606            string? newUriString = null;
 607            bool dontEscape;
 608
 0609            if (baseUri.Syntax!.IsSimple)
 0610            {
 0611                dontEscape = InFact(Flags.UserEscaped);
 0612                Uri? resolvedRelativeUri = ResolveHelper(baseUri, this, ref newUriString, ref dontEscape);
 613
 0614                if (resolvedRelativeUri != null)
 0615                {
 0616                    if (!ReferenceEquals(this, resolvedRelativeUri))
 0617                        CreateThisFromUri(resolvedRelativeUri);
 618
 0619                    return;
 620                }
 0621            }
 622            else
 0623            {
 0624                dontEscape = false;
 0625                newUriString = baseUri.Syntax.InternalResolve(baseUri, this, out UriFormatException? e);
 0626                if (e != null)
 0627                    throw e;
 0628            }
 629
 0630            _flags = Flags.Zero;
 0631            _info = null!;
 0632            _syntax = null!;
 0633            _originalUnicodeString = null!;
 0634            CreateThis(newUriString, dontEscape ? Flags.UserEscaped : Flags.Zero, UriKind.Absolute);
 0635        }
 636
 637        //
 638        // This method is shared by base+relative Uris constructors and is only called from them.
 639        // The assumptions:
 640        //  - baseUri is a valid absolute Uri
 641        //  - relative part is not null and not empty
 642        private static void GetCombinedString(Uri baseUri, string relativeStr,
 643            bool dontEscape, ref string? result)
 0644        {
 645            // NB: This is not RFC2396 compliant although it is inline with w3c.org recommendations
 646            // This parser will allow the relativeStr to be an absolute Uri with the different scheme
 647            // In fact this is strict violation of RFC2396
 648            //
 0649            int i = relativeStr.IndexOfAny(s_segmentSeparatorChars);
 650
 651            // Note we don't support one-letter Uri schemes (i > 1).
 652            // Hence anything like x:sdsd is a relative path and be added to the baseUri Path
 0653            if ((uint)i < (uint)relativeStr.Length && relativeStr[i] == ':' && i > 1)
 0654            {
 0655                ParsingError error = ParsingError.None;
 0656                UriParser? syntax = CheckSchemeSyntax(relativeStr.AsSpan(0, i), ref error);
 657
 0658                if (error == ParsingError.None)
 0659                {
 0660                    if (baseUri.Syntax == syntax)
 0661                    {
 662                        //Remove the scheme for backward Uri parsers compatibility
 0663                        if (i + 1 < relativeStr.Length)
 0664                        {
 0665                            relativeStr = relativeStr.Substring(i + 1);
 0666                        }
 667                        else
 0668                        {
 0669                            relativeStr = string.Empty;
 0670                        }
 0671                    }
 672                    else
 0673                    {
 674                        // This is the place where we switch the scheme.
 675                        // Return relative part as the result Uri.
 0676                        result = relativeStr;
 0677                        return;
 678                    }
 0679                }
 0680            }
 681
 0682            if (relativeStr.Length == 0)
 0683            {
 0684                result = baseUri.OriginalString;
 0685            }
 686            else
 0687            {
 0688                result = CombineUri(baseUri, relativeStr, dontEscape ? UriFormat.UriEscaped : UriFormat.SafeUnescaped);
 0689            }
 0690        }
 691
 692        private static UriFormatException GetException(ParsingError err)
 0693        {
 0694            Debug.Assert(err != ParsingError.None);
 695
 0696            string message = err switch
 0697            {
 0698                // Could be OK for Relative Uri
 0699                ParsingError.BadFormat => SR.net_uri_BadFormat,
 0700                ParsingError.BadScheme => SR.net_uri_BadScheme,
 0701                ParsingError.BadAuthority => SR.net_uri_BadAuthority,
 0702                ParsingError.EmptyUriString => SR.net_uri_EmptyUri,
 0703
 0704                // Fatal
 0705                ParsingError.SchemeLimit => SR.net_uri_SchemeLimit,
 0706                ParsingError.MustRootedPath => SR.net_uri_MustRootedPath,
 0707
 0708                // Derived class controllable
 0709                ParsingError.BadHostName => SR.net_uri_BadHostName,
 0710                ParsingError.BadPort => SR.net_uri_BadPort,
 0711                ParsingError.BadAuthorityTerminator => SR.net_uri_BadAuthorityTerminator,
 0712                ParsingError.CannotCreateRelative => SR.net_uri_CannotCreateRelative,
 0713
 0714                _ => throw new UnreachableException()
 0715            };
 716
 0717            return new UriFormatException(message);
 0718        }
 719
 720        public string AbsolutePath
 721        {
 722            get
 0723            {
 0724                if (IsNotAbsoluteUri)
 0725                {
 0726                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 727                }
 728
 0729                string path = PrivateAbsolutePath;
 730                //
 731                // For Compat:
 732                // Remove the first slash from a Dos Path if it's present
 733                //
 0734                if (IsDosPath && path[0] == '/')
 0735                {
 0736                    path = path.Substring(1);
 0737                }
 0738                return path;
 0739            }
 740        }
 741
 742        private string PrivateAbsolutePath
 743        {
 744            get
 0745            {
 0746                Debug.Assert(IsAbsoluteUri);
 747
 0748                MoreInfo info = EnsureUriInfo().MoreInfo;
 0749                return info.Path ??= GetParts(UriComponents.Path | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
 0750            }
 751        }
 752
 753        public string AbsoluteUri
 754        {
 755            get
 0756            {
 0757                if (_syntax == null)
 0758                {
 0759                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 760                }
 761
 0762                MoreInfo info = EnsureUriInfo().MoreInfo;
 0763                return info.AbsoluteUri ??= GetParts(UriComponents.AbsoluteUri, UriFormat.UriEscaped);
 0764            }
 765        }
 766
 767        //
 768        // LocalPath
 769        //
 770        //  Returns a 'local' version of the path. This is mainly for file: URI
 771        //  such that DOS and UNC paths are returned with '/' converted back to
 772        //  '\', and any escape sequences converted
 773        //
 774        //  The form of the returned path is in NOT Escaped
 775        //
 776        public string LocalPath
 777        {
 778            get
 0779            {
 0780                if (IsNotAbsoluteUri)
 0781                {
 0782                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 783                }
 0784                return GetLocalPath();
 0785            }
 786        }
 787
 788        //
 789        // The result is of the form "hostname[:port]" Port is omitted if default
 790        //
 791        public string Authority
 792        {
 793            get
 0794            {
 0795                if (IsNotAbsoluteUri)
 0796                {
 0797                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 798                }
 799
 800                // Note: Compatibility with V1 that does not report user info
 0801                return GetParts(UriComponents.Host | UriComponents.Port, UriFormat.UriEscaped);
 0802            }
 803        }
 804
 805
 806        public UriHostNameType HostNameType
 807        {
 808            get
 661809            {
 661810                if (IsNotAbsoluteUri)
 0811                {
 0812                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 813                }
 814
 661815                if (_syntax.IsSimple)
 661816                    EnsureUriInfo();
 817                else
 0818                {
 819                    // For a custom parser we request HostString creation to confirm HostType
 0820                    EnsureHostString(false);
 0821                }
 822
 661823                switch (HostType)
 824                {
 0825                    case Flags.DnsHostType: return UriHostNameType.Dns;
 123826                    case Flags.IPv4HostType: return UriHostNameType.IPv4;
 538827                    case Flags.IPv6HostType: return UriHostNameType.IPv6;
 0828                    case Flags.BasicHostType: return UriHostNameType.Basic;
 0829                    case Flags.UncHostType: return UriHostNameType.Basic;
 0830                    case Flags.UnknownHostType: return UriHostNameType.Unknown;
 831                    default:
 0832                        break;
 833                }
 0834                return UriHostNameType.Unknown;
 661835            }
 836        }
 837
 838        public bool IsDefaultPort
 839        {
 840            get
 0841            {
 0842                if (IsNotAbsoluteUri)
 0843                {
 0844                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 845                }
 0846                if (_syntax.IsSimple)
 0847                    EnsureUriInfo();
 848                else
 0849                {
 850                    // For a custom parser we request HostString creation that will also set the port
 0851                    EnsureHostString(false);
 0852                }
 853
 0854                return NotAny(Flags.NotDefaultPort);
 0855            }
 856        }
 857
 858        public bool IsFile
 859        {
 860            get
 0861            {
 0862                if (IsNotAbsoluteUri)
 0863                {
 0864                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 865                }
 866
 0867                return (object)_syntax.SchemeName == (object)UriSchemeFile;
 0868            }
 869        }
 870
 871        public bool IsLoopback
 872        {
 873            get
 0874            {
 0875                if (IsNotAbsoluteUri)
 0876                {
 0877                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 878                }
 879
 0880                EnsureHostString(false);
 881
 0882                return InFact(Flags.LoopbackHost);
 0883            }
 884        }
 885
 886        //
 887        //  Gets the escaped Uri.AbsolutePath and Uri.Query
 888        //  properties separated by a "?" character.
 889        public string PathAndQuery
 890        {
 891            get
 0892            {
 0893                if (IsNotAbsoluteUri)
 0894                {
 0895                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 896                }
 897
 0898                UriInfo info = EnsureUriInfo();
 899
 0900                if (info.PathAndQuery is null)
 0901                {
 0902                    string result = GetParts(UriComponents.PathAndQuery, UriFormat.UriEscaped);
 903
 904                    // Compatibility:
 905                    // Remove the first slash from a Dos Path if it's present
 0906                    if (IsDosPath && result[0] == '/')
 0907                    {
 0908                        result = result.Substring(1);
 0909                    }
 910
 0911                    info.PathAndQuery = result;
 0912                }
 913
 0914                return info.PathAndQuery;
 0915            }
 916        }
 917
 918        //
 919        //  Gets an array of the segments that make up a URI.
 920        public string[] Segments
 921        {
 922            get
 0923            {
 0924                if (IsNotAbsoluteUri)
 0925                {
 0926                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 927                }
 928
 929                string[] segments;
 0930                string path = PrivateAbsolutePath;
 931
 0932                if (path.Length == 0)
 0933                {
 0934                    segments = Array.Empty<string>();
 0935                }
 936                else
 0937                {
 0938                    var pathSegments = new ValueListBuilder<string>(4);
 939
 0940                    int current = 0;
 0941                    while (current < path.Length)
 0942                    {
 0943                        int next = path.IndexOf('/', current);
 0944                        if (next == -1)
 0945                        {
 0946                            next = path.Length - 1;
 0947                        }
 0948                        pathSegments.Append(path.Substring(current, (next - current) + 1));
 0949                        current = next + 1;
 0950                    }
 951
 0952                    segments = pathSegments.AsSpan().ToArray();
 0953                    pathSegments.Dispose();
 0954                }
 955
 0956                return segments;
 0957            }
 958        }
 959
 960        public bool IsUnc
 961        {
 962            get
 0963            {
 0964                if (IsNotAbsoluteUri)
 0965                {
 0966                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 967                }
 0968                return IsUncPath;
 0969            }
 970        }
 971
 972        //
 973        // Gets a hostname part (special formatting for IPv6 form)
 974        public string Host
 975        {
 976            get
 0977            {
 0978                if (IsNotAbsoluteUri)
 0979                {
 0980                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 981                }
 982
 0983                return GetParts(UriComponents.Host, UriFormat.UriEscaped);
 0984            }
 985        }
 986
 987        private static bool StaticIsFile(UriParser syntax)
 0988        {
 0989            return syntax.InFact(UriSyntaxFlags.FileLikeUri);
 0990        }
 991
 992        private string GetLocalPath()
 0993        {
 0994            EnsureParseRemaining();
 995
 996            //Other cases will get a Unix-style path
 0997            if (IsUncOrDosPath)
 0998            {
 0999                EnsureHostString(false);
 01000                Debug.Assert(_info != null);
 01001                Debug.Assert(_info.Host != null);
 1002                int start;
 1003
 1004                // Do we have a valid local path right in _string?
 01005                if (NotAny(Flags.HostNotCanonical | Flags.PathNotCanonical | Flags.ShouldBeCompressed))
 01006                {
 01007                    start = IsUncPath ? _info.Offset.Host - 2 : _info.Offset.Path;
 1008
 01009                    string str = (IsImplicitFile && _info.Offset.Host == (IsDosPath ? 0 : 2) &&
 01010                        _info.Offset.Query == _info.Offset.End)
 01011                            ? _string
 01012                            : (IsDosPath && _string[start] is '/' or '\\')
 01013                                ? _string.Substring(start + 1, _info.Offset.Query - start - 1)
 01014                                : _string.Substring(start, _info.Offset.Query - start);
 1015
 1016                    // Should be a rare case, convert c|\ into c:\
 01017                    if (IsDosPath && str[1] == '|')
 01018                    {
 1019                        // Sadly, today there is no method for replacing just one occurrence
 01020                        str = str.Remove(1, 1);
 01021                        str = str.Insert(1, ":");
 01022                    }
 1023
 1024                    // check for all back slashes
 01025                    str = str.Replace('/', '\\');
 1026
 01027                    return str;
 1028                }
 1029
 01030                var result = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
 1031
 01032                string host = _info.Host;
 01033                start = _info.Offset.Path;
 1034
 01035                if (IsUncPath)
 01036                {
 01037                    result.Append('\\');
 01038                    result.Append('\\');
 01039                    result.Append(host);
 01040                }
 1041                else
 01042                {
 1043                    // Dos path
 01044                    if (_string[start] is '/' or '\\')
 01045                    {
 1046                        // Skip leading slash for a DOS path
 01047                        start++;
 01048                    }
 01049                }
 1050
 01051                int pathStart = result.Length; // save for optional Compress() call
 1052
 01053                ReadOnlySpan<char> path = _string.AsSpan(start, _info.Offset.Query - start);
 1054
 01055                if (InFact(Flags.PathNotCanonical) && !IsImplicitFile)
 01056                {
 01057                    UriHelper.Unescape(path, ref result);
 01058                }
 1059                else
 01060                {
 01061                    result.Append(path);
 01062                }
 1063
 1064                // Possibly convert c|\ into c:\
 01065                if (result[1] == '|')
 01066                    result[1] = ':';
 1067
 01068                if (InFact(Flags.ShouldBeCompressed))
 01069                {
 1070                    // suspecting not compressed path
 1071                    // For a dos path we won't compress the "x:" part if found /../ sequences
 01072                    Compress(ref result, IsDosPath ? pathStart + 2 : pathStart, _syntax);
 01073                }
 1074
 1075                // We don't know whether all slashes were the back ones
 1076                // Plus going through Compress will turn them into / anyway
 1077                // Converting / back into \
 01078                Span<char> slashSpan = result.RawChars.Slice(0, result.Length);
 01079                slashSpan.Replace('/', '\\');
 1080
 01081                return result.ToString();
 1082            }
 1083            else
 01084            {
 1085                // Return unescaped canonical path
 1086                // Note we cannot call GetParts here because it has circular dependency on GelLocalPath method
 01087                return GetUnescapedParts(UriComponents.Path | UriComponents.KeepDelimiter, UriFormat.Unescaped);
 1088            }
 01089        }
 1090
 1091        public int Port
 1092        {
 1093            get
 01094            {
 01095                if (IsNotAbsoluteUri)
 01096                {
 01097                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 1098                }
 1099
 01100                if (_syntax.IsSimple)
 01101                    EnsureUriInfo();
 1102                else
 01103                {
 1104                    // For a custom parser we request HostString creation that will also set the port
 01105                    EnsureHostString(false);
 01106                }
 1107
 01108                if (InFact(Flags.NotDefaultPort))
 01109                {
 01110                    return (int)_info.Offset.PortValue;
 1111                }
 01112                return _syntax.DefaultPort;
 01113            }
 1114        }
 1115
 1116        //
 1117        //  Gets the escaped query.
 1118        public string Query
 1119        {
 1120            get
 01121            {
 01122                if (IsNotAbsoluteUri)
 01123                {
 01124                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 1125                }
 1126
 01127                MoreInfo info = EnsureUriInfo().MoreInfo;
 01128                return info.Query ??= GetParts(UriComponents.Query | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
 01129            }
 1130        }
 1131
 1132        //
 1133        //    Gets the escaped fragment.
 1134        public string Fragment
 1135        {
 1136            get
 01137            {
 01138                if (IsNotAbsoluteUri)
 01139                {
 01140                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 1141                }
 1142
 01143                MoreInfo info = EnsureUriInfo().MoreInfo;
 01144                return info.Fragment ??= GetParts(UriComponents.Fragment | UriComponents.KeepDelimiter, UriFormat.UriEsc
 01145            }
 1146        }
 1147
 1148        //
 1149        //  Gets the Scheme string of this Uri
 1150        //
 1151        public string Scheme
 1152        {
 1153            get
 01154            {
 01155                if (IsNotAbsoluteUri)
 01156                {
 01157                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 1158                }
 1159
 01160                return _syntax.SchemeName;
 01161            }
 1162        }
 1163
 1164        //
 1165        //  Gets the exact string passed by a user.
 1166        //  The original string will switched from _string to _originalUnicodeString if
 1167        //  iri is turned on and we have non-ascii chars
 1168        //
 26441169        public string OriginalString => _originalUnicodeString ?? _string;
 1170
 1171        //
 1172        //    Gets the host string that is unescaped and if it's Ipv6 host,
 1173        //    then the returned string is suitable for DNS lookup.
 1174        //
 1175        //    For Ipv6 this will strip [] and add ScopeId if was found in the original string
 1176        public string DnsSafeHost
 1177        {
 1178            get
 01179            {
 01180                if (IsNotAbsoluteUri)
 01181                {
 01182                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 1183                }
 1184
 01185                EnsureHostString(false);
 1186
 01187                Flags hostType = HostType;
 01188                if (hostType == Flags.IPv6HostType || (hostType == Flags.BasicHostType && InFact(Flags.HostNotCanonical 
 01189                {
 01190                    return IdnHost;
 1191                }
 1192                else
 01193                {
 01194                    return _info.Host!;
 1195                }
 01196            }
 1197        }
 1198
 1199        // Returns the host name represented as IDN (using punycode encoding) regardless of app.config settings
 1200        public string IdnHost
 1201        {
 1202            get
 01203            {
 01204                if (IsNotAbsoluteUri)
 01205                {
 01206                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 1207                }
 1208
 01209                if (_info?.IdnHost is null)
 01210                {
 01211                    EnsureHostString(false);
 1212
 01213                    string host = _info.Host!;
 1214
 01215                    Flags hostType = HostType;
 01216                    if (hostType == Flags.DnsHostType)
 01217                    {
 01218                        host = DomainNameHelper.IdnEquivalent(host);
 01219                    }
 01220                    else if (hostType == Flags.IPv6HostType)
 01221                    {
 01222                        host = _info._moreInfo?.ScopeId is string scopeId ?
 01223                            string.Concat(host.AsSpan(1, host.Length - 2), scopeId) :
 01224                            host.Substring(1, host.Length - 2);
 01225                    }
 1226                    // Validate that this basic host qualifies as Dns safe,
 1227                    // It has looser parsing rules that might allow otherwise.
 1228                    // It might be a registry-based host from RFC 2396 Section 3.2.1
 01229                    else if (hostType == Flags.BasicHostType && InFact(Flags.HostNotCanonical | Flags.E_HostNotCanonical
 01230                    {
 01231                        host = UnescapeDataString(host);
 01232                    }
 1233
 01234                    _info.IdnHost = host;
 01235                }
 1236
 01237                return _info.IdnHost;
 01238            }
 1239        }
 1240
 1241        //
 1242        //  Returns false if the string passed in the constructor cannot be parsed as
 1243        //  valid AbsoluteUri. This could be a relative Uri instead.
 1244        //
 1245        public bool IsAbsoluteUri
 1246        {
 1247            get
 13221248            {
 13221249                return _syntax != null;
 13221250            }
 1251        }
 1252
 1253        //
 1254        //  Returns 'true' if the 'dontEscape' parameter was set to 'true ' when the Uri instance was created.
 1255        public bool UserEscaped
 1256        {
 1257            get
 01258            {
 01259                return InFact(Flags.UserEscaped);
 01260            }
 1261        }
 1262
 1263        //
 1264        //  Gets the user name, password, and other user specific information associated
 1265        //  with the Uniform Resource Identifier (URI).
 1266        public string UserInfo
 1267        {
 1268            get
 01269            {
 01270                if (IsNotAbsoluteUri)
 01271                {
 01272                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 1273                }
 1274
 01275                return GetParts(UriComponents.UserInfo, UriFormat.UriEscaped);
 01276            }
 1277        }
 1278
 1279        //
 1280        // CheckHostName
 1281        //
 1282        //  Determines whether a host name authority is a valid Host name according
 1283        //  to DNS naming rules and IPv4 canonicalization rules
 1284        //
 1285        // Returns:
 1286        //  true if <name> is valid else false
 1287        //
 1288        // Throws:
 1289        //  Nothing
 1290        //
 1291        public static UriHostNameType CheckHostName(string? name)
 6611292        {
 6611293            if (string.IsNullOrEmpty(name))
 01294            {
 01295                return UriHostNameType.Unknown;
 1296            }
 1297
 1298            int end;
 6611299            if (name.StartsWith('[') && name.EndsWith(']'))
 5381300            {
 1301                // we require that _entire_ name is recognized as ipv6 address
 5381302                if (IPv6AddressHelper.IsValid(name, out end) && end == name.Length)
 5381303                {
 5381304                    return UriHostNameType.IPv6;
 1305                }
 01306            }
 1307
 1231308            if (IPv4AddressHelper.IsValid(name.AsSpan(), out end, false, false, false) && end == name.Length)
 1231309            {
 1231310                return UriHostNameType.IPv4;
 1311            }
 1312
 01313            if (DomainNameHelper.IsValid(name, iri: false, notImplicitFile: false, out end) && end == name.Length)
 01314            {
 01315                return UriHostNameType.Dns;
 1316            }
 1317
 01318            if (DomainNameHelper.IsValid(name, iri: true, notImplicitFile: false, out end) && end == name.Length)
 01319            {
 01320                return UriHostNameType.Dns;
 1321            }
 1322
 1323            // This checks the form without []
 1324            // we require that _entire_ name is recognized as ipv6 address
 01325            if (IPv6AddressHelper.IsValid($"[{name}]", out end) && end - 2 == name.Length)
 01326            {
 01327                return UriHostNameType.IPv6;
 1328            }
 1329
 01330            return UriHostNameType.Unknown;
 6611331        }
 1332
 1333        //
 1334        // GetLeftPart
 1335        //
 1336        //  Returns part of the URI based on the parameters:
 1337        //
 1338        // Inputs:
 1339        //  <argument>  part
 1340        //      Which part of the URI to return
 1341        //
 1342        // Returns:
 1343        //  The requested substring
 1344        //
 1345        // Throws:
 1346        //  UriFormatException if URI type doesn't have host-port or authority parts
 1347        //
 1348        public string GetLeftPart(UriPartial part)
 01349        {
 01350            if (IsNotAbsoluteUri)
 01351            {
 01352                throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 1353            }
 1354
 01355            EnsureUriInfo();
 1356            const UriComponents NonPathPart = (UriComponents.Scheme | UriComponents.UserInfo | UriComponents.Host | UriC
 1357
 01358            switch (part)
 1359            {
 1360                case UriPartial.Scheme:
 1361
 01362                    return GetParts(UriComponents.Scheme | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
 1363
 1364                case UriPartial.Authority:
 1365
 01366                    if (NotAny(Flags.AuthorityFound) || IsDosPath)
 01367                    {
 1368                        // V1.0 compatibility.
 1369                        // It not return an empty string but instead "scheme:" because it is a LEFT part.
 1370                        // Also neither it should check for IsDosPath here
 1371
 1372                        // From V1.0 comments:
 1373
 1374                        // anything that didn't have "//" after the scheme name
 1375                        // (mailto: and news: e.g.) doesn't have an authority
 1376                        //
 1377
 01378                        return string.Empty;
 1379                    }
 01380                    return GetParts(NonPathPart, UriFormat.UriEscaped);
 1381
 1382                case UriPartial.Path:
 01383                    return GetParts(NonPathPart | UriComponents.Path, UriFormat.UriEscaped);
 1384
 1385                case UriPartial.Query:
 01386                    return GetParts(NonPathPart | UriComponents.Path | UriComponents.Query, UriFormat.UriEscaped);
 1387            }
 01388            throw new ArgumentException(SR.Format(SR.Argument_InvalidUriSubcomponent, part), nameof(part));
 01389        }
 1390
 1391        //
 1392        //
 1393        /// Transforms a character into its hexadecimal representation.
 1394        public static string HexEscape(char character)
 01395        {
 01396            ArgumentOutOfRangeException.ThrowIfGreaterThan(character, '\xff');
 1397
 01398            return string.Create(3, (byte)character, (Span<char> chars, byte b) =>
 01399            {
 01400                chars[0] = '%';
 01401                HexConverter.ToCharsBuffer(b, chars, 1, HexConverter.Casing.Upper);
 01402            });
 01403        }
 1404
 1405        //
 1406        // HexUnescape
 1407        //
 1408        //  Converts a substring of the form "%XX" to the single character represented
 1409        //  by the hexadecimal value XX. If the substring s[Index] does not conform to
 1410        //  the hex encoding format then the character at s[Index] is returned
 1411        //
 1412        // Inputs:
 1413        //  <argument>  pattern
 1414        //      String from which to read the hexadecimal encoded substring
 1415        //
 1416        //  <argument>  index
 1417        //      Offset within <pattern> from which to start reading the hexadecimal
 1418        //      encoded substring
 1419        //
 1420        // Outputs:
 1421        //  <argument>  index
 1422        //      Incremented to the next character position within the string. This
 1423        //      may be EOS if this was the last character/encoding within <pattern>
 1424        //
 1425        // Returns:
 1426        //  Either the converted character if <pattern>[<index>] was hex encoded, or
 1427        //  the character at <pattern>[<index>]
 1428        //
 1429        // Throws:
 1430        //  ArgumentOutOfRangeException
 1431        //
 1432
 1433        public static char HexUnescape(string pattern, ref int index)
 01434        {
 01435            ArgumentOutOfRangeException.ThrowIfNegative(index);
 01436            ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, pattern.Length);
 1437
 01438            if ((pattern[index] == '%')
 01439                && (pattern.Length - index >= 3))
 01440            {
 01441                char ret = UriHelper.DecodeHexChars(pattern[index + 1], pattern[index + 2]);
 01442                if (ret != c_DummyChar)
 01443                {
 01444                    index += 3;
 01445                    return ret;
 1446                }
 01447            }
 01448            return pattern[index++];
 01449        }
 1450
 1451        //
 1452        // IsHexEncoding
 1453        //
 1454        //  Determines whether a substring has the URI hex encoding format of '%'
 1455        //  followed by 2 hexadecimal characters
 1456        //
 1457        // Inputs:
 1458        //  <argument>  pattern
 1459        //      String to check
 1460        //
 1461        //  <argument>  index
 1462        //      Offset in <pattern> at which to check substring for hex encoding
 1463        //
 1464        // Assumes:
 1465        //  0 <= <index> < <pattern>.Length
 1466        //
 1467        // Returns:
 1468        //  true if <pattern>[<index>] is hex encoded, else false
 1469        //
 1470        // Throws:
 1471        //  Nothing
 1472        //
 1473        public static bool IsHexEncoding(string pattern, int index)
 01474        {
 01475            return
 01476                (pattern.Length - index) >= 3 &&
 01477                pattern[index] == '%' &&
 01478                char.IsAsciiHexDigit(pattern[index + 1]) &&
 01479                char.IsAsciiHexDigit(pattern[index + 2]);
 01480        }
 1481
 11482        private static readonly SearchValues<char> s_schemeChars =
 11483            SearchValues.Create("+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
 1484
 1485        // CheckSchemeName
 1486        //
 1487        //  Determines whether a string is a valid scheme name according to RFC 2396.
 1488        //  Syntax is:
 1489        //      scheme = alpha *(alpha | digit | '+' | '-' | '.')
 1490        public static bool CheckSchemeName([NotNullWhen(true)] string? schemeName) =>
 01491            !string.IsNullOrEmpty(schemeName) &&
 01492            char.IsAsciiLetter(schemeName[0]) &&
 01493            !schemeName.AsSpan().ContainsAnyExcept(s_schemeChars);
 1494
 1495        //
 1496        // IsHexDigit
 1497        //
 1498        //  Determines whether a character is a valid hexadecimal digit in the range
 1499        //  [0..9] | [A..F] | [a..f]
 1500        //
 1501        // Inputs:
 1502        //  <argument>  character
 1503        //      Character to test
 1504        //
 1505        // Returns:
 1506        //  true if <character> is a hexadecimal digit character
 1507        //
 1508        // Throws:
 1509        //  Nothing
 1510        //
 1511        public static bool IsHexDigit(char character)
 01512        {
 01513            return char.IsAsciiHexDigit(character);
 01514        }
 1515
 1516        //
 1517        // Returns:
 1518        //  Number in the range 0..15
 1519        //
 1520        // Throws:
 1521        //  ArgumentException
 1522        //
 1523        public static int FromHex(char digit)
 01524        {
 01525            int result = HexConverter.FromChar(digit);
 01526            if (result == 0xFF)
 01527            {
 01528                throw new ArgumentException(null, nameof(digit));
 1529            }
 1530
 01531            return result;
 01532        }
 1533
 1534        public override int GetHashCode()
 01535        {
 01536            if (IsNotAbsoluteUri)
 01537            {
 01538                return OriginalString.GetHashCode();
 1539            }
 1540            else
 01541            {
 01542                MoreInfo info = EnsureUriInfo().MoreInfo;
 1543
 01544                UriComponents components = UriComponents.HttpRequestUrl;
 1545
 01546                if (_syntax.InFact(UriSyntaxFlags.MailToLikeUri))
 01547                {
 01548                    components |= UriComponents.UserInfo;
 01549                }
 1550
 01551                string remoteUrl = info.RemoteUrl ??= GetParts(components, UriFormat.SafeUnescaped);
 1552
 01553                if (IsFile)
 01554                {
 01555                    return StringComparer.OrdinalIgnoreCase.GetHashCode(remoteUrl);
 1556                }
 1557                else
 01558                {
 01559                    return remoteUrl.GetHashCode();
 1560                }
 1561            }
 01562        }
 1563
 1564        //
 1565        // ToString
 1566        //
 1567        private const UriFormat V1ToStringUnescape = (UriFormat)0x7FFF;
 1568
 1569        public override string ToString()
 01570        {
 01571            if (_syntax == null)
 01572            {
 01573                return _string;
 1574            }
 1575
 01576            EnsureUriInfo();
 01577            return _info.String ??=
 01578                _syntax.IsSimple ?
 01579                    GetComponentsHelper(UriComponents.AbsoluteUri, V1ToStringUnescape) :
 01580                    GetParts(UriComponents.AbsoluteUri, UriFormat.SafeUnescaped);
 01581        }
 1582
 1583        /// <summary>
 1584        /// Attempts to format a canonical string representation for the <see cref="Uri"/> instance into the specified s
 1585        /// </summary>
 1586        /// <param name="destination">The span into which to write this instance's value formatted as a span of characte
 1587        /// <param name="charsWritten">When this method returns, contains the number of characters that were written in 
 1588        /// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</retur
 1589        public bool TryFormat(Span<char> destination, out int charsWritten)
 01590        {
 1591            ReadOnlySpan<char> result;
 1592
 01593            if (_syntax is null)
 01594            {
 01595                result = _string;
 01596            }
 1597            else
 01598            {
 01599                EnsureUriInfo();
 01600                if (_info.String is not null)
 01601                {
 01602                    result = _info.String;
 01603                }
 1604                else
 01605                {
 01606                    UriFormat uriFormat = V1ToStringUnescape;
 01607                    if (!_syntax.IsSimple)
 01608                    {
 01609                        if (IsNotAbsoluteUri)
 01610                        {
 01611                            throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 1612                        }
 1613
 01614                        if (UserDrivenParsing)
 01615                        {
 01616                            throw new InvalidOperationException(SR.Format(SR.net_uri_UserDrivenParsing, GetType()));
 1617                        }
 1618
 01619                        if (DisablePathAndQueryCanonicalization)
 01620                        {
 01621                            throw new InvalidOperationException(SR.net_uri_GetComponentsCalledWhenCanonicalizationDisabl
 1622                        }
 1623
 01624                        uriFormat = UriFormat.SafeUnescaped;
 01625                    }
 1626
 01627                    EnsureParseRemaining();
 01628                    EnsureHostString(allowDnsOptimization: true);
 1629
 01630                    ushort nonCanonical = (ushort)((ushort)_flags & (ushort)Flags.CannotDisplayCanonical);
 01631                    if (((_flags & (Flags.ShouldBeCompressed | Flags.FirstSlashAbsent | Flags.BackslashInPath)) != 0) ||
 01632                        (IsDosPath && _string[_info.Offset.Path + SecuredPathIndex - 1] == '|')) // A rare case of c|\
 01633                    {
 01634                        nonCanonical |= (ushort)Flags.PathNotCanonical;
 01635                    }
 1636
 01637                    if (((ushort)UriComponents.AbsoluteUri & nonCanonical) != 0)
 01638                    {
 01639                        return TryRecreateParts(destination, out charsWritten, UriComponents.AbsoluteUri, nonCanonical, 
 1640                    }
 1641
 01642                    result = _string.AsSpan(_info.Offset.Scheme, _info.Offset.End - _info.Offset.Scheme);
 01643                }
 01644            }
 1645
 01646            if (result.TryCopyTo(destination))
 01647            {
 01648                charsWritten = result.Length;
 01649                return true;
 1650            }
 1651
 01652            charsWritten = 0;
 01653            return false;
 01654        }
 1655
 1656        /// <inheritdoc/>
 1657        bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormat
 01658            TryFormat(destination, out charsWritten);
 1659
 1660        /// <inheritdoc/>
 1661        string IFormattable.ToString(string? format, IFormatProvider? formatProvider) =>
 01662            ToString();
 1663
 1664        public static bool operator ==(Uri? uri1, Uri? uri2)
 01665        {
 01666            if (ReferenceEquals(uri1, uri2))
 01667            {
 01668                return true;
 1669            }
 1670
 01671            if (uri1 is null || uri2 is null)
 01672            {
 01673                return false;
 1674            }
 1675
 01676            return uri1.Equals(uri2);
 01677        }
 1678
 1679        public static bool operator !=(Uri? uri1, Uri? uri2)
 01680        {
 01681            if (ReferenceEquals(uri1, uri2))
 01682            {
 01683                return false;
 1684            }
 1685
 01686            if (uri1 is null || uri2 is null)
 01687            {
 01688                return true;
 1689            }
 1690
 01691            return !uri1.Equals(uri2);
 01692        }
 1693
 1694        public override bool Equals([NotNullWhen(true)] object? comparand)
 01695        {
 01696            if (comparand is null)
 01697            {
 01698                return false;
 1699            }
 1700
 01701            if (ReferenceEquals(this, comparand))
 01702            {
 01703                return true;
 1704            }
 1705
 01706            Uri? other = comparand as Uri;
 1707
 1708            // we allow comparisons of Uri and String objects only. If a string
 1709            // is passed, convert to Uri. This is inefficient, but allows us to
 1710            // canonicalize the comparand, making comparison possible
 01711            if (other is null)
 01712            {
 01713                if (DisablePathAndQueryCanonicalization)
 01714                    return false;
 1715
 01716                if (!(comparand is string s))
 01717                    return false;
 1718
 01719                if (ReferenceEquals(s, OriginalString))
 01720                    return true;
 1721
 01722                if (!TryCreate(s, UriKind.RelativeOrAbsolute, out other))
 01723                    return false;
 01724            }
 1725
 01726            return Equals(other);
 01727        }
 1728
 1729        /// <summary>
 1730        /// Compares two <see cref="Uri"/> instances for equality.
 1731        /// </summary>
 1732        /// <param name="other">The <see cref="Uri"/> to compare to this instance.</param>
 1733        /// <returns><see langword="true"/> if the two instances represent the same URI; otherwise, <see langword="false
 1734        public bool Equals([NotNullWhen(true)] Uri? other)
 01735        {
 01736            if (other is null)
 01737            {
 01738                return false;
 1739            }
 1740
 01741            if (ReferenceEquals(this, other))
 01742            {
 01743                return true;
 1744            }
 1745
 01746            if (DisablePathAndQueryCanonicalization != other.DisablePathAndQueryCanonicalization)
 01747                return false;
 1748
 01749            if (ReferenceEquals(OriginalString, other.OriginalString))
 01750            {
 01751                return true;
 1752            }
 1753
 01754            if (IsAbsoluteUri != other.IsAbsoluteUri)
 01755                return false;
 1756
 01757            if (IsNotAbsoluteUri)
 01758                return OriginalString.Equals(other.OriginalString);
 1759
 01760            if (NotAny(Flags.AllUriInfoSet) || other.NotAny(Flags.AllUriInfoSet))
 01761            {
 1762                // Try raw compare for _strings as the last chance to keep the working set small
 01763                if (string.Equals(_string, other._string, IsUncOrDosPath ? StringComparison.OrdinalIgnoreCase : StringCo
 01764                {
 01765                    return true;
 1766                }
 01767            }
 1768
 1769            // Note that equality test will bring the working set of both
 1770            // objects up to creation of _info.MoreInfo member
 01771            EnsureUriInfo();
 01772            other.EnsureUriInfo();
 1773
 01774            if (!UserDrivenParsing && !other.UserDrivenParsing && Syntax!.IsSimple && other.Syntax.IsSimple)
 01775            {
 1776                // Optimization of canonical DNS names by avoiding host string creation.
 1777                // Note there could be explicit ports specified that would invalidate path offsets
 01778                if (InFact(Flags.CanonicalDnsHost) && other.InFact(Flags.CanonicalDnsHost))
 01779                {
 01780                    int i1 = _info.Offset.Host;
 01781                    int end1 = _info.Offset.Path;
 1782
 01783                    int i2 = other._info.Offset.Host;
 01784                    int end2 = other._info.Offset.Path;
 01785                    string str = other._string;
 1786                    //Taking the shortest part
 01787                    if (end1 - i1 > end2 - i2)
 01788                    {
 01789                        end1 = i1 + end2 - i2;
 01790                    }
 1791                    // compare and break on ':' if found
 01792                    while (i1 < end1)
 01793                    {
 01794                        if (_string[i1] != str[i2])
 01795                        {
 01796                            return false;
 1797                        }
 01798                        if (str[i2] == ':')
 01799                        {
 1800                            // The other must have ':' too to have equal host
 01801                            break;
 1802                        }
 01803                        ++i1; ++i2;
 01804                    }
 1805
 1806                    // The longest host must have ':' or be of the same size
 01807                    if (i1 < _info.Offset.Path && _string[i1] != ':')
 01808                    {
 01809                        return false;
 1810                    }
 01811                    if (i2 < end2 && str[i2] != ':')
 01812                    {
 01813                        return false;
 1814                    }
 1815                    //hosts are equal!
 01816                }
 1817                else
 01818                {
 01819                    EnsureHostString(false);
 01820                    other.EnsureHostString(false);
 01821                    if (!_info.Host!.Equals(other._info.Host))
 01822                    {
 01823                        return false;
 1824                    }
 01825                }
 1826
 01827                if (Port != other.Port)
 01828                {
 01829                    return false;
 1830                }
 01831            }
 1832
 1833            // We want to cache RemoteUrl to improve perf for Uri as a key.
 1834            // We should consider reducing the overall working set by not caching some other properties mentioned in Mor
 1835
 01836            MoreInfo selfInfo = _info.MoreInfo;
 01837            MoreInfo otherInfo = other._info.MoreInfo;
 1838
 1839            // Fragment AND UserInfo (for non-mailto URIs) are ignored
 01840            UriComponents components = UriComponents.HttpRequestUrl;
 1841
 01842            if (_syntax.InFact(UriSyntaxFlags.MailToLikeUri))
 01843            {
 01844                if (!other._syntax.InFact(UriSyntaxFlags.MailToLikeUri))
 01845                    return false;
 1846
 01847                components |= UriComponents.UserInfo;
 01848            }
 1849
 01850            string selfUrl = selfInfo.RemoteUrl ??= GetParts(components, UriFormat.SafeUnescaped);
 01851            string otherUrl = otherInfo.RemoteUrl ??= other.GetParts(components, UriFormat.SafeUnescaped);
 1852
 1853            // if IsUncOrDosPath is true then we ignore case in the path comparison
 01854            return string.Equals(selfUrl, otherUrl, IsUncOrDosPath ? StringComparison.OrdinalIgnoreCase : StringComparis
 01855        }
 1856
 1857        public Uri MakeRelativeUri(Uri uri)
 01858        {
 01859            ArgumentNullException.ThrowIfNull(uri);
 1860
 01861            if (IsNotAbsoluteUri || uri.IsNotAbsoluteUri)
 01862                throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 1863
 1864            // Note that the UserInfo part is ignored when computing a relative Uri.
 01865            if ((Scheme == uri.Scheme) && (Host == uri.Host) && (Port == uri.Port))
 01866            {
 01867                string otherPath = uri.AbsolutePath;
 1868
 1869                // Relative Path
 01870                string relativeUriString = PathDifference(AbsolutePath, otherPath, !IsUncOrDosPath);
 1871
 1872                // Relative Uri's cannot have a colon ':' in the first path segment (RFC 3986, Section 4.2)
 01873                if (CheckForColonInFirstPathSegment(relativeUriString)
 01874                    // Except for full implicit dos file paths
 01875                    && !(uri.IsDosPath && otherPath.Equals(relativeUriString, StringComparison.Ordinal)))
 01876                    relativeUriString = "./" + relativeUriString;
 1877
 1878                // Query & Fragment
 01879                relativeUriString += uri.GetParts(UriComponents.Query | UriComponents.Fragment, UriFormat.UriEscaped);
 1880
 01881                return new Uri(relativeUriString, UriKind.Relative);
 1882            }
 01883            return uri;
 01884        }
 1885
 1886        // http://www.ietf.org/rfc/rfc3986.txt
 1887        //
 1888        // 3.3.  Path
 1889        // In addition, a URI reference (Section 4.1) may be a relative-path reference, in which case the  first
 1890        // path segment cannot contain a colon (":") character.
 1891        //
 1892        // 4.2.  Relative Reference
 1893        // A path segment that contains a colon character (e.g., "this:that") cannot be used as the first segment
 1894        // of a relative-path reference, as it would be mistaken for a scheme name.  Such a segment must be
 1895        // preceded by a dot-segment (e.g., "./this:that") to make a relative-path reference.
 1896        //
 1897        // 5.4.2. Abnormal Examples
 1898        // http:(relativeUri) may be considered a valid relative Uri.
 1899        //
 1900        // Returns true if a colon is found in the first path segment, false otherwise
 11901        private static readonly SearchValues<char> s_segmentSeparatorChars =
 11902            SearchValues.Create(@":\/?#");
 1903
 1904        private static bool CheckForColonInFirstPathSegment(string uriString)
 01905        {
 1906            // Check for anything that may terminate the first regular path segment
 1907            // or an illegal colon
 01908            int index = uriString.AsSpan().IndexOfAny(s_segmentSeparatorChars);
 01909            return (uint)index < (uint)uriString.Length && uriString[index] == ':';
 01910        }
 1911
 1912        internal static string InternalEscapeString(string rawString) =>
 01913            rawString is null ? string.Empty :
 01914            UriHelper.EscapeString(rawString, checkExistingEscaped: true, UriHelper.UnreservedReservedExceptQuestionMark
 1915
 1916        //
 1917        //  This method is called first to figure out the scheme or a simple file path
 1918        //  Is called only at the .ctor time
 1919        //
 1920        private static ParsingError ParseScheme(string uriString, ref Flags flags, ref UriParser? syntax)
 6611921        {
 6611922            Debug.Assert((flags & Flags.Debug_LeftConstructor) == 0);
 1923
 6611924            int length = uriString.Length;
 6611925            if (length == 0)
 01926                return ParsingError.EmptyUriString;
 1927
 1928            // Fast path for valid http(s) schemes with no leading whitespace that are expected to be very common.
 6611929            if (uriString.StartsWith("https:", StringComparison.OrdinalIgnoreCase))
 01930            {
 01931                syntax = UriParser.HttpsUri;
 01932                flags |= (Flags)6;
 01933            }
 6611934            else if (uriString.StartsWith("http:", StringComparison.OrdinalIgnoreCase))
 6611935            {
 6611936                syntax = UriParser.HttpUri;
 6611937                flags |= (Flags)5;
 6611938            }
 1939            else
 01940            {
 1941                // STEP1: parse scheme, lookup this Uri Syntax or create one using UnknownV1SyntaxFlags uri syntax templ
 01942                ParsingError err = ParsingError.None;
 01943                int idx = ParseSchemeCheckImplicitFile(uriString, ref err, ref flags, ref syntax);
 01944                Debug.Assert((err is ParsingError.None) == (syntax is not null));
 1945
 01946                if (err != ParsingError.None)
 01947                    return err;
 1948
 01949                flags |= (Flags)idx;
 01950            }
 1951
 6611952            return ParsingError.None;
 6611953        }
 1954
 1955        //
 1956        // A wrapper for ParseMinimal() called from a user parser
 1957        // It signals back that the call has been done
 1958        // plus it communicates back a flag for an error if any
 1959        //
 1960        internal UriFormatException? ParseMinimal()
 01961        {
 01962            Debug.Assert(_syntax != null && !_syntax.IsSimple);
 01963            Debug.Assert((_flags & Flags.CustomParser_ParseMinimalAlreadyCalled) != 0);
 01964            DebugAssertInCtor();
 1965
 01966            ParsingError result = PrivateParseMinimal();
 01967            if (result == ParsingError.None)
 01968                return null;
 1969
 1970            // Means the we think the Uri is invalid, bu that can be later overridden by a user parser
 01971            _flags |= Flags.ErrorOrParsingRecursion;
 1972
 01973            return GetException(result);
 01974        }
 1975
 1976        //  This method tries to parse the minimal information needed to certify the validity
 1977        //  of a uri string
 1978        //
 1979        //      scheme://userinfo@host:Port/Path?Query#Fragment
 1980        //
 1981        //  The method must be called only at the .ctor time
 1982        //
 1983        //  Returns ParsingError.None if the Uri syntax is valid, an error otherwise
 1984        private ParsingError PrivateParseMinimal()
 6611985        {
 6611986            Debug.Assert(_syntax != null);
 6611987            DebugAssertInCtor();
 6611988            Debug.Assert(ReferenceEquals(_string, OriginalString));
 1989
 6611990            int idx = (int)(_flags & Flags.IndexMask);
 1991
 1992            // Means a custom UriParser did call "base" InitializeAndValidate()
 6611993            _flags &= ~(Flags.IndexMask | Flags.UserDrivenParsing);
 1994
 6611995            ReadOnlySpan<char> str = _string;
 1996
 1997            // Trim any trailing spaces
 6611998            while (!str.IsEmpty && UriHelper.IsLWS(str[str.Length - 1]))
 01999            {
 02000                str = str.Slice(0, str.Length - 1);
 02001            }
 2002
 2003            // Unix Path
 6612004            if (!OperatingSystem.IsWindows() && InFact(Flags.UnixPath))
 02005            {
 02006                _flags |= Flags.BasicHostType;
 02007                _flags |= (Flags)idx;
 02008                return ParsingError.None;
 2009            }
 2010
 2011            // Old Uri parser tries to figure out on a DosPath in all cases.
 2012            // Hence http://c:/ is treated as DosPath without the host while it should be a host "c", port 80
 2013            //
 2014            // This block is compatible with Old Uri parser in terms it will look for the DosPath if the scheme
 2015            // syntax allows both empty hostnames and DosPath
 6612016            if (_syntax.IsAllSet(UriSyntaxFlags.AllowEmptyHost | UriSyntaxFlags.AllowDOSPath) &&
 6612017                NotAny(Flags.ImplicitFile) &&
 6612018                (idx + 1 < str.Length))
 02019            {
 02020                int i = idx;
 2021
 2022                // V1 Compat: Allow _compression_ of > 3 slashes only for File scheme.
 2023                // This will skip all slashes and if their number is 2+ it sets the AuthorityFound flag
 02024                for (; (uint)i < (uint)str.Length; i++)
 02025                {
 02026                    if (str[i] is not ('\\' or '/'))
 02027                    {
 02028                        break;
 2029                    }
 02030                }
 2031
 02032                if (_syntax.InFact(UriSyntaxFlags.FileLikeUri) || i - idx <= 3)
 02033                {
 2034                    // if more than one slash after the scheme, the authority is present
 02035                    if (i - idx >= 2)
 02036                    {
 02037                        _flags |= Flags.AuthorityFound;
 02038                    }
 2039
 2040                    // DOS-like path?
 02041                    if ((uint)(i + 1) < (uint)str.Length && str[i + 1] is ':' or '|' && char.IsAsciiLetter(str[i]))
 02042                    {
 02043                        if ((uint)(i + 2) >= (uint)str.Length || str[i + 2] is not ('\\' or '/'))
 02044                        {
 2045                            // report an error but only for a file: scheme
 02046                            if (_syntax.InFact(UriSyntaxFlags.FileLikeUri))
 02047                                return ParsingError.MustRootedPath;
 02048                        }
 2049                        else
 02050                        {
 2051                            // This will set IsDosPath
 02052                            _flags |= Flags.DosPath;
 2053
 02054                            if (_syntax.InFact(UriSyntaxFlags.MustHaveAuthority))
 02055                            {
 2056                                // when DosPath found and Authority is required, set this flag even if Authority is empt
 02057                                _flags |= Flags.AuthorityFound;
 02058                            }
 02059                            if (i != idx && i - idx != 2)
 02060                            {
 2061                                //This will remember that DosPath is rooted
 02062                                idx = i - 1;
 02063                            }
 2064                            else
 02065                            {
 02066                                idx = i;
 02067                            }
 02068                        }
 02069                    }
 2070                    // UNC share?
 02071                    else if (_syntax.InFact(UriSyntaxFlags.FileLikeUri) &&
 02072                        i - idx >= 2 && i - idx != 3 &&
 02073                        (uint)i < (uint)str.Length && str[i] is not ('?' or '#'))
 02074                    {
 2075                        // V1.0 did not support file:///, fixing it with minimal behavior change impact
 2076                        // Only FILE scheme may have UNC Path flag set
 02077                        _flags |= Flags.UncPath;
 02078                        idx = i;
 02079                    }
 02080                    else if (!OperatingSystem.IsWindows() && _syntax.InFact(UriSyntaxFlags.FileLikeUri) && str[i - 1] ==
 02081                    {
 02082                        _syntax = UriParser.UnixFileUri;
 02083                        _flags |= Flags.UnixPath | Flags.AuthorityFound;
 02084                        idx += 2;
 02085                    }
 02086                }
 02087            }
 2088
 2089            // Decide on the Authority component
 6612090            if ((_flags & (Flags.UncPath | Flags.DosPath | Flags.UnixPath)) != 0)
 02091            {
 2092
 02093            }
 6612094            else if ((uint)(idx + 1) < (uint)str.Length)
 6612095            {
 6612096                char first = str[idx];
 6612097                char second = str[idx + 1];
 2098
 6612099                if (_syntax.InFact(UriSyntaxFlags.MustHaveAuthority))
 6612100                {
 2101                    // (V1.0 compatibility) This will allow http:\\ http:\/ http:/\
 6612102                    if ((first == '/' || first == '\\') && (second == '/' || second == '\\'))
 6612103                    {
 6612104                        _flags |= Flags.AuthorityFound;
 6612105                        idx += 2;
 6612106                    }
 2107                    else
 02108                    {
 02109                        return ParsingError.BadAuthority;
 2110                    }
 6612111                }
 02112                else if (_syntax.InFact(UriSyntaxFlags.OptionalAuthority) && (InFact(Flags.AuthorityFound) ||
 02113                    (first == '/' && second == '/')))
 02114                {
 02115                    _flags |= Flags.AuthorityFound;
 02116                    idx += 2;
 02117                }
 2118                // There is no Authority component, save the Path index
 2119                // Ideally we would treat mailto like any other URI, but for historical reasons we have to separate out 
 02120                else if (_syntax.NotAny(UriSyntaxFlags.MailToLikeUri))
 02121                {
 2122                    // By now we know the URI has no Authority, so if the URI must be normalized, initialize it without 
 02123                    if (InFact(Flags.HasUnicode))
 02124                    {
 02125                        _string = _string.Substring(0, idx);
 02126                    }
 2127                    // Since there is no Authority, the path index is just the end of the scheme.
 02128                    _flags |= ((Flags)idx | Flags.UnknownHostType);
 02129                    return ParsingError.None;
 2130                }
 6612131            }
 02132            else if (_syntax.InFact(UriSyntaxFlags.MustHaveAuthority))
 02133            {
 02134                return ParsingError.BadAuthority;
 2135            }
 2136            // There is no Authority component, save the Path index
 2137            // Ideally we would treat mailto like any other URI, but for historical reasons we have to separate out its 
 02138            else if (_syntax.NotAny(UriSyntaxFlags.MailToLikeUri))
 02139            {
 2140                // By now we know the URI has no Authority, so if the URI must be normalized, initialize it without one.
 02141                if (InFact(Flags.HasUnicode))
 02142                {
 02143                    _string = _string.Substring(0, idx);
 02144                }
 2145                // Since there is no Authority, the path index is just the end of the scheme.
 02146                _flags |= ((Flags)idx | Flags.UnknownHostType);
 02147                return ParsingError.None;
 2148            }
 2149
 2150            // vsmacros://c:\path\file
 2151            // Note that two slashes say there must be an Authority but instead the path goes
 2152            // Fro V1 compat the next block allow this case but not for schemes like http
 6612153            if (InFact(Flags.DosPath))
 02154            {
 02155                _flags |= (((_flags & Flags.AuthorityFound) != 0) ? Flags.BasicHostType : Flags.UnknownHostType);
 02156                _flags |= (Flags)idx;
 02157                return ParsingError.None;
 2158            }
 2159
 2160            //STEP 2: Check the syntax of authority expecting at least one character in it
 2161            //
 2162            // Note here we do know that there is an authority in the string OR it's a DOS path
 2163
 2164            // We may find a userInfo and the port when parsing an authority
 2165            // Also we may find a registry based authority.
 2166            // We must ensure that known schemes do use a server-based authority
 6612167            {
 6612168                string? newHost = null; // stores newly parsed host when original strings are being switched
 2169
 6612170                idx = CheckAuthorityHelper(str, idx, out ParsingError err, ref _flags, _syntax.Flags, ref newHost);
 6612171                if (err != ParsingError.None)
 02172                    return err;
 2173
 6612174                if ((uint)idx < (uint)str.Length)
 6612175                {
 6612176                    char hostTerminator = str[idx];
 2177
 2178                    // This will disallow '\' as the host terminator for any scheme that is not implicitFile or cannot h
 6612179                    if (hostTerminator == '\\' && NotAny(Flags.ImplicitFile) && _syntax.NotAny(UriSyntaxFlags.AllowDOSPa
 02180                    {
 02181                        return ParsingError.BadAuthorityTerminator;
 2182                    }
 2183                    // When the hostTerminator is '/' on Unix, use the UnixFile syntax (preserve backslashes)
 6612184                    else if (!OperatingSystem.IsWindows() && hostTerminator == '/' && NotAny(Flags.ImplicitFile) && InFa
 02185                    {
 02186                        _syntax = UriParser.UnixFileUri;
 02187                    }
 6612188                }
 2189
 6612190                if (newHost is not null)
 02191                {
 02192                    _string = newHost;
 02193                }
 6612194            }
 2195
 2196            // The Path (or Port) parsing index is reloaded on demand in CreateUriInfo when accessing a Uri property
 6612197            _flags |= (Flags)idx;
 2198
 2199            // The rest of the string will be parsed on demand
 2200            // The Host/Authority is all checked, the type is known but the host value string
 2201            // is not created/canonicalized at this point.
 2202
 6612203            return ParsingError.None;
 6612204        }
 2205
 2206        //
 2207        //
 2208        // The method is called when we have to access _info members.
 2209        // This will create the _info based on the copied parser context.
 2210        // If multi-threading, this method may do duplicated yet harmless work.
 2211        //
 2212        private void CreateUriInfo(Flags cF)
 6612213        {
 6612214            UriInfo info = new UriInfo();
 2215
 6612216            string str = OriginalString;
 2217
 2218            // This will be revisited in ParseRemaining if we're rewriting the _string.
 6612219            info.Offset.End = str.Length;
 2220
 6612221            if (UserDrivenParsing)
 02222                goto Done;
 2223
 2224            int idx;
 6612225            bool notCanonicalScheme = false;
 2226
 2227            // Skip leading whitespace
 6612228            idx = 0;
 6612229            while (UriHelper.IsLWS(str[idx]))
 02230            {
 02231                idx++;
 02232                info.Offset.Scheme++;
 02233            }
 2234
 6612235            if ((cF & Flags.ImplicitFile) != 0)
 02236            {
 02237                if (StaticInFact(cF, Flags.UncPath))
 02238                {
 02239                    Debug.Assert(str[idx] is '/' or '\\' && str[idx + 1] is '/' or '\\');
 2240
 2241                    // For implicit file AND Unc only
 02242                    idx += 2;
 2243
 2244                    // Skip any other slashes (compatibility with V1.0 parser)
 02245                    int end = (int)(cF & Flags.IndexMask);
 02246                    while (idx < end && (str[idx] == '/' || str[idx] == '\\'))
 02247                    {
 02248                        ++idx;
 02249                    }
 02250                }
 02251            }
 2252            else
 6612253            {
 2254                // This is NOT an ImplicitFile uri
 6612255                idx += _syntax.SchemeName.Length + 1;
 6612256                Debug.Assert(str[idx - 1] == ':');
 2257
 6612258                if ((cF & Flags.AuthorityFound) != 0)
 6612259                {
 6612260                    if (str[idx] is '/' or '\\' && str[idx + 1] is '/' or '\\')
 6612261                    {
 6612262                        if (str[idx] == '\\' || str[idx + 1] == '\\')
 02263                        {
 02264                            notCanonicalScheme = true;
 02265                        }
 2266
 6612267                        idx += 2;
 6612268                    }
 2269                    else
 02270                    {
 02271                        Debug.Assert(IsDosPath);
 02272                        Debug.Assert(char.IsAsciiLetter(str.AsSpan(idx).TrimStart(['/', '\\'])[0]));
 02273                        Debug.Assert(str.AsSpan(idx).TrimStart(['/', '\\'])[1] is ':' or '|');
 02274                    }
 2275
 6612276                    if ((cF & (Flags.UncPath | Flags.DosPath)) != 0)
 02277                    {
 2278                        // Skip slashes if it was allowed during ctor time
 2279                        // NB: Today this is only allowed if a Unc or DosPath was found after the scheme
 02280                        int end = (int)(cF & Flags.IndexMask);
 02281                        while (idx < end && (str[idx] == '/' || str[idx] == '\\'))
 02282                        {
 02283                            notCanonicalScheme = true;
 02284                            ++idx;
 02285                        }
 02286                    }
 6612287                }
 6612288            }
 2289
 2290            // Up until the userinfo/host, the _string and OriginalString are the same.
 6612291            Debug.Assert(_string.AsSpan(0, idx).SequenceEqual(OriginalString.AsSpan(0, idx)));
 2292
 2293            // Some schemes (mailto) do not have Authority-based syntax, still they do have a port
 6612294            if (_syntax.DefaultPort != UriParser.NoDefaultPort)
 6612295                info.Offset.PortValue = (ushort)_syntax.DefaultPort;
 2296
 6612297            info.Offset.User = idx;
 2298
 6612299            if ((cF & Flags.HasUserInfo) != 0)
 02300            {
 2301                // We've seen the '@' before. It should also be the first '@' in the input.
 02302                Debug.Assert(str.AsSpan(idx).Contains('@'));
 02303                Debug.Assert(str.IndexOf('@') == str.IndexOf('@', idx));
 2304
 2305                // The UserInfo in _string may have been modified if we're recreating the string.
 2306                // Since Offset.Host must point into _string and not OriginalString, find the offset in the new string.
 02307                idx = _string.IndexOf('@') + 1;
 02308            }
 2309
 6612310            info.Offset.Host = idx;
 2311
 2312            // Now reload the end of the parsed host.
 2313            // This index is pointing into the original string.
 6612314            idx = (int)(cF & Flags.IndexMask);
 6612315            Debug.Assert((uint)idx <= (uint)OriginalString.Length);
 2316
 2317            // From now on we do not need IndexMask bits, and reuse the space for X_NotCanonical flags
 6612318            cF &= ~Flags.IndexMask;
 2319
 6612320            if (notCanonicalScheme)
 02321            {
 02322                cF |= Flags.SchemeNotCanonical;
 02323            }
 2324
 2325            // UnknownHostType / DosPath can't have a port.
 6612326            if ((cF & Flags.HostTypeMask) != Flags.UnknownHostType && (cF & Flags.DosPath) == 0 &&
 6612327                (uint)idx < (uint)str.Length && str[idx] == ':')
 02328            {
 02329                Debug.Assert(!IsUnc);
 2330
 2331                // Parse the Port if any. The new spec allows a port after ':' to be empty (assuming default?)
 2332                // Note we already checked on general port syntax in ParseMinimal()
 02333                bool notEmpty = false;
 02334                int port = 0;
 2335
 02336                idx++; // Skip ':'
 2337
 2338                // Check on some non-canonical cases http://host:0324/, http://host:03, http://host:0, etc
 02339                if ((uint)idx < (uint)str.Length)
 02340                {
 02341                    port = str[idx] - '0';
 02342                    if ((uint)port <= ('9' - '0'))
 02343                    {
 02344                        notEmpty = true;
 02345                        if (port == 0)
 02346                        {
 02347                            cF |= (Flags.PortNotCanonical | Flags.E_PortNotCanonical);
 02348                        }
 2349
 02350                        for (idx++; (uint)idx < (uint)str.Length; idx++)
 02351                        {
 02352                            int val = str[idx] - '0';
 02353                            if ((uint)val > ('9' - '0'))
 02354                            {
 02355                                break;
 2356                            }
 02357                            port = (port * 10 + val);
 02358                        }
 02359                    }
 02360                }
 2361
 02362                if (notEmpty && _syntax.DefaultPort != port)
 02363                {
 02364                    info.Offset.PortValue = (ushort)port;
 02365                    cF |= Flags.NotDefaultPort;
 02366                }
 2367                else
 02368                {
 2369                    // This will tell that we do have a ':' but the port value does
 2370                    // not follow to canonical rules
 02371                    cF |= (Flags.PortNotCanonical | Flags.E_PortNotCanonical);
 02372                }
 02373            }
 2374
 2375            // While info.Offset values must point into _string instead of OriginalString when we're done with parsing,
 2376            // we'll temporarily point the path offset into OriginalString.
 2377            // ParseRemaining will update the value if we're replacing the _string.
 6612378            info.Offset.Path = idx;
 2379
 6612380        Done:
 6612381            cF |= Flags.MinimalUriInfoSet;
 2382
 6612383            Interlocked.CompareExchange(ref _info, info, null!);
 2384
 6612385            Flags current = _flags;
 6612386            while ((current & Flags.MinimalUriInfoSet) == 0)
 6612387            {
 6612388                Flags oldValue = Interlocked.CompareExchange(ref _flags, (current & ~Flags.IndexMask) | cF, current);
 6612389                if (oldValue == current)
 6612390                {
 6612391                    return;
 2392                }
 02393                current = oldValue;
 02394            }
 6612395        }
 2396
 2397        //
 2398        // This will create a Host string. The validity has been already checked
 2399        //
 2400        // Assuming: UriInfo member is already set at this point
 2401        private void CreateHostString()
 02402        {
 02403            if (!_syntax.IsSimple)
 02404            {
 02405                lock (_info)
 02406                {
 2407                    // ATTN: Avoid possible recursion through
 2408                    // CreateHostString->Syntax.GetComponents->Uri.GetComponentsHelper->CreateHostString
 02409                    if (NotAny(Flags.ErrorOrParsingRecursion))
 02410                    {
 02411                        _flags |= Flags.ErrorOrParsingRecursion;
 2412                        // Need to get host string through the derived type
 02413                        GetHostViaCustomSyntax();
 02414                        _flags &= ~Flags.ErrorOrParsingRecursion;
 02415                        return;
 2416                    }
 02417                }
 02418            }
 2419
 02420            Flags flags = _flags;
 02421            string host = CreateHostStringHelper(_string, _info.Offset.Host, _info.Offset.Path, ref flags, _info);
 2422
 2423            // now check on canonical host representation
 02424            if (host.Length != 0)
 02425            {
 2426                // An Authority may need escaping except when it's an inet server address
 02427                if (HostType == Flags.BasicHostType)
 02428                {
 02429                    Check result = CheckCanonical(host, c_DummyChar, out _);
 2430
 02431                    if ((result & Check.DisplayCanonical) == 0)
 02432                    {
 2433                        // For implicit file the user string must be in perfect display format,
 2434                        // Hence, ignoring complains from CheckCanonical()
 02435                        if (NotAny(Flags.ImplicitFile) || (result & Check.ReservedFound) != 0)
 02436                        {
 02437                            flags |= Flags.HostNotCanonical;
 02438                        }
 02439                    }
 2440
 02441                    if (InFact(Flags.ImplicitFile) && (result & (Check.ReservedFound | Check.EscapedCanonical)) != 0)
 02442                    {
 2443                        // need to re-escape this host if any escaped sequence was found
 02444                        result &= ~Check.EscapedCanonical;
 02445                    }
 2446
 02447                    if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical)
 02448                    {
 2449                        // we will make a canonical host in _info.Host, but mark that _string holds wrong data
 02450                        flags |= Flags.E_HostNotCanonical;
 02451                        if (NotAny(Flags.UserEscaped))
 02452                        {
 02453                            host = UriHelper.EscapeString(host, checkExistingEscaped: !IsImplicitFile, UriHelper.Unreser
 02454                        }
 2455                        else
 02456                        {
 2457                            // We should throw here but currently just accept user input known as invalid
 02458                        }
 02459                    }
 02460                }
 02461                else if (NotAny(Flags.CanonicalDnsHost))
 02462                {
 2463                    // Check to see if we can take the canonical host string out of _string
 02464                    if (_info._moreInfo?.ScopeId is not null)
 02465                    {
 2466                        // IPv6 ScopeId is included when serializing a Uri
 02467                        flags |= (Flags.HostNotCanonical | Flags.E_HostNotCanonical);
 02468                    }
 2469                    else
 02470                    {
 02471                        for (int i = 0; i < host.Length; ++i)
 02472                        {
 02473                            if ((_info.Offset.Host + i) >= _info.Offset.End ||
 02474                                host[i] != _string[_info.Offset.Host + i])
 02475                            {
 02476                                flags |= (Flags.HostNotCanonical | Flags.E_HostNotCanonical);
 02477                                break;
 2478                            }
 02479                        }
 02480                    }
 02481                }
 02482            }
 2483
 02484            _info.Host = host;
 02485            InterlockedSetFlags(flags);
 02486        }
 2487
 2488        private static string CreateHostStringHelper(string str, int idx, int end, ref Flags flags, UriInfo info)
 02489        {
 02490            bool loopback = false;
 2491            string host;
 02492            switch (flags & Flags.HostTypeMask)
 2493            {
 2494                case Flags.DnsHostType:
 02495                    host = DomainNameHelper.ParseCanonicalName(str, idx, end, ref loopback);
 02496                    break;
 2497
 2498                case Flags.IPv6HostType:
 2499                    // The helper will return [...] string that is not suited for Dns.Resolve()
 02500                    host = IPv6AddressHelper.ParseCanonicalName(str.AsSpan(idx), ref loopback, out ReadOnlySpan<char> sc
 2501
 02502                    if (!scopeIdSpan.IsEmpty)
 02503                    {
 02504                        info.MoreInfo.ScopeId = new string(scopeIdSpan);
 02505                    }
 02506                    break;
 2507
 2508                case Flags.IPv4HostType:
 02509                    host = IPv4AddressHelper.ParseCanonicalName(str, idx, end, ref loopback);
 02510                    break;
 2511
 2512                case Flags.UncHostType:
 02513                    host = UncNameHelper.ParseCanonicalName(str, idx, end, ref loopback);
 02514                    break;
 2515
 2516                case Flags.BasicHostType:
 02517                    if (StaticInFact(flags, Flags.DosPath))
 02518                    {
 02519                        host = string.Empty;
 02520                    }
 2521                    else
 02522                    {
 2523                        // This is for a registry-based authority, not relevant for known schemes
 02524                        host = str.Substring(idx, end - idx);
 02525                    }
 2526                    // A empty host would count for a loopback
 02527                    if (host.Length == 0)
 02528                    {
 02529                        loopback = true;
 02530                    }
 2531                    //there will be no port
 02532                    break;
 2533
 2534                case Flags.UnknownHostType:
 2535                    //means the host is *not expected* for this uri type
 02536                    host = string.Empty;
 02537                    break;
 2538
 2539                default:
 02540                    throw GetException(ParsingError.BadHostName)!;
 2541            }
 2542
 02543            if (loopback)
 02544            {
 02545                flags |= Flags.LoopbackHost;
 02546            }
 02547            return host;
 02548        }
 2549
 2550        //
 2551        // Called under lock()
 2552        //
 2553        private void GetHostViaCustomSyntax()
 02554        {
 2555            // A multithreading check
 02556            if (_info.Host != null)
 02557                return;
 2558
 02559            string host = _syntax.InternalGetComponents(this, UriComponents.Host, UriFormat.UriEscaped);
 2560
 2561            // ATTN: Check on whether recursion has not happened
 02562            if (_info.Host is null)
 02563            {
 02564                Flags flags = _flags & ~Flags.HostTypeMask;
 02565                string? newHost = null;
 2566
 02567                if (CheckAuthorityHelper(host, 0, out ParsingError err, ref flags, _syntax.Flags, ref newHost) != host.L
 02568                    err != ParsingError.None)
 02569                {
 2570                    // Well, custom parser has returned a not known host type, take it as Basic then.
 02571                    _flags = (_flags & ~Flags.HostTypeMask) | Flags.BasicHostType;
 02572                }
 2573                else
 02574                {
 02575                    host = CreateHostStringHelper(host, 0, host.Length, ref flags, _info);
 2576
 02577                    for (int i = 0; i < host.Length; ++i)
 02578                    {
 02579                        if ((_info.Offset.Host + i) >= _info.Offset.End || host[i] != _string[_info.Offset.Host + i])
 02580                        {
 02581                            _flags |= (Flags.HostNotCanonical | Flags.E_HostNotCanonical);
 02582                            break;
 2583                        }
 02584                    }
 02585                    _flags = (_flags & ~Flags.HostTypeMask) | (flags & Flags.HostTypeMask);
 02586                }
 02587            }
 2588            //
 2589            // This is a chance for a custom parser to report a different port value
 2590            //
 02591            string portStr = _syntax.InternalGetComponents(this, UriComponents.StrongPort, UriFormat.UriEscaped);
 02592            int port = 0;
 02593            if (string.IsNullOrEmpty(portStr))
 02594            {
 2595                // It's like no port
 02596                _flags &= ~Flags.NotDefaultPort;
 02597                _flags |= (Flags.PortNotCanonical | Flags.E_PortNotCanonical);
 02598                _info.Offset.PortValue = 0;
 02599            }
 2600            else
 02601            {
 02602                for (int idx = 0; idx < portStr.Length; ++idx)
 02603                {
 02604                    int val = portStr[idx] - '0';
 02605                    if (val < 0 || val > 9 || (port = (port * 10 + val)) > 0xFFFF)
 02606                        throw new UriFormatException(SR.Format(SR.net_uri_PortOutOfRange, _syntax.GetType(), portStr));
 02607                }
 02608                if (port != _info.Offset.PortValue)
 02609                {
 02610                    if (port == _syntax.DefaultPort)
 02611                        _flags &= ~Flags.NotDefaultPort;
 2612                    else
 02613                        _flags |= Flags.NotDefaultPort;
 2614
 02615                    _flags |= (Flags.PortNotCanonical | Flags.E_PortNotCanonical);
 02616                    _info.Offset.PortValue = (ushort)port;
 02617                }
 02618            }
 2619            // This must be done as the last thing in this method
 02620            _info.Host = host;
 02621        }
 2622
 2623        //
 2624        // An internal shortcut into Uri extensibility API
 2625        //
 2626        internal string GetParts(UriComponents uriParts, UriFormat formatAs)
 02627        {
 02628            return InternalGetComponents(uriParts, formatAs);
 02629        }
 2630
 2631        private string GetEscapedParts(UriComponents uriParts)
 02632        {
 02633            Debug.Assert(_info != null && (_flags & Flags.MinimalUriInfoSet) != 0);
 2634
 2635            // Which Uri parts are not escaped canonically ?
 2636            // Notice that public UriPart and private Flags must be in Sync so below code can work
 2637            //
 02638            ushort nonCanonical = unchecked((ushort)(((ushort)_flags & ((ushort)Flags.CannotDisplayCanonical << 7)) >> 6
 02639            if (InFact(Flags.SchemeNotCanonical))
 02640            {
 02641                nonCanonical |= (ushort)Flags.SchemeNotCanonical;
 02642            }
 2643
 2644            // We keep separate flags for some of path canonicalization facts
 02645            if ((uriParts & UriComponents.Path) != 0)
 02646            {
 02647                if (InFact(Flags.ShouldBeCompressed | Flags.FirstSlashAbsent | Flags.BackslashInPath))
 02648                {
 02649                    nonCanonical |= (ushort)Flags.PathNotCanonical;
 02650                }
 02651                else if (IsDosPath && _string[_info.Offset.Path + SecuredPathIndex - 1] == '|')
 02652                {
 2653                    // A rare case of c|\
 02654                    nonCanonical |= (ushort)Flags.PathNotCanonical;
 02655                }
 02656            }
 2657
 02658            if ((unchecked((ushort)uriParts) & nonCanonical) == 0)
 02659            {
 02660                string? ret = GetUriPartsFromUserString(uriParts);
 02661                if (ret is not null)
 02662                {
 02663                    return ret;
 2664                }
 02665            }
 2666
 02667            return RecreateParts(uriParts, nonCanonical, UriFormat.UriEscaped);
 02668        }
 2669
 2670        private string GetUnescapedParts(UriComponents uriParts, UriFormat formatAs)
 02671        {
 02672            Debug.Assert(_info != null && (_flags & Flags.MinimalUriInfoSet) != 0);
 2673
 2674            // Which Uri parts are not escaped canonically ?
 2675            // Notice that public UriComponents and private Uri.Flags must be in Sync so below code can work
 2676            //
 02677            ushort nonCanonical = unchecked((ushort)((ushort)_flags & (ushort)Flags.CannotDisplayCanonical));
 2678
 2679            // We keep separate flags for some of path canonicalization facts
 02680            if ((uriParts & UriComponents.Path) != 0)
 02681            {
 02682                if ((_flags & (Flags.ShouldBeCompressed | Flags.FirstSlashAbsent | Flags.BackslashInPath)) != 0)
 02683                {
 02684                    nonCanonical |= (ushort)Flags.PathNotCanonical;
 02685                }
 02686                else if (IsDosPath && _string[_info.Offset.Path + SecuredPathIndex - 1] == '|')
 02687                {
 2688                    // A rare case of c|\
 02689                    nonCanonical |= (ushort)Flags.PathNotCanonical;
 02690                }
 02691            }
 2692
 02693            if ((unchecked((ushort)uriParts) & nonCanonical) == 0)
 02694            {
 02695                string? ret = GetUriPartsFromUserString(uriParts);
 02696                if (ret is not null)
 02697                {
 02698                    return ret;
 2699                }
 02700            }
 2701
 02702            return RecreateParts(uriParts, nonCanonical, formatAs);
 02703        }
 2704
 2705        private string RecreateParts(UriComponents parts, ushort nonCanonical, UriFormat formatAs)
 02706        {
 02707            EnsureHostString(allowDnsOptimization: false);
 2708
 02709            string str = _string;
 2710
 02711            var dest = str.Length <= StackallocThreshold
 02712                ? new ValueStringBuilder(stackalloc char[StackallocThreshold])
 02713                : new ValueStringBuilder(str.Length);
 2714
 02715            scoped ReadOnlySpan<char> result = RecreateParts(ref dest, str, parts, nonCanonical, formatAs);
 2716
 02717            string s = result.ToString();
 02718            dest.Dispose();
 02719            return s;
 02720        }
 2721
 2722        private bool TryRecreateParts(scoped Span<char> span, out int charsWritten, UriComponents parts, ushort nonCanon
 02723        {
 02724            EnsureHostString(allowDnsOptimization: false);
 2725
 02726            string str = _string;
 2727
 02728            var dest = str.Length <= StackallocThreshold
 02729                ? new ValueStringBuilder(stackalloc char[StackallocThreshold])
 02730                : new ValueStringBuilder(str.Length);
 2731
 02732            scoped ReadOnlySpan<char> result = RecreateParts(ref dest, str, parts, nonCanonical, formatAs);
 2733
 02734            bool copied = result.TryCopyTo(span);
 02735            charsWritten = copied ? result.Length : 0;
 02736            dest.Dispose();
 02737            return copied;
 02738        }
 2739
 2740        private ReadOnlySpan<char> RecreateParts(scoped ref ValueStringBuilder dest, string str, UriComponents parts, us
 02741        {
 02742            Debug.Assert(InFact(Flags.AllUriInfoSet));
 2743
 2744            //Scheme and slashes
 02745            if ((parts & UriComponents.Scheme) != 0)
 02746            {
 02747                dest.Append(_syntax.SchemeName);
 02748                if (parts != UriComponents.Scheme)
 02749                {
 02750                    dest.Append(':');
 02751                    if (InFact(Flags.AuthorityFound))
 02752                    {
 02753                        dest.Append('/');
 02754                        dest.Append('/');
 02755                    }
 02756                }
 02757            }
 2758
 2759            //UserInfo
 02760            if ((parts & UriComponents.UserInfo) != 0 && InFact(Flags.HasUserInfo))
 02761            {
 02762                ReadOnlySpan<char> slice = str.AsSpan(_info.Offset.User, _info.Offset.Host - _info.Offset.User);
 2763
 02764                if ((nonCanonical & (ushort)UriComponents.UserInfo) != 0)
 02765                {
 02766                    switch (formatAs)
 2767                    {
 2768                        case UriFormat.UriEscaped:
 02769                            if (NotAny(Flags.UserEscaped))
 02770                            {
 02771                                UriHelper.EscapeString(slice, ref dest, checkExistingEscaped: true, UriHelper.Unreserved
 02772                            }
 2773                            else
 02774                            {
 2775                                // We would ideally throw here if InFact(Flags.E_UserNotCanonical) but currently just ac
 02776                                dest.Append(slice);
 02777                            }
 02778                            break;
 2779
 2780                        case UriFormat.SafeUnescaped:
 02781                            UriHelper.UnescapeString(slice[..^1],
 02782                                ref dest, '@', '/', '\\',
 02783                                InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape,
 02784                                _syntax, isQuery: false);
 02785                            dest.Append('@');
 02786                            break;
 2787
 2788                        case UriFormat.Unescaped:
 02789                            UriHelper.Unescape(slice, ref dest);
 02790                            break;
 2791
 2792                        default: //V1ToStringUnescape
 02793                            dest.Append(slice);
 02794                            break;
 2795                    }
 02796                }
 2797                else
 02798                {
 02799                    dest.Append(slice);
 02800                }
 2801
 02802                if (parts == UriComponents.UserInfo)
 02803                {
 2804                    //strip '@' delimiter
 02805                    dest.Length--;
 02806                }
 02807            }
 2808
 2809            // Host
 02810            if ((parts & UriComponents.Host) != 0)
 02811            {
 02812                string host = _info.Host!;
 2813
 02814                if (host.Length != 0)
 02815                {
 02816                    var hostBuilder = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
 2817
 2818                    // NormalizedHost
 02819                    if ((parts & UriComponents.NormalizedHost) != 0)
 02820                    {
 02821                        host = UriHelper.StripBidiControlCharacters(host, host);
 2822
 2823                        // Upconvert any punycode to unicode, xn--pck -> ?
 02824                        if (!DomainNameHelper.TryGetUnicodeEquivalent(host, ref hostBuilder))
 02825                        {
 02826                            hostBuilder.Length = 0;
 02827                        }
 02828                    }
 2829
 02830                    ReadOnlySpan<char> hostSlice = hostBuilder.Length == 0 ? host : hostBuilder.AsSpan();
 2831
 02832                    if (formatAs != UriFormat.UriEscaped && HostType == Flags.BasicHostType && (nonCanonical & (ushort)U
 02833                    {
 2834                        // only Basic host could be in the escaped form
 02835                        if (formatAs == UriFormat.Unescaped)
 02836                        {
 02837                            UriHelper.Unescape(hostSlice, ref dest);
 02838                        }
 2839                        else
 02840                        {
 02841                            UriHelper.UnescapeString(hostSlice,
 02842                                ref dest, '/', '?', '#',
 02843                                InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape,
 02844                                _syntax, isQuery: false);
 02845                        }
 02846                    }
 2847                    else
 02848                    {
 02849                        dest.Append(hostSlice);
 02850                    }
 2851
 02852                    hostBuilder.Dispose();
 2853
 2854                    // A fix up only for SerializationInfo and IpV6 host with a scopeID
 02855                    if ((parts & UriComponents.SerializationInfoString) != 0 && HostType == Flags.IPv6HostType && _info.
 02856                    {
 02857                        dest.Length--;
 02858                        dest.Append(scopeId);
 02859                        dest.Append(']');
 02860                    }
 02861                }
 02862            }
 2863
 2864            //Port (always wants a ':' delimiter if got to this method)
 02865            if ((parts & UriComponents.Port) != 0 &&
 02866                (InFact(Flags.NotDefaultPort) || ((parts & UriComponents.StrongPort) != 0 && _syntax.DefaultPort != UriP
 02867            {
 02868                dest.Append(':');
 2869
 2870                const int MaxUshortLength = 5;
 02871                bool success = _info.Offset.PortValue.TryFormat(dest.AppendSpan(MaxUshortLength), out int charsWritten);
 02872                Debug.Assert(success);
 02873                dest.Length -= MaxUshortLength - charsWritten;
 02874            }
 2875
 2876            //Path
 02877            if ((parts & UriComponents.Path) != 0)
 02878            {
 02879                GetCanonicalPath(ref dest, formatAs);
 2880
 2881                // (possibly strip the leading '/' delimiter)
 02882                if (parts == UriComponents.Path)
 02883                {
 2884                    int offset;
 02885                    if (InFact(Flags.AuthorityFound) && dest.Length != 0 && dest[0] == '/')
 02886                    {
 02887                        offset = 1;
 02888                    }
 2889                    else
 02890                    {
 02891                        offset = 0;
 02892                    }
 2893
 02894                    return dest.AsSpan(offset);
 2895                }
 02896            }
 2897
 2898            //Query (possibly strip the '?' delimiter)
 02899            if ((parts & UriComponents.Query) != 0 && _info.Offset.Query < _info.Offset.Fragment)
 02900            {
 02901                int offset = (_info.Offset.Query + 1);
 02902                if (parts != UriComponents.Query)
 02903                    dest.Append('?');
 2904
 02905                FormatQueryOrFragment(str.AsSpan(offset, _info.Offset.Fragment - offset), ref dest, nonCanonical, format
 02906            }
 2907
 2908            //Fragment (possibly strip the '#' delimiter)
 02909            if ((parts & UriComponents.Fragment) != 0 && _info.Offset.Fragment < _info.Offset.End)
 02910            {
 02911                int offset = _info.Offset.Fragment + 1;
 02912                if (parts != UriComponents.Fragment)
 02913                    dest.Append('#');
 2914
 02915                FormatQueryOrFragment(str.AsSpan(offset, _info.Offset.End - offset), ref dest, nonCanonical, formatAs, i
 02916            }
 2917
 02918            return dest.AsSpan();
 2919
 2920            void FormatQueryOrFragment(ReadOnlySpan<char> slice, ref ValueStringBuilder dest, ushort nonCanonical, UriFo
 02921            {
 02922                if ((nonCanonical & (ushort)(isQuery ? UriComponents.Query : UriComponents.Fragment)) == 0)
 02923                {
 02924                    dest.Append(slice);
 02925                }
 2926                else
 02927                {
 02928                    if (formatAs == UriFormat.UriEscaped)
 02929                    {
 02930                        if (NotAny(Flags.UserEscaped))
 02931                        {
 02932                            UriHelper.EscapeString(slice, ref dest, checkExistingEscaped: true, isQuery ? UriHelper.Unre
 02933                        }
 2934                        else
 02935                        {
 02936                            dest.Append(slice);
 02937                        }
 02938                    }
 02939                    else if (formatAs == UriFormat.Unescaped)
 02940                    {
 02941                        UriHelper.Unescape(slice, ref dest);
 02942                    }
 2943                    else
 02944                    {
 02945                        UnescapeMode mode = InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnesc
 2946
 02947                        if (formatAs == V1ToStringUnescape)
 02948                        {
 02949                            mode |= UnescapeMode.V1ToStringFlag;
 02950                        }
 2951
 02952                        UriHelper.UnescapeString(slice,
 02953                            ref dest, '#', c_DummyChar, c_DummyChar,
 02954                            mode, _syntax, isQuery);
 02955                    }
 02956                }
 02957            }
 02958        }
 2959
 2960        //
 2961        // This method is called only if the user string has a canonical representation
 2962        // of requested parts
 2963        //
 2964        private string? GetUriPartsFromUserString(UriComponents uriParts)
 02965        {
 2966            int delimiterAwareIdx;
 2967
 02968            switch (uriParts & ~UriComponents.KeepDelimiter)
 2969            {
 2970                // For FindServicePoint perf
 2971                case UriComponents.Scheme | UriComponents.Host | UriComponents.Port:
 02972                    if (!InFact(Flags.HasUserInfo))
 02973                        return _string.Substring(_info.Offset.Scheme, _info.Offset.Path - _info.Offset.Scheme);
 2974
 02975                    return string.Concat(
 02976                        _string.AsSpan(_info.Offset.Scheme, _info.Offset.User - _info.Offset.Scheme),
 02977                        _string.AsSpan(_info.Offset.Host, _info.Offset.Path - _info.Offset.Host));
 2978
 2979                // For HttpWebRequest.ConnectHostAndPort perf
 2980                case UriComponents.HostAndPort:  //Host|StrongPort
 2981
 02982                    if (!InFact(Flags.HasUserInfo))
 02983                        goto case UriComponents.StrongAuthority;
 2984
 02985                    if (InFact(Flags.NotDefaultPort) || _syntax.DefaultPort == UriParser.NoDefaultPort)
 02986                        return _string.Substring(_info.Offset.Host, _info.Offset.Path - _info.Offset.Host);
 2987
 02988                    return string.Concat(_string.AsSpan(_info.Offset.Host, _info.Offset.Path - _info.Offset.Host),
 02989                        ":", _info.Offset.PortValue.ToString(CultureInfo.InvariantCulture));
 2990
 2991                // For an obvious common case perf
 2992                case UriComponents.AbsoluteUri:     //Scheme|UserInfo|Host|Port|Path|Query|Fragment,
 02993                    if (_info.Offset.Scheme == 0 && _info.Offset.End == _string.Length)
 02994                        return _string;
 2995
 02996                    return _string.Substring(_info.Offset.Scheme, _info.Offset.End - _info.Offset.Scheme);
 2997
 2998                // For Uri.Equals() and HttpWebRequest through a proxy perf
 2999                case UriComponents.HttpRequestUrl:   //Scheme|Host|Port|Path|Query,
 03000                    if (InFact(Flags.HasUserInfo))
 03001                    {
 03002                        return string.Concat(
 03003                            _string.AsSpan(_info.Offset.Scheme, _info.Offset.User - _info.Offset.Scheme),
 03004                            _string.AsSpan(_info.Offset.Host, _info.Offset.Fragment - _info.Offset.Host));
 3005                    }
 03006                    if (_info.Offset.Scheme == 0 && _info.Offset.Fragment == _string.Length)
 03007                        return _string;
 3008
 03009                    return _string.Substring(_info.Offset.Scheme, _info.Offset.Fragment - _info.Offset.Scheme);
 3010
 3011                // For CombineUri() perf
 3012                case UriComponents.SchemeAndServer | UriComponents.UserInfo:
 03013                    return _string.Substring(_info.Offset.Scheme, _info.Offset.Path - _info.Offset.Scheme);
 3014
 3015                // For Cache perf
 3016                case (UriComponents.AbsoluteUri & ~UriComponents.Fragment):
 03017                    if (_info.Offset.Scheme == 0 && _info.Offset.Fragment == _string.Length)
 03018                        return _string;
 3019
 03020                    return _string.Substring(_info.Offset.Scheme, _info.Offset.Fragment - _info.Offset.Scheme);
 3021
 3022
 3023                // Strip scheme delimiter if was not requested
 3024                case UriComponents.Scheme:
 03025                    if (uriParts != UriComponents.Scheme)
 03026                        return _string.Substring(_info.Offset.Scheme, _info.Offset.User - _info.Offset.Scheme);
 3027
 03028                    return _syntax.SchemeName;
 3029
 3030                // KeepDelimiter makes no sense for this component
 3031                case UriComponents.Host:
 03032                    int idx = _info.Offset.Path;
 03033                    if (InFact(Flags.NotDefaultPort | Flags.PortNotCanonical))
 03034                    {
 3035                        //Means we do have ':' after the host
 03036                        while (_string[--idx] != ':')
 03037                            ;
 03038                    }
 03039                    return (idx - _info.Offset.Host == 0) ? string.Empty : _string.Substring(_info.Offset.Host,
 03040                        idx - _info.Offset.Host);
 3041
 3042                case UriComponents.Path:
 3043
 3044                    // Strip the leading '/' for a hierarchical URI if no delimiter was requested
 03045                    if (uriParts == UriComponents.Path && InFact(Flags.AuthorityFound) &&
 03046                        _info.Offset.End > _info.Offset.Path && _string[_info.Offset.Path] == '/')
 03047                        delimiterAwareIdx = _info.Offset.Path + 1;
 3048                    else
 03049                        delimiterAwareIdx = _info.Offset.Path;
 3050
 03051                    if (delimiterAwareIdx >= _info.Offset.Query)
 03052                        return string.Empty;
 3053
 3054
 03055                    return _string.Substring(delimiterAwareIdx, _info.Offset.Query - delimiterAwareIdx);
 3056
 3057                case UriComponents.Query:
 3058                    // Strip the '?' if no delimiter was requested
 03059                    if (uriParts == UriComponents.Query)
 03060                        delimiterAwareIdx = _info.Offset.Query + 1;
 3061                    else
 03062                        delimiterAwareIdx = _info.Offset.Query;
 3063
 03064                    if (delimiterAwareIdx >= _info.Offset.Fragment)
 03065                        return string.Empty;
 3066
 03067                    return _string.Substring(delimiterAwareIdx, _info.Offset.Fragment - delimiterAwareIdx);
 3068
 3069                case UriComponents.Fragment:
 3070                    // Strip the '#' if no delimiter was requested
 03071                    if (uriParts == UriComponents.Fragment)
 03072                        delimiterAwareIdx = _info.Offset.Fragment + 1;
 3073                    else
 03074                        delimiterAwareIdx = _info.Offset.Fragment;
 3075
 03076                    if (delimiterAwareIdx >= _info.Offset.End)
 03077                        return string.Empty;
 3078
 03079                    return _string.Substring(delimiterAwareIdx, _info.Offset.End - delimiterAwareIdx);
 3080
 3081                case UriComponents.UserInfo | UriComponents.Host | UriComponents.Port:
 03082                    return (_info.Offset.Path - _info.Offset.User == 0) ? string.Empty :
 03083                        _string.Substring(_info.Offset.User, _info.Offset.Path - _info.Offset.User);
 3084
 3085                case UriComponents.StrongAuthority:  //UserInfo|Host|StrongPort
 03086                    if (InFact(Flags.NotDefaultPort) || _syntax.DefaultPort == UriParser.NoDefaultPort)
 03087                        goto case UriComponents.UserInfo | UriComponents.Host | UriComponents.Port;
 3088
 03089                    return string.Concat(_string.AsSpan(_info.Offset.User, _info.Offset.Path - _info.Offset.User),
 03090                        ":", _info.Offset.PortValue.ToString(CultureInfo.InvariantCulture));
 3091
 3092                case UriComponents.PathAndQuery:        //Path|Query,
 03093                    return _string.Substring(_info.Offset.Path, _info.Offset.Fragment - _info.Offset.Path);
 3094
 3095                case UriComponents.HttpRequestUrl | UriComponents.Fragment: //Scheme|Host|Port|Path|Query|Fragment,
 03096                    if (InFact(Flags.HasUserInfo))
 03097                    {
 03098                        return string.Concat(
 03099                            _string.AsSpan(_info.Offset.Scheme, _info.Offset.User - _info.Offset.Scheme),
 03100                            _string.AsSpan(_info.Offset.Host, _info.Offset.End - _info.Offset.Host));
 3101                    }
 03102                    if (_info.Offset.Scheme == 0 && _info.Offset.End == _string.Length)
 03103                        return _string;
 3104
 03105                    return _string.Substring(_info.Offset.Scheme, _info.Offset.End - _info.Offset.Scheme);
 3106
 3107                case UriComponents.PathAndQuery | UriComponents.Fragment:  //LocalUrl|Fragment
 03108                    return _string.Substring(_info.Offset.Path, _info.Offset.End - _info.Offset.Path);
 3109
 3110                case UriComponents.UserInfo:
 3111                    // Strip the '@' if no delimiter was requested
 3112
 03113                    if (NotAny(Flags.HasUserInfo))
 03114                        return string.Empty;
 3115
 03116                    if (uriParts == UriComponents.UserInfo)
 03117                        delimiterAwareIdx = _info.Offset.Host - 1;
 3118                    else
 03119                        delimiterAwareIdx = _info.Offset.Host;
 3120
 03121                    if (_info.Offset.User >= delimiterAwareIdx)
 03122                        return string.Empty;
 3123
 03124                    return _string.Substring(_info.Offset.User, delimiterAwareIdx - _info.Offset.User);
 3125
 3126                default:
 03127                    return null;
 3128            }
 03129        }
 3130
 3131        //
 3132        //This method does:
 3133        //  - Creates _info member
 3134        //  - checks all components up to path on their canonical representation
 3135        //  - continues parsing starting the path position
 3136        //  - Sets the offsets of remaining components
 3137        //  - Sets the Canonicalization flags if applied
 3138        //  - Will NOT create MoreInfo members
 3139        //
 3140        private void ParseRemaining()
 03141        {
 3142            // ensure we parsed up to the path
 03143            EnsureUriInfo();
 3144
 03145            Flags cF = _flags | Flags.AllUriInfoSet;
 3146
 03147            if ((cF & Flags.UserDrivenParsing) != 0)
 03148                goto Done;
 3149
 3150            // _info.Offset values may be set concurrently, but that's okay since this parsing logic is idempotent.
 3151            // We use a local copy of changed flags (cF) and merge any updates into _flags atomically at the end.
 3152
 03153            if ((cF & Flags.HasUnicode) != 0)
 03154            {
 03155                ParseRemaining_RecreateNormalizedString();
 03156            }
 3157
 03158            string inputStr = _string;
 3159
 3160            // Skip trailing whitespace
 03161            int length = inputStr.Length;
 3162
 03163            while ((uint)(length - 1) < (uint)inputStr.Length && UriHelper.IsLWS(inputStr[length - 1]))
 03164            {
 03165                length--;
 03166            }
 3167
 03168            if ((cF & Flags.ImplicitFile) != 0)
 03169            {
 03170                cF |= Flags.SchemeNotCanonical;
 03171            }
 3172            else
 03173            {
 03174                ReadOnlySpan<char> str = inputStr.AsSpan(_info.Offset.Scheme);
 3175
 03176                Debug.Assert(str.StartsWith(_syntax.SchemeName, StringComparison.OrdinalIgnoreCase));
 03177                Debug.Assert(str[_syntax.SchemeName.Length] == ':');
 3178
 03179                int schemeLength = _syntax.SchemeName.Length;
 3180
 3181                // Check that the scheme casing matches the canonical one (lowercase).
 3182                // Using a manual loop instead of ContainsAnyInRange because the expected length is small.
 03183                foreach (char c in str.Slice(0, schemeLength))
 03184                {
 03185                    if (char.IsAsciiLetterUpper(c))
 03186                    {
 03187                        cF |= Flags.SchemeNotCanonical;
 03188                        break;
 3189                    }
 03190                }
 3191
 3192                // For an authority Uri only // after the scheme would be canonical
 3193                // (for compatibility with: http:\\host)
 03194                if ((cF & Flags.AuthorityFound) != 0 && !str.Slice(schemeLength).StartsWith("://", StringComparison.Ordi
 03195                {
 03196                    cF |= Flags.SchemeNotCanonical | Flags.SchemeNotCanonical_NoTrailingSlashes;
 03197                }
 03198            }
 3199
 03200            UriSyntaxFlags syntaxFlags = _syntax.Flags;
 03201            bool iriParsing = (syntaxFlags & UriSyntaxFlags.AllowIriParsing) != 0;
 3202
 3203            // Check the form of the user info
 03204            if ((cF & Flags.HasUserInfo) != 0)
 03205            {
 03206                int userStart = _info.Offset.User;
 03207                Check result = CheckCanonical(inputStr.AsSpan(userStart, _info.Offset.Host - userStart), '@', out _);
 3208
 03209                if ((result & Check.DisplayCanonical) == 0)
 03210                {
 03211                    cF |= Flags.UserNotCanonical;
 03212                }
 03213                if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical)
 03214                {
 03215                    cF |= Flags.E_UserNotCanonical;
 03216                }
 03217                if (iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
 03218                                                | Check.FoundNonAscii | Check.NotIriCanonical))
 03219                                                == (Check.DisplayCanonical | Check.FoundNonAscii)))
 03220                {
 03221                    cF |= Flags.UserIriCanonical;
 03222                }
 03223            }
 3224
 3225            // Delay canonical Host checking to avoid creation of a host string.
 3226            // We will do that on demand.
 3227            // We have already checked on the port in EnsureUriInfo() that calls CreateUriInfo.
 3228
 3229            // Parsing the Path if any.
 3230            // For iri parsing if we found unicode the idx has offset into _originalUnicodeString,
 3231            // so restart parsing from there and make _info.Offset.Path as _string.Length
 3232
 3233            // If the user explicitly disabled canonicalization, only figure out the offsets
 03234            if ((cF & Flags.DisablePathAndQueryCanonicalization) != 0)
 03235            {
 3236                int queryStart;
 03237                if ((cF & Flags.ImplicitFile) != 0 || (syntaxFlags & UriSyntaxFlags.MayHaveQuery) == 0)
 03238                {
 03239                    queryStart = inputStr.Length;
 03240                }
 3241                else
 03242                {
 03243                    queryStart = inputStr.IndexOf('?');
 03244                    if (queryStart < 0)
 03245                    {
 03246                        queryStart = inputStr.Length;
 03247                    }
 03248                }
 3249
 03250                _info.Offset.Query = queryStart;
 03251                _info.Offset.Fragment = inputStr.Length; // There is no fragment in DisablePathAndQueryCanonicalization 
 03252                _info.Offset.End = inputStr.Length;
 3253
 03254                goto Done;
 3255            }
 3256
 03257            int idx = _info.Offset.Path;
 3258
 3259            //Some uris do not have a query
 3260            //    When '?' is passed as delimiter, then it's special case
 3261            //    so both '?' and '#' will work as delimiters
 03262            {
 03263                char delim =
 03264                    (cF & Flags.ImplicitFile) != 0 || (syntaxFlags & (UriSyntaxFlags.MayHaveQuery | UriSyntaxFlags.MayHa
 03265                    ((syntaxFlags & UriSyntaxFlags.MayHaveQuery) != 0) ? '?' : '#';
 3266
 03267                Check result = CheckCanonical(inputStr.AsSpan(idx, length - idx), delim, out int checkedChars);
 03268                idx += checkedChars;
 3269
 3270                // ATTN:
 3271                // This may render problems for unknown schemes, but in general for an authority based Uri
 3272                // (that has slashes) a path should start with "/"
 3273                // This becomes more interesting knowing how a file uri is used in "file://c:/path"
 3274                // It will be converted to file:///c:/path
 3275                //
 3276                // However, even more interesting is that vsmacros://c:\path will not add the third slash in the _canoic
 3277                //
 3278                // We use special syntax flag to check if the path is rooted, i.e. has a first slash
 3279                //
 03280                if (((cF & Flags.AuthorityFound) != 0) && ((syntaxFlags & UriSyntaxFlags.PathIsRooted) != 0)
 03281                    && (_info.Offset.Path == length || inputStr[_info.Offset.Path] is not ('/' or '\\')))
 03282                {
 03283                    cF |= Flags.FirstSlashAbsent;
 03284                }
 3285
 3286                // Check the need for compression or backslashes conversion
 3287                // we included IsDosPath since it may come with other than FILE uri, for ex. scheme://C:\path
 3288                // (This is very unfortunate that the original design has included that feature)
 03289                bool nonCanonical = false;
 03290                if ((cF & Flags.DosPath) != 0 || (((cF & Flags.AuthorityFound) != 0) &&
 03291                    (((syntaxFlags & (UriSyntaxFlags.CompressPath | UriSyntaxFlags.ConvertPathSlashes)) != 0) ||
 03292                    (syntaxFlags & UriSyntaxFlags.UnEscapeDotsAndSlashes) != 0)))
 03293                {
 03294                    if (((result & Check.DotSlashEscaped) != 0) && (syntaxFlags & UriSyntaxFlags.UnEscapeDotsAndSlashes)
 03295                    {
 03296                        cF |= (Flags.E_PathNotCanonical | Flags.PathNotCanonical);
 03297                        nonCanonical = true;
 03298                    }
 3299
 03300                    if (((syntaxFlags & (UriSyntaxFlags.ConvertPathSlashes)) != 0) && (result & Check.BackslashInPath) !
 03301                    {
 03302                        cF |= (Flags.E_PathNotCanonical | Flags.PathNotCanonical);
 03303                        nonCanonical = true;
 03304                    }
 3305
 03306                    if (((syntaxFlags & (UriSyntaxFlags.CompressPath)) != 0) && ((cF & Flags.E_PathNotCanonical) != 0 ||
 03307                        (result & Check.DotSlashAttn) != 0))
 03308                    {
 03309                        cF |= Flags.ShouldBeCompressed;
 03310                    }
 3311
 03312                    if ((result & Check.BackslashInPath) != 0)
 03313                        cF |= Flags.BackslashInPath;
 03314                }
 03315                else if ((result & Check.BackslashInPath) != 0)
 03316                {
 3317                    // for a "generic" path '\' should be escaped
 03318                    cF |= Flags.E_PathNotCanonical;
 03319                    nonCanonical = true;
 03320                }
 3321
 03322                if ((result & Check.DisplayCanonical) == 0)
 03323                {
 3324                    // For implicit file the user string is usually in perfect display format,
 3325                    // Hence, ignoring complains from CheckCanonical()
 3326                    // V1 compat. In fact we should simply ignore dontEscape parameter for Implicit file.
 3327                    // Currently we don't.
 03328                    if (((cF & Flags.ImplicitFile) == 0) || ((cF & Flags.UserEscaped) != 0) ||
 03329                        (result & Check.ReservedFound) != 0)
 03330                    {
 3331                        //means it's found as escaped or has unescaped Reserved Characters
 03332                        cF |= Flags.PathNotCanonical;
 03333                        nonCanonical = true;
 03334                    }
 03335                }
 3336
 03337                if (((cF & Flags.ImplicitFile) != 0) && (result & (Check.ReservedFound | Check.EscapedCanonical)) != 0)
 03338                {
 3339                    // need to escape reserved chars or re-escape '%' if an "escaped sequence" was found
 03340                    result &= ~Check.EscapedCanonical;
 03341                }
 3342
 03343                if ((result & Check.EscapedCanonical) == 0)
 03344                {
 3345                    //means it's found as not completely escaped
 03346                    cF |= Flags.E_PathNotCanonical;
 03347                }
 3348
 03349                if (iriParsing && !nonCanonical && ((result & (Check.DisplayCanonical | Check.EscapedCanonical
 03350                                | Check.FoundNonAscii | Check.NotIriCanonical))
 03351                                == (Check.DisplayCanonical | Check.FoundNonAscii)))
 03352                {
 03353                    cF |= Flags.PathIriCanonical;
 03354                }
 03355            }
 3356
 3357            // Now we've got to parse the Query if any. Note that Query requires the presence of '?'
 03358            _info.Offset.Query = idx;
 3359
 03360            if ((uint)idx < (uint)inputStr.Length && inputStr[idx] == '?')
 03361            {
 03362                idx++; // This is to exclude first '?' character from checking
 03363                Check result = CheckCanonical(inputStr.AsSpan(idx, length - idx), (syntaxFlags & (UriSyntaxFlags.MayHave
 03364                idx += charsChecked;
 3365
 03366                if ((result & Check.DisplayCanonical) == 0)
 03367                {
 03368                    cF |= Flags.QueryNotCanonical;
 03369                }
 3370
 03371                if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical)
 03372                {
 03373                    cF |= Flags.E_QueryNotCanonical;
 03374                }
 3375
 03376                if (iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
 03377                            | Check.FoundNonAscii | Check.NotIriCanonical))
 03378                            == (Check.DisplayCanonical | Check.FoundNonAscii)))
 03379                {
 03380                    cF |= Flags.QueryIriCanonical;
 03381                }
 03382            }
 3383
 3384            // Now we've got to parse the Fragment if any. Note that Fragment requires the presence of '#'
 03385            _info.Offset.Fragment = idx;
 3386
 03387            if ((uint)idx < (uint)inputStr.Length && inputStr[idx] == '#')
 03388            {
 03389                idx++; // This is to exclude first '#' character from checking
 3390                // We don't using c_DummyChar since want to allow '?' and '#' as unescaped
 03391                Check result = CheckCanonical(inputStr.AsSpan(idx, length - idx), c_EOL, out int checkedChars);
 03392                idx += checkedChars;
 3393
 03394                if ((result & Check.DisplayCanonical) == 0)
 03395                {
 03396                    cF |= Flags.FragmentNotCanonical;
 03397                }
 3398
 03399                if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical)
 03400                {
 03401                    cF |= Flags.E_FragmentNotCanonical;
 03402                }
 3403
 03404                if (iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
 03405                            | Check.FoundNonAscii | Check.NotIriCanonical))
 03406                            == (Check.DisplayCanonical | Check.FoundNonAscii)))
 03407                {
 03408                    cF |= Flags.FragmentIriCanonical;
 03409                }
 03410            }
 3411
 03412            _info.Offset.End = idx;
 3413
 03414        Done:
 03415            InterlockedSetFlags(cF);
 03416        }
 3417
 3418        /// <summary>Recreates the <see cref="_string"/> by normalizing the Path, Query and Fragment for non-ASCII input
 3419        private void ParseRemaining_RecreateNormalizedString()
 03420        {
 03421            DebugAssertInCtor();
 03422            Debug.Assert(InFact(Flags.HasUnicode));
 03423            Debug.Assert(InFact(Flags.MinimalUriInfoSet));
 03424            Debug.Assert(!InFact(Flags.UserDrivenParsing));
 3425
 03426            var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
 3427
 03428            Flags flags = _flags;
 03429            UriSyntaxFlags syntaxFlags = _syntax.Flags;
 03430            ReadOnlySpan<char> original = _originalUnicodeString.AsSpan(_info.Offset.Path);
 3431
 3432            // Dos/Unix paths have no host.  Other schemes cleared/set _string with host information in PrivateParseMini
 03433            if ((flags & (Flags.DosPath | Flags.UnixPath)) != 0)
 03434            {
 03435                if ((flags & Flags.ImplicitFile) == 0)
 03436                {
 03437                    vsb.Append(_syntax.SchemeName);
 3438
 03439                    if ((flags & Flags.AuthorityFound) != 0)
 03440                    {
 03441                        vsb.Append(SchemeDelimiter);
 03442                    }
 3443                    else
 03444                    {
 03445                        vsb.Append(':');
 03446                    }
 03447                }
 3448
 03449                _info.Offset.Scheme = 0;
 03450                _info.Offset.User = vsb.Length;
 03451                _info.Offset.Host = vsb.Length;
 03452            }
 3453            else
 03454            {
 03455                Debug.Assert(!ReferenceEquals(_string, OriginalString));
 3456
 03457                vsb.Append(_string);
 03458            }
 3459
 03460            _info.Offset.Path = vsb.Length;
 3461
 03462            if ((flags & Flags.DisablePathAndQueryCanonicalization) != 0)
 03463            {
 03464                _string = string.Concat(vsb.AsSpan(), original);
 03465                vsb.Dispose();
 03466                return;
 3467            }
 3468
 3469            // Trim trailing whitespace
 03470            while (!original.IsEmpty && UriHelper.IsLWS(original[^1]))
 03471            {
 03472                original = original.Slice(0, original.Length - 1);
 03473            }
 3474
 03475            int pathLength = (flags & Flags.ImplicitFile) != 0 || (syntaxFlags & UriSyntaxFlags.MayHaveQuery) == 0
 03476                ? -1 // We can't have a query
 03477                : original.IndexOfAny('?', '#');
 3478
 03479            if ((uint)pathLength >= (uint)original.Length || original[pathLength] == '#')
 03480            {
 3481                // Everything is considered either the path or fragment
 03482                IriHelper.EscapeUnescapeIri(ref vsb, original, isQuery: false);
 03483            }
 3484            else
 03485            {
 03486                IriHelper.EscapeUnescapeIri(ref vsb, original.Slice(0, pathLength), isQuery: false); // Path
 03487                original = original.Slice(pathLength);
 3488
 03489                Debug.Assert(original.StartsWith('?'));
 3490
 03491                int fragmentOffset = (syntaxFlags & UriSyntaxFlags.MayHaveFragment) == 0
 03492                    ? -1 // We can't have a fragment
 03493                    : original.IndexOf('#');
 3494
 03495                if ((uint)fragmentOffset >= (uint)original.Length)
 03496                {
 03497                    IriHelper.EscapeUnescapeIri(ref vsb, original, isQuery: true); // Query
 03498                }
 3499                else
 03500                {
 03501                    IriHelper.EscapeUnescapeIri(ref vsb, original.Slice(0, fragmentOffset), isQuery: true); // Query
 03502                    IriHelper.EscapeUnescapeIri(ref vsb, original.Slice(fragmentOffset), isQuery: false); // Fragment
 03503                }
 03504            }
 3505
 03506            _string = vsb.ToString();
 03507        }
 3508
 3509        private static string EscapeUnescapeIri(scoped ReadOnlySpan<char> prefix, scoped ReadOnlySpan<char> span, bool i
 03510        {
 03511            var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
 03512            IriHelper.EscapeUnescapeIri(ref vsb, span, isQuery);
 03513            string result = string.Concat(prefix, vsb.AsSpan());
 03514            vsb.Dispose();
 03515            return result;
 03516        }
 3517
 3518        // verifies the syntax of the scheme part
 3519        // Checks on implicit File: scheme due to simple Dos/Unc path passed
 3520        // returns the start of the next component  position
 3521        private static int ParseSchemeCheckImplicitFile(string uriString, ref ParsingError err, ref Flags flags, ref Uri
 03522        {
 03523            Debug.Assert(err == ParsingError.None);
 03524            Debug.Assert((flags & Flags.Debug_LeftConstructor) == 0);
 3525
 03526            int i = 0;
 3527
 3528            // skip whitespace
 03529            while ((uint)i < (uint)uriString.Length && UriHelper.IsLWS(uriString[i]))
 03530            {
 03531                i++;
 03532            }
 3533
 3534            // Unix: Unix path?
 3535            // A path starting with 2 / or \ (including mixed) is treated as UNC and will be matched below
 03536            if (!OperatingSystem.IsWindows() &&
 03537                StaticNotAny(flags, Flags.DisableImplicitFilePaths) &&
 03538                (uint)i < (uint)uriString.Length && uriString[i] == '/' &&
 03539                ((uint)(i + 1) >= (uint)uriString.Length || uriString[i + 1] is not ('/' or '\\')))
 03540            {
 03541                flags |= (Flags.UnixPath | Flags.ImplicitFile | Flags.AuthorityFound);
 03542                syntax = UriParser.UnixFileUri;
 03543                return i;
 3544            }
 3545
 3546            // Find the colon.
 3547            // Note that we don't support one-letter schemes that will be put into a DOS path bucket
 03548            int colonOffset = uriString.AsSpan(i).IndexOf(':');
 3549
 3550            // NB: A string must have at least 3 characters and at least 1 before ':'
 03551            if ((uint)(i + 2) >= (uint)uriString.Length || colonOffset == 0)
 03552            {
 03553                err = ParsingError.BadFormat;
 03554                return 0;
 3555            }
 3556
 3557            // Check for supported special cases like a DOS file path OR a UNC share path
 3558            // NB: A string may not have ':' if this is a UNC path
 03559            if (uriString[i + 1] is ':' or '|')
 03560            {
 3561                // DOS-like path?
 03562                if (char.IsAsciiLetter(uriString[i]) && StaticNotAny(flags, Flags.DisableImplicitFilePaths))
 03563                {
 03564                    if (uriString[i + 2] is '\\' or '/')
 03565                    {
 03566                        flags |= (Flags.DosPath | Flags.ImplicitFile | Flags.AuthorityFound);
 03567                        syntax = UriParser.FileUri;
 03568                        return i;
 3569                    }
 3570
 03571                    err = ParsingError.MustRootedPath;
 03572                    return 0;
 3573                }
 3574
 03575                err = uriString[i + 1] == ':' ? ParsingError.BadScheme : ParsingError.BadFormat;
 03576                return 0;
 3577            }
 03578            else if (uriString[i] is '/' or '\\')
 03579            {
 3580                // UNC share?
 03581                if ((uriString[i + 1] is '\\' or '/') && StaticNotAny(flags, Flags.DisableImplicitFilePaths))
 03582                {
 03583                    flags |= (Flags.UncPath | Flags.ImplicitFile | Flags.AuthorityFound);
 03584                    syntax = UriParser.FileUri;
 03585                    i += 2;
 3586
 3587                    // V1.1 compat this will simply eat any slashes prepended to a UNC path
 03588                    while ((uint)i < (uint)uriString.Length && uriString[i] is '/' or '\\')
 03589                    {
 03590                        i++;
 03591                    }
 3592
 03593                    return i;
 3594                }
 3595
 03596                err = ParsingError.BadFormat;
 03597                return 0;
 3598            }
 3599
 03600            if (colonOffset < 0)
 03601            {
 03602                err = ParsingError.BadFormat;
 03603                return 0;
 3604            }
 3605
 3606            // This is a potentially valid scheme, but we have not identified it yet.
 3607            // Check for illegal characters, canonicalize, and check the length.
 03608            syntax = CheckSchemeSyntax(uriString.AsSpan(i, colonOffset), ref err);
 03609            if (syntax is null)
 03610            {
 03611                return 0;
 3612            }
 03613            return i + colonOffset + 1;
 03614        }
 3615
 3616        // This will check whether a scheme string follows the rules
 3617        private static UriParser? CheckSchemeSyntax(ReadOnlySpan<char> scheme, ref ParsingError error)
 03618        {
 03619            Debug.Assert(error == ParsingError.None);
 3620
 03621            switch (scheme.Length)
 3622            {
 3623                case 2:
 03624                    if (scheme.Equals("ws", StringComparison.OrdinalIgnoreCase)) return UriParser.WsUri;
 03625                    break;
 3626
 3627                case 3:
 03628                    if (scheme.Equals("wss", StringComparison.OrdinalIgnoreCase)) return UriParser.WssUri;
 03629                    if (scheme.Equals("ftp", StringComparison.OrdinalIgnoreCase)) return UriParser.FtpUri;
 03630                    break;
 3631
 3632                case 4:
 03633                    if (scheme.Equals("http", StringComparison.OrdinalIgnoreCase)) return UriParser.HttpUri;
 03634                    if (scheme.Equals("file", StringComparison.OrdinalIgnoreCase)) return UriParser.FileUri;
 03635                    if (scheme.Equals("uuid", StringComparison.OrdinalIgnoreCase)) return UriParser.UuidUri;
 03636                    if (scheme.Equals("nntp", StringComparison.OrdinalIgnoreCase)) return UriParser.NntpUri;
 03637                    if (scheme.Equals("ldap", StringComparison.OrdinalIgnoreCase)) return UriParser.LdapUri;
 03638                    if (scheme.Equals("news", StringComparison.OrdinalIgnoreCase)) return UriParser.NewsUri;
 03639                    break;
 3640
 3641                case 5:
 03642                    if (scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) return UriParser.HttpsUri;
 03643                    break;
 3644
 3645                case 6:
 03646                    if (scheme.Equals("mailto", StringComparison.OrdinalIgnoreCase)) return UriParser.MailToUri;
 03647                    if (scheme.Equals("gopher", StringComparison.OrdinalIgnoreCase)) return UriParser.GopherUri;
 03648                    if (scheme.Equals("telnet", StringComparison.OrdinalIgnoreCase)) return UriParser.TelnetUri;
 03649                    break;
 3650
 3651                case 7:
 03652                    if (scheme.Equals("net.tcp", StringComparison.OrdinalIgnoreCase)) return UriParser.NetTcpUri;
 03653                    break;
 3654
 3655                case 8:
 03656                    if (scheme.Equals("net.pipe", StringComparison.OrdinalIgnoreCase)) return UriParser.NetPipeUri;
 03657                    break;
 3658            }
 3659
 3660            // scheme = alpha *(alpha | digit | '+' | '-' | '.')
 03661            if (scheme.Length == 0 ||
 03662                !char.IsAsciiLetter(scheme[0]) ||
 03663                scheme.ContainsAnyExcept(s_schemeChars))
 03664            {
 03665                error = ParsingError.BadScheme;
 03666                return null;
 3667            }
 3668
 03669            if (scheme.Length > SchemeLengthLimit)
 03670            {
 03671                error = ParsingError.SchemeLimit;
 03672                return null;
 3673            }
 3674
 3675            // Then look up the syntax in a string-based table.
 03676            return UriParser.FindOrFetchAsUnknownV1Syntax(UriHelper.SpanToLowerInvariantString(scheme));
 03677        }
 3678
 13679        private static readonly SearchValues<char> s_userInfoEndChars =
 13680            SearchValues.Create(@"@?#\/");
 3681
 3682        // Checks the syntax of an authority component. It may also get a userInfo if present
 3683        // Returns an error if no/mailformed authority found
 3684        // Does not NOT touch _info
 3685        // Returns position of the Path component
 3686        //
 3687        // Must be called in the ctor only
 3688        private int CheckAuthorityHelper(ReadOnlySpan<char> str, int startOffset, out ParsingError err, ref Flags flags,
 6613689        {
 6613690            Debug.Assert((_flags & Flags.Debug_LeftConstructor) == 0 || (!_syntax.IsSimple && Monitor.IsEntered(_info)))
 6613691            Debug.Assert((_flags & Flags.HasUserInfo) == 0 && (_flags & Flags.HostTypeMask) == 0);
 6613692            Debug.Assert((uint)startOffset <= (uint)str.Length);
 3693
 6613694            err = ParsingError.None;
 6613695            newHost = null;
 6613696            bool hasUnicode = ((flags & Flags.HasUnicode) != 0);
 3697
 3698            // need to build new Iri'zed string
 6613699            if (hasUnicode)
 03700            {
 03701                newHost = _originalUnicodeString.Substring(0, startOffset);
 03702            }
 3703
 3704            char ch;
 3705
 3706            // Special case is an empty authority
 6613707            if ((uint)startOffset >= (uint)str.Length || ((ch = str[startOffset]) == '/' || (ch == '\\' && (syntaxFlags 
 03708            {
 03709                if ((syntaxFlags & UriSyntaxFlags.AllowEmptyHost) != 0)
 03710                {
 03711                    flags &= ~Flags.UncPath;    //UNC cannot have an empty hostname
 03712                    if (StaticInFact(flags, Flags.ImplicitFile))
 03713                        err = ParsingError.BadHostName;
 3714                    else
 03715                        flags |= Flags.BasicHostType;
 03716                }
 3717                else
 03718                    err = ParsingError.BadHostName;
 3719
 03720                return startOffset;
 3721            }
 3722
 3723            // Attempt to parse user info first
 6613724            int i = startOffset;
 3725
 6613726            if ((syntaxFlags & UriSyntaxFlags.MayHaveUserInfo) != 0)
 6613727            {
 6613728                ReadOnlySpan<char> slice = str.Slice(i);
 6613729                int userInfoLength = slice.IndexOfAny(s_userInfoEndChars);
 3730
 3731                // Check if the first delimiter is '@' and there's at least one character after it.
 6613732                if ((uint)userInfoLength < (uint)slice.Length && slice[userInfoLength] == '@' && (uint)(++userInfoLength
 03733                {
 03734                    ch = slice[userInfoLength];
 03735                    i += userInfoLength;
 3736
 03737                    flags |= Flags.HasUserInfo;
 3738
 3739                    // Iri'ze userinfo
 03740                    if (hasUnicode)
 03741                    {
 03742                        newHost = EscapeUnescapeIri(newHost, slice.Slice(0, userInfoLength), isQuery: false);
 03743                    }
 03744                }
 6613745            }
 3746
 6613747            int hostLength = 0;
 3748
 6613749            if (ch == '[' && (syntaxFlags & UriSyntaxFlags.AllowIPv6Host) != 0 && IPv6AddressHelper.IsValid(str.Slice(i)
 5383750            {
 5383751                int delimiterIdx = i + hostLength;
 3752
 5383753                if ((uint)delimiterIdx < (uint)str.Length &&
 5383754                    str[delimiterIdx] is not ('/' or '\\') &&
 5383755                    (IsImplicitFile || str[delimiterIdx] is not (':' or '?' or '#')))
 03756                {
 3757                    // A valid IPv6 address wasn't followed by a valid delimiter (e.g. http://[::]extra).
 3758                    // For implicit files we also disallow ? or #.
 03759                    err = ParsingError.BadHostName;
 03760                    return i;
 3761                }
 3762
 5383763                flags |= Flags.IPv6HostType;
 3764
 5383765                if (hasUnicode)
 03766                {
 03767                    newHost = string.Concat(newHost, str.Slice(i, hostLength));
 03768                }
 5383769            }
 1233770            else if (char.IsAsciiDigit(ch) && (syntaxFlags & UriSyntaxFlags.AllowIPv4Host) != 0 &&
 1233771                IPv4AddressHelper.IsValid(str.Slice(i), out hostLength, allowIPv6: false, StaticNotAny(flags, Flags.Impl
 1233772            {
 1233773                flags |= Flags.IPv4HostType;
 3774
 1233775                if (hasUnicode)
 03776                {
 03777                    newHost = string.Concat(newHost, str.Slice(i, hostLength));
 03778                }
 1233779            }
 03780            else if ((syntaxFlags & (UriSyntaxFlags.AllowDnsHost | UriSyntaxFlags.AllowIriParsing)) == UriSyntaxFlags.Al
 03781                DomainNameHelper.IsValid(str.Slice(i), iri: false, StaticNotAny(flags, Flags.ImplicitFile), out hostLeng
 03782            {
 03783                Debug.Assert(!hasUnicode);
 3784
 3785                // comes here if there are only ascii chars in host with original parsing and no Iri
 03786                flags |= Flags.DnsHostType;
 3787
 3788                // Canonical DNS hostnames don't contain uppercase letters
 03789                if (!str.Slice(i, hostLength).ContainsAnyInRange('A', 'Z'))
 03790                {
 03791                    flags |= Flags.CanonicalDnsHost;
 03792                }
 03793            }
 03794            else if (((syntaxFlags & UriSyntaxFlags.AllowDnsHost) != 0) &&
 03795                (hasUnicode || (syntaxFlags & UriSyntaxFlags.AllowIdn) != 0) &&
 03796                DomainNameHelper.IsValid(str.Slice(i), iri: true, StaticNotAny(flags, Flags.ImplicitFile), out hostLengt
 03797            {
 03798                flags |= Flags.DnsHostType;
 3799
 03800                if (hasUnicode)
 03801                {
 03802                    ReadOnlySpan<char> host = str.Slice(i, hostLength);
 3803
 03804                    if (UriHelper.StripBidiControlCharacters(host, out string? stripped))
 03805                    {
 03806                        host = stripped;
 03807                    }
 3808
 3809                    try
 03810                    {
 03811                        newHost = UriHelper.NormalizeAndConcat(newHost, host);
 03812                    }
 03813                    catch (ArgumentException)
 03814                    {
 03815                        err = ParsingError.BadHostName;
 03816                    }
 03817                }
 03818            }
 03819            else if ((syntaxFlags & UriSyntaxFlags.AllowUncHost) != 0)
 03820            {
 3821                // This must remain as the last check before BasicHost type
 03822                if (UncNameHelper.IsValid(str.Slice(i), StaticNotAny(flags, Flags.ImplicitFile), out hostLength))
 03823                {
 03824                    if (hostLength <= UncNameHelper.MaximumInternetNameLength)
 03825                    {
 03826                        flags |= Flags.UncHostType;
 03827                        if (hasUnicode)
 03828                        {
 03829                            newHost = string.Concat(newHost, str.Slice(i, hostLength));
 03830                        }
 03831                    }
 03832                }
 03833            }
 3834
 6613835            int endOfHost = i + hostLength;
 6613836            char hostDelimiter = (uint)endOfHost < (uint)str.Length ? str[endOfHost] : '\0';
 3837
 3838            // The deal here is that we won't allow '\' host terminator except for the File scheme
 3839            // If we see '\' we try to make it a part of a Basic host
 6613840            if (hostDelimiter == '\\' && (flags & Flags.HostTypeMask) != Flags.HostNotParsed && (syntaxFlags & UriSyntax
 03841            {
 03842                if ((syntaxFlags & UriSyntaxFlags.V1_UnknownUri) != 0)
 03843                {
 03844                    err = ParsingError.BadHostName;
 03845                    return endOfHost;
 3846                }
 3847
 03848                flags &= ~Flags.HostTypeMask;
 03849            }
 3850            // Here we have checked the syntax up to the end of host
 3851            // The only thing that can cause an exception is the port value
 3852            // Spend some (duplicated) cycles on that.
 6613853            else if (hostDelimiter == ':' && hostLength != 0)
 03854            {
 03855                if ((syntaxFlags & UriSyntaxFlags.MayHavePort) != 0)
 03856                {
 03857                    int port = 0;
 03858                    int startPort = endOfHost;
 3859
 03860                    for (i = endOfHost + 1; (uint)i < (uint)str.Length; i++)
 03861                    {
 03862                        int val = str[i] - '0';
 03863                        if ((uint)val <= ('9' - '0'))
 03864                        {
 03865                            if ((port = (port * 10 + val)) > 0xFFFF)
 03866                                break;
 03867                        }
 03868                        else if (val == ('/' - '0') || val == ('?' - '0') || val == ('#' - '0'))
 03869                        {
 03870                            break;
 3871                        }
 3872                        else
 03873                        {
 3874                            // The second check is to keep compatibility with V1 until the UriParser is registered
 03875                            if ((syntaxFlags & (UriSyntaxFlags.AllowAnyOtherHost | UriSyntaxFlags.V1_UnknownUri)) == Uri
 03876                            {
 03877                                flags &= ~Flags.HostTypeMask;
 03878                                break;
 3879                            }
 3880                            else
 03881                            {
 03882                                err = ParsingError.BadPort;
 03883                                return i;
 3884                            }
 3885                        }
 03886                    }
 3887
 3888                    // check on 0-ffff range
 03889                    if (port > 0xFFFF)
 03890                    {
 03891                        if ((syntaxFlags & UriSyntaxFlags.AllowAnyOtherHost) != 0)
 03892                        {
 03893                            flags &= ~Flags.HostTypeMask;
 03894                        }
 3895                        else
 03896                        {
 03897                            err = ParsingError.BadPort;
 03898                            return i;
 3899                        }
 03900                    }
 3901
 03902                    if (hasUnicode)
 03903                    {
 03904                        newHost = string.Concat(newHost, str.Slice(startPort, i - startPort));
 03905                    }
 03906                }
 3907                else
 03908                {
 03909                    flags &= ~Flags.HostTypeMask;
 03910                }
 03911            }
 3912
 3913            // check on whether nothing has worked out
 6613914            if ((flags & Flags.HostTypeMask) == Flags.HostNotParsed)
 03915            {
 3916                //No user info for a Basic hostname
 03917                flags &= ~Flags.HasUserInfo;
 3918
 3919                // Some schemes do not allow HostType = Basic (plus V1 almost never understands this issue)
 03920                if ((syntaxFlags & UriSyntaxFlags.AllowAnyOtherHost) != 0)
 03921                {
 03922                    flags |= Flags.BasicHostType;
 03923                    int basicHostEnd = str.Slice(i).IndexOfAny('/', '?', '#');
 03924                    endOfHost = basicHostEnd >= 0 ? i + basicHostEnd : str.Length;
 3925
 03926                    if (hasUnicode)
 03927                    {
 3928                        // Normalize any other host or do idn
 3929                        try
 03930                        {
 03931                            newHost = UriHelper.NormalizeAndConcat(newHost, str.Slice(i, endOfHost - i));
 03932                        }
 03933                        catch (ArgumentException)
 03934                        {
 03935                            err = ParsingError.BadHostName;
 03936                        }
 03937                    }
 03938                }
 3939                else
 03940                {
 3941                    // ATTN V1 compat: V1 supports hostnames like ".." and ".", and so we do but only for unknown scheme
 03942                    if ((syntaxFlags & UriSyntaxFlags.V1_UnknownUri) != 0)
 03943                    {
 3944                        // Can assert here that the host is not empty so we will set dotFound
 3945                        // at least once or fail before exiting the loop
 03946                        bool dotFound = false;
 03947                        for (endOfHost = i; (uint)endOfHost < (uint)str.Length; endOfHost++)
 03948                        {
 03949                            if (dotFound && (str[endOfHost] == '/' || str[endOfHost] == '?' || str[endOfHost] == '#'))
 03950                                break;
 3951
 03952                            if (endOfHost < (i + 2) && str[endOfHost] == '.')
 03953                            {
 3954                                // allow one or two dots
 03955                                dotFound = true;
 03956                            }
 3957                            else
 03958                            {
 3959                                //failure
 03960                                err = ParsingError.BadHostName;
 03961                                return i;
 3962                            }
 03963                        }
 3964
 3965                        //success
 03966                        flags |= Flags.BasicHostType;
 3967
 03968                        if (hasUnicode)
 03969                        {
 3970                            // Normalize any other host
 3971                            try
 03972                            {
 03973                                newHost = UriHelper.NormalizeAndConcat(newHost, str.Slice(i, endOfHost - i));
 03974                            }
 03975                            catch (ArgumentException)
 03976                            {
 03977                                err = ParsingError.BadFormat;
 03978                                return i;
 3979                            }
 03980                        }
 03981                    }
 03982                    else if ((syntaxFlags & (UriSyntaxFlags.MustHaveAuthority | UriSyntaxFlags.MailToLikeUri)) != 0)
 03983                    {
 03984                        err = ParsingError.BadHostName;
 03985                        return i;
 3986                    }
 03987                }
 03988            }
 3989
 6613990            return endOfHost;
 6613991        }
 3992
 3993        //
 3994        // The method checks whether a string needs transformation before going to display or wire
 3995        //
 3996        // Parameters:
 3997        // - escaped   true = treat all valid escape sequences as escaped sequences, false = escape all %
 3998        // - delim     a character signaling the termination of the component being checked
 3999        //
 4000        // When delim=='?', then '#' character is also considered as delimiter additionally to passed '?'.
 4001        //
 4002        // The method pays attention to the dots and slashes so to signal potential Path compression action needed.
 4003        // Even that is not required for other components, the cycles are still spent (little inefficiency)
 4004        //
 4005
 4006        internal const char c_DummyChar = (char)0xFFFF;     //An Invalid Unicode character used as a dummy char passed i
 4007        internal const char c_EOL = (char)0xFFFE;     //An Invalid Unicode character used by CheckCanonical as "no delim
 4008        [Flags]
 4009        private enum Check
 4010        {
 4011            None = 0x0,
 4012            EscapedCanonical = 0x1,
 4013            DisplayCanonical = 0x2,
 4014            DotSlashAttn = 0x4,
 4015            DotSlashEscaped = 0x80,
 4016            BackslashInPath = 0x10,
 4017            ReservedFound = 0x20,
 4018            NotIriCanonical = 0x40,
 4019            FoundNonAscii = 0x8
 4020        }
 4021
 4022        // Used by ParseRemaining as well by InternalIsWellFormedOriginalString
 4023        private Check CheckCanonical(ReadOnlySpan<char> span, char delim, out int end)
 04024        {
 04025            Debug.Assert(delim is '@' or '/' or '?' or '#' or c_DummyChar or c_EOL);
 4026
 04027            Check res = Check.None;
 04028            bool needsEscaping = false;
 04029            bool foundEscaping = false;
 04030            bool iriParsing = IriParsing;
 4031
 4032            int i;
 04033            for (i = 0; (uint)i < (uint)span.Length; i++)
 04034            {
 04035                char c = span[i];
 4036
 04037                if (char.IsAsciiLetterOrDigit(c))
 04038                {
 4039                    // The most common case - unreserved chars.
 04040                }
 04041                else if (c <= '\x1F' || (c >= '\x7F' && c <= '\x9F'))
 04042                {
 4043                    // Control chars usually should be escaped in any case
 04044                    needsEscaping = true;
 04045                    foundEscaping = true;
 04046                    res |= Check.ReservedFound;
 04047                }
 04048                else if (c > '~')
 04049                {
 04050                    if (iriParsing)
 04051                    {
 04052                        bool valid = false;
 04053                        res |= Check.FoundNonAscii;
 4054
 04055                        if (char.IsHighSurrogate(c))
 04056                        {
 04057                            if ((uint)(i + 1) < (uint)span.Length)
 04058                            {
 04059                                valid = IriHelper.CheckIriUnicodeRange(c, span[i + 1], out _, true);
 04060                                i++;
 04061                            }
 04062                        }
 4063                        else
 04064                        {
 04065                            valid = IriHelper.CheckIriUnicodeRange(c, true);
 04066                        }
 4067
 04068                        if (!valid) res |= Check.NotIriCanonical;
 04069                    }
 4070
 04071                    needsEscaping = true;
 04072                }
 04073                else if (c == delim)
 04074                {
 04075                    break;
 4076                }
 04077                else if (delim == '?' && c == '#' && (_syntax != null && _syntax.InFact(UriSyntaxFlags.MayHaveFragment))
 04078                {
 4079                    // this is a special case when deciding on Query/Fragment
 04080                    break;
 4081                }
 04082                else if (c == '?')
 04083                {
 04084                    if (IsImplicitFile || (_syntax != null && !_syntax.InFact(UriSyntaxFlags.MayHaveQuery) && delim != c
 04085                    {
 4086                        // If found as reserved this char is not suitable for safe unescaped display
 4087                        // Will need to escape it when both escaping and unescaping the string
 04088                        res |= Check.ReservedFound;
 04089                        foundEscaping = true;
 04090                        needsEscaping = true;
 04091                    }
 04092                }
 04093                else if (c == '#')
 04094                {
 04095                    needsEscaping = true;
 4096
 04097                    if (IsImplicitFile || (_syntax != null && !_syntax.InFact(UriSyntaxFlags.MayHaveFragment)))
 04098                    {
 4099                        // If found as reserved this char is not suitable for safe unescaped display
 4100                        // Will need to escape it when both escaping and unescaping the string
 04101                        res |= Check.ReservedFound;
 04102                        foundEscaping = true;
 04103                    }
 04104                }
 04105                else if (c == '/' || c == '\\')
 04106                {
 04107                    if (c == '\\')
 04108                    {
 04109                        res |= Check.BackslashInPath;
 04110                    }
 4111
 04112                    if ((uint)(i + 1) < (uint)span.Length && (span[i + 1] == '/' || span[i + 1] == '\\'))
 04113                    {
 04114                        res |= Check.DotSlashAttn;
 04115                    }
 04116                }
 04117                else if (c == '.')
 04118                {
 04119                    if (((uint)(i + 1) >= (uint)span.Length || span[i + 1] == '.' || span[i + 1] == '/' || span[i + 1] =
 04120                    {
 04121                        res |= Check.DotSlashAttn;
 04122                    }
 04123                }
 04124                else if (((c <= '"' && c != '!') || (c >= '[' && c <= '^') || c == '>'
 04125                        || c == '<' || c == '`'))
 04126                {
 04127                    needsEscaping = true;
 4128
 4129                    // The check above validates only that we have valid IRI characters, which is not enough to
 4130                    // conclude that we have a valid canonical IRI.
 4131                    // If we have an IRI with Flags.HasUnicode, we need to set Check.NotIriCanonical so that the
 4132                    // path, query, and fragment will be validated.
 04133                    if ((_flags & Flags.HasUnicode) != 0)
 04134                    {
 04135                        res |= Check.NotIriCanonical;
 04136                    }
 04137                }
 04138                else if (c >= '{' && c <= '}') // includes '{', '|', '}'
 04139                {
 04140                    needsEscaping = true;
 04141                }
 04142                else if (c == '%')
 04143                {
 04144                    foundEscaping = true;
 4145
 4146                    // try unescape a byte hex escaping
 04147                    if ((uint)(i + 2) < (uint)span.Length && (c = UriHelper.DecodeHexChars(span[i + 1], span[i + 2])) !=
 04148                    {
 04149                        if (c == '.' || c == '/' || c == '\\')
 04150                        {
 04151                            res |= Check.DotSlashEscaped;
 04152                        }
 4153
 04154                        i += 2;
 04155                        continue;
 4156                    }
 4157
 4158                    // otherwise we follow to non escaped case
 04159                    needsEscaping = true;
 04160                }
 04161            }
 4162
 04163            if (!needsEscaping)
 04164            {
 04165                res |= Check.EscapedCanonical;
 04166            }
 4167
 04168            if (!foundEscaping)
 04169            {
 04170                res |= Check.DisplayCanonical;
 04171            }
 4172
 04173            end = i;
 04174            return res;
 04175        }
 4176
 4177        //
 4178        // Returns the escaped and canonicalized path string
 4179        // the passed array must be long enough to hold at least
 4180        // canonical unescaped path representation (allocated by the caller)
 4181        //
 4182        private void GetCanonicalPath(ref ValueStringBuilder dest, UriFormat formatAs)
 04183        {
 04184            if (InFact(Flags.FirstSlashAbsent))
 04185                dest.Append('/');
 4186
 04187            if (_info.Offset.Path == _info.Offset.Query)
 04188                return;
 4189
 04190            int start = dest.Length;
 4191
 04192            int dosPathIdx = SecuredPathIndex;
 4193
 4194            // Note that unescaping and then escaping back is not transitive hence not safe.
 4195            // We are vulnerable due to the way the UserEscaped flag is processed.
 4196            // Try to unescape only needed chars.
 04197            if (formatAs == UriFormat.UriEscaped)
 04198            {
 04199                if (InFact(Flags.ShouldBeCompressed))
 04200                {
 04201                    dest.Append(_string.AsSpan(_info.Offset.Path, _info.Offset.Query - _info.Offset.Path));
 4202
 4203                    // If the path was found as needed compression and contains escaped characters, unescape only
 4204                    // interesting characters (safe)
 4205
 04206                    if (_syntax.InFact(UriSyntaxFlags.UnEscapeDotsAndSlashes) && InFact(Flags.PathNotCanonical)
 04207                        && !IsImplicitFile)
 04208                    {
 04209                        UnescapePathSlashesAndDots(ref dest, start, _syntax.InFact(UriSyntaxFlags.ConvertPathSlashes));
 04210                    }
 04211                }
 4212                else
 04213                {
 4214                    //Note: we may produce non escaped Uri characters on the wire
 04215                    if (InFact(Flags.E_PathNotCanonical) && NotAny(Flags.UserEscaped))
 04216                    {
 04217                        ReadOnlySpan<char> str = _string;
 4218
 4219                        // Check on not canonical disk designation like C|\, should be rare, rare case
 04220                        if (dosPathIdx != 0 && str[dosPathIdx + _info.Offset.Path - 1] == '|')
 04221                        {
 04222                            char[] chars = str.ToArray();
 04223                            chars[dosPathIdx + _info.Offset.Path - 1] = ':';
 04224                            str = chars;
 04225                        }
 4226
 04227                        UriHelper.EscapeString(
 04228                            str.Slice(_info.Offset.Path, _info.Offset.Query - _info.Offset.Path),
 04229                            ref dest, checkExistingEscaped: !IsImplicitFile, UriHelper.UnreservedReservedExceptQuestionM
 04230                    }
 4231                    else
 04232                    {
 04233                        dest.Append(_string.AsSpan(_info.Offset.Path, _info.Offset.Query - _info.Offset.Path));
 04234                    }
 04235                }
 4236
 4237                // On Unix, escape '\\' in path of file uris to '%5C' canonical form.
 04238                if (!OperatingSystem.IsWindows() && InFact(Flags.BackslashInPath) && _syntax.NotAny(UriSyntaxFlags.Conve
 04239                {
 4240                    // We can't do an in-place escape, create a copy
 04241                    var copy = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
 04242                    copy.Append(dest.AsSpan(start, dest.Length - start));
 4243
 04244                    dest.Length = start;
 4245
 04246                    UriHelper.EscapeString(copy.AsSpan(), ref dest, checkExistingEscaped: true, UriHelper.UnreservedRese
 04247                    start = dest.Length;
 4248
 04249                    copy.Dispose();
 04250                }
 04251            }
 4252            else
 04253            {
 04254                dest.Append(_string.AsSpan(_info.Offset.Path, _info.Offset.Query - _info.Offset.Path));
 4255
 04256                if (InFact(Flags.ShouldBeCompressed))
 04257                {
 4258                    // If the path was found as needed compression and contains escaped characters,
 4259                    // unescape only interesting characters (safe)
 4260
 04261                    if (_syntax.InFact(UriSyntaxFlags.UnEscapeDotsAndSlashes) && InFact(Flags.PathNotCanonical)
 04262                        && !IsImplicitFile)
 04263                    {
 04264                        UnescapePathSlashesAndDots(ref dest, start, _syntax.InFact(UriSyntaxFlags.ConvertPathSlashes));
 04265                    }
 04266                }
 04267            }
 4268
 4269            // Here we already got output data as copied into dest array
 4270            // We just may need more processing of that data
 4271
 4272            //
 4273            // if this URI is using 'non-proprietary' disk drive designation, convert to MS-style
 4274            //
 4275            // (path is already  >= 3 chars if recognized as a DOS-like)
 4276            //
 04277            int offset = start + dosPathIdx;
 04278            if (dosPathIdx != 0 && dest[offset - 1] == '|')
 04279                dest[offset - 1] = ':';
 4280
 04281            if (InFact(Flags.ShouldBeCompressed) && dest.Length - offset > 0)
 04282            {
 4283                // It will also convert back slashes if needed
 04284                Compress(ref dest, offset, _syntax);
 4285
 04286                if (dest.Length > start && dest[start] == '\\')
 04287                {
 04288                    dest[start] = '/';
 04289                }
 4290
 4291                // Escape path if requested and found as not fully escaped
 04292                if (formatAs == UriFormat.UriEscaped && NotAny(Flags.UserEscaped) && InFact(Flags.E_PathNotCanonical))
 04293                {
 4294                    //Note: Flags.UserEscaped check is solely based on trusting the user
 4295
 4296                    // We can't do an in-place escape, create a copy
 04297                    var copy = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
 04298                    copy.Append(dest.AsSpan(start, dest.Length - start));
 4299
 04300                    dest.Length = start;
 4301
 04302                    UriHelper.EscapeString(copy.AsSpan(), ref dest, checkExistingEscaped: !IsImplicitFile, UriHelper.Unr
 04303                    start = dest.Length;
 4304
 04305                    copy.Dispose();
 04306                }
 04307            }
 4308
 04309            if (formatAs != UriFormat.UriEscaped && InFact(Flags.PathNotCanonical))
 04310            {
 04311                ReadOnlySpan<char> slice = dest.AsSpan(start, dest.Length - start);
 4312
 04313                if (formatAs == UriFormat.Unescaped)
 04314                {
 04315                    if (!IsImplicitFile)
 04316                    {
 4317                        // Unescape in-place
 04318                        dest.Length = start;
 04319                        UriHelper.Unescape(slice, ref dest);
 04320                        Debug.Assert(slice.IsEmpty || slice.Overlaps(dest.RawChars));
 04321                    }
 04322                }
 4323                else
 04324                {
 4325                    // UriFormat.SafeUnescaped / V1ToStringUnescape
 04326                    UnescapeMode mode = InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape;
 4327
 04328                    if (IsImplicitFile)
 04329                    {
 04330                        mode &= ~UnescapeMode.Unescape;
 04331                    }
 4332
 04333                    if (formatAs == V1ToStringUnescape)
 04334                    {
 04335                        mode |= UnescapeMode.V1ToStringFlag;
 04336                    }
 4337
 04338                    if (mode != UnescapeMode.None)
 04339                    {
 4340                        // We can't do an in-place escape/unescape, create a copy
 04341                        var copy = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
 04342                        copy.Append(slice);
 4343
 04344                        dest.Length = start;
 4345
 04346                        UriHelper.UnescapeString(copy.AsSpan(),
 04347                            ref dest, '?', '#', c_DummyChar,
 04348                            mode,
 04349                            _syntax, isQuery: false);
 4350
 04351                        copy.Dispose();
 04352                    }
 04353                }
 04354            }
 04355        }
 4356
 4357        /// <summary>Partially unescape the path ('/', '.' and optionally '\\') before compressing.</summary>
 4358        private static void UnescapePathSlashesAndDots(ref ValueStringBuilder vsb, int i, bool unescapeBackslashes)
 04359        {
 04360            Span<char> chars = vsb.RawChars.Slice(0, vsb.Length);
 4361
 04362            int writeOffset = i;
 4363
 04364            while ((uint)i < (uint)chars.Length)
 04365            {
 04366                if (chars[i] == '%' && (uint)(i + 2) < (uint)chars.Length)
 04367                {
 04368                    char decoded = UriHelper.DecodeHexChars(chars[i + 1], chars[i + 2]);
 4369
 04370                    if (decoded == '/' || decoded == '.' || (unescapeBackslashes && decoded == '\\'))
 04371                    {
 04372                        chars[writeOffset++] = decoded;
 04373                        i += 3;
 04374                        continue;
 4375                    }
 04376                }
 4377
 04378                chars[writeOffset++] = chars[i];
 04379                i++;
 04380            }
 4381
 04382            vsb.Length = writeOffset;
 04383        }
 4384
 4385        private static void Compress(ref ValueStringBuilder dest, int start, UriParser syntax)
 04386        {
 04387            Debug.Assert(start <= dest.Length);
 4388
 04389            dest.Length = start + UriHelper.Compress(
 04390                dest.RawChars.Slice(start, dest.Length - start),
 04391                syntax.InFact(UriSyntaxFlags.ConvertPathSlashes),
 04392                syntax.InFact(UriSyntaxFlags.CanonicalizeAsFilePath));
 04393        }
 4394
 4395        private static void Compress(char[] dest, int start, ref int destLength, UriParser syntax)
 04396        {
 04397            destLength = start + UriHelper.Compress(
 04398                dest.AsSpan(start, destLength - start),
 04399                syntax.InFact(UriSyntaxFlags.ConvertPathSlashes),
 04400                syntax.InFact(UriSyntaxFlags.CanonicalizeAsFilePath));
 04401        }
 4402
 4403        //
 4404        // CombineUri
 4405        //
 4406        //  Given 2 URI strings, combine them into a single resultant URI string
 4407        //
 4408        // Inputs:
 4409        //  <argument>  basePart
 4410        //      Base URI to combine with
 4411        //
 4412        //  <argument>  relativePart
 4413        //      String expected to be relative URI
 4414        //
 4415        // Assumes:
 4416        //  <basePart> is in canonic form
 4417        //
 4418        // Returns:
 4419        //  Resulting combined URI string
 4420        //
 4421        private static string CombineUri(Uri basePart, string relativePart, UriFormat uriFormat)
 04422        {
 4423            //NB: relativePart is ensured as not empty by the caller
 4424            //    Another assumption is that basePart is an AbsoluteUri
 4425
 4426            // This method was not optimized for efficiency
 4427            // Means a relative Uri ctor may be relatively slow plus it increases the footprint of the baseUri
 4428
 04429            char c1 = relativePart[0];
 4430
 4431            //check a special case for the base as DOS path and a rooted relative string
 04432            if (basePart.IsDosPath &&
 04433                (c1 == '/' || c1 == '\\') &&
 04434                (relativePart.Length == 1 || (relativePart[1] != '/' && relativePart[1] != '\\')))
 04435            {
 4436                // take relative part appended to the base string after the drive letter
 04437                int idx = basePart.OriginalString.IndexOf(':');
 04438                if (basePart.IsImplicitFile)
 04439                {
 04440                    return string.Concat(basePart.OriginalString.AsSpan(0, idx + 1), relativePart);
 4441                }
 4442
 4443                // The basePart has explicit scheme (could be not file:), take the DOS drive ':' position
 04444                idx = basePart.OriginalString.IndexOf(':', idx + 1);
 04445                return string.Concat(basePart.OriginalString.AsSpan(0, idx + 1), relativePart);
 4446            }
 4447
 4448            // Check special case for Unc or absolute path in relativePart when base is FILE
 04449            if (StaticIsFile(basePart.Syntax))
 04450            {
 04451                if (c1 == '\\' || c1 == '/')
 04452                {
 04453                    if (relativePart.Length >= 2 && (relativePart[1] == '\\' || relativePart[1] == '/'))
 04454                    {
 4455                        //Assuming relative is a Unc path and base is a file uri.
 04456                        return basePart.IsImplicitFile ? relativePart : "file:" + relativePart;
 4457                    }
 4458
 4459                    // here we got an absolute path in relativePart,
 4460                    // For compatibility with V1.0 parser we restrict the compression scope to Unc Share, i.e. \\host\sh
 04461                    if (basePart.IsUnc)
 04462                    {
 04463                        ReadOnlySpan<char> share = basePart.GetParts(UriComponents.Path | UriComponents.KeepDelimiter, U
 04464                        int i = share.Slice(1).IndexOf('/');
 04465                        if (i >= 0)
 04466                        {
 04467                            share = share.Slice(0, i + 1);
 04468                        }
 4469
 04470                        if (basePart.IsImplicitFile)
 04471                        {
 04472                            return string.Concat(@"\\", basePart.GetParts(UriComponents.Host, UriFormat.Unescaped), shar
 4473                        }
 4474
 04475                        return string.Concat("file://", basePart.GetParts(UriComponents.Host, uriFormat), share, relativ
 4476                    }
 4477                    // It's not obvious but we've checked (for this relativePart format) that baseUti is nor UNC nor DOS
 4478                    //
 4479                    // Means base is a Unix style path and, btw, IsImplicitFile cannot be the case either
 04480                    return "file://" + relativePart;
 4481                }
 04482            }
 4483
 4484            // If we are here we did not recognize absolute DOS/UNC path for a file: base uri
 4485            // Note that DOS path may still happen in the relativePart and if so it may override the base uri scheme.
 4486
 04487            bool convBackSlashes = basePart.Syntax.InFact(UriSyntaxFlags.ConvertPathSlashes);
 4488
 4489            string? left;
 4490
 4491            // check for network or local absolute path
 04492            if (c1 == '/' || (c1 == '\\' && convBackSlashes))
 04493            {
 04494                if (relativePart.Length >= 2 && relativePart[1] == '/')
 04495                {
 4496                    // got an authority in relative path and the base scheme is not file (checked)
 04497                    return basePart.Scheme + ':' + relativePart;
 4498                }
 4499
 4500                // Got absolute relative path, and the base is not FILE nor a DOS path (checked at the method start)
 04501                if (basePart.HostType == Flags.IPv6HostType)
 04502                {
 04503                    left = $"{basePart.GetParts(UriComponents.Scheme | UriComponents.UserInfo, uriFormat)}[{basePart.Dns
 04504                }
 4505                else
 04506                {
 04507                    left = basePart.GetParts(UriComponents.SchemeAndServer | UriComponents.UserInfo, uriFormat);
 04508                }
 4509
 04510                return convBackSlashes && c1 == '\\' ?
 04511                    string.Concat(left, "/", relativePart.AsSpan(1)) :
 04512                    left + relativePart;
 4513            }
 4514
 4515            // Here we got a relative path
 4516            // Need to run path Compression because this is how relative Uri combining works
 4517
 4518            // Take the base part path up to and including the last slash
 04519            left = basePart.GetParts(UriComponents.Path | UriComponents.KeepDelimiter,
 04520                basePart.IsImplicitFile ? UriFormat.Unescaped : uriFormat);
 04521            int length = left.Length;
 04522            char[] path = new char[length + relativePart.Length];
 4523
 04524            if (length > 0)
 04525            {
 04526                left.CopyTo(0, path, 0, length);
 04527                while (length > 0)
 04528                {
 04529                    if (path[--length] == '/')
 04530                    {
 04531                        ++length;
 04532                        break;
 4533                    }
 04534                }
 04535            }
 4536
 4537            //Append relative path to the result
 04538            relativePart.CopyTo(0, path, length, relativePart.Length);
 4539
 4540            // Split relative on path and extra (for compression)
 04541            c1 = basePart.Syntax.InFact(UriSyntaxFlags.MayHaveQuery) ? '?' : c_DummyChar;
 4542
 4543            // The  implicit file check is to avoid a fragment in the implicit file combined uri.
 04544            char c2 = (!basePart.IsImplicitFile && basePart.Syntax.InFact(UriSyntaxFlags.MayHaveFragment)) ? '#' :
 04545                c_DummyChar;
 04546            ReadOnlySpan<char> extra = string.Empty;
 4547
 4548            // assuming c_DummyChar may not happen in an unicode uri string
 04549            if (!(c1 == c_DummyChar && c2 == c_DummyChar))
 04550            {
 04551                int i = 0;
 04552                for (; i < relativePart.Length; ++i)
 04553                {
 04554                    if (path[length + i] == c1 || path[length + i] == c2)
 04555                    {
 04556                        break;
 4557                    }
 04558                }
 04559                if (i == 0)
 04560                {
 04561                    extra = relativePart;
 04562                }
 04563                else if (i < relativePart.Length)
 04564                {
 04565                    extra = relativePart.AsSpan(i);
 04566                }
 04567                length += i;
 04568            }
 4569            else
 04570            {
 04571                length += relativePart.Length;
 04572            }
 4573
 4574            // Take the base part up to the path
 04575            if (basePart.HostType == Flags.IPv6HostType)
 04576            {
 04577                if (basePart.IsImplicitFile)
 04578                {
 04579                    left = @"\\[" + basePart.DnsSafeHost + ']';
 04580                }
 4581                else
 04582                {
 04583                    left = basePart.GetParts(UriComponents.Scheme | UriComponents.UserInfo, uriFormat)
 04584                            + '[' + basePart.DnsSafeHost + ']'
 04585                            + basePart.GetParts(UriComponents.KeepDelimiter | UriComponents.Port, uriFormat);
 04586                }
 04587            }
 4588            else
 04589            {
 04590                if (basePart.IsImplicitFile)
 04591                {
 04592                    if (basePart.IsDosPath)
 04593                    {
 4594                        // The FILE DOS path comes as /c:/path, we have to exclude first 3 chars from compression
 04595                        Compress(path, 3, ref length, basePart.Syntax);
 04596                        return string.Concat(path.AsSpan(1, length - 1), extra);
 4597                    }
 04598                    else if (!OperatingSystem.IsWindows() && basePart.IsUnixPath)
 04599                    {
 04600                        left = basePart.GetParts(UriComponents.Host, UriFormat.Unescaped);
 04601                    }
 4602                    else
 04603                    {
 04604                        left = @"\\" + basePart.GetParts(UriComponents.Host, UriFormat.Unescaped);
 04605                    }
 04606                }
 4607                else
 04608                {
 04609                    left = basePart.GetParts(UriComponents.SchemeAndServer | UriComponents.UserInfo, uriFormat);
 04610                }
 04611            }
 4612            //compress the path
 04613            Compress(path, basePart.SecuredPathIndex, ref length, basePart.Syntax);
 04614            return string.Concat(left, path.AsSpan(0, length), extra);
 04615        }
 4616
 4617        //
 4618        // PathDifference
 4619        //
 4620        //  Performs the relative path calculation for MakeRelative()
 4621        //
 4622        // Inputs:
 4623        //  <argument>  path1
 4624        //  <argument>  path2
 4625        //      Paths for which we calculate the difference
 4626        //
 4627        //  <argument>  compareCase
 4628        //      False if we consider characters that differ only in case to be
 4629        //      equal
 4630        //
 4631        // Returns:
 4632        //  A string which is the relative path difference between <path1> and
 4633        //  <path2> such that if <path1> and the calculated difference are used
 4634        //  as arguments to Combine(), <path2> is returned
 4635        //
 4636        // Throws:
 4637        //  Nothing
 4638        //
 4639        private static string PathDifference(string path1, string path2, bool compareCase)
 04640        {
 4641            int i;
 04642            int si = -1;
 4643
 04644            for (i = 0; (i < path1.Length) && (i < path2.Length); ++i)
 04645            {
 04646                if ((path1[i] != path2[i])
 04647                    && (compareCase
 04648                        || (char.ToLowerInvariant(path1[i])
 04649                            != char.ToLowerInvariant(path2[i]))))
 04650                {
 04651                    break;
 4652                }
 04653                else if (path1[i] == '/')
 04654                {
 04655                    si = i;
 04656                }
 04657            }
 4658
 04659            if (i == 0)
 04660            {
 04661                return path2;
 4662            }
 04663            if ((i == path1.Length) && (i == path2.Length))
 04664            {
 04665                return string.Empty;
 4666            }
 4667
 04668            StringBuilder relPath = new StringBuilder();
 4669            // Walk down several dirs
 04670            for (; i < path1.Length; ++i)
 04671            {
 04672                if (path1[i] == '/')
 04673                {
 04674                    relPath.Append("../");
 04675                }
 04676            }
 4677            // Same path except that path1 ended with a file name and path2 didn't
 04678            if (relPath.Length == 0 && path2.Length - 1 == si)
 04679                return "./"; // Truncate the file name
 04680            return relPath.Append(path2.AsSpan(si + 1)).ToString();
 04681        }
 4682
 4683        //
 4684        // MakeRelative (toUri)
 4685        //
 4686        //  Return a relative path which when applied to this Uri would create the
 4687        //  resulting Uri <toUri>
 4688        //
 4689        // Inputs:
 4690        //  <argument>  toUri
 4691        //      Uri to which we calculate the transformation from this Uri
 4692        //
 4693        // Returns:
 4694        //  If the 2 Uri are common except for a relative path difference, then that
 4695        //  difference, else the display name of this Uri
 4696        //
 4697        // Throws:
 4698        //  ArgumentNullException, InvalidOperationException
 4699        //
 4700        [Obsolete("Uri.MakeRelative has been deprecated. Use MakeRelativeUri(Uri uri) instead.")]
 4701        public string MakeRelative(Uri toUri)
 04702        {
 04703            ArgumentNullException.ThrowIfNull(toUri);
 4704
 04705            if (IsNotAbsoluteUri || toUri.IsNotAbsoluteUri)
 04706                throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 4707
 04708            if ((Scheme == toUri.Scheme) && (Host == toUri.Host) && (Port == toUri.Port))
 04709                return PathDifference(AbsolutePath, toUri.AbsolutePath, !IsUncOrDosPath);
 4710
 04711            return toUri.ToString();
 04712        }
 4713
 4714        /// <internalonly/>
 4715        [Obsolete("Uri.Canonicalize has been deprecated and is not supported.")]
 4716        protected virtual void Canonicalize()
 04717        {
 4718            // this method if suppressed by the derived class
 4719            // would lead to suppressing of a path compression
 4720            // It does not make much sense and violates Fxcop on calling a virtual method in the ctor.
 4721            // Should be deprecated and removed asap.
 04722        }
 4723
 4724        /// <internalonly/>
 4725        [Obsolete("Uri.Parse has been deprecated and is not supported.")]
 4726        protected virtual void Parse()
 04727        {
 4728            // this method if suppressed by the derived class
 4729            // would lead to an unconstructed Uri instance.
 4730            // It does not make any sense and violates Fxcop on calling a virtual method in the ctor.
 4731            // Should be deprecated and removed asap.
 04732        }
 4733
 4734        /// <internalonly/>
 4735        [Obsolete("Uri.Escape has been deprecated and is not supported.")]
 4736        protected virtual void Escape()
 04737        {
 4738            // this method if suppressed by the derived class
 4739            // would lead to the same effect as dontEscape=true.
 4740            // It does not make much sense and violates Fxcop on calling a virtual method in the ctor.
 4741            // Should be deprecated and removed asap.
 04742        }
 4743
 4744        //
 4745        // Unescape
 4746        //
 4747        //  Convert any escape sequences in <path>. Escape sequences can be
 4748        //  hex encoded reserved characters (e.g. %40 == '@') or hex encoded
 4749        //  UTF-8 sequences (e.g. %C4%D2 == 'Latin capital Ligature Ij')
 4750        //
 4751        /// <internalonly/>
 4752        [Obsolete("Uri.Unescape has been deprecated. Use GetComponents() or Uri.UnescapeDataString() to unescape a Uri c
 4753        protected virtual string Unescape(string path)
 04754        {
 4755            // This method is dangerous since it gives path unescaping control
 4756            // to the derived class without any permission demand.
 4757            // Should be deprecated and removed asap.
 4758
 04759            return UnescapeDataString(path);
 04760        }
 4761
 4762        [Obsolete("Uri.EscapeString has been deprecated. Use GetComponents() or Uri.EscapeDataString to escape a Uri com
 4763        protected static string EscapeString(string? str) =>
 04764            str is null ? string.Empty :
 04765            UriHelper.EscapeString(str, checkExistingEscaped: true, UriHelper.UnreservedReservedExceptQuestionMarkHash);
 4766
 4767        //
 4768        // CheckSecurity
 4769        //
 4770        //  Check for any invalid or problematic character sequences
 4771        //
 4772        /// <internalonly/>
 4773        [Obsolete("Uri.CheckSecurity has been deprecated and is not supported.")]
 4774        protected virtual void CheckSecurity()
 04775        {
 4776            // This method just does not make sense
 4777            // Should be deprecated and removed asap.
 04778        }
 4779
 4780        //
 4781        // IsReservedCharacter
 4782        //
 4783        //  Determine whether a character is part of the reserved set
 4784        //
 4785        // Returns:
 4786        //  true if <character> is reserved else false
 4787        //
 4788        /// <internalonly/>
 4789        [Obsolete("Uri.IsReservedCharacter has been deprecated and is not supported.")]
 4790        protected virtual bool IsReservedCharacter(char character)
 04791        {
 4792            // This method just does not make sense as protected virtual
 4793            // It should go public static asap
 4794
 04795            return (character == ';')
 04796                || (character == '/')
 04797                || (character == ':')
 04798                || (character == '@')   // OK FS char
 04799                || (character == '&')
 04800                || (character == '=')
 04801                || (character == '+')   // OK FS char
 04802                || (character == '$')   // OK FS char
 04803                || (character == ',')
 04804                ;
 04805        }
 4806
 4807        //
 4808        // IsExcludedCharacter
 4809        //
 4810        //  Determine if a character should be excluded from a URI and therefore be
 4811        //  escaped
 4812        //
 4813        // Returns:
 4814        //  true if <character> should be escaped else false
 4815        //
 4816        /// <internalonly/>
 4817        [Obsolete("Uri.IsExcludedCharacter has been deprecated and is not supported.")]
 4818        protected static bool IsExcludedCharacter(char character)
 04819        {
 4820            // This method just does not make sense as protected
 4821            // It should go public static asap
 4822
 4823            //
 4824            // the excluded characters...
 4825            //
 4826
 04827            return (character <= 0x20)
 04828                || (character >= 0x7f)
 04829                || (character == '<')
 04830                || (character == '>')
 04831                || (character == '#')
 04832                || (character == '%')
 04833                || (character == '"')
 04834
 04835                //
 04836                // the 'unwise' characters...
 04837                //
 04838
 04839                || (character == '{')
 04840                || (character == '}')
 04841                || (character == '|')
 04842                || (character == '\\')
 04843                || (character == '^')
 04844                || (character == '[')
 04845                || (character == ']')
 04846                || (character == '`')
 04847                ;
 04848        }
 4849
 4850        //
 4851        // IsBadFileSystemCharacter
 4852        //
 4853        //  Determine whether a character would be an invalid character if used in
 4854        //  a file system name. Note, this is really based on NTFS rules
 4855        //
 4856        // Returns:
 4857        //  true if <character> would be a treated as a bad file system character
 4858        //  else false
 4859        //
 4860        [Obsolete("Uri.IsBadFileSystemCharacter has been deprecated and is not supported.")]
 4861        protected virtual bool IsBadFileSystemCharacter(char character)
 04862        {
 4863            // This method just does not make sense as protected virtual
 4864            // It should go public static asap
 4865
 04866            return (character < 0x20)
 04867                || (character == ';')
 04868                || (character == '/')
 04869                || (character == '?')
 04870                || (character == ':')
 04871                || (character == '&')
 04872                || (character == '=')
 04873                || (character == ',')
 04874                || (character == '*')
 04875                || (character == '<')
 04876                || (character == '>')
 04877                || (character == '"')
 04878                || (character == '|')
 04879                || (character == '\\')
 04880                || (character == '^')
 04881                ;
 04882        }
 4883
 4884        //Used by UriBuilder
 4885        internal bool HasAuthority
 4886        {
 4887            get
 4888            {
 4889                return InFact(Flags.AuthorityFound);
 4890            }
 4891        }
 4892    } // class Uri
 4893} // namespace System
 4894

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.Uri/src/System/UriExt.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Globalization;
 8using System.Runtime.CompilerServices;
 9using System.Runtime.InteropServices;
 10using System.Text;
 11
 12namespace System
 13{
 14    public partial class Uri
 15    {
 16        /// <summary>Helper called by all constructors to construct the Uri.</summary>
 17        [MemberNotNull(nameof(_string))]
 18        private void CreateThis(string? uri, Flags flags, UriKind uriKind)
 019        {
 020            UriFormatException? e = TryCreateThis(uri, flags, uriKind);
 21
 022            if (e is not null)
 023            {
 024                throw e;
 25            }
 026        }
 27
 28        /// <summary>Core helper called by all constructors and TryCreate factories to construct the Uri.</summary>
 29        [MemberNotNull(nameof(_string))]
 30        private UriFormatException? TryCreateThis(string? uri, Flags flags, UriKind uriKind)
 66131        {
 66132            DebugAssertInCtor();
 33
 66134            if (uriKind is < UriKind.RelativeOrAbsolute or > UriKind.Relative)
 035            {
 036                throw new ArgumentException(SR.Format(SR.net_uri_InvalidUriKind, uriKind));
 37            }
 38
 66139            Debug.Assert((flags & Flags.DisableImplicitFilePaths) == 0 || uriKind != UriKind.Relative);
 66140            Debug.Assert((flags & Flags.DisablePathAndQueryCanonicalization) == 0 || uriKind == UriKind.Absolute);
 41
 66142            _string = uri ?? string.Empty;
 43
 66144            Debug.Assert(_originalUnicodeString is null && _info is null && _syntax is null && _flags == Flags.Zero);
 45
 66146            _flags = flags;
 47
 66148            ParsingError err = ParseScheme(_string, ref _flags, ref _syntax!);
 66149            Debug.Assert((err is ParsingError.None) == (_syntax is not null));
 50
 66151            bool hasUnicode = false;
 52
 66153            if (IriParsing && CheckForUnicodeOrEscapedUnreserved(_string))
 054            {
 055                _flags |= Flags.HasUnicode;
 056                hasUnicode = true;
 057                _originalUnicodeString = _string; // original string location changed
 058            }
 59
 66160            if (err != ParsingError.None)
 061            {
 62                // If we encountered any parsing errors that indicate this may be a relative Uri,
 63                // and we'll allow relative Uri's, then create one.
 064                if (uriKind != UriKind.Absolute && err <= ParsingError.LastErrorOkayForRelativeUris)
 065                {
 066                    goto SwitchToRelativeUri;
 67                }
 68
 69                // This is a fatal error based solely on scheme name parsing
 070                return GetException(err);
 71            }
 72
 66173            Debug.Assert(_syntax is not null);
 74
 66175            if (IsImplicitFile)
 076            {
 077                if (uriKind == UriKind.Relative)
 078                {
 079                    goto SwitchToRelativeUri;
 80                }
 81
 82                // V1 compat
 83                // A relative Uri wins over implicit UNC path unless the UNC path is of the form "\\something" and
 84                // uriKind != Absolute
 85                // A relative Uri wins over implicit Unix path unless uriKind == Absolute
 086                if (NotAny(Flags.DosPath) && uriKind == UriKind.RelativeOrAbsolute &&
 087                   ((_string.Length >= 2 && (_string[0] != '\\' || _string[1] != '\\'))
 088                    || (!OperatingSystem.IsWindows() && InFact(Flags.UnixPath))))
 089                {
 090                    goto SwitchToRelativeUri;
 91                    // Otherwise an absolute file Uri wins when it's of the form "\\something"
 92                }
 093            }
 94
 66195            if (_syntax.IsSimple)
 66196            {
 66197                if ((err = PrivateParseMinimal()) != ParsingError.None)
 098                {
 099                    if (uriKind != UriKind.Absolute && err <= ParsingError.LastErrorOkayForRelativeUris)
 0100                    {
 101                        // RFC 3986 Section 5.4.2 - http:(relativeUri) may be considered a valid relative Uri.
 0102                        goto SwitchToRelativeUri;
 103                    }
 104
 0105                    return GetException(err);
 106                }
 661107            }
 108            else
 0109            {
 110                // offer custom parser to create a parsing context
 0111                _syntax = _syntax.InternalOnNewUri();
 112
 113                // in case they won't call us
 0114                _flags |= Flags.UserDrivenParsing;
 115
 116                // Ask a registered type to validate this uri
 0117                _syntax.InternalValidate(this, out UriFormatException? e);
 118
 0119                if (e is not null)
 0120                {
 0121                    return e;
 122                }
 123
 0124                if (InFact(Flags.ErrorOrParsingRecursion))
 0125                {
 126                    // User parser took over on an invalid Uri
 127                    // we use = here to clear all parsing flags for a uri that we think is invalid.
 0128                    _flags = Flags.UserDrivenParsing | (_flags & Flags.UserEscaped);
 0129                }
 0130            }
 131
 661132            if (uriKind == UriKind.Relative)
 0133            {
 134                // Here we know that we can create an absolute Uri, but the user has requested only a relative one
 0135                return GetException(ParsingError.CannotCreateRelative);
 136            }
 137
 661138            if (hasUnicode)
 0139            {
 140                // In this scenario we need to parse the whole string
 141                try
 0142                {
 0143                    EnsureParseRemaining();
 0144                }
 0145                catch (UriFormatException ex)
 0146                {
 0147                    return ex;
 148                }
 0149            }
 150
 151            // We have a valid absolute Uri.
 661152            DebugSetLeftCtor();
 661153            return null;
 154
 0155        SwitchToRelativeUri:
 0156            Debug.Assert(uriKind != UriKind.Absolute);
 157
 0158            _syntax = null!;
 0159            _flags &= Flags.UserEscaped | Flags.HasUnicode; // the only flags that make sense for a relative uri
 160
 0161            if (hasUnicode)
 0162            {
 0163                _string = EscapeUnescapeIri(default, _originalUnicodeString, isQuery: false);
 0164            }
 165
 0166            DebugSetLeftCtor();
 0167            return null;
 661168        }
 169
 170        /// <summary>SearchValues for all ASCII characters other than %</summary>
 1171        private static readonly SearchValues<char> s_asciiOtherThanPercent = SearchValues.Create(
 1172            "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000A\u000B\u000C\u000D\u000E\u000F" +
 1173            "\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" +
 1174            "\u0020\u0021\u0022\u0023\u0024" +  "\u0026\u0027\u0028\u0029\u002A\u002B\u002C\u002D\u002E\u002F" +
 1175            "\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B\u003C\u003D\u003E\u003F" +
 1176            "\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004A\u004B\u004C\u004D\u004E\u004F" +
 1177            "\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005A\u005B\u005C\u005D\u005E\u005F" +
 1178            "\u0060\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006A\u006B\u006C\u006D\u006E\u006F" +
 1179            "\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007A\u007B\u007C\u007D\u007E\u007F");
 180
 181        /// <summary>
 182        /// Unescapes entire string and checks if it has unicode chars.Also checks for sequences that are 3986 Unreserve
 183        /// </summary>
 184        private static bool CheckForUnicodeOrEscapedUnreserved(string data)
 661185        {
 661186            int i = data.AsSpan().IndexOfAnyExcept(s_asciiOtherThanPercent);
 661187            if (i >= 0)
 0188            {
 0189                for ( ; i < data.Length; i++)
 0190                {
 0191                    char c = data[i];
 0192                    if (c == '%')
 0193                    {
 0194                        if ((uint)(i + 2) < (uint)data.Length)
 0195                        {
 0196                            char value = UriHelper.DecodeHexChars(data[i + 1], data[i + 2]);
 197
 0198                            if (!char.IsAscii(value) || UriHelper.Unreserved.Contains(value))
 0199                            {
 0200                                return true;
 201                            }
 202
 0203                            i += 2;
 0204                        }
 0205                    }
 0206                    else if (c > 0x7F)
 0207                    {
 0208                        return true;
 209                    }
 0210                }
 0211            }
 212
 661213            return false;
 661214        }
 215
 216        /// <inheritdoc/>
 217        static Uri IParsable<Uri>.Parse(string s, IFormatProvider? provider)
 0218        {
 0219            ArgumentNullException.ThrowIfNull(s);
 220
 0221            Uri result = new();
 0222            result.CreateThis(s, Flags.DisableImplicitFilePaths, UriKind.RelativeOrAbsolute);
 0223            return result;
 0224        }
 225
 226        /// <inheritdoc/>
 227        static bool IParsable<Uri>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [NotNullWhen(true)
 0228        {
 0229            result = CreateHelper(s, Flags.DisableImplicitFilePaths, UriKind.RelativeOrAbsolute);
 0230            return result is not null;
 0231        }
 232
 233        //
 234        //  Returns true if the string represents a valid argument to the Uri ctor
 235        //  If uriKind != AbsoluteUri then certain parsing errors are ignored but Uri usage is limited
 236        //
 237        public static bool TryCreate([NotNullWhen(true), StringSyntax(StringSyntaxAttribute.Uri, "uriKind")] string? uri
 661238        {
 661239            result = CreateHelper(uriString, Flags.Zero, uriKind);
 661240            return result is not null;
 661241        }
 242
 243        /// <summary>
 244        /// Creates a new <see cref="Uri"/> using the specified <see cref="string"/> instance and <see cref="UriCreation
 245        /// </summary>
 246        /// <param name="uriString">The string representation of the <see cref="Uri"/>.</param>
 247        /// <param name="creationOptions">Options that control how the <seealso cref="Uri"/> is created and behaves.</pa
 248        /// <param name="result">The constructed <see cref="Uri"/>.</param>
 249        /// <returns><see langword="true"/> if the <see cref="Uri"/> was successfully created; otherwise, <see langword=
 250        public static bool TryCreate([NotNullWhen(true), StringSyntax(StringSyntaxAttribute.Uri)] string? uriString, in 
 0251        {
 0252            result = CreateHelper(uriString, creationOptions._flags, UriKind.Absolute);
 0253            return result is not null;
 0254        }
 255
 256        public static bool TryCreate(Uri? baseUri, string? relativeUri, [NotNullWhen(true)] out Uri? result)
 0257        {
 0258            if (TryCreate(relativeUri, UriKind.RelativeOrAbsolute, out Uri? relativeLink))
 0259            {
 0260                if (!relativeLink.IsAbsoluteUri)
 0261                    return TryCreate(baseUri, relativeLink, out result);
 262
 0263                result = relativeLink;
 0264                return true;
 265            }
 266
 0267            result = null;
 0268            return false;
 0269        }
 270
 271        public static bool TryCreate(Uri? baseUri, Uri? relativeUri, [NotNullWhen(true)] out Uri? result)
 0272        {
 0273            result = null;
 274
 0275            if (baseUri is null || relativeUri is null)
 0276                return false;
 277
 0278            if (baseUri.IsNotAbsoluteUri)
 0279                return false;
 280
 0281            string? newUriString = null;
 282
 283            bool dontEscape;
 0284            if (baseUri.Syntax.IsSimple)
 0285            {
 0286                dontEscape = relativeUri.UserEscaped;
 0287                result = ResolveHelper(baseUri, relativeUri, ref newUriString, ref dontEscape);
 0288            }
 289            else
 0290            {
 0291                dontEscape = false;
 0292                newUriString = baseUri.Syntax.InternalResolve(baseUri, relativeUri, out UriFormatException? e);
 293
 0294                if (e != null)
 0295                    return false;
 0296            }
 297
 0298            result ??= CreateHelper(newUriString!, dontEscape ? Flags.UserEscaped : Flags.Zero, UriKind.Absolute);
 0299            Debug.Assert(result is null || result.IsAbsoluteUri);
 300
 0301            return result is not null;
 0302        }
 303
 304        public string GetComponents(UriComponents components, UriFormat format)
 0305        {
 0306            if (DisablePathAndQueryCanonicalization && (components & (UriComponents.Path | UriComponents.Query)) != 0)
 0307            {
 0308                throw new InvalidOperationException(SR.net_uri_GetComponentsCalledWhenCanonicalizationDisabled);
 309            }
 310
 0311            return InternalGetComponents(components, format);
 0312        }
 313
 314        private string InternalGetComponents(UriComponents components, UriFormat format)
 0315        {
 0316            if (((components & UriComponents.SerializationInfoString) != 0) && components != UriComponents.Serialization
 0317                throw new ArgumentOutOfRangeException(nameof(components), components, SR.net_uri_NotJustSerialization);
 318
 0319            if ((format & ~UriFormat.SafeUnescaped) != 0)
 0320                throw new ArgumentOutOfRangeException(nameof(format));
 321
 0322            if (IsNotAbsoluteUri)
 0323            {
 0324                if (components == UriComponents.SerializationInfoString)
 0325                    return GetRelativeSerializationString(format);
 326                else
 0327                    throw new InvalidOperationException(SR.net_uri_NotAbsolute);
 328            }
 329
 0330            if (Syntax.IsSimple)
 0331                return GetComponentsHelper(components, format);
 332
 0333            return Syntax.InternalGetComponents(this, components, format);
 0334        }
 335
 336        //
 337        // This is for languages that do not support == != operators overloading
 338        //
 339        // Note that Uri.Equals will get an optimized path but is limited to true/false result only
 340        //
 341        public static int Compare(Uri? uri1, Uri? uri2, UriComponents partsToCompare, UriFormat compareFormat,
 342            StringComparison comparisonType)
 0343        {
 0344            if (uri1 is null)
 0345            {
 0346                if (uri2 is null)
 0347                    return 0; // Equal
 0348                return -1;    // null < non-null
 349            }
 350
 0351            if (uri2 is null)
 0352                return 1;     // non-null > null
 353
 354            // a relative uri is always less than an absolute one
 0355            if (!uri1.IsAbsoluteUri || !uri2.IsAbsoluteUri)
 0356                return uri1.IsAbsoluteUri ? 1 : uri2.IsAbsoluteUri ? -1 : string.Compare(uri1.OriginalString,
 0357                    uri2.OriginalString, comparisonType);
 358
 0359            return string.Compare(
 0360                                    uri1.GetParts(partsToCompare, compareFormat),
 0361                                    uri2.GetParts(partsToCompare, compareFormat),
 0362                                    comparisonType
 0363                                  );
 0364        }
 365
 366        public bool IsWellFormedOriginalString()
 0367        {
 0368            if (IsNotAbsoluteUri || Syntax.IsSimple)
 0369                return InternalIsWellFormedOriginalString();
 370
 0371            return Syntax.InternalIsWellFormedOriginalString(this);
 0372        }
 373
 374        public static bool IsWellFormedUriString([NotNullWhen(true), StringSyntax(StringSyntaxAttribute.Uri, "uriKind")]
 0375        {
 376            Uri? result;
 377
 0378            if (!Uri.TryCreate(uriString, uriKind, out result))
 0379                return false;
 380
 0381            return result.IsWellFormedOriginalString();
 0382        }
 383
 384        // Returns false if OriginalString value
 385        // (1) is not correctly escaped as per URI spec excluding intl UNC name case
 386        // (2) or is an absolute Uri that represents implicit file Uri "c:\dir\file"
 387        // (3) or is an absolute Uri that misses a slash before path "file://c:/dir/file"
 388        // (4) or contains unescaped backslashes even if they will be treated
 389        //     as forward slashes like http:\\host/path\file or file:\\\c:\path
 390        internal bool InternalIsWellFormedOriginalString()
 0391        {
 0392            if (UserDrivenParsing)
 0393                throw new InvalidOperationException(SR.Format(SR.net_uri_UserDrivenParsing, this.GetType()));
 394
 0395            string str = _string;
 396
 397            // For a relative Uri we only care about escaping and backslashes
 0398            if (!IsAbsoluteUri)
 0399            {
 400                // my:scheme/path?query is not well formed because the colon is ambiguous
 0401                if (CheckForColonInFirstPathSegment(str))
 0402                {
 0403                    return false;
 404                }
 405
 0406                return (CheckCanonical(str, c_EOL, out _) & (Check.BackslashInPath | Check.EscapedCanonical)) == Check.E
 407            }
 408
 409            // (2) or is an absolute Uri that represents implicit file Uri "c:\dir\file"
 0410            if (IsImplicitFile)
 0411                return false;
 412
 413            // This will get all the offsets, a Host name will be checked separately below
 0414            EnsureParseRemaining();
 415
 0416            Flags nonCanonical = (_flags & (Flags.E_CannotDisplayCanonical | Flags.IriCanonical));
 417
 418            // Cleanup canonical IRI from nonCanonical
 0419            if ((nonCanonical & (Flags.UserIriCanonical | Flags.PathIriCanonical | Flags.QueryIriCanonical | Flags.Fragm
 0420            {
 0421                if ((nonCanonical & (Flags.E_UserNotCanonical | Flags.UserIriCanonical)) == (Flags.E_UserNotCanonical | 
 0422                {
 0423                    nonCanonical &= ~(Flags.E_UserNotCanonical | Flags.UserIriCanonical);
 0424                }
 425
 0426                if ((nonCanonical & (Flags.E_PathNotCanonical | Flags.PathIriCanonical)) == (Flags.E_PathNotCanonical | 
 0427                {
 0428                    nonCanonical &= ~(Flags.E_PathNotCanonical | Flags.PathIriCanonical);
 0429                }
 430
 0431                if ((nonCanonical & (Flags.E_QueryNotCanonical | Flags.QueryIriCanonical)) == (Flags.E_QueryNotCanonical
 0432                {
 0433                    nonCanonical &= ~(Flags.E_QueryNotCanonical | Flags.QueryIriCanonical);
 0434                }
 435
 0436                if ((nonCanonical & (Flags.E_FragmentNotCanonical | Flags.FragmentIriCanonical)) == (Flags.E_FragmentNot
 0437                {
 0438                    nonCanonical &= ~(Flags.E_FragmentNotCanonical | Flags.FragmentIriCanonical);
 0439                }
 0440            }
 441
 442            // User, Path, Query or Fragment may have some non escaped characters
 0443            if (((nonCanonical & Flags.E_CannotDisplayCanonical & (Flags.E_UserNotCanonical | Flags.E_PathNotCanonical |
 0444                                    Flags.E_QueryNotCanonical | Flags.E_FragmentNotCanonical)) != Flags.Zero))
 0445            {
 0446                return false;
 447            }
 448
 449            // checking on scheme:\\ or file:////
 0450            if (InFact(Flags.AuthorityFound))
 0451            {
 0452                if (InFact(Flags.SchemeNotCanonical_NoTrailingSlashes))
 0453                {
 0454                    return false;
 455                }
 456
 0457                if (InFact(Flags.UncPath | Flags.DosPath))
 0458                {
 0459                    int idx = _info.Offset.Scheme + _syntax.SchemeName.Length + 3;
 460
 0461                    if (idx < _info.Offset.User && str[idx] is '/' or '\\')
 0462                    {
 0463                        return false;
 464                    }
 0465                }
 0466            }
 467
 468            // (3) or is an absolute Uri that misses a slash before path "file://c:/dir/file"
 469            // Note that for this check to be more general we assert that if Path is non empty and if it requires a firs
 470            // (which looks absent) then the method has to fail.
 471            // Today it's only possible for a Dos like path, i.e. file://c:/bla would fail below check.
 0472            if (InFact(Flags.FirstSlashAbsent) && _info.Offset.Query > _info.Offset.Path)
 0473                return false;
 474
 475            // (4) or contains unescaped backslashes even if they will be treated
 476            //     as forward slashes like http:\\host/path\file or file:\\\c:\path
 477            // Note we do not check for Flags.ShouldBeCompressed i.e. allow // /./ and alike as valid
 0478            if (InFact(Flags.BackslashInPath))
 0479                return false;
 480
 481            // Capturing a rare case like file:///c|/dir
 0482            if (IsDosPath && str[_info.Offset.Path + SecuredPathIndex - 1] == '|')
 0483                return false;
 484
 485            // Check escaping for authority
 486            // IPv6 hosts cannot be properly validated by CheckCanonical
 0487            if ((_flags & Flags.CanonicalDnsHost) == 0 && HostType != Flags.IPv6HostType)
 0488            {
 0489                int idx = _info.Offset.User;
 0490                Check result = CheckCanonical(str.AsSpan(idx, _info.Offset.Path - idx), '/', out _);
 491
 0492                if ((result & (Check.ReservedFound | Check.BackslashInPath | Check.EscapedCanonical)) != Check.EscapedCa
 0493                    && (!IriParsing || (result & (Check.DisplayCanonical | Check.FoundNonAscii | Check.NotIriCanonical))
 0494                {
 0495                    return false;
 496                }
 0497            }
 498
 499            // The scheme, host, port or path may need some canonicalization, but the uri string is found to be a "well 
 0500            return true;
 0501        }
 502
 503        /// <summary>Converts a string to its unescaped representation.</summary>
 504        /// <param name="stringToUnescape">The string to unescape.</param>
 505        /// <returns>The unescaped representation of <paramref name="stringToUnescape"/>.</returns>
 506        public static string UnescapeDataString(string stringToUnescape)
 0507        {
 0508            ArgumentNullException.ThrowIfNull(stringToUnescape);
 509
 0510            return UnescapeDataString(stringToUnescape, stringToUnescape);
 0511        }
 512
 513        /// <summary>Converts a span to its unescaped representation.</summary>
 514        /// <param name="charsToUnescape">The span to unescape.</param>
 515        /// <returns>The unescaped representation of <paramref name="charsToUnescape"/>.</returns>
 516        public static string UnescapeDataString(ReadOnlySpan<char> charsToUnescape)
 0517        {
 0518            return UnescapeDataString(charsToUnescape, backingString: null);
 0519        }
 520
 521        private static string UnescapeDataString(ReadOnlySpan<char> charsToUnescape, string? backingString = null)
 0522        {
 0523            Debug.Assert(backingString is null || backingString.Length == charsToUnescape.Length);
 524
 0525            int indexOfFirstToUnescape = charsToUnescape.IndexOf('%');
 0526            if (indexOfFirstToUnescape < 0)
 0527            {
 528                // Nothing to unescape, just return the original value.
 0529                return backingString ?? charsToUnescape.ToString();
 530            }
 531
 0532            var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
 533
 534            // We may throw for very large inputs (when growing the ValueStringBuilder).
 0535            vsb.EnsureCapacity(charsToUnescape.Length - indexOfFirstToUnescape);
 536
 0537            UriHelper.Unescape(charsToUnescape.Slice(indexOfFirstToUnescape), ref vsb);
 538
 0539            string result = string.Concat(charsToUnescape.Slice(0, indexOfFirstToUnescape), vsb.AsSpan());
 0540            vsb.Dispose();
 0541            return result;
 0542        }
 543
 544        /// <summary>Attempts to convert a span to its unescaped representation.</summary>
 545        /// <param name="charsToUnescape">The span to unescape.</param>
 546        /// <param name="destination">The output span that contains the unescaped result of the operation.</param>
 547        /// <param name="charsWritten">When this method returns, contains the number of chars that were written into <pa
 548        /// <returns><see langword="true"/> if the <paramref name="destination"/> was large enough to hold the entire re
 549        public static bool TryUnescapeDataString(ReadOnlySpan<char> charsToUnescape, Span<char> destination, out int cha
 0550        {
 0551            int indexOfFirstToUnescape = charsToUnescape.IndexOf('%');
 0552            if (indexOfFirstToUnescape < 0)
 0553            {
 554                // Nothing to unescape, just copy the original chars.
 0555                if (charsToUnescape.TryCopyTo(destination))
 0556                {
 0557                    charsWritten = charsToUnescape.Length;
 0558                    return true;
 559                }
 560
 0561                charsWritten = 0;
 0562                return false;
 563            }
 564
 0565            if (destination.Length < indexOfFirstToUnescape)
 0566            {
 0567                charsWritten = 0;
 0568                return false;
 569            }
 570
 571            // We may throw for very large inputs (when growing the ValueStringBuilder).
 572            scoped ValueStringBuilder vsb;
 573
 574            // If the input and destination buffers overlap, we must take care not to overwrite parts of the input befor
 575            // If the buffers start at the same location, we can still use the destination as the output length is stric
 0576            bool overlapped = charsToUnescape.Overlaps(destination) &&
 0577                !Unsafe.AreSame(ref MemoryMarshal.GetReference(charsToUnescape), ref MemoryMarshal.GetReference(destinat
 578
 0579            if (overlapped)
 0580            {
 0581                vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
 0582                vsb.EnsureCapacity(charsToUnescape.Length - indexOfFirstToUnescape);
 0583            }
 584            else
 0585            {
 0586                vsb = new ValueStringBuilder(destination.Slice(indexOfFirstToUnescape));
 0587            }
 588
 0589            UriHelper.Unescape(charsToUnescape.Slice(indexOfFirstToUnescape), ref vsb);
 590
 0591            int newLength = indexOfFirstToUnescape + vsb.Length;
 0592            Debug.Assert(newLength <= charsToUnescape.Length);
 593
 0594            if (destination.Length >= newLength)
 0595            {
 0596                charsToUnescape.Slice(0, indexOfFirstToUnescape).CopyTo(destination);
 597
 0598                if (overlapped)
 0599                {
 0600                    vsb.AsSpan().CopyTo(destination.Slice(indexOfFirstToUnescape));
 0601                    vsb.Dispose();
 0602                }
 603                else
 0604                {
 605                    // We are expecting the builder not to grow if the original span was large enough.
 606                    // This means that we MUST NOT over allocate anywhere in UnescapeString (e.g. append and then decrea
 0607                    Debug.Assert(vsb.RawChars.Overlaps(destination));
 0608                }
 609
 0610                charsWritten = newLength;
 0611                return true;
 612            }
 613
 0614            vsb.Dispose();
 0615            charsWritten = 0;
 0616            return false;
 0617        }
 618
 619        // Where stringToEscape is intended to be a completely unescaped URI string.
 620        // This method will escape any character that is not a reserved or unreserved character, including percent signs
 621        [Obsolete(Obsoletions.EscapeUriStringMessage, DiagnosticId = Obsoletions.EscapeUriStringDiagId, UrlFormat = Obso
 622        public static string EscapeUriString(string stringToEscape) =>
 0623            UriHelper.EscapeString(stringToEscape, checkExistingEscaped: false, UriHelper.UnreservedReserved);
 624
 625        // Where stringToEscape is intended to be URI data, but not an entire URI.
 626        // This method will escape any character that is not an unreserved character, including percent signs.
 627
 628        /// <summary>Converts a string to its escaped representation.</summary>
 629        /// <param name="stringToEscape">The string to escape.</param>
 630        /// <returns>The escaped representation of <paramref name="stringToEscape"/>.</returns>
 631        public static string EscapeDataString(string stringToEscape) =>
 0632            UriHelper.EscapeString(stringToEscape, checkExistingEscaped: false, UriHelper.Unreserved);
 633
 634        /// <summary>Converts a span to its escaped representation.</summary>
 635        /// <param name="charsToEscape">The span to escape.</param>
 636        /// <returns>The escaped representation of <paramref name="charsToEscape"/>.</returns>
 637        public static string EscapeDataString(ReadOnlySpan<char> charsToEscape) =>
 0638            UriHelper.EscapeString(charsToEscape, checkExistingEscaped: false, UriHelper.Unreserved, backingString: null
 639
 640        /// <summary>Attempts to convert a span to its escaped representation.</summary>
 641        /// <param name="charsToEscape">The span to escape.</param>
 642        /// <param name="destination">The output span that contains the escaped result of the operation.</param>
 643        /// <param name="charsWritten">When this method returns, contains the number of chars that were written into <pa
 644        /// <returns><see langword="true"/> if the <paramref name="destination"/> was large enough to hold the entire re
 645        public static bool TryEscapeDataString(ReadOnlySpan<char> charsToEscape, Span<char> destination, out int charsWr
 0646            UriHelper.TryEscapeDataString(charsToEscape, destination, out charsWritten);
 647
 648#pragma warning disable CS8618 // _string will be initialized by CreateThis or TryCreateThis later.
 649        /// <summary>Creates an uninitialized instance for the parsing helpers.</summary>
 1983650        private Uri() { }
 651#pragma warning restore CS8618
 652
 653        /// <summary>Called by TryCreate.</summary>
 654        internal static Uri? CreateHelper(string? uriString, Flags flags, UriKind uriKind)
 661655        {
 661656            if (uriString is null)
 0657            {
 0658                return null;
 659            }
 660
 661661            Uri result = new();
 662            try
 661663            {
 661664                UriFormatException? e = result.TryCreateThis(uriString, flags, uriKind);
 661665                return e is null ? result : null;
 666            }
 0667            catch (UriFormatException)
 0668            {
 0669                Debug.Assert(result.Syntax is { IsSimple: false }, "A custom UriParser threw on InitializeAndValidate.")
 0670                return null;
 671            }
 661672        }
 673
 674        //
 675        // Resolves into either baseUri or relativeUri according to conditions OR if not possible it uses newUriString
 676        // to  return combined URI strings from both Uris
 677        // otherwise if e != null on output the operation has failed
 678        //
 679        internal static Uri? ResolveHelper(Uri baseUri, Uri? relativeUri, ref string? newUriString, ref bool userEscaped
 0680        {
 0681            Debug.Assert(!baseUri.IsNotAbsoluteUri && !baseUri.UserDrivenParsing, "Uri::ResolveHelper()|baseUri is not A
 682
 683            string relativeStr;
 684
 0685            if (relativeUri is not null)
 0686            {
 0687                if (relativeUri.IsAbsoluteUri)
 0688                    return relativeUri;
 689
 0690                relativeStr = relativeUri.OriginalString;
 0691                userEscaped = relativeUri.UserEscaped;
 0692            }
 693            else
 0694            {
 0695                relativeStr = string.Empty;
 0696            }
 697
 698            // Here we can assert that passed "relativeUri" is indeed a relative one
 699
 0700            if (relativeStr.Length > 0 && (UriHelper.IsLWS(relativeStr[0]) || UriHelper.IsLWS(relativeStr[relativeStr.Le
 0701                relativeStr = relativeStr.Trim(UriHelper.s_WSchars);
 702
 0703            if (relativeStr.Length == 0)
 0704            {
 0705                newUriString = baseUri.GetParts(UriComponents.AbsoluteUri,
 0706                    baseUri.UserEscaped ? UriFormat.UriEscaped : UriFormat.SafeUnescaped);
 0707                return null;
 708            }
 709
 710            // Check for a simple fragment in relative part
 0711            if (relativeStr[0] == '#' && !baseUri.IsImplicitFile && baseUri.Syntax!.InFact(UriSyntaxFlags.MayHaveFragmen
 0712            {
 0713                newUriString = baseUri.GetParts(UriComponents.AbsoluteUri & ~UriComponents.Fragment,
 0714                    UriFormat.UriEscaped) + relativeStr;
 0715                return null;
 716            }
 717
 718            // Check for a simple query in relative part
 0719            if (relativeStr[0] == '?' && !baseUri.IsImplicitFile && baseUri.Syntax!.InFact(UriSyntaxFlags.MayHaveQuery))
 0720            {
 0721                newUriString = baseUri.GetParts(UriComponents.AbsoluteUri & ~UriComponents.Query & ~UriComponents.Fragme
 0722                    UriFormat.UriEscaped) + relativeStr;
 0723                return null;
 724            }
 725
 726            // Check on the DOS path in the relative Uri (a special case)
 0727            if (relativeStr.Length >= 3
 0728                && (relativeStr[1] == ':' || relativeStr[1] == '|')
 0729                && char.IsAsciiLetter(relativeStr[0])
 0730                && (relativeStr[2] == '\\' || relativeStr[2] == '/'))
 0731            {
 0732                if (baseUri.IsImplicitFile)
 0733                {
 734                    // It could have file:/// prepended to the result but we want to keep it as *Implicit* File Uri
 0735                    newUriString = relativeStr;
 0736                    return null;
 737                }
 0738                else if (baseUri.Syntax!.InFact(UriSyntaxFlags.AllowDOSPath))
 0739                {
 740                    // The scheme is not changed just the path gets replaced
 741                    string prefix;
 0742                    if (baseUri.InFact(Flags.AuthorityFound))
 0743                        prefix = baseUri.Syntax.InFact(UriSyntaxFlags.PathIsRooted) ? ":///" : "://";
 744                    else
 0745                        prefix = baseUri.Syntax.InFact(UriSyntaxFlags.PathIsRooted) ? ":/" : ":";
 746
 0747                    newUriString = baseUri.Scheme + prefix + relativeStr;
 0748                    return null;
 749                }
 750                // If we are here then input like "http://host/path/" + "C:\x" will produce the result  http://host/path
 0751            }
 752
 0753            GetCombinedString(baseUri, relativeStr, userEscaped, ref newUriString);
 754
 0755            if (ReferenceEquals(newUriString, baseUri._string))
 0756                return baseUri;
 757
 0758            return null;
 0759        }
 760
 761        private string GetRelativeSerializationString(UriFormat format)
 0762        {
 0763            if (format == UriFormat.UriEscaped)
 0764            {
 0765                return UriHelper.EscapeString(_string, checkExistingEscaped: true, UriHelper.UnreservedReserved);
 766            }
 0767            else if (format == UriFormat.Unescaped)
 0768            {
 0769                return UnescapeDataString(_string);
 770            }
 0771            else if (format == UriFormat.SafeUnescaped)
 0772            {
 0773                if (_string.Length == 0)
 0774                    return string.Empty;
 775
 0776                var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]);
 0777                UriHelper.UnescapeString(_string, ref vsb, c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.EscapeUne
 0778                return vsb.ToString();
 779            }
 780            else
 0781            {
 0782                throw new ArgumentOutOfRangeException(nameof(format));
 783            }
 0784        }
 785
 786        //
 787        // UriParser helpers methods
 788        //
 789        internal string GetComponentsHelper(UriComponents uriComponents, UriFormat uriFormat)
 0790        {
 0791            if (uriComponents == UriComponents.Scheme)
 0792                return _syntax.SchemeName;
 793
 794            // A serialization info is "almost" the same as AbsoluteUri except for IPv6 + ScopeID hostname case
 0795            if ((uriComponents & UriComponents.SerializationInfoString) != 0)
 0796                uriComponents |= UriComponents.AbsoluteUri;
 797
 798            //This will get all the offsets, HostString will be created below if needed
 0799            EnsureParseRemaining();
 800
 0801            if ((uriComponents & UriComponents.NormalizedHost) != 0)
 0802            {
 803                // Down the path we rely on Host to be ON for NormalizedHost
 0804                uriComponents |= UriComponents.Host;
 0805            }
 806
 807            //Check to see if we need the host/authority string
 0808            if ((uriComponents & UriComponents.Host) != 0)
 0809                EnsureHostString(true);
 810
 811            //This, single Port request is always processed here
 0812            if (uriComponents == UriComponents.Port || uriComponents == UriComponents.StrongPort)
 0813            {
 0814                if (((_flags & Flags.NotDefaultPort) != 0) || (uriComponents == UriComponents.StrongPort
 0815                    && _syntax.DefaultPort != UriParser.NoDefaultPort))
 0816                {
 817                    // recreate string from the port value
 0818                    return _info.Offset.PortValue.ToString(CultureInfo.InvariantCulture);
 819                }
 0820                return string.Empty;
 821            }
 822
 0823            if ((uriComponents & UriComponents.StrongPort) != 0)
 0824            {
 825                // Down the path we rely on Port to be ON for StrongPort
 0826                uriComponents |= UriComponents.Port;
 0827            }
 828
 829            //This request sometime is faster to process here
 0830            if (uriComponents == UriComponents.Host && (uriFormat == UriFormat.UriEscaped
 0831                || ((_flags & (Flags.HostNotCanonical | Flags.E_HostNotCanonical)) == 0)))
 0832            {
 0833                EnsureHostString(false);
 0834                return _info.Host!;
 835            }
 836
 0837            switch (uriFormat)
 838            {
 839                case UriFormat.UriEscaped:
 0840                    return GetEscapedParts(uriComponents);
 841
 842                case V1ToStringUnescape:
 843                case UriFormat.SafeUnescaped:
 844                case UriFormat.Unescaped:
 0845                    return GetUnescapedParts(uriComponents, uriFormat);
 846
 847                default:
 0848                    throw new ArgumentOutOfRangeException(nameof(uriFormat));
 849            }
 0850        }
 851
 852        public bool IsBaseOf(Uri uri)
 0853        {
 0854            ArgumentNullException.ThrowIfNull(uri);
 855
 0856            if (!IsAbsoluteUri)
 0857                return false;
 858
 0859            if (Syntax.IsSimple)
 0860                return IsBaseOfHelper(uri);
 861
 0862            return Syntax.InternalIsBaseOf(this, uri);
 0863        }
 864
 865
 866        internal bool IsBaseOfHelper(Uri uriLink)
 0867        {
 868            const UriComponents ComponentsToCompare =
 869                UriComponents.AbsoluteUri
 870                & ~UriComponents.Fragment
 871                & ~UriComponents.UserInfo;
 872
 0873            if (!IsAbsoluteUri || UserDrivenParsing)
 0874                return false;
 875
 0876            if (!uriLink.IsAbsoluteUri)
 0877            {
 878                //a relative uri could have quite tricky form, it's better to fix it now.
 0879                string? newUriString = null;
 0880                bool dontEscape = false;
 881
 0882                uriLink = ResolveHelper(this, uriLink, ref newUriString, ref dontEscape)!;
 883
 0884                if (uriLink is null)
 0885                {
 0886                    uriLink = CreateHelper(newUriString!, dontEscape ? Flags.UserEscaped : Flags.Zero, UriKind.Absolute)
 887
 0888                    if (uriLink is null)
 0889                    {
 0890                        return false;
 891                    }
 0892                }
 0893            }
 894
 0895            if (Syntax.SchemeName != uriLink.Syntax.SchemeName)
 0896                return false;
 897
 898            // Canonicalize and test for substring match up to the last path slash
 0899            string self = GetParts(ComponentsToCompare, UriFormat.SafeUnescaped);
 0900            string other = uriLink.GetParts(ComponentsToCompare, UriFormat.SafeUnescaped);
 901
 0902            return UriHelper.TestForSubPath(self, other, IsUncOrDosPath || uriLink.IsUncOrDosPath);
 0903        }
 904
 905        //
 906        // Only a ctor time call
 907        //
 908        [MemberNotNull(nameof(_string))]
 909        private void CreateThisFromUri(Uri otherUri)
 0910        {
 0911            DebugAssertInCtor();
 0912            Debug.Assert((otherUri._flags & Flags.Debug_LeftConstructor) != 0);
 913
 0914            _flags = otherUri._flags & ~Flags.Debug_LeftConstructor;
 915
 0916            if (InFact(Flags.AllUriInfoSet))
 0917            {
 918                // We can share it now without mutation concern, for since AllUriInfoSet it is immutable.
 0919                _info = otherUri._info;
 0920            }
 921            else
 0922            {
 0923                Debug.Assert(!InFact(Flags.HasUnicode) || otherUri.IsNotAbsoluteUri);
 924                // Clone the other URI but develop own UriInfo member
 925                // We cannot just reference otherUri._info as this UriInfo will be mutated later
 926                // which could be happening concurrently and in a not thread safe manner.
 0927                _info = null!;
 928
 0929                if (InFact(Flags.MinimalUriInfoSet))
 0930                {
 0931                    _flags &= ~(Flags.MinimalUriInfoSet | Flags.AllUriInfoSet | Flags.IndexMask);
 932                    // Port / Path offset
 0933                    int portIndex = otherUri._info.Offset.Path;
 0934                    if (InFact(Flags.NotDefaultPort))
 0935                    {
 936                        // Find the start of the port.  Account for non-canonical ports like :00123
 0937                        while (otherUri._string[portIndex] != ':' && portIndex > otherUri._info.Offset.Host)
 0938                        {
 0939                            portIndex--;
 0940                        }
 0941                        if (otherUri._string[portIndex] != ':')
 0942                        {
 943                            // Something wrong with the NotDefaultPort flag.  Reset to path index
 0944                            Debug.Fail("Uri failed to locate custom port at index: " + portIndex);
 945                            portIndex = otherUri._info.Offset.Path;
 946                        }
 0947                    }
 0948                    _flags |= (Flags)portIndex; // Port or path
 0949                }
 0950            }
 951
 0952            _syntax = otherUri._syntax;
 0953            _string = otherUri._string;
 0954            _originalUnicodeString = otherUri._originalUnicodeString;
 0955        }
 956    }
 957}
 958

Methods/Properties

.cctor()
.ctor(System.String)
DebugSetLeftCtor()
AssertInvariants()
DebugAssertInCtor()
MoreInfo()
InterlockedSetFlags(System.Uri/Flags)
IsImplicitFile()
IsUncOrDosPath()
IsDosPath()
IsUncPath()
IsUnixPath()
HostType()
Syntax()
IsNotAbsoluteUri()
IriParsing()
IriParsingStatic(System.UriParser)
DisablePathAndQueryCanonicalization()
UserDrivenParsing()
SecuredPathIndex()
NotAny(System.Uri/Flags)
InFact(System.Uri/Flags)
StaticNotAny(System.Uri/Flags,System.Uri/Flags)
StaticInFact(System.Uri/Flags,System.Uri/Flags)
EnsureUriInfo()
EnsureParseRemaining()
EnsureHostString(System.Boolean)
.ctor(System.String,System.Boolean)
.ctor(System.Uri,System.String,System.Boolean)
.ctor(System.String,System.UriKind)
.ctor(System.String,System.UriCreationOptions&)
.ctor(System.Uri,System.String)
.ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)
System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)
GetObjectData(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)
CreateUri(System.Uri,System.String,System.Boolean)
.ctor(System.Uri,System.Uri)
GetCombinedString(System.Uri,System.String,System.Boolean,System.String&)
GetException(System.ParsingError)
AbsolutePath()
PrivateAbsolutePath()
AbsoluteUri()
LocalPath()
Authority()
HostNameType()
IsDefaultPort()
IsFile()
IsLoopback()
PathAndQuery()
Segments()
IsUnc()
Host()
StaticIsFile(System.UriParser)
GetLocalPath()
Port()
Query()
Fragment()
Scheme()
OriginalString()
DnsSafeHost()
IdnHost()
IsAbsoluteUri()
UserEscaped()
UserInfo()
CheckHostName(System.String)
GetLeftPart(System.UriPartial)
HexEscape(System.Char)
HexUnescape(System.String,System.Int32&)
IsHexEncoding(System.String,System.Int32)
CheckSchemeName(System.String)
IsHexDigit(System.Char)
FromHex(System.Char)
GetHashCode()
ToString()
TryFormat(System.Span`1<System.Char>,System.Int32&)
System.ISpanFormattable.TryFormat(System.Span`1<System.Char>,System.Int32&,System.ReadOnlySpan`1<System.Char>,System.IFormatProvider)
System.IFormattable.ToString(System.String,System.IFormatProvider)
op_Equality(System.Uri,System.Uri)
op_Inequality(System.Uri,System.Uri)
Equals(System.Object)
Equals(System.Uri)
MakeRelativeUri(System.Uri)
CheckForColonInFirstPathSegment(System.String)
InternalEscapeString(System.String)
ParseScheme(System.String,System.Uri/Flags&,System.UriParser&)
ParseMinimal()
PrivateParseMinimal()
CreateUriInfo(System.Uri/Flags)
CreateHostString()
CreateHostStringHelper(System.String,System.Int32,System.Int32,System.Uri/Flags&,System.Uri/UriInfo)
GetHostViaCustomSyntax()
GetParts(System.UriComponents,System.UriFormat)
GetEscapedParts(System.UriComponents)
GetUnescapedParts(System.UriComponents,System.UriFormat)
RecreateParts(System.UriComponents,System.UInt16,System.UriFormat)
TryRecreateParts(System.Span`1<System.Char>,System.Int32&,System.UriComponents,System.UInt16,System.UriFormat)
RecreateParts(System.Text.ValueStringBuilder&,System.String,System.UriComponents,System.UInt16,System.UriFormat)
FormatQueryOrFragment(System.ReadOnlySpan`1<System.Char>,System.Text.ValueStringBuilder&,System.UInt16,System.UriFormat,System.Boolean)
GetUriPartsFromUserString(System.UriComponents)
ParseRemaining()
ParseRemaining_RecreateNormalizedString()
EscapeUnescapeIri(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Boolean)
ParseSchemeCheckImplicitFile(System.String,System.ParsingError&,System.Uri/Flags&,System.UriParser&)
CheckSchemeSyntax(System.ReadOnlySpan`1<System.Char>,System.ParsingError&)
CheckAuthorityHelper(System.ReadOnlySpan`1<System.Char>,System.Int32,System.ParsingError&,System.Uri/Flags&,System.UriSyntaxFlags,System.String&)
CheckCanonical(System.ReadOnlySpan`1<System.Char>,System.Char,System.Int32&)
GetCanonicalPath(System.Text.ValueStringBuilder&,System.UriFormat)
UnescapePathSlashesAndDots(System.Text.ValueStringBuilder&,System.Int32,System.Boolean)
Compress(System.Text.ValueStringBuilder&,System.Int32,System.UriParser)
Compress(System.Char[],System.Int32,System.Int32&,System.UriParser)
CombineUri(System.Uri,System.String,System.UriFormat)
PathDifference(System.String,System.String,System.Boolean)
MakeRelative(System.Uri)
Canonicalize()
Parse()
Escape()
Unescape(System.String)
EscapeString(System.String)
CheckSecurity()
IsReservedCharacter(System.Char)
IsExcludedCharacter(System.Char)
IsBadFileSystemCharacter(System.Char)
CreateThis(System.String,System.Uri/Flags,System.UriKind)
TryCreateThis(System.String,System.Uri/Flags,System.UriKind)
.cctor()
CheckForUnicodeOrEscapedUnreserved(System.String)
System.IParsable<System.Uri>.Parse(System.String,System.IFormatProvider)
System.IParsable<System.Uri>.TryParse(System.String,System.IFormatProvider,System.Uri&)
TryCreate(System.String,System.UriKind,System.Uri&)
TryCreate(System.String,System.UriCreationOptions&,System.Uri&)
TryCreate(System.Uri,System.String,System.Uri&)
TryCreate(System.Uri,System.Uri,System.Uri&)
GetComponents(System.UriComponents,System.UriFormat)
InternalGetComponents(System.UriComponents,System.UriFormat)
Compare(System.Uri,System.Uri,System.UriComponents,System.UriFormat,System.StringComparison)
IsWellFormedOriginalString()
IsWellFormedUriString(System.String,System.UriKind)
InternalIsWellFormedOriginalString()
UnescapeDataString(System.String)
UnescapeDataString(System.ReadOnlySpan`1<System.Char>)
UnescapeDataString(System.ReadOnlySpan`1<System.Char>,System.String)
TryUnescapeDataString(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Char>,System.Int32&)
EscapeUriString(System.String)
EscapeDataString(System.String)
EscapeDataString(System.ReadOnlySpan`1<System.Char>)
TryEscapeDataString(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Char>,System.Int32&)
.ctor()
CreateHelper(System.String,System.Uri/Flags,System.UriKind)
ResolveHelper(System.Uri,System.Uri,System.String&,System.Boolean&)
GetRelativeSerializationString(System.UriFormat)
GetComponentsHelper(System.UriComponents,System.UriFormat)
IsBaseOf(System.Uri)
IsBaseOfHelper(System.Uri)
CreateThisFromUri(System.Uri)