File: net\System\UriExt.cs
Project: ndp\fx\src\System.csproj (System)
/*++
Copyright (c) 2003 Microsoft Corporation
 
Module Name:
 
    UriExt.cs
 
Abstract:
 
    Uri extensibility model Implementation.
    This file utilizes partial class feature.
    Uri.cs file contains core System.Uri functionality.
 
Author:
    Alexei Vopilov    Nov 21 2003
 
Revision History:
 
--*/
 
namespace System {
    using System.Globalization;
    using System.Net;
    using System.Text;
    using System.Runtime.InteropServices;
    using System.Diagnostics;
 
    public partial class Uri {
        //
        // All public ctors go through here
        //
        private void CreateThis(string uri, bool dontEscape, UriKind uriKind)
        {
            // if (!Enum.IsDefined(typeof(UriKind), uriKind)) -- We currently believe that Enum.IsDefined() is too slow 
            // to be used here.
            if ((int)uriKind < (int)UriKind.RelativeOrAbsolute || (int)uriKind > (int)UriKind.Relative) {
                throw new ArgumentException(SR.GetString(SR.net_uri_InvalidUriKind, uriKind));
            }
 
            m_String = uri == null? string.Empty: uri;
 
            if (dontEscape)
                m_Flags |= Flags.UserEscaped;
 
            ParsingError err = ParseScheme(m_String, ref m_Flags, ref m_Syntax);
            UriFormatException e;
 
            InitializeUri(err, uriKind, out e);
            if (e != null)
                throw e;
        }
        //
        private void InitializeUri(ParsingError err, UriKind uriKind, out UriFormatException e)
        {
            if (err == ParsingError.None)
            {
                if (IsImplicitFile)
                {
                    // V1 compat VsWhidbey#252282
                    // A relative Uri wins over implicit UNC path unless the UNC path is of the form "\\something" and 
                    // uriKind != Absolute
                    if (
#if !PLATFORM_UNIX
                        NotAny(Flags.DosPath) &&
#endif // !PLATFORM_UNIX
                        uriKind != UriKind.Absolute &&
                       (uriKind == UriKind.Relative || (m_String.Length >= 2 && (m_String[0] != '\\' || m_String[1] != '\\'))))
 
                    {
                        m_Syntax = null; //make it be relative Uri
                        m_Flags &= Flags.UserEscaped; // the only flag that makes sense for a relative uri
                        e = null;
                        return;
                        // Otheriwse an absolute file Uri wins when it's of the form "\\something"
                    }
                    //
                    // VsWhidbey#423805 and V1 compat issue
                    // We should support relative Uris of the form c:\bla or c:/bla
                    //
#if !PLATFORM_UNIX
                    else if (uriKind == UriKind.Relative && InFact(Flags.DosPath))
                    {
                        m_Syntax = null; //make it be relative Uri
                        m_Flags &= Flags.UserEscaped; // the only flag that makes sense for a relative uri
                        e = null;
                        return;
                        // Otheriwse an absolute file Uri wins when it's of the form "c:\something"
                    }
#endif // !PLATFORM_UNIX
                }
            }
            else if (err > ParsingError.LastRelativeUriOkErrIndex)
            {
                //This is a fatal error based solely on scheme name parsing
                m_String = null; // make it be invalid Uri
                e = GetException(err);
                return;
            }
 
            //
            //
            //
            bool hasUnicode = false;
 
            // Is there unicode ..
            if ((!s_ConfigInitialized) && CheckForConfigLoad(m_String)){
                InitializeUriConfig();
            }
 
            m_iriParsing = (s_IriParsing && ((m_Syntax == null) || m_Syntax.InFact(UriSyntaxFlags.AllowIriParsing)));
            
            if (m_iriParsing && 
                (CheckForUnicode(m_String) || CheckForEscapedUnreserved(m_String))) {
                m_Flags |= Flags.HasUnicode;
                hasUnicode = true;
                // switch internal strings
                m_originalUnicodeString = m_String; // original string location changed
            }
 
            if (m_Syntax != null)
            {
                if (m_Syntax.IsSimple)
                {
                    if ((err = PrivateParseMinimal()) != ParsingError.None)
                    {
                        if (uriKind != UriKind.Absolute && err <= ParsingError.LastRelativeUriOkErrIndex)
                        {
                            // RFC 3986 Section 5.4.2 - http:(relativeUri) may be considered a valid relative Uri.
                            m_Syntax = null; // convert to relative uri
                            e = null;
                            m_Flags &= Flags.UserEscaped; // the only flag that makes sense for a relative uri
                        }
                        else
                            e = GetException(err);
                    }
                    else if (uriKind == UriKind.Relative)
                    {
                        // Here we know that we can create an absolute Uri, but the user has requested only a relative one
                        e = GetException(ParsingError.CannotCreateRelative);
                    }
                    else
                        e = null;
                    // will return from here
 
                    // Parse unicode and reserved characters only if we're sure that we have an absolute Uri, either because it was
                    // specified via uriKind, or because the parsing error does not indicate that we have a relative URI.
                    if (m_iriParsing && hasUnicode && (uriKind == UriKind.Absolute || err == ParsingError.None))
                    {
                        // In this scenario we need to parse the whole string 
                        try
                        {
                            EnsureParseRemaining();
                        }
                        catch (UriFormatException ex)
                        {
                            if (ServicePointManager.AllowAllUriEncodingExpansion)
                            {
                                throw;
                            }
                            else
                            {
                                e = ex;
                                return;
                            }
                        }
                    }
                }
                else
                {
                    // offer custom parser to create a parsing context
                    m_Syntax = m_Syntax.InternalOnNewUri();
 
                    // incase they won't call us
                    m_Flags |= Flags.UserDrivenParsing;
 
                    // Ask a registered type to validate this uri
                    m_Syntax.InternalValidate(this, out e);
 
                    if (e != null)
                    {
                        // Can we still take it as a relative Uri?
                        if (uriKind != UriKind.Absolute && err != ParsingError.None 
                            && err <= ParsingError.LastRelativeUriOkErrIndex)
                        {
                            m_Syntax = null; // convert it to relative
                            e = null;
                            m_Flags &= Flags.UserEscaped; // the only flag that makes sense for a relative uri
                        }
                    }
                    else // e == null
                    {
                        if (err != ParsingError.None || InFact(Flags.ErrorOrParsingRecursion))
                        {
                            // User parser took over on an invalid Uri
                            SetUserDrivenParsing();
                        }
                        else if (uriKind == UriKind.Relative)
                        {
                            // Here we know that custom parser can create an absolute Uri, but the user has requested only a 
                            // relative one
                            e = GetException(ParsingError.CannotCreateRelative);
                        }
 
                        if (m_iriParsing && hasUnicode){
                            // In this scenario we need to parse the whole string 
                            try
                            {
                                EnsureParseRemaining();
                            }
                            catch (UriFormatException ex)
                            {
                                if (ServicePointManager.AllowAllUriEncodingExpansion)
                                {
                                    throw;
                                }
                                else
                                {
                                    e = ex;
                                    return;
                                }
                            }
                        }
                        
                    }
                    // will return from here
                }
            }
            // If we encountered any parsing errors that indicate this may be a relative Uri, 
            // and we'll allow relative Uri's, then create one.
            else if (err != ParsingError.None && uriKind != UriKind.Absolute 
                && err <= ParsingError.LastRelativeUriOkErrIndex)
            {
                e = null;
                m_Flags &= (Flags.UserEscaped | Flags.HasUnicode); // the only flags that makes sense for a relative uri
                if (m_iriParsing && hasUnicode)
                {
                    // Iri'ze and then normalize relative uris
                    m_String = EscapeUnescapeIri(m_originalUnicodeString, 0, m_originalUnicodeString.Length,
                                                (UriComponents)0);
                    try
                    {
                        if (UriParser.ShouldUseLegacyV2Quirks)
                            m_String = m_String.Normalize(NormalizationForm.FormC);
                    }
                    catch (ArgumentException)
                    {
                        e = GetException(ParsingError.BadFormat);
                    }
                }
            }
            else
            {
               m_String = null; // make it be invalid Uri
               e = GetException(err);
            }
        }
        
