Initial commit of Microsoft .NET Framework reference source files
This commit adds the 4 files we need from the Microsoft .NET Framework reference source along with the license (MIT) and metadata. The source files have not been modified.
Change-Id: Ideeb869cd9ea62f10aab3785eb212544d17de9ae
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ccdf041
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) Microsoft Corporation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..c2157c4
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: ".NET Framework Reference Source"
+description:
+ "Source from the Microsoft .NET Reference "
+ "Source that represent a subset of the "
+ ".NET Framework"
+
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://referencesource.microsoft.com/"
+ }
+ url {
+ type: GIT
+ value: "https://github.com/microsoft/referencesource.git"
+ }
+ version: "4.6.2"
+ last_upgrade_date { year: 2023 month: 03 day: 29 }
+ license_type: NOTICE
+}
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..60df063
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,12 @@
+set noparent
+
+adamws@google.com
+bcairns@google.com
+gregdenton@google.com
+hugheshilton@google.com
+ianfisk@google.com
+igorveli@google.com
+joshuta@google.com
+kellerc@google.com
+lhumphries@google.com
+tryland@google.com
\ No newline at end of file
diff --git a/System/net/System/URI.cs b/System/net/System/URI.cs
new file mode 100644
index 0000000..e264a1f
--- /dev/null
+++ b/System/net/System/URI.cs
@@ -0,0 +1,5518 @@
+//------------------------------------------------------------------------------
+// <copyright file="URI.cs" company="Microsoft">
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace System {
+ using System.Runtime.InteropServices;
+ using System.Text;
+ using System.Globalization;
+ using System.Security;
+ using System.ComponentModel;
+ // Not in SL:
+ using System.Configuration;
+ using System.Security.Permissions;
+ using System.Runtime.Serialization;
+ using Microsoft.Win32;
+ using System.Threading;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Net;
+ [Serializable]
+ [TypeConverter(typeof(UriTypeConverter))]
+ public partial class Uri : ISerializable {
+
+ public static readonly string UriSchemeFile = UriParser.FileUri.SchemeName;
+ public static readonly string UriSchemeFtp = UriParser.FtpUri.SchemeName;
+ public static readonly string UriSchemeGopher = UriParser.GopherUri.SchemeName;
+ public static readonly string UriSchemeHttp = UriParser.HttpUri.SchemeName;
+ public static readonly string UriSchemeHttps = UriParser.HttpsUri.SchemeName;
+ internal static readonly string UriSchemeWs = UriParser.WsUri.SchemeName;
+ internal static readonly string UriSchemeWss = UriParser.WssUri.SchemeName;
+ public static readonly string UriSchemeMailto = UriParser.MailToUri.SchemeName;
+ public static readonly string UriSchemeNews = UriParser.NewsUri.SchemeName;
+ public static readonly string UriSchemeNntp = UriParser.NntpUri.SchemeName;
+ public static readonly string UriSchemeNetTcp = UriParser.NetTcpUri.SchemeName;
+ public static readonly string UriSchemeNetPipe = UriParser.NetPipeUri.SchemeName;
+ public static readonly string SchemeDelimiter = "://";
+
+
+ private const int c_Max16BitUtf8SequenceLength = 3+3+3+3; //each unicode byte takes 3 escaped chars
+ internal const int c_MaxUriBufferSize = 0xFFF0;
+ private const int c_MaxUriSchemeName = 1024;
+
+ // untouched user string unless string has unicode chars and iriparsing is enabled
+ // or idn is on and we have unicode host or idn host
+ // In that case, this string is normalized, stripped of bidi chars, and validated
+ // with char limits
+ private string m_String;
+
+ // untouched user string if string has unicode with iri on or unicode/idn host with idn on
+ private string m_originalUnicodeString;
+
+ private UriParser m_Syntax; // 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 m_DnsSafeHost = null;
+
+ [Flags]
+ private enum Flags : ulong {
+ Zero = 0x00000000,
+
+ SchemeNotCanonical = 0x1,
+ UserNotCanonical = 0x2,
+ HostNotCanonical = 0x4,
+ PortNotCanonical = 0x8,
+ PathNotCanonical = 0x10,
+ QueryNotCanonical = 0x20,
+ FragmentNotCanonical = 0x40,
+ CannotDisplayCanonical = 0x7F,
+
+ E_UserNotCanonical = 0x80,
+ E_HostNotCanonical = 0x100,
+ E_PortNotCanonical = 0x200,
+ E_PathNotCanonical = 0x400,
+ E_QueryNotCanonical = 0x800,
+ E_FragmentNotCanonical = 0x1000,
+ E_CannotDisplayCanonical = 0x1F80,
+
+
+ ShouldBeCompressed = 0x2000,
+ FirstSlashAbsent = 0x4000,
+ BackslashInPath = 0x8000,
+
+ IndexMask = 0x0000FFFF,
+ HostTypeMask = 0x00070000,
+ HostNotParsed = 0x00000000,
+ IPv6HostType = 0x00010000,
+ IPv4HostType = 0x00020000,
+ DnsHostType = 0x00030000,
+#if !PLATFORM_UNIX
+ UncHostType = 0x00040000,
+#endif // !PLATFORM_UNIX
+ BasicHostType = 0x00050000,
+ UnusedHostType = 0x00060000,
+ UnknownHostType = 0x00070000,
+
+ UserEscaped = 0x00080000,
+ AuthorityFound = 0x00100000,
+ HasUserInfo = 0x00200000,
+ LoopbackHost = 0x00400000,
+ NotDefaultPort = 0x00800000,
+
+ UserDrivenParsing = 0x01000000,
+ CanonicalDnsHost = 0x02000000,
+ ErrorOrParsingRecursion = 0x04000000, // Used to signal a default parser error and alsoe to confirm Port
+ // and Host values in case of a custom user Parser
+#if !PLATFORM_UNIX
+ DosPath = 0x08000000,
+ UncPath = 0x10000000,
+#endif // !PLATFORM_UNIX
+ ImplicitFile = 0x20000000,
+ MinimalUriInfoSet = 0x40000000,
+ AllUriInfoSet = unchecked(0x80000000),
+ IdnHost = 0x100000000,
+ HasUnicode = 0x200000000,
+ HostUnicodeNormalized = 0x400000000,
+ RestUnicodeNormalized = 0x800000000,
+ UnicodeHost = 0x1000000000,
+ IntranetUri = 0x2000000000,
+ UseOrigUncdStrOffset= 0x4000000000,
+ // Is this component Iri canonical
+ UserIriCanonical = 0x8000000000,
+ PathIriCanonical = 0x10000000000,
+ QueryIriCanonical = 0x20000000000,
+ FragmentIriCanonical = 0x40000000000,
+ IriCanonical = 0x78000000000,
+ }
+
+ private Flags m_Flags;
+ private UriInfo m_Info;
+
+ private class UriInfo {
+ public string Host;
+ public string ScopeId; //only IP v6 may need this
+ public string String;
+ 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.
+ };
+
+ [StructLayout(LayoutKind.Sequential, Pack=1)]
+ private struct Offset {
+ public ushort Scheme;
+ public ushort User;
+ public ushort Host;
+ public ushort PortValue;
+ public ushort Path;
+ public ushort Query;
+ public ushort Fragment;
+ public ushort End;
+ };
+
+ private class MoreInfo {
+ public string Path;
+ public string Query;
+ public string Fragment;
+ public string AbsoluteUri;
+ public int Hash;
+ public string RemoteUrl;
+ };
+
+ private bool IsImplicitFile {
+ get {return (m_Flags & Flags.ImplicitFile) != 0;}
+ }
+
+ private bool IsUncOrDosPath {
+#if !PLATFORM_UNIX
+ get {return (m_Flags & (Flags.UncPath|Flags.DosPath)) != 0;}
+#else
+ get {return false;}
+#endif // !PLATFORM_UNIX
+ }
+
+ private bool IsDosPath {
+#if !PLATFORM_UNIX
+ get {return (m_Flags & Flags.DosPath) != 0;}
+#else
+ get {return false;}
+#endif // !PLATFORM_UNIX
+ }
+
+ private bool IsUncPath {
+#if !PLATFORM_UNIX
+ get {return (m_Flags & Flags.UncPath) != 0;}
+#else
+ get {return false;}
+#endif // !PLATFORM_UNIX
+ }
+
+ private Flags HostType {
+ get {return m_Flags & Flags.HostTypeMask;}
+ }
+
+ private UriParser Syntax {
+ get {
+ return m_Syntax;
+ }
+ }
+
+ private bool IsNotAbsoluteUri {
+ get {return (object) m_Syntax == null;}
+ }
+
+ //
+ // Checks if Iri parsing is allowed by the syntax & by config
+ //
+ private bool m_iriParsing;
+
+ //
+ // Statically checks if Iri parsing is allowed by the syntax & by config
+ //
+ internal static bool IriParsingStatic( UriParser syntax )
+ {
+ return (s_IriParsing && (((syntax != null) && syntax.InFact(UriSyntaxFlags.AllowIriParsing)) ||
+ (syntax == null)));
+ }
+
+ //
+ // Checks if Idn is allowed by the syntax & by config
+ //
+ private bool AllowIdn
+ {
+ get { return ((m_Syntax != null) && ((m_Syntax.Flags & UriSyntaxFlags.AllowIdn) != 0) &&
+ ((s_IdnScope == UriIdnScope.All) || ((s_IdnScope == UriIdnScope.AllExceptIntranet)
+ && NotAny(Flags.IntranetUri)))); }
+ }
+
+ //
+ // Checks statically if Idn is allowed by the syntax & by config
+ //
+ private bool AllowIdnStatic(UriParser syntax, Flags flags)
+ {
+ return ((syntax != null) && ((syntax.Flags & UriSyntaxFlags.AllowIdn) != 0) &&
+ ((s_IdnScope == UriIdnScope.All) || ((s_IdnScope == UriIdnScope.AllExceptIntranet)
+ && StaticNotAny(flags, Flags.IntranetUri))));
+ }
+
+ //
+ // check if the scheme + host are in intranet or not
+ // Used to determine of we apply idn or not
+ //
+ private static volatile IInternetSecurityManager s_ManagerRef = null;
+ private static object s_IntranetLock = new object();
+
+ private bool IsIntranet(string schemeHost)
+ {
+ bool error = false;
+ int zone = -1;
+ int E_FAIL = unchecked((int)0x80004005);
+
+ // MapUrlToZone call below fails on scheme length > 32 so we consider this
+ // not be be intranet
+ //
+ if (m_Syntax.SchemeName.Length > 32)
+ return false;
+
+ if (s_ManagerRef == null){
+ lock (s_IntranetLock){
+ if(s_ManagerRef == null)
+ {
+#if !FEATURE_PAL
+ // Go through CoCreateInstance as creating arbitary COM object is no longer supported in AppX scenario
+ Guid clsid = typeof(InternetSecurityManager).GUID;
+ Guid iid = typeof(IInternetSecurityManager).GUID;
+
+ object managerRef;
+ UnsafeNclNativeMethods.CoCreateInstance(
+ ref clsid ,
+ IntPtr.Zero,
+ UnsafeNclNativeMethods.CLSCTX_SERVER,
+ ref iid,
+ out managerRef
+ );
+ s_ManagerRef = managerRef as IInternetSecurityManager;
+#else
+ s_ManagerRef = (IInternetSecurityManager)new InternetSecurityManager();
+#endif // !FEATURE_PAL
+ }
+ }
+ }
+
+ try{
+ s_ManagerRef.MapUrlToZone(schemeHost.TrimStart(_WSchars), out zone, 0);
+ }
+ catch (COMException ex){
+ if (ex.ErrorCode == E_FAIL){ // E_FAIL
+ error = true;
+ }
+ }
+ // If s_ManagerRef was initilized on an STA thread then it cannot be accessed from other threads.
+ // Visual Studio Unit Tests are the primary scenario for this.
+ catch (InvalidComObjectException) {
+ error = true;
+ }
+
+ if(zone == (int) SecurityZone.Intranet)
+ return true;
+
+ // Do dot check for intranet if zone is trusted or untrusted
+ // since an intranet zone may be in these zones as well
+ if ((zone == (int)SecurityZone.Trusted) ||
+ (zone == (int)SecurityZone.Untrusted) || error)
+ {
+ // do dot check
+ for (int i = 0; i < schemeHost.Length; ++i)
+ {
+ if (schemeHost[i] == '.')
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ internal bool UserDrivenParsing
+ {
+ get {
+ return (m_Flags & Flags.UserDrivenParsing) != 0;
+ }
+ }
+ private void SetUserDrivenParsing()
+ {
+ // we use = here to clear all parsing flags for a uri that we think is invalid.
+ m_Flags = Flags.UserDrivenParsing | (m_Flags & Flags.UserEscaped);
+ }
+
+ private ushort SecuredPathIndex {
+ get {
+ // This is one more trouble with a Dos Path.
+ // This property gets "safe" first path slash that is not the first if path = c:\
+ if (IsDosPath) {
+ char ch = m_String[m_Info.Offset.Path];
+ return (ushort)((ch == '/' || ch == '\\')? 3 :2);
+ }
+ return (ushort)0;
+ }
+ }
+
+ private bool NotAny(Flags flags) {
+ return (m_Flags & flags) == 0;
+ }
+
+ private bool InFact(Flags flags) {
+ return (m_Flags & flags) != 0;
+ }
+
+ private static bool StaticNotAny(Flags allFlags, Flags checkFlags) {
+ return (allFlags & checkFlags) == 0;
+ }
+
+ private static bool StaticInFact(Flags allFlags, Flags checkFlags) {
+ return (allFlags & checkFlags) != 0;
+ }
+
+ //
+ //
+ private UriInfo EnsureUriInfo() {
+ Flags cF = m_Flags;
+ if ((m_Flags & Flags.MinimalUriInfoSet) == 0) {
+ CreateUriInfo(cF);
+ }
+ return m_Info;
+ }
+ //
+ //
+ private void EnsureParseRemaining() {
+ if ((m_Flags & Flags.AllUriInfoSet) == 0) {
+ ParseRemaining();
+ }
+ }
+ //
+ //
+ private void EnsureHostString(bool allowDnsOptimization) {
+ EnsureUriInfo();
+ if ((object)m_Info.Host == null) {
+ if (allowDnsOptimization && InFact(Flags.CanonicalDnsHost)) {
+ /* Optimization for a canonical DNS name
+ * ATTN: the host string won't be created,
+ * Hence ALL m_Info.Host callers first call EnsureHostString(false)
+ * For example IsLoopBack property is one of such callers.
+ */
+ return;
+ }
+ CreateHostString();
+ }
+ }
+
+ //
+ // Uri(string)
+ //
+ // We expect to create a Uri from a display name - e.g. that was typed by
+ // a user, or that was copied & pasted from a document. That is, we do not
+ // expect already encoded URI to be supplied.
+ //
+ public Uri(string uriString){
+ if ((object)uriString == null)
+ throw new ArgumentNullException("uriString");
+
+ CreateThis(uriString, false, UriKind.Absolute);
+ }
+
+
+ //
+ // Uri(string, bool)
+ //
+ // Uri constructor. Assumes that input string is canonically escaped
+ //
+ [Obsolete("The constructor has been deprecated. Please use new Uri(string). The dontEscape parameter is deprecated and is always false. http://go.microsoft.com/fwlink/?linkid=14202")]
+ public Uri(string uriString, bool dontEscape) {
+ if ((object)uriString == null)
+ throw new ArgumentNullException("uriString");
+
+ CreateThis(uriString, dontEscape, UriKind.Absolute);
+ }
+
+ //
+ // Uri(Uri, string, bool)
+ //
+ // Uri combinatorial constructor. Do not perform character escaping if
+ // DontEscape is true
+ //
+ [Obsolete("The constructor has been deprecated. Please new Uri(Uri, string). The dontEscape parameter is deprecated and is always false. http://go.microsoft.com/fwlink/?linkid=14202")]
+ public Uri(Uri baseUri, string relativeUri, bool dontEscape){
+ if ((object)baseUri == null)
+ throw new ArgumentNullException("baseUri");
+
+ if (!baseUri.IsAbsoluteUri)
+ throw new ArgumentOutOfRangeException("baseUri");
+
+ CreateUri(baseUri, relativeUri, dontEscape);
+ }
+
+ //
+ // Uri(string, UriKind);
+ //
+ public Uri(string uriString, UriKind uriKind)
+ {
+ if ((object)uriString == null)
+ throw new ArgumentNullException("uriString");
+
+ CreateThis(uriString, false, uriKind);
+ }
+
+
+ //
+ // Uri(Uri, string)
+ //
+ // Construct a new Uri from a base and relative URI. The relative URI may
+ // also be an absolute URI, in which case the resultant URI is constructed
+ // entirely from it
+ //
+ public Uri(Uri baseUri, string relativeUri){
+ if ((object)baseUri == null)
+ throw new ArgumentNullException("baseUri");
+
+ if (!baseUri.IsAbsoluteUri)
+ throw new ArgumentOutOfRangeException("baseUri");
+
+ CreateUri(baseUri, relativeUri, false);
+ }
+
+ private void CreateUri(Uri baseUri, string relativeUri, bool dontEscape)
+ {
+ // Parse relativeUri and populate Uri internal data.
+ CreateThis(relativeUri, dontEscape, UriKind.RelativeOrAbsolute);
+
+ UriFormatException e;
+ if (baseUri.Syntax.IsSimple)
+ {
+ // Resolve Uris if possible OR get merged Uri String to re-parse below
+ Uri uriResult = ResolveHelper(baseUri, this, ref relativeUri, ref dontEscape, out e);
+
+ if (e != null)
+ throw e;
+
+ // If resolved into a Uri then we build from that Uri
+ if (uriResult != null)
+ {
+ if ((object)uriResult != (object)this)
+ CreateThisFromUri(uriResult);
+
+ return;
+ }
+ }
+ else
+ {
+ dontEscape = false;
+ relativeUri = baseUri.Syntax.InternalResolve(baseUri, this, out e);
+ if (e != null)
+ throw e;
+ }
+
+ m_Flags = Flags.Zero;
+ m_Info = null;
+ m_Syntax = null;
+ // If not resolved, we reparse modified Uri string and populate Uri internal data.
+ CreateThis(relativeUri, dontEscape, UriKind.Absolute);
+ }
+
+ //
+ // Uri(Uri , Uri )
+ // Note: a static Create() method should be used by users, not this .ctor
+ //
+ public Uri(Uri baseUri, Uri relativeUri)
+ {
+ if ((object)baseUri == null)
+ throw new ArgumentNullException("baseUri");
+
+ if (!baseUri.IsAbsoluteUri)
+ throw new ArgumentOutOfRangeException("baseUri");
+
+ CreateThisFromUri(relativeUri);
+
+ string newUriString = null;
+ UriFormatException e;
+ bool dontEscape;
+
+ if (baseUri.Syntax.IsSimple)
+ {
+ dontEscape = InFact(Flags.UserEscaped);
+ relativeUri = ResolveHelper(baseUri, this, ref newUriString, ref dontEscape, out e);
+
+ if (e != null)
+ throw e;
+
+ if (relativeUri != null)
+ {
+ if ((object)relativeUri != (object)this)
+ CreateThisFromUri(relativeUri);
+
+ return;
+ }
+ }
+ else
+ {
+ dontEscape = false;
+ newUriString = baseUri.Syntax.InternalResolve(baseUri, this, out e);
+ if (e != null)
+ throw e;
+ }
+
+ m_Flags = Flags.Zero;
+ m_Info = null;
+ m_Syntax = null;
+ CreateThis(newUriString, dontEscape, UriKind.Absolute);
+ }
+
+ //
+ // This method is shared by base+relative Uris constructors and is only called from them.
+ // The assumptions:
+ // - baseUri is a valid absolute Uri
+ // - relative part is not null and not empty
+ private unsafe static ParsingError GetCombinedString(Uri baseUri, string relativeStr,
+ bool dontEscape, ref string result)
+ {
+ // NB: This is not RFC2396 compliant although it is inline with w3c.org recommendations
+ // This parser will allow the relativeStr to be an absolute Uri with the different scheme
+ // In fact this is strict violation of RFC2396
+ //
+ for (int i=0; i < relativeStr.Length; ++i)
+ {
+ if (relativeStr[i] == '/' || relativeStr[i] == '\\' || relativeStr[i] == '?' || relativeStr[i] == '#')
+ {
+ break;
+ }
+ else if (relativeStr[i] == ':')
+ {
+ if (i < 2)
+ {
+ // Note we don't support one-letter Uri schemes.
+ // Hence anything like x:sdsd is a relative path and be added to the baseUri Path
+ break;
+ }
+ string scheme = relativeStr.Substring(0, i);
+ fixed (char* sptr = scheme) {
+ UriParser syntax = null;
+ if (CheckSchemeSyntax(sptr, (ushort) scheme.Length, ref syntax) == ParsingError.None) {
+ if (baseUri.Syntax == syntax) {
+ //Remove the scheme for backward Uri parsers compatibility
+ if (i+1 < relativeStr.Length) {
+ relativeStr = relativeStr.Substring(i+1);
+ }
+ else {
+ relativeStr = string.Empty;
+ }
+ }
+ else {
+ // This is the place where we switch the scheme.
+ // Return relative part as the result Uri.
+ result = relativeStr;
+ return ParsingError.None;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ if (relativeStr.Length == 0) {
+ result = baseUri.OriginalString;
+ return ParsingError.None;
+ }
+
+ result = CombineUri(baseUri, relativeStr, dontEscape? UriFormat.UriEscaped: UriFormat.SafeUnescaped);
+ return ParsingError.None;
+ }
+ //
+ private static UriFormatException GetException(ParsingError err)
+ {
+ switch (err)
+ {
+ case ParsingError.None:
+ return null;
+ // Could be OK for Relative Uri
+ case ParsingError.BadFormat:
+ return new UriFormatException(SR.GetString(SR.net_uri_BadFormat));
+ case ParsingError.BadScheme:
+ return new UriFormatException(SR.GetString(SR.net_uri_BadScheme));
+ case ParsingError.BadAuthority:
+ return new UriFormatException(SR.GetString(SR.net_uri_BadAuthority));
+ case ParsingError.EmptyUriString:
+ return new UriFormatException(SR.GetString(SR.net_uri_EmptyUri));
+ // Fatal
+ case ParsingError.SchemeLimit:
+ return new UriFormatException(SR.GetString(SR.net_uri_SchemeLimit));
+ case ParsingError.SizeLimit:
+ return new UriFormatException(SR.GetString(SR.net_uri_SizeLimit));
+ case ParsingError.MustRootedPath:
+ return new UriFormatException(SR.GetString(SR.net_uri_MustRootedPath));
+ // Derived class controllable
+ case ParsingError.BadHostName:
+ return new UriFormatException(SR.GetString(SR.net_uri_BadHostName));
+ case ParsingError.NonEmptyHost: //unix-only
+ return new UriFormatException(SR.GetString(SR.net_uri_BadFormat));
+ case ParsingError.BadPort:
+ return new UriFormatException(SR.GetString(SR.net_uri_BadPort));
+ case ParsingError.BadAuthorityTerminator:
+ return new UriFormatException(SR.GetString(SR.net_uri_BadAuthorityTerminator));
+ case ParsingError.CannotCreateRelative:
+ return new UriFormatException(SR.GetString(SR.net_uri_CannotCreateRelative));
+ default:
+ break;
+ }
+ return new UriFormatException(SR.GetString(SR.net_uri_BadFormat));
+ }
+
+ #region !Silverlight
+
+ //
+ // ISerializable constructor
+ //
+ protected Uri(SerializationInfo serializationInfo, StreamingContext streamingContext)
+ {
+ string uriString = serializationInfo.GetString("AbsoluteUri");
+
+ if (uriString.Length != 0)
+ {
+ CreateThis(uriString, false, UriKind.Absolute);
+ return;
+ }
+
+ uriString = serializationInfo.GetString("RelativeUri");
+ if ((object)uriString == null)
+ throw new ArgumentNullException("uriString");
+
+ CreateThis(uriString, false, UriKind.Relative);
+ }
+
+ //
+ // ISerializable method
+ //
+ /// <internalonly/>
+ [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase", Justification = "System.dll is still using pre-v4 security model and needs this demand")]
+ [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter=true)]
+ void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
+ {
+ GetObjectData(serializationInfo, streamingContext);
+ }
+
+ //
+ // FxCop: provide some way for derived classes to access GetObjectData even if the derived class
+ // explicitly re-inherits ISerializable.
+ //
+ [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter=true)]
+ protected void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
+ {
+
+ if (IsAbsoluteUri)
+ serializationInfo.AddValue("AbsoluteUri", GetParts(UriComponents.SerializationInfoString, UriFormat.UriEscaped));
+ else
+ {
+ serializationInfo.AddValue("AbsoluteUri", string.Empty);
+ serializationInfo.AddValue("RelativeUri", GetParts(UriComponents.SerializationInfoString, UriFormat.UriEscaped));
+ }
+ }
+
+ #endregion !Silverlight
+
+ //
+ //
+ //
+ public string AbsolutePath {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ string path = PrivateAbsolutePath;
+ //
+ //
+
+
+ if (IsDosPath && path[0] == '/') {
+ path = path.Substring(1);
+ }
+ return path;
+ }
+ }
+ //
+ private string PrivateAbsolutePath {
+ get {
+ UriInfo info = EnsureUriInfo();
+ if ((object) info.MoreInfo == null) {
+ info.MoreInfo = new MoreInfo();
+ }
+ string result = info.MoreInfo.Path;
+ if ((object) result == null) {
+ result = GetParts(UriComponents.Path | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
+ info.MoreInfo.Path = result;
+ }
+ return result;
+ }
+ }
+
+ //
+ //
+ //
+ public string AbsoluteUri {
+ get {
+ if (m_Syntax == null){
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ UriInfo info = EnsureUriInfo();
+ if ((object) info.MoreInfo == null) {
+ info.MoreInfo = new MoreInfo();
+ }
+ string result = info.MoreInfo.AbsoluteUri;
+ if ((object) result == null) {
+ result = GetParts(UriComponents.AbsoluteUri, UriFormat.UriEscaped);
+ info.MoreInfo.AbsoluteUri = result;
+ }
+ return result;
+ }
+ }
+
+ //
+ // LocalPath
+ //
+ // Returns a 'local' version of the path. This is mainly for file: URI
+ // such that DOS and UNC paths are returned with '/' converted back to
+ // '\', and any escape sequences converted
+ //
+ // The form of the returned path is in NOT Escaped
+ //
+ public string LocalPath {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+ return GetLocalPath();
+ }
+ }
+
+ #region !Silverlight
+ //
+ //
+ // The result is of the form "hostname[:port]" Port is omitted if default
+ //
+ public string Authority {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ //
+ return GetParts(UriComponents.Host | UriComponents.Port, UriFormat.UriEscaped);
+ }
+ }
+ //
+ //
+ public UriHostNameType HostNameType {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ if (m_Syntax.IsSimple)
+ EnsureUriInfo();
+ else
+ {
+ // For a custom parser we request HostString creation to confirm HostType
+ EnsureHostString(false);
+ }
+
+ switch (HostType) {
+ case Flags.DnsHostType: return UriHostNameType.Dns;
+ case Flags.IPv4HostType: return UriHostNameType.IPv4;
+ case Flags.IPv6HostType: return UriHostNameType.IPv6;
+ case Flags.BasicHostType: return UriHostNameType.Basic;
+#if !PLATFORM_UNIX
+ case Flags.UncHostType: return UriHostNameType.Basic; //return (UriHostNameType)(UriHostNameType.Basic+10);
+#endif // !PLATFORM_UNIX
+ case Flags.UnknownHostType: return UriHostNameType.Unknown;
+ default:
+ break;
+ }
+ return UriHostNameType.Unknown;
+ }
+ }
+ //
+ //
+ public bool IsDefaultPort {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+ if (m_Syntax.IsSimple)
+ EnsureUriInfo();
+ else
+ {
+ // For a custom parser we request HostString creation that will aso set the port
+ EnsureHostString(false);
+ }
+
+ return NotAny(Flags.NotDefaultPort);
+ }
+ }
+ //
+ //
+ public bool IsFile {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ return (object)m_Syntax.SchemeName == (object)UriSchemeFile;
+ }
+ }
+ //
+ //
+ public bool IsLoopback {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ EnsureHostString(false);
+
+ return InFact(Flags.LoopbackHost);
+ }
+ }
+
+ //
+ //
+ // Gets the escaped Uri.AbsolutePath and Uri.Query
+ // properties separated by a "?" character.
+ public string PathAndQuery {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ string result = GetParts(UriComponents.PathAndQuery, UriFormat.UriEscaped);
+ //
+ //
+
+
+ if (IsDosPath && result[0] == '/') {
+ result = result.Substring(1);
+ }
+ return result;
+ }
+ }
+
+ //
+ //
+ // Gets an array of the segments that make up a URI.
+ public string[] Segments {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ string[] segments = null; // used to be a class cached result
+ if (segments == null) {
+
+ string path = PrivateAbsolutePath;
+
+ if (path.Length == 0) {
+ segments = new string[0];
+ }
+ else {
+ System.Collections.ArrayList pathSegments = new System.Collections.ArrayList();
+ int current = 0;
+ while (current < path.Length) {
+ int next = path.IndexOf('/', current);
+ if (next == -1) {
+ next = path.Length - 1;
+ }
+ pathSegments.Add(path.Substring(current, (next - current) + 1));
+ current = next + 1;
+ }
+ segments = (string[])(pathSegments.ToArray(typeof(string)));
+ }
+ }
+ return segments;
+ }
+ }
+
+ #endregion !Silverlight
+
+ //
+ //
+ public bool IsUnc {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+ return IsUncPath;
+ }
+ }
+
+ //
+ // Gets a hostname part (special formatting for IPv6 form)
+ public string Host {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ return GetParts(UriComponents.Host, UriFormat.UriEscaped);
+ }
+ }
+
+ private static bool StaticIsFile(UriParser syntax)
+ {
+ return syntax.InFact(UriSyntaxFlags.FileLikeUri);
+ }
+
+ private static volatile bool s_ConfigInitialized; // Have the config values been initalized from config file
+ private static volatile bool s_ConfigInitializing; // used for recursion detection while init. config values
+
+ // Value from config Uri section
+ // The use of this IDN mechanic is discouraged on Win8+ due to native platform improvements.
+ private static volatile UriIdnScope s_IdnScope = IdnElement.EnabledDefaultValue;
+
+ // Value from config Uri section
+ // On by default in .NET 4.5+ and cannot be disabled by config.
+ private static volatile bool s_IriParsing =
+ (UriParser.ShouldUseLegacyV2Quirks ? IriParsingElement.EnabledDefaultValue : true);
+
+ private static object s_initLock;
+
+ private static object InitializeLock {
+ get {
+ if (s_initLock == null) {
+ object o = new object();
+ Interlocked.CompareExchange(ref s_initLock, o, null);
+ }
+ return s_initLock;
+ }
+ }
+
+ //
+ // Reads values from config uri section
+ //
+ // This method is called if:
+ // - a Uri is constructed, we parse the string and we find '%', >127 chars, or 'xn--'
+ // - we parse the host and figure out if it is an IPv6 address
+ private static void InitializeUriConfig()
+ {
+ if (!s_ConfigInitialized) {
+ lock(InitializeLock) {
+ if (!s_ConfigInitialized && !s_ConfigInitializing) {
+
+ // setting s_ConfigInitializing to true makes sure, that in web scenarios,
+ // where Uri instances may be created while parsing the web.config files, will not
+ // call into this code block again. We'll enter the following code only once per
+ // AppDomain.
+ s_ConfigInitializing = true;
+ UriSectionInternal section = UriSectionInternal.GetSection();
+
+ if (section != null) {
+ s_IdnScope = section.IdnScope;
+ // Iri can no longer be altered by the config, it is always on.
+ if (UriParser.ShouldUseLegacyV2Quirks) {
+ s_IriParsing = section.IriParsing;
+ }
+
+ SetEscapedDotSlashSettings(section, "http");
+ SetEscapedDotSlashSettings(section, "https");
+ SetEscapedDotSlashSettings(section, Uri.UriSchemeWs);
+ SetEscapedDotSlashSettings(section, Uri.UriSchemeWss);
+ }
+
+ s_ConfigInitialized = true;
+ s_ConfigInitializing = false;
+ }
+ }
+ }
+ }
+
+ // Legacy - This no longer has any affect in .NET 4.5 (non-quirks). See UriParser.HttpSyntaxFlags.
+ private static void SetEscapedDotSlashSettings(UriSectionInternal uriSection, string scheme)
+ {
+ // Currently we only support setting DontUnescapePathDotsAndSlashes for HTTP and HTTPS schemes.
+ // We ignore all other values. We won't throw for two reasons:
+ // - backward compatibility: Uri didn't throw so far.
+ // - the config section gets only read if we actually find e.g. a %-character in the Uri. If not, this
+ // code never gets executed, resulting in a weird behavior for the customer: If one application run
+ // doesn't use Uris with %-characters, it doesn't throw, if another run uses %-characters it throws.
+ // => If we want to throw we have to rethink the current implementation.
+ SchemeSettingInternal schemeSetting = uriSection.GetSchemeSetting(scheme);
+ if (schemeSetting != null) {
+ // We check for equality, not if Options contains DontUnescapePathDotsAndSlashes:
+ // Currently we only support this flag. If more than this flag are set, then it is an invalid
+ // setting and we ignore it.
+ if (schemeSetting.Options == GenericUriParserOptions.DontUnescapePathDotsAndSlashes) {
+ UriParser parser = UriParser.GetSyntax(scheme);
+ parser.SetUpdatableFlags(UriSyntaxFlags.None);
+ }
+ }
+ }
+
+ private string GetLocalPath(){
+ EnsureParseRemaining();
+
+ //Other cases will get a Unix-style path
+ if (IsUncOrDosPath)
+ {
+ EnsureHostString(false);
+ int start;
+
+ // Do we have a valid local path right in m_string?
+ if (NotAny(Flags.HostNotCanonical|Flags.PathNotCanonical|Flags.ShouldBeCompressed)) {
+
+ start = IsUncPath? m_Info.Offset.Host-2 :m_Info.Offset.Path;
+
+ string str = (IsImplicitFile && m_Info.Offset.Host == (IsDosPath ? 0 : 2) &&
+ m_Info.Offset.Query == m_Info.Offset.End)
+ ? m_String
+ : (IsDosPath && (m_String[start] == '/' || m_String[start] == '\\'))
+ ? m_String.Substring(start + 1, m_Info.Offset.Query - start - 1)
+ : m_String.Substring(start, m_Info.Offset.Query - start);
+
+ // Should be a rare case, convert c|\ into c:\
+ if (IsDosPath && str[1] == '|') {
+ // Sadly, today there is no method for replacong just one occurrence
+ str = str.Remove(1, 1);
+ str = str.Insert(1, ":");
+ }
+
+ // check for all back slashes (though may be string.Replace is smart?)
+ for (int i = 0; i < str.Length; ++i) {
+ if (str[i] == '/') {
+ str = str.Replace('/', '\\');
+ break;
+ }
+ }
+
+ return str;
+ }
+
+ // Not everything went well, trying harder
+
+ char[] result;
+ int count = 0;
+ start = m_Info.Offset.Path;
+
+ string host = m_Info.Host;
+ result = new char [host.Length + 3 + m_Info.Offset.Fragment - m_Info.Offset.Path ];
+
+ if (IsUncPath)
+ {
+ result[0] = '\\';
+ result[1] = '\\';
+ count = 2;
+
+ UriHelper.UnescapeString(host, 0, host.Length, result, ref count, c_DummyChar, c_DummyChar,
+ c_DummyChar, UnescapeMode.CopyOnly, m_Syntax, false);
+
+ }
+ else {
+ // Dos path
+ if(m_String[start] == '/' || m_String[start] == '\\') {
+ // Skip leading slash for a DOS path
+ ++start;
+ }
+ }
+
+
+ ushort pathStart = (ushort)count; //save for optional Compress() call
+
+ UnescapeMode mode = (InFact(Flags.PathNotCanonical) && !IsImplicitFile)
+ ? (UnescapeMode.Unescape | UnescapeMode.UnescapeAll): UnescapeMode.CopyOnly;
+ UriHelper.UnescapeString(m_String, start, m_Info.Offset.Query, result, ref count, c_DummyChar,
+ c_DummyChar, c_DummyChar, mode, m_Syntax, true);
+
+ // Possibly convert c|\ into c:\
+ if (result[1] == '|')
+ result[1] = ':';
+
+ if (InFact(Flags.ShouldBeCompressed)) {
+ // suspecting not compressed path
+ // For a dos path we won't compress the "x:" part if found /../ sequences
+ result = Compress(result, (ushort)(IsDosPath? pathStart + 2: pathStart), ref count, m_Syntax);
+ }
+
+ // We don't know whether all slashes were the back ones
+ // Plus going through Compress will turn them into / anyway
+ // Converting / back into \
+ for (ushort i = 0; i < (ushort) count; ++i) {
+ if (result[i] == '/') {
+ result[i] = '\\';
+ }
+ }
+
+ return new string(result, 0, count);
+
+ }
+ else {
+ // Return unescaped canonical path
+ // Note we cannot call GetParts here because it has circular dependancy on GelLocalPath method
+ return GetUnescapedParts(UriComponents.Path | UriComponents.KeepDelimiter, UriFormat.Unescaped);
+ }
+ }
+
+ //
+ //
+ //
+ //
+ public int Port {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ if (m_Syntax.IsSimple)
+ EnsureUriInfo();
+ else
+ {
+ // For a custom parser we request HostString creation that will aso set the port
+ EnsureHostString(false);
+ }
+
+ if (InFact(Flags.NotDefaultPort)) {
+ return (int)m_Info.Offset.PortValue;
+ }
+ return m_Syntax.DefaultPort;
+ }
+ }
+ //
+ //
+ //
+ // Gets the escaped query.
+ public string Query {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ UriInfo info = EnsureUriInfo();
+ if ((object)info.MoreInfo == null) {
+ info.MoreInfo = new MoreInfo();
+ }
+ string result = info.MoreInfo.Query;
+ if ((object)result == null) {
+ result = GetParts(UriComponents.Query | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
+ info.MoreInfo.Query = result;
+ }
+ return result;
+ }
+ }
+ //
+ //
+ //
+ // Gets the escaped fragment.
+ public string Fragment {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ UriInfo info = EnsureUriInfo();
+ if ((object)info.MoreInfo == null) {
+ info.MoreInfo = new MoreInfo();
+ }
+ string result = info.MoreInfo.Fragment;
+ if ((object)result == null) {
+ result = GetParts(UriComponents.Fragment | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
+ info.MoreInfo.Fragment = result;
+ }
+ return result;
+ }
+ }
+
+ //
+ // Gets the Scheme string of this Uri
+ //
+ //
+ public string Scheme {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ return m_Syntax.SchemeName;
+ }
+ }
+
+ //
+ // 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 ((m_iriParsing && InFact(Flags.HasUnicode)) ||
+ (AllowIdn && (InFact(Flags.IdnHost) || InFact(Flags.UnicodeHost))));}
+ }
+ //
+ // Gets the exact string passed by a user.
+ public String OriginalString {
+ get {
+ return OriginalStringSwitched ? m_originalUnicodeString : m_String;
+ }
+ }
+
+ //
+ // Gets the host string that is unescaped and if it's Ipv6 host,
+ // then the returned string is suitable for DNS lookup.
+ //
+ // For Ipv6 this will strip [] and add ScopeId if was found in the original string
+ public string DnsSafeHost {
+ get {
+
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ if (AllowIdn && (((m_Flags & Flags.IdnHost) != 0) || ((m_Flags & Flags.UnicodeHost) != 0))){
+ // return pre generated idn
+ EnsureUriInfo();
+ return m_Info.DnsSafeHost;
+ }
+
+ EnsureHostString(false);
+
+ if (!String.IsNullOrEmpty(m_Info.DnsSafeHost)) {
+ // Cached
+ return m_Info.DnsSafeHost;
+ } else if (m_Info.Host.Length == 0) {
+ // Empty host, no possible processing
+ return String.Empty;
+ }
+
+ // Special case, will include ScopeID and strip [] around IPv6
+ // This will also unescape the host string
+ string ret = m_Info.Host;
+
+ if (HostType == Flags.IPv6HostType) {
+ ret = ret.Substring(1, ret.Length - 2);
+ if ((object)m_Info.ScopeId != null) {
+ ret += m_Info.ScopeId;
+ }
+ }
+ // Validate that this basic host qualifies as Dns safe,
+ // It has looser parsing rules that might allow otherwise.
+ // It might be a registry-based host from RFC 2396 Section 3.2.1
+ else if (HostType == Flags.BasicHostType
+ && InFact(Flags.HostNotCanonical | Flags.E_HostNotCanonical)) {
+ // Unescape everything
+ char[] dest = new char[ret.Length];
+ int count = 0;
+ UriHelper.UnescapeString(ret, 0, ret.Length, dest, ref count, c_DummyChar, c_DummyChar,
+ c_DummyChar, UnescapeMode.Unescape | UnescapeMode.UnescapeAll, m_Syntax, false);
+ ret = new string(dest, 0, count);
+ }
+
+ m_Info.DnsSafeHost = ret;
+
+ return ret;
+ }
+ }
+
+ // Returns the host name represented as IDN (using punycode encoding) regardless of app.config settings
+ public string IdnHost {
+ get {
+ string host = this.DnsSafeHost;
+
+ if (HostType == Flags.DnsHostType) {
+ host = DomainNameHelper.IdnEquivalent(host);
+ }
+
+ return host;
+ }
+ }
+
+ //
+ // Returns false if the string passed in the constructor cannot be parsed as
+ // valid AbsoluteUri. This could be a relative Uri instead.
+ //
+ public bool IsAbsoluteUri {
+ get {
+ return m_Syntax != null;
+ }
+ }
+ //
+ //
+ // Returns 'true' if the 'dontEscape' parameter was set to 'true ' when the Uri instance was created.
+ public bool UserEscaped {
+ get {
+ return InFact(Flags.UserEscaped);
+ }
+ }
+ //
+ //
+ // Gets the user name, password, and other user specific information associated
+ // with the Uniform Resource Identifier (URI).
+ public string UserInfo {
+ get {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ return GetParts(UriComponents.UserInfo, UriFormat.UriEscaped);
+ }
+ }
+
+ #region !Silverlight
+
+ //
+ // CheckHostName
+ //
+ // Determines whether a host name authority is a valid Host name according
+ // to DNS naming rules and IPv4 canonicalization rules
+ //
+ // Returns:
+ // true if <name> is valid else false
+ //
+ // Throws:
+ // Nothing
+ //
+ public static UriHostNameType CheckHostName(string name) {
+
+ if ((object)name == null || name.Length == 0 || name.Length > short.MaxValue) {
+ return UriHostNameType.Unknown;
+ }
+ int end = name.Length;
+ unsafe {
+ fixed (char* fixedName = name) {
+
+ if (name[0] == '[' && name[name.Length-1] == ']') {
+ // we require that _entire_ name is recognized as ipv6 address
+ if (IPv6AddressHelper.IsValid(fixedName, 1, ref end) && end == name.Length) {
+ return UriHostNameType.IPv6;
+ }
+ }
+ end = name.Length;
+ if (IPv4AddressHelper.IsValid(fixedName, 0 , ref end, false, false, false) && end == name.Length) {
+ return UriHostNameType.IPv4;
+ }
+ end = name.Length;
+ bool dummyBool = false;
+ if (DomainNameHelper.IsValid(fixedName, 0, ref end, ref dummyBool, false) && end == name.Length) {
+ return UriHostNameType.Dns;
+ }
+
+ end = name.Length;
+ dummyBool = false;
+ if (DomainNameHelper.IsValidByIri(fixedName, 0, ref end, ref dummyBool, false)
+ && end == name.Length) {
+ return UriHostNameType.Dns;
+ }
+ }
+
+ //This checks the form without []
+ end = name.Length+2;
+ // we require that _entire_ name is recognized as ipv6 address
+ name = "["+name+"]";
+ fixed (char* newFixedName = name) {
+ if (IPv6AddressHelper.IsValid(newFixedName, 1, ref end) && end == name.Length) {
+ return UriHostNameType.IPv6;
+ }
+ }
+ }
+ return UriHostNameType.Unknown;
+ }
+
+ //
+ // GetLeftPart
+ //
+ // Returns part of the URI based on the parameters:
+ //
+ // Inputs:
+ // <argument> part
+ // Which part of the URI to return
+ //
+ // Returns:
+ // The requested substring
+ //
+ // Throws:
+ // UriFormatException if URI type doesn't have host-port or authority parts
+ //
+ public string GetLeftPart(UriPartial part) {
+ if (IsNotAbsoluteUri) {
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+ }
+
+ EnsureUriInfo();
+ const UriComponents NonPathPart = (UriComponents.Scheme | UriComponents.UserInfo | UriComponents.Host | UriComponents.Port);
+
+ switch (part) {
+ case UriPartial.Scheme:
+
+ return GetParts(UriComponents.Scheme | UriComponents.KeepDelimiter, UriFormat.UriEscaped);
+
+ case UriPartial.Authority:
+
+ if (NotAny(Flags.AuthorityFound) || IsDosPath) {
+
+ //
+
+
+
+ // From V1.0 comments:
+
+ // anything that didn't have "//" after the scheme name
+ // (mailto: and news: e.g.) doesn't have an authority
+ //
+
+ return String.Empty;
+ }
+ return GetParts(NonPathPart, UriFormat.UriEscaped);
+
+ case UriPartial.Path:
+ return GetParts(NonPathPart | UriComponents.Path, UriFormat.UriEscaped);
+
+ case UriPartial.Query:
+ return GetParts(NonPathPart | UriComponents.Path | UriComponents.Query, UriFormat.UriEscaped);
+
+ }
+ throw new ArgumentException("part");
+ }
+
+ //
+ //
+ /// Transforms a character into its hexadecimal representation.
+ public static string HexEscape(char character) {
+ if (character > '\xff') {
+ throw new ArgumentOutOfRangeException("character");
+ }
+ char[] chars = new char[3];
+ int pos = 0;
+ UriHelper.EscapeAsciiChar(character, chars, ref pos);
+ return new string(chars);
+ }
+
+ //
+ // HexUnescape
+ //
+ // Converts a substring of the form "%XX" to the single character represented
+ // by the hexadecimal value XX. If the substring s[Index] does not conform to
+ // the hex encoding format then the character at s[Index] is returned
+ //
+ // Inputs:
+ // <argument> pattern
+ // String from which to read the hexadecimal encoded substring
+ //
+ // <argument> index
+ // Offset within <pattern> from which to start reading the hexadecimal
+ // encoded substring
+ //
+ // Outputs:
+ // <argument> index
+ // Incremented to the next character position within the string. This
+ // may be EOS if this was the last character/encoding within <pattern>
+ //
+ // Returns:
+ // Either the converted character if <pattern>[<index>] was hex encoded, or
+ // the character at <pattern>[<index>]
+ //
+ // Throws:
+ // ArgumentOutOfRangeException
+ //
+
+ public static char HexUnescape(string pattern, ref int index) {
+ if ((index < 0) || (index >= pattern.Length)) {
+ throw new ArgumentOutOfRangeException("index");
+ }
+ if ((pattern[index] == '%')
+ && (pattern.Length - index >= 3)) {
+ char ret = UriHelper.EscapedAscii(pattern[index + 1], pattern[index + 2]);
+ if (ret != c_DummyChar) {
+ index += 3;
+ return ret;
+ }
+ }
+ return pattern[index++];
+ }
+
+ //
+ // IsHexEncoding
+ //
+ // Determines whether a substring has the URI hex encoding format of '%'
+ // followed by 2 hexadecimal characters
+ //
+ // Inputs:
+ // <argument> pattern
+ // String to check
+ //
+ // <argument> index
+ // Offset in <pattern> at which to check substring for hex encoding
+ //
+ // Assumes:
+ // 0 <= <index> < <pattern>.Length
+ //
+ // Returns:
+ // true if <pattern>[<index>] is hex encoded, else false
+ //
+ // Throws:
+ // Nothing
+ //
+ public static bool IsHexEncoding(string pattern, int index) {
+ if ((pattern.Length - index) < 3) {
+ return false;
+ }
+ if ((pattern[index] == '%') && UriHelper.EscapedAscii(pattern[index + 1], pattern[index + 2]) != c_DummyChar) {
+ return true;
+ }
+ return false;
+ }
+
+ //
+ // Is this a gen delim char from RFC 3986
+ //
+ internal static bool IsGenDelim(char ch)
+ {
+ return (ch == ':' || ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']' || ch == '@');
+ }
+
+ #endregion !Silverlight
+
+ //
+ // CheckSchemeName
+ //
+ // Determines whether a string is a valid scheme name according to RFC 2396.
+ // Syntax is:
+ // scheme = alpha *(alpha | digit | '+' | '-' | '.')
+ //
+ public static bool CheckSchemeName(string schemeName) {
+ if (((object)schemeName == null)
+ || (schemeName.Length == 0)
+ || !IsAsciiLetter(schemeName[0])) {
+ return false;
+ }
+ for (int i = schemeName.Length - 1; i > 0; --i) {
+ if (!(IsAsciiLetterOrDigit(schemeName[i])
+ || (schemeName[i] == '+')
+ || (schemeName[i] == '-')
+ || (schemeName[i] == '.'))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ //
+ // IsHexDigit
+ //
+ // Determines whether a character is a valid hexadecimal digit in the range
+ // [0..9] | [A..F] | [a..f]
+ //
+ // Inputs:
+ // <argument> character
+ // Character to test
+ //
+ // Returns:
+ // true if <character> is a hexadecimal digit character
+ //
+ // Throws:
+ // Nothing
+ //
+ public static bool IsHexDigit(char character) {
+ return ((character >= '0') && (character <= '9'))
+ || ((character >= 'A') && (character <= 'F'))
+ || ((character >= 'a') && (character <= 'f'));
+ }
+
+ //
+ // Returns:
+ // Number in the range 0..15
+ //
+ // Throws:
+ // ArgumentException
+ //
+ public static int FromHex(char digit) {
+ if (((digit >= '0') && (digit <= '9'))
+ || ((digit >= 'A') && (digit <= 'F'))
+ || ((digit >= 'a') && (digit <= 'f'))) {
+ return (digit <= '9')
+ ? ((int)digit - (int)'0')
+ : (((digit <= 'F')
+ ? ((int)digit - (int)'A')
+ : ((int)digit - (int)'a'))
+ + 10);
+ }
+ throw new ArgumentException("digit");
+ }
+ //
+ // GetHashCode
+ //
+ // Overrides default function (in Object class)
+ //
+ //
+ [SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.Infrastructure)]
+ public override int GetHashCode() {
+ if (IsNotAbsoluteUri)
+ {
+ return CalculateCaseInsensitiveHashCode(OriginalString);
+ }
+
+ // Consider moving hash code storage from m_Info.MoreInfo to m_Info
+ UriInfo info = EnsureUriInfo();
+ if ((object)info.MoreInfo == null) {
+ info.MoreInfo = new MoreInfo();
+ }
+ int tempHash = info.MoreInfo.Hash;
+ if (tempHash == 0) {
+ string chkString = info.MoreInfo.RemoteUrl;
+ if ((object) chkString == null)
+ chkString = GetParts(UriComponents.HttpRequestUrl, UriFormat.SafeUnescaped);
+ tempHash = CalculateCaseInsensitiveHashCode(chkString);
+ if (tempHash == 0) {
+ tempHash = 0x1000000; //making it not zero still large enough to be maped to zero by a hashtable
+ }
+ info.MoreInfo.Hash = tempHash;
+ }
+ return tempHash;
+ }
+
+ //
+ // ToString
+ //
+ // The better implementation would be just
+ //
+ private const UriFormat V1ToStringUnescape = (UriFormat)0x7FFF;
+
+ [SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.Infrastructure)]
+ public override string ToString()
+ {
+ if (m_Syntax == null) {
+ return (m_iriParsing && InFact(Flags.HasUnicode)) ? m_String : OriginalString;
+ }
+
+ EnsureUriInfo();
+ if ((object)m_Info.String == null)
+ {
+
+ // V1.1 compat unless #353711 is appoved, otheriwse it should be just a call into GetParts() as shown below
+ // m_Info.String = GetParts(UriComponents.AbsoluteUri, UriFormat.SafeUnescaped);
+
+ if (Syntax.IsSimple)
+ m_Info.String = GetComponentsHelper(UriComponents.AbsoluteUri, V1ToStringUnescape);
+ else
+ m_Info.String = GetParts(UriComponents.AbsoluteUri, UriFormat.SafeUnescaped);
+
+ }
+ return m_Info.String;
+ }
+
+ //
+ //
+ // A static shortcut to Uri.Equals
+ //
+ [SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.Infrastructure)]
+ public static bool operator == (Uri uri1, Uri uri2) {
+ if ((object)uri1 == (object)uri2) {
+ return true;
+ }
+ if ((object)uri1 == null || (object)uri2 == null) {
+ return false;
+ }
+ return uri2.Equals(uri1);
+ }
+
+ //
+ //
+ // A static shortcut to !Uri.Equals
+ //
+ [SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.Infrastructure)]
+ public static bool operator != (Uri uri1, Uri uri2) {
+ if ((object)uri1 == (object)uri2) {
+ return false;
+ }
+
+ if ((object)uri1 == null || (object)uri2 == null) {
+ return true;
+ }
+
+ return !uri2.Equals(uri1);
+ }
+
+
+
+ //
+ // Equals
+ //
+ // Overrides default function (in Object class)
+ //
+ // Assumes:
+ // <comparand> is an object of class Uri
+ //
+ // Returns:
+ // true if objects have the same value, else false
+ //
+ // Throws:
+ // Nothing
+ //
+ [SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.Infrastructure)]
+ public override bool Equals(object comparand) {
+ if ((object) comparand == null) {
+ return false;
+ }
+
+ if ((object)this == (object)comparand) {
+ return true;
+ }
+
+ Uri obj = comparand as Uri;
+
+ //
+ // we allow comparisons of Uri and String objects only. If a string
+ // is passed, convert to Uri. This is inefficient, but allows us to
+ // canonicalize the comparand, making comparison possible
+ //
+ if ((object)obj == null) {
+ string s = comparand as string;
+
+ if ((object)s == null)
+ return false;
+
+ if (!TryCreate(s, UriKind.RelativeOrAbsolute, out obj))
+ return false;
+ }
+
+ // Since v1.0 two Uris are equal if everything but fragment and UserInfo does match
+
+ // This check is for a case where we already fixed up the equal references
+ if ((object)this.m_String == (object)obj.m_String) {
+ return true;
+ }
+
+ if (IsAbsoluteUri != obj.IsAbsoluteUri)
+ return false;
+
+ if (IsNotAbsoluteUri)
+ return OriginalString.Equals(obj.OriginalString);
+
+ if (NotAny(Flags.AllUriInfoSet) || obj.NotAny(Flags.AllUriInfoSet)) {
+ // Try raw compare for m_Strings as the last chance to keep the working set small
+ if (!IsUncOrDosPath ) {
+ if (m_String.Length == obj.m_String.Length) {
+ unsafe {
+ // Try case sensitive compare on m_Strings
+ fixed (char* pMe = m_String) {
+ fixed (char* pShe = obj.m_String) {
+ // This will never go negative since m_String is checked to be a valid URI
+ int i = (m_String.Length-1);
+ for ( ;i >= 0 ; --i) {
+ if (*(pMe+i) != *(pShe+i)) {
+ break;
+ }
+ }
+ if (i == -1) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (String.Compare(m_String, obj.m_String, StringComparison.OrdinalIgnoreCase) == 0) {
+ return true;
+ }
+ }
+
+ // Note that equality test will bring the working set of both
+ // objects up to creation of m_Info.MoreInfo member
+ EnsureUriInfo();
+ obj.EnsureUriInfo();
+
+ if (!UserDrivenParsing && !obj.UserDrivenParsing && Syntax.IsSimple && obj.Syntax.IsSimple)
+ {
+ // Optimization of canonical DNS names by avoiding host string creation.
+ // Note there could be explicit ports specified that would invalidate path offsets
+ if (InFact(Flags.CanonicalDnsHost) && obj.InFact(Flags.CanonicalDnsHost)) {
+ ushort i1 = m_Info.Offset.Host;
+ ushort end1 = m_Info.Offset.Path;
+
+ ushort i2 = obj.m_Info.Offset.Host;
+ ushort end2 = obj.m_Info.Offset.Path;
+ string str = obj.m_String;
+ //Taking the shortest part
+ if (end1-i1 > end2-i2) {
+ end1 = (ushort)(i1 + end2-i2);
+ }
+ // compare and break on ':' if found
+ while (i1 < end1) {
+ if (m_String[i1] != str[i2]) {
+ return false;
+ }
+ if (str[i2] == ':') {
+ // The other must have ':' too to have equal host
+ break;
+ }
+ ++i1;++i2;
+ }
+
+ // The longest host must have ':' or be of the same size
+ if (i1 < m_Info.Offset.Path && m_String[i1] != ':') {
+ return false;
+ }
+ if (i2 < end2 && str[i2] != ':') {
+ return false;
+ }
+ //hosts are equal!
+ }
+ else {
+ EnsureHostString(false);
+ obj.EnsureHostString(false);
+ if (!m_Info.Host.Equals(obj.m_Info.Host)) {
+ return false;
+ }
+ }
+
+ if (Port != obj.Port) {
+ return false;
+ }
+ }
+
+ // see Whidbey#21590
+ // We want to cache RemoteUrl to improve perf for Uri as a key.
+ // We should consider reducing the overall working set by not caching some other properties mentioned in MoreInfo
+
+ // Mutli-threading!
+ UriInfo meInfo = m_Info;
+ UriInfo sheInfo = obj.m_Info;
+ if ((object)meInfo.MoreInfo == null) {
+ meInfo.MoreInfo = new MoreInfo();
+ }
+ if ((object)sheInfo.MoreInfo == null) {
+ sheInfo.MoreInfo = new MoreInfo();
+ }
+
+ // NB: To avoid a race condition when creating MoreInfo field
+ // "meInfo" and "sheInfo" shall remain as local copies.
+ string me = meInfo.MoreInfo.RemoteUrl;
+ if ((object)me == null) {
+ me = GetParts(UriComponents.HttpRequestUrl, UriFormat.SafeUnescaped);
+ meInfo.MoreInfo.RemoteUrl = me;
+ }
+ string she = sheInfo.MoreInfo.RemoteUrl;
+ if ((object)she == null) {
+ she = obj.GetParts(UriComponents.HttpRequestUrl, UriFormat.SafeUnescaped);
+ sheInfo.MoreInfo.RemoteUrl = she;
+ }
+
+ if (!IsUncOrDosPath ) {
+ if (me.Length != she.Length) {
+ return false;
+ }
+ unsafe {
+ // Try case sensitive compare on m_Strings
+ fixed (char* pMe = me) {
+ fixed (char* pShe = she) {
+ char *endMe = pMe + me.Length;
+ char *endShe = pShe + me.Length;
+ while (endMe != pMe) {
+ if (*--endMe != *--endShe) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ }
+ }
+
+
+ // if IsUncOrDosPath is true then we ignore case in the path comparison
+ // Get Unescaped form as most safe for the comparison
+ // Fragment AND UserInfo are ignored
+ //
+ return (String.Compare(meInfo.MoreInfo.RemoteUrl,
+ sheInfo.MoreInfo.RemoteUrl,
+ IsUncOrDosPath ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal ) == 0);
+ }
+ //
+ public Uri MakeRelativeUri(Uri uri)
+ {
+ if ((object)uri == null)
+ throw new ArgumentNullException("uri");
+
+ if (IsNotAbsoluteUri || uri.IsNotAbsoluteUri)
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+
+ // Note that the UserInfo part is ignored when computing a relative Uri.
+ if ((Scheme == uri.Scheme) && (Host == uri.Host) && (Port == uri.Port))
+ {
+ String otherPath = uri.AbsolutePath;
+
+ // Relative Path
+ String relativeUriString = PathDifference(AbsolutePath, otherPath, !IsUncOrDosPath);
+
+ // Relative Uri's cannot have a colon ':' in the first path segment (RFC 3986, Section 4.2)
+ if (CheckForColonInFirstPathSegment(relativeUriString)
+ // Except for full implicit dos file paths
+ && !(uri.IsDosPath && otherPath.Equals(relativeUriString, StringComparison.Ordinal)))
+ relativeUriString = "./" + relativeUriString;
+
+ // Query & Fragment
+ relativeUriString += uri.GetParts(UriComponents.Query | UriComponents.Fragment, UriFormat.UriEscaped);
+
+ return new Uri(relativeUriString, UriKind.Relative);
+ }
+ return uri;
+ }
+
+ //
+ // http://www.ietf.org/rfc/rfc3986.txt
+ //
+ // 3.3. Path
+ // In addition, a URI reference (Section 4.1) may be a relative-path reference, in which case the first
+ // path segment cannot contain a colon (":") character.
+ //
+ // 4.2. Relative Reference
+ // A path segment that contains a colon character (e.g., "this:that") cannot be used as the first segment
+ // of a relative-path reference, as it would be mistaken for a scheme name. Such a segment must be
+ // preceded by a dot-segment (e.g., "./this:that") to make a relative-path reference.
+ //
+ // 5.4.2. Abnormal Examples
+ // http:(relativeUri) may be considered a valid relative Uri.
+ //
+ // Returns true if a colon is found in the first path segment, false otherwise
+ //
+ private static bool CheckForColonInFirstPathSegment(String uriString)
+ {
+ // Check for anything that may terminate the first regular path segment
+ // or an illegal colon
+ char[] pathDelims = new char[] { ':', '\\', '/', '?', '#' };
+ int index = uriString.IndexOfAny(pathDelims);
+
+ return (index >= 0 && uriString[index] == ':');
+ }
+ // This is used by UriBuilder,
+
+ internal unsafe static string InternalEscapeString(string rawString) {
+ if ((object)rawString == null)
+ return String.Empty;
+
+ int position = 0;
+ char[] dest = UriHelper.EscapeString(rawString, 0, rawString.Length, null, ref position, true, '?', '#', '%');
+ if ((object)dest == null)
+ return rawString;
+
+ return new string(dest, 0, position);
+ }
+
+ //
+ // This method is called first to figure out the scheme or a simple file path
+ // Is called only at the .ctor time
+ //
+ private static unsafe ParsingError ParseScheme(string uriString, ref Flags flags, ref UriParser syntax)
+ {
+ int length = uriString.Length;
+ if (length == 0)
+ return ParsingError.EmptyUriString;
+
+ // we don;t work with >= 64k Uris
+ if (length >= c_MaxUriBufferSize)
+ return ParsingError.SizeLimit;
+
+ //STEP1: parse scheme, lookup this Uri Syntax or create one using UnknownV1SyntaxFlags uri syntax template
+ fixed (char* pUriString = uriString)
+ {
+ ParsingError err = ParsingError.None;
+ ushort idx = ParseSchemeCheckImplicitFile(pUriString, (ushort)length, ref err, ref flags, ref syntax);
+
+ if (err != ParsingError.None)
+ return err;
+
+ flags |= (Flags)idx;
+ }
+ return ParsingError.None;
+ }
+
+ //
+ // A wrapper for ParseMinimal() called from a user parser
+ // It signals back that the call has been done
+ // plus it communicates back a flag for an error if any
+ //
+ internal UriFormatException ParseMinimal()
+ {
+ ParsingError result = PrivateParseMinimal();
+ if (result == ParsingError.None)
+ return null;
+
+ // Means the we think the Uri is invalid, bu that can be later overriden by a user parser
+ m_Flags |= Flags.ErrorOrParsingRecursion;
+
+ return GetException(result);
+ }
+ //
+ //
+ // This method tries to parse the minimal information needed to certify the valifity
+ // of a uri string
+ //
+ // scheme://userinfo@host:Port/Path?Query#Fragment
+ //
+ // The method must be called only at the .ctor time
+ //
+ // Returns ParsingError.None if the Uri syntax is valid, an error otheriwse
+ //
+ private unsafe ParsingError PrivateParseMinimal()
+ {
+ ushort idx = (ushort) (m_Flags & Flags.IndexMask);
+ ushort length = (ushort) m_String.Length;
+ string newHost = null; // stores newly parsed host when original strings are being switched
+
+ // Means a custom UriParser did call "base" InitializeAndValidate()
+ m_Flags &= ~(Flags.IndexMask | Flags.UserDrivenParsing);
+
+ //STEP2: Parse up to the port
+
+ fixed (char* pUriString = ((m_iriParsing &&
+ ((m_Flags & Flags.HasUnicode)!=0) &&
+ ((m_Flags & Flags.HostUnicodeNormalized) == 0)) ? m_originalUnicodeString : m_String))
+ {
+ // Cut trailing spaces in m_String
+ if (length > idx && IsLWS(pUriString[length-1]))
+ {
+ --length;
+ while (length != idx && IsLWS(pUriString[--length]))
+ ;
+ ++length;
+ }
+
+ // Microsoft codereview:
+ // Old Uri parser tries to figure out on a DosPath in all cases.
+ // Hence http://c:/ is treated as as DosPath without the host while it should be a host "c", port 80
+ //
+ // This block is compatible with Old Uri parser in terms it will look for the DosPath if the scheme
+ // syntax allows both empty hostnames and DosPath
+ //
+#if !PLATFORM_UNIX
+ if (m_Syntax.IsAllSet(UriSyntaxFlags.AllowEmptyHost | UriSyntaxFlags.AllowDOSPath)
+ && NotAny(Flags.ImplicitFile) && (idx + 1 < length)) {
+
+ char c;
+ ushort i = (ushort) idx;
+
+ // V1 Compat: Allow _compression_ of > 3 slashes only for File scheme, see VsWhidbey 87448.
+ // This will skip all slashes and if their number is 2+ it sets the AuthorityFound flag
+ for (; i < length; ++i) {
+ if (!((c=pUriString[i])== '\\' || c == '/'))
+ break;
+ }
+
+ if (m_Syntax.InFact(UriSyntaxFlags.FileLikeUri) || i-idx <= 3) {
+ // if more than one slash after the scheme, the authority is present
+ if (i-idx >= 2) {
+ m_Flags |= Flags.AuthorityFound;
+ }
+ // DOS-like path?
+ if (i+1 < (ushort) length && ((c=pUriString[i+1]) == ':' || c == '|') &&
+ IsAsciiLetter(pUriString[i])) {
+
+ if (i+2 >= (ushort) length || ((c=pUriString[i+2]) != '\\' && c != '/'))
+ {
+ // report an error but only for a file: scheme
+ if (m_Syntax.InFact(UriSyntaxFlags.FileLikeUri))
+ return ParsingError.MustRootedPath;
+ }
+ else
+ {
+ // This will set IsDosPath
+ m_Flags |= Flags.DosPath;
+
+ if (m_Syntax.InFact(UriSyntaxFlags.MustHaveAuthority)) {
+ // when DosPath found and Authority is required, set this flag even if Authority is empty
+ m_Flags |= Flags.AuthorityFound;
+ }
+ if (i != idx && i-idx != 2) {
+ //This will remember that DosPath is rooted
+ idx = (ushort)(i-1);
+ }
+ else {
+ idx = i;
+ }
+ }
+ }
+ else if (m_Syntax.InFact(UriSyntaxFlags.FileLikeUri) && (i - idx >= 2 && i - idx != 3 &&
+ i < length && pUriString[i] != '?' && pUriString[i] != '#'))
+ {
+ // see VsWhidbey#226745 V1.0 did not support file:///, fixing it with minimal behavior change impact
+ // Only FILE scheme may have UNC Path flag set
+ m_Flags |= Flags.UncPath;
+ idx = i;
+ }
+ }
+ }
+#endif // !PLATFORM_UNIX
+ //
+ //STEP 1.5 decide on the Authority component
+ //
+#if !PLATFORM_UNIX
+ if ((m_Flags & (Flags.UncPath|Flags.DosPath)) != 0) {
+ }
+#else
+ if ((m_Flags & Flags.ImplicitFile) != 0) {
+ // Already parsed up to the path
+ }
+#endif // !PLATFORM_UNIX
+ else if ((idx+2) <= length) {
+ char first = pUriString[idx];
+ char second = pUriString[idx+1];
+
+ if (m_Syntax.InFact(UriSyntaxFlags.MustHaveAuthority)) {
+ // (V1.0 compatiblity) This will allow http:\\ http:\/ http:/\
+#if !PLATFORM_UNIX
+ if ((first == '/' || first == '\\') && (second == '/' || second == '\\'))
+#else
+ if (first == '/' && second == '/')
+#endif // !PLATFORM_UNIX
+ {
+ m_Flags |= Flags.AuthorityFound;
+ idx+=2;
+ }
+ else {
+ return ParsingError.BadAuthority;
+ }
+ }
+ else if (m_Syntax.InFact(UriSyntaxFlags.OptionalAuthority) && (InFact(Flags.AuthorityFound) ||
+ (first == '/' && second == '/'))) {
+ m_Flags |= Flags.AuthorityFound;
+ idx+=2;
+ }
+ //
+ else if (m_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 (m_iriParsing && (m_Flags & Flags.HasUnicode) != 0 && (m_Flags & Flags.HostUnicodeNormalized) == 0)
+ {
+ m_String = m_String.Substring(0, idx);
+ }
+ // Since there is no Authority, the path index is just the end of the scheme.
+ m_Flags |= ((Flags)idx | Flags.UnknownHostType);
+ return ParsingError.None;
+ }
+ }
+ else if (m_Syntax.InFact(UriSyntaxFlags.MustHaveAuthority)) {
+ return ParsingError.BadAuthority;
+ }
+ //
+ else if (m_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 (m_iriParsing && (m_Flags & Flags.HasUnicode) != 0 && (m_Flags & Flags.HostUnicodeNormalized) == 0)
+ {
+ m_String = m_String.Substring(0, idx);
+ }
+ // Since there is no Authority, the path index is just the end of the scheme.
+ m_Flags |= ((Flags)idx | Flags.UnknownHostType);
+ return ParsingError.None;
+ }
+
+#if !PLATFORM_UNIX
+ // The following sample taken from the original parser comments makes the whole story sad
+ // vsmacros://c:\path\file
+ // Note that two slashes say there must be an Authority but instead the path goes
+ // Fro V1 compat the next block allow this case but not for schemes like http
+ if (InFact(Flags.DosPath)) {
+
+ m_Flags |= (((m_Flags & Flags.AuthorityFound)!= 0)? Flags.BasicHostType :Flags.UnknownHostType);
+ m_Flags |= (Flags)idx;
+ return ParsingError.None;
+ }
+#endif // !PLATFORM_UNIX
+
+ //STEP 2: Check the syntax of authority expecting at least one character in it
+ //
+ // Note here we do know that there is an authority in the string OR it's a DOS path
+
+ // We may find a userInfo and the port when parsing an authority
+ // Also we may find a registry based authority.
+ // We must ensure that known schemes do use a server-based authority
+ {
+ ParsingError err = ParsingError.None;
+ idx = CheckAuthorityHelper(pUriString, idx, (ushort)length, ref err, ref m_Flags, m_Syntax, ref newHost);
+ if (err != ParsingError.None)
+ return err;
+
+ // This will disallow '\' as the host terminator for any scheme that is not implicitFile or cannot have a Dos Path
+ if ((idx < (ushort)length && pUriString[idx] == '\\') && NotAny(Flags.ImplicitFile) &&
+ m_Syntax.NotAny(UriSyntaxFlags.AllowDOSPath)) {
+ return ParsingError.BadAuthorityTerminator;
+ }
+
+ }
+
+ // The Path (or Port) parsing index is reloaded on demand in CreateUriInfo when accessing a Uri property
+ m_Flags |= (Flags)idx;
+
+ // The rest of the string will be parsed on demand
+ // The Host/Authorty is all checked, the type is known but the host value string
+ // is not created/canonicalized at this point.
+ }
+
+ if((s_IdnScope != UriIdnScope.None) || m_iriParsing)
+ PrivateParseMinimalIri(newHost, idx);
+
+ return ParsingError.None;
+ }
+
+ private void PrivateParseMinimalIri(string newHost, ushort idx)
+ {
+ // we have a new host!
+ if (newHost != null)
+ m_String = newHost;
+
+ // conditions where we dont need to go to parseremaining, so we copy the rest of the
+ // original string.. and switch offsets
+ if ((!m_iriParsing && AllowIdn && (((m_Flags & Flags.IdnHost) != 0) || ((m_Flags & Flags.UnicodeHost) != 0))) ||
+ (m_iriParsing && ((m_Flags & Flags.HasUnicode) == 0) && AllowIdn && ((m_Flags & Flags.IdnHost) != 0))){
+ // update the start of path from the end of new string
+ m_Flags &= ~(Flags.IndexMask);
+ m_Flags |= (Flags)m_String.Length;
+
+ m_String += m_originalUnicodeString.Substring(idx, m_originalUnicodeString.Length - idx);
+ }
+
+ // Indicate to createuriinfo that offset is in m_originalUnicodeString
+ if (m_iriParsing && ((m_Flags & Flags.HasUnicode) != 0)){
+ // offset in Flags.IndexMask refers to m_originalUnicodeString
+ m_Flags |= Flags.UseOrigUncdStrOffset;
+ }
+ }
+
+ //
+ //
+ // The method is called when we have to access m_Info members
+ // This will create the m_Info based on the copied parser context
+ // Under milti-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
+ info.Offset.End = (ushort)m_String.Length;
+
+ if (UserDrivenParsing)
+ goto Done;
+
+ ushort idx;
+ bool notCanonicalScheme = false;
+
+ // The m_String may have leading spaces, figure that out
+ // plus it will set idx value for next steps
+ if ((cF & Flags.ImplicitFile) != 0) {
+ idx = (ushort)0;
+ while (IsLWS(m_String[idx])) {
+ ++idx;
+ ++info.Offset.Scheme;
+ }
+
+#if !PLATFORM_UNIX
+ if (StaticInFact(cF, Flags.UncPath)) {
+ // For implicit file AND Unc only
+ idx += 2;
+ //skip any other slashes (compatibility with V1.0 parser)
+ while(idx < (ushort)(cF & Flags.IndexMask) && (m_String[idx] == '/' || m_String[idx] == '\\')) {
+ ++idx;
+ }
+ }
+#endif // !PLATFORM_UNIX
+ }
+ else {
+ // This is NOT an ImplicitFile uri
+ idx = (ushort)m_Syntax.SchemeName.Length;
+
+ while (m_String[idx++] != ':') {
+ ++info.Offset.Scheme;
+ }
+
+ if ((cF & Flags.AuthorityFound) != 0)
+ {
+ if (m_String[idx] == '\\' || m_String[idx+1] == '\\')
+ notCanonicalScheme = true;
+
+ idx+=2;
+#if !PLATFORM_UNIX
+ if ((cF & (Flags.UncPath|Flags.DosPath)) != 0) {
+ // Skip slashes if it was allowed during ctor time
+ // NB: Today this is only allowed if a Unc or DosPath was found after the scheme
+ while( idx < (ushort)(cF & Flags.IndexMask) && (m_String[idx] == '/' || m_String[idx] == '\\')) {
+ notCanonicalScheme = true;
+ ++idx;
+ }
+ }
+#endif // !PLATFORM_UNIX
+ }
+ }
+
+ // This is weird but some schemes (mailto) do not have Authority-based syntax, still they do have a port
+ if (m_Syntax.DefaultPort != UriParser.NoDefaultPort)
+ info.Offset.PortValue = (ushort)m_Syntax.DefaultPort;
+
+ //Here we set the indexes for already parsed components
+ if ((cF & Flags.HostTypeMask) == Flags.UnknownHostType
+#if !PLATFORM_UNIX
+ || StaticInFact(cF, Flags.DosPath)
+#endif // !PLATFORM_UNIX
+ ) {
+ //there is no Authotity component defined
+ info.Offset.User = (ushort) (cF & Flags.IndexMask);
+ info.Offset.Host = info.Offset.User;
+ info.Offset.Path = info.Offset.User;
+ cF &= ~Flags.IndexMask;
+ if (notCanonicalScheme) {
+ cF |= Flags.SchemeNotCanonical;
+ }
+ goto Done;
+ }
+
+ info.Offset.User = idx;
+
+ //Basic Host Type does not have userinfo and port
+ if (HostType == Flags.BasicHostType) {
+ info.Offset.Host = idx;
+ info.Offset.Path = (ushort) (cF & Flags.IndexMask);
+ cF &= ~Flags.IndexMask;
+ goto Done;
+ }
+
+ if ((cF & Flags.HasUserInfo) != 0) {
+ // we previously found a userinfo, get it again
+ while (m_String[idx] != '@') {
+ ++idx;
+ }
+ ++idx;
+ info.Offset.Host = idx;
+ }
+ else {
+ info.Offset.Host = idx;
+ }
+
+ //Now reload the end of the parsed host
+
+ idx = (ushort) (cF & Flags.IndexMask);
+
+ //From now on we do not need IndexMask bits, and reuse the space for X_NotCanonical flags
+ //clear them now
+ cF &= ~Flags.IndexMask;
+
+ // If this is not canonical, don't count on user input to be good
+ if (notCanonicalScheme) {
+ cF |= Flags.SchemeNotCanonical;
+ }
+
+ //Guessing this is a path start
+ info.Offset.Path = idx;
+
+ // parse Port if any. The new spec allows a port after ':' to be empty (assuming default?)
+ bool notEmpty = false;
+ // 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
+
+ bool UseOrigUnicodeStrOffset = ((cF& Flags.UseOrigUncdStrOffset) != 0);
+ // This should happen only once. Reset it
+ cF &= ~Flags.UseOrigUncdStrOffset;
+
+ if (UseOrigUnicodeStrOffset)
+ info.Offset.End = (ushort)m_originalUnicodeString.Length;
+
+ if (idx < info.Offset.End ){
+ fixed (char* userString = UseOrigUnicodeStrOffset ? m_originalUnicodeString : m_String){
+ if (userString[idx] == ':'){
+ int port = 0;
+
+ //Check on some noncanonical cases http://host:0324/, http://host:03, http://host:0, etc
+ if (++idx < info.Offset.End){
+ port = (ushort)(userString[idx] - '0');
+ if (!(port == unchecked((ushort)('/' - '0')) || port == (ushort)('?' - '0') ||
+ port == unchecked((ushort)('#' - '0')))) {
+ notEmpty = true;
+ if (port == 0){
+ cF |= (Flags.PortNotCanonical | Flags.E_PortNotCanonical);
+ }
+ for (++idx; idx < info.Offset.End; ++idx){
+ ushort val = (ushort)((ushort)userString[idx] - (ushort)'0');
+ if (val == unchecked((ushort)('/' - '0')) || val == (ushort)('?' - '0') ||
+ val == unchecked((ushort)('#' - '0'))){
+ break;
+ }
+ port = (port * 10 + val);
+ }
+ }
+ }
+ if (notEmpty && info.Offset.PortValue != (ushort)port){
+ info.Offset.PortValue = (ushort)port;
+ cF |= Flags.NotDefaultPort;
+ }
+ else{
+ //This will tell that we do have a ':' but the port value does
+ //not follow to canonical rules
+ cF |= (Flags.PortNotCanonical | Flags.E_PortNotCanonical);
+ }
+ info.Offset.Path = (ushort)idx;
+ }
+ }
+ }
+
+ Done:
+ cF |= Flags.MinimalUriInfoSet;
+/*********
+ // The spinlock would be better than below lock but it's too late for Beta2, consider for RTM
+ // Also DON'T forget to check EnsureUriInfo method
+ int copyF = m_Flags;
+ while ((copyF & Flags.MinimalUriInfoSet) == 0)
+ {
+ if (copyF != (copyF = Interlocked.CompareExchange(ref m_Flags, cF | (copyF & ~Flags.IndexMask), copyF))
+ continue;
+ m_Info = info;
+ }
+*********/
+ info.DnsSafeHost = m_DnsSafeHost;
+ lock (m_String)
+ {
+ if (( m_Flags & Flags.MinimalUriInfoSet) == 0)
+ {
+ m_Info = info;
+ m_Flags = (m_Flags & ~Flags.IndexMask) | cF;
+ }
+ }
+
+ }
+
+ //
+ // This will create a Host string. The validity has been already checked
+ //
+ // Assuming: UriInfo memeber is already set at this point
+ private unsafe void CreateHostString() {
+ //
+ // Mutlithrreading!
+ //
+ if (!m_Syntax.IsSimple)
+ {
+ lock (m_Info)
+ {
+ // ATTN: Avoid possible recursion through
+ // CreateHostString->Syntax.GetComponents->Uri.GetComponentsHelper->CreateHostString
+ if (NotAny(Flags.ErrorOrParsingRecursion))
+ {
+ m_Flags |= Flags.ErrorOrParsingRecursion;
+ // Need to get host string through the derived type
+ GetHostViaCustomSyntax();
+ m_Flags &= ~Flags.ErrorOrParsingRecursion;
+ return;
+ }
+ }
+ }
+ Flags flags = m_Flags;
+ string host = CreateHostStringHelper(m_String, m_Info.Offset.Host, m_Info.Offset.Path, ref flags, ref m_Info.ScopeId);
+
+ // now check on canonical host representation
+ if (host.Length != 0)
+ {
+ // An Authority may need escaping except when it's an inet server address
+ //
+ // We do not escape UNC names and will get rid of this type when switching to IDN spec
+ //
+ if (HostType == Flags.BasicHostType) {
+ ushort idx = 0;
+ Check result;
+ fixed (char* pHost = host) {
+ result = CheckCanonical(pHost, ref idx, (ushort)host.Length, c_DummyChar);
+ }
+
+ if ((result & Check.DisplayCanonical) == 0) {
+ // For implicit file the user string must be in perfect display format,
+ // Hence, ignoring complains from CheckCanonical()
+ if (NotAny(Flags.ImplicitFile) || (result & Check.ReservedFound) != 0) {
+ flags |= Flags.HostNotCanonical;
+ }
+ }
+
+ if (InFact(Flags.ImplicitFile) && (result & (Check.ReservedFound | Check.EscapedCanonical)) != 0) {
+ // need to re-escape this host if any escaped sequence was found
+ result &= ~Check.EscapedCanonical;
+ }
+
+ 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
+ flags |= Flags.E_HostNotCanonical;
+ if (NotAny(Flags.UserEscaped))
+ {
+ int position = 0;
+ char[] dest = UriHelper.EscapeString(host, 0, host.Length, null, ref position, true, '?',
+ '#', IsImplicitFile ? c_DummyChar : '%');
+ if ((object)dest != null)
+ host = new string(dest, 0, position);
+ }
+ else {
+ //
+
+ }
+ }
+ }
+ else if (NotAny(Flags.CanonicalDnsHost)){
+ // Check to see if we can take the canonical host string out of m_String
+ if ((object)m_Info.ScopeId != null) {
+ // IPv6 ScopeId is included when serializing a Uri
+ flags |= (Flags.HostNotCanonical | Flags.E_HostNotCanonical);
+ }
+ else {
+ for (int i=0 ; i < host.Length; ++i) {
+ if ((m_Info.Offset.Host + i) >= m_Info.Offset.End ||
+ host[i] != m_String[m_Info.Offset.Host + i]) {
+ flags |= (Flags.HostNotCanonical | Flags.E_HostNotCanonical);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ m_Info.Host = host;
+ lock (m_Info)
+ {
+ m_Flags |= flags;
+ }
+ }
+ //
+ private static string CreateHostStringHelper(string str, ushort idx, ushort end, ref Flags flags, ref string scopeId)
+ {
+ bool loopback = false;
+ string host;
+ switch (flags & Flags.HostTypeMask) {
+
+ case Flags.DnsHostType:
+ host = DomainNameHelper.ParseCanonicalName(str, idx, end, ref loopback);
+ break;
+
+ case Flags.IPv6HostType:
+ //Microsoft codereview
+ // The helper will return [...] string that is not suited for Dns.Resolve()
+ host = IPv6AddressHelper.ParseCanonicalName(str, idx, ref loopback, ref scopeId);
+ break;
+
+ case Flags.IPv4HostType:
+ host = IPv4AddressHelper.ParseCanonicalName(str, idx, end, ref loopback);
+ break;
+
+#if !PLATFORM_UNIX
+ case Flags.UncHostType:
+ host = UncNameHelper.ParseCanonicalName(str, idx, end, ref loopback);
+ break;
+#endif // !PLATFORM_UNIX
+
+ case Flags.BasicHostType:
+#if !PLATFORM_UNIX
+ if (StaticInFact(flags, Flags.DosPath)) {
+ host = string.Empty;
+ }
+ else
+#endif // !PLATFORM_UNIX
+ {
+ // This is for a registry-based authority, not relevant for known schemes
+ host = str.Substring(idx, end-idx);
+ }
+ // A empty host would count for a loopback
+ if (host.Length == 0) {
+ loopback = true;
+ }
+ //there will be no port
+ break;
+
+ case Flags.UnknownHostType:
+ //means the host is *not expected* for this uri type
+ host = string.Empty;
+ break;
+
+ default: //it's a bug
+ throw GetException(ParsingError.BadHostName);
+ }
+
+ if (loopback) {
+ flags |= Flags.LoopbackHost;
+ }
+ return host;
+ }
+ //
+ // Called under lock()
+ //
+ private unsafe void GetHostViaCustomSyntax()
+ {
+ // A multithreading check
+ if (m_Info.Host != null)
+ return;
+
+ string host = m_Syntax.InternalGetComponents(this, UriComponents.Host, UriFormat.UriEscaped);
+
+ // ATTN: Check on whether recursion has not happened
+ if ((object)m_Info.Host == null)
+ {
+ if (host.Length >= c_MaxUriBufferSize)
+ throw GetException(ParsingError.SizeLimit);
+
+ ParsingError err = ParsingError.None;
+ Flags flags = m_Flags & ~Flags.HostTypeMask;
+
+ fixed (char *pHost = host)
+ {
+ string newHost = null;
+ if (CheckAuthorityHelper(pHost, 0, (ushort)host.Length, ref err, ref flags, m_Syntax, ref newHost) !=
+ (ushort)host.Length)
+ {
+ // We cannot parse the entire host string
+ flags &= ~Flags.HostTypeMask;
+ flags |= Flags.UnknownHostType;
+ }
+ }
+
+ if (err != ParsingError.None || (flags & Flags.HostTypeMask) == Flags.UnknownHostType)
+ {
+ // Well, custom parser has returned a not known host type, take it as Basic then.
+ m_Flags = (m_Flags & ~Flags.HostTypeMask) | Flags.BasicHostType;
+ }
+ else
+ {
+ host = CreateHostStringHelper(host, 0, (ushort)host.Length, ref flags, ref m_Info.ScopeId);
+ for (int i=0 ; i < host.Length; ++i) {
+ if ((m_Info.Offset.Host + i) >= m_Info.Offset.End || host[i] != m_String[m_Info.Offset.Host + i]) {
+ m_Flags |= (Flags.HostNotCanonical | Flags.E_HostNotCanonical);
+ break;
+ }
+ }
+ m_Flags = (m_Flags & ~Flags.HostTypeMask) | (flags & Flags.HostTypeMask);
+ }
+ }
+ //
+ // This is a chance for a custom parser to report a different port value
+ //
+ string portStr = m_Syntax.InternalGetComponents(this, UriComponents.StrongPort, UriFormat.UriEscaped);
+ int port = 0;
+ if ((object)portStr == null || portStr.Length == 0)
+ {
+ // It's like no port
+ m_Flags &= ~Flags.NotDefaultPort;
+ m_Flags |= (Flags.PortNotCanonical|Flags.E_PortNotCanonical);
+ m_Info.Offset.PortValue = 0;
+ }
+ else
+ {
+ for (int idx=0; idx < portStr.Length; ++idx)
+ {
+ int val = portStr[idx] - '0';
+ if (val < 0 || val > 9 || (port = (port * 10 + val)) > 0xFFFF)
+ throw new UriFormatException(SR.GetString(SR.net_uri_PortOutOfRange, m_Syntax.GetType().FullName, portStr));
+ }
+ if (port != m_Info.Offset.PortValue)
+ {
+ if (port == m_Syntax.DefaultPort)
+ m_Flags &= ~Flags.NotDefaultPort;
+ else
+ m_Flags |= Flags.NotDefaultPort;
+
+ m_Flags |= (Flags.PortNotCanonical|Flags.E_PortNotCanonical);
+ m_Info.Offset.PortValue = (ushort) port;
+ }
+ }
+ // This must be done as the last thing in this method
+ m_Info.Host = host;
+ }
+ //
+ // An internal shortcut into Uri extenisiblity API
+ //
+ internal string GetParts(UriComponents uriParts, UriFormat formatAs)
+ {
+ return GetComponents(uriParts, formatAs);
+ }
+
+ //
+ //
+ //
+ private string GetEscapedParts(UriComponents uriParts) {
+ // Which Uri parts are not escaped canonically ?
+ // Notice that public UriPart and private Flags must me in Sync so below code can work
+ //
+ ushort nonCanonical = (ushort)(((ushort)m_Flags & ((ushort)Flags.CannotDisplayCanonical<<7)) >> 6);
+ if (InFact(Flags.SchemeNotCanonical)) {
+ nonCanonical |= (ushort)Flags.SchemeNotCanonical;
+ }
+
+ // We keep separate flags for some of path canonicalization facts
+ if ((uriParts & UriComponents.Path) != 0) {
+ if (InFact(Flags.ShouldBeCompressed|Flags.FirstSlashAbsent|Flags.BackslashInPath)) {
+ nonCanonical |= (ushort)Flags.PathNotCanonical;
+ }
+ else if (IsDosPath && m_String[m_Info.Offset.Path + SecuredPathIndex - 1] == '|') {
+ // A rare case of c|\
+ nonCanonical |= (ushort)Flags.PathNotCanonical;
+ }
+ }
+
+ if (((ushort)uriParts & nonCanonical) == 0) {
+ string ret = GetUriPartsFromUserString(uriParts);
+ if ((object)ret != null) {
+ return ret;
+ }
+ }
+
+ return ReCreateParts(uriParts, nonCanonical, UriFormat.UriEscaped);
+ }
+
+ private string GetUnescapedParts(UriComponents uriParts, UriFormat formatAs) {
+ // Which Uri parts are not escaped canonically ?
+ // Notice that public UriComponents and private Uri.Flags must me in Sync so below code can work
+ //
+ ushort nonCanonical = (ushort)((ushort)m_Flags & (ushort)Flags.CannotDisplayCanonical);
+
+ // We keep separate flags for some of path canonicalization facts
+ if ((uriParts & UriComponents.Path) != 0) {
+ if ((m_Flags & (Flags.ShouldBeCompressed|Flags.FirstSlashAbsent|Flags.BackslashInPath)) !=0) {
+ nonCanonical |= (ushort)Flags.PathNotCanonical;
+ }
+ else if (IsDosPath && m_String[m_Info.Offset.Path + SecuredPathIndex - 1] == '|') {
+ // A rare case of c|\
+ nonCanonical |= (ushort)Flags.PathNotCanonical;
+ }
+
+ }
+
+ if (((ushort)uriParts & nonCanonical) == 0) {
+ string ret = GetUriPartsFromUserString(uriParts);
+ if ((object)ret != null) {
+ return ret;
+ }
+ }
+
+ return ReCreateParts(uriParts, nonCanonical, formatAs);
+ }
+
+ //
+ //
+ //
+ private string ReCreateParts(UriComponents parts, ushort nonCanonical, UriFormat formatAs)
+ {
+ // going hard core
+ EnsureHostString(false);
+ string stemp = (parts & UriComponents.Host) == 0? string.Empty: m_Info.Host;
+ // we reserve more space than required because a canonical Ipv6 Host
+ // may take more characteres than in original m_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
+ int count = (m_Info.Offset.End-m_Info.Offset.User) * (formatAs == UriFormat.UriEscaped?12:1);
+ char[] chars = new char[stemp.Length + count + m_Syntax.SchemeName.Length + 3 + 1];
+ count = 0;
+
+ //Scheme and slashes
+ if ((parts & UriComponents.Scheme) != 0) {
+ m_Syntax.SchemeName.CopyTo(0, chars, count, m_Syntax.SchemeName.Length);
+ count += m_Syntax.SchemeName.Length;
+ if (parts != UriComponents.Scheme) {
+ chars[count++] = ':';
+ if (InFact(Flags.AuthorityFound)) {
+ chars[count++] = '/';
+ chars[count++] = '/';
+ }
+ }
+ }
+
+ //UserInfo
+ if ((parts & UriComponents.UserInfo) != 0 && InFact(Flags.HasUserInfo))
+ {
+ if ((nonCanonical & (ushort)UriComponents.UserInfo) != 0) {
+ switch (formatAs) {
+ case UriFormat.UriEscaped:
+ if (NotAny(Flags.UserEscaped))
+ {
+ chars = UriHelper.EscapeString(m_String, m_Info.Offset.User, m_Info.Offset.Host, chars,
+ ref count, true, '?', '#', '%');
+ }
+ else {
+ if (InFact(Flags.E_UserNotCanonical)) {
+ //
+
+ }
+ m_String.CopyTo(m_Info.Offset.User, chars, count, m_Info.Offset.Host - m_Info.Offset.User);
+ count += (m_Info.Offset.Host - m_Info.Offset.User);
+ }
+ break;
+
+ case UriFormat.SafeUnescaped:
+ chars = UriHelper.UnescapeString(m_String, m_Info.Offset.User, m_Info.Offset.Host - 1,
+ chars, ref count, '@', '/', '\\', InFact(Flags.UserEscaped) ? UnescapeMode.Unescape :
+ UnescapeMode.EscapeUnescape, m_Syntax, false);
+ chars[count++] = '@';
+ break;
+
+ case UriFormat.Unescaped:
+ chars = UriHelper.UnescapeString(m_String, m_Info.Offset.User, m_Info.Offset.Host, chars,
+ ref count, c_DummyChar, c_DummyChar, c_DummyChar,
+ UnescapeMode.Unescape | UnescapeMode.UnescapeAll, m_Syntax, false);
+ break;
+
+ default: //V1ToStringUnescape
+ chars = UriHelper.UnescapeString(m_String, m_Info.Offset.User, m_Info.Offset.Host, chars,
+ ref count, c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, m_Syntax,
+ false);
+ break;
+ }
+ }
+ else {
+ UriHelper.UnescapeString(m_String, m_Info.Offset.User, m_Info.Offset.Host, chars, ref count,
+ c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, m_Syntax, false);
+ }
+ if (parts == UriComponents.UserInfo)
+ {
+ //strip '@' delimiter
+ --count;
+ }
+ }
+
+ // Host
+ if ((parts & UriComponents.Host) != 0 && stemp.Length != 0)
+ {
+ UnescapeMode mode;
+ if (formatAs != UriFormat.UriEscaped && HostType == Flags.BasicHostType
+ && (nonCanonical & (ushort)UriComponents.Host) != 0) {
+ // only Basic host could be in the escaped form
+ mode = formatAs == UriFormat.Unescaped
+ ? (UnescapeMode.Unescape | UnescapeMode.UnescapeAll) :
+ (InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape);
+
+ }
+ else {
+ mode = UnescapeMode.CopyOnly;
+ }
+ // NormalizedHost
+ if ((parts & UriComponents.NormalizedHost) != 0)
+ {
+ unsafe
+ {
+ fixed (char* hostPtr = stemp)
+ {
+ bool allAscii = false;
+ bool atLeastOneValidIdn = false;
+ try
+ {
+ // Upconvert any punycode to unicode, xn--pck -> ?
+ stemp = DomainNameHelper.UnicodeEquivalent(
+ hostPtr, 0, stemp.Length, ref allAscii, ref atLeastOneValidIdn);
+ }
+ // The host may be invalid punycode (www.xn--?-pck.com),
+ // but we shouldn't throw after the constructor.
+ catch (UriFormatException) { }
+ }
+ }
+ }
+ chars = UriHelper.UnescapeString(stemp, 0, stemp.Length, chars, ref count, '/', '?', '#', mode,
+ m_Syntax, false);
+
+ // A fix up only for SerializationInfo and IpV6 host with a scopeID
+ if ((parts & UriComponents.SerializationInfoString) != 0 && HostType == Flags.IPv6HostType &&
+ (object)m_Info.ScopeId != null)
+ {
+ m_Info.ScopeId.CopyTo(0, chars, count-1, m_Info.ScopeId.Length);
+ count += m_Info.ScopeId.Length;
+ chars[count-1] = ']';
+ }
+ }
+
+ //Port (always wants a ':' delimiter if got to this method)
+ if ((parts & UriComponents.Port) != 0)
+ {
+ if ((nonCanonical & (ushort)UriComponents.Port) == 0) {
+ //take it from m_String
+ if (InFact(Flags.NotDefaultPort)) {
+ ushort start = m_Info.Offset.Path;
+ while (m_String[--start] != ':') {
+ ;
+ }
+ m_String.CopyTo(start, chars, count, m_Info.Offset.Path - start);
+ count += (m_Info.Offset.Path - start);
+ }
+ else if ((parts & UriComponents.StrongPort) != 0 && m_Syntax.DefaultPort != UriParser.NoDefaultPort) {
+ chars[count++]= ':';
+ stemp = m_Info.Offset.PortValue.ToString(CultureInfo.InvariantCulture);
+ stemp.CopyTo(0, chars, count, stemp.Length);
+ count += stemp.Length;
+ }
+ }
+ else if (InFact(Flags.NotDefaultPort) || ((parts & UriComponents.StrongPort) != 0 &&
+ m_Syntax.DefaultPort != UriParser.NoDefaultPort)) {
+ // recreate string from port value
+ chars[count++]= ':';
+ stemp = m_Info.Offset.PortValue.ToString(CultureInfo.InvariantCulture);
+ stemp.CopyTo(0, chars, count, stemp.Length);
+ count += stemp.Length;
+ }
+ }
+
+ ushort delimiterAwareIndex;
+
+ //Path
+ if ((parts & UriComponents.Path) != 0)
+ {
+ chars = GetCanonicalPath(chars, ref count, formatAs);
+
+ // (possibly strip the leading '/' delimiter)
+ if (parts == UriComponents.Path)
+ {
+ if (InFact(Flags.AuthorityFound) && count !=0 && chars[0] == '/')
+ {
+ delimiterAwareIndex = 1; --count;
+ }
+ else
+ {
+ delimiterAwareIndex = 0;
+ }
+ return count == 0? string.Empty: new string(chars, delimiterAwareIndex, count);
+ }
+ }
+
+ //Query (possibly strip the '?' delimiter)
+ if ((parts & UriComponents.Query) != 0 && m_Info.Offset.Query < m_Info.Offset.Fragment)
+ {
+ delimiterAwareIndex = (ushort)(m_Info.Offset.Query+1);
+ if(parts != UriComponents.Query)
+ chars[count++] = '?'; //see Fragment+1 below
+
+ if ((nonCanonical & (ushort)UriComponents.Query) != 0)
+ {
+ switch (formatAs)
+ {
+ case UriFormat.UriEscaped:
+ //Can Assert IsImplicitfile == false
+ if (NotAny(Flags.UserEscaped))
+ chars = UriHelper.EscapeString(m_String, delimiterAwareIndex, m_Info.Offset.Fragment, chars,
+ ref count, true, '#', c_DummyChar, '%');
+ else
+ {
+ //
+
+
+ UriHelper.UnescapeString(m_String, delimiterAwareIndex, m_Info.Offset.Fragment, chars,
+ ref count, c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, m_Syntax,
+ true);
+ }
+ break;
+
+ case V1ToStringUnescape:
+
+ chars = UriHelper.UnescapeString(m_String, delimiterAwareIndex, m_Info.Offset.Fragment, chars,
+ ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ?
+ UnescapeMode.Unescape : UnescapeMode.EscapeUnescape) | UnescapeMode.V1ToStringFlag,
+ m_Syntax, true);
+ break;
+
+ case UriFormat.Unescaped:
+
+ chars = UriHelper.UnescapeString(m_String, delimiterAwareIndex, m_Info.Offset.Fragment, chars,
+ ref count, '#', c_DummyChar, c_DummyChar,
+ (UnescapeMode.Unescape | UnescapeMode.UnescapeAll), m_Syntax, true);
+ break;
+
+ default: // UriFormat.SafeUnescaped
+
+ chars = UriHelper.UnescapeString(m_String, delimiterAwareIndex, m_Info.Offset.Fragment, chars,
+ ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ?
+ UnescapeMode.Unescape : UnescapeMode.EscapeUnescape), m_Syntax, true);
+ break;
+ }
+ }
+ else
+ {
+ UriHelper.UnescapeString(m_String, delimiterAwareIndex, m_Info.Offset.Fragment, chars, ref count,
+ c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, m_Syntax, true);
+ }
+ }
+
+ //Fragment (possibly strip the '#' delimiter)
+ if ((parts & UriComponents.Fragment) != 0 && m_Info.Offset.Fragment < m_Info.Offset.End)
+ {
+ delimiterAwareIndex = (ushort)(m_Info.Offset.Fragment+1);
+ if(parts != UriComponents.Fragment)
+ chars[count++] = '#'; //see Fragment+1 below
+
+ if ((nonCanonical & (ushort)UriComponents.Fragment) != 0)
+ {
+ switch (formatAs) {
+ case UriFormat.UriEscaped:
+ if (NotAny(Flags.UserEscaped))
+ chars = UriHelper.EscapeString(m_String, delimiterAwareIndex, m_Info.Offset.End, chars,
+ ref count, true, UriParser.ShouldUseLegacyV2Quirks ? '#' : c_DummyChar, c_DummyChar, '%');
+ else
+ {
+ //
+
+
+ UriHelper.UnescapeString(m_String, delimiterAwareIndex, m_Info.Offset.End, chars,
+ ref count, c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, m_Syntax,
+ false);
+ }
+ break;
+
+ case V1ToStringUnescape:
+
+ chars = UriHelper.UnescapeString(m_String, delimiterAwareIndex, m_Info.Offset.End, chars,
+ ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ?
+ UnescapeMode.Unescape : UnescapeMode.EscapeUnescape) | UnescapeMode.V1ToStringFlag,
+ m_Syntax, false);
+ break;
+ case UriFormat.Unescaped:
+
+ chars = UriHelper.UnescapeString(m_String, delimiterAwareIndex, m_Info.Offset.End, chars,
+ ref count, '#', c_DummyChar, c_DummyChar,
+ UnescapeMode.Unescape | UnescapeMode.UnescapeAll, m_Syntax, false);
+ break;
+
+ default: // UriFormat.SafeUnescaped
+
+ chars = UriHelper.UnescapeString(m_String, delimiterAwareIndex, m_Info.Offset.End, chars,
+ ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ?
+ UnescapeMode.Unescape : UnescapeMode.EscapeUnescape), m_Syntax, false);
+ break;
+ }
+ }
+ else
+ {
+ UriHelper.UnescapeString(m_String, delimiterAwareIndex, m_Info.Offset.End, chars, ref count,
+ c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, m_Syntax, false);
+ }
+ }
+
+ return new string(chars, 0, count);
+ }
+
+ //
+ // This method is called only if the user string has a canonical representation
+ // of requested parts
+ //
+ private string GetUriPartsFromUserString(UriComponents uriParts) {
+
+ ushort delimiterAwareIdx;
+
+ switch (uriParts & ~UriComponents.KeepDelimiter) {
+ // For FindServicePoint perf
+ case UriComponents.Scheme | UriComponents.Host | UriComponents.Port:
+ if (!InFact(Flags.HasUserInfo))
+ return m_String.Substring(m_Info.Offset.Scheme, m_Info.Offset.Path - m_Info.Offset.Scheme);
+
+ return m_String.Substring(m_Info.Offset.Scheme, m_Info.Offset.User - m_Info.Offset.Scheme)
+ + m_String.Substring(m_Info.Offset.Host, m_Info.Offset.Path - m_Info.Offset.Host);
+
+ // For HttpWebRequest.ConnectHostAndPort perf
+ case UriComponents.HostAndPort: //Host|StrongPort
+
+ if (!InFact(Flags.HasUserInfo))
+ goto case UriComponents.StrongAuthority;
+
+ if (InFact(Flags.NotDefaultPort) || m_Syntax.DefaultPort == UriParser.NoDefaultPort)
+ return m_String.Substring(m_Info.Offset.Host, m_Info.Offset.Path - m_Info.Offset.Host);
+
+ return m_String.Substring(m_Info.Offset.Host, m_Info.Offset.Path - m_Info.Offset.Host)
+ + ':' + m_Info.Offset.PortValue.ToString(CultureInfo.InvariantCulture);
+
+ // For an obvious common case perf
+ case UriComponents.AbsoluteUri: //Scheme|UserInfo|Host|Port|Path|Query|Fragment,
+ if (m_Info.Offset.Scheme == 0 && m_Info.Offset.End == m_String.Length)
+ return m_String;
+
+ return m_String.Substring(m_Info.Offset.Scheme, m_Info.Offset.End - m_Info.Offset.Scheme);
+
+ // For Uri.Equals() and HttpWebRequest through a proxy perf
+ case UriComponents.HttpRequestUrl: //Scheme|Host|Port|Path|Query,
+ if (InFact(Flags.HasUserInfo)) {
+ return m_String.Substring(m_Info.Offset.Scheme, m_Info.Offset.User - m_Info.Offset.Scheme)
+ + m_String.Substring(m_Info.Offset.Host, m_Info.Offset.Fragment - m_Info.Offset.Host);
+ }
+ if (m_Info.Offset.Scheme == 0 && m_Info.Offset.Fragment == m_String.Length)
+ return m_String;
+
+ return m_String.Substring(m_Info.Offset.Scheme, m_Info.Offset.Fragment - m_Info.Offset.Scheme);
+
+ // For CombineUri() perf
+ case UriComponents.SchemeAndServer|UriComponents.UserInfo:
+ return m_String.Substring(m_Info.Offset.Scheme, m_Info.Offset.Path - m_Info.Offset.Scheme);
+
+ // For Cache perf
+ case (UriComponents.AbsoluteUri & ~UriComponents.Fragment):
+ if (m_Info.Offset.Scheme == 0 && m_Info.Offset.Fragment == m_String.Length)
+ return m_String;
+
+ return m_String.Substring(m_Info.Offset.Scheme, m_Info.Offset.Fragment - m_Info.Offset.Scheme);
+
+
+ // Strip scheme delimiter if was not requested
+ case UriComponents.Scheme:
+ if (uriParts != UriComponents.Scheme)
+ return m_String.Substring(m_Info.Offset.Scheme, m_Info.Offset.User - m_Info.Offset.Scheme);
+
+ return m_Syntax.SchemeName;
+
+ // KeepDelimiter makes no sense for this component
+ case UriComponents.Host:
+ ushort idx = m_Info.Offset.Path;
+ if (InFact(Flags.NotDefaultPort|Flags.PortNotCanonical)) {
+ //Means we do have ':' after the host
+ while (m_String[--idx] != ':')
+ ;
+ }
+ return (idx - m_Info.Offset.Host == 0)? string.Empty: m_String.Substring(m_Info.Offset.Host,
+ idx - m_Info.Offset.Host);
+
+ case UriComponents.Path:
+
+ // Strip the leading '/' for a hierarchical URI if no delimiter was requested
+ if (uriParts == UriComponents.Path && InFact(Flags.AuthorityFound) &&
+ m_Info.Offset.End > m_Info.Offset.Path && m_String[m_Info.Offset.Path] == '/')
+ delimiterAwareIdx = (ushort)(m_Info.Offset.Path + 1);
+ else
+ delimiterAwareIdx = m_Info.Offset.Path;
+
+ if (delimiterAwareIdx >= m_Info.Offset.Query)
+ return string.Empty;
+
+
+ return m_String.Substring(delimiterAwareIdx, m_Info.Offset.Query - delimiterAwareIdx);
+
+ case UriComponents.Query:
+ // Strip the '?' if no delimiter was requested
+ if (uriParts == UriComponents.Query)
+ delimiterAwareIdx = (ushort)(m_Info.Offset.Query + 1);
+ else
+ delimiterAwareIdx = m_Info.Offset.Query;
+
+ if (delimiterAwareIdx >= m_Info.Offset.Fragment)
+ return string.Empty;
+
+ return m_String.Substring(delimiterAwareIdx, m_Info.Offset.Fragment - delimiterAwareIdx);
+
+ case UriComponents.Fragment:
+ // Strip the '#' if no delimiter was requested
+ if (uriParts == UriComponents.Fragment)
+ delimiterAwareIdx = (ushort)(m_Info.Offset.Fragment + 1);
+ else
+ delimiterAwareIdx = m_Info.Offset.Fragment;
+
+ if (delimiterAwareIdx >= m_Info.Offset.End)
+ return string.Empty;
+
+ return m_String.Substring(delimiterAwareIdx, m_Info.Offset.End - delimiterAwareIdx);
+
+ case UriComponents.UserInfo | UriComponents.Host | UriComponents.Port:
+ return (m_Info.Offset.Path - m_Info.Offset.User == 0) ? string.Empty :
+ m_String.Substring(m_Info.Offset.User, m_Info.Offset.Path - m_Info.Offset.User);
+
+ case UriComponents.StrongAuthority: //UserInfo|Host|StrongPort
+ if (InFact(Flags.NotDefaultPort) || m_Syntax.DefaultPort == UriParser.NoDefaultPort)
+ goto case UriComponents.UserInfo | UriComponents.Host | UriComponents.Port;
+
+ return m_String.Substring(m_Info.Offset.User, m_Info.Offset.Path - m_Info.Offset.User)
+ + ':' + m_Info.Offset.PortValue.ToString(CultureInfo.InvariantCulture);
+
+ case UriComponents.PathAndQuery: //Path|Query,
+ return m_String.Substring(m_Info.Offset.Path, m_Info.Offset.Fragment - m_Info.Offset.Path);
+
+ case UriComponents.HttpRequestUrl|UriComponents.Fragment: //Scheme|Host|Port|Path|Query|Fragment,
+ if (InFact(Flags.HasUserInfo)) {
+ return m_String.Substring(m_Info.Offset.Scheme, m_Info.Offset.User - m_Info.Offset.Scheme)
+ + m_String.Substring(m_Info.Offset.Host, m_Info.Offset.End - m_Info.Offset.Host);
+ }
+ if (m_Info.Offset.Scheme == 0 && m_Info.Offset.End == m_String.Length)
+ return m_String;
+
+ return m_String.Substring(m_Info.Offset.Scheme, m_Info.Offset.End - m_Info.Offset.Scheme);
+
+ case UriComponents.PathAndQuery|UriComponents.Fragment: //LocalUrl|Fragment
+ return m_String.Substring(m_Info.Offset.Path, m_Info.Offset.End - m_Info.Offset.Path);
+
+ case UriComponents.UserInfo:
+ // Strip the '@' if no delimiter was requested
+
+ if (NotAny(Flags.HasUserInfo))
+ return string.Empty;
+
+ if (uriParts == UriComponents.UserInfo)
+ delimiterAwareIdx = (ushort)(m_Info.Offset.Host - 1);
+ else
+ delimiterAwareIdx = m_Info.Offset.Host;
+
+ if (m_Info.Offset.User >= delimiterAwareIdx)
+ return string.Empty;
+
+ return m_String.Substring(m_Info.Offset.User, delimiterAwareIdx - m_Info.Offset.User);
+
+ default:
+ return null;
+ }
+ }
+
+
+ //
+ //This method does:
+ // - Creates m_Info member
+ // - checks all componenets up to path on their canonical representation
+ // - continues parsing starting the path position
+ // - Sets the offsets of remaining components
+ // - Sets the Canonicalization flags if applied
+ // - Will NOT create MoreInfo members
+ //
+ private unsafe void ParseRemaining() {
+
+ // ensure we parsed up to the path
+ EnsureUriInfo();
+
+ Flags cF = Flags.Zero;
+
+ if (UserDrivenParsing)
+ goto Done;
+
+ // Do we have to continue building Iri'zed string from original string
+ bool buildIriStringFromPath = m_iriParsing && ((m_Flags & Flags.HasUnicode) != 0) && ((m_Flags & Flags.RestUnicodeNormalized) == 0);
+
+ ushort origIdx; // stores index to switched original string
+ ushort idx = m_Info.Offset.Scheme;
+ ushort length = (ushort)m_String.Length;
+ Check result = Check.None;
+ UriSyntaxFlags syntaxFlags = m_Syntax.Flags; // perf
+
+ // Multithreading!
+ // m_Info.Offset values may be parsed twice but we lock only on m_Flags update.
+
+ fixed (char* str = m_String){
+ // Cut trailing spaces in m_String
+ if (length > idx && IsLWS(str[length - 1]))
+ {
+ --length;
+ while (length != idx && IsLWS(str[--length]))
+ ;
+ ++length;
+ }
+
+ if (IsImplicitFile){
+ cF |= Flags.SchemeNotCanonical;
+ }
+ else {
+ ushort i = 0;
+ ushort syntaxLength = (ushort)m_Syntax.SchemeName.Length;
+ for (; i < syntaxLength; ++i)
+ {
+ if (m_Syntax.SchemeName[i] != str[idx + i])
+ cF |= Flags.SchemeNotCanonical;
+ }
+ // For an authority Uri only // after the scheme would be canonical
+ // (compatibility bug http:\\host)
+ if (((m_Flags & Flags.AuthorityFound) != 0) && (idx + i + 3 >= length || str[idx + i + 1] != '/' ||
+ str[idx + i + 2] != '/'))
+ {
+ cF |= Flags.SchemeNotCanonical;
+ }
+ }
+
+
+ //Check the form of the user info
+ if ((m_Flags & Flags.HasUserInfo) != 0){
+ idx = m_Info.Offset.User;
+ result = CheckCanonical(str, ref idx, m_Info.Offset.Host, '@');
+ if ((result & Check.DisplayCanonical) == 0){
+ cF |= Flags.UserNotCanonical;
+ }
+ if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical){
+ cF |= Flags.E_UserNotCanonical;
+ }
+ if (m_iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
+ | Check.FoundNonAscii | Check.NotIriCanonical))
+ == (Check.DisplayCanonical | Check.FoundNonAscii))){
+ cF |= Flags.UserIriCanonical;
+ }
+ }
+ }
+ //
+ // Delay canonical Host checking to avoid creation of a host string
+ // Will do that on demand.
+ //
+
+
+ //
+ //We have already checked on the port in EnsureUriInfo() that calls CreateUriInfo
+ //
+
+ //
+ // 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
+
+ idx = m_Info.Offset.Path;
+ origIdx = m_Info.Offset.Path;
+
+ //Some uris do not have a query
+ // When '?' is passed as delimiter, then it's special case
+ // 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.
+ if (IsDosPath) {
+ if (IsImplicitFile) {
+ m_String = String.Empty;
+ }
+ else {
+ m_String = m_Syntax.SchemeName + SchemeDelimiter;
+ }
+ }
+
+ m_Info.Offset.Path = (ushort)m_String.Length;
+ idx = m_Info.Offset.Path;
+
+ ushort offset = origIdx;
+ if (IsImplicitFile || ((syntaxFlags & (UriSyntaxFlags.MayHaveQuery | UriSyntaxFlags.MayHaveFragment)) == 0)){
+ FindEndOfComponent(m_originalUnicodeString, ref origIdx, (ushort)m_originalUnicodeString.Length, c_DummyChar);
+ }
+ else{
+ FindEndOfComponent(m_originalUnicodeString, ref origIdx, (ushort)m_originalUnicodeString.Length,
+ (m_Syntax.InFact(UriSyntaxFlags.MayHaveQuery) ? '?' : m_Syntax.InFact(UriSyntaxFlags.MayHaveFragment) ? '#' : c_EOL));
+ }
+
+ // Correctly escape unescape
+ string escapedPath = EscapeUnescapeIri(m_originalUnicodeString, offset, origIdx, UriComponents.Path);
+
+ // Normalize path
+ try{
+ if (UriParser.ShouldUseLegacyV2Quirks)
+ m_String += escapedPath.Normalize(NormalizationForm.FormC);
+ else
+ m_String += escapedPath;
+ }
+ catch (ArgumentException){
+ UriFormatException e = GetException(ParsingError.BadFormat);
+ throw e;
+ }
+
+ if (!ServicePointManager.AllowAllUriEncodingExpansion && m_String.Length > ushort.MaxValue){
+ UriFormatException e = GetException(ParsingError.SizeLimit);
+ throw e;
+ }
+
+ length = (ushort)m_String.Length;
+ }
+
+ fixed (char* str = m_String){
+ if (IsImplicitFile || ((syntaxFlags & (UriSyntaxFlags.MayHaveQuery | UriSyntaxFlags.MayHaveFragment)) == 0)){
+ result = CheckCanonical(str, ref idx, length, c_DummyChar);
+ }
+ else {
+ result = CheckCanonical(str, ref idx, length, (((syntaxFlags & UriSyntaxFlags.MayHaveQuery) != 0)
+ ? '?' : m_Syntax.InFact(UriSyntaxFlags.MayHaveFragment) ? '#' : c_EOL));
+ }
+
+ // ATTN:
+ // This may render problems for unknown schemes, but in general for an authority based Uri
+ // (that has slashes) a path should start with "/"
+ // This becomes more interesting knowning how a file uri is used in "file://c:/path"
+ // It will be converted to file:///c:/path
+ //
+ // However, even more interesting is that vsmacros://c:\path will not add the third slash in the _canoical_ case
+ // (vsmacros inventors have violated the RFC)
+ //
+ // We use special syntax flag to check if the path is rooted, i.e. has a first slash
+ //
+ if (((m_Flags & Flags.AuthorityFound) != 0) && ((syntaxFlags & UriSyntaxFlags.PathIsRooted) != 0)
+ && (m_Info.Offset.Path == length || (str[m_Info.Offset.Path] != '/' && str[m_Info.Offset.Path] != '\\'))){
+ cF |= Flags.FirstSlashAbsent;
+ }
+ }
+ // Check the need for compression or backslashes conversion
+ // we included IsDosPath since it may come with other than FILE uri, for ex. scheme://C:\path
+ // (This is very unfortunate that the original design has included that feature)
+ bool nonCanonical = false;
+ if (IsDosPath || (((m_Flags & Flags.AuthorityFound) != 0) &&
+ (((syntaxFlags & (UriSyntaxFlags.CompressPath | UriSyntaxFlags.ConvertPathSlashes)) != 0) ||
+ m_Syntax.InFact(UriSyntaxFlags.UnEscapeDotsAndSlashes))))
+ {
+ if (((result & Check.DotSlashEscaped) != 0) && m_Syntax.InFact(UriSyntaxFlags.UnEscapeDotsAndSlashes))
+ {
+ cF |= (Flags.E_PathNotCanonical | Flags.PathNotCanonical);
+ nonCanonical = true;
+ }
+
+ if (((syntaxFlags & (UriSyntaxFlags.ConvertPathSlashes)) != 0) && (result & Check.BackslashInPath) != 0){
+ cF |= (Flags.E_PathNotCanonical | Flags.PathNotCanonical);
+ nonCanonical = true;
+ }
+
+ if (((syntaxFlags & (UriSyntaxFlags.CompressPath)) != 0) && ((cF & Flags.E_PathNotCanonical) != 0 ||
+ (result & Check.DotSlashAttn) != 0))
+ {
+ cF |= Flags.ShouldBeCompressed;
+ }
+
+ if ((result & Check.BackslashInPath) != 0)
+ cF |= Flags.BackslashInPath;
+
+ }
+ else if ((result & Check.BackslashInPath) != 0){
+ // for a "generic" path '\' should be escaped
+ cF |= Flags.E_PathNotCanonical;
+ nonCanonical = true;
+ }
+
+ if ((result & Check.DisplayCanonical) == 0){
+ // For implicit file the user string is usually in perfect display format,
+ // Hence, ignoring complains from CheckCanonical()
+ //
+
+ if (((m_Flags & Flags.ImplicitFile) == 0) || ((m_Flags & Flags.UserEscaped) != 0) ||
+ (result & Check.ReservedFound) != 0) {
+ //means it's found as escaped or has unescaped Reserved Characters
+ cF |= Flags.PathNotCanonical;
+ nonCanonical = true;
+ }
+ }
+
+ if (((m_Flags & Flags.ImplicitFile) != 0) && (result & (Check.ReservedFound | Check.EscapedCanonical)) != 0){
+ // need to escape reserved chars or re-escape '%' if an "escaped sequence" was found
+ result &= ~Check.EscapedCanonical;
+ }
+
+ if ((result & Check.EscapedCanonical) == 0){
+ //means it's found as not completely escaped
+ cF |= Flags.E_PathNotCanonical;
+ }
+
+ if (m_iriParsing && !nonCanonical & ((result & (Check.DisplayCanonical | Check.EscapedCanonical
+ | Check.FoundNonAscii | Check.NotIriCanonical))
+ == (Check.DisplayCanonical | Check.FoundNonAscii))){
+ cF |= Flags.PathIriCanonical;
+ }
+
+ //
+ //Now we've got to parse the Query if any. Note that Query requires the presence of '?'
+ //
+ if (buildIriStringFromPath){
+ ushort offset = origIdx;
+
+ if (origIdx < m_originalUnicodeString.Length && m_originalUnicodeString[origIdx] == '?'){
+ ++origIdx; // This is to exclude first '?' character from checking
+ FindEndOfComponent(m_originalUnicodeString, ref origIdx, (ushort)m_originalUnicodeString.Length, ((syntaxFlags &(UriSyntaxFlags.MayHaveFragment)) != 0) ? '#' : c_EOL);
+
+ // Correctly escape unescape
+ string escapedPath = EscapeUnescapeIri(m_originalUnicodeString, offset, origIdx, UriComponents.Query);
+
+ // Normalize path
+ try{
+ if (UriParser.ShouldUseLegacyV2Quirks)
+ m_String += escapedPath.Normalize(NormalizationForm.FormC);
+ else
+ m_String += escapedPath;
+ }
+ catch (ArgumentException){
+ UriFormatException e = GetException(ParsingError.BadFormat);
+ throw e;
+ }
+
+ if (!ServicePointManager.AllowAllUriEncodingExpansion && m_String.Length > ushort.MaxValue){
+ UriFormatException e = GetException(ParsingError.SizeLimit);
+ throw e;
+ }
+
+ length = (ushort)m_String.Length;
+ }
+ }
+
+ m_Info.Offset.Query = idx;
+
+ fixed (char* str = m_String){
+ if (idx < length && str[idx] == '?'){
+ ++idx; // This is to exclude first '?' character from checking
+ result = CheckCanonical(str, ref idx, length, ((syntaxFlags & (UriSyntaxFlags.MayHaveFragment)) != 0)
+ ? '#' : c_EOL);
+ if ((result & Check.DisplayCanonical) == 0){
+ cF |= Flags.QueryNotCanonical;
+ }
+
+ if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical){
+ cF |= Flags.E_QueryNotCanonical;
+ }
+
+ if (m_iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
+ | Check.FoundNonAscii | Check.NotIriCanonical))
+ == (Check.DisplayCanonical | Check.FoundNonAscii))){
+ cF |= Flags.QueryIriCanonical;
+ }
+
+ }
+ }
+ //
+ //Now we've got to parse the Fragment if any. Note that Fragment requires the presense of '#'
+ //
+ if (buildIriStringFromPath){
+ ushort offset = origIdx;
+
+ if (origIdx < m_originalUnicodeString.Length && m_originalUnicodeString[origIdx] == '#')
+ {
+ ++origIdx; // This is to exclude first '#' character from checking
+ FindEndOfComponent(m_originalUnicodeString, ref origIdx, (ushort)m_originalUnicodeString.Length, c_EOL);
+
+ // Correctly escape unescape
+ string escapedPath = EscapeUnescapeIri(m_originalUnicodeString, offset, origIdx, UriComponents.Fragment);
+
+ // Normalize path
+ try{
+ if (UriParser.ShouldUseLegacyV2Quirks)
+ m_String += escapedPath.Normalize(NormalizationForm.FormC);
+ else
+ m_String += escapedPath;
+ }
+ catch (ArgumentException){
+ UriFormatException e = GetException(ParsingError.BadFormat);
+ throw e;
+ }
+
+ if (!ServicePointManager.AllowAllUriEncodingExpansion && m_String.Length > ushort.MaxValue){
+ UriFormatException e = GetException(ParsingError.SizeLimit);
+ throw e;
+ }
+
+ length = (ushort)m_String.Length;
+ }
+ }
+
+ m_Info.Offset.Fragment = idx;
+
+ fixed (char* str = m_String){
+ if (idx < length && str[idx] == '#'){
+ ++idx; // This is to exclude first '#' character from checking
+ //We don't using c_DummyChar since want to allow '?' and '#' as unescaped
+ result = CheckCanonical(str, ref idx, length, c_EOL);
+ if ((result & Check.DisplayCanonical) == 0){
+ cF |= Flags.FragmentNotCanonical;
+ }
+
+ if ((result & (Check.EscapedCanonical | Check.BackslashInPath)) != Check.EscapedCanonical){
+ cF |= Flags.E_FragmentNotCanonical;
+ }
+
+ if (m_iriParsing && ((result & (Check.DisplayCanonical | Check.EscapedCanonical | Check.BackslashInPath
+ | Check.FoundNonAscii | Check.NotIriCanonical))
+ == (Check.DisplayCanonical | Check.FoundNonAscii))){
+ cF |= Flags.FragmentIriCanonical;
+ }
+
+ }
+ }
+ m_Info.Offset.End = idx;
+ Done:
+
+ cF |= Flags.AllUriInfoSet;
+ lock (m_Info)
+ {
+ m_Flags |= cF;
+ }
+ m_Flags |= Flags.RestUnicodeNormalized;
+ }
+
+ //
+ //
+ // verifies the syntax of the scheme part
+ // Checks on implicit File: scheme due to simple Dos/Unc path passed
+ // returns the start of the next component position
+ // throws UriFormatException if invalid scheme
+ //
+ unsafe static private ushort ParseSchemeCheckImplicitFile(char *uriString, ushort length,
+ ref ParsingError err, ref Flags flags, ref UriParser syntax) {
+
+ ushort idx = 0;
+
+ //skip whitespaces
+ while(idx < length && IsLWS(uriString[idx])) {
+ ++idx;
+ }
+
+ // sets the recognizer for well known registered schemes
+ // file, ftp, http, https, uuid, etc
+ // Note that we don't support one-letter schemes that will be put into a DOS path bucket
+
+ //
+ ushort end = idx;
+ while (end < length && uriString[end] != ':') {
+ ++end;
+ }
+
+ // NB: On 64-bits we will use less optimized code from CheckSchemeSyntax()
+ //
+ if (IntPtr.Size == 4) {
+ // long = 4chars: The minimal size of a known scheme is 2 + ':'
+ if (end != length && end >= idx+2 &&
+ CheckKnownSchemes((long*) (uriString + idx), (ushort)(end-idx), ref syntax)) {
+ return (ushort)(end+1);
+ }
+ }
+
+ //NB: A string must have at least 3 characters and at least 1 before ':'
+ if (idx+2 >= length || end == idx) {
+ err = ParsingError.BadFormat;
+ return 0;
+ }
+
+ //Check for supported special cases like a DOS file path OR a UNC share path
+ //NB: A string may not have ':' if this is a UNC path
+ {
+ char c;
+ if ((c=uriString[idx+1]) == ':' || c == '|') {
+#if !PLATFORM_UNIX
+ //DOS-like path?
+ if (IsAsciiLetter(uriString[idx])) {
+ if((c=uriString[idx+2]) == '\\' || c== '/') {
+ flags |= (Flags.DosPath|Flags.ImplicitFile|Flags.AuthorityFound);
+ syntax = UriParser.FileUri;
+ return idx;
+ }
+ err = ParsingError.MustRootedPath;
+ return 0;
+ }
+#endif // !PLATFORM_UNIX
+ if (c == ':')
+ err = ParsingError.BadScheme;
+ else
+ err = ParsingError.BadFormat;
+ return 0;
+ }
+#if !PLATFORM_UNIX
+ else if ((c=uriString[idx]) == '/' || c == '\\') {
+ //UNC share ?
+ if ((c=uriString[idx+1]) == '\\' || c == '/') {
+ flags |= (Flags.UncPath|Flags.ImplicitFile|Flags.AuthorityFound);
+ syntax = UriParser.FileUri;
+ idx+=2;
+ // V1.1 compat this will simply eat any slashes prepended to a UNC path
+ while (idx < length && ((c=uriString[idx]) == '/' || c == '\\'))
+ ++idx;
+
+ return idx;
+ }
+ err = ParsingError.BadFormat;
+ return 0;
+ }
+#else
+ else if (uriString[idx] == '/') {
+ // On UNIX an implicit file has the form /<path> or scheme:///<path>
+ if (idx == 0 || uriString[idx-1] != ':' ) {
+ // No scheme present; implicit /<path> starting at idx
+ flags |= (Flags.ImplicitFile|Flags.AuthorityFound);
+ syntax = UriParser.FileUri;
+ return idx;
+ } else if (uriString[idx+1] == '/' && uriString[idx+2] == '/') {
+ // scheme present; rooted path starts at idx + 2
+ flags |= (Flags.ImplicitFile|Flags.AuthorityFound);
+ syntax = UriParser.FileUri;
+ idx+=2;
+ return idx;
+ }
+ }
+ else if (uriString[idx] == '\\') {
+ err = ParsingError.BadFormat;
+ return 0;
+ }
+#endif // !PLATFORM_UNIX
+ }
+
+ if (end == length) {
+ err = ParsingError.BadFormat;
+ return 0;
+ }
+
+ // Here could be a possibly valid, and not well-known scheme
+ // Finds the scheme delimiter
+ // we don;t work with the schemes names > c_MaxUriSchemeName (should be ~1k)
+ if ((end-idx) > c_MaxUriSchemeName) {
+ err = ParsingError.SchemeLimit;
+ return 0;
+ }
+
+ //Check the syntax, canonicalize and avoid a GC call
+ char* schemePtr = stackalloc char[end-idx];
+ for (length = 0; idx < end; ++idx) {
+ schemePtr[length++] = uriString[idx];
+ }
+ err = CheckSchemeSyntax(schemePtr, length, ref syntax);
+ if (err != ParsingError.None) {
+ return 0;
+ }
+ return (ushort)(end+1);
+ }
+ //
+ // Quickly parses well known schemes.
+ // nChars does not include the last ':'. Assuming there is one at the end of passed buffer
+ unsafe static private bool CheckKnownSchemes(long *lptr, ushort nChars, ref UriParser syntax) {
+ //NOTE beware of too short input buffers!
+
+ const long _HTTP_Mask0 = 'h'|('t'<<16)|((long)'t'<<32)|((long)'p'<<48);
+ const char _HTTPS_Mask1 = 's';
+ const int _WS_Mask = 'w'|('s'<<16);
+ const long _WSS_Mask = 'w'|('s'<<16)|((long)'s'<<32)|((long)':'<<48);
+ const long _FTP_Mask = 'f'|('t'<<16)|((long)'p'<<32)|((long)':'<<48);
+ const long _FILE_Mask0 = 'f'|('i'<<16)|((long)'l'<<32)|((long)'e'<<48);
+ const long _GOPHER_Mask0 = 'g'|('o'<<16)|((long)'p'<<32)|((long)'h'<<48);
+ const int _GOPHER_Mask1 = 'e'|('r'<<16);
+ const long _MAILTO_Mask0 = 'm'|('a'<<16)|((long)'i'<<32)|((long)'l'<<48);
+ const int _MAILTO_Mask1 = 't'|('o'<<16);
+ const long _NEWS_Mask0 = 'n'|('e'<<16)|((long)'w'<<32)|((long)'s'<<48);
+ const long _NNTP_Mask0 = 'n'|('n'<<16)|((long)'t'<<32)|((long)'p'<<48);
+ const long _UUID_Mask0 = 'u'|('u'<<16)|((long)'i'<<32)|((long)'d'<<48);
+
+ const long _TELNET_Mask0 = 't'|('e'<<16)|((long)'l'<<32)|((long)'n'<<48);
+ const int _TELNET_Mask1 = 'e'|('t'<<16);
+
+ const long _NETXXX_Mask0 = 'n'|('e'<<16)|((long)'t'<<32)|((long)'.'<<48);
+ const long _NETTCP_Mask1 = 't'|('c'<<16)|((long)'p'<<32)|((long)':'<<48);
+ const long _NETPIPE_Mask1 = 'p'|('i'<<16)|((long)'p'<<32)|((long)'e'<<48);
+
+ const long _LDAP_Mask0 = 'l'|('d'<<16)|((long)'a'<<32)|((long)'p'<<48);
+
+
+ const long _LOWERCASE_Mask = 0x0020002000200020L;
+ const int _INT_LOWERCASE_Mask = 0x00200020;
+
+ if (nChars == 2) {
+ // This is the only known scheme of length 2
+ if ((((int)*lptr) | _INT_LOWERCASE_Mask) == _WS_Mask) {
+ syntax = UriParser.WsUri;
+ return true;
+ }
+ return false;
+ }
+
+ //Map to a known scheme if possible
+ //upgrade 4 letters to ASCII lower case, keep a false case to stay false
+ switch (*lptr | _LOWERCASE_Mask) {
+ case _HTTP_Mask0:
+ if (nChars == 4) {
+ syntax = UriParser.HttpUri;
+ return true;
+ }
+ if (nChars == 5 && ((*(char*)(lptr+1))|0x20) == _HTTPS_Mask1) {
+ syntax = UriParser.HttpsUri;
+ return true;
+ }
+ break;
+ case _WSS_Mask:
+ if (nChars == 3)
+ {
+ syntax = UriParser.WssUri;
+ return true;
+ }
+ break;
+ case _FILE_Mask0:
+ if (nChars == 4) {
+ syntax = UriParser.FileUri;
+ return true;
+ }
+ break;
+ case _FTP_Mask:
+ if (nChars == 3) {
+ syntax = UriParser.FtpUri;
+ return true;
+ }
+ break;
+
+ case _NEWS_Mask0:
+ if (nChars == 4) {
+ syntax = UriParser.NewsUri;
+ return true;
+ }
+ break;
+
+ case _NNTP_Mask0:
+ if (nChars == 4) {
+ syntax = UriParser.NntpUri;
+ return true;
+ }
+ break;
+
+ case _UUID_Mask0:
+ if (nChars == 4) {
+ syntax = UriParser.UuidUri;
+ return true;
+ }
+ break;
+
+ case _GOPHER_Mask0:
+ if (nChars == 6 && (*(int*)(lptr+1)|_INT_LOWERCASE_Mask) == _GOPHER_Mask1) {
+ syntax = UriParser.GopherUri;
+ return true;
+ }
+ break;
+ case _MAILTO_Mask0:
+ if (nChars == 6 && (*(int*)(lptr+1)|_INT_LOWERCASE_Mask) == _MAILTO_Mask1) {
+ syntax = UriParser.MailToUri;
+ return true;
+ }
+ break;
+
+ case _TELNET_Mask0:
+ if (nChars == 6 && (*(int*)(lptr+1)|_INT_LOWERCASE_Mask) == _TELNET_Mask1) {
+ syntax = UriParser.TelnetUri;
+ return true;
+ }
+ break;
+
+ case _NETXXX_Mask0:
+ if (nChars == 8 && (*(lptr+1)|_LOWERCASE_Mask) == _NETPIPE_Mask1) {
+ syntax = UriParser.NetPipeUri;
+ return true;
+ }
+ else if (nChars == 7 && (*(lptr+1)|_LOWERCASE_Mask) == _NETTCP_Mask1) {
+ syntax = UriParser.NetTcpUri;
+ return true;
+ }
+ break;
+
+ case _LDAP_Mask0:
+ if (nChars == 4) {
+ syntax = UriParser.LdapUri;
+ return true;
+ }
+ break;
+ default: break;
+ }
+ return false;
+ }
+
+ //
+ //
+ // This will check whether a scheme string follows the rules
+ //
+ unsafe static private ParsingError CheckSchemeSyntax(char* ptr, ushort length, ref UriParser syntax) {
+ //First character must be an alpha
+ {
+ char c = *ptr;
+ if (c >= 'a' && c <= 'z') {
+ ;
+ } else if (c >= 'A' && c <= 'Z') {
+ *ptr = (char)(c | 0x20); //make it lowercase
+ } else {
+ return ParsingError.BadScheme;
+ }
+ }
+
+ for (ushort i = 1; i < length; ++i) {
+ char c = ptr[i];
+ if (c >= 'a' && c <= 'z') {
+ ;
+ } else if (c >= 'A' && c <= 'Z') {
+ ptr[i] = (char)(c | 0x20); //make it lowercase
+ } else if (c >= '0' && c <= '9') {
+ ;
+ } else if (c == '+' || c == '-' || c == '.') {
+ ;
+ } else {
+ return ParsingError.BadScheme;
+ }
+ }
+ // A not well-known scheme, needs string creation
+ // Note it is already in the lower case as required.
+ string str = new string(ptr, 0, length);
+ syntax = UriParser.FindOrFetchAsUnknownV1Syntax(str);
+ return ParsingError.None;
+ }
+ //
+ //
+ // 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
+ // Returns position of the Path component
+ //
+ // Must be called in the ctor only
+ private unsafe ushort CheckAuthorityHelper( char* pString, ushort idx, ushort length,
+ ref ParsingError err, ref Flags flags, UriParser syntax, ref string newHost )
+ {
+ int end = length;
+ char ch;
+ int startInput = idx;
+ ushort start = idx;
+ newHost = null;
+ bool justNormalized = false;
+ bool iriParsing = (s_IriParsing && IriParsingStatic(syntax)); // perf
+ bool hasUnicode = ((flags & Flags.HasUnicode) != 0); // perf
+ bool hostNotUnicodeNormalized = ((flags & Flags.HostUnicodeNormalized) == 0); // perf
+ UriSyntaxFlags syntaxFlags = syntax.Flags;
+
+ // need to build new Iri'zed string
+ if (hasUnicode && iriParsing && hostNotUnicodeNormalized){
+ newHost = m_originalUnicodeString.Substring(0, startInput);
+ }
+
+ //Special case is an empty authority
+ if (idx == length || ((ch=pString[idx]) == '/' || (ch == '\\' && StaticIsFile(syntax)) || ch == '#' || ch == '?'))
+ {
+ if (syntax.InFact(UriSyntaxFlags.AllowEmptyHost))
+ {
+ flags &= ~Flags.UncPath; //UNC cannot have an empty hostname
+ if (StaticInFact(flags, Flags.ImplicitFile))
+ err = ParsingError.BadHostName;
+ else
+ flags |= Flags.BasicHostType;
+ }
+ else
+ err = ParsingError.BadHostName;
+
+ if (hasUnicode && iriParsing && hostNotUnicodeNormalized){
+ flags |= Flags.HostUnicodeNormalized;// no host
+ }
+
+ return idx;
+ }
+
+//#if PLATFORM_UNIX
+// if (StaticIsFile(syntax) && ch != '/') {
+// // On UNIX a file URL may only have an empty authority
+// err = ParsingError.NonEmptyHost;
+// return idx;
+// }
+//#endif // PLATFORM_UNIX
+
+ string userInfoString = null;
+ // Attempt to parse user info first
+
+ if ((syntaxFlags & UriSyntaxFlags.MayHaveUserInfo) != 0)
+ {
+ for (; start < end; ++start)
+ {
+ if (start == end - 1 || pString[start] == '?' || pString[start] == '#' || pString[start] == '\\' ||
+ pString[start] == '/')
+ {
+ start = idx;
+ break;
+ }
+ else if (pString[start] == '@')
+ {
+ flags |= Flags.HasUserInfo;
+
+ // Iri'ze userinfo
+ if (iriParsing || (s_IdnScope != UriIdnScope.None)){
+ if (iriParsing && hasUnicode && hostNotUnicodeNormalized){
+ // Normalize user info
+ userInfoString = IriHelper.EscapeUnescapeIri(pString, startInput, start + 1, UriComponents.UserInfo);
+ try{
+ if (UriParser.ShouldUseLegacyV2Quirks)
+ userInfoString = userInfoString.Normalize(NormalizationForm.FormC);
+ }
+ catch (ArgumentException){
+ err = ParsingError.BadFormat;
+ return idx;
+ }
+
+ newHost += userInfoString;
+
+ if (!ServicePointManager.AllowAllUriEncodingExpansion && newHost.Length > ushort.MaxValue){
+ err = ParsingError.SizeLimit;
+ return idx;
+ }
+ }
+ else{
+ userInfoString = new string(pString, startInput, start - startInput + 1);
+ }
+ }
+ ++start;
+ ch = pString[start];
+ break;
+ }
+ }
+ }
+
+ // DNS name only optimization
+ // Fo an overriden parsing the optimization is suppressed since hostname can be changed to anything
+ bool dnsNotCanonical = ((syntaxFlags & UriSyntaxFlags.SimpleUserSyntax) == 0);
+
+ if (ch == '[' && syntax.InFact(UriSyntaxFlags.AllowIPv6Host)
+ && IPv6AddressHelper.IsValid(pString, (int)start+1, ref end))
+ {
+ flags |= Flags.IPv6HostType;
+
+ // Force load config here if config not loaded earlier since we handle IsWellFormed differently
+ // for IPv6 if the iri parsing flag is on or off
+ if (!s_ConfigInitialized) {
+ InitializeUriConfig();
+ m_iriParsing = (s_IriParsing && IriParsingStatic(syntax));
+ }
+
+ if (hasUnicode && iriParsing && hostNotUnicodeNormalized) {
+ newHost += new string(pString, start, end - start);
+ flags |= Flags.HostUnicodeNormalized;
+ justNormalized = true;
+ }
+ }
+ else if ( ch <= '9' && ch >= '0' && syntax.InFact(UriSyntaxFlags.AllowIPv4Host) &&
+ IPv4AddressHelper.IsValid(pString, (int) start, ref end, false, StaticNotAny(flags, Flags.ImplicitFile), syntax.InFact(UriSyntaxFlags.V1_UnknownUri)))
+ {
+ flags |= Flags.IPv4HostType;
+
+ if (hasUnicode && iriParsing && hostNotUnicodeNormalized){
+ newHost += new string(pString, start, end - start);
+ flags |= Flags.HostUnicodeNormalized;
+ justNormalized = true;
+ }
+ }
+ else if (((syntaxFlags & UriSyntaxFlags.AllowDnsHost)!= 0) && !iriParsing &&
+ DomainNameHelper.IsValid(pString, start, ref end, ref dnsNotCanonical, StaticNotAny(flags, Flags.ImplicitFile)))
+ {
+ // comes here if there are only ascii chars in host with original parsing and no Iri
+
+ flags |= Flags.DnsHostType;
+ if (!dnsNotCanonical) {
+ flags |= Flags.CanonicalDnsHost;
+ }
+
+ if ((s_IdnScope != UriIdnScope.None)){
+ // check if intranet
+ //
+ if ((s_IdnScope == UriIdnScope.AllExceptIntranet) && IsIntranet(new string(pString, 0, end))){
+ flags |= Flags.IntranetUri;
+ }
+ if (AllowIdnStatic(syntax, flags)){
+ bool allAscii = true;
+ bool atLeastOneIdn = false;
+
+ string idnValue = DomainNameHelper.UnicodeEquivalent(pString, start, end, ref allAscii, ref atLeastOneIdn);
+
+ // did we find at least one valid idn
+ if (atLeastOneIdn)
+ {
+ // need to switch string here since we didnt know before hand there there was an idn host
+ if (StaticNotAny(flags, Flags.HasUnicode))
+ m_originalUnicodeString = m_String; // lazily switching strings
+ flags |= Flags.IdnHost;
+
+ // need to build string for this special scenario
+ newHost = m_originalUnicodeString.Substring(0, startInput) + userInfoString + idnValue;
+ flags |= Flags.CanonicalDnsHost;
+ m_DnsSafeHost = new string(pString, start, end - start);
+ justNormalized = true;
+ }
+ flags |= Flags.HostUnicodeNormalized;
+ }
+ }
+ }
+ else if (((syntaxFlags & UriSyntaxFlags.AllowDnsHost) != 0)
+ && ((syntax.InFact(UriSyntaxFlags.AllowIriParsing) && hostNotUnicodeNormalized)
+ || syntax.InFact(UriSyntaxFlags.AllowIdn))
+ && DomainNameHelper.IsValidByIri(pString, start, ref end, ref dnsNotCanonical,
+ StaticNotAny(flags, Flags.ImplicitFile)))
+ {
+ CheckAuthorityHelperHandleDnsIri(pString, start, end, startInput, iriParsing, hasUnicode, syntax,
+ userInfoString, ref flags, ref justNormalized, ref newHost, ref err);
+ }
+#if !PLATFORM_UNIX
+ else if ((syntaxFlags & UriSyntaxFlags.AllowUncHost) != 0)
+ {
+ //
+ // This must remain as the last check befor BasicHost type
+ //
+ if (UncNameHelper.IsValid(pString, start, ref end, StaticNotAny(flags, Flags.ImplicitFile)))
+ {
+ if (end - start <= UncNameHelper.MaximumInternetNameLength)
+ {
+ flags |= Flags.UncHostType;
+ if (hasUnicode && iriParsing && hostNotUnicodeNormalized)
+ {
+ newHost += new string(pString, start, end - start);
+ flags |= Flags.HostUnicodeNormalized;
+ justNormalized = true;
+ }
+ }
+ }
+ }
+#endif // !PLATFORM_UNIX
+
+ // The deal here is that we won't allow '\' host terminator except for the File scheme
+ // If we see '\' we try to make it a part of of a Basic host
+ if (end < length && pString[end] == '\\' && (flags & Flags.HostTypeMask) != Flags.HostNotParsed
+ && !StaticIsFile(syntax))
+ {
+ if (syntax.InFact(UriSyntaxFlags.V1_UnknownUri))
+ {
+ err = ParsingError.BadHostName;
+ flags |= Flags.UnknownHostType;
+ return (ushort) end;
+ }
+ flags &= ~Flags.HostTypeMask;
+ }
+ // Here we have checked the syntax up to the end of host
+ // The only thing that can cause an exception is the port value
+ // Spend some (duplicated) cycles on that.
+ else if (end < length && pString[end] == ':')
+ {
+ if (syntax.InFact(UriSyntaxFlags.MayHavePort))
+ {
+ int port = 0;
+ int startPort = end;
+ for (idx = (ushort)(end+1); idx < length; ++idx) {
+ ushort val = (ushort)((ushort)pString[idx] - (ushort)'0');
+ if ((val >= 0) && (val <= 9))
+ {
+ if ((port = (port * 10 + val)) > 0xFFFF)
+ break;
+ }
+ else if (val == unchecked((ushort)('/' - '0')) || val == (ushort)('?' - '0')
+ || val == unchecked((ushort)('#' - '0')))
+ {
+ break;
+ }
+ else
+ {
+ // The second check is to keep compatibility with V1 until the UriParser is registered
+ if(syntax.InFact(UriSyntaxFlags.AllowAnyOtherHost)
+ && syntax.NotAny(UriSyntaxFlags.V1_UnknownUri))
+ {
+ flags &= ~Flags.HostTypeMask;
+ break;
+ }
+ else
+ {
+ err = ParsingError.BadPort;
+ return idx;
+ }
+ }
+ }
+ // check on 0-ffff range
+ if (port > 0xFFFF)
+ {
+ if (syntax.InFact(UriSyntaxFlags.AllowAnyOtherHost))
+ {
+ flags &= ~Flags.HostTypeMask;
+ }
+ else
+ {
+ err = ParsingError.BadPort;
+ return idx;
+ }
+ }
+
+ if (iriParsing && hasUnicode && justNormalized){
+ newHost += new string(pString, startPort, idx - startPort);
+ }
+ }
+ else
+ {
+ flags &= ~Flags.HostTypeMask;
+ }
+
+ }
+
+ // check on whether nothing has worked out
+ if ((flags & Flags.HostTypeMask) == Flags.HostNotParsed)
+ {
+ //No user info for a Basic hostname
+ flags &= ~Flags.HasUserInfo;
+ // Some schemes do not allow HostType = Basic (plus V1 almost never understands this cause of a bug)
+ //
+ if(syntax.InFact(UriSyntaxFlags.AllowAnyOtherHost))
+ {
+ flags |= Flags.BasicHostType;
+ for (end = idx; end < length; ++end) {
+ if (pString[end] == '/' || (pString[end] == '?' || pString[end] == '#')) {
+ break;
+ }
+ }
+ CheckAuthorityHelperHandleAnyHostIri(pString, startInput, end, iriParsing, hasUnicode, syntax,
+ ref flags, ref newHost, ref err);
+ }
+ else
+ {
+ //
+ // ATTN V1 compat: V1 supports hostnames like ".." and ".", and so we do but only for unknown schemes.
+ // (VsWhidbey#438821)
+ if (syntax.InFact(UriSyntaxFlags.V1_UnknownUri))
+ {
+ // Can assert here that the host is not empty so we will set dotFound
+ // at least once or fail before exiting the loop
+ bool dotFound = false;
+ int startOtherHost = idx;
+ for (end = idx; end < length; ++end)
+ {
+ if (dotFound && (pString[end] == '/' || pString[end] == '?' || pString[end] == '#'))
+ break;
+ else if (end < (idx + 2) && pString[end] == '.')
+ {
+ // allow one or two dots
+ dotFound = true;
+ }
+ else
+ {
+ //failure
+ err = ParsingError.BadHostName;
+ flags |= Flags.UnknownHostType;
+ return idx;
+ }
+ }
+ //success
+ flags |= Flags.BasicHostType;
+
+ if (iriParsing && hasUnicode
+ && StaticNotAny(flags, Flags.HostUnicodeNormalized)){
+ // Normalize any other host
+ String user = new string(pString, startOtherHost, end - startOtherHost);
+ try
+ {
+ newHost += user.Normalize(NormalizationForm.FormC);
+ }
+ catch (ArgumentException){
+ err = ParsingError.BadFormat;
+ return idx;
+ }
+
+ flags |= Flags.HostUnicodeNormalized;
+ }
+ }
+ else if (syntax.InFact(UriSyntaxFlags.MustHaveAuthority) ||
+ (syntax.InFact(UriSyntaxFlags.MailToLikeUri) && !UriParser.ShouldUseLegacyV2Quirks))
+ {
+ err = ParsingError.BadHostName;
+ flags |= Flags.UnknownHostType;
+ return idx;
+ }
+ }
+ }
+ return (ushort) end;
+ }
+
+ unsafe void CheckAuthorityHelperHandleDnsIri( char* pString, ushort start, int end, int startInput,
+ bool iriParsing, bool hasUnicode, UriParser syntax, string userInfoString, ref Flags flags,
+ ref bool justNormalized, ref string newHost, ref ParsingError err)
+ {
+ // comes here only if host has unicode chars and iri is on or idn is allowed
+
+ flags |= Flags.DnsHostType;
+
+ // check if intranet
+ //
+ if ((s_IdnScope == UriIdnScope.AllExceptIntranet) && IsIntranet(new string(pString, 0, end)))
+ {
+ flags |= Flags.IntranetUri;
+ }
+
+ if (AllowIdnStatic(syntax, flags))
+ {
+ bool allAscii = true;
+ bool atLeastOneIdn = false;
+
+ string idnValue = DomainNameHelper.IdnEquivalent(pString, start, end, ref allAscii, ref atLeastOneIdn);
+ string UniEquvlt = DomainNameHelper.UnicodeEquivalent(idnValue, pString, start, end);
+
+ if (!allAscii)
+ flags |= Flags.UnicodeHost; // we have a unicode host
+
+ if (atLeastOneIdn)
+ flags |= Flags.IdnHost; // we have at least one valid idn label
+
+ if (allAscii && atLeastOneIdn && StaticNotAny(flags, Flags.HasUnicode))
+ {
+ // original string location changed lazily
+ m_originalUnicodeString = m_String;
+ newHost = m_originalUnicodeString.Substring(0, startInput) +
+ (StaticInFact(flags, Flags.HasUserInfo) ? userInfoString : null);
+ justNormalized = true;
+ }
+ else if (!iriParsing && (StaticInFact(flags, Flags.UnicodeHost) || StaticInFact(flags, Flags.IdnHost)))
+ {
+ // original string location changed lazily
+ m_originalUnicodeString = m_String;
+ newHost = m_originalUnicodeString.Substring(0, startInput) +
+ (StaticInFact(flags, Flags.HasUserInfo) ? userInfoString : null);
+ justNormalized = true;
+ }
+
+ if (!(allAscii && !atLeastOneIdn))
+ {
+ m_DnsSafeHost = idnValue;
+ newHost += UniEquvlt;
+ justNormalized = true;
+ }
+ else if (allAscii && !atLeastOneIdn && iriParsing && hasUnicode)
+ {
+ newHost += UniEquvlt;
+ justNormalized = true;
+ }
+ }
+ else
+ {
+ if (hasUnicode)
+ {
+ string temp = StripBidiControlCharacter(pString, start, end - start);
+ try{
+ newHost += ((temp != null) ? temp.Normalize(NormalizationForm.FormC) : null);
+ }
+ catch (ArgumentException){
+ err = ParsingError.BadHostName;
+ }
+ justNormalized = true;
+ }
+ }
+ flags |= Flags.HostUnicodeNormalized;
+ }
+
+ unsafe void CheckAuthorityHelperHandleAnyHostIri(char* pString, int startInput, int end,
+ bool iriParsing, bool hasUnicode, UriParser syntax,
+ ref Flags flags, ref string newHost, ref ParsingError err)
+ {
+ if (StaticNotAny(flags, Flags.HostUnicodeNormalized) && (AllowIdnStatic(syntax, flags) ||
+ (iriParsing && hasUnicode)))
+ {
+ // Normalize any other host or do idn
+ String user = new string(pString, startInput, end - startInput);
+
+ if (AllowIdnStatic(syntax, flags))
+ {
+ bool allAscii = true;
+ bool atLeastOneIdn = false;
+
+ string UniEquvlt = DomainNameHelper.UnicodeEquivalent(pString, startInput, end, ref allAscii,
+ ref atLeastOneIdn);
+
+ if (((allAscii && atLeastOneIdn) || !allAscii) && !(iriParsing && hasUnicode))
+ {
+ // original string location changed lazily
+ m_originalUnicodeString = m_String;
+ newHost = m_originalUnicodeString.Substring(0, startInput);
+ flags |= Flags.HasUnicode;
+ }
+ if (atLeastOneIdn || !allAscii)
+ {
+ newHost += UniEquvlt;
+ string bidiStrippedHost = null;
+ m_DnsSafeHost = DomainNameHelper.IdnEquivalent(pString, startInput, end, ref allAscii,
+ ref bidiStrippedHost);
+ if (atLeastOneIdn)
+ flags |= Flags.IdnHost;
+ if (!allAscii)
+ flags |= Flags.UnicodeHost;
+ }
+ else if (iriParsing && hasUnicode)
+ {
+ newHost += user;
+
+ }
+ }
+ else
+ {
+ 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
+ //
+ // Parameters:
+ // - escaped true = treat all valid escape sequences as escaped sequences, false = escape all %
+ // - delim a character signalling the termination of the component being checked
+ //
+ // When delim=='?', then '#' character is also considered as delimiter additionally to passed '?'.
+ //
+ // The method pays attention to the dots and slashes so to signal potential Path compression action needed.
+ // Even that is not required for other components, the cycles are still spent (little inefficiency)
+ //
+
+ internal const char c_DummyChar = (char) 0xFFFF; //An Invalid Unicode character used as a dummy char passed into the parameter
+ internal const char c_EOL = (char) 0xFFFE; //An Invalid Unicode character used by CheckCanonical as "no delimiter condition"
+ [Flags]
+ private enum Check {
+ None = 0x0,
+ EscapedCanonical= 0x1,
+ DisplayCanonical= 0x2,
+ DotSlashAttn = 0x4,
+ DotSlashEscaped = 0x80,
+ BackslashInPath = 0x10,
+ ReservedFound = 0x20,
+ NotIriCanonical = 0x40,
+ FoundNonAscii = 0x8
+ }
+
+ //
+ // Finds the end of component
+ //
+
+ private unsafe void FindEndOfComponent(string input, ref ushort idx, ushort end, char delim)
+ {
+ fixed (char* str = input)
+ {
+ FindEndOfComponent(str, ref idx, end, delim);
+ }
+ }
+ private unsafe void FindEndOfComponent(char* str, ref ushort idx, ushort end, char delim)
+ {
+ char c = c_DummyChar;
+ ushort i=idx;
+ for (; i < end; ++i)
+ {
+ c = str[i];
+ if (c == delim)
+ {
+ break;
+ }
+ else if (delim == '?' && c == '#' && (m_Syntax != null && m_Syntax.InFact(UriSyntaxFlags.MayHaveFragment)))
+ {
+ // this is a special case when deciding on Query/Fragment
+ break;
+ }
+ }
+ idx = i;
+ }
+
+ //
+ // Used by ParseRemaining as well by InternalIsWellFormedOriginalString
+ //
+ private unsafe Check CheckCanonical(char* str, ref ushort idx, ushort end, char delim) {
+ Check res = Check.None;
+ bool needsEscaping = false;
+ bool foundEscaping = false;
+
+ char c = c_DummyChar;
+ ushort i=idx;
+ for (; i < end; ++i)
+ {
+ c = str[i];
+ // Control chars usually should be escaped in any case
+ if (c <= '\x1F' || (c >= '\x7F' && c <= '\x9F'))
+ {
+ needsEscaping = true;
+ foundEscaping = true;
+ res |= Check.ReservedFound;
+ }
+ else if (c > 'z' && c != '~') {
+ if(m_iriParsing){
+ bool valid = false;
+ res |= Check.FoundNonAscii;
+
+ if (Char.IsHighSurrogate(c)){
+ if ((i + 1) < end){
+ bool surrPair = false;
+ valid = IriHelper.CheckIriUnicodeRange(c, str[i + 1], ref surrPair, true);
+ }
+ }
+ else{
+ valid = IriHelper.CheckIriUnicodeRange(c, true);
+ }
+ if (!valid) res |= Check.NotIriCanonical;
+ }
+
+ if (!needsEscaping) needsEscaping = true;
+ }
+ else if (c == delim) {
+ break;
+ }
+ else if (delim == '?' && c == '#' && (m_Syntax != null && m_Syntax.InFact(UriSyntaxFlags.MayHaveFragment))) {
+ // this is a special case when deciding on Query/Fragment
+ break;
+ }
+ else if (c == '?') {
+ if (IsImplicitFile || (m_Syntax != null && !m_Syntax.InFact(UriSyntaxFlags.MayHaveQuery)
+ && delim != c_EOL))
+ {
+ // VsWhidbey#87423
+ // If found as reserved this char is not suitable for safe unescaped display
+ // Will need to escape it when both escaping and unescaping the string
+ res |= Check.ReservedFound;
+ foundEscaping = true;
+ needsEscaping = true;
+ }
+ }
+ else if (c == '#') {
+ needsEscaping = true;
+ if (IsImplicitFile || (m_Syntax != null && !m_Syntax.InFact(UriSyntaxFlags.MayHaveFragment))) {
+ // VsWhidbey#87423, 122037
+ // If found as reserved this char is not suitable for safe unescaped display
+ // Will need to escape it when both escaping and unescaping the string
+ res |= Check.ReservedFound;
+ foundEscaping = true;
+ }
+ }
+ else if (c == '/' || c == '\\') {
+ if ((res & Check.BackslashInPath) == 0 && c == '\\') {
+ res |= Check.BackslashInPath;
+ }
+ if ((res & Check.DotSlashAttn) == 0 && i+1 != end && (str[i+1] == '/' || str[i+1] == '\\' )) {
+ res |= Check.DotSlashAttn;
+ }
+ }
+ else if (c == '.') {
+ if ((res & Check.DotSlashAttn) == 0 && i+1 == end || str[i+1] == '.' || str[i+1] == '/'
+ || str[i+1] == '\\' || str[i+1] == '?' || str[i+1] == '#') {
+ res |= Check.DotSlashAttn;
+ }
+ }
+ else if (!needsEscaping && ((c <= '"' && c != '!') || (c >= '[' && c <= '^') || c == '>'
+ || c == '<' || c == '`')) {
+ needsEscaping = true;
+ }
+ else if (c == '%') {
+ if (!foundEscaping) foundEscaping = true;
+ //try unescape a byte hex escaping
+ if (i + 2 < end && (c = UriHelper.EscapedAscii(str[i + 1], str[i + 2])) != c_DummyChar)
+ {
+ if (c == '.' || c == '/' || c == '\\') {
+ res |= Check.DotSlashEscaped;
+ }
+ i+=2;
+ continue;
+ }
+ // otherwise we follow to non escaped case
+ if (!needsEscaping) {
+ needsEscaping = true;
+ }
+ }
+ }
+
+ if (foundEscaping) {
+ if (!needsEscaping) {
+ res |= Check.EscapedCanonical;
+ }
+ }
+ else {
+ res |= Check.DisplayCanonical;
+ if (!needsEscaping) {
+ res |= Check.EscapedCanonical;
+ }
+ }
+ idx = i;
+ return res;
+ }
+
+ //
+ // Returns the escaped and canonicalized path string
+ // the passed array must be long enough to hold at least
+ // canonical unescaped path representation (allocated by the caller)
+ //
+ private unsafe char[] GetCanonicalPath(char[] dest, ref int pos, UriFormat formatAs)
+ {
+
+ if (InFact(Flags.FirstSlashAbsent))
+ dest[pos++] = '/';
+
+ if (m_Info.Offset.Path == m_Info.Offset.Query)
+ return dest;
+
+ int end = pos;
+
+ int dosPathIdx = SecuredPathIndex;
+
+ // Note that unescaping and then escapig back is not transitive hence not safe.
+ // We are vulnerable due to the way the UserEscaped flag is processed (see NDPWhidbey#10612 bug).
+ // Try to unescape only needed chars.
+ if (formatAs == UriFormat.UriEscaped)
+ {
+ if (InFact(Flags.ShouldBeCompressed))
+ {
+ m_String.CopyTo(m_Info.Offset.Path, dest, end, m_Info.Offset.Query - m_Info.Offset.Path);
+ end += (m_Info.Offset.Query - m_Info.Offset.Path);
+
+ // If the path was found as needed compression and contains escaped characters, unescape only
+ // interesting characters (safe)
+
+ if (m_Syntax.InFact(UriSyntaxFlags.UnEscapeDotsAndSlashes) && InFact(Flags.PathNotCanonical)
+ && !IsImplicitFile)
+ {
+ fixed (char* pdest = dest)
+ {
+ UnescapeOnly(pdest, pos, ref end, '.', '/',
+ m_Syntax.InFact(UriSyntaxFlags.ConvertPathSlashes) ? '\\' : c_DummyChar);
+ }
+ }
+ }
+ else
+ {
+ //
+ if (InFact(Flags.E_PathNotCanonical) && NotAny(Flags.UserEscaped)) {
+ string str = m_String;
+
+ // Check on not canonical disk designation like C|\, should be rare, rare case
+ if (dosPathIdx != 0 && str[dosPathIdx + m_Info.Offset.Path -1] == '|')
+ {
+ str = str.Remove(dosPathIdx + m_Info.Offset.Path -1, 1);
+ str = str.Insert(dosPathIdx + m_Info.Offset.Path -1, ":");
+ }
+ dest = UriHelper.EscapeString(str, m_Info.Offset.Path, m_Info.Offset.Query, dest, ref end, true,
+ '?', '#', IsImplicitFile? c_DummyChar: '%');
+ }
+ else {
+ m_String.CopyTo(m_Info.Offset.Path, dest, end, m_Info.Offset.Query - m_Info.Offset.Path);
+ end += (m_Info.Offset.Query - m_Info.Offset.Path);
+ }
+ }
+ }
+ else
+ {
+ m_String.CopyTo(m_Info.Offset.Path, dest, end, m_Info.Offset.Query - m_Info.Offset.Path);
+ end += (m_Info.Offset.Query - m_Info.Offset.Path);
+
+ if (InFact(Flags.ShouldBeCompressed))
+ {
+ // If the path was found as needed compression and contains escaped characters,
+ // unescape only interesting characters (safe)
+
+ if (m_Syntax.InFact(UriSyntaxFlags.UnEscapeDotsAndSlashes) && InFact(Flags.PathNotCanonical)
+ && !IsImplicitFile)
+ {
+ fixed (char* pdest = dest)
+ {
+ UnescapeOnly(pdest, pos, ref end, '.', '/',
+ m_Syntax.InFact(UriSyntaxFlags.ConvertPathSlashes) ? '\\' : c_DummyChar);
+ }
+ }
+ }
+ }
+
+ // Here we already got output data as copied into dest array
+ // We just may need more processing of that data
+
+ //
+ // if this URI is using 'non-proprietary' disk drive designation, convert to MS-style
+ //
+ // (path is already >= 3 chars if recognized as a DOS-like)
+ //
+ if (dosPathIdx != 0 && dest[dosPathIdx + pos - 1] == '|')
+ dest[dosPathIdx + pos - 1] = ':';
+
+ if (InFact(Flags.ShouldBeCompressed))
+ {
+ // It will also convert back slashes if needed
+ dest = Compress(dest, (ushort)(pos + dosPathIdx), ref end, m_Syntax);
+ if (dest[pos] == '\\')
+ dest[pos] = '/';
+
+ // Escape path if requested and found as not fully escaped
+ if (formatAs == UriFormat.UriEscaped && NotAny(Flags.UserEscaped) && InFact(Flags.E_PathNotCanonical)) {
+ //
+ string srcString = new string(dest, pos, end-pos);
+ dest = UriHelper.EscapeString(srcString, 0, end - pos, dest, ref pos, true, '?', '#',
+ IsImplicitFile ? c_DummyChar : '%');
+ end = pos;
+ }
+ }
+ else if (m_Syntax.InFact(UriSyntaxFlags.ConvertPathSlashes) && InFact(Flags.BackslashInPath))
+ {
+ for (int i = pos; i < end; ++i)
+ if (dest[i] == '\\') dest[i] = '/';
+ }
+
+ if (formatAs != UriFormat.UriEscaped && InFact(Flags.PathNotCanonical))
+ {
+ UnescapeMode mode;
+ if (InFact(Flags.PathNotCanonical))
+ {
+ switch (formatAs)
+ {
+ case V1ToStringUnescape:
+
+ mode = (InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape)
+ | UnescapeMode.V1ToStringFlag;
+ if (IsImplicitFile)
+ mode &= ~UnescapeMode.Unescape;
+ break;
+
+ case UriFormat.Unescaped:
+ mode = IsImplicitFile ? UnescapeMode.CopyOnly
+ : UnescapeMode.Unescape | UnescapeMode.UnescapeAll;
+ break;
+
+ default: // UriFormat.SafeUnescaped
+
+ mode = InFact(Flags.UserEscaped)? UnescapeMode.Unescape: UnescapeMode.EscapeUnescape;
+ if (IsImplicitFile)
+ mode &= ~UnescapeMode.Unescape;
+ break;
+ }
+ }
+ else {
+ mode = UnescapeMode.CopyOnly;
+ }
+
+ char[] dest1 = new char[dest.Length];
+ Buffer.BlockCopy(dest, 0, dest1, 0, end<<1);
+ fixed (char *pdest = dest1)
+ {
+ dest = UriHelper.UnescapeString(pdest, pos, end, dest, ref pos, '?', '#', c_DummyChar, mode,
+ m_Syntax, false);
+ }
+ }
+ else
+ {
+ pos = end;
+ }
+
+ return dest;
+ }
+
+ // works only with ASCII characters, used to partially unescape path before compressing
+ private unsafe static void UnescapeOnly(char* pch, int start, ref int end, char ch1, char ch2, char ch3) {
+ if (end - start < 3) {
+ //no chance that something is escaped
+ return;
+ }
+
+ char *pend = pch + end-2;
+ pch += start;
+ char *pnew = null;
+
+ over:
+
+ // Just looking for a interested escaped char
+ if (pch >= pend) goto done;
+ if(*pch++ != '%') goto over;
+
+ char ch = UriHelper.EscapedAscii(*pch++, *pch++);
+ if (!(ch == ch1 || ch == ch2 || ch == ch3)) goto over;
+
+ // Here we found something and now start copying the scanned chars
+ pnew = pch-2;
+ *(pnew-1) = ch;
+
+ over_new:
+
+ if (pch >= pend) goto done;
+ if((*pnew++ = *pch++) != '%') goto over_new;
+
+ ch = UriHelper.EscapedAscii((*pnew++ = *pch++), (*pnew++ = *pch++));
+ if (!(ch == ch1 || ch == ch2 || ch == ch3)) {
+ goto over_new;
+ }
+
+ pnew -= 2;
+ *(pnew-1) = ch;
+
+ goto over_new;
+
+ done:
+ pend+=2;
+
+ if (pnew == null) {
+ //nothing was found
+ return;
+ }
+
+ //the tail may be already processed
+ if(pch == pend) {
+ end -= (int) (pch-pnew);
+ return;
+ }
+
+ *pnew++ = *pch++;
+ if(pch == pend) {
+ end -= (int) (pch-pnew);
+ return;
+ }
+ *pnew++ = *pch++;
+ end -= (int) (pch-pnew);
+ }
+
+ //
+ //
+ // This will compress any "\" "/../" "/./" "///" "/..../" /XXX.../, etc found in the input
+ //
+ // The passed syntax controls whether to use agressive compression or the one specified in RFC 2396
+ //
+ //
+ private static char[] Compress(char[] dest, ushort start, ref int destLength, UriParser syntax)
+ {
+ ushort slashCount = 0;
+ ushort lastSlash = 0;
+ ushort dotCount = 0;
+ ushort removeSegments = 0;
+
+ unchecked {
+ //ushort i == -1 and start == -1 overflow is ok here
+ ushort i = (ushort)((ushort)destLength - (ushort)1);
+ start = (ushort)(start-1);
+
+ for (; i != start ; --i) {
+ char ch = dest[i];
+ if (ch == '\\' && syntax.InFact(UriSyntaxFlags.ConvertPathSlashes)) {
+ dest[i] = ch = '/';
+ }
+
+ //
+ // compress multiple '/' for file URI
+ //
+ if (ch == '/') {
+ ++slashCount;
+ /*
+ QFE 4390 - remove the compression of multiple slashes to a single slash
+ if (slashCount > 1) {
+ continue;
+ }
+ */
+ }
+ else {
+ if (slashCount > 1) {
+ /*
+ QFE 4390 - remove the compression of multiple slashes to a single slash
+ if (syntax.InFact(UriSyntaxFlags.CanonicalizeAsFilePath))
+ {
+ // We saw > 1 slashes so remove all but the last one
+ // dest.Remove(i+1, slashCount -1);
+ Buffer.BlockCopy(dest, (i + slashCount) << 1, dest, (i + 1) << 1,
+ (destLength - (i + slashCount)) << 1);
+ destLength -= (slashCount - 1);
+ }
+ */
+ // else preserve repeated slashes
+ lastSlash = (ushort)(i + 1);
+ }
+ slashCount = 0;
+ }
+
+ if (ch == '.') {
+ ++dotCount;
+ continue;
+ }
+ else if (dotCount != 0) {
+
+ bool skipSegment = syntax.NotAny(UriSyntaxFlags.CanonicalizeAsFilePath)
+ && (dotCount > 2 || ch != '/' || i == start);
+
+ //
+ // Cases:
+ // /./ = remove this segment
+ // /../ = remove this segment, mark next for removal
+ // /....x = DO NOT TOUCH, leave as is
+ // x.../ = DO NOT TOUCH, leave as is, except for V2 legacy mode
+ //
+ if (!skipSegment && ch == '/') {
+ if ((lastSlash == i + dotCount + 1 // "/..../"
+ || (lastSlash == 0 && i + dotCount + 1 == destLength)) // "/..."
+ && (UriParser.ShouldUseLegacyV2Quirks || dotCount <= 2)) {
+ //
+ // /./ or /.<eos> or /../ or /..<eos>
+ //
+ // just reusing a variable slot we perform //dest.Remove(i+1, dotCount + (lastSlash==0?0:1));
+ lastSlash = (ushort)(i + 1 + dotCount + (lastSlash==0?0:1));
+ Buffer.BlockCopy(dest, lastSlash<<1, dest, (i+1)<<1, (destLength - lastSlash)<<1);
+ destLength -= (lastSlash-i-1);
+
+ lastSlash = i;
+ if (dotCount == 2) {
+ //
+ // We have 2 dots in between like /../ or /..<eos>,
+ // Mark next segment for removal and remove this /../ or /..
+ //
+ ++removeSegments;
+ }
+ dotCount = 0;
+ continue;
+ }
+ }
+ else if (UriParser.ShouldUseLegacyV2Quirks
+ && !skipSegment
+ && (removeSegments == 0)
+ && (lastSlash == i+dotCount+1 || (lastSlash == 0 && i+dotCount+1 == destLength))) {
+ //
+ // Note if removeSegments!=0, then ignore and remove the whole segment later
+ //
+ // x.../ or x...<eos>
+ // remove trailing dots
+ //
+ //
+ // just reusing a variable slot we perform //dest.Remove(i+1, dotCount);
+ dotCount = (ushort)(i + 1 + dotCount);
+ Buffer.BlockCopy(dest, dotCount<<1, dest, (i+1)<<1, (destLength - dotCount)<<1);
+ destLength -= (dotCount-i-1);
+ lastSlash = 0; //the other dots in this segment will stay intact
+ dotCount = 0;
+ continue;
+ }
+ // .NET 4.5 no longer removes trailing dots in a path segment x.../ or x...<eos>
+ dotCount = 0;
+
+ //
+ // Here all other cases go such as
+ // x.[..]y or /.[..]x or (/x.[...][/] && removeSegments !=0)
+ }
+
+ //
+ // Now we may want to remove a segment because of previous /../
+ //
+ if (ch == '/') {
+ if (removeSegments != 0) {
+ --removeSegments;
+
+ // just reusing a variable slot we perform //dest.Remove(i+1, lastSlash - i);
+ lastSlash = (ushort)(lastSlash + 1);
+ Buffer.BlockCopy(dest, lastSlash<<1, dest, (i+1)<<1, (destLength - lastSlash)<<1);
+ destLength -= (lastSlash-i-1);
+ }
+ lastSlash = i;
+ }
+ }
+
+ start = (ushort)((ushort)start + (ushort)1);
+ } //end of unchecked
+
+ // Dead Code?
+ if ((ushort)destLength > start && syntax.InFact(UriSyntaxFlags.CanonicalizeAsFilePath))
+ {
+ if (slashCount > 1) {
+ /*
+ Buffer.BlockCopy(dest, lastSlash << 1, dest, start << 1, (destLength - lastSlash) << 1);
+ destLength -= (slashCount - 1);
+ */
+ //QFE 4390 - Fall through for compat after not multiple slashes to a single slashl
+ }
+ else if (removeSegments != 0 && dest[start] != '/') {
+ //remove first not rooted segment
+ // dest.Remove(i+1, lastSlash - i);
+ lastSlash = (ushort)(lastSlash + 1);
+ Buffer.BlockCopy(dest, lastSlash<<1, dest, start<<1, (destLength - lastSlash)<<1);
+ destLength -= lastSlash;
+ }
+ else if (dotCount != 0) {
+ // If final string starts with a segment looking like .[...]/ or .[...]<eos>
+ // then we remove this fisrt segment
+ if (lastSlash == dotCount+1 || (lastSlash == 0 && dotCount + 1 == destLength)) {
+ //dest.Remove(0, dotCount + (lastSlash==0?0:1));
+ dotCount = (ushort)(dotCount + (lastSlash==0?0:1));
+ Buffer.BlockCopy(dest, dotCount<<1, dest, start<<1, (destLength - dotCount)<<1);
+ destLength -= dotCount;
+ }
+ }
+ }
+ return dest;
+ }
+
+ //used by DigestClient
+ internal static readonly char[] HexLowerChars = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ internal static int CalculateCaseInsensitiveHashCode(string text)
+ {
+ return StringComparer.InvariantCultureIgnoreCase.GetHashCode(text);
+ }
+ //
+ // CombineUri
+ //
+ // Given 2 URI strings, combine them into a single resultant URI string
+ //
+ // Inputs:
+ // <argument> basePart
+ // Base URI to combine with
+ //
+ // <argument> relativePart
+ // String expected to be relative URI
+ //
+ // Assumes:
+ // <basePart> is in canonic form
+ //
+ // Returns:
+ // Resulting combined URI string
+ //
+ private static string CombineUri(Uri basePart, string relativePart, UriFormat uriFormat) {
+ //NB: relativePart is ensured as not empty by the caller
+ // Another assumption is that basePart is an AbsoluteUri
+
+ // This method was not optimized for efficiency
+ // Means a relative Uri ctor may be relatively slow plus it increases the footprint of the baseUri
+
+ char c1 = relativePart[0];
+
+#if !PLATFORM_UNIX
+ //check a special case for the base as DOS path and a rooted relative string
+ if ( basePart.IsDosPath &&
+ (c1 == '/' || c1 == '\\') &&
+ (relativePart.Length == 1 || (relativePart[1] != '/' && relativePart[1] != '\\')))
+ {
+ // take relative part appended to the base string after the drive letter
+ int idx = basePart.OriginalString.IndexOf(':');
+ if (basePart.IsImplicitFile) {
+ return basePart.OriginalString.Substring(0, idx+1 ) + relativePart;
+ }
+ // The basePart has explicit scheme (could be not file:), take the DOS drive ':' position
+ idx = basePart.OriginalString.IndexOf(':', idx+1);
+ return basePart.OriginalString.Substring(0, idx+1 ) + relativePart;
+ }
+#endif // !PLATFORM_UNIX
+
+ // Check special case for Unc or absolute path in relativePart when base is FILE
+ if (StaticIsFile(basePart.Syntax))
+ {
+
+ if (c1 == '\\' || c1 == '/') {
+
+ if(relativePart.Length >= 2 && (relativePart[1] == '\\' || relativePart[1] == '/')) {
+ //Assuming relative is a Unc path and base is a file uri.
+ return basePart.IsImplicitFile? relativePart: "file:" + relativePart;
+ }
+
+ // here we got an absolute path in relativePart,
+ // For compatibility with V1.0 parser we restrict the compression scope to Unc Share, i.e. \\host\share\
+ if (basePart.IsUnc) {
+ string share = basePart.GetParts(UriComponents.Path | UriComponents.KeepDelimiter,
+ UriFormat.Unescaped);
+ for (int i = 1; i < share.Length; ++i) {
+ if (share[i] == '/') {
+ share = share.Substring(0, i);
+ break;
+ }
+ }
+ if (basePart.IsImplicitFile) {
+ return @"\\"
+ + basePart.GetParts(UriComponents.Host, UriFormat.Unescaped)
+ + share
+ + relativePart;
+ }
+ return "file://"
+ + basePart.GetParts(UriComponents.Host, uriFormat)
+ + share
+ + relativePart;
+
+ }
+ // It's not obvious but we've checked (for this relativePart format) that baseUti is nor UNC nor DOS path
+ //
+ // Means base is a Unix style path and, btw, IsImplicitFile cannot be the case either
+ return "file://" + relativePart;
+ }
+ }
+
+ // If we are here we did not recognize absolute DOS/UNC path for a file: base uri
+ // Note that DOS path may still happen in the relativePart and if so it may override the base uri scheme.
+
+ bool convBackSlashes = basePart.Syntax.InFact(UriSyntaxFlags.ConvertPathSlashes);
+
+ string left = null;
+
+ // check for network or local absolute path
+ if (c1 == '/' || (c1 == '\\' && convBackSlashes)) {
+ if (relativePart.Length >= 2 && relativePart[1] == '/') {
+ // got an authority in relative path and the base scheme is not file (checked)
+ return basePart.Scheme + ':' + relativePart;
+ }
+
+ // Got absolute relative path, and the base is nor FILE nor a DOS path (checked at the method start)
+ if (basePart.HostType == Flags.IPv6HostType) {
+ left = basePart.GetParts(UriComponents.Scheme|UriComponents.UserInfo, uriFormat)
+ + '[' + basePart.DnsSafeHost + ']'
+ + basePart.GetParts(UriComponents.KeepDelimiter|UriComponents.Port, uriFormat);
+ }
+ else {
+ left = basePart.GetParts(UriComponents.SchemeAndServer|UriComponents.UserInfo, uriFormat);
+ }
+ //VsWhidbey#241426
+ if (convBackSlashes && c1 == '\\')
+ relativePart = '/' + relativePart.Substring(1);
+
+ return left + relativePart;
+ }
+
+ // Here we got a relative path
+ // Need to run path Compression because this is how relative Uri combining works
+
+ // Take the base part path up to and including the last slash
+ left = basePart.GetParts(UriComponents.Path | UriComponents.KeepDelimiter,
+ basePart.IsImplicitFile ? UriFormat.Unescaped : uriFormat);
+ int length = left.Length;
+ char[] path = new char[length + relativePart.Length];
+
+ if (length > 0) {
+ left.CopyTo(0, path, 0, length);
+ while(length > 0) {
+ if (path[--length] == '/') {
+ ++length;
+ break;
+ }
+ }
+ }
+
+ //Append relative path to the result
+ relativePart.CopyTo(0, path, length, relativePart.Length);
+
+ // Split relative on path and extra (for compression)
+ c1 = basePart.Syntax.InFact(UriSyntaxFlags.MayHaveQuery)? '?': c_DummyChar;
+
+ // The implcit file check is to avoid a fragment in the implicit file combined uri.
+ // The behavior change request is tracked vis VsWhidbey#261387 ans that was approved through VsWhidbey#266417.
+ char c2 = (!basePart.IsImplicitFile && basePart.Syntax.InFact(UriSyntaxFlags.MayHaveFragment)) ? '#' :
+ c_DummyChar;
+ string extra = String.Empty;
+
+ // assuming c_DummyChar may not happen in an unicode uri string
+ if (!(c1 == c_DummyChar && c2 == c_DummyChar)) {
+ int i=0;
+ for (;i < relativePart.Length; ++i) {
+ if (path[length + i] == c1 || path[length + i] == c2) {
+ break;
+ }
+ }
+ if (i == 0) {
+ extra = relativePart;
+ }
+ else if (i < relativePart.Length) {
+ extra = relativePart.Substring(i);
+ }
+ length += i;
+ }
+ else {
+ length += relativePart.Length;
+ }
+
+ // Take the base part up to the path
+ if (basePart.HostType == Flags.IPv6HostType) {
+ if (basePart.IsImplicitFile) {
+ left = @"\\[" + basePart.DnsSafeHost + ']';
+ }
+ else {
+ left = basePart.GetParts(UriComponents.Scheme|UriComponents.UserInfo, uriFormat)
+ + '[' + basePart.DnsSafeHost + ']'
+ + basePart.GetParts(UriComponents.KeepDelimiter|UriComponents.Port, uriFormat);
+ }
+ }
+ else {
+ if (basePart.IsImplicitFile) {
+#if !PLATFORM_UNIX
+ if (basePart.IsDosPath) {
+ // The FILE DOS path comes as /c:/path, we have to exclude first 3 chars from compression
+ path = Compress(path, 3, ref length, basePart.Syntax);
+ return new string(path, 1, length-1) + extra;
+ }
+ else {
+ left = @"\\" + basePart.GetParts(UriComponents.Host, UriFormat.Unescaped);
+ }
+#else
+ left = basePart.GetParts(UriComponents.Host, UriFormat.Unescaped);
+#endif // !PLATFORM_UNIX
+
+ }
+ else {
+ left = basePart.GetParts(UriComponents.SchemeAndServer|UriComponents.UserInfo, uriFormat);
+ }
+ }
+ //compress the path
+ path = Compress(path, basePart.SecuredPathIndex, ref length, basePart.Syntax);
+ return left + new string(path, 0, length) + extra;
+ }
+
+ //
+ // PathDifference
+ //
+ // Performs the relative path calculation for MakeRelative()
+ //
+ // Inputs:
+ // <argument> path1
+ // <argument> path2
+ // Paths for which we calculate the difference
+ //
+ // <argument> compareCase
+ // False if we consider characters that differ only in case to be
+ // equal
+ //
+ // Returns:
+ // A string which is the relative path difference between <path1> and
+ // <path2> such that if <path1> and the calculated difference are used
+ // as arguments to Combine(), <path2> is returned
+ //
+ // Throws:
+ // Nothing
+ //
+ private static string PathDifference(string path1, string path2, bool compareCase) {
+
+ int i;
+ int si = -1;
+
+ for (i = 0; (i < path1.Length) && (i < path2.Length); ++i) {
+ if ((path1[i] != path2[i])
+ && (compareCase
+ || (Char.ToLower(path1[i], CultureInfo.InvariantCulture)
+ != Char.ToLower(path2[i], CultureInfo.InvariantCulture))))
+ {
+ break;
+
+ } else if (path1[i] == '/') {
+ si = i;
+ }
+ }
+
+ if (i == 0) {
+ return path2;
+ }
+ if ((i == path1.Length) && (i == path2.Length)) {
+ return String.Empty;
+ }
+
+ StringBuilder relPath = new StringBuilder();
+ // Walk down several dirs
+ for (; i < path1.Length; ++i) {
+ if (path1[i] == '/') {
+ relPath.Append("../");
+ }
+ }
+ // Same path except that path1 ended with a file name and path2 didn't
+ if (relPath.Length == 0 && path2.Length - 1 == si)
+ return "./"; // Truncate the file name
+ return relPath.ToString() + path2.Substring(si + 1);
+ }
+
+ //Used by Uribuilder
+ internal bool HasAuthority {
+ get {
+ return InFact(Flags.AuthorityFound);
+ }
+ }
+
+ private static readonly char[] _WSchars = new char[] {' ', '\n', '\r', '\t'};
+ private static bool IsLWS(char ch) {
+
+ return (ch <= ' ') && (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t');
+ }
+
+ //Only consider ASCII characters
+ private static bool IsAsciiLetter(char character) {
+
+ return (character >= 'a' && character <= 'z') ||
+ (character >= 'A' && character <= 'Z');
+ }
+
+ internal static bool IsAsciiLetterOrDigit(char character) {
+ return IsAsciiLetter(character) || (character >= '0' && character <= '9');
+ }
+
+ //
+ // Is this a Bidirectional control char.. These get stripped
+ //
+ internal static bool IsBidiControlCharacter(char ch)
+ {
+ return (ch == '\u200E' /*LRM*/ || ch == '\u200F' /*RLM*/ || ch == '\u202A' /*LRE*/ ||
+ ch == '\u202B' /*RLE*/ || ch == '\u202C' /*PDF*/ || ch == '\u202D' /*LRO*/ ||
+ ch == '\u202E' /*RLO*/);
+ }
+
+ //
+ // Strip Bidirectional control charcters from this string
+ //
+ internal static unsafe string StripBidiControlCharacter(char* strToClean, int start, int length)
+ {
+ if (length <= 0) return "";
+
+ char [] cleanStr = new char[length];
+ int count = 0;
+ for (int i = 0; i < length; ++i){
+ char c = strToClean[start + i];
+ if (c < '\u200E' || c > '\u202E' || !IsBidiControlCharacter(c)){
+ cleanStr[count++] = c;
+ }
+ }
+ return new string(cleanStr, 0, count);
+ }
+
+ //
+ // MakeRelative (toUri)
+ //
+ // Return a relative path which when applied to this Uri would create the
+ // resulting Uri <toUri>
+ //
+ // Inputs:
+ // <argument> toUri
+ // Uri to which we calculate the transformation from this Uri
+ //
+ // Returns:
+ // If the 2 Uri are common except for a relative path difference, then that
+ // difference, else the display name of this Uri
+ //
+ // Throws:
+ // ArgumentNullException, InvalidOperationException
+ //
+ [Obsolete("The method has been deprecated. Please use MakeRelativeUri(Uri uri). http://go.microsoft.com/fwlink/?linkid=14202")]
+ public string MakeRelative(Uri toUri)
+ {
+ if ((object)toUri == null)
+ throw new ArgumentNullException("toUri");
+
+ if (IsNotAbsoluteUri || toUri.IsNotAbsoluteUri)
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NotAbsolute));
+
+ if ((Scheme == toUri.Scheme) && (Host == toUri.Host) && (Port == toUri.Port))
+ return PathDifference(AbsolutePath, toUri.AbsolutePath, !IsUncOrDosPath);
+
+ return toUri.ToString();
+ }
+
+ /// <internalonly/>
+ [Obsolete("The method has been deprecated. It is not used by the system. http://go.microsoft.com/fwlink/?linkid=14202")]
+ protected virtual void Parse()
+ {
+ // Microsoft cr: In V1-Everett this method if suppressed by the derived class
+ // would lead to an unconstructed Uri instance.
+ // It does not make any sense and violates Fxcop on calling a virtual method in the ctor.
+ // Should be deprecated and removed asap.
+ }
+ /// <internalonly/>
+ [Obsolete("The method has been deprecated. It is not used by the system. http://go.microsoft.com/fwlink/?linkid=14202")]
+ protected virtual void Canonicalize()
+ {
+ // Microsoft cr: In V1-Everett this method if suppressed by the derived class
+ // would lead to supressing of a path compression
+ // It does not make much sense and violates Fxcop on calling a virtual method in the ctor.
+ // Should be deprecated and removed asap.
+ }
+ /// <internalonly/>
+ [Obsolete("The method has been deprecated. It is not used by the system. http://go.microsoft.com/fwlink/?linkid=14202")]
+ protected virtual void Escape()
+ {
+ // Microsoft cr: In V1-Everett this method if suppressed by the derived class
+ // would lead to the same effect as dontEscape=true.
+ // It does not make much sense and violates Fxcop on calling a virtual method in the ctor.
+ // Should be deprecated and removed asap.
+ }
+ //
+ // Unescape
+ //
+ // Convert any escape sequences in <path>. Escape sequences can be
+ // hex encoded reserved characters (e.g. %40 == '@') or hex encoded
+ // UTF-8 sequences (e.g. %C4%D2 == 'Latin capital Ligature Ij')
+ //
+ /// <internalonly/>
+ [Obsolete("The method has been deprecated. Please use GetComponents() or static UnescapeDataString() to unescape a Uri component or a string. http://go.microsoft.com/fwlink/?linkid=14202")]
+ protected virtual string Unescape(string path) {
+
+ // Microsoft cr: This method is dangerous since it gives path unescaping control
+ // to the derived class without any permission demand.
+ // Should be deprecated and removed asap.
+
+ char[] dest = new char[path.Length];
+ int count = 0;
+ dest = UriHelper.UnescapeString(path, 0, path.Length, dest, ref count, c_DummyChar, c_DummyChar,
+ c_DummyChar, UnescapeMode.Unescape | UnescapeMode.UnescapeAll, null, false);
+ return new string(dest, 0, count);
+ }
+
+ [Obsolete("The method has been deprecated. Please use GetComponents() or static EscapeUriString() to escape a Uri component or a string. http://go.microsoft.com/fwlink/?linkid=14202")]
+ protected static string EscapeString(string str) {
+
+ // Microsoft cr: This method just does not make sense sa protected
+ // It should go public static asap
+
+ if ((object)str == null) {
+ return string.Empty;
+ }
+
+ int destStart = 0;
+ char[] dest = UriHelper.EscapeString(str, 0, str.Length, null, ref destStart, true, '?', '#', '%');
+ if ((object)dest == null)
+ return str;
+ return new string(dest, 0, destStart);
+ }
+
+ //
+ // CheckSecurity
+ //
+ // Check for any invalid or problematic character sequences
+ //
+ /// <internalonly/>
+ [Obsolete("The method has been deprecated. It is not used by the system. http://go.microsoft.com/fwlink/?linkid=14202")]
+ protected virtual void CheckSecurity() {
+
+ // Microsoft cr: This method just does not make sense
+ // Should be deprecated and removed asap.
+
+ if (Scheme == "telnet") {
+
+ //
+ // remove everything after ';' for telnet
+ //
+
+ }
+ }
+
+ //
+ // IsReservedCharacter
+ //
+ // Determine whether a character is part of the reserved set
+ //
+ // Returns:
+ // true if <character> is reserved else false
+ //
+ /// <internalonly/>
+ [Obsolete("The method has been deprecated. It is not used by the system. http://go.microsoft.com/fwlink/?linkid=14202")]
+ protected virtual bool IsReservedCharacter(char character) {
+
+ // Microsoft cr: This method just does not make sense as virtual protected
+ // It should go public static asap
+
+ return (character == ';')
+ || (character == '/')
+ || (character == ':')
+ || (character == '@') // OK FS char
+ || (character == '&')
+ || (character == '=')
+ || (character == '+') // OK FS char
+ || (character == '$') // OK FS char
+ || (character == ',')
+ ;
+ }
+
+ //
+ // IsExcludedCharacter
+ //
+ // Determine if a character should be exluded from a URI and therefore be
+ // escaped
+ //
+ // Returns:
+ // true if <character> should be escaped else false
+ //
+ /// <internalonly/>
+ [Obsolete("The method has been deprecated. It is not used by the system. http://go.microsoft.com/fwlink/?linkid=14202")]
+ protected static bool IsExcludedCharacter(char character) {
+
+ // Microsoft cr: This method just does not make sense sa protected
+ // It should go public static asap
+
+ //
+ // the excluded characters...
+ //
+
+ return (character <= 0x20)
+ || (character >= 0x7f)
+ || (character == '<')
+ || (character == '>')
+ || (character == '#')
+ || (character == '%')
+ || (character == '"')
+
+ //
+ // the 'unwise' characters...
+ //
+
+ || (character == '{')
+ || (character == '}')
+ || (character == '|')
+ || (character == '\\')
+ || (character == '^')
+ || (character == '[')
+ || (character == ']')
+ || (character == '`')
+ ;
+ }
+
+ //
+ // IsBadFileSystemCharacter
+ //
+ // Determine whether a character would be an invalid character if used in
+ // a file system name. Note, this is really based on NTFS rules
+ //
+ // Returns:
+ // true if <character> would be a treated as a bad file system character
+ // else false
+ //
+ [Obsolete("The method has been deprecated. It is not used by the system. http://go.microsoft.com/fwlink/?linkid=14202")]
+ protected virtual bool IsBadFileSystemCharacter(char character) {
+
+ // Microsoft cr: This method just does not make sense sa protected virtual
+ // It should go public static asap
+
+ return (character < 0x20)
+ || (character == ';')
+ || (character == '/')
+ || (character == '?')
+ || (character == ':')
+ || (character == '&')
+ || (character == '=')
+ || (character == ',')
+ || (character == '*')
+ || (character == '<')
+ || (character == '>')
+ || (character == '"')
+ || (character == '|')
+ || (character == '\\')
+ || (character == '^')
+ ;
+ }
+
+
+ } // class Uri
+} // namespace System
+
diff --git a/System/net/System/UriExt.cs b/System/net/System/UriExt.cs
new file mode 100644
index 0000000..b9e018e
--- /dev/null
+++ b/System/net/System/UriExt.cs
@@ -0,0 +1,1033 @@
+/*++
+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;
+ }
+ }
+ }
+}
diff --git a/System/net/System/UriHelper.cs b/System/net/System/UriHelper.cs
new file mode 100644
index 0000000..f81a765
--- /dev/null
+++ b/System/net/System/UriHelper.cs
@@ -0,0 +1,731 @@
+namespace System
+{
+ using System.Globalization;
+ using System.Text;
+ using System.Diagnostics;
+
+ internal static class UriHelper
+ {
+ private static readonly char[] HexUpperChars = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ // http://host/Path/Path/File?Query is the base of
+ // - http://host/Path/Path/File/ ... (those "File" words may be different in semantic but anyway)
+ // - http://host/Path/Path/#Fragment
+ // - http://host/Path/Path/?Query
+ // - http://host/Path/Path/MoreDir/ ...
+ // - http://host/Path/Path/OtherFile?Query
+ // - http://host/Path/Path/Fl
+ // - http://host/Path/Path/
+ //
+ // It is not a base for
+ // - http://host/Path/Path (that last "Path" is not considered as a directory)
+ // - http://host/Path/Path?Query
+ // - http://host/Path/Path#Fragment
+ // - http://host/Path/Path2/
+ // - http://host/Path/Path2/MoreDir
+ // - http://host/Path/File
+ //
+ // ASSUMES that strings like http://host/Path/Path/MoreDir/../../ have been canonicalized before going to this method.
+ // ASSUMES that back slashes already have been converted if applicable.
+ //
+ internal static unsafe bool TestForSubPath( char* pMe, ushort meLength, char* pShe, ushort sheLength,
+ bool ignoreCase)
+ {
+ ushort i = 0;
+ char chMe;
+ char chShe;
+
+ bool AllSameBeforeSlash = true;
+
+ for( ;i < meLength && i < sheLength; ++i)
+ {
+ chMe = *(pMe+i);
+ chShe = *(pShe+i);
+
+ if (chMe == '?' || chMe == '#')
+ {
+ // survived so far and pMe does not have any more path segments
+ return true;
+ }
+
+ // If pMe terminates a path segment, so must pShe
+ if (chMe == '/')
+ {
+ if (chShe != '/')
+ {
+ // comparison has falied
+ return false;
+ }
+ // plus the segments must be the same
+ if (!AllSameBeforeSlash)
+ {
+ // comparison has falied
+ return false;
+ }
+ //so far so good
+ AllSameBeforeSlash = true;
+ continue;
+ }
+
+ // if pShe terminates then pMe must not have any more path segments
+ if (chShe == '?' || chShe == '#')
+ {
+ break;
+ }
+
+ if (!ignoreCase)
+ {
+ if (chMe != chShe)
+ {
+ AllSameBeforeSlash = false;
+ }
+ }
+ else
+ {
+ if (Char.ToLower(chMe, CultureInfo.InvariantCulture) != Char.ToLower(chShe, CultureInfo.InvariantCulture))
+ {
+ AllSameBeforeSlash = false;
+ }
+ }
+ }
+
+ // If me is longer then it must not have any more path segments
+ for (; i < meLength; ++i)
+ {
+ if ((chMe = *(pMe+i)) == '?' || chMe == '#')
+ {
+ return true;
+ }
+ if (chMe == '/')
+ {
+ return false;
+ }
+ }
+ //survived by getting to the end of pMe
+ return true;
+ }
+
+ // - forceX characters are always escaped if found
+ // - rsvd character will remain unescaped
+ //
+ // start - starting offset from input
+ // end - the exclusive ending offset in input
+ // destPos - starting offset in dest for output, on return this will be an exclusive "end" in the output.
+ //
+ // In case "dest" has lack of space it will be reallocated by preserving the _whole_ content up to current destPos
+ //
+ // Returns null if nothing has to be escaped AND passed dest was null, otherwise the resulting array with the updated destPos
+ //
+ const short c_MaxAsciiCharsReallocate = 40;
+ const short c_MaxUnicodeCharsReallocate = 40;
+ const short c_MaxUTF_8BytesPerUnicodeChar = 4;
+ const short c_EncodedCharsPerByte = 3;
+ internal unsafe static char[] EscapeString(string input, int start, int end, char[] dest, ref int destPos,
+ bool isUriString, char force1, char force2, char rsvd)
+ {
+ if (end - start >= Uri.c_MaxUriBufferSize)
+ throw new UriFormatException(SR.GetString(SR.net_uri_SizeLimit));
+
+ int i = start;
+ int prevInputPos = start;
+ byte *bytes = stackalloc byte[c_MaxUnicodeCharsReallocate*c_MaxUTF_8BytesPerUnicodeChar]; // 40*4=160
+
+ fixed (char* pStr = input)
+ {
+ for(; i < end; ++i)
+ {
+ char ch = pStr[i];
+
+ // a Unicode ?
+ if (ch > '\x7F')
+ {
+ short maxSize = (short)Math.Min(end - i, (int)c_MaxUnicodeCharsReallocate-1);
+
+ short count = 1;
+ for (; count < maxSize && pStr[i + count] > '\x7f'; ++count)
+ ;
+
+ // Is the last a high surrogate?
+ if (pStr[i + count-1] >= 0xD800 && pStr[i + count-1] <= 0xDBFF)
+ {
+ // Should be a rare case where the app tries to feed an invalid Unicode surrogates pair
+ if (count == 1 || count == end - i)
+ throw new UriFormatException(SR.GetString(SR.net_uri_BadString));
+ // need to grab one more char as a Surrogate except when it's a bogus input
+ ++count;
+ }
+
+ dest = EnsureDestinationSize(pStr, dest, i,
+ (short)(count * c_MaxUTF_8BytesPerUnicodeChar * c_EncodedCharsPerByte),
+ c_MaxUnicodeCharsReallocate * c_MaxUTF_8BytesPerUnicodeChar * c_EncodedCharsPerByte,
+ ref destPos, prevInputPos);
+
+ short numberOfBytes = (short)Encoding.UTF8.GetBytes(pStr+i, count, bytes,
+ c_MaxUnicodeCharsReallocate*c_MaxUTF_8BytesPerUnicodeChar);
+
+ // This is the only exception that built in UriParser can throw after a Uri ctor.
+ // Should not happen unless the app tries to feed an invalid Unicode String
+ if (numberOfBytes == 0)
+ throw new UriFormatException(SR.GetString(SR.net_uri_BadString));
+
+ i += (count-1);
+
+ for (count = 0 ; count < numberOfBytes; ++count)
+ EscapeAsciiChar((char)bytes[count], dest, ref destPos);
+
+ prevInputPos = i+1;
+ }
+ else if (ch == '%' && rsvd == '%')
+ {
+ // Means we don't reEncode '%' but check for the possible escaped sequence
+ dest = EnsureDestinationSize(pStr, dest, i, c_EncodedCharsPerByte,
+ c_MaxAsciiCharsReallocate * c_EncodedCharsPerByte, ref destPos, prevInputPos);
+ if(i + 2 < end && EscapedAscii(pStr[i+1], pStr[i+2]) != Uri.c_DummyChar)
+ {
+ // leave it escaped
+ dest[destPos++] = '%';
+ dest[destPos++] = pStr[i+1];
+ dest[destPos++] = pStr[i+2];
+ i += 2;
+ }
+ else
+ {
+ EscapeAsciiChar('%', dest, ref destPos);
+ }
+ prevInputPos = i+1;
+ }
+ else if (ch == force1 || ch == force2)
+ {
+ dest = EnsureDestinationSize(pStr, dest, i, c_EncodedCharsPerByte,
+ c_MaxAsciiCharsReallocate * c_EncodedCharsPerByte, ref destPos, prevInputPos);
+ EscapeAsciiChar(ch, dest, ref destPos);
+ prevInputPos = i+1;
+ }
+ else if (ch != rsvd && (isUriString ? !IsReservedUnreservedOrHash(ch) : !IsUnreserved(ch)))
+ {
+ dest = EnsureDestinationSize(pStr, dest, i, c_EncodedCharsPerByte,
+ c_MaxAsciiCharsReallocate * c_EncodedCharsPerByte, ref destPos, prevInputPos);
+ EscapeAsciiChar(ch, dest, ref destPos);
+ prevInputPos = i+1;
+ }
+ }
+
+ if (prevInputPos != i)
+ {
+ // need to fill up the dest array ?
+ if (prevInputPos != start || dest != null)
+ dest = EnsureDestinationSize(pStr, dest, i, 0, 0, ref destPos, prevInputPos);
+ }
+ }
+
+ return dest;
+ }
+
+ //
+ // ensure destination array has enough space and contains all the needed input stuff
+ //
+ private unsafe static char[] EnsureDestinationSize(char* pStr, char[] dest, int currentInputPos,
+ short charsToAdd, short minReallocateChars, ref int destPos, int prevInputPos)
+ {
+ if ((object) dest == null || dest.Length < destPos + (currentInputPos-prevInputPos) + charsToAdd)
+ {
+ // allocating or reallocating array by ensuring enough space based on maxCharsToAdd.
+ char[] newresult = new char[destPos + (currentInputPos-prevInputPos) + minReallocateChars];
+
+ if ((object) dest != null && destPos != 0)
+ Buffer.BlockCopy(dest, 0, newresult, 0, destPos<<1);
+ dest = newresult;
+ }
+
+ // ensuring we copied everything form the input string left before last escaping
+ while (prevInputPos != currentInputPos)
+ dest[destPos++] = pStr[prevInputPos++];
+ return dest;
+ }
+
+ //
+ // This method will assume that any good Escaped Sequence will be unescaped in the output
+ // - Assumes Dest.Length - detPosition >= end-start
+ // - UnescapeLevel controls various modes of opearion
+ // - Any "bad" escape sequence will remain as is or '%' will be escaped.
+ // - destPosition tells the starting index in dest for placing the result.
+ // On return destPosition tells the last character + 1 postion in the "dest" array.
+ // - The control chars and chars passed in rsdvX parameters may be re-escaped depending on UnescapeLevel
+ // - It is a RARE case when Unescape actually needs escaping some characteres mentioned above.
+ // For this reason it returns a char[] that is usually the same ref as the input "dest" value.
+ //
+ internal unsafe static char[] UnescapeString(string input, int start, int end, char[] dest,
+ ref int destPosition, char rsvd1, char rsvd2, char rsvd3, UnescapeMode unescapeMode, UriParser syntax,
+ bool isQuery)
+ {
+ fixed (char *pStr = input)
+ {
+ return UnescapeString(pStr, start, end, dest, ref destPosition, rsvd1, rsvd2, rsvd3, unescapeMode,
+ syntax, isQuery);
+ }
+ }
+ internal unsafe static char[] UnescapeString(char* pStr, int start, int end, char[] dest, ref int destPosition,
+ char rsvd1, char rsvd2, char rsvd3, UnescapeMode unescapeMode, UriParser syntax, bool isQuery)
+ {
+ byte [] bytes = null;
+ byte escapedReallocations = 0;
+ bool escapeReserved = false;
+ int next = start;
+ bool iriParsing = Uri.IriParsingStatic(syntax)
+ && ((unescapeMode & UnescapeMode.EscapeUnescape) == UnescapeMode.EscapeUnescape);
+
+ while (true)
+ {
+ // we may need to re-pin dest[]
+ fixed (char* pDest = dest)
+ {
+ if ((unescapeMode & UnescapeMode.EscapeUnescape) == UnescapeMode.CopyOnly)
+ {
+ while (start < end)
+ pDest[destPosition++] = pStr[start++];
+ return dest;
+ }
+
+ while (true)
+ {
+ char ch = (char)0;
+
+ for (;next < end; ++next)
+ {
+ if ((ch = pStr[next]) == '%')
+ {
+ if ((unescapeMode & UnescapeMode.Unescape) == 0)
+ {
+ // re-escape, don't check anything else
+ escapeReserved = true;
+ }
+ else if (next+2 < end)
+ {
+ ch = EscapedAscii(pStr[next+1], pStr[next+2]);
+ // Unescape a good sequence if full unescape is requested
+ if (unescapeMode >= UnescapeMode.UnescapeAll)
+ {
+ if (ch == Uri.c_DummyChar)
+ {
+ if (unescapeMode >= UnescapeMode.UnescapeAllOrThrow)
+ {
+ // Should be a rare case where the app tries to feed an invalid escaped sequence
+ throw new UriFormatException(SR.GetString(SR.net_uri_BadString));
+ }
+ continue;
+ }
+ }
+ // re-escape % from an invalid sequence
+ else if (ch == Uri.c_DummyChar)
+ {
+ if ((unescapeMode & UnescapeMode.Escape) != 0)
+ escapeReserved = true;
+ else
+ continue; // we should throw instead but since v1.0 would just print '%'
+ }
+ // Do not unescape '%' itself unless full unescape is requested
+ else if (ch == '%')
+ {
+ next += 2;
+ continue;
+ }
+ // Do not unescape a reserved char unless full unescape is requested
+ else if (ch == rsvd1 || ch == rsvd2 || ch == rsvd3)
+ {
+ next += 2;
+ continue;
+ }
+ // Do not unescape a dangerous char unless it's V1ToStringFlags mode
+ else if ((unescapeMode & UnescapeMode.V1ToStringFlag) == 0 && IsNotSafeForUnescape(ch))
+ {
+ next += 2;
+ continue;
+ }
+ else if (iriParsing && ((ch <='\x9F' && IsNotSafeForUnescape(ch)) ||
+ (ch >'\x9F' &&!IriHelper.CheckIriUnicodeRange(ch, isQuery))))
+ {
+ // check if unenscaping gives a char ouside iri range
+ // if it does then keep it escaped
+ next += 2;
+ continue;
+ }
+ // unescape escaped char or escape %
+ break;
+ }
+ else if (unescapeMode >= UnescapeMode.UnescapeAll)
+ {
+ if (unescapeMode >= UnescapeMode.UnescapeAllOrThrow)
+ {
+ // Should be a rare case where the app tries to feed an invalid escaped sequence
+ throw new UriFormatException(SR.GetString(SR.net_uri_BadString));
+ }
+ // keep a '%' as part of a bogus sequence
+ continue;
+ }
+ else
+ {
+ escapeReserved = true;
+ }
+ // escape (escapeReserved==ture) or otheriwse unescape the sequence
+ break;
+ }
+ else if ((unescapeMode & (UnescapeMode.Unescape | UnescapeMode.UnescapeAll))
+ == (UnescapeMode.Unescape | UnescapeMode.UnescapeAll))
+ {
+ continue;
+ }
+ else if ((unescapeMode & UnescapeMode.Escape) != 0)
+ {
+ // Could actually escape some of the characters
+ if (ch == rsvd1 || ch == rsvd2 || ch == rsvd3)
+ {
+ // found an unescaped reserved character -> escape it
+ escapeReserved = true;
+ break;
+ }
+ else if ((unescapeMode & UnescapeMode.V1ToStringFlag) == 0
+ && (ch <= '\x1F' || (ch >= '\x7F' && ch <= '\x9F')))
+ {
+ // found an unescaped reserved character -> escape it
+ escapeReserved = true;
+ break;
+ }
+ }
+ }
+
+ //copy off previous characters from input
+ while (start < next)
+ pDest[destPosition++] = pStr[start++];
+
+ if (next != end)
+ {
+ //VsWhidbey#87423
+ if (escapeReserved)
+ {
+ //escape that char
+ // Since this should be _really_ rare case, reallocate with constant size increase of 30 rsvd-type characters.
+ if (escapedReallocations == 0)
+ {
+ escapedReallocations = 30;
+ char[] newDest = new char[dest.Length + escapedReallocations*3];
+ fixed (char *pNewDest = newDest)
+ {
+ for (int i = 0; i < destPosition; ++i)
+ pNewDest[i] = pDest[i];
+ }
+ dest = newDest;
+ // re-pin new dest[] array
+ goto dest_fixed_loop_break;
+ }
+ else
+ {
+ --escapedReallocations;
+ EscapeAsciiChar(pStr[next], dest, ref destPosition);
+ escapeReserved = false;
+ start = ++next;
+ continue;
+ }
+ }
+
+ // unescaping either one Ascii or possibly multiple Unicode
+
+ if (ch <= '\x7F')
+ {
+ //ASCII
+ dest[destPosition++] = ch;
+ next+=3;
+ start = next;
+ continue;
+ }
+
+ // Unicode
+
+ int byteCount = 1;
+ // lazy initialization of max size, will reuse the array for next sequences
+ if ((object) bytes == null)
+ bytes = new byte[end - next];
+
+ bytes[0] = (byte)ch;
+ next+=3;
+ while (next < end)
+ {
+ // Check on exit criterion
+ if ((ch = pStr[next]) != '%' || next+2 >= end)
+ break;
+
+ // already made sure we have 3 characters in str
+ ch = EscapedAscii(pStr[next+1], pStr[next+2]);
+
+ //invalid hex sequence ?
+ if (ch == Uri.c_DummyChar)
+ break;
+ // character is not part of a UTF-8 sequence ?
+ else if (ch < '\x80')
+ break;
+ else
+ {
+ //a UTF-8 sequence
+ bytes[byteCount++] = (byte)ch;
+ next += 3;
+ }
+ }
+ Encoding noFallbackCharUTF8 = (Encoding)Encoding.UTF8.Clone();
+ noFallbackCharUTF8.EncoderFallback = new EncoderReplacementFallback("");
+ noFallbackCharUTF8.DecoderFallback = new DecoderReplacementFallback("");
+
+ char[] unescapedChars = new char[bytes.Length];
+ int charCount = noFallbackCharUTF8.GetChars(bytes, 0, byteCount, unescapedChars, 0);
+
+ start = next;
+
+ // match exact bytes
+ // Do not unescape chars not allowed by Iri
+ // need to check for invalid utf sequences that may not have given any chars
+
+ MatchUTF8Sequence(pDest, dest, ref destPosition, unescapedChars, charCount, bytes,
+ byteCount, isQuery, iriParsing);
+ }
+
+ if (next == end)
+ goto done;
+ }
+dest_fixed_loop_break: ;
+ }
+ }
+
+done: return dest;
+ }
+
+ //
+ // Need to check for invalid utf sequences that may not have given any chars.
+ // We got the unescaped chars, we then reencode them and match off the bytes
+ // to get the invalid sequence bytes that we just copy off
+ //
+ internal static unsafe void MatchUTF8Sequence(char* pDest, char[] dest, ref int destOffset, char[] unescapedChars,
+ int charCount, byte[] bytes, int byteCount, bool isQuery, bool iriParsing)
+ {
+ int count = 0;
+ fixed (char* unescapedCharsPtr = unescapedChars)
+ {
+ for (int j = 0; j < charCount; ++j)
+ {
+ bool isHighSurr = Char.IsHighSurrogate(unescapedCharsPtr[j]);
+
+ byte[] encodedBytes = Encoding.UTF8.GetBytes(unescapedChars, j, isHighSurr ? 2 : 1);
+ int encodedBytesLength = encodedBytes.Length;
+
+ // we have to keep unicode chars outside Iri range escaped
+ bool inIriRange = false;
+ if (iriParsing)
+ {
+ if (!isHighSurr)
+ inIriRange = IriHelper.CheckIriUnicodeRange(unescapedChars[j], isQuery);
+ else
+ {
+ bool surrPair = false;
+ inIriRange = IriHelper.CheckIriUnicodeRange(unescapedChars[j], unescapedChars[j + 1],
+ ref surrPair, isQuery);
+ }
+ }
+
+ while (true)
+ {
+ // Escape any invalid bytes that were before this character
+ while (bytes[count] != encodedBytes[0])
+ {
+ Debug.Assert(dest.Length > destOffset, "Buffer overrun detected");
+ EscapeAsciiChar((char)bytes[count++], dest, ref destOffset);
+ }
+
+ // check if all bytes match
+ bool allBytesMatch = true;
+ int k = 0;
+ for (; k < encodedBytesLength; ++k)
+ {
+ if (bytes[count + k] != encodedBytes[k])
+ {
+ allBytesMatch = false;
+ break;
+ }
+ }
+
+ if (allBytesMatch)
+ {
+ count += encodedBytesLength;
+ if (iriParsing)
+ {
+ if (!inIriRange)
+ {
+ // need to keep chars not allowed as escaped
+ for (int l = 0; l < encodedBytes.Length; ++l)
+ {
+ Debug.Assert(dest.Length > destOffset, "Buffer overrun detected");
+ EscapeAsciiChar((char)encodedBytes[l], dest, ref destOffset);
+ }
+ }
+ else if (!Uri.IsBidiControlCharacter(unescapedCharsPtr[j]) || !UriParser.DontKeepUnicodeBidiFormattingCharacters)
+ {
+ //copy chars
+ Debug.Assert(dest.Length > destOffset, "Buffer overrun detected");
+ pDest[destOffset++] = unescapedCharsPtr[j];
+ if (isHighSurr)
+ {
+ Debug.Assert(dest.Length > destOffset, "Buffer overrun detected");
+ pDest[destOffset++] = unescapedCharsPtr[j + 1];
+ }
+ }
+ }
+ else
+ {
+ //copy chars
+ Debug.Assert(dest.Length > destOffset);
+ pDest[destOffset++] = unescapedCharsPtr[j];
+
+ if (isHighSurr)
+ {
+ Debug.Assert(dest.Length > destOffset, "Buffer overrun detected");
+ pDest[destOffset++] = unescapedCharsPtr[j + 1];
+ }
+ }
+
+ break; // break out of while (true) since we've matched this char bytes
+ }
+ else
+ {
+ // copy bytes till place where bytes dont match
+ for (int l = 0; l < k; ++l)
+ {
+ Debug.Assert(dest.Length > destOffset, "Buffer overrun detected");
+ EscapeAsciiChar((char)bytes[count++], dest, ref destOffset);
+ }
+ }
+ }
+
+ if (isHighSurr) j++;
+
+ }
+ }
+
+ // Include any trailing invalid sequences
+ while (count < byteCount)
+ {
+ Debug.Assert(dest.Length > destOffset, "Buffer overrun detected");
+ EscapeAsciiChar((char)bytes[count++], dest, ref destOffset);
+ }
+ }
+
+ internal static void EscapeAsciiChar(char ch, char[] to, ref int pos)
+ {
+ to[pos++] = '%';
+ to[pos++] = HexUpperChars[(ch & 0xf0) >> 4];
+ to[pos++] = HexUpperChars[ch & 0xf];
+ }
+
+ internal static char EscapedAscii(char digit, char next)
+ {
+ if (!(((digit >= '0') && (digit <= '9'))
+ || ((digit >= 'A') && (digit <= 'F'))
+ || ((digit >= 'a') && (digit <= 'f'))))
+ {
+ return Uri.c_DummyChar;
+ }
+
+ int res = (digit <= '9')
+ ? ((int)digit - (int)'0')
+ : (((digit <= 'F')
+ ? ((int)digit - (int)'A')
+ : ((int)digit - (int)'a'))
+ + 10);
+
+ if (!(((next >= '0') && (next <= '9'))
+ || ((next >= 'A') && (next <= 'F'))
+ || ((next >= 'a') && (next <= 'f'))))
+ {
+ return Uri.c_DummyChar;
+ }
+
+ return (char)((res << 4) + ((next <= '9')
+ ? ((int)next - (int)'0')
+ : (((next <= 'F')
+ ? ((int)next - (int)'A')
+ : ((int)next - (int)'a'))
+ + 10)));
+ }
+
+ internal const string RFC3986ReservedMarks = @";/?:@&=+$,#[]!'()*";
+ private const string RFC2396ReservedMarks = @";/?:@&=+$,";
+ private const string RFC3986UnreservedMarks = @"-_.~";
+ private const string RFC2396UnreservedMarks = @"-_.~*'()!";
+ private const string AdditionalUnsafeToUnescape = @"%\#";// While not specified as reserved, these are still unsafe to unescape.
+
+ // When unescaping in safe mode, do not unescape the RFC 3986 reserved set:
+ // gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+ // sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+ // / "*" / "+" / "," / ";" / "="
+ //
+ // In addition, do not unescape the following unsafe characters:
+ // excluded = "%" / "\"
+ //
+ // This implementation used to use the following variant of the RFC 2396 reserved set.
+ // That behavior is now off by default, but can be enabled by an App Context Switch.
+ // reserved = ";" | "/" | "?" | "@" | "&" | "=" | "+" | "$" | ","
+ // excluded = control | "#" | "%" | "\"
+ internal static bool IsNotSafeForUnescape(char ch)
+ {
+ if (ch <= '\x1F' || (ch >= '\x7F' && ch <= '\x9F'))
+ {
+ return true;
+ }
+ else if (UriParser.DontEnableStrictRFC3986ReservedCharacterSets)
+ {
+ if ((ch != ':' && (RFC2396ReservedMarks.IndexOf(ch) >= 0) || (AdditionalUnsafeToUnescape.IndexOf(ch) >= 0)))
+ {
+ return true;
+ }
+ }
+ else if ((RFC3986ReservedMarks.IndexOf(ch) >= 0) || (AdditionalUnsafeToUnescape.IndexOf(ch) >= 0))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static unsafe bool IsReservedUnreservedOrHash(char c)
+ {
+ if (IsUnreserved(c))
+ {
+ return true;
+ }
+ if (UriParser.ShouldUseLegacyV2Quirks)
+ {
+ return ((RFC2396ReservedMarks.IndexOf(c) >= 0) || c == '#');
+ }
+ return (RFC3986ReservedMarks.IndexOf(c) >= 0);
+ }
+
+ internal static unsafe bool IsUnreserved(char c)
+ {
+ if (Uri.IsAsciiLetterOrDigit(c))
+ {
+ return true;
+ }
+ if (UriParser.ShouldUseLegacyV2Quirks)
+ {
+ return (RFC2396UnreservedMarks.IndexOf(c) >= 0);
+ }
+ return (RFC3986UnreservedMarks.IndexOf(c) >= 0);
+ }
+
+ internal static bool Is3986Unreserved(char c)
+ {
+ if (Uri.IsAsciiLetterOrDigit(c))
+ {
+ return true;
+ }
+ return (RFC3986UnreservedMarks.IndexOf(c) >= 0);
+ }
+ }
+}
diff --git a/System/net/System/_UriSyntax.cs b/System/net/System/_UriSyntax.cs
new file mode 100644
index 0000000..07c34d3
--- /dev/null
+++ b/System/net/System/_UriSyntax.cs
@@ -0,0 +1,644 @@
+//------------------------------------------------------------------------------
+// <copyright file="_UriSyntax.cs" company="Microsoft">
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+ //
+ // This file utilizes partial class feature and contains
+ // only internal implementation of UriParser type
+ //
+
+namespace System {
+
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Runtime.Versioning;
+
+ // This enum specifies the Uri syntax flags that is understood by builtin Uri parser.
+ [Flags]
+ internal enum UriSyntaxFlags {
+ None = 0x0,
+
+ MustHaveAuthority = 0x1, // must have "//" after scheme:
+ OptionalAuthority = 0x2, // used by generic parser due to unknown Uri syntax
+ MayHaveUserInfo = 0x4,
+ MayHavePort = 0x8,
+ MayHavePath = 0x10,
+ MayHaveQuery = 0x20,
+ MayHaveFragment = 0x40,
+
+ AllowEmptyHost = 0x80,
+ AllowUncHost = 0x100,
+ AllowDnsHost = 0x200,
+ AllowIPv4Host = 0x400,
+ AllowIPv6Host = 0x800,
+ AllowAnInternetHost = AllowDnsHost|AllowIPv4Host|AllowIPv6Host,
+ AllowAnyOtherHost = 0x1000, // Relaxed authority syntax
+
+ FileLikeUri = 0x2000, //Special case to allow file:\\balbla or file://\\balbla
+ MailToLikeUri = 0x4000, //V1 parser inheritance mailTo:AuthorityButNoSlashes
+
+ V1_UnknownUri = 0x10000, // a Compatibility with V1 parser for an unknown scheme
+ SimpleUserSyntax = 0x20000, // It is safe to not call virtual UriParser methods
+ BuiltInSyntax = 0x40000, // This is a simple Uri plus it is hardcoded in the product
+ ParserSchemeOnly = 0x80000, // This is a Parser that does only Uri scheme parsing
+
+ AllowDOSPath = 0x100000, // will check for "x:\"
+ PathIsRooted = 0x200000, // For an authority based Uri the first path char is '/'
+ ConvertPathSlashes = 0x400000, // will turn '\' into '/'
+ CompressPath = 0x800000, // For an authority based Uri remove/compress /./ /../ in the path
+ CanonicalizeAsFilePath = 0x1000000, // remove/convert sequences /.../ /x../ /x./ dangerous for a DOS path
+ UnEscapeDotsAndSlashes = 0x2000000, // additionally unescape dots and slashes before doing path compression
+ AllowIdn = 0x4000000, // IDN host conversion allowed
+ AllowIriParsing = 0x10000000, // Iri parsing. String is normalized, bidi control
+ // characters are removed, unicode char limits are checked etc.
+
+// KeepTailLWS = 0x8000000,
+ }
+
+ //
+ // Only internal members are included here
+ //
+ public abstract partial class UriParser {
+ private static readonly Dictionary<String, UriParser> m_Table;
+ private static Dictionary<String, UriParser> m_TempTable;
+
+ private UriSyntaxFlags m_Flags;
+
+ // Some flags (specified in c_UpdatableFlags) besides being set in the ctor, can also be set at a later
+ // point. Such "updatable" flags can be set using SetUpdatableFlags(); if this method is called,
+ // the value specified in the ctor is ignored (i.e. for all c_UpdatableFlags the value in m_Flags is
+ // ignored), and the new value is used (i.e. for all c_UpdatableFlags the value in m_UpdatableFlags is used).
+ private volatile UriSyntaxFlags m_UpdatableFlags;
+ private volatile bool m_UpdatableFlagsUsed;
+
+ // The following flags can be updated at any time.
+ private const UriSyntaxFlags c_UpdatableFlags = UriSyntaxFlags.UnEscapeDotsAndSlashes;
+
+ private int m_Port;
+ private string m_Scheme;
+
+ internal const int NoDefaultPort = -1;
+ private const int c_InitialTableSize = 25;
+
+ // These are always available without paying hashtable lookup cost
+ // Note: see UpdateStaticSyntaxReference()
+ internal static UriParser HttpUri;
+ internal static UriParser HttpsUri;
+ internal static UriParser WsUri;
+ internal static UriParser WssUri;
+ internal static UriParser FtpUri;
+ internal static UriParser FileUri;
+ internal static UriParser GopherUri;
+ internal static UriParser NntpUri;
+ internal static UriParser NewsUri;
+ internal static UriParser MailToUri;
+ internal static UriParser UuidUri;
+ internal static UriParser TelnetUri;
+ internal static UriParser LdapUri;
+ internal static UriParser NetTcpUri;
+ internal static UriParser NetPipeUri;
+
+ internal static UriParser VsMacrosUri; //bad guy
+
+
+ private enum UriQuirksVersion {
+ // V1 = 1, // RFC 1738 - Not supported
+ V2 = 2, // RFC 2396
+ V3 = 3, // RFC 3986, 3987
+ }
+
+ // Store in a static field to allow for test manipulation and emergency workarounds via reflection.
+ // Note this is not placed in the Uri class in order to avoid circular static dependencies.
+ private static readonly UriQuirksVersion s_QuirksVersion =
+ (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5
+ // || BinaryCompatibility.TargetsAtLeast_Silverlight_V6
+ // || BinaryCompatibility.TargetsAtLeast_Phone_V8_0
+ ) ? UriQuirksVersion.V3 : UriQuirksVersion.V2;
+
+ internal static bool ShouldUseLegacyV2Quirks {
+ get {
+ return s_QuirksVersion <= UriQuirksVersion.V2;
+ }
+ }
+
+ internal static bool DontEnableStrictRFC3986ReservedCharacterSets
+ {
+ get {
+ return LocalAppContextSwitches.DontEnableStrictRFC3986ReservedCharacterSets;
+ }
+ }
+
+ internal static bool DontKeepUnicodeBidiFormattingCharacters
+ {
+ get
+ {
+ return LocalAppContextSwitches.DontKeepUnicodeBidiFormattingCharacters;
+ }
+ }
+
+ static UriParser() {
+
+ m_Table = new Dictionary<String, UriParser>(c_InitialTableSize);
+ m_TempTable = new Dictionary<String, UriParser>(c_InitialTableSize);
+
+ //Now we will call for the instance constructors that will interrupt this static one.
+
+ // Below we simulate calls into FetchSyntax() but avoid using lock() and other things redundant for a .cctor
+
+ HttpUri = new BuiltInUriParser("http", 80, HttpSyntaxFlags);
+ m_Table[HttpUri.SchemeName] = HttpUri; //HTTP
+
+ HttpsUri = new BuiltInUriParser("https", 443, HttpUri.m_Flags);
+ m_Table[HttpsUri.SchemeName] = HttpsUri; //HTTPS cloned from HTTP
+
+ WsUri = new BuiltInUriParser("ws", 80, HttpSyntaxFlags);
+ m_Table[WsUri.SchemeName] = WsUri; // WebSockets
+
+ WssUri = new BuiltInUriParser("wss", 443, HttpSyntaxFlags);
+ m_Table[WssUri.SchemeName] = WssUri; // Secure WebSockets
+
+ FtpUri = new BuiltInUriParser("ftp", 21, FtpSyntaxFlags);
+ m_Table[FtpUri.SchemeName] = FtpUri; //FTP
+
+ FileUri = new BuiltInUriParser("file", NoDefaultPort, FileSyntaxFlags);
+ m_Table[FileUri.SchemeName] = FileUri; //FILE
+
+ GopherUri = new BuiltInUriParser("gopher", 70, GopherSyntaxFlags);
+ m_Table[GopherUri.SchemeName] = GopherUri; //GOPHER
+
+ NntpUri = new BuiltInUriParser("nntp", 119, NntpSyntaxFlags);
+ m_Table[NntpUri.SchemeName] = NntpUri; //NNTP
+
+ NewsUri = new BuiltInUriParser("news", NoDefaultPort, NewsSyntaxFlags);
+ m_Table[NewsUri.SchemeName] = NewsUri; //NEWS
+
+ MailToUri = new BuiltInUriParser("mailto", 25, MailtoSyntaxFlags);
+ m_Table[MailToUri.SchemeName] = MailToUri; //MAILTO
+
+ UuidUri = new BuiltInUriParser("uuid", NoDefaultPort, NewsUri.m_Flags);
+ m_Table[UuidUri.SchemeName] = UuidUri; //UUID cloned from NEWS
+
+ TelnetUri = new BuiltInUriParser("telnet", 23, TelnetSyntaxFlags);
+ m_Table[TelnetUri.SchemeName] = TelnetUri; //TELNET
+
+ LdapUri = new BuiltInUriParser("ldap", 389, LdapSyntaxFlags);
+ m_Table[LdapUri.SchemeName] = LdapUri; //LDAP
+
+ NetTcpUri = new BuiltInUriParser("net.tcp", 808, NetTcpSyntaxFlags);
+ m_Table[NetTcpUri.SchemeName] = NetTcpUri;
+
+ NetPipeUri = new BuiltInUriParser("net.pipe", NoDefaultPort, NetPipeSyntaxFlags);
+ m_Table[NetPipeUri.SchemeName] = NetPipeUri;
+
+ VsMacrosUri = new BuiltInUriParser("vsmacros", NoDefaultPort, VsmacrosSyntaxFlags);
+ m_Table[VsMacrosUri.SchemeName] = VsMacrosUri; //VSMACROS
+
+ }
+ //
+ private class BuiltInUriParser: UriParser
+ {
+ //
+ // All BuiltIn parsers use that ctor. They are marked with "simple" and "built-in" flags
+ //
+ internal BuiltInUriParser(string lwrCaseScheme, int defaultPort, UriSyntaxFlags syntaxFlags)
+ : base ((syntaxFlags | UriSyntaxFlags.SimpleUserSyntax | UriSyntaxFlags.BuiltInSyntax))
+ {
+ m_Scheme = lwrCaseScheme;
+ m_Port = defaultPort;
+ }
+ }
+ //
+ internal UriSyntaxFlags Flags {
+ get {
+ return m_Flags;
+ }
+ }
+ //
+ internal bool NotAny(UriSyntaxFlags flags)
+ {
+ // Return true if none of the flags specified in 'flags' are set.
+ return IsFullMatch(flags, UriSyntaxFlags.None);
+ }
+ //
+ internal bool InFact(UriSyntaxFlags flags)
+ {
+ // Return true if at least one of the flags in 'flags' is set.
+ return !IsFullMatch(flags, UriSyntaxFlags.None);
+ }
+ //
+ internal bool IsAllSet(UriSyntaxFlags flags)
+ {
+ // Return true if all flags in 'flags' are set.
+ return IsFullMatch(flags, flags);
+ }
+
+ private bool IsFullMatch(UriSyntaxFlags flags, UriSyntaxFlags expected)
+ {
+ // Return true, if masking the current set of flags with 'flags' equals 'expected'.
+ // Definition 'current set of flags':
+ // a) if updatable flags were never set: m_Flags
+ // b) if updatable flags were set: set union between all flags in m_Flags which are not updatable
+ // (i.e. not part of c_UpdatableFlags) and all flags in m_UpdatableFlags
+
+ UriSyntaxFlags mergedFlags;
+
+ // if none of the flags in 'flags' is an updatable flag, we ignore m_UpdatableFlags
+ if (((flags & c_UpdatableFlags) == 0) || !m_UpdatableFlagsUsed)
+ {
+ mergedFlags = m_Flags;
+ }
+ else
+ {
+ // mask m_Flags to only use the flags not in c_UpdatableFlags
+ mergedFlags = (m_Flags & (~c_UpdatableFlags)) | m_UpdatableFlags;
+ }
+
+ return (mergedFlags & flags) == expected;
+ }
+
+ //
+ // Internal .ctor, any ctor eventually goes through this one
+ //
+ internal UriParser(UriSyntaxFlags flags)
+ {
+ m_Flags = flags;
+ m_Scheme = string.Empty;
+ }
+ //
+ private static void FetchSyntax(UriParser syntax, string lwrCaseSchemeName, int defaultPort)
+ {
+ if (syntax.SchemeName.Length != 0)
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_NeedFreshParser, syntax.SchemeName));
+
+ lock (m_Table)
+ {
+ syntax.m_Flags &= ~UriSyntaxFlags.V1_UnknownUri;
+ UriParser oldSyntax = null;
+ m_Table.TryGetValue(lwrCaseSchemeName, out oldSyntax);
+ if (oldSyntax != null)
+ throw new InvalidOperationException(SR.GetString(SR.net_uri_AlreadyRegistered, oldSyntax.SchemeName));
+
+ m_TempTable.TryGetValue(syntax.SchemeName, out oldSyntax);
+ if (oldSyntax != null)
+ {
+ // optimization on schemeName, will try to keep the first reference
+ lwrCaseSchemeName = oldSyntax.m_Scheme;
+ m_TempTable.Remove(lwrCaseSchemeName);
+ }
+
+ syntax.OnRegister(lwrCaseSchemeName, defaultPort);
+ syntax.m_Scheme = lwrCaseSchemeName;
+ syntax.CheckSetIsSimpleFlag();
+ syntax.m_Port = defaultPort;
+
+ m_Table[syntax.SchemeName] = syntax;
+ }
+ }
+ //
+ private const int c_MaxCapacity = 512;
+ //schemeStr must be in lower case!
+ internal static UriParser FindOrFetchAsUnknownV1Syntax(string lwrCaseScheme) {
+
+ // check may be other thread just added one
+ UriParser syntax = null;
+ m_Table.TryGetValue(lwrCaseScheme, out syntax);
+ if (syntax != null) {
+ return syntax;
+ }
+ m_TempTable.TryGetValue(lwrCaseScheme, out syntax);
+ if (syntax != null) {
+ return syntax;
+ }
+ lock (m_Table) {
+ // This is a bit paranoid but let's prevent static table growing infinitly
+ if (m_TempTable.Count >= c_MaxCapacity) {
+ m_TempTable = new Dictionary<String, UriParser>(c_InitialTableSize);
+ }
+ syntax = new BuiltInUriParser(lwrCaseScheme, NoDefaultPort, UnknownV1SyntaxFlags);
+ m_TempTable[lwrCaseScheme] = syntax;
+ return syntax;
+ }
+ }
+ //
+ internal static UriParser GetSyntax(string lwrCaseScheme) {
+ UriParser ret = null;
+ m_Table.TryGetValue(lwrCaseScheme, out ret);
+ if (ret == null) {
+ m_TempTable.TryGetValue(lwrCaseScheme, out ret);
+ }
+ return ret;
+ }
+ //
+ // Builtin and User Simple syntaxes do not need custom validation/parsing (i.e. virtual method calls),
+ //
+ internal bool IsSimple
+ {
+ get {
+ return InFact(UriSyntaxFlags.SimpleUserSyntax);
+ }
+ }
+ //
+ internal void CheckSetIsSimpleFlag()
+ {
+ Type type = this.GetType();
+
+ if ( type == typeof(GenericUriParser)
+ || type == typeof(HttpStyleUriParser)
+ || type == typeof(FtpStyleUriParser)
+ || type == typeof(FileStyleUriParser)
+ || type == typeof(NewsStyleUriParser)
+ || type == typeof(GopherStyleUriParser)
+ || type == typeof(NetPipeStyleUriParser)
+ || type == typeof(NetTcpStyleUriParser)
+ || type == typeof(LdapStyleUriParser)
+ )
+ {
+ m_Flags |= UriSyntaxFlags.SimpleUserSyntax;
+ }
+ }
+
+ //
+ // This method is used to update flags. The scenario where this is needed is when the user specifies
+ // flags in the config file. The config file is read after UriParser instances were created.
+ //
+ internal void SetUpdatableFlags(UriSyntaxFlags flags) {
+
+ Debug.Assert(!m_UpdatableFlagsUsed,
+ "SetUpdatableFlags() already called. It can only be called once per parser.");
+ Debug.Assert((flags & (~c_UpdatableFlags)) == 0, "Only updatable flags can be set.");
+
+ // No locks necessary. Reordering won't happen due to volatile.
+ m_UpdatableFlags = flags;
+ m_UpdatableFlagsUsed = true;
+ }
+
+ //
+ // These are simple internal wrappers that will call virtual protected methods
+ // (to avoid "protected internal" siganures in the public docs)
+ //
+ internal UriParser InternalOnNewUri()
+ {
+ UriParser effectiveParser = OnNewUri();
+ if ((object)this != (object)effectiveParser)
+ {
+ effectiveParser.m_Scheme = m_Scheme;
+ effectiveParser.m_Port = m_Port;
+ effectiveParser.m_Flags = m_Flags;
+ }
+ return effectiveParser;
+ }
+
+ //
+ internal void InternalValidate(Uri thisUri, out UriFormatException parsingError)
+ {
+ InitializeAndValidate(thisUri, out parsingError);
+ }
+
+ //
+ internal string InternalResolve(Uri thisBaseUri, Uri uriLink, out UriFormatException parsingError)
+ {
+ return Resolve(thisBaseUri, uriLink, out parsingError);
+ }
+
+ //
+ internal bool InternalIsBaseOf(Uri thisBaseUri, Uri uriLink)
+ {
+ return IsBaseOf(thisBaseUri, uriLink);
+ }
+
+ //
+ internal string InternalGetComponents(Uri thisUri, UriComponents uriComponents, UriFormat uriFormat)
+ {
+ return GetComponents(thisUri, uriComponents, uriFormat);
+ }
+
+ //
+ internal bool InternalIsWellFormedOriginalString(Uri thisUri)
+ {
+ return IsWellFormedOriginalString(thisUri);
+ }
+
+ //
+ // Various Uri scheme syntax flags
+ //
+ private const UriSyntaxFlags UnknownV1SyntaxFlags =
+ UriSyntaxFlags.V1_UnknownUri | // This flag must be always set here
+ UriSyntaxFlags.OptionalAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveQuery |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowEmptyHost |
+ UriSyntaxFlags.AllowUncHost | //
+ UriSyntaxFlags.AllowAnInternetHost |
+ // UriSyntaxFlags.AllowAnyOtherHost | // V1.1 has a bug and so does not support this case
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowDOSPath | //
+ UriSyntaxFlags.ConvertPathSlashes | // V1 compat, it will always convert backslashes
+ UriSyntaxFlags.CompressPath | // V1 compat, it will always compress path even for non hierarchical Uris
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+ private static readonly UriSyntaxFlags HttpSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveQuery |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowUncHost | //
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ //
+ UriSyntaxFlags.ConvertPathSlashes |
+ UriSyntaxFlags.CompressPath |
+ UriSyntaxFlags.CanonicalizeAsFilePath |
+ (UriParser.ShouldUseLegacyV2Quirks
+ ? UriSyntaxFlags.UnEscapeDotsAndSlashes : UriSyntaxFlags.None) |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+ private const UriSyntaxFlags FtpSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowUncHost | //
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ //
+ UriSyntaxFlags.ConvertPathSlashes |
+ UriSyntaxFlags.CompressPath |
+ UriSyntaxFlags.CanonicalizeAsFilePath|
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+ private static readonly UriSyntaxFlags FileSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.AllowEmptyHost |
+ UriSyntaxFlags.AllowUncHost |
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ (UriParser.ShouldUseLegacyV2Quirks
+ ? UriSyntaxFlags.None : UriSyntaxFlags.MayHaveQuery) |
+ //
+ UriSyntaxFlags.FileLikeUri |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowDOSPath |
+ //
+ UriSyntaxFlags.ConvertPathSlashes |
+ UriSyntaxFlags.CompressPath |
+ UriSyntaxFlags.CanonicalizeAsFilePath |
+ UriSyntaxFlags.UnEscapeDotsAndSlashes |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+ // bad guy
+ private const UriSyntaxFlags VsmacrosSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.AllowEmptyHost |
+ UriSyntaxFlags.AllowUncHost |
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.FileLikeUri |
+ //
+ UriSyntaxFlags.AllowDOSPath |
+ UriSyntaxFlags.ConvertPathSlashes |
+ UriSyntaxFlags.CompressPath |
+ UriSyntaxFlags.CanonicalizeAsFilePath |
+ UriSyntaxFlags.UnEscapeDotsAndSlashes |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+ private const UriSyntaxFlags GopherSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowUncHost | //
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+// UriSyntaxFlags.KeepTailLWS |
+
+ //Note that NNTP and NEWS are quite different in syntax
+ private const UriSyntaxFlags NewsSyntaxFlags =
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ UriSyntaxFlags.AllowIriParsing;
+
+ private const UriSyntaxFlags NntpSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo|
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowUncHost | //
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+ private const UriSyntaxFlags TelnetSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.MayHaveUserInfo|
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.AllowUncHost | //
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+ private const UriSyntaxFlags LdapSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ //
+ UriSyntaxFlags.AllowEmptyHost |
+ UriSyntaxFlags.AllowUncHost | //
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveQuery |
+ UriSyntaxFlags.MayHaveFragment |
+ //
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+ private const UriSyntaxFlags MailtoSyntaxFlags =
+ //
+ UriSyntaxFlags.AllowEmptyHost |
+ UriSyntaxFlags.AllowUncHost | //
+ UriSyntaxFlags.AllowAnInternetHost |
+ //
+ UriSyntaxFlags.MayHaveUserInfo |
+ UriSyntaxFlags.MayHavePort |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveFragment |
+ UriSyntaxFlags.MayHaveQuery | //to maintain everett compat
+ //
+ UriSyntaxFlags.MailToLikeUri |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+
+ private const UriSyntaxFlags NetPipeSyntaxFlags =
+ UriSyntaxFlags.MustHaveAuthority |
+ UriSyntaxFlags.MayHavePath |
+ UriSyntaxFlags.MayHaveQuery |
+ UriSyntaxFlags.MayHaveFragment |
+ UriSyntaxFlags.AllowAnInternetHost |
+ UriSyntaxFlags.PathIsRooted |
+ UriSyntaxFlags.ConvertPathSlashes |
+ UriSyntaxFlags.CompressPath |
+ UriSyntaxFlags.CanonicalizeAsFilePath |
+ UriSyntaxFlags.UnEscapeDotsAndSlashes |
+ UriSyntaxFlags.AllowIdn |
+ UriSyntaxFlags.AllowIriParsing;
+
+
+ private const UriSyntaxFlags NetTcpSyntaxFlags = NetPipeSyntaxFlags | UriSyntaxFlags.MayHavePort;
+
+ }
+}