// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Globalization;
using System.Collections.Generic;
-using System.Runtime.Serialization;
using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Text;
namespace System
{
private string _originalUnicodeString = null!; // initialized in ctor via helper
private UriParser _syntax = null!; // Initialized in ctor via helper. This is a whole Uri syntax, not only the scheme name
- // temporarily stores dnssafe host when we have unicode/idn host and idn is on
- private string? _dnsSafeHost = null;
[Flags]
private enum Flags : ulong
RestUnicodeNormalized = 0x800000000,
UnicodeHost = 0x1000000000,
IntranetUri = 0x2000000000,
- UseOrigUncdStrOffset = 0x4000000000,
// Is this component Iri canonical
UserIriCanonical = 0x8000000000,
PathIriCanonical = 0x10000000000,
public Offset Offset;
public string? DnsSafeHost; // stores dns safe host when idn is on and we have unicode or idn host
public MoreInfo? MoreInfo; // Multi-threading: This field must be always accessed through a _local_
- // stack copy of m_Info.
+ // stack copy of _info.
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
//
// Checks if Iri parsing is allowed by the syntax & by config
//
- private bool _iriParsing;
+ private bool IriParsing => IriParsingStatic(_syntax);
- //
- // Statically checks if Iri parsing is allowed by the syntax & by config
- //
internal static bool IriParsingStatic(UriParser? syntax)
{
return syntax is null || syntax.InFact(UriSyntaxFlags.AllowIriParsing);
{
/* Optimization for a canonical DNS name
* ATTN: the host string won't be created,
- * Hence ALL m_Info.Host callers first call EnsureHostString(false)
+ * Hence ALL _info.Host callers first call EnsureHostString(false)
* For example IsLoopBack property is one of such callers.
*/
return;
Debug.Assert(_info.Host != null);
int start;
- // Do we have a valid local path right in m_string?
+ // Do we have a valid local path right in _string?
if (NotAny(Flags.HostNotCanonical | Flags.PathNotCanonical | Flags.ShouldBeCompressed))
{
start = IsUncPath ? _info.Offset.Host - 2 : _info.Offset.Path;
}
//
- // Was the original string switched from m_String to m_OriginalUnicodeString
- // Will happen when Iri is turned on and we have unicode chars or of idn is
- // is on and we have an idn or unicode host.
- //
- private bool OriginalStringSwitched
- {
- get
- {
- return (_iriParsing && InFact(Flags.HasUnicode));
- }
- }
-
+ // Gets the exact string passed by a user.
+ // The original string will switched from _string to _originalUnicodeString if
+ // iri is turned on and we have non-ascii chars
//
- // Gets the exact string passed by a user.
- public string OriginalString
- {
- get
- {
- return OriginalStringSwitched ? _originalUnicodeString : _string;
- }
- }
+ public string OriginalString => _originalUnicodeString ?? _string;
//
// Gets the host string that is unescaped and if it's Ipv6 host,
EnsureHostString(false);
- if (!string.IsNullOrEmpty(_info.DnsSafeHost))
+ if (_info.DnsSafeHost != null)
{
// Cached
return _info.DnsSafeHost;
{
if (_syntax == null)
{
- return (_iriParsing && InFact(Flags.HasUnicode)) ? _string : OriginalString;
+ return _string;
}
EnsureUriInfo();
- if ((object?)_info.String == null)
+ if (_info.String is null)
{
- if (Syntax!.IsSimple)
+ if (_syntax.IsSimple)
_info.String = GetComponentsHelper(UriComponents.AbsoluteUri, V1ToStringUnescape);
else
_info.String = GetParts(UriComponents.AbsoluteUri, UriFormat.SafeUnescaped);
if (NotAny(Flags.AllUriInfoSet) || obj.NotAny(Flags.AllUriInfoSet))
{
- // Try raw compare for m_Strings as the last chance to keep the working set small
+ // Try raw compare for _strings as the last chance to keep the working set small
if (!IsUncOrDosPath)
{
if (_string.Length == obj._string.Length)
{
unsafe
{
- // Try case sensitive compare on m_Strings
+ // Try case sensitive compare on _strings
fixed (char* selfPtr = _string)
{
fixed (char* otherPtr = obj._string)
{
- // This will never go negative since m_String is checked to be a valid URI
+ // This will never go negative since _string is checked to be a valid URI
int i = (_string.Length - 1);
for (; i >= 0; --i)
{
}
// Note that equality test will bring the working set of both
- // objects up to creation of m_Info.MoreInfo member
+ // objects up to creation of _info.MoreInfo member
EnsureUriInfo();
obj.EnsureUriInfo();
}
unsafe
{
- // Try case sensitive compare on m_Strings
+ // Try case sensitive compare on _strings
fixed (char* seltPtr = selfUrl)
{
fixed (char* otherPtr = otherUrl)
//STEP2: Parse up to the port
- fixed (char* pUriString = ((_iriParsing &&
- ((_flags & Flags.HasUnicode) != 0) &&
- ((_flags & Flags.HostUnicodeNormalized) == 0)) ? _originalUnicodeString : _string))
+ fixed (char* pUriString = (_flags & Flags.HostUnicodeNormalized) == 0 ? OriginalString : _string)
{
// Cut trailing spaces in _string
if (length > idx && UriHelper.IsLWS(pUriString[length - 1]))
else if (_syntax.NotAny(UriSyntaxFlags.MailToLikeUri))
{
// By now we know the URI has no Authority, so if the URI must be normalized, initialize it without one.
- if (_iriParsing && (_flags & Flags.HasUnicode) != 0 && (_flags & Flags.HostUnicodeNormalized) == 0)
+ if ((_flags & (Flags.HasUnicode | Flags.HostUnicodeNormalized)) == Flags.HasUnicode)
{
_string = _string.Substring(0, idx);
}
else if (_syntax.NotAny(UriSyntaxFlags.MailToLikeUri))
{
// By now we know the URI has no Authority, so if the URI must be normalized, initialize it without one.
- if (_iriParsing && (_flags & Flags.HasUnicode) != 0 && (_flags & Flags.HostUnicodeNormalized) == 0)
+ if ((_flags & (Flags.HasUnicode | Flags.HostUnicodeNormalized)) == Flags.HasUnicode)
{
_string = _string.Substring(0, idx);
}
// is not created/canonicalized at this point.
}
- if (_iriParsing)
+ if (IriParsing && newHost != null)
{
// we have a new host!
- if (newHost != null)
- _string = newHost;
-
- // Indicate to createuriinfo that offset is in m_originalUnicodeString
- if (_iriParsing && ((_flags & Flags.HasUnicode) != 0))
- {
- // offset in Flags.IndexMask refers to m_originalUnicodeString
- _flags |= Flags.UseOrigUncdStrOffset;
- }
+ _string = newHost;
}
return ParsingError.None;
//
//
- // The method is called when we have to access m_Info members.
- // This will create the m_Info based on the copied parser context.
+ // The method is called when we have to access _info members.
+ // This will create the _info based on the copied parser context.
// If multi-threading, this method may do duplicated yet harmless work.
//
private unsafe void CreateUriInfo(Flags cF)
{
UriInfo info = new UriInfo();
- // This will be revisited in ParseRemaining but for now just have it at least m_String.Length
+ // This will be revisited in ParseRemaining but for now just have it at least _string.Length
info.Offset.End = (ushort)_string.Length;
if (UserDrivenParsing)
int idx;
bool notCanonicalScheme = false;
- // The m_String may have leading spaces, figure that out
+ // The _string may have leading spaces, figure that out
// plus it will set idx value for next steps
if ((cF & Flags.ImplicitFile) != 0)
{
// Note we already checked on general port syntax in ParseMinimal()
// If iri parsing is on with unicode chars then the end of parsed host
- // points to m_orig string and not m_String
+ // points to _originalUnicodeString and not _string
- bool UseOrigUnicodeStrOffset = ((cF & Flags.UseOrigUncdStrOffset) != 0);
- // This should happen only once. Reset it
- cF &= ~Flags.UseOrigUncdStrOffset;
-
- if (UseOrigUnicodeStrOffset)
+ if ((cF & Flags.HasUnicode) != 0)
info.Offset.End = (ushort)_originalUnicodeString.Length;
if (idx < info.Offset.End)
{
- fixed (char* userString = UseOrigUnicodeStrOffset ? _originalUnicodeString : _string)
+ fixed (char* userString = OriginalString)
{
if (userString[idx] == ':')
{
Done:
cF |= Flags.MinimalUriInfoSet;
- info.DnsSafeHost = _dnsSafeHost;
lock (_string)
{
if ((_flags & Flags.MinimalUriInfoSet) == 0)
if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical)
{
- // we will make a canonical host in m_Info.Host, but mark that m_String holds wrong data
+ // we will make a canonical host in _info.Host, but mark that _string holds wrong data
flags |= Flags.E_HostNotCanonical;
if (NotAny(Flags.UserEscaped))
{
}
else if (NotAny(Flags.CanonicalDnsHost))
{
- // Check to see if we can take the canonical host string out of m_String
+ // Check to see if we can take the canonical host string out of _string
if ((object?)_info.ScopeId != null)
{
// IPv6 ScopeId is included when serializing a Uri
EnsureHostString(false);
string stemp = (parts & UriComponents.Host) == 0 ? string.Empty : _info.Host!;
// we reserve more space than required because a canonical Ipv6 Host
- // may take more characters than in original m_String
+ // may take more characters than in original _string
// Also +3 is for :// and +1 is for absent first slash
// Also we may escape every character, hence multiplying by 12
// UTF-8 can use up to 4 bytes per char * 3 chars per byte (%A4) = 12 encoded chars
{
if ((nonCanonical & (ushort)UriComponents.Port) == 0)
{
- //take it from m_String
+ //take it from _string
if (InFact(Flags.NotDefaultPort))
{
int start = _info.Offset.Path;
//
//This method does:
- // - Creates m_Info member
+ // - Creates _info member
// - checks all components up to path on their canonical representation
// - continues parsing starting the path position
// - Sets the offsets of remaining components
goto Done;
// Do we have to continue building Iri'zed string from original string
- bool buildIriStringFromPath = _iriParsing && ((_flags & Flags.HasUnicode) != 0) && ((_flags & Flags.RestUnicodeNormalized) == 0);
+ bool buildIriStringFromPath = (_flags & (Flags.HasUnicode | Flags.RestUnicodeNormalized)) == Flags.HasUnicode;
int origIdx; // stores index to switched original string
int idx = _info.Offset.Scheme;
Check result = Check.None;
UriSyntaxFlags syntaxFlags = _syntax.Flags;
- // m_Info.Offset values may be parsed twice but we lock only on m_Flags update.
+ // _info.Offset values may be parsed twice but we lock only on _flags update.
fixed (char* str = _string)
{
{
cF |= Flags.E_UserNotCanonical;
}
- if (_iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
+ if (IriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
| Check.FoundNonAscii | Check.NotIriCanonical))
== (Check.DisplayCanonical | Check.FoundNonAscii)))
{
// Parsing the Path if any
//
- // For iri parsing if we found unicode the idx has offset into m_orig string..
- // so restart parsing from there and make m_Info.Offset.Path as m_string.length
+ // For iri parsing if we found unicode the idx has offset into _originalUnicodeString..
+ // so restart parsing from there and make _info.Offset.Path as _string.Length
idx = _info.Offset.Path;
origIdx = _info.Offset.Path;
// so both '?' and '#' will work as delimiters
if (buildIriStringFromPath)
{
- // Dos paths have no host. Other schemes cleared/set m_String with host information in PrivateParseMinimal.
+ // Dos paths have no host. Other schemes cleared/set _string with host information in PrivateParseMinimal.
if (IsDosPath)
{
if (IsImplicitFile)
cF |= Flags.E_PathNotCanonical;
}
- if (_iriParsing && !nonCanonical & ((result & (Check.DisplayCanonical | Check.EscapedCanonical
+ if (IriParsing && !nonCanonical & ((result & (Check.DisplayCanonical | Check.EscapedCanonical
| Check.FoundNonAscii | Check.NotIriCanonical))
== (Check.DisplayCanonical | Check.FoundNonAscii)))
{
cF |= Flags.E_QueryNotCanonical;
}
- if (_iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
+ if (IriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
| Check.FoundNonAscii | Check.NotIriCanonical))
== (Check.DisplayCanonical | Check.FoundNonAscii)))
{
cF |= Flags.E_FragmentNotCanonical;
}
- if (_iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
+ if (IriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
| Check.FoundNonAscii | Check.NotIriCanonical))
== (Check.DisplayCanonical | Check.FoundNonAscii)))
{
//
// Checks the syntax of an authority component. It may also get a userInfo if present
// Returns an error if no/mailformed authority found
- // Does not NOT touch m_Info
+ // Does not NOT touch _info
// Returns position of the Path component
//
// Must be called in the ctor only
bool justNormalized = false;
bool iriParsing = IriParsingStatic(syntax);
bool hasUnicode = ((flags & Flags.HasUnicode) != 0);
- bool hostNotUnicodeNormalized = ((flags & Flags.HostUnicodeNormalized) == 0);
+ bool hostNotUnicodeNormalized = hasUnicode && ((flags & Flags.HostUnicodeNormalized) == 0);
UriSyntaxFlags syntaxFlags = syntax.Flags;
//Special case is an empty authority
else
err = ParsingError.BadHostName;
- if (hasUnicode && iriParsing && hostNotUnicodeNormalized)
+ if (hostNotUnicodeNormalized)
{
flags |= Flags.HostUnicodeNormalized; // no host
}
}
// need to build new Iri'zed string
- if (hasUnicode && iriParsing && hostNotUnicodeNormalized)
+ if (hostNotUnicodeNormalized)
{
newHost = _originalUnicodeString.Substring(0, startInput);
}
// Iri'ze userinfo
if (iriParsing)
{
- if (iriParsing && hasUnicode && hostNotUnicodeNormalized)
+ if (hostNotUnicodeNormalized)
{
// Normalize user info
userInfoString = IriHelper.EscapeUnescapeIri(pString, startInput, start + 1, UriComponents.UserInfo);
{
flags |= Flags.IPv6HostType;
- _iriParsing = IriParsingStatic(syntax);
-
- if (hasUnicode && iriParsing && hostNotUnicodeNormalized)
+ if (hostNotUnicodeNormalized)
{
newHost += new string(pString, start, end - start);
flags |= Flags.HostUnicodeNormalized;
{
flags |= Flags.IPv4HostType;
- if (hasUnicode && iriParsing && hostNotUnicodeNormalized)
+ if (hostNotUnicodeNormalized)
{
newHost += new string(pString, start, end - start);
flags |= Flags.HostUnicodeNormalized;
}
}
else if (((syntaxFlags & UriSyntaxFlags.AllowDnsHost) != 0)
- && ((syntax.InFact(UriSyntaxFlags.AllowIriParsing) && hostNotUnicodeNormalized)
- || syntax.InFact(UriSyntaxFlags.AllowIdn))
+ && (hostNotUnicodeNormalized || syntax.InFact(UriSyntaxFlags.AllowIdn))
&& DomainNameHelper.IsValidByIri(pString, start, ref end, ref dnsNotCanonical,
StaticNotAny(flags, Flags.ImplicitFile)))
{
if (end - start <= UncNameHelper.MaximumInternetNameLength)
{
flags |= Flags.UncHostType;
- if (hasUnicode && iriParsing && hostNotUnicodeNormalized)
+ if (hostNotUnicodeNormalized)
{
newHost += new string(pString, start, end - start);
flags |= Flags.HostUnicodeNormalized;
}
}
- if (iriParsing && hasUnicode && justNormalized)
+ if (hasUnicode && justNormalized)
{
newHost += new string(pString, startPort, idx - startPort);
}
break;
}
}
- CheckAuthorityHelperHandleAnyHostIri(pString, startInput, end, iriParsing, hasUnicode,
- ref flags, ref newHost, ref err);
+
+ if (hostNotUnicodeNormalized)
+ {
+ // Normalize any other host or do idn
+ string user = new string(pString, startInput, end - startInput);
+
+ try
+ {
+ newHost += user.Normalize(NormalizationForm.FormC);
+ }
+ catch (ArgumentException)
+ {
+ err = ParsingError.BadHostName;
+ }
+
+ flags |= Flags.HostUnicodeNormalized;
+ }
}
else
{
//success
flags |= Flags.BasicHostType;
- if (iriParsing && hasUnicode
- && StaticNotAny(flags, Flags.HostUnicodeNormalized))
+ if (hostNotUnicodeNormalized)
{
// Normalize any other host
string user = new string(pString, startOtherHost, end - startOtherHost);
flags |= Flags.HostUnicodeNormalized;
}
- private unsafe void CheckAuthorityHelperHandleAnyHostIri(char* pString, int startInput, int end,
- bool iriParsing, bool hasUnicode,
- ref Flags flags, ref string? newHost, ref ParsingError err)
- {
- if (StaticNotAny(flags, Flags.HostUnicodeNormalized) && (iriParsing && hasUnicode))
- {
- // Normalize any other host or do idn
- string user = new string(pString, startInput, end - startInput);
-
- try
- {
- newHost += user.Normalize(NormalizationForm.FormC);
- }
- catch (ArgumentException)
- {
- err = ParsingError.BadHostName;
- }
-
- flags |= Flags.HostUnicodeNormalized;
- }
- }
-
//
// The method checks whether a string needs transformation before going to display or wire
//
Check res = Check.None;
bool needsEscaping = false;
bool foundEscaping = false;
+ bool iriParsing = IriParsing;
- char c = c_DummyChar;
+ char c;
int i = idx;
for (; i < end; ++i)
{
}
else if (c > '~')
{
- if (_iriParsing)
+ if (iriParsing)
{
bool valid = false;
res |= Check.FoundNonAscii;
// conclude that we have a valid canonical IRI.
// If we have an IRI with Flags.HasUnicode, we need to set Check.NotIriCanonical so that the
// path, query, and fragment will be validated.
- if ((_flags & Flags.HasUnicode) != 0 && _iriParsing)
+ if ((_flags & Flags.HasUnicode) != 0)
{
res |= Check.NotIriCanonical;
}