        //
        // Checks if there are any unicode or escaped chars or ace to determine whether to load
        // config
        //
        private unsafe bool CheckForConfigLoad(String data)
        {
            bool initConfig = false;
            int length = data.Length;
 
            fixed (char* temp = data){
                for (int i = 0; i < length; ++i){
 
                    if ((temp[i] > '\x7f') || (temp[i] == '%') ||
                        ((temp[i] == 'x') && ((i + 3) < length) && (temp[i + 1] == 'n') && (temp[i + 2] == '-') && (temp[i + 3] == '-')))
                    {
 
                        // Unicode or maybe ace 
                        initConfig = true;
                        break;
                    }
                }
 
 
            }
 
            return initConfig;
        }
 
        //
        // Unescapes entire string and checks if it has unicode chars
        //
        private bool CheckForUnicode(String data)
        {
            bool hasUnicode = false;
            char[] chars = new char[data.Length];
            int count = 0;
 
            chars = UriHelper.UnescapeString(data, 0, data.Length, chars, ref count, c_DummyChar, c_DummyChar, 
                c_DummyChar, UnescapeMode.Unescape | UnescapeMode.UnescapeAll, null, false);
 
            for (int i = 0; i < count; ++i){
                if (chars[i] > '\x7f'){
                    // Unicode 
                    hasUnicode = true;
                    break;
                }
            }
            return hasUnicode;
        }
 
