|
//------------------------------------------------------------------------------
// <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
|