3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 // <OWNER>Microsoft</OWNER>
10 // Implementation of membership condition for zones
13 namespace System
.Security
.Util
{
16 using System
.Collections
;
17 using System
.Collections
.Generic
;
18 using System
.Runtime
.CompilerServices
;
19 using System
.Runtime
.InteropServices
;
20 using System
.Runtime
.Versioning
;
21 using System
.Runtime
.Serialization
;
22 using System
.Globalization
;
25 using System
.Diagnostics
.Contracts
;
28 internal sealed class URLString
: SiteString
30 private String m_protocol
;
31 [OptionalField(VersionAdded
= 2)]
32 private String m_userpass
;
33 private SiteString m_siteString
;
35 private LocalSiteString m_localSite
;
36 private DirectoryString m_directory
;
38 private const String m_defaultProtocol
= "file";
40 [OptionalField(VersionAdded
= 2)]
41 private bool m_parseDeferred
;
42 [OptionalField(VersionAdded
= 2)]
43 private String m_urlOriginal
;
44 [OptionalField(VersionAdded
= 2)]
45 private bool m_parsedOriginal
;
47 [OptionalField(VersionAdded
= 3)]
48 private bool m_isUncShare
;
50 // legacy field from v1.x, not used in v2 and beyond. Retained purely for serialization compatability.
51 private String m_fullurl
;
55 public void OnDeserialized(StreamingContext ctx
)
58 if (m_urlOriginal
== null)
60 // pre-v2 deserialization. Need to fix-up fields here
61 m_parseDeferred
= false;
62 m_parsedOriginal
= false; // Dont care what this value is - never used
64 m_urlOriginal
= m_fullurl
;
69 private void OnSerializing(StreamingContext ctx
)
72 if ((ctx
.State
& ~
(StreamingContextStates
.Clone
|StreamingContextStates
.CrossAppDomain
)) != 0)
75 m_fullurl
= m_urlOriginal
;
79 private void OnSerialized(StreamingContext ctx
)
81 if ((ctx
.State
& ~
(StreamingContextStates
.Clone
|StreamingContextStates
.CrossAppDomain
)) != 0)
91 m_siteString
= new SiteString();
94 m_directory
= new DirectoryString();
95 m_parseDeferred
= false;
98 private void DoDeferredParse()
102 ParseString(m_urlOriginal
, m_parsedOriginal
);
103 m_parseDeferred
= false;
107 public URLString(string url
) : this(url
, false, false) {}
108 public URLString(string url
, bool parsed
) : this(url
, parsed
, false) {}
110 internal URLString(string url
, bool parsed
, bool doDeferredParsing
)
116 m_parsedOriginal
= parsed
;
117 m_parseDeferred
= true;
118 if (doDeferredParsing
)
122 // Converts %XX and %uYYYY to the actual characters (I.e. Unesacpes any escape characters present in the URL)
123 private String
UnescapeURL(String url
)
125 StringBuilder intermediate
= StringBuilderCache
.Acquire(url
.Length
);
126 int Rindex
= 0; // index into temp that gives the rest of the string to be processed
130 braIndex
= url
.IndexOf('[',Rindex
);
132 ketIndex
= url
.IndexOf(']', braIndex
);
136 index
= url
.IndexOf( '%', Rindex
);
140 intermediate
= intermediate
.Append(url
, Rindex
, (url
.Length
- Rindex
));
143 // if we hit a '%' in the middle of an IPv6 address, dont process that
144 if (index
> braIndex
&& index
< ketIndex
)
146 intermediate
= intermediate
.Append(url
, Rindex
, (ketIndex
- Rindex
+1));
151 if (url
.Length
- index
< 2) // Check that there is at least 1 char after the '%'
152 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
154 if (url
[index
+1] == 'u' || url
[index
+1] == 'U')
156 if (url
.Length
- index
< 6) // example: "%u004d" is 6 chars long
157 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
159 // We have a unicode character specified in hex
163 char c
= (char)(Hex
.ConvertHexDigit( url
[index
+2] ) << 12 |
164 Hex
.ConvertHexDigit( url
[index
+3] ) << 8 |
165 Hex
.ConvertHexDigit( url
[index
+4] ) << 4 |
166 Hex
.ConvertHexDigit( url
[index
+5] ));
167 intermediate
= intermediate
.Append(url
, Rindex
, index
- Rindex
);
168 intermediate
= intermediate
.Append(c
);
170 catch(ArgumentException
) // Hex.ConvertHexDigit can throw an "out of range" ArgumentException
172 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
175 Rindex
= index
+ 6 ; //update the 'seen' length
179 // we have a hex character.
181 if (url
.Length
- index
< 3) // example: "%4d" is 3 chars long
182 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
186 char c
= (char)(Hex
.ConvertHexDigit( url
[index
+1] ) << 4 | Hex
.ConvertHexDigit( url
[index
+2] ));
188 intermediate
= intermediate
.Append(url
, Rindex
, index
- Rindex
);
189 intermediate
= intermediate
.Append(c
);
191 catch(ArgumentException
) // Hex.ConvertHexDigit can throw an "out of range" ArgumentException
193 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
196 Rindex
= index
+ 3; // update the 'seen' length
201 return StringBuilderCache
.GetStringAndRelease(intermediate
);
204 // Helper Function for ParseString:
205 // Search for the end of the protocol info and grab the actual protocol string
206 // ex. http://www.microsoft.com/complus would have a protocol string of http
207 private String
ParseProtocol(String url
)
210 int index
= url
.IndexOf( ':' );
214 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
216 else if (index
== -1)
218 m_protocol
= m_defaultProtocol
;
221 else if (url
.Length
> index
+ 1)
223 if (index
== m_defaultProtocol
.Length
&&
224 String
.Compare(url
, 0, m_defaultProtocol
, 0, index
, StringComparison
.OrdinalIgnoreCase
) == 0)
226 m_protocol
= m_defaultProtocol
;
227 temp
= url
.Substring( index
+ 1 );
229 // Since an explicit file:// URL could be immediately followed by a host name, we will be
230 // conservative and assume that it is on a share rather than a potentally relative local
234 else if (url
[index
+1] != '\\')
236 if (url
.Length
> index
+ 2 &&
237 url
[index
+1] == '/' &&
240 m_protocol
= url
.Substring( 0, index
);
242 for (int i
= 0; i
< m_protocol
.Length
; ++i
)
244 char c
= m_protocol
[i
];
246 if ((c
>= 'a' && c
<= 'z') ||
247 (c
>= 'A' && c
<= 'Z') ||
248 (c
>= '0' && c
<= '9') ||
257 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
260 temp
= url
.Substring( index
+ 3 );
264 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
269 m_protocol
= m_defaultProtocol
;
275 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
281 private String
ParsePort(String url
)
284 char[] separators
= new char[] { ':', '/' }
;
286 int userpassIndex
= temp
.IndexOf('@');
287 if (userpassIndex
!= -1) {
288 if (temp
.IndexOf('/',0,userpassIndex
) == -1) {
289 // this is a user:pass type of string
290 m_userpass
= temp
.Substring(0,userpassIndex
);
291 Rindex
= userpassIndex
+ 1;
298 braIndex
= url
.IndexOf('[',Rindex
);
300 ketIndex
= url
.IndexOf(']', braIndex
);
303 // IPv6 address...ignore the IPv6 block when searching for the port
304 portIndex
= temp
.IndexOfAny(separators
,ketIndex
);
308 portIndex
= temp
.IndexOfAny(separators
,Rindex
);
313 if (portIndex
!= -1 && temp
[portIndex
] == ':')
315 // make sure it really is a port, and has a number after the :
316 if ( temp
[portIndex
+1] >= '0' && temp
[portIndex
+1] <= '9' )
318 int tempIndex
= temp
.IndexOf( '/', Rindex
);
322 m_port
= Int32
.Parse( temp
.Substring(portIndex
+ 1), CultureInfo
.InvariantCulture
);
325 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
327 temp
= temp
.Substring( Rindex
, portIndex
- Rindex
);
329 else if (tempIndex
> portIndex
)
331 m_port
= Int32
.Parse( temp
.Substring(portIndex
+ 1, tempIndex
- portIndex
- 1), CultureInfo
.InvariantCulture
);
332 temp
= temp
.Substring( Rindex
, portIndex
- Rindex
) + temp
.Substring( tempIndex
);
335 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
338 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
341 // Chop of the user/pass portion if any
342 temp
= temp
.Substring(Rindex
);
348 // This does three things:
349 // 1. It makes the following modifications to the start of the string:
350 // a. \\?\ and \\?/ => <empty>
351 // b. \\.\ and \\./ => <empty>
352 // 2. If isFileUrl is true, converts all slashes to front slashes and strips leading
353 // front slashes. See comment by code.
354 // 3. Throws a PathTooLongException if the length of the resulting URL is >= MAX_PATH.
355 // This is done to prevent security issues due to canonicalization truncations.
356 // Remove this method when the Path class supports "\\?\"
357 internal static string PreProcessForExtendedPathRemoval(string url
, bool isFileUrl
)
359 return PreProcessForExtendedPathRemoval(checkPathLength
: true, url
: url
, isFileUrl
: isFileUrl
);
362 internal static string PreProcessForExtendedPathRemoval(bool checkPathLength
, string url
, bool isFileUrl
)
364 bool isUncShare
= false;
365 return PreProcessForExtendedPathRemoval(checkPathLength
: checkPathLength
, url
: url
, isFileUrl
: isFileUrl
, isUncShare
: ref isUncShare
);
368 // Keeping this signature to avoid reflection breaks
369 private static string PreProcessForExtendedPathRemoval(string url
, bool isFileUrl
, ref bool isUncShare
)
371 return PreProcessForExtendedPathRemoval(checkPathLength
: true, url
: url
, isFileUrl
: isFileUrl
, isUncShare
: ref isUncShare
);
374 private static string PreProcessForExtendedPathRemoval(bool checkPathLength
, string url
, bool isFileUrl
, ref bool isUncShare
)
376 // This is the modified URL that we will return
377 StringBuilder modifiedUrl
= new StringBuilder(url
);
379 // ITEM 1 - remove extended path characters.
381 // Keep track of where we are in both the comparison and altered strings.
385 // If all the '\' have already been converted to '/', just check for //?/ or //./
386 if ((url
.Length
- curCmpIdx
) >= 4 &&
387 (String
.Compare(url
, curCmpIdx
, "//?/", 0, 4, StringComparison
.OrdinalIgnoreCase
) == 0 ||
388 String
.Compare(url
, curCmpIdx
, "//./", 0, 4, StringComparison
.OrdinalIgnoreCase
) == 0))
390 modifiedUrl
.Remove(curModIdx
, 4);
396 // We need to handle an indefinite number of leading front slashes for file URLs since we could
397 // get something like:
402 while (url
[curCmpIdx
] == '/')
409 // Remove the extended path characters
410 if ((url
.Length
- curCmpIdx
) >= 4 &&
411 (String
.Compare(url
, curCmpIdx
, "\\\\?\\", 0, 4, StringComparison
.OrdinalIgnoreCase
) == 0 ||
412 String
.Compare(url
, curCmpIdx
, "\\\\?/", 0, 4, StringComparison
.OrdinalIgnoreCase
) == 0 ||
413 String
.Compare(url
, curCmpIdx
, "\\\\.\\", 0, 4, StringComparison
.OrdinalIgnoreCase
) == 0 ||
414 String
.Compare(url
, curCmpIdx
, "\\\\./", 0, 4, StringComparison
.OrdinalIgnoreCase
) == 0))
416 modifiedUrl
.Remove(curModIdx
, 4);
422 // ITEM 2 - convert all slashes to forward slashes, and strip leading slashes.
426 bool seenFirstBackslash
= false;
428 while (slashCount
< modifiedUrl
.Length
&& (modifiedUrl
[slashCount
] == '/' || modifiedUrl
[slashCount
] == '\\'))
430 // Look for sets of consecutive backslashes. We can't just look for these at the start
431 // of the string, since file:// might come first. Instead, once we see the first \, look
432 // for a second one following it.
433 if (!seenFirstBackslash
&& modifiedUrl
[slashCount
] == '\\')
435 seenFirstBackslash
= true;
436 if (slashCount
+ 1 < modifiedUrl
.Length
&& modifiedUrl
[slashCount
+ 1] == '\\')
443 modifiedUrl
.Remove(0, slashCount
);
444 modifiedUrl
.Replace('\\', '/');
447 // ITEM 3 - If the path is greater than or equal (due to terminating NULL in windows) MAX_PATH, we throw.
450 // This needs to be a separate method to avoid hitting the static constructor on AppContextSwitches
451 CheckPathTooLong(modifiedUrl
);
454 // Create the result string from the StringBuilder
455 return modifiedUrl
.ToString();
458 [MethodImpl(MethodImplOptions
.NoInlining
)]
459 private static void CheckPathTooLong(StringBuilder path
)
461 if (path
.Length
>= (AppContextSwitches
.BlockLongPaths
? PathInternal
.MaxShortPath
: PathInternal
.MaxLongPath
))
463 throw new PathTooLongException(Environment
.GetResourceString("IO.PathTooLong"));
467 // Do any misc massaging of data in the URL
468 private String
PreProcessURL(String url
, bool isFileURL
)
471 // Remove when the Path class supports "\\?\"
472 url
= PreProcessForExtendedPathRemoval(url
, true, ref m_isUncShare
);
475 url
= url
.Replace('\\', '/');
480 private void ParseFileURL(String url
)
484 int index
= temp
.IndexOf( '/');
488 temp
[index
-1] != ':' &&
489 temp
[index
-1] != '|') ||
491 index
!= temp
.Length
- 1)
493 // Also, if it is a UNC share, we want m_localSite to
494 // be of the form "computername/share", so if the first
495 // fileEnd character found is a slash, do some more parsing
496 // to find the proper end character.
498 int tempIndex
= temp
.IndexOf( '/', index
+1);
510 localSite
= temp
.Substring(0,index
);
512 if (localSite
.Length
== 0)
513 throw new ArgumentException( Environment
.GetResourceString( "Argument_InvalidUrl" ) );
518 if (localSite
[0] == '\\' && localSite
[1] == '\\')
520 spacesAllowed
= true;
526 spacesAllowed
= false;
529 bool useSmallCharToUpper
= true;
531 for (; i
< localSite
.Length
; ++i
)
533 char c
= localSite
[i
];
535 if ((c
>= 'A' && c
<= 'Z') ||
536 (c
>= 'a' && c
<= 'z') ||
537 (c
>= '0' && c
<= '9') ||
538 (c
== '-') || (c
== '/') ||
539 (c
== ':') || (c
== '|') ||
540 (c
== '.') || (c
== '*') ||
541 (c
== '$') || (spacesAllowed
&& c
== ' '))
547 useSmallCharToUpper
= false;
552 if (useSmallCharToUpper
)
553 localSite
= String
.SmallCharToUpper( localSite
);
555 localSite
= localSite
.ToUpper(CultureInfo
.InvariantCulture
);
557 m_localSite
= new LocalSiteString( localSite
);
561 if (localSite
[localSite
.Length
-1] == '*')
562 m_directory
= new DirectoryString( "*", false );
564 m_directory
= new DirectoryString();
568 String directoryString
= temp
.Substring( index
+ 1 );
569 if (directoryString
.Length
== 0)
571 m_directory
= new DirectoryString();
575 m_directory
= new DirectoryString( directoryString
, true);
583 private void ParseNonFileURL(String url
)
586 int index
= temp
.IndexOf('/');
590 m_localSite
= null; // for drive letter
591 m_siteString
= new SiteString( temp
);
592 m_directory
= new DirectoryString();
596 String site
= temp
.Substring( 0, index
);
598 m_siteString
= new SiteString( site
);
600 String directoryString
= temp
.Substring( index
+ 1 );
602 if (directoryString
.Length
== 0)
604 m_directory
= new DirectoryString();
608 m_directory
= new DirectoryString( directoryString
, false );
614 void DoFastChecks( String url
)
618 throw new ArgumentNullException( "url" );
620 Contract
.EndContractBlock();
624 throw new FormatException(Environment
.GetResourceString("Format_StringZeroLength"));
629 // 1. We support URLs that follow the common Internet scheme syntax
630 // (<scheme>://user:pass@<host>:<port>/<url-path>) and all windows file URLs.
631 // 2. In the general case we parse of the site and create a SiteString out of it
632 // (which supports our wildcarding scheme). In the case of files we don't support
633 // wildcarding and furthermore SiteString doesn't like ':' and '|' which can appear
634 // in file urls so we just keep that info in a separate string and set the
635 // SiteString to null.
637 // ex. http://www.microsoft.com/complus -> m_siteString = "www.microsoft.com" m_localSite = null
638 // ex. file:///c:/complus/mscorlib.dll -> m_siteString = null m_localSite = "c:"
639 // ex. file:///c|/complus/mscorlib.dll -> m_siteString = null m_localSite = "c:"
640 void ParseString( String url
, bool parsed
)
642 // If there are any escaped hex or unicode characters in the url, translate those
643 // into the proper character.
647 url
= UnescapeURL(url
);
650 // Identify the protocol and strip the protocol info from the string, if present.
651 String temp
= ParseProtocol(url
);
653 bool fileProtocol
= (String
.Compare( m_protocol
, "file", StringComparison
.OrdinalIgnoreCase
) == 0);
655 // handle any special preocessing...removing extra characters, etc.
656 temp
= PreProcessURL(temp
, fileProtocol
);
664 // Check if there is a port number and parse that out.
665 temp
= ParsePort(temp
);
666 ParseNonFileURL(temp
);
667 // Note: that we allow DNS and Netbios names for non-file protocols (since sitestring will check
668 // that the hostname satisfies these two protocols. DNS-only checking can theoretically be added
669 // here but that would break all the programs that use '_' (which is fairly common, yet illegal).
670 // If this needs to be done at any point, add a call to m_siteString.IsLegalDNSName().
692 if (m_siteString
!= null)
694 return m_siteString
.ToString();
698 return m_localSite
.ToString();
712 return m_port
.ToString(CultureInfo
.InvariantCulture
);
716 public String Directory
722 return m_directory
.ToString();
727 /// Make a best guess at determining if this is URL refers to a file with a relative path. Since
728 /// this is a guess to help out users of UrlMembershipCondition who may accidentally supply a
729 /// relative URL, we'd rather err on the side of absolute than relative. (We'd rather accept some
730 /// meaningless membership conditions rather than reject meaningful ones).
732 /// In order to be a relative file URL, the URL needs to have a protocol of file, and not be on a
735 /// If both of the above are true, then the heuristics we'll use to detect an absolute URL are:
736 /// 1. A host name which is:
737 /// a. greater than one character and ends in a colon (representing the drive letter) OR
738 /// b. ends with a * (so we match any file with the given prefix if any)
739 /// 2. Has a directory name (cannot be simply file://c:)
741 public bool IsRelativeFileUrl
747 if (String
.Equals(m_protocol
, "file", StringComparison
.OrdinalIgnoreCase
) && !m_isUncShare
)
749 string host
= m_localSite
!= null ? m_localSite
.ToString() : null;
750 // If the host name ends with the * character, treat this as an absolute URL since the *
751 // could represent the rest of the full path.
752 if (host
.EndsWith('*'))
754 string directory
= m_directory
!= null ? m_directory
.ToString() : null;
756 return host
== null || host
.Length
< 2 || !host
.EndsWith(':') ||
757 String
.IsNullOrEmpty(directory
);
760 // Since this is not a local URL, it cannot be relative
765 public String
GetFileName()
769 if (String
.Compare( m_protocol
, "file", StringComparison
.OrdinalIgnoreCase
) != 0)
772 String intermediateDirectory
= this.Directory
.Replace( '/', '\\' );
774 String directory
= this.Host
.Replace( '/', '\\' );
776 int directorySlashIndex
= directory
.IndexOf( '\\' );
777 if (directorySlashIndex
== -1)
779 if (directory
.Length
!= 2 ||
780 !(directory
[1] == ':' || directory
[1] == '|'))
782 directory
= "\\\\" + directory
;
785 else if (directorySlashIndex
!= 2 ||
786 (directorySlashIndex
== 2 && directory
[1] != ':' && directory
[1] != '|'))
788 directory
= "\\\\" + directory
;
791 directory
+= "\\" + intermediateDirectory
;
797 public String
GetDirectoryName()
801 if (String
.Compare( m_protocol
, "file", StringComparison
.OrdinalIgnoreCase
) != 0)
804 String intermediateDirectory
= this.Directory
.Replace( '/', '\\' );
807 for (int i
= intermediateDirectory
.Length
; i
> 0; i
--)
809 if (intermediateDirectory
[i
-1] == '\\')
816 String directory
= this.Host
.Replace( '/', '\\' );
818 int directorySlashIndex
= directory
.IndexOf( '\\' );
819 if (directorySlashIndex
== -1)
821 if (directory
.Length
!= 2 ||
822 !(directory
[1] == ':' || directory
[1] == '|'))
824 directory
= "\\\\" + directory
;
827 else if (directorySlashIndex
> 2 ||
828 (directorySlashIndex
== 2 && directory
[1] != ':' && directory
[1] != '|'))
830 directory
= "\\\\" + directory
;
837 directory
+= intermediateDirectory
.Substring( 0, slashIndex
);
843 public override SiteString
Copy()
845 return new URLString( m_urlOriginal
, m_parsedOriginal
);
848 [ResourceExposure(ResourceScope
.None
)]
849 [ResourceConsumption(ResourceScope
.Machine
, ResourceScope
.Machine
)]
850 public override bool IsSubsetOf( SiteString site
)
857 URLString url
= site
as URLString
;
865 url
.DoDeferredParse();
867 URLString normalUrl1
= this.SpecialNormalizeUrl();
868 URLString normalUrl2
= url
.SpecialNormalizeUrl();
870 if (String
.Compare( normalUrl1
.m_protocol
, normalUrl2
.m_protocol
, StringComparison
.OrdinalIgnoreCase
) == 0 &&
871 normalUrl1
.m_directory
.IsSubsetOf( normalUrl2
.m_directory
))
873 if (normalUrl1
.m_localSite
!= null)
875 // We do a little extra processing in here for local files since we allow
876 // both <drive_letter>: and <drive_letter>| forms of urls.
878 return normalUrl1
.m_localSite
.IsSubsetOf( normalUrl2
.m_localSite
);
882 if (normalUrl1
.m_port
!= normalUrl2
.m_port
)
885 return normalUrl2
.m_siteString
!= null && normalUrl1
.m_siteString
.IsSubsetOf( normalUrl2
.m_siteString
);
894 public override String
ToString()
896 return m_urlOriginal
;
899 public override bool Equals(Object o
)
903 if (o
== null || !(o
is URLString
))
906 return this.Equals( (URLString
)o
);
909 public override int GetHashCode()
913 TextInfo info
= CultureInfo
.InvariantCulture
.TextInfo
;
916 if (this.m_protocol
!= null)
917 accumulator
= info
.GetCaseInsensitiveHashCode( this.m_protocol
);
919 if (this.m_localSite
!= null)
921 accumulator
= accumulator ^
this.m_localSite
.GetHashCode();
925 accumulator
= accumulator ^
this.m_siteString
.GetHashCode();
927 accumulator
= accumulator ^
this.m_directory
.GetHashCode();
932 public bool Equals( URLString url
)
934 return CompareUrls( this, url
);
937 [ResourceExposure(ResourceScope
.None
)]
938 [ResourceConsumption(ResourceScope
.Machine
, ResourceScope
.Machine
)]
939 public static bool CompareUrls( URLString url1
, URLString url2
)
941 if (url1
== null && url2
== null)
944 if (url1
== null || url2
== null)
947 url1
.DoDeferredParse();
948 url2
.DoDeferredParse();
950 URLString normalUrl1
= url1
.SpecialNormalizeUrl();
951 URLString normalUrl2
= url2
.SpecialNormalizeUrl();
953 // Compare protocol (case insensitive)
955 if (String
.Compare( normalUrl1
.m_protocol
, normalUrl2
.m_protocol
, StringComparison
.OrdinalIgnoreCase
) != 0)
958 // Do special processing for file urls
960 if (String
.Compare( normalUrl1
.m_protocol
, "file", StringComparison
.OrdinalIgnoreCase
) == 0)
962 if (!normalUrl1
.m_localSite
.IsSubsetOf( normalUrl2
.m_localSite
) ||
963 !normalUrl2
.m_localSite
.IsSubsetOf( normalUrl1
.m_localSite
))
968 if (String
.Compare( normalUrl1
.m_userpass
, normalUrl2
.m_userpass
, StringComparison
.Ordinal
) != 0)
971 if (!normalUrl1
.m_siteString
.IsSubsetOf( normalUrl2
.m_siteString
) ||
972 !normalUrl2
.m_siteString
.IsSubsetOf( normalUrl1
.m_siteString
))
975 if (url1
.m_port
!= url2
.m_port
)
979 if (!normalUrl1
.m_directory
.IsSubsetOf( normalUrl2
.m_directory
) ||
980 !normalUrl2
.m_directory
.IsSubsetOf( normalUrl1
.m_directory
))
986 internal String
NormalizeUrl()
989 StringBuilder builtUrl
= StringBuilderCache
.Acquire();
991 if (String
.Compare( m_protocol
, "file", StringComparison
.OrdinalIgnoreCase
) == 0)
993 builtUrl
= builtUrl
.AppendFormat("FILE:///{0}/{1}", m_localSite
.ToString(), m_directory
.ToString());
997 builtUrl
= builtUrl
.AppendFormat("{0}://{1}{2}", m_protocol
, m_userpass
, m_siteString
.ToString());
1000 builtUrl
= builtUrl
.AppendFormat("{0}",m_port
);
1002 builtUrl
= builtUrl
.AppendFormat("/{0}", m_directory
.ToString());
1005 return StringBuilderCache
.GetStringAndRelease(builtUrl
).ToUpper(CultureInfo
.InvariantCulture
);
1008 [System
.Security
.SecuritySafeCritical
] // auto-generated
1009 [ResourceExposure(ResourceScope
.Machine
)]
1010 [ResourceConsumption(ResourceScope
.Machine
)]
1011 internal URLString
SpecialNormalizeUrl()
1013 // Under WinXP, file protocol urls can be mapped to
1014 // drives that aren't actually file protocol underneath
1015 // due to drive mounting. This code attempts to figure
1016 // out what a drive is mounted to and create the
1020 if (String
.Compare( m_protocol
, "file", StringComparison
.OrdinalIgnoreCase
) != 0)
1026 String localSite
= m_localSite
.ToString();
1028 if (localSite
.Length
== 2 &&
1029 (localSite
[1] == '|' ||
1030 localSite
[1] == ':'))
1032 String deviceName
= null;
1033 GetDeviceName(localSite
, JitHelpers
.GetStringHandleOnStack(ref deviceName
));
1035 if (deviceName
!= null)
1037 if (deviceName
.IndexOf( "://", StringComparison
.Ordinal
) != -1)
1039 URLString u
= new URLString( deviceName
+ "/" + this.m_directory
.ToString() );
1040 u
.DoDeferredParse(); // Presumably the caller of SpecialNormalizeUrl wants a fully parsed URL
1045 URLString u
= new URLString( "file://" + deviceName
+ "/" + this.m_directory
.ToString() );
1046 u
.DoDeferredParse();// Presumably the caller of SpecialNormalizeUrl wants a fully parsed URL
1060 [System
.Security
.SecurityCritical
] // auto-generated
1061 [ResourceExposure(ResourceScope
.Machine
)]
1062 [DllImport(JitHelpers
.QCall
, CharSet
= CharSet
.Unicode
)]
1063 [SuppressUnmanagedCodeSecurity
]
1064 private static extern void GetDeviceName( String driveLetter
, StringHandleOnStack retDeviceName
);
1069 internal class DirectoryString
: SiteString
1071 private bool m_checkForIllegalChars
;
1073 private new static char[] m_separators
= { '/' }
;
1075 // From KB #Q177506, file/folder illegal characters are \ / : * ? " < > |
1076 protected static char[] m_illegalDirectoryCharacters
= { '\\', ':', '*', '?', '"', '<', '>', '|' }
;
1078 public DirectoryString()
1081 m_separatedSite
= new ArrayList();
1084 public DirectoryString( String directory
, bool checkForIllegalChars
)
1087 m_checkForIllegalChars
= checkForIllegalChars
;
1088 m_separatedSite
= CreateSeparatedString(directory
);
1091 private ArrayList
CreateSeparatedString(String directory
)
1093 if (directory
== null || directory
.Length
== 0)
1095 throw new ArgumentException(Environment
.GetResourceString("Argument_InvalidDirectoryOnUrl"));
1097 Contract
.EndContractBlock();
1099 ArrayList list
= new ArrayList();
1100 String
[] separatedArray
= directory
.Split(m_separators
);
1102 for (int index
= 0; index
< separatedArray
.Length
; ++index
)
1104 if (separatedArray
[index
] == null || separatedArray
[index
].Equals( "" ))
1106 // this case is fine, we just ignore it the extra separators.
1108 else if (separatedArray
[index
].Equals( "*" ))
1110 if (index
!= separatedArray
.Length
-1)
1112 throw new ArgumentException(Environment
.GetResourceString("Argument_InvalidDirectoryOnUrl"));
1114 list
.Add( separatedArray
[index
] );
1116 else if (m_checkForIllegalChars
&& separatedArray
[index
].IndexOfAny( m_illegalDirectoryCharacters
) != -1)
1118 throw new ArgumentException(Environment
.GetResourceString("Argument_InvalidDirectoryOnUrl"));
1122 list
.Add( separatedArray
[index
] );
1129 public virtual bool IsSubsetOf( DirectoryString operand
)
1131 return this.IsSubsetOf( operand
, true );
1134 public virtual bool IsSubsetOf( DirectoryString operand
, bool ignoreCase
)
1136 if (operand
== null)
1140 else if (operand
.m_separatedSite
.Count
== 0)
1142 return this.m_separatedSite
.Count
== 0 || this.m_separatedSite
.Count
> 0 && String
.Compare((String
)this.m_separatedSite
[0], "*", StringComparison
.Ordinal
) == 0;
1144 else if (this.m_separatedSite
.Count
== 0)
1146 return String
.Compare((String
)operand
.m_separatedSite
[0], "*", StringComparison
.Ordinal
) == 0;
1150 return base.IsSubsetOf( operand
, ignoreCase
);
1156 internal class LocalSiteString
: SiteString
1158 private new static char[] m_separators
= { '/' }
;
1160 public LocalSiteString( String site
)
1162 m_site
= site
.Replace( '|', ':');
1164 if (m_site
.Length
> 2 && m_site
.IndexOf( ':' ) != -1)
1165 throw new ArgumentException(Environment
.GetResourceString("Argument_InvalidDirectoryOnUrl"));
1167 m_separatedSite
= CreateSeparatedString(m_site
);
1170 private ArrayList
CreateSeparatedString(String directory
)
1172 if (directory
== null || directory
.Length
== 0)
1174 throw new ArgumentException(Environment
.GetResourceString("Argument_InvalidDirectoryOnUrl"));
1176 Contract
.EndContractBlock();
1178 ArrayList list
= new ArrayList();
1179 String
[] separatedArray
= directory
.Split(m_separators
);
1181 for (int index
= 0; index
< separatedArray
.Length
; ++index
)
1183 if (separatedArray
[index
] == null || separatedArray
[index
].Equals( "" ))
1186 directory
[index
] == '/')
1190 else if (index
!= separatedArray
.Length
-1)
1192 throw new ArgumentException(Environment
.GetResourceString("Argument_InvalidDirectoryOnUrl"));
1195 else if (separatedArray
[index
].Equals( "*" ))
1197 if (index
!= separatedArray
.Length
-1)
1199 throw new ArgumentException(Environment
.GetResourceString("Argument_InvalidDirectoryOnUrl"));
1201 list
.Add( separatedArray
[index
] );
1205 list
.Add( separatedArray
[index
] );
1212 public virtual bool IsSubsetOf( LocalSiteString operand
)
1214 return this.IsSubsetOf( operand
, true );
1217 public virtual bool IsSubsetOf( LocalSiteString operand
, bool ignoreCase
)
1219 if (operand
== null)
1223 else if (operand
.m_separatedSite
.Count
== 0)
1225 return this.m_separatedSite
.Count
== 0 || this.m_separatedSite
.Count
> 0 && String
.Compare((String
)this.m_separatedSite
[0], "*", StringComparison
.Ordinal
) == 0;
1227 else if (this.m_separatedSite
.Count
== 0)
1229 return String
.Compare((String
)operand
.m_separatedSite
[0], "*", StringComparison
.Ordinal
) == 0;
1233 return base.IsSubsetOf( operand
, ignoreCase
);