        // Does this string have any %6A sequences that are 3986 Unreserved characters?  These should be un-escaped.
        private unsafe bool CheckForEscapedUnreserved(String data)
        {
            fixed (char* tempPtr = data)
            {
                for (int i = 0; i < data.Length - 2; ++i)
                {
                    if (tempPtr[i] == '%' && IsHexDigit(tempPtr[i + 1]) && IsHexDigit(tempPtr[i + 2])
                        && tempPtr[i + 1] >= '0' && tempPtr[i + 1] <= '7') // max 0x7F
                    {
                        char ch = UriHelper.EscapedAscii(tempPtr[i + 1], tempPtr[i + 2]);
                        if (ch != c_DummyChar && UriHelper.Is3986Unreserved(ch))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
        
        //
        //
        //  Returns true if the string represents a valid argument to the Uri ctor
        //  If uriKind != AbsoluteUri then certain parsing erros are ignored but Uri usage is limited
        //
        public static bool TryCreate(string uriString, UriKind uriKind, out Uri result)
        {
            if ((object)uriString == null)
            {
                result = null;
                return false;
            }
            UriFormatException e = null;
            result = CreateHelper(uriString, false, uriKind, ref e);
            return (object) e == null && result != null;
        }
        //
        public static bool TryCreate(Uri baseUri, string relativeUri, out Uri result)
        {
            Uri relativeLink;
            if (TryCreate(relativeUri, UriKind.RelativeOrAbsolute, out relativeLink))
            {
                if (!relativeLink.IsAbsoluteUri)
                    return TryCreate(baseUri, relativeLink, out result);
 
                result = relativeLink;
                return true;
            }
            result = null;
            return false;
        }
        //
        public static bool TryCreate(Uri baseUri, Uri relativeUri, out Uri result)
        {
            result = null;
 
            //Consider: Work out the baseUri==null case
            if ((object)baseUri == null || (object)relativeUri == null)
                return false;
 
            if (baseUri.IsNotAbsoluteUri)
                return false;
 
            UriFormatException e;
            string newUriString = null;
 
            bool dontEscape;
            if (baseUri.Syntax.IsSimple)
            {
                dontEscape = relativeUri.UserEscaped;
                result = ResolveHelper(baseUri, relativeUri, ref newUriString, ref dontEscape, out e);
            }
            else
            {
                dontEscape = false;
                newUriString = baseUri.Syntax.InternalResolve(baseUri, relativeUri, out e);
            }
 
            if (e != null)
                return false;
 
            if ((object) result == null)
                result = CreateHelper(newUriString, dontEscape, UriKind.Absolute, ref e);
 
            return (object) e == null && result != null && result.IsAbsoluteUri;
        }
        //
        //
        public string GetComponents(UriComponents components, UriFormat format)
        {
            if (((components & UriComponents.SerializationInfoString) != 0) && components != UriComponents.SerializationInfoString)
                throw new ArgumentOutOfRangeException("components", components, SR.GetString(SR.net_uri_NotJustSerialization));
 
            if ((format & ~UriFormat.SafeUnescaped) != 0)
                throw new ArgumentOutOfRangeException("format");
 
            if (IsNotAbsoluteUri)
            {
                if (components == UriComponents.SerializationInfoString)
                    return GetRelativeSerializationString(format);
                else
                    throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
            }
 
            if (Syntax.IsSimple)
                return GetComponentsHelper(components, format);
 
            return Syntax.InternalGetComponents(this, components, format);
        }
        //
        //
        // This is for languages that do not support == != operators overloading
        //
        // Note that Uri.Equals will get an optimized path but is limited to true/fasle result only
        //
        public static int Compare(Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat, 
            StringComparison comparisonType)
        {
 
            if ((object) uri1 == null)
            {
                if (uri2 == null)
                    return 0; // Equal
                return -1;    // null < non-null
            }
            if ((object) uri2 == null)
                return 1;     // non-null > null
 
            // a relative uri is always less than an absolute one
            if (!uri1.IsAbsoluteUri || !uri2.IsAbsoluteUri)
                return uri1.IsAbsoluteUri? 1: uri2.IsAbsoluteUri? -1: string.Compare(uri1.OriginalString, 
                    uri2.OriginalString, comparisonType);
 
            return string.Compare(
                                    uri1.GetParts(partsToCompare, compareFormat),
                                    uri2.GetParts(partsToCompare, compareFormat),
                                    comparisonType
                                  );
        }
        
        public bool IsWellFormedOriginalString()
        {
            if (IsNotAbsoluteUri || Syntax.IsSimple)
                return InternalIsWellFormedOriginalString();
 
            return Syntax.InternalIsWellFormedOriginalString(this);
        }
 
        // Consider: (perf) Making it to not create a Uri internally
        public static bool IsWellFormedUriString(string uriString, UriKind uriKind)
        {
            Uri result;
 
            if (!Uri.TryCreate(uriString, uriKind, out result))
                return false;
 
            return result.IsWellFormedOriginalString();
        }
 
        //
        // Internal stuff
        //
 
        // Returns false if OriginalString value
        // (1) is not correctly escaped as per URI spec excluding intl UNC name case
        // (2) or is an absolute Uri that represents implicit file Uri "c:\dir\file"
        // (3) or is an absolute Uri that misses a slash before path "file://c:/dir/file"
        // (4) or contains unescaped backslashes even if they will be treated
        //     as forward slashes like http:\\host/path\file or file:\\\c:\path
        //
        internal unsafe bool InternalIsWellFormedOriginalString()
        {
            if (UserDrivenParsing)
                throw new InvalidOperationException(SR.GetString(SR.net_uri_UserDrivenParsing, this.GetType().FullName));
 
            fixed (char* str = m_String)
            {
                ushort idx = 0;
                //
                // For a relative Uri we only care about escaping and backslashes
                //
                if (!IsAbsoluteUri)
                {
                    // my:scheme/path?query is not well formed because the colon is ambiguous
                    if (!UriParser.ShouldUseLegacyV2Quirks && CheckForColonInFirstPathSegment(m_String))
                    {
                        return false;
                    }
                    return (CheckCanonical(str, ref idx, (ushort)m_String.Length, c_EOL) 
                            & (Check.BackslashInPath | Check.EscapedCanonical)) == Check.EscapedCanonical;
                }
 
                //
                // (2) or is an absolute Uri that represents implicit file Uri "c:\dir\file"
                //
                if (IsImplicitFile)
                    return false;
 
                //This will get all the offsets, a Host name will be checked separatelly below
                EnsureParseRemaining();
 
                Flags nonCanonical = (m_Flags & (Flags.E_CannotDisplayCanonical | Flags.IriCanonical));
                // User, Path, Query or Fragment may have some non escaped characters
                if (((nonCanonical & Flags.E_CannotDisplayCanonical & (Flags.E_UserNotCanonical | Flags.E_PathNotCanonical |
                                        Flags.E_QueryNotCanonical | Flags.E_FragmentNotCanonical)) != Flags.Zero) &&
                    (!m_iriParsing || (m_iriParsing &&
                    (((nonCanonical & Flags.E_UserNotCanonical) == 0) || ((nonCanonical & Flags.UserIriCanonical) == 0)) &&
                    (((nonCanonical & Flags.E_PathNotCanonical) == 0) || ((nonCanonical & Flags.PathIriCanonical) == 0)) &&
                    (((nonCanonical & Flags.E_QueryNotCanonical) == 0) || ((nonCanonical & Flags.QueryIriCanonical) == 0)) &&
                    (((nonCanonical & Flags.E_FragmentNotCanonical) == 0) || ((nonCanonical & Flags.FragmentIriCanonical) == 0)))))
                {
                    return false;
                }
 
                // checking on scheme:\\ or file:////
                if (InFact(Flags.AuthorityFound))
                {
                    idx = (ushort)(m_Info.Offset.Scheme + m_Syntax.SchemeName.Length + 2);
                    if (idx >= m_Info.Offset.User || m_String[idx - 1] == '\\' || m_String[idx] == '\\')
                        return false;
 
#if !PLATFORM_UNIX
                    if (InFact(Flags.UncPath | Flags.DosPath))
                    {
                        while (++idx < m_Info.Offset.User && (m_String[idx] == '/' || m_String[idx] == '\\'))
                            return false;
                    }
#endif // !PLATFORM_UNIX
                }
 
 
                // (3) or is an absolute Uri that misses a slash before path "file://c:/dir/file"
                // Note that for this check to be more general we assert that if Path is non empty and if it requires a first slash
                // (which looks absent) then the method has to fail.
                // Today it's only possible for a Dos like path, i.e. file://c:/bla would fail below check.
                if (InFact(Flags.FirstSlashAbsent) && m_Info.Offset.Query > m_Info.Offset.Path)
                    return false;
 
                // (4) or contains unescaped backslashes even if they will be treated
                //     as forward slashes like http:\\host/path\file or file:\\\c:\path
                // Note we do not check for Flags.ShouldBeCompressed i.e. allow // /./ and alike as valid
                if (InFact(Flags.BackslashInPath))
                    return false;
 
                // Capturing a rare case like file:///c|/dir
                if (IsDosPath && m_String[m_Info.Offset.Path + SecuredPathIndex - 1] == '|')
                    return false;
 
                //
                // May need some real CPU processing to anwser the request
                //
                //
                // Check escaping for authority
                //
                // IPv6 hosts cannot be properly validated by CheckCannonical
                if ((m_Flags & Flags.CanonicalDnsHost) == 0 && HostType != Flags.IPv6HostType)
                {
                    idx = m_Info.Offset.User;
                    Check result = CheckCanonical(str, ref idx, (ushort)m_Info.Offset.Path, '/');
                    if (((result & (Check.ReservedFound | Check.BackslashInPath | Check.EscapedCanonical)) 
                        != Check.EscapedCanonical) 
                        && (!m_iriParsing || (m_iriParsing 
                            && ((result & (Check.DisplayCanonical | Check.FoundNonAscii | Check.NotIriCanonical))
                                != (Check.DisplayCanonical | Check.FoundNonAscii)))))
                    {
                        return false;
                    }
                }
 
                // Want to ensure there are slashes after the scheme
                if ((m_Flags & (Flags.SchemeNotCanonical | Flags.AuthorityFound)) 
                    == (Flags.SchemeNotCanonical | Flags.AuthorityFound))
                {
                    idx = (ushort)m_Syntax.SchemeName.Length;
                    while (str[idx++] != ':') ;
                    if (idx + 1 >= m_String.Length || str[idx] != '/' || str[idx + 1] != '/')
                        return false;
                }
            }
            //
            // May be scheme, host, port or path need some canonicalization but still the uri string is found to be a 
            // "well formed" one
            //
            return true;
        }
 
        //
        //
        //
        public static string UnescapeDataString(string stringToUnescape)
        {
            if ((object) stringToUnescape == null)
                throw new ArgumentNullException("stringToUnescape");
 
            if (stringToUnescape.Length == 0)
                return string.Empty;
 
            unsafe {
                fixed (char* pStr = stringToUnescape)
                {
                    int position;
                    for (position = 0; position < stringToUnescape.Length; ++position)
                        if (pStr[position] == '%')
                            break;
 
                    if (position == stringToUnescape.Length)
                        return stringToUnescape;
 
                    UnescapeMode unescapeMode = UnescapeMode.Unescape | UnescapeMode.UnescapeAll;                    
                    position = 0;
                    char[] dest = new char[stringToUnescape.Length];
                    dest = UriHelper.UnescapeString(stringToUnescape, 0, stringToUnescape.Length, dest, ref position, 
                        c_DummyChar, c_DummyChar, c_DummyChar, unescapeMode, null, false);
                    return new string(dest, 0, position);
                }
            }
        }
        //
        // Where stringToEscape is intented to be a completely unescaped URI string.
        // This method will escape any character that is not a reserved or unreserved character, including percent signs.
        // Note that EscapeUriString will also do not escape a '#' sign.
        //
        public static string EscapeUriString(string stringToEscape)
        {
            if ((object)stringToEscape == null)
                throw new ArgumentNullException("stringToEscape");
 
            if (stringToEscape.Length == 0)
                return string.Empty;
 
            int position = 0;
            char[] dest = UriHelper.EscapeString(stringToEscape, 0, stringToEscape.Length, null, ref position, true, 
                c_DummyChar, c_DummyChar, c_DummyChar);
            if ((object) dest == null)
                return stringToEscape;
            return new string(dest, 0, position);
        }
        //
        // Where stringToEscape is intended to be URI data, but not an entire URI.
        // This method will escape any character that is not an unreserved character, including percent signs.
        //
        public static string EscapeDataString(string stringToEscape)
        {
            if ((object) stringToEscape == null)
                throw new ArgumentNullException("stringToEscape");
 
            if (stringToEscape.Length == 0)
                return string.Empty;
 
            int position = 0;
            char[] dest = UriHelper.EscapeString(stringToEscape, 0, stringToEscape.Length, null, ref position, false, 
                c_DummyChar, c_DummyChar, c_DummyChar);
            if (dest == null)
                return stringToEscape;
            return new string(dest, 0, position);
        }
 
        //
        // Cleans up the specified component according to Iri rules
        // a) Chars allowed by iri in a component are unescaped if found escaped
        // b) Bidi chars are stripped
        //
        // should be called only if IRI parsing is switched on 
        internal unsafe string EscapeUnescapeIri(string input, int start, int end, UriComponents component)
        {
            fixed (char *pInput = input)
            {
                return IriHelper.EscapeUnescapeIri(pInput, start, end, component);
            }
        }
        
        // Should never be used except by the below method
        private Uri(Flags flags, UriParser uriParser, string uri)
        {
            m_Flags = flags;
            m_Syntax = uriParser;
            m_String = uri;
        }
        //
        // a Uri.TryCreate() method goes through here.
        //
        internal static Uri CreateHelper(string uriString, bool dontEscape, UriKind uriKind, ref UriFormatException e)
        {
            // if (!Enum.IsDefined(typeof(UriKind), uriKind)) -- We currently believe that Enum.IsDefined() is too slow 
            // to be used here.
            if ((int)uriKind < (int)UriKind.RelativeOrAbsolute || (int)uriKind > (int)UriKind.Relative){
                throw new ArgumentException(SR.GetString(SR.net_uri_InvalidUriKind, uriKind));
            }
 
            UriParser syntax = null;
            Flags flags = Flags.Zero;
            ParsingError err = ParseScheme(uriString, ref flags, ref syntax);
 
            if (dontEscape)
                flags |= Flags.UserEscaped;
 
            // We won't use User factory for these errors
            if (err != ParsingError.None)
            {
                // If it looks as a relative Uri, custom factory is ignored
                if (uriKind != UriKind.Absolute && err <= ParsingError.LastRelativeUriOkErrIndex)
                    return new Uri((flags & Flags.UserEscaped), null, uriString);
 
                return null;
            }
 
            // Cannot be relative Uri if came here
            Uri result = new Uri(flags, syntax, uriString);
 
            // Validate instance using ether built in or a user Parser
            try
            {
                result.InitializeUri(err, uriKind, out e);
 
                if (e == null)
                    return result;
 
                return null;
            }
            catch (UriFormatException ee)
            {
                Debug.Assert(!syntax.IsSimple, "A UriPraser threw on InitializeAndValidate.");
                e = ee;
                // A precaution since custom Parser should never throw in this case.
                return null;
            }
        }
        //
        // Resolves into either baseUri or relativeUri according to conditions OR if not possible it uses newUriString 
        // to  return combined URI strings from both Uris 
        // otherwise if e != null on output the operation has failed
        //
 
        internal static Uri ResolveHelper(Uri baseUri, Uri relativeUri, ref string newUriString, ref bool userEscaped, 
            out UriFormatException e)
        {
            Debug.Assert(!baseUri.IsNotAbsoluteUri && !baseUri.UserDrivenParsing, "Uri::ResolveHelper()|baseUri is not Absolute or is controlled by User Parser.");
 
            e = null;
            string relativeStr = string.Empty;
 
            if ((object)relativeUri != null)
            {
                if (relativeUri.IsAbsoluteUri)
                    return relativeUri;
 
                relativeStr = relativeUri.OriginalString;
                userEscaped = relativeUri.UserEscaped;
            }
            else
                relativeStr = string.Empty;
 
            // Here we can assert that passed "relativeUri" is indeed a relative one
 
            if (relativeStr.Length > 0 && (IsLWS(relativeStr[0]) || IsLWS(relativeStr[relativeStr.Length - 1])))
                relativeStr = relativeStr.Trim(_WSchars);
 
            if (relativeStr.Length == 0)
            {
                newUriString = baseUri.GetParts(UriComponents.AbsoluteUri, 
                    baseUri.UserEscaped ? UriFormat.UriEscaped : UriFormat.SafeUnescaped);
                return null;
            }
 
            // Check for a simple fragment in relative part
            if (relativeStr[0] == '#' && !baseUri.IsImplicitFile && baseUri.Syntax.InFact(UriSyntaxFlags.MayHaveFragment))
            {
                newUriString = baseUri.GetParts(UriComponents.AbsoluteUri & ~UriComponents.Fragment, 
                    UriFormat.UriEscaped) + relativeStr;
                return null;
            }
            
            // Check for a simple query in relative part
            if (relativeStr[0] == '?' && !baseUri.IsImplicitFile && baseUri.Syntax.InFact(UriSyntaxFlags.MayHaveQuery))
            {
                newUriString = baseUri.GetParts(UriComponents.AbsoluteUri & ~UriComponents.Query & ~UriComponents.Fragment, 
                    UriFormat.UriEscaped) + relativeStr;
                return null;
            }
            
            // Check on the DOS path in the relative Uri (a special case)
            if (relativeStr.Length >= 3
                && (relativeStr[1] == ':' || relativeStr[1] == '|')
                && IsAsciiLetter(relativeStr[0])
                && (relativeStr[2] == '\\' || relativeStr[2] == '/'))
            {
 
                if (baseUri.IsImplicitFile)
                {
                    // It could have file:/// prepended to the result but we want to keep it as *Implicit* File Uri
                    newUriString = relativeStr;
                    return null;
                }
                else if (baseUri.Syntax.InFact(UriSyntaxFlags.AllowDOSPath))
                {
                    // The scheme is not changed just the path gets replaced
                    string prefix;
                    if (baseUri.InFact(Flags.AuthorityFound))
                        prefix = baseUri.Syntax.InFact(UriSyntaxFlags.PathIsRooted) ? ":///" : "://";
                    else
                        prefix = baseUri.Syntax.InFact(UriSyntaxFlags.PathIsRooted) ? ":/" : ":";
 
                    newUriString = baseUri.Scheme + prefix + relativeStr;
                    return null;
                }
                // If we are here then input like "http://host/path/" + "C:\x" will produce the result  http://host/path/c:/x
            }
 
 
            ParsingError err = GetCombinedString(baseUri, relativeStr, userEscaped, ref newUriString);
 
            if (err != ParsingError.None)
            {
                e = GetException(err);
                return null;
            }
 
            if ((object)newUriString == (object)baseUri.m_String)
                return baseUri;
 
            return null;
        }
 
        private unsafe string GetRelativeSerializationString(UriFormat format)
        {
            if (format == UriFormat.UriEscaped)
            {
                if (m_String.Length == 0)
                    return string.Empty;
                int position = 0;
                char[] dest = UriHelper.EscapeString(m_String, 0, m_String.Length, null, ref position, true, 
                    c_DummyChar, c_DummyChar, '%');
                if ((object)dest == null)
                    return m_String;
                return new string(dest, 0, position);
            }
 
            else if (format == UriFormat.Unescaped)
                return UnescapeDataString(m_String);
 
            else if (format == UriFormat.SafeUnescaped)
            {
                if (m_String.Length == 0)
                    return string.Empty;
 
                char[] dest = new char[m_String.Length];
                int position = 0;
                dest = UriHelper.UnescapeString(m_String, 0, m_String.Length, dest, ref position, c_DummyChar, 
                    c_DummyChar, c_DummyChar, UnescapeMode.EscapeUnescape, null, false);
                return new string(dest, 0, position);
            }
            else
                throw new ArgumentOutOfRangeException("format");
 
        }
 
        //
        // UriParser helpers methods
        //
        internal string GetComponentsHelper(UriComponents uriComponents, UriFormat uriFormat)
        {
            if (uriComponents == UriComponents.Scheme)
                return m_Syntax.SchemeName;
 
            // A serialzation info is "almost" the same as AbsoluteUri except for IPv6 + ScopeID hostname case
            if ((uriComponents & UriComponents.SerializationInfoString) != 0)
                uriComponents |= UriComponents.AbsoluteUri;
 
            //This will get all the offsets, HostString will be created below if needed
            EnsureParseRemaining();
 
            if ((uriComponents & UriComponents.NormalizedHost) != 0)
            {
                // Down the path we rely on Host to be ON for NormalizedHost
                uriComponents |= UriComponents.Host;
            }
 
            //Check to see if we need the host/authotity string
            if ((uriComponents & UriComponents.Host) != 0)
                EnsureHostString(true);
 
            //This, single Port request is always processed here
            if (uriComponents == UriComponents.Port || uriComponents == UriComponents.StrongPort)
            {
                if (((m_Flags & Flags.NotDefaultPort) != 0) || (uriComponents == UriComponents.StrongPort 
                    && m_Syntax.DefaultPort != UriParser.NoDefaultPort))
                {
                    // recreate string from the port value
                    return m_Info.Offset.PortValue.ToString(CultureInfo.InvariantCulture);
                }
                return string.Empty;
            }
 
            if ((uriComponents & UriComponents.StrongPort) != 0)
            {
                // Down the path we rely on Port to be ON for StrongPort
                uriComponents |= UriComponents.Port;
            }
 
            //This request sometime is faster to process here
            if (uriComponents == UriComponents.Host && (uriFormat == UriFormat.UriEscaped 
                || (( m_Flags & (Flags.HostNotCanonical | Flags.E_HostNotCanonical)) == 0)))
            {
                EnsureHostString(false);
                return m_Info.Host;
            }
 
            switch (uriFormat)
            {
                case UriFormat.UriEscaped:
                    return GetEscapedParts(uriComponents);
 
                case V1ToStringUnescape:
                case UriFormat.SafeUnescaped:
                case UriFormat.Unescaped:
                    return GetUnescapedParts(uriComponents, uriFormat);
 
                default:
                    throw new ArgumentOutOfRangeException("uriFormat");
            }
        }
        //
        //
        public bool IsBaseOf(Uri uri)
        {
            if ((object)uri == null)
                throw new ArgumentNullException("uri");
 
            if (!IsAbsoluteUri)
                return false;
 
            if (Syntax.IsSimple)
                return IsBaseOfHelper(uri);
 
            return Syntax.InternalIsBaseOf(this, uri);
        }
        //
        //
        //
        internal bool IsBaseOfHelper(Uri uriLink)
        {
            //TO 
            if (!IsAbsoluteUri || UserDrivenParsing)
                return false;
 
            if (!uriLink.IsAbsoluteUri)
            {
                //a relative uri could have quite tricky form, it's better to fix it now.
                string newUriString = null;
                UriFormatException e;
                bool dontEscape = false;
 
                uriLink = ResolveHelper(this, uriLink, ref newUriString, ref dontEscape, out e);
                if (e != null)
                    return false;
 
                if ((object)uriLink == null)
                    uriLink = CreateHelper(newUriString, dontEscape, UriKind.Absolute, ref e);
 
                if (e != null)
                    return false;
            }
 
            if (Syntax.SchemeName != uriLink.Syntax.SchemeName)
                return false;
 
            // Canonicalize and test for substring match up to the last path slash
            string me = GetParts(UriComponents.AbsoluteUri & ~UriComponents.Fragment, UriFormat.SafeUnescaped);
            string she = uriLink.GetParts(UriComponents.AbsoluteUri & ~UriComponents.Fragment, UriFormat.SafeUnescaped);
 
            unsafe
            {
                fixed (char* pMe = me)
                {
                    fixed (char* pShe = she)
                    {
                        return UriHelper.TestForSubPath(pMe, (ushort)me.Length, pShe, (ushort)she.Length, 
                            IsUncOrDosPath || uriLink.IsUncOrDosPath);
                    }
                }
            }
        }
        //
        // Only a ctor time call
        //
        private void CreateThisFromUri(Uri otherUri)
        {
            // Clone the other guy but develop own UriInfo member
            m_Info = null;
 
            m_Flags = otherUri.m_Flags;
            if (InFact(Flags.MinimalUriInfoSet))
            {
                m_Flags &= ~(Flags.MinimalUriInfoSet | Flags.AllUriInfoSet | Flags.IndexMask);
                // Port / Path offset
                int portIndex = otherUri.m_Info.Offset.Path;
                if (InFact(Flags.NotDefaultPort))
                {
                    // Find the start of the port.  Account for non-canonical ports like :00123
                    while (otherUri.m_String[portIndex] != ':' && portIndex > otherUri.m_Info.Offset.Host)
                    {
                        portIndex--;
                    }
                    if (otherUri.m_String[portIndex] != ':')
                    {
                        // Something wrong with the NotDefaultPort flag.  Reset to path index
                        Debug.Assert(false, "Uri failed to locate custom port at index: " + portIndex);
                        portIndex = otherUri.m_Info.Offset.Path;
                    }
                }
                m_Flags |= (Flags)portIndex; // Port or path
            }
 
            m_Syntax = otherUri.m_Syntax;
            m_String = otherUri.m_String;
            m_iriParsing = otherUri.m_iriParsing;
            if (otherUri.OriginalStringSwitched){
                m_originalUnicodeString = otherUri.m_originalUnicodeString;
            }
            if (otherUri.AllowIdn && (otherUri.InFact(Flags.IdnHost) || otherUri.InFact(Flags.UnicodeHost))){
                m_DnsSafeHost = otherUri.m_DnsSafeHost;
            }
        }
    }
}