Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / mscorlib / system / security / util / urlstring.cs
blob9178585f19e17273a81f5697f625bd640e47b112
1 // ==++==
2 //
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 //
5 // ==--==
6 // URLString
7 //
8 // <OWNER>Microsoft</OWNER>
9 //
10 // Implementation of membership condition for zones
13 namespace System.Security.Util {
15 using System;
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;
23 using System.Text;
24 using System.IO;
25 using System.Diagnostics.Contracts;
27 [Serializable]
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;
34 private int m_port;
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;
54 [OnDeserialized]
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
63 m_userpass = "";
64 m_urlOriginal = m_fullurl;
65 m_fullurl = null;
68 [OnSerializing]
69 private void OnSerializing(StreamingContext ctx)
72 if ((ctx.State & ~(StreamingContextStates.Clone|StreamingContextStates.CrossAppDomain)) != 0)
74 DoDeferredParse();
75 m_fullurl = m_urlOriginal;
78 [OnSerialized]
79 private void OnSerialized(StreamingContext ctx)
81 if ((ctx.State & ~(StreamingContextStates.Clone|StreamingContextStates.CrossAppDomain)) != 0)
83 m_fullurl = null;
87 public URLString()
89 m_protocol = "";
90 m_userpass = "";
91 m_siteString = new SiteString();
92 m_port = -1;
93 m_localSite = null;
94 m_directory = new DirectoryString();
95 m_parseDeferred = false;
98 private void DoDeferredParse()
100 if (m_parseDeferred)
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)
112 m_port = -1;
113 m_userpass = "";
114 DoFastChecks(url);
115 m_urlOriginal = url;
116 m_parsedOriginal = parsed;
117 m_parseDeferred = true;
118 if (doDeferredParsing)
119 DoDeferredParse();
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
127 int index;
128 int braIndex = -1;
129 int ketIndex = -1;
130 braIndex = url.IndexOf('[',Rindex);
131 if (braIndex != -1)
132 ketIndex = url.IndexOf(']', braIndex);
136 index = url.IndexOf( '%', Rindex);
138 if (index == -1)
140 intermediate = intermediate.Append(url, Rindex, (url.Length - Rindex));
141 break;
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));
147 Rindex = ketIndex+1;
148 continue;
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
177 else
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" ) );
184 try
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
200 while (true);
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)
209 String temp;
210 int index = url.IndexOf( ':' );
212 if (index == 0)
214 throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
216 else if (index == -1)
218 m_protocol = m_defaultProtocol;
219 temp = url;
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
231 // URL.
232 m_isUncShare = true;
234 else if (url[index+1] != '\\')
236 if (url.Length > index + 2 &&
237 url[index+1] == '/' &&
238 url[index+2] == '/')
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') ||
249 (c == '+') ||
250 (c == '.') ||
251 (c == '-'))
253 continue;
255 else
257 throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
260 temp = url.Substring( index + 3 );
262 else
264 throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
267 else
269 m_protocol = m_defaultProtocol;
270 temp = url;
273 else
275 throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
278 return temp;
281 private String ParsePort(String url)
283 String temp = url;
284 char[] separators = new char[] { ':', '/' };
285 int Rindex = 0;
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;
295 int braIndex = -1;
296 int ketIndex = -1;
297 int portIndex = -1;
298 braIndex = url.IndexOf('[',Rindex);
299 if (braIndex != -1)
300 ketIndex = url.IndexOf(']', braIndex);
301 if (ketIndex != -1)
303 // IPv6 address...ignore the IPv6 block when searching for the port
304 portIndex = temp.IndexOfAny(separators,ketIndex);
306 else
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);
320 if (tempIndex == -1)
322 m_port = Int32.Parse( temp.Substring(portIndex + 1), CultureInfo.InvariantCulture );
324 if (m_port < 0)
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 );
334 else
335 throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
337 else
338 throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
340 else {
341 // Chop of the user/pass portion if any
342 temp = temp.Substring(Rindex);
345 return temp;
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.
382 int curCmpIdx = 0;
383 int curModIdx = 0;
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);
391 curCmpIdx += 4;
393 else
395 if (isFileUrl) {
396 // We need to handle an indefinite number of leading front slashes for file URLs since we could
397 // get something like:
398 // file://\\?\
399 // file:/\\?\
400 // file:\\?\
401 // etc...
402 while (url[curCmpIdx] == '/')
404 curCmpIdx++;
405 curModIdx++;
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);
417 curCmpIdx += 4;
422 // ITEM 2 - convert all slashes to forward slashes, and strip leading slashes.
423 if (isFileUrl)
425 int slashCount = 0;
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] == '\\')
437 isUncShare = true;
440 slashCount++;
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.
448 if (checkPathLength)
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)
470 if (isFileURL) {
471 // Remove when the Path class supports "\\?\"
472 url = PreProcessForExtendedPathRemoval(url, true, ref m_isUncShare);
474 else {
475 url = url.Replace('\\', '/');
477 return url;
480 private void ParseFileURL(String url)
482 String temp = url;
484 int index = temp.IndexOf( '/');
486 if (index != -1 &&
487 ((index == 2 &&
488 temp[index-1] != ':' &&
489 temp[index-1] != '|') ||
490 index != 2) &&
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);
500 if (tempIndex != -1)
501 index = tempIndex;
502 else
503 index = -1;
506 String localSite;
507 if (index == -1)
508 localSite = temp;
509 else
510 localSite = temp.Substring(0,index);
512 if (localSite.Length == 0)
513 throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
515 int i;
516 bool spacesAllowed;
518 if (localSite[0] == '\\' && localSite[1] == '\\')
520 spacesAllowed = true;
521 i = 2;
523 else
525 i = 0;
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 == ' '))
543 continue;
545 else
547 useSmallCharToUpper = false;
548 break;
552 if (useSmallCharToUpper)
553 localSite = String.SmallCharToUpper( localSite );
554 else
555 localSite = localSite.ToUpper(CultureInfo.InvariantCulture);
557 m_localSite = new LocalSiteString( localSite );
559 if (index == -1)
561 if (localSite[localSite.Length-1] == '*')
562 m_directory = new DirectoryString( "*", false );
563 else
564 m_directory = new DirectoryString();
566 else
568 String directoryString = temp.Substring( index + 1 );
569 if (directoryString.Length == 0)
571 m_directory = new DirectoryString();
573 else
575 m_directory = new DirectoryString( directoryString, true);
579 m_siteString = null;
580 return;
583 private void ParseNonFileURL(String url)
585 String temp = url;
586 int index = temp.IndexOf('/');
588 if (index == -1)
590 m_localSite = null; // for drive letter
591 m_siteString = new SiteString( temp );
592 m_directory = new DirectoryString();
594 else
596 String site = temp.Substring( 0, index );
597 m_localSite = null;
598 m_siteString = new SiteString( site );
600 String directoryString = temp.Substring( index + 1 );
602 if (directoryString.Length == 0)
604 m_directory = new DirectoryString();
606 else
608 m_directory = new DirectoryString( directoryString, false );
611 return;
614 void DoFastChecks( String url )
616 if (url == null)
618 throw new ArgumentNullException( "url" );
620 Contract.EndContractBlock();
622 if (url.Length == 0)
624 throw new FormatException(Environment.GetResourceString("Format_StringZeroLength"));
628 // NOTE:
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.
645 if (!parsed)
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);
658 if (fileProtocol)
660 ParseFileURL(temp);
662 else
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().
676 public String Scheme
680 DoDeferredParse();
682 return m_protocol;
686 public String Host
690 DoDeferredParse();
692 if (m_siteString != null)
694 return m_siteString.ToString();
696 else
698 return m_localSite.ToString();
703 public String Port
705 get
707 DoDeferredParse();
709 if (m_port == -1)
710 return null;
711 else
712 return m_port.ToString(CultureInfo.InvariantCulture);
716 public String Directory
720 DoDeferredParse();
722 return m_directory.ToString();
726 /// <summary>
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).
731 ///
732 /// In order to be a relative file URL, the URL needs to have a protocol of file, and not be on a
733 /// UNC share.
734 ///
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:)
740 /// </summary>
741 public bool IsRelativeFileUrl
745 DoDeferredParse();
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('*'))
753 return false;
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
761 return false;
765 public String GetFileName()
767 DoDeferredParse();
769 if (String.Compare( m_protocol, "file", StringComparison.OrdinalIgnoreCase) != 0)
770 return null;
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;
793 return directory;
797 public String GetDirectoryName()
799 DoDeferredParse();
801 if (String.Compare( m_protocol, "file", StringComparison.OrdinalIgnoreCase ) != 0)
802 return null;
804 String intermediateDirectory = this.Directory.Replace( '/', '\\' );
806 int slashIndex = 0;
807 for (int i = intermediateDirectory.Length; i > 0; i--)
809 if (intermediateDirectory[i-1] == '\\')
811 slashIndex = i;
812 break;
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;
833 directory += "\\";
835 if (slashIndex > 0)
837 directory += intermediateDirectory.Substring( 0, slashIndex );
840 return directory;
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 )
852 if (site == null)
854 return false;
857 URLString url = site as URLString;
859 if (url == null)
861 return false;
864 DoDeferredParse();
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 );
880 else
882 if (normalUrl1.m_port != normalUrl2.m_port)
883 return false;
885 return normalUrl2.m_siteString != null && normalUrl1.m_siteString.IsSubsetOf( normalUrl2.m_siteString );
888 else
890 return false;
894 public override String ToString()
896 return m_urlOriginal;
899 public override bool Equals(Object o)
901 DoDeferredParse();
903 if (o == null || !(o is URLString))
904 return false;
905 else
906 return this.Equals( (URLString)o );
909 public override int GetHashCode()
911 DoDeferredParse();
913 TextInfo info = CultureInfo.InvariantCulture.TextInfo;
914 int accumulator = 0;
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();
923 else
925 accumulator = accumulator ^ this.m_siteString.GetHashCode();
927 accumulator = accumulator ^ this.m_directory.GetHashCode();
929 return accumulator;
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)
942 return true;
944 if (url1 == null || url2 == null)
945 return false;
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)
956 return false;
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 ))
964 return false;
966 else
968 if (String.Compare( normalUrl1.m_userpass, normalUrl2.m_userpass, StringComparison.Ordinal) != 0)
969 return false;
971 if (!normalUrl1.m_siteString.IsSubsetOf( normalUrl2.m_siteString ) ||
972 !normalUrl2.m_siteString.IsSubsetOf( normalUrl1.m_siteString ))
973 return false;
975 if (url1.m_port != url2.m_port)
976 return false;
979 if (!normalUrl1.m_directory.IsSubsetOf( normalUrl2.m_directory ) ||
980 !normalUrl2.m_directory.IsSubsetOf( normalUrl1.m_directory ))
981 return false;
983 return true;
986 internal String NormalizeUrl()
988 DoDeferredParse();
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());
995 else
997 builtUrl = builtUrl.AppendFormat("{0}://{1}{2}", m_protocol, m_userpass, m_siteString.ToString());
999 if (m_port != -1)
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
1017 // url is maps to.
1019 DoDeferredParse();
1020 if (String.Compare( m_protocol, "file", StringComparison.OrdinalIgnoreCase) != 0)
1022 return this;
1024 else
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
1041 return u;
1043 else
1045 URLString u = new URLString( "file://" + deviceName + "/" + this.m_directory.ToString() );
1046 u.DoDeferredParse();// Presumably the caller of SpecialNormalizeUrl wants a fully parsed URL
1047 return u;
1050 else
1051 return this;
1053 else
1055 return this;
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 );
1068 [Serializable]
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()
1080 m_site = "";
1081 m_separatedSite = new ArrayList();
1084 public DirectoryString( String directory, bool checkForIllegalChars )
1086 m_site = directory;
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"));
1120 else
1122 list.Add( separatedArray[index] );
1126 return list;
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)
1138 return false;
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;
1148 else
1150 return base.IsSubsetOf( operand, ignoreCase );
1155 [Serializable]
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( "" ))
1185 if (index < 2 &&
1186 directory[index] == '/')
1188 list.Add( "//" );
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] );
1203 else
1205 list.Add( separatedArray[index] );
1209 return list;
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)
1221 return false;
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;
1231 else
1233 return base.IsSubsetOf( operand, ignoreCase );