Merge pull request #225 from mistoll/master
[mono.git] / mcs / class / System / System / Uri.cs
1 //
2 // System.Uri
3 //
4 // Authors:
5 //    Lawrence Pit (loz@cable.a2000.nl)
6 //    Garrett Rooney (rooneg@electricjellyfish.net)
7 //    Ian MacLean (ianm@activestate.com)
8 //    Ben Maurer (bmaurer@users.sourceforge.net)
9 //    Atsushi Enomoto (atsushi@ximian.com)
10 //    Sebastien Pouliot  <sebastien@ximian.com>
11 //    Stephane Delcroix  <stephane@delcroix.org>
12 //
13 // (C) 2001 Garrett Rooney
14 // (C) 2003 Ian MacLean
15 // (C) 2003 Ben Maurer
16 // Copyright (C) 2003,2009 Novell, Inc (http://www.novell.com)
17 // Copyright (c) 2009 Stephane Delcroix
18 //
19 // Permission is hereby granted, free of charge, to any person obtaining
20 // a copy of this software and associated documentation files (the
21 // "Software"), to deal in the Software without restriction, including
22 // without limitation the rights to use, copy, modify, merge, publish,
23 // distribute, sublicense, and/or sell copies of the Software, and to
24 // permit persons to whom the Software is furnished to do so, subject to
25 // the following conditions:
26 // 
27 // The above copyright notice and this permission notice shall be
28 // included in all copies or substantial portions of the Software.
29 // 
30 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
31 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
33 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
34 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
35 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
36 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 //
38 // See RFC 2396 for more info on URI's.
39 //
40 // TODO: optimize by parsing host string only once
41 //
42 using System.ComponentModel;
43 using System.IO;
44 using System.Net;
45 using System.Runtime.Serialization;
46 using System.Text;
47 using System.Collections;
48 using System.Collections.Generic;
49 using System.Globalization;
50
51 //
52 // Disable warnings on Obsolete methods being used
53 //
54 #pragma warning disable 612
55
56 namespace System {
57
58         [Serializable]
59         [TypeConverter (typeof (UriTypeConverter))]
60         public class Uri : ISerializable {
61                 // NOTES:
62                 // o  scheme excludes the scheme delimiter
63                 // o  port is -1 to indicate no port is defined
64                 // o  path is empty or starts with / when scheme delimiter == "://"
65                 // o  query is empty or starts with ? char, escaped.
66                 // o  fragment is empty or starts with # char, unescaped.
67                 // o  all class variables are in escaped format when they are escapable,
68                 //    except cachedToString.
69                 // o  UNC is supported, as starts with "\\" for windows,
70                 //    or "//" with unix.
71
72                 private bool isUnixFilePath;
73                 private string source;
74                 private string scheme = String.Empty;
75                 private string host = String.Empty;
76                 private int port = -1;
77                 private string path = String.Empty;
78                 private string query = String.Empty;
79                 private string fragment = String.Empty;
80                 private string userinfo;
81                 private bool isUnc;
82                 private bool isOpaquePart;
83                 private bool isAbsoluteUri = true;
84                 private long scope_id;
85
86                 private List<string> segments;
87                 
88                 private bool userEscaped;
89                 private string cachedAbsoluteUri;
90                 private string cachedToString;
91                 private string cachedLocalPath;
92                 private int cachedHashCode;
93
94                 private static readonly string hexUpperChars = "0123456789ABCDEF";
95                 private static readonly string [] Empty = new string [0];
96                 private static bool isWin32 = (Path.DirectorySeparatorChar == '\\');
97
98         
99                 // Fields
100                 
101                 public static readonly string SchemeDelimiter = "://";
102                 public static readonly string UriSchemeFile = "file";
103                 public static readonly string UriSchemeFtp = "ftp";
104                 public static readonly string UriSchemeGopher = "gopher";
105                 public static readonly string UriSchemeHttp = "http";
106                 public static readonly string UriSchemeHttps = "https";
107                 public static readonly string UriSchemeMailto = "mailto";
108                 public static readonly string UriSchemeNews = "news";
109                 public static readonly string UriSchemeNntp = "nntp";
110                 public static readonly string UriSchemeNetPipe = "net.pipe";
111                 public static readonly string UriSchemeNetTcp = "net.tcp";
112
113                 // Constructors         
114
115 #if MOONLIGHT
116                 public Uri (string uriString) : this (uriString, UriKind.Absolute) 
117                 {
118                 }
119 #else
120                 public Uri (string uriString) : this (uriString, false) 
121                 {
122                 }
123 #endif
124                 protected Uri (SerializationInfo serializationInfo, StreamingContext streamingContext)
125                 {
126                         string uri = serializationInfo.GetString ("AbsoluteUri");
127                         if (uri.Length > 0) {
128                                 source = uri;
129                                 ParseUri(UriKind.Absolute);
130                         } else {
131                                 uri = serializationInfo.GetString ("RelativeUri");
132                                 if (uri.Length > 0) {
133                                         source = uri;
134                                         ParseUri(UriKind.Relative);
135                                 } else {
136                                         throw new ArgumentException("Uri string was null or empty.");
137                                 }
138                         }
139                 }
140
141                 public Uri (string uriString, UriKind uriKind)
142                 {
143                         source = uriString;
144                         ParseUri (uriKind);
145
146                         switch (uriKind) {
147                         case UriKind.Absolute:
148                                 if (!IsAbsoluteUri)
149                                         throw new UriFormatException("Invalid URI: The format of the URI could not be "
150                                                 + "determined.");
151                                 break;
152                         case UriKind.Relative:
153                                 if (IsAbsoluteUri)
154                                         throw new UriFormatException("Invalid URI: The format of the URI could not be "
155                                                 + "determined because the parameter 'uriString' represents an absolute URI.");
156                                 break;
157                         case UriKind.RelativeOrAbsolute:
158                                 break;
159                         default:
160                                 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
161                                 throw new ArgumentException (msg);
162                         }
163                 }
164
165                 //
166                 // An exception-less constructor, returns success
167                 // condition on the out parameter `success'.
168                 //
169                 Uri (string uriString, UriKind uriKind, out bool success)
170                 {
171                         if (uriString == null) {
172                                 success = false;
173                                 return;
174                         }
175
176                         if (uriKind != UriKind.RelativeOrAbsolute &&
177                                 uriKind != UriKind.Absolute &&
178                                 uriKind != UriKind.Relative) {
179                                 string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
180                                 throw new ArgumentException (msg);
181                         }
182
183                         source = uriString;
184                         if (ParseNoExceptions (uriKind, uriString) != null)
185                                 success = false;
186                         else {
187                                 success = true;
188                                 
189                                 switch (uriKind) {
190                                 case UriKind.Absolute:
191                                         if (!IsAbsoluteUri)
192                                                 success = false;
193                                         break;
194                                 case UriKind.Relative:
195                                         if (IsAbsoluteUri)
196                                                 success = false;
197                                         break;
198                                 case UriKind.RelativeOrAbsolute:
199                                         break;
200                                 default:
201                                         success = false;
202                                         break;
203                                 }
204                         }
205                 }
206
207                 public Uri (Uri baseUri, Uri relativeUri)
208                 {
209                         Merge (baseUri, relativeUri == null ? String.Empty : relativeUri.OriginalString);
210                         // FIXME: this should call UriParser.Resolve
211                 }
212
213                 // note: doc says that dontEscape is always false but tests show otherwise
214                 [Obsolete]
215                 public Uri (string uriString, bool dontEscape) 
216                 {
217                         userEscaped = dontEscape;
218                         source = uriString;
219                         ParseUri (UriKind.Absolute);
220                         if (!isAbsoluteUri)
221                                 throw new UriFormatException("Invalid URI: The format of the URI could not be "
222                                         + "determined: " + uriString);
223                 }
224
225                 public Uri (Uri baseUri, string relativeUri) 
226                 {
227                         Merge (baseUri, relativeUri);
228                         // FIXME: this should call UriParser.Resolve
229                 }
230
231                 [Obsolete ("dontEscape is always false")]
232                 public Uri (Uri baseUri, string relativeUri, bool dontEscape) 
233                 {
234                         userEscaped = dontEscape;
235                         Merge (baseUri, relativeUri);
236                 }
237
238                 private void Merge (Uri baseUri, string relativeUri)
239                 {
240                         if (baseUri == null)
241                                 throw new ArgumentNullException ("baseUri");
242                         if (!baseUri.IsAbsoluteUri)
243                                 throw new ArgumentOutOfRangeException ("baseUri");
244                         if (relativeUri == null)
245                                 relativeUri = String.Empty;
246
247                         // See RFC 2396 Par 5.2 and Appendix C
248
249                         // Check Windows UNC (for // it is scheme/host separator)
250                         if (relativeUri.Length >= 2 && relativeUri [0] == '\\' && relativeUri [1] == '\\') {
251                                 source = relativeUri;
252                                 ParseUri (UriKind.Absolute);
253                                 return;
254                         }
255
256                         int pos = relativeUri.IndexOf (':');
257                         if (pos != -1) {
258
259                                 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
260
261                                 // pos2 < 0 ... e.g. mailto
262                                 // pos2 > pos ... to block ':' in query part
263                                 if (pos2 > pos || pos2 < 0) {
264                                         // in some cases, it is equivanent to new Uri (relativeUri, dontEscape):
265                                         // 1) when the URI scheme in the 
266                                         // relative path is different from that
267                                         // of the baseUri, or
268                                         // 2) the URI scheme is non-standard
269                                         // ones (non-standard URIs are always
270                                         // treated as absolute here), or
271                                         // 3) the relative URI path is absolute.
272                                         if (String.CompareOrdinal (baseUri.Scheme, 0, relativeUri, 0, pos) != 0 ||
273                                             !IsPredefinedScheme (baseUri.Scheme) ||
274                                             (relativeUri.Length > pos + 1 && relativeUri [pos + 1] == '/')) {
275                                                 Uri tmp = null;
276                                                 if (Uri.TryCreate (relativeUri, UriKind.Absolute, out tmp)) {
277                                                         source = relativeUri;
278                                                         ParseUri (UriKind.Absolute);
279                                                         return;
280                                                 } else if (pos == 1) {
281                                                         // special case as this looks like a windows path
282                                                         string msg = ParseAsWindowsAbsoluteFilePath (relativeUri);
283                                                         if (msg != null)
284                                                                 throw new UriFormatException (msg);
285                                                 }
286                                                 // otherwise continue with 'full' relativeUri
287                                         }
288                                         else
289                                                 relativeUri = relativeUri.Substring (pos + 1);
290                                 }
291                         }
292
293                         this.scheme = baseUri.scheme;
294                         this.host = baseUri.host;
295                         this.port = baseUri.port;
296                         this.userinfo = baseUri.userinfo;
297                         this.isUnc = baseUri.isUnc;
298                         this.isUnixFilePath = baseUri.isUnixFilePath;
299                         this.isOpaquePart = baseUri.isOpaquePart;
300
301                         if (relativeUri.Length == 0) {
302                                 this.path = baseUri.path;
303                                 this.query = baseUri.query;
304                                 this.fragment = baseUri.fragment;
305                                 return;
306                         }
307
308                         // 8 fragment
309                         // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
310                         string original_fragment = String.Empty;
311                         pos = relativeUri.IndexOf ('#');
312                         if (pos != -1) {
313                                 original_fragment = relativeUri.Substring (pos);
314                                 if (userEscaped)
315                                         fragment = original_fragment;
316                                 else
317                                         fragment = "#" + EscapeString (relativeUri.Substring (pos+1));
318                                 relativeUri = pos == 0 ? String.Empty : relativeUri.Substring (0, pos);
319                         }
320
321                         bool consider_query = false;
322
323                         // 6 query
324                         pos = relativeUri.IndexOf ('?');
325                         if (pos != -1) {
326                                 query = relativeUri.Substring (pos);
327                                 if (!userEscaped)
328                                         query = EscapeString (query);
329 #if !NET_4_0 && !MOONLIGHT && !MOBILE
330                                 consider_query = query.Length > 0;
331 #endif
332                                 relativeUri = pos == 0 ? String.Empty : relativeUri.Substring (0, pos);
333                         } else if (relativeUri.Length == 0) {
334                                 // if there is no relative path then we keep the Query and Fragment from the absolute
335                                 query = baseUri.query;
336                         }
337
338                         if (relativeUri.Length > 0 && relativeUri [0] == '/') {
339                                 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
340                                         source = scheme + ':' + relativeUri;
341                                         ParseUri (UriKind.Absolute);
342                                         return;
343                                 } else {
344                                         path = relativeUri;
345                                         if (!userEscaped)
346                                                 path = EscapeString (path);
347                                         return;
348                                 }
349                         }
350                         
351                         // par 5.2 step 6 a)
352                         path = baseUri.path;
353                         if ((relativeUri.Length > 0) || consider_query) {
354                                 pos = path.LastIndexOf ('/');
355                                 if (pos >= 0) 
356                                         path = path.Substring (0, pos + 1);
357                         }
358
359                         if (relativeUri.Length == 0) {
360                                 // when merging URI the OriginalString is not quite original
361                                 source = GetLeftPart (UriPartial.Authority) + query + original_fragment;
362                                 return;
363                         }
364         
365                         // 6 b)
366                         path += relativeUri;
367
368                         // 6 c)
369                         int startIndex = 0;
370                         while (true) {
371                                 pos = path.IndexOf ("./", startIndex);
372                                 if (pos == -1)
373                                         break;
374                                 if (pos == 0)
375                                         path = path.Remove (0, 2);
376                                 else if (path [pos - 1] != '.')
377                                         path = path.Remove (pos, 2);
378                                 else
379                                         startIndex = pos + 1;
380                         }
381                         
382                         // 6 d)
383                         if (path.Length > 1 && 
384                             path [path.Length - 1] == '.' &&
385                             path [path.Length - 2] == '/')
386                                 path = path.Remove (path.Length - 1, 1);
387                         
388                         // 6 e)
389                         startIndex = 0;
390                         while (true) {
391                                 pos = path.IndexOf ("/../", startIndex);
392                                 if (pos == -1)
393                                         break;
394                                 if (pos == 0) {
395                                         startIndex = 3;
396                                         continue;
397                                 }
398                                 int pos2 = path.LastIndexOf ('/', pos - 1);
399                                 if (pos2 == -1) {
400                                         startIndex = pos + 1;
401                                 } else {
402                                         if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
403                                                 path = path.Remove (pos2 + 1, pos - pos2 + 3);
404                                         else
405                                                 startIndex = pos + 1;
406                                 }
407                         }
408                         
409                         // 6 f)
410                         if (path.Length > 3 && path.EndsWith ("/..")) {
411                                 pos = path.LastIndexOf ('/', path.Length - 4);
412                                 if (pos != -1)
413                                         if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
414                                                 path = path.Remove (pos + 1, path.Length - pos - 1);
415                         }
416                         
417                         // 6 g)
418                         while (path.StartsWith ("/../", StringComparison.Ordinal))
419                                 path = path.Substring (3);
420                         
421                         if (!userEscaped)
422                                 path = EscapeString (path);
423
424                         // when merging URI the OriginalString is not quite original
425                         source = GetLeftPart (UriPartial.Authority) + path + query + original_fragment;
426                 }
427                 
428                 // Properties
429                 
430                 public string AbsolutePath { 
431                         get {
432                                 EnsureAbsoluteUri ();
433                                 if (scheme == "mailto" || scheme == "file")
434                                         // faster (mailto) and special (file) cases
435                                         return path;
436                                 
437                                 if (path.Length == 0) {
438                                         string start = scheme + SchemeDelimiter;
439                                         if (path.StartsWith (start, StringComparison.Ordinal))
440                                                 return "/";
441                                         else
442                                                 return String.Empty;
443                                 }
444                                 return path;
445                         }
446                 }
447
448                 public string AbsoluteUri { 
449                         get { 
450                                 EnsureAbsoluteUri ();
451                                 if (cachedAbsoluteUri == null) {
452                                         cachedAbsoluteUri = GetLeftPart (UriPartial.Path);
453                                         if (query.Length > 0)
454                                                 cachedAbsoluteUri += query;
455                                         if (fragment.Length > 0)
456                                                 cachedAbsoluteUri += fragment;
457                                 }
458                                 return cachedAbsoluteUri;
459                         } 
460                 }
461
462                 public string Authority { 
463                         get { 
464                                 EnsureAbsoluteUri ();
465                                 return (GetDefaultPort (Scheme) == port)
466                                      ? host : host + ":" + port;
467                         } 
468                 }
469
470                 public string Fragment { 
471                         get { 
472                                 EnsureAbsoluteUri ();
473                                 return fragment; 
474                         } 
475                 }
476
477                 public string Host { 
478                         get { 
479                                 EnsureAbsoluteUri ();
480                                 return host; 
481                         } 
482                 }
483
484                 public UriHostNameType HostNameType { 
485                         get {
486                                 EnsureAbsoluteUri ();
487                                 UriHostNameType ret = CheckHostName (Host);
488                                 if (ret != UriHostNameType.Unknown)
489                                         return ret;
490
491                                 if (scheme == "mailto")
492                                         return UriHostNameType.Basic;
493                                 return (IsFile) ? UriHostNameType.Basic : ret;
494                         } 
495                 }
496
497                 public bool IsDefaultPort { 
498                         get {
499                                 EnsureAbsoluteUri ();
500                                 return GetDefaultPort (Scheme) == port;
501                         }
502                 }
503
504                 public bool IsFile { 
505                         get {
506                                 EnsureAbsoluteUri ();
507                                 return (Scheme == UriSchemeFile);
508                         }
509                 }
510
511                 public bool IsLoopback { 
512                         get {
513                                 EnsureAbsoluteUri ();
514                                 
515                                 if (Host.Length == 0) {
516                                         return IsFile;
517                                 }
518
519                                 if (host == "loopback" || host == "localhost") 
520                                         return true;
521
522                                 IPAddress result;
523                                 if (IPAddress.TryParse (host, out result))
524                                         if (IPAddress.Loopback.Equals (result))
525                                                 return true;
526
527                                 IPv6Address result6;
528                                 if (IPv6Address.TryParse (host, out result6)){
529                                         if (IPv6Address.IsLoopback (result6))
530                                                 return true;
531                                 }
532
533                                 return false;
534                         } 
535                 }
536
537                 public bool IsUnc {
538                         // rule: This should be true only if
539                         //   - uri string starts from "\\", or
540                         //   - uri string starts from "//" (Samba way)
541                         get { 
542                                 EnsureAbsoluteUri ();
543                                 return isUnc; 
544                         } 
545                 }
546
547                 private bool IsLocalIdenticalToAbsolutePath ()
548                 {
549                         if (IsFile)
550                                 return false;
551
552                         if ((scheme == Uri.UriSchemeNews) || (scheme == Uri.UriSchemeNntp) || (scheme == Uri.UriSchemeFtp))
553                                 return false;
554
555                         return IsWellFormedOriginalString ();
556                 }
557
558                 public string LocalPath { 
559                         get {
560                                 EnsureAbsoluteUri ();
561                                 if (cachedLocalPath != null)
562                                         return cachedLocalPath;
563
564                                 if (IsLocalIdenticalToAbsolutePath ()) {
565                                         cachedLocalPath = Unescape (AbsolutePath);
566                                         return cachedLocalPath;
567                                 }
568
569                                 if (!IsUnc) {
570                                         string p = Unescape (path);
571                                         bool windows = (path.Length > 3 && path [1] == ':' &&
572                                                 (path [2] == '\\' || path [2] == '/'));
573
574                                         if (windows)
575                                                 cachedLocalPath = p.Replace ('/', '\\');
576                                         else
577                                                 cachedLocalPath = p;
578                                 } else {
579                                         // support *nix and W32 styles
580                                         if (path.Length > 1 && path [1] == ':')
581                                                 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
582
583                                         // LAMESPEC: ok, now we cannot determine
584                                         // if such URI like "file://foo/bar" is
585                                         // Windows UNC or unix file path, so
586                                         // they should be handled differently.
587                                         else if (System.IO.Path.DirectorySeparatorChar == '\\') {
588                                                 string h = host;
589                                                 if (path.Length > 0) {
590                                                         if ((path.Length > 1) || (path[0] != '/')) {
591                                                                 h += path.Replace ('/', '\\');
592                                                         }
593                                                 }
594                                                 cachedLocalPath = "\\\\" + Unescape (h);
595                                         }  else
596                                                 cachedLocalPath = Unescape (path);
597                                 }
598                                 if (cachedLocalPath.Length == 0)
599                                         cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
600                                 return cachedLocalPath;
601                         } 
602                 }
603
604                 public string PathAndQuery { 
605                         get {
606                                 EnsureAbsoluteUri ();
607                                 return path + Query;
608                         } 
609                 }
610
611                 public int Port { 
612                         get { 
613                                 EnsureAbsoluteUri ();
614                                 return port; 
615                         } 
616                 }
617
618                 public string Query { 
619                         get { 
620                                 EnsureAbsoluteUri ();
621                                 return query; 
622                         }
623                 }
624
625                 public string Scheme { 
626                         get { 
627                                 EnsureAbsoluteUri ();
628                                 return scheme; 
629                         } 
630                 }
631
632                 public string [] Segments { 
633                         get { 
634                                 EnsureAbsoluteUri ();
635
636                                 // return a (pre-allocated) empty array
637                                 if (path.Length == 0)
638                                         return Empty;
639                                 // do not return the original array (since items can be changed)
640                                 if (segments != null)
641                                         return segments.ToArray ();
642
643                                 List<string> list = new List<string> ();
644                                 StringBuilder current = new StringBuilder ();
645                                 for (int i = 0; i < path.Length; i++) {
646                                         switch (path [i]) {
647                                         case '/':
648                                         case '\\':
649                                                 current.Append (path [i]);
650                                                 list.Add (current.ToString ());
651                                                 current.Length = 0;
652                                                 break;
653                                         case '%':
654                                                 if ((i < path.Length - 2) && (path [i + 1] == '5' && path [i + 2] == 'C')) {
655                                                         current.Append ("%5C");
656                                                         list.Add (current.ToString ());
657                                                         current.Length = 0;
658                                                         i += 2;
659                                                 } else {
660                                                         current.Append ('%');
661                                                 }
662                                                 break;
663                                         default:
664                                                 current.Append (path [i]);
665                                                 break;
666                                         }
667                                 }
668
669                                 if (current.Length > 0)
670                                         list.Add (current.ToString ());
671
672                                 if (IsFile && (list.Count > 0)) {
673                                         string first = list [0];
674                                         if ((first.Length > 1) && (first [1] == ':')) {
675                                                 list.Insert (0, "/");
676                                         }
677                                 }
678                                 segments = list;
679                                 return segments.ToArray ();
680                         } 
681                 }
682
683                 public bool UserEscaped { 
684                         get { return userEscaped; } 
685                 }
686
687                 public string UserInfo { 
688                         get { 
689                                 EnsureAbsoluteUri ();
690                                 return userinfo == null ? String.Empty : userinfo;
691                         }
692                 }
693                 
694                 public string DnsSafeHost {
695                         get {
696                                 EnsureAbsoluteUri ();
697                                 string host = Host;
698                                 if (HostNameType == UriHostNameType.IPv6) {
699                                         host = Host.Substring (1, Host.Length - 2);
700                                         if (scope_id != 0)
701                                                 host += "%" + scope_id.ToString ();
702                                 }
703                                 return Unescape (host);
704                         }
705                 }
706
707 #if NET_2_0
708                 public
709 #else
710                 internal
711 #endif
712                 bool IsAbsoluteUri {
713                         get { return isAbsoluteUri; }
714                 }
715
716                 // LAMESPEC: source field is supplied in such case that this
717                 // property makes sense. For such case that source field is
718                 // not supplied (i.e. .ctor(Uri, string), this property
719                 // makes no sense. To avoid silly regression it just returns
720                 // ToString() value now. See bug #78374.
721                 public string OriginalString {
722                         get { return source != null ? source : ToString (); }
723                 }
724
725                 // Methods              
726
727                 public static UriHostNameType CheckHostName (string name) 
728                 {
729                         if (name == null || name.Length == 0)
730                                 return UriHostNameType.Unknown;
731
732                         if (IsIPv4Address (name)) 
733                                 return UriHostNameType.IPv4;
734                                 
735                         if (IsDomainAddress (name))
736                                 return UriHostNameType.Dns;                             
737                                 
738                         IPv6Address addr;
739                         if (IPv6Address.TryParse (name, out addr))
740                                 return UriHostNameType.IPv6;
741                         
742                         return UriHostNameType.Unknown;
743                 }
744                 
745                 internal static bool IsIPv4Address (string name)
746                 {
747                         string [] captures = name.Split (new char [] {'.'});
748                         if (captures.Length != 4)
749                                 return false;
750
751                         for (int i = 0; i < 4; i++) {
752                                 int length;
753
754                                 length = captures [i].Length;
755                                 if (length == 0)
756                                         return false;
757                                 uint number;
758                                 if (!UInt32.TryParse (captures [i], out number))
759                                         return false;
760                                 if (number > 255)
761                                         return false;
762                         }
763                         return true;
764                 }                       
765                                 
766                 internal static bool IsDomainAddress (string name)
767                 {
768                         int len = name.Length;
769                         
770                         int count = 0;
771                         for (int i = 0; i < len; i++) {
772                                 char c = name [i];
773                                 if (count == 0) {
774                                         if (!Char.IsLetterOrDigit (c))
775                                                 return false;
776                                 } else if (c == '.') {
777                                         // www..host.com is bad
778                                         if (i + 1 < len && name [i + 1] == '.')
779                                                 return false;
780                                         count = 0;
781                                 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
782                                         return false;
783                                 }
784                                 if (++count == 64)
785                                         return false;
786                         }
787                         
788                         return true;
789                 }
790 #if !NET_2_1
791
792                 [Obsolete("This method does nothing, it has been obsoleted")]
793                 protected virtual void Canonicalize ()
794                 {
795                         //
796                         // This is flagged in the Microsoft documentation as used
797                         // internally, no longer in use, and Obsolete.
798                         //
799                 }
800
801                 [MonoTODO ("Find out what this should do")]
802                 [Obsolete]
803                 protected virtual void CheckSecurity ()
804                 {
805                 }
806
807 #endif // NET_2_1
808
809                 // defined in RFC3986 as = ALPHA *( ALPHA / DIGIT / "+" / "-" / ".")
810                 public static bool CheckSchemeName (string schemeName) 
811                 {
812                         if (schemeName == null || schemeName.Length == 0)
813                                 return false;
814                         
815                         if (!IsAlpha (schemeName [0]))
816                                 return false;
817
818                         int len = schemeName.Length;
819                         for (int i = 1; i < len; i++) {
820                                 char c = schemeName [i];
821                                 if (!Char.IsDigit (c) && !IsAlpha (c) && c != '.' && c != '+' && c != '-')
822                                         return false;
823                         }
824                         
825                         return true;
826                 }
827
828                 private static bool IsAlpha (char c)
829                 {
830                         // as defined in rfc2234
831                         // %x41-5A / %x61-7A (A-Z / a-z)
832                         int i = (int) c;
833                         return (((i >= 0x41) && (i <= 0x5A)) || ((i >= 0x61) && (i <= 0x7A)));
834                 }
835
836                 public override bool Equals (object comparand) 
837                 {
838                         if (comparand == null) 
839                                 return false;
840
841                         Uri uri = comparand as Uri;
842                         if ((object) uri == null) {
843                                 string s = comparand as String;
844                                 if (s == null)
845                                         return false;
846
847                                 if (!TryCreate (s, UriKind.RelativeOrAbsolute, out uri))
848                                         return false;
849                         }
850
851                         return InternalEquals (uri);
852                 }
853
854                 // Assumes: uri != null
855                 bool InternalEquals (Uri uri)
856                 {
857                         if (this.isAbsoluteUri != uri.isAbsoluteUri)
858                                 return false;
859                         if (!this.isAbsoluteUri)
860                                 return this.source == uri.source;
861
862                         CultureInfo inv = CultureInfo.InvariantCulture;
863                         return this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)
864                                 && this.host.ToLower (inv) == uri.host.ToLower (inv)
865                                 && this.port == uri.port
866                                 && this.query == uri.query
867                                 && this.path == uri.path;
868                 }
869
870                 public static bool operator == (Uri uri1, Uri uri2)
871                 {
872                         return object.Equals (uri1, uri2);
873                 }
874
875                 public static bool operator != (Uri uri1, Uri uri2)
876                 {
877                         return !(uri1 == uri2);
878                 }
879
880                 public override int GetHashCode () 
881                 {
882                         if (cachedHashCode == 0) {
883                                 CultureInfo inv = CultureInfo.InvariantCulture;
884                                 if (isAbsoluteUri) {
885                                         cachedHashCode = scheme.ToLower (inv).GetHashCode ()
886                                                 ^ host.ToLower (inv).GetHashCode ()
887                                                 ^ port
888                                                 ^ query.GetHashCode ()
889                                                 ^ path.GetHashCode ();
890                                 }
891                                 else {
892                                         cachedHashCode = source.GetHashCode ();
893                                 }
894                         }
895                         return cachedHashCode;
896                 }
897                 
898                 public string GetLeftPart (UriPartial part) 
899                 {
900                         EnsureAbsoluteUri ();
901                         int defaultPort;
902                         switch (part) {                         
903                         case UriPartial.Scheme : 
904                                 return scheme + GetOpaqueWiseSchemeDelimiter ();
905                         case UriPartial.Authority :
906                                 if ((scheme == Uri.UriSchemeMailto) || (scheme == Uri.UriSchemeNews))
907                                         return String.Empty;
908                                         
909                                 StringBuilder s = new StringBuilder ();
910                                 s.Append (scheme);
911                                 s.Append (GetOpaqueWiseSchemeDelimiter ());
912                                 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme)) 
913                                         s.Append ('/');  // win32 file
914                                 if (userinfo != null) 
915                                         s.Append (userinfo).Append ('@');
916                                 s.Append (host);
917                                 defaultPort = GetDefaultPort (scheme);
918                                 if ((port != -1) && (port != defaultPort))
919                                         s.Append (':').Append (port);                    
920                                 return s.ToString ();                           
921                         case UriPartial.Path :
922                                 StringBuilder sb = new StringBuilder ();
923                                 sb.Append (scheme);
924                                 sb.Append (GetOpaqueWiseSchemeDelimiter ());
925                                 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme)) 
926                                         sb.Append ('/');  // win32 file
927                                 if (userinfo != null) 
928                                         sb.Append (userinfo).Append ('@');
929                                 sb.Append (host);
930                                 defaultPort = GetDefaultPort (scheme);
931                                 if ((port != -1) && (port != defaultPort))
932                                         sb.Append (':').Append (port);
933
934                                 if (path.Length > 0) {
935                                         if (scheme == "mailto" || scheme == "news")
936                                                 sb.Append (path);
937                                         else 
938                                                 sb.Append (Reduce (path, CompactEscaped (scheme)));
939                                 }
940                                 return sb.ToString ();
941                         }
942                         return null;
943                 }
944
945                 public static int FromHex (char digit) 
946                 {
947                         if ('0' <= digit && digit <= '9') {
948                                 return (int) (digit - '0');
949                         }
950                                 
951                         if ('a' <= digit && digit <= 'f')
952                                 return (int) (digit - 'a' + 10);
953
954                         if ('A' <= digit && digit <= 'F')
955                                 return (int) (digit - 'A' + 10);
956                                 
957                         throw new ArgumentException ("digit");
958                 }
959
960                 public static string HexEscape (char character) 
961                 {
962                         if (character > 255) {
963                                 throw new ArgumentOutOfRangeException ("character");
964                         }
965                         
966                         return "%" + hexUpperChars [((character & 0xf0) >> 4)] 
967                                    + hexUpperChars [((character & 0x0f))];
968                 }
969
970                 public static char HexUnescape (string pattern, ref int index) 
971                 {
972                         if (pattern == null) 
973                                 throw new ArgumentException ("pattern");
974                                 
975                         if (index < 0 || index >= pattern.Length)
976                                 throw new ArgumentOutOfRangeException ("index");
977
978                         if (!IsHexEncoding (pattern, index))
979                                 return pattern [index++];
980
981                         index++;
982                         int msb = FromHex (pattern [index++]);
983                         int lsb = FromHex (pattern [index++]);
984                         return (char) ((msb << 4) | lsb);
985                 }
986
987                 public static bool IsHexDigit (char character) 
988                 {
989                         return (('0' <= character && character <= '9') ||
990                                 ('a' <= character && character <= 'f') ||
991                                 ('A' <= character && character <= 'F'));
992                 }
993
994                 public static bool IsHexEncoding (string pattern, int index) 
995                 {
996                         if ((index + 3) > pattern.Length)
997                                 return false;
998
999                         return ((pattern [index++] == '%') &&
1000                                 IsHexDigit (pattern [index++]) &&
1001                                 IsHexDigit (pattern [index]));
1002                 }
1003
1004                 //
1005                 // Implemented by copying most of the MakeRelative code
1006                 //
1007                 public Uri MakeRelativeUri (Uri uri)
1008                 {
1009 #if NET_4_0 || MOONLIGHT || MOBILE
1010                         if (uri == null)
1011                                 throw new ArgumentNullException ("uri");
1012 #endif
1013                         if (Host != uri.Host || Scheme != uri.Scheme)
1014                                 return uri;
1015
1016                         string result = String.Empty;
1017                         if (this.path != uri.path){
1018                                 string [] segments = this.Segments;
1019                                 string [] segments2 = uri.Segments;
1020                                 
1021                                 int k = 0;
1022                                 int max = Math.Min (segments.Length, segments2.Length);
1023                                 for (; k < max; k++)
1024                                         if (segments [k] != segments2 [k]) 
1025                                                 break;
1026                                 
1027                                 for (int i = k + 1; i < segments.Length; i++)
1028                                         result += "../";
1029                                 for (int i = k; i < segments2.Length; i++)
1030                                         result += segments2 [i];
1031                                 
1032                         }
1033                         uri.AppendQueryAndFragment (ref result);
1034
1035                         return new Uri (result, UriKind.Relative);
1036                 }
1037
1038                 [Obsolete ("Use MakeRelativeUri(Uri uri) instead.")]
1039                 public string MakeRelative (Uri toUri) 
1040                 {
1041                         if ((this.Scheme != toUri.Scheme) ||
1042                             (this.Authority != toUri.Authority))
1043                                 return toUri.ToString ();
1044
1045                         string result = String.Empty;
1046                         if (this.path != toUri.path){
1047                                 string [] segments = this.Segments;
1048                                 string [] segments2 = toUri.Segments;
1049                                 int k = 0;
1050                                 int max = Math.Min (segments.Length, segments2.Length);
1051                                 for (; k < max; k++)
1052                                         if (segments [k] != segments2 [k]) 
1053                                                 break;
1054                                 
1055                                 for (int i = k + 1; i < segments.Length; i++)
1056                                         result += "../";
1057                                 for (int i = k; i < segments2.Length; i++)
1058                                         result += segments2 [i];
1059                         }
1060
1061                         // Important: MakeRelative does not append fragment or query.
1062
1063                         return result;
1064                 }
1065
1066                 void AppendQueryAndFragment (ref string result)
1067                 {
1068                         if (query.Length > 0) {
1069                                 string q = query [0] == '?' ? '?' + Unescape (query.Substring (1), true, false) : Unescape (query, false);
1070                                 result += q;
1071                         }
1072                         if (fragment.Length > 0)
1073                                 result += Unescape (fragment, true, false);
1074                 }
1075                 
1076                 public override string ToString () 
1077                 {
1078                         if (cachedToString != null) 
1079                                 return cachedToString;
1080
1081                         if (isAbsoluteUri) {
1082                                 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true);
1083                                 AppendQueryAndFragment (ref cachedToString);
1084                         } else {
1085                                 // Everything is contained in path in this case. 
1086                                 cachedToString = path;
1087                         }
1088
1089                         return cachedToString;
1090                 }
1091
1092                 protected void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
1093                 {
1094                         if (this.isAbsoluteUri) {
1095                                 serializationInfo.AddValue ("AbsoluteUri", this.AbsoluteUri);
1096                         } else {
1097                                 serializationInfo.AddValue("AbsoluteUri", String.Empty);
1098                                 serializationInfo.AddValue("RelativeUri", this.OriginalString);
1099                         }
1100                 }
1101
1102                 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
1103                 {
1104                         GetObjectData (info, context);
1105                 }
1106
1107
1108                 // Internal Methods             
1109
1110                 [Obsolete]
1111                 protected virtual void Escape ()
1112                 {
1113                         path = EscapeString (path);
1114                 }
1115
1116 #if MOONLIGHT
1117                 static string EscapeString (string str)
1118 #else
1119                 [Obsolete]
1120                 protected static string EscapeString (string str) 
1121 #endif
1122                 {
1123                         return EscapeString (str, Uri.EscapeCommonHexBrackets);
1124                 }
1125
1126                 private const string EscapeCommon = "<>%\"{}|\\^`";
1127                 private const string EscapeReserved = ";/?:@&=+$,";
1128                 private const string EscapeFragment = "#";
1129                 private const string EscapeBrackets = "[]";
1130
1131                 private const string EscapeNews = EscapeCommon + EscapeBrackets + "?";
1132                 private const string EscapeCommonHex = EscapeCommon + EscapeFragment;
1133                 private const string EscapeCommonBrackets = EscapeCommon + EscapeBrackets;
1134                 internal const string EscapeCommonHexBrackets = EscapeCommon + EscapeFragment + EscapeBrackets;
1135                 internal const string EscapeCommonHexBracketsQuery = EscapeCommonHexBrackets + "?";
1136
1137                 internal static string EscapeString (string str, string escape)
1138                 {
1139                         return EscapeString (str, escape, true);
1140                 }
1141
1142                 internal static string EscapeString (string str, string escape, bool nonAsciiEscape) 
1143                 {
1144                         if (String.IsNullOrEmpty (str))
1145                                 return String.Empty;
1146                         
1147                         StringBuilder s = new StringBuilder ();
1148                         int len = str.Length;   
1149                         for (int i = 0; i < len; i++) {
1150                                 // reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
1151                                 // mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
1152                                 // control     = <US-ASCII coded characters 00-1F and 7F hexadecimal>
1153                                 // space       = <US-ASCII coded character 20 hexadecimal>
1154                                 // delims      = "<" | ">" | "#" | "%" | <">
1155                                 // unwise      = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
1156
1157                                 // check for escape code already placed in str, 
1158                                 // i.e. for encoding that follows the pattern 
1159                                 // "%hexhex" in a string, where "hex" is a digit from 0-9 
1160                                 // or a letter from A-F (case-insensitive).
1161                                 if (IsHexEncoding (str,i)) {
1162                                         // if ,yes , copy it as is
1163                                         s.Append(str.Substring (i, 3));
1164                                         i += 2;
1165                                         continue;
1166                                 }
1167
1168                                 char c = str [i];
1169                                 bool outside_limited_ascii = ((c <= 0x20) || (c >= 0x7f));
1170                                 bool needs_escape = (escape.IndexOf (c) != -1);
1171                                 if (nonAsciiEscape && outside_limited_ascii) {
1172                                         byte [] data = Encoding.UTF8.GetBytes (new char [] { c });
1173                                         int length = data.Length;
1174                                         for (int j = 0; j < length; j++) {
1175                                                 c = (char) data [j];
1176                                                 if (needs_escape || nonAsciiEscape)
1177                                                         s.Append (HexEscape (c));
1178                                                 else
1179                                                         s.Append (c);
1180                                         }
1181                                 } else if (needs_escape) {
1182                                         s.Append (HexEscape (c));
1183                                 } else {
1184                                         s.Append (c);
1185                                 }
1186                         }
1187                         
1188                         return s.ToString ();
1189                 }
1190
1191                 // On .NET 1.x, this method is called from .ctor(). When overriden, we 
1192                 // can avoid the "absolute uri" constraints of the .ctor() by
1193                 // overriding with custom code.
1194                 [Obsolete("The method has been deprecated. It is not used by the system.")]
1195                 protected virtual void Parse ()
1196                 {
1197                 }
1198
1199                 private void ParseUri (UriKind kind)
1200                 {
1201                         Parse (kind, source);
1202
1203                         if (userEscaped)
1204                                 return;
1205
1206                         // non-ascii characters are not escaped for the host name
1207                         host = EscapeString (host, EscapeCommonHex, false);
1208                         if (host.Length > 1 && host [0] != '[' && host [host.Length - 1] != ']') {
1209                                 // host name present (but not an IPv6 address)
1210                                 host = host.ToLower (CultureInfo.InvariantCulture);
1211                         }
1212
1213                         if (isAbsoluteUri && (path.Length > 0))
1214                                 path = EscapeString (path);
1215                 }
1216
1217 #if MOONLIGHT
1218                 string Unescape (string path)
1219 #else
1220                 [Obsolete]
1221                 protected virtual string Unescape (string path)
1222 #endif
1223                 {
1224                         return Unescape (path, false, false);
1225                 }
1226
1227                 internal static string Unescape (string str, bool excludeSpecial)
1228                 {
1229                         return Unescape (str, excludeSpecial, excludeSpecial);
1230                 }
1231                 
1232                 internal static string Unescape (string str, bool excludeSpecial, bool excludeBackslash) 
1233                 {
1234                         if (String.IsNullOrEmpty (str))
1235                                 return String.Empty;
1236
1237                         StringBuilder s = new StringBuilder ();
1238                         int len = str.Length;
1239                         for (int i = 0; i < len; i++) {
1240                                 char c = str [i];
1241                                 if (c == '%') {
1242                                         char surrogate;
1243                                         char x = HexUnescapeMultiByte (str, ref i, out surrogate);
1244                                         if (excludeSpecial && x == '#')
1245                                                 s.Append ("%23");
1246                                         else if (excludeSpecial && x == '%')
1247                                                 s.Append ("%25");
1248                                         else if (excludeSpecial && x == '?')
1249                                                 s.Append ("%3F");
1250                                         else if (excludeBackslash && x == '\\')
1251                                                 s.Append ("%5C");
1252                                         else {
1253                                                 s.Append (x);
1254                                                 if (surrogate != char.MinValue)
1255                                                         s.Append (surrogate);
1256                                         }
1257                                         i--;
1258                                 } else
1259                                         s.Append (c);
1260                         }
1261                         return s.ToString ();
1262                 }
1263
1264                 
1265                 // Private Methods
1266                 
1267                 private void ParseAsWindowsUNC (string uriString)
1268                 {
1269                         scheme = UriSchemeFile;
1270                         port = -1;
1271                         fragment = String.Empty;
1272                         query = String.Empty;
1273                         isUnc = true;
1274
1275                         uriString = uriString.TrimStart (new char [] {'\\'});
1276                         int pos = uriString.IndexOf ('\\');
1277                         if (pos > 0) {
1278                                 path = uriString.Substring (pos);
1279                                 host = uriString.Substring (0, pos);
1280                         } else { // "\\\\server"
1281                                 host = uriString;
1282                                 path = String.Empty;
1283                         }
1284                         path = path.Replace ("\\", "/");
1285                 }
1286
1287                 //
1288                 // Returns null on success, string with error on failure
1289                 //
1290                 private string ParseAsWindowsAbsoluteFilePath (string uriString)
1291                 {
1292                         if (uriString.Length > 2 && uriString [2] != '\\' && uriString [2] != '/')
1293                                 return "Relative file path is not allowed.";
1294                         scheme = UriSchemeFile;
1295                         host = String.Empty;
1296                         port = -1;
1297                         path = uriString.Replace ("\\", "/");
1298                         fragment = String.Empty;
1299                         query = String.Empty;
1300
1301                         return null;
1302                 }
1303
1304                 private void ParseAsUnixAbsoluteFilePath (string uriString)
1305                 {
1306                         isUnixFilePath = true;
1307                         scheme = UriSchemeFile;
1308                         port = -1;
1309                         fragment = String.Empty;
1310                         query = String.Empty;
1311                         host = String.Empty;
1312                         path = null;
1313
1314                         if (uriString.Length >= 2 && uriString [0] == '/' && uriString [1] == '/') {
1315                                 uriString = uriString.TrimStart (new char [] {'/'});
1316                                 // Now we don't regard //foo/bar as "foo" host.
1317                                 /* 
1318                                 int pos = uriString.IndexOf ('/');
1319                                 if (pos > 0) {
1320                                         path = '/' + uriString.Substring (pos + 1);
1321                                         host = uriString.Substring (0, pos);
1322                                 } else { // "///server"
1323                                         host = uriString;
1324                                         path = String.Empty;
1325                                 }
1326                                 */
1327                                 path = '/' + uriString;
1328                         }
1329                         if (path == null)
1330                                 path = uriString;
1331                 }
1332
1333                 //
1334                 // This parse method will throw exceptions on failure
1335                 //  
1336                 private void Parse (UriKind kind, string uriString)
1337                 {                       
1338                         if (uriString == null)
1339                                 throw new ArgumentNullException ("uriString");
1340
1341                         string s = ParseNoExceptions (kind, uriString);
1342                         if (s != null)
1343                                 throw new UriFormatException (s);
1344                 }
1345
1346                 private bool SupportsQuery ()
1347                 {
1348                         return ((scheme != Uri.UriSchemeNntp) && (scheme != Uri.UriSchemeFtp) && (scheme != Uri.UriSchemeFile));
1349                 }
1350
1351                 //
1352                 // This parse method will not throw exceptions on failure
1353                 //
1354                 // Returns null on success, or a description of the error in the parsing
1355                 //
1356                 private string ParseNoExceptions (UriKind kind, string uriString)
1357                 {
1358                         //
1359                         // From RFC 2396 :
1360                         //
1361                         //      ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
1362                         //       12            3  4          5       6  7        8 9
1363                         //                      
1364                         
1365                         uriString = uriString.Trim();
1366                         int len = uriString.Length;
1367
1368                         if (len == 0){
1369                                 if (kind == UriKind.Relative || kind == UriKind.RelativeOrAbsolute){
1370                                         isAbsoluteUri = false;
1371                                         return null;
1372                                 }
1373                         }
1374                         
1375                         if (len <= 1 && (kind == UriKind.Absolute))
1376                                 return "Absolute URI is too short";
1377
1378                         int pos = 0;
1379
1380                         // 1, 2
1381                         // Identify Windows path, unix path, or standard URI.
1382                         if (uriString [0] == '/' && Path.DirectorySeparatorChar == '/'){
1383                                 //Unix Path
1384                                 ParseAsUnixAbsoluteFilePath (uriString);
1385 #if MOONLIGHT
1386                                 isAbsoluteUri = false;
1387 #else
1388                                 if (kind == UriKind.Relative)
1389                                         isAbsoluteUri = false;
1390 #endif
1391                                 return null;
1392                         } else if (uriString.Length >= 2 && uriString [0] == '\\' && uriString [1] == '\\') {
1393                                 //Windows UNC
1394                                 ParseAsWindowsUNC (uriString);
1395                                 return null;
1396                         }
1397
1398
1399                         pos = uriString.IndexOf (':');
1400                         if (pos == 0) {
1401                                 if (kind == UriKind.Absolute)
1402                                         return "Invalid URI: The format of the URI could not be determined.";
1403                                 isAbsoluteUri = false;
1404                                 path = uriString;
1405                                 return null;
1406                         } else if (pos < 0) {
1407                                 /* Relative path */
1408                                 isAbsoluteUri = false;
1409                                 path = uriString;
1410                                 return null;
1411                         } else if (pos == 1) {
1412                                 if (!IsAlpha (uriString [0])) {
1413                                         if (kind == UriKind.Absolute)
1414                                                 return "Invalid URI: The URI scheme is not valid.";
1415                                         isAbsoluteUri = false;
1416                                         path = uriString;
1417                                         return null;
1418                                 }
1419                                 // This means 'a:' == windows full path.
1420                                 string msg = ParseAsWindowsAbsoluteFilePath (uriString);
1421                                 if (msg != null)
1422                                         return msg;
1423                                 return null;
1424                         } 
1425
1426                         // scheme
1427                         scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
1428
1429                         // Check scheme name characters as specified in RFC2396.
1430                         // Note: different checks in 1.x and 2.0
1431                         if (!CheckSchemeName (scheme)) {
1432                                 if (kind == UriKind.Absolute)
1433                                         return "Invalid URI: The URI scheme is not valid.";
1434                                 isAbsoluteUri = false;
1435                                 path = uriString;
1436                                 return null;
1437                         }
1438
1439                         // from here we're practically working on uriString.Substring(startpos,endpos-startpos)
1440                         int startpos = pos + 1;
1441                         int endpos = uriString.Length;
1442
1443                         // 8 fragment
1444                         pos = uriString.IndexOf ('#', startpos);
1445                         if (!IsUnc && pos != -1) {
1446                                 if (userEscaped)
1447                                         fragment = uriString.Substring (pos);
1448                                 else
1449                                         fragment = "#" + EscapeString (uriString.Substring (pos+1));
1450
1451                                 endpos = pos;
1452                         }
1453
1454                         // special case: there is no query part for 'news'
1455                         if (scheme == Uri.UriSchemeNews) {
1456                                 pos = scheme.Length + 1;
1457                                 path = EscapeString (uriString.Substring (pos, endpos - pos), EscapeNews);
1458                                 return null;
1459                         }
1460
1461                         // special case: there is no query part for 'nntp', 'file' and 'ftp' but there is an host, port, user...
1462                         if (SupportsQuery ()) {
1463                                 // 6 query
1464                                 pos = uriString.IndexOf ('?', startpos, endpos-startpos);
1465                                 if (pos != -1) {
1466                                         query = uriString.Substring (pos, endpos-pos);
1467                                         endpos = pos;
1468                                         if (!userEscaped)
1469                                                 query = EscapeString (query);
1470                                 }
1471                         }
1472
1473                         // 3
1474                         if (IsPredefinedScheme (scheme) && scheme != UriSchemeMailto && (
1475                                 (endpos-startpos < 2) ||
1476                                 (endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] != '/')))                                
1477                                 return "Invalid URI: The Authority/Host could not be parsed.";
1478                         
1479                         
1480                         bool startsWithSlashSlash = endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] == '/';
1481                         bool unixAbsPath = scheme == UriSchemeFile && startsWithSlashSlash && (endpos-startpos == 2 || uriString [startpos+2] == '/');
1482                         bool windowsFilePath = false;
1483                         if (startsWithSlashSlash) {
1484                                 if (kind == UriKind.Relative)
1485                                         return "Absolute URI when we expected a relative one";
1486                                 
1487                                 if (scheme != UriSchemeMailto)
1488                                         startpos += 2;
1489
1490                                 if (scheme == UriSchemeFile) {
1491                                         int num_leading_slash = 2;
1492                                         for (int i = startpos; i < endpos; i++) {
1493                                                 if (uriString [i] != '/')
1494                                                         break;
1495                                                 num_leading_slash++;
1496                                         }
1497                                         if (num_leading_slash >= 4) {
1498                                                 unixAbsPath = false;
1499                                                 while (startpos < endpos && uriString[startpos] == '/') {
1500                                                         startpos++;
1501                                                 }
1502                                         } else if (num_leading_slash >= 3) {
1503                                                 startpos += 1;
1504                                         }
1505                                 }
1506                                 
1507                                 if (endpos - startpos > 1 && uriString [startpos + 1] == ':') {
1508                                         unixAbsPath = false;
1509                                         windowsFilePath = true;
1510                                 }
1511
1512                         } else if (!IsPredefinedScheme (scheme)) {
1513                                 path = uriString.Substring(startpos, endpos-startpos);
1514                                 isOpaquePart = true;
1515                                 return null;
1516                         }
1517
1518                         // 5 path
1519                         if (unixAbsPath) {
1520                                 pos = -1;
1521                         } else {
1522                                 pos = uriString.IndexOf ('/', startpos, endpos - startpos);
1523                                 if (pos == -1 && windowsFilePath)
1524                                         pos = uriString.IndexOf ('\\', startpos, endpos - startpos);
1525                         }
1526                         if (pos != -1) {
1527                                 path = uriString.Substring (pos, endpos - pos);
1528                                 if (!SupportsQuery ()) {
1529                                         if (scheme != Uri.UriSchemeNntp)
1530                                                 path = path.Replace ('\\', '/');
1531                                         path = EscapeString (path, EscapeNews);
1532                                 }
1533                                 endpos = pos;
1534                         } else {
1535                                 if (scheme != Uri.UriSchemeMailto)
1536                                         path = "/";
1537                         }
1538
1539                         // 4.a user info
1540                         if (unixAbsPath)
1541                                 pos = -1;
1542                         else
1543                                 pos = uriString.IndexOf ('@', startpos, endpos-startpos);
1544                         if (pos != -1) {
1545                                 // supplying username / password on a file URI is not supported
1546                                 if (scheme == UriSchemeFile)
1547                                         return "Invalid host";
1548                                 userinfo = uriString.Substring (startpos, pos-startpos);
1549                                 startpos = pos + 1;
1550                         }
1551
1552                         // 4.b port
1553                         bool valid_port = true;
1554                         port = -1;
1555                         if (unixAbsPath)
1556                                 pos = -1;
1557                         else
1558                                 pos = uriString.LastIndexOf (':', endpos-1, endpos-startpos);
1559                         if (pos != -1 && pos != endpos - 1) {
1560                                 string portStr = uriString.Substring(pos + 1, endpos - (pos + 1));
1561                                 if (portStr.Length > 0 && portStr[portStr.Length - 1] != ']') {
1562                                         if (!Int32.TryParse (portStr, NumberStyles.None, CultureInfo.InvariantCulture, out port) ||
1563                                             port < 0 || port > UInt16.MaxValue)
1564                                                 valid_port = false; // delay reporting
1565                                         else
1566                                                 endpos = pos;
1567                                 } else {
1568                                         if (port == -1) {
1569                                                 port = GetDefaultPort (scheme);
1570                                         }
1571                                 }
1572                         } else if (!IsFile) {
1573                                 // if no port is specified by a colon ':' is present then we must ignore it
1574                                 // since it would be part of the host name and, as such, would be invalid
1575                                 if (pos == endpos - 1)
1576                                         endpos--;
1577
1578                                 if (port == -1)
1579                                         port = GetDefaultPort (scheme);
1580                         }
1581                         
1582                         // 4 authority
1583                         uriString = uriString.Substring(startpos, endpos-startpos);
1584                         host = uriString;
1585
1586                         if (unixAbsPath) {
1587                                 path = Reduce ('/' + uriString, true);
1588                                 host = String.Empty;
1589                         } else if (host.Length == 2 && host [1] == ':') {
1590                                 if (scheme != UriSchemeFile) {
1591                                         host = host [0].ToString ();
1592                                 } else {
1593                                         // windows filepath
1594                                         path = host + path;
1595                                         host = String.Empty;
1596                                 }
1597                         } else if (isUnixFilePath) {
1598                                 uriString = "//" + uriString;
1599                                 host = String.Empty;
1600                         } else if (scheme == UriSchemeFile) {
1601                                 // under Windows all file:// URI are considered UNC, which is not the case other MacOS (e.g. Silverlight)
1602                                 isUnc = isWin32;
1603                         } else if (host.Length == 0 &&
1604                                    (scheme == UriSchemeHttp || scheme == UriSchemeGopher || scheme == UriSchemeNntp ||
1605                                     scheme == UriSchemeHttps || scheme == UriSchemeFtp)) {
1606                                 return "Invalid URI: The Authority/Host could not be parsed.";
1607                         }
1608
1609                         if (host.Length > 0) {
1610                                 switch (CheckHostName (host)) {
1611                                 case UriHostNameType.Unknown:
1612                                         if ((host [0] == ':') || (host [0] == '@'))
1613                                                 return "Invalid URI: The hostname could not be parsed.";
1614                                         if (host.IndexOf (':') != -1)
1615                                                 return "Invalid URI: Invalid port specified.";
1616                                         if (Parser is DefaultUriParser || Parser == null)
1617                                                 return "Invalid URI: The hostname could not be parsed.";
1618                                         break;
1619                                 case UriHostNameType.IPv6:
1620                                         IPv6Address ipv6addr;
1621                                         if (IPv6Address.TryParse (host, out ipv6addr)) {
1622                                                 host = "[" + ipv6addr.ToString (true) + "]";
1623                                                 scope_id = ipv6addr.ScopeId;
1624                                         }
1625                                         break;
1626                                 }
1627                         }
1628                         // delayed reporting (to throw the expected exception in the right order)
1629                         if (!valid_port)
1630                                 return "Invalid URI: Invalid port number";
1631
1632                         UriFormatException ex = null;
1633                         if (Parser != null)
1634                                 Parser.InitializeAndValidate (this, out ex);
1635                         if (ex != null)
1636                                 return ex.Message;
1637
1638                         if ((scheme != Uri.UriSchemeMailto) && (scheme != Uri.UriSchemeFile)) {
1639                                 path = Reduce (path, CompactEscaped (scheme));
1640                         }
1641
1642                         return null;
1643                 }
1644
1645                 private static bool CompactEscaped (string scheme)
1646                 {
1647                         if (scheme == null || scheme.Length < 4)
1648                                 return false;
1649
1650                         char first = scheme [0];
1651                         if (first == 'h'){
1652                                 return scheme == "http" || scheme == "https";
1653                         } else if (first == 'f' && scheme == "file"){
1654                                 return true;
1655                         } else if (first == 'n')
1656                                 return scheme == "net.pipe" || scheme == "net.tcp";
1657
1658                         return false;
1659                 }
1660
1661                 // replace '\', %5C ('\') and %2f ('/') into '/'
1662                 // replace %2e ('.') into '.'
1663                 private static string NormalizePath (string path)
1664                 {
1665                         StringBuilder res = new StringBuilder ();
1666                         for (int i = 0; i < path.Length; i++) {
1667                                 char c = path [i];
1668                                 switch (c) {
1669                                 case '\\':
1670                                         c = '/';
1671                                         break;
1672                                 case '%':
1673                                         if (i < path.Length - 2) {
1674                                                 char c1 = path [i + 1];
1675                                                 char c2 = Char.ToUpper (path [i + 2]);
1676                                                 if ((c1 == '2') && (c2 == 'E')) {
1677                                                         c = '.';
1678                                                         i += 2;
1679                                                 } else if (((c1 == '2') && (c2 == 'F')) || ((c1 == '5') && (c2 == 'C'))) {
1680                                                         c = '/';
1681                                                         i += 2;
1682                                                 }
1683                                         }
1684                                         break;
1685                                 }
1686                                 res.Append (c);
1687                         }
1688                         return res.ToString ();
1689                 }
1690
1691                 // This is called "compacting" in the MSDN documentation
1692                 private static string Reduce (string path, bool compact_escaped)
1693                 {
1694                         // quick out, allocation-free, for a common case
1695                         if (path == "/")
1696                                 return path;
1697
1698                         if (compact_escaped && (path.IndexOf ('%') != -1)) {
1699                                 // replace '\', %2f, %5c with '/' and replace %2e with '.'
1700                                 // other escaped values seems to survive this step
1701                                 path = NormalizePath (path);
1702                         } else {
1703                                 // (always) replace '\' with '/'
1704                                 path = path.Replace ('\\', '/');
1705                         }
1706
1707                         List<string> result = new List<string> ();
1708
1709                         bool begin = true;
1710                         for (int startpos = 0; startpos < path.Length; ) {
1711                                 int endpos = path.IndexOf ('/', startpos);
1712                                 if (endpos == -1)
1713                                         endpos = path.Length;
1714                                 string current = path.Substring (startpos, endpos-startpos);
1715                                 startpos = endpos + 1;
1716                                 if ((begin && current.Length == 0) || current == "." ) {
1717                                         begin = false;
1718                                         continue;
1719                                 }
1720
1721                                 begin = false;
1722                                 if (current == "..") {
1723                                         int resultCount = result.Count;
1724                                         // in 2.0 profile, skip leading ".." parts
1725                                         if (resultCount == 0) {
1726                                                 continue;
1727                                         }
1728
1729                                         result.RemoveAt (resultCount - 1);
1730                                         continue;
1731                                 }
1732
1733                                 result.Add (current);
1734                         }
1735
1736                         if (result.Count == 0)
1737                                 return "/";
1738
1739                         StringBuilder res = new StringBuilder ();
1740
1741                         if (path [0] == '/')
1742                                 res.Append ('/');
1743
1744                         bool first = true;
1745                         foreach (string part in result) {
1746                                 if (first) {
1747                                         first = false;
1748                                 } else {
1749                                         res.Append ('/');
1750                                 }
1751                                 res.Append(part);
1752                         }
1753
1754                         if (path [path.Length - 1] == '/')
1755                                 res.Append ('/');
1756                                 
1757                         return res.ToString();
1758                 }
1759
1760                 // A variant of HexUnescape() which can decode multi-byte escaped
1761                 // sequences such as (e.g.) %E3%81%8B into a single character
1762                 private static char HexUnescapeMultiByte (string pattern, ref int index, out char surrogate) 
1763                 {
1764                         surrogate = char.MinValue;
1765
1766                         if (pattern == null) 
1767                                 throw new ArgumentException ("pattern");
1768                                 
1769                         if (index < 0 || index >= pattern.Length)
1770                                 throw new ArgumentOutOfRangeException ("index");
1771
1772                         if (!IsHexEncoding (pattern, index))
1773                                 return pattern [index++];
1774
1775                         int orig_index = index++;
1776                         int msb = FromHex (pattern [index++]);
1777                         int lsb = FromHex (pattern [index++]);
1778
1779                         // We might be dealing with a multi-byte character:
1780                         // The number of ones at the top-end of the first byte will tell us
1781                         // how many bytes will make up this character.
1782                         int msb_copy = msb;
1783                         int num_bytes = 0;
1784                         while ((msb_copy & 0x8) == 0x8) {
1785                                 num_bytes++;
1786                                 msb_copy <<= 1;
1787                         }
1788
1789                         // We might be dealing with a single-byte character:
1790                         // If there was only 0 or 1 leading ones then we're not dealing
1791                         // with a multi-byte character.
1792                         if (num_bytes <= 1)
1793                                 return (char) ((msb << 4) | lsb);
1794
1795                         // Now that we know how many bytes *should* follow, we'll check them
1796                         // to ensure we are dealing with a valid multi-byte character.
1797                         byte [] chars = new byte [num_bytes];
1798                         bool all_invalid = false;
1799                         chars[0] = (byte) ((msb << 4) | lsb);
1800
1801                         for (int i = 1; i < num_bytes; i++) {
1802                                 if (!IsHexEncoding (pattern, index++)) {
1803                                         all_invalid = true;
1804                                         break;
1805                                 }
1806
1807                                 // All following bytes must be in the form 10xxxxxx
1808                                 int cur_msb = FromHex (pattern [index++]);
1809                                 if ((cur_msb & 0xc) != 0x8) {
1810                                         all_invalid = true;
1811                                         break;
1812                                 }
1813
1814                                 int cur_lsb = FromHex (pattern [index++]);
1815                                 chars[i] = (byte) ((cur_msb << 4) | cur_lsb);
1816                         }
1817
1818                         // If what looked like a multi-byte character is invalid, then we'll
1819                         // just return the first byte as a single byte character.
1820                         if (all_invalid) {
1821                                 index = orig_index + 3;
1822                                 return (char) chars[0];
1823                         }
1824
1825                         // Otherwise, we're dealing with a valid multi-byte character.
1826                         // We need to ignore the leading ones from the first byte:
1827                         byte mask = (byte) 0xFF;
1828                         mask >>= (num_bytes + 1);
1829                         int result = chars[0] & mask;
1830
1831                         // The result will now be built up from the following bytes.
1832                         for (int i = 1; i < num_bytes; i++) {
1833                                 // Ignore upper two bits
1834                                 result <<= 6;
1835                                 result |= (chars[i] & 0x3F);
1836                         }
1837
1838                         if (result <= 0xFFFF) {
1839                                 return (char) result;
1840                         } else {
1841                                 // We need to handle this as a UTF16 surrogate (i.e. return
1842                                 // two characters)
1843                                 result -= 0x10000;
1844                                 surrogate = (char) ((result & 0x3FF) | 0xDC00);
1845                                 return (char) ((result >> 10) | 0xD800);
1846                         }
1847                 }
1848
1849                 private struct UriScheme 
1850                 {
1851                         public string scheme;
1852                         public string delimiter;
1853                         public int defaultPort;
1854
1855                         public UriScheme (string s, string d, int p) 
1856                         {
1857                                 scheme = s;
1858                                 delimiter = d;
1859                                 defaultPort = p;
1860                         }
1861                 };
1862
1863                 static UriScheme [] schemes = new UriScheme [] {
1864                         new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1865                         new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1866                         new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1867                         new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1868                         new UriScheme (UriSchemeMailto, ":", 25),
1869                         new UriScheme (UriSchemeNews, ":", 119),
1870                         new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1871                         new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1872                 };
1873                                 
1874                 internal static string GetSchemeDelimiter (string scheme) 
1875                 {
1876                         for (int i = 0; i < schemes.Length; i++) 
1877                                 if (schemes [i].scheme == scheme)
1878                                         return schemes [i].delimiter;
1879                         return Uri.SchemeDelimiter;
1880                 }
1881                 
1882                 internal static int GetDefaultPort (string scheme)
1883                 {
1884                         UriParser parser = UriParser.GetParser (scheme);
1885                         if (parser == null)
1886                                 return -1;
1887                         return parser.DefaultPort;
1888                 }
1889
1890                 private string GetOpaqueWiseSchemeDelimiter ()
1891                 {
1892                         if (isOpaquePart)
1893                                 return ":";
1894                         else
1895                                 return GetSchemeDelimiter (scheme);
1896                 }
1897
1898                 [Obsolete]
1899                 protected virtual bool IsBadFileSystemCharacter (char character)
1900                 {
1901                         // It does not always overlap with InvalidPathChars.
1902                         int chInt = (int) character;
1903                         if (chInt < 32 || (chInt < 64 && chInt > 57))
1904                                 return true;
1905                         switch (chInt) {
1906                         case 0:
1907                         case 34: // "
1908                         case 38: // &
1909                         case 42: // *
1910                         case 44: // ,
1911                         case 47: // /
1912                         case 92: // \
1913                         case 94: // ^
1914                         case 124: // |
1915                                 return true;
1916                         }
1917
1918                         return false;
1919                 }
1920
1921                 [Obsolete]
1922                 protected static bool IsExcludedCharacter (char character)
1923                 {
1924                         if (character <= 32 || character >= 127)
1925                                 return true;
1926                         
1927                         if (character == '"' || character == '#' || character == '%' || character == '<' ||
1928                             character == '>' || character == '[' || character == '\\' || character == ']' ||
1929                             character == '^' || character == '`' || character == '{' || character == '|' ||
1930                             character == '}')
1931                                 return true;
1932                         return false;
1933                 }
1934
1935                 internal static bool MaybeUri (string s)
1936                 {
1937                         int p = s.IndexOf (':');
1938                         if (p == -1)
1939                                 return false;
1940
1941                         if (p >= 10)
1942                                 return false;
1943
1944                         return IsPredefinedScheme (s.Substring (0, p));
1945                 }
1946                 
1947                 //
1948                 // Using a simple block of if's is twice as slow as the compiler generated
1949                 // switch statement.   But using this tuned code is faster than the
1950                 // compiler generated code, with a million loops on x86-64:
1951                 //
1952                 // With "http": .10 vs .51 (first check)
1953                 // with "https": .16 vs .51 (second check)
1954                 // with "foo": .22 vs .31 (never found)
1955                 // with "mailto": .12 vs .51  (last check)
1956                 //
1957                 //
1958                 private static bool IsPredefinedScheme (string scheme)
1959                 {
1960                         if (scheme == null || scheme.Length < 3)
1961                                 return false;
1962                         
1963                         char c = scheme [0];
1964                         if (c == 'h')
1965                                 return (scheme == "http" || scheme == "https");
1966                         if (c == 'f')
1967                                 return (scheme == "file" || scheme == "ftp");
1968                                 
1969                         if (c == 'n'){
1970                                 c = scheme [1];
1971                                 if (c == 'e')
1972                                         return (scheme == "news" || scheme == "net.pipe" || scheme == "net.tcp");
1973                                 if (scheme == "nntp")
1974                                         return true;
1975                                 return false;
1976                         }
1977                         if ((c == 'g' && scheme == "gopher") || (c == 'm' && scheme == "mailto"))
1978                                 return true;
1979
1980                         return false;
1981                 }
1982
1983                 [Obsolete]
1984                 protected virtual bool IsReservedCharacter (char character)
1985                 {
1986                         if (character == '$' || character == '&' || character == '+' || character == ',' ||
1987                             character == '/' || character == ':' || character == ';' || character == '=' ||
1988                             character == '@')
1989                                 return true;
1990                         return false;
1991                 }
1992
1993                 [NonSerialized]
1994                 private UriParser parser;
1995
1996                 private UriParser Parser {
1997                         get {
1998                                 if (parser == null) {
1999                                         parser = UriParser.GetParser (Scheme);
2000                                         // no specific parser ? then use a default one
2001                                         if (parser == null)
2002                                                 parser = new DefaultUriParser ("*");
2003                                 }
2004                                 return parser;
2005                         }
2006                         set { parser = value; }
2007                 }
2008
2009                 public string GetComponents (UriComponents components, UriFormat format)
2010                 {
2011                         return Parser.GetComponents (this, components, format);
2012                 }
2013
2014                 public bool IsBaseOf (Uri uri)
2015                 {
2016 #if NET_4_0 || MOONLIGHT || MOBILE
2017                         if (uri == null)
2018                                 throw new ArgumentNullException ("uri");
2019 #endif
2020                         return Parser.IsBaseOf (this, uri);
2021                 }
2022
2023                 public bool IsWellFormedOriginalString ()
2024                 {
2025                         // funny, but it does not use the Parser's IsWellFormedOriginalString().
2026                         // Also, it seems we need to *not* escape hex.
2027                         return EscapeString (OriginalString, EscapeCommonBrackets) == OriginalString;
2028                 }
2029
2030                 // static methods
2031
2032                 private const int MaxUriLength = 32766;
2033
2034                 public static int Compare (Uri uri1, Uri uri2, UriComponents partsToCompare, UriFormat compareFormat, StringComparison comparisonType)
2035                 {
2036                         if ((comparisonType < StringComparison.CurrentCulture) || (comparisonType > StringComparison.OrdinalIgnoreCase)) {
2037                                 string msg = Locale.GetText ("Invalid StringComparison value '{0}'", comparisonType);
2038                                 throw new ArgumentException ("comparisonType", msg);
2039                         }
2040
2041                         if ((uri1 == null) && (uri2 == null))
2042                                 return 0;
2043
2044                         string s1 = uri1.GetComponents (partsToCompare, compareFormat);
2045                         string s2 = uri2.GetComponents (partsToCompare, compareFormat);
2046                         return String.Compare (s1, s2, comparisonType);
2047                 }
2048
2049                 //
2050                 // The rules for EscapeDataString
2051                 //
2052                 static bool NeedToEscapeDataChar (char b)
2053                 {
2054                         return !((b >= 'A' && b <= 'Z') ||
2055                                  (b >= 'a' && b <= 'z') ||
2056                                  (b >= '0' && b <= '9') ||
2057                                  b == '_' || b == '~' || b == '!' || b == '\'' ||
2058                                  b == '(' || b == ')' || b == '*' || b == '-' || b == '.');
2059                 }
2060                 
2061                 public static string EscapeDataString (string stringToEscape)
2062                 {
2063                         if (stringToEscape == null)
2064                                 throw new ArgumentNullException ("stringToEscape");
2065
2066                         if (stringToEscape.Length > MaxUriLength) {
2067                                 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
2068                                 throw new UriFormatException (msg);
2069                         }
2070                         bool escape = false;
2071                         foreach (char c in stringToEscape){
2072                                 if (NeedToEscapeDataChar (c)){
2073                                         escape = true;
2074                                         break;
2075                                 }
2076                         }
2077                         if (!escape){
2078                                 return stringToEscape;
2079                         }
2080                         
2081                         StringBuilder sb = new StringBuilder ();
2082                         byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
2083                         foreach (byte b in bytes){
2084                                 if (NeedToEscapeDataChar ((char) b))
2085                                         sb.Append (HexEscape ((char) b));
2086                                 else
2087                                         sb.Append ((char) b);
2088                         }
2089                         return sb.ToString ();
2090                 }
2091
2092                 //
2093                 // The rules for EscapeUriString
2094                 //
2095                 static bool NeedToEscapeUriChar (char b)
2096                 {
2097                         return !((b >= 'A' && b <= 'Z') ||
2098                                  (b >= 'a' && b <= 'z') ||
2099                                  (b >= '&' && b <= ';') ||
2100                                  b == '!' || b == '#' || b == '$' || b == '=' ||
2101                                  b == '?' || b == '@' || b == '_' || b == '~');
2102                 }
2103                 
2104                 public static string EscapeUriString (string stringToEscape)
2105                 {
2106                         if (stringToEscape == null)
2107                                 throw new ArgumentNullException ("stringToEscape");
2108
2109                         if (stringToEscape.Length > MaxUriLength) {
2110                                 string msg = Locale.GetText ("Uri is longer than the maximum {0} characters.");
2111                                 throw new UriFormatException (msg);
2112                         }
2113
2114                         bool escape = false;
2115                         foreach (char c in stringToEscape){
2116                                 if (NeedToEscapeUriChar (c)){
2117                                         escape = true;
2118                                         break;
2119                                 }
2120                         }
2121                         if (!escape)
2122                                 return stringToEscape;
2123
2124                         StringBuilder sb = new StringBuilder ();
2125                         byte [] bytes = Encoding.UTF8.GetBytes (stringToEscape);
2126                         foreach (byte b in bytes){
2127                                 if (NeedToEscapeUriChar ((char) b))
2128                                         sb.Append (HexEscape ((char) b));
2129                                 else
2130                                         sb.Append ((char) b);
2131                         }
2132                         return sb.ToString ();
2133                 }
2134
2135                 public static bool IsWellFormedUriString (string uriString, UriKind uriKind)
2136                 {
2137                         if (uriString == null)
2138                                 return false;
2139
2140                         Uri uri;
2141                         if (Uri.TryCreate (uriString, uriKind, out uri))
2142                                 return uri.IsWellFormedOriginalString ();
2143                         return false;
2144                 }
2145
2146                 public static bool TryCreate (string uriString, UriKind uriKind, out Uri result)
2147                 {
2148                         bool success;
2149
2150                         Uri r = new Uri (uriString, uriKind, out success);
2151                         if (success) {
2152                                 result = r;
2153                                 return true;
2154                         }
2155                         result = null;
2156                         return false;
2157                 }
2158
2159                 // [MonoTODO ("rework code to avoid exception catching")]
2160                 public static bool TryCreate (Uri baseUri, string relativeUri, out Uri result)
2161                 {
2162                         result = null;
2163                         if (relativeUri == null)
2164                                 return false;
2165
2166                         try {
2167                                 Uri relative = new Uri (relativeUri, UriKind.RelativeOrAbsolute);
2168                                 if ((baseUri != null) && baseUri.IsAbsoluteUri) {
2169                                         // FIXME: this should call UriParser.Resolve
2170                                         result = new Uri (baseUri, relative);
2171                                 } else if (relative.IsAbsoluteUri) {
2172                                         // special case - see unit tests
2173                                         result = relative;
2174                                 }
2175                                 return (result != null);
2176                         } catch (UriFormatException) {
2177                                 return false;
2178                         }
2179                 }
2180
2181                 //[MonoTODO ("rework code to avoid exception catching")]
2182                 public static bool TryCreate (Uri baseUri, Uri relativeUri, out Uri result)
2183                 {
2184                         result = null;
2185                         if ((baseUri == null) || !baseUri.IsAbsoluteUri)
2186                                 return false;
2187 #if NET_4_0 || MOONLIGHT || MOBILE
2188                         if (relativeUri == null)
2189                                 return false;
2190 #endif
2191                         try {
2192                                 // FIXME: this should call UriParser.Resolve
2193                                 result = new Uri (baseUri, relativeUri.OriginalString);
2194                                 return true;
2195                         } catch (UriFormatException) {
2196                                 return false;
2197                         }
2198                 }
2199
2200                 public static string UnescapeDataString (string stringToUnescape)
2201                 {
2202                         return UnescapeDataString (stringToUnescape, false);
2203                 }
2204
2205                 internal static string UnescapeDataString (string stringToUnescape, bool safe)
2206                 {
2207                         if (stringToUnescape == null)
2208                                 throw new ArgumentNullException ("stringToUnescape");
2209
2210                         if (stringToUnescape.IndexOf ('%') == -1 && stringToUnescape.IndexOf ('+') == -1)
2211                                 return stringToUnescape;
2212
2213                         StringBuilder output = new StringBuilder ();
2214                         long len = stringToUnescape.Length;
2215                         MemoryStream bytes = new MemoryStream ();
2216                         int xchar;
2217
2218                         for (int i = 0; i < len; i++) {
2219                                 if (stringToUnescape [i] == '%' && i + 2 < len && stringToUnescape [i + 1] != '%') {
2220                                         if (stringToUnescape [i + 1] == 'u' && i + 5 < len) {
2221                                                 if (bytes.Length > 0) {
2222                                                         output.Append (GetChars (bytes, Encoding.UTF8));
2223                                                         bytes.SetLength (0);
2224                                                 }
2225
2226                                                 xchar = GetChar (stringToUnescape, i + 2, 4, safe);
2227                                                 if (xchar != -1) {
2228                                                         output.Append ((char) xchar);
2229                                                         i += 5;
2230                                                 }
2231                                                 else {
2232                                                         output.Append ('%');
2233                                                 }
2234                                         }
2235                                         else if ((xchar = GetChar (stringToUnescape, i + 1, 2, safe)) != -1) {
2236                                                 bytes.WriteByte ((byte) xchar);
2237                                                 i += 2;
2238                                         }
2239                                         else {
2240                                                 output.Append ('%');
2241                                         }
2242                                         continue;
2243                                 }
2244
2245                                 if (bytes.Length > 0) {
2246                                         output.Append (GetChars (bytes, Encoding.UTF8));
2247                                         bytes.SetLength (0);
2248                                 }
2249
2250                                 output.Append (stringToUnescape [i]);
2251                         }
2252
2253                         if (bytes.Length > 0) {
2254                                 output.Append (GetChars (bytes, Encoding.UTF8));
2255                         }
2256
2257                         bytes = null;
2258                         return output.ToString ();
2259                 }
2260
2261                 private static int GetInt (byte b)
2262                 {
2263                         char c = (char) b;
2264                         if (c >= '0' && c <= '9')
2265                                 return c - '0';
2266
2267                         if (c >= 'a' && c <= 'f')
2268                                 return c - 'a' + 10;
2269
2270                         if (c >= 'A' && c <= 'F')
2271                                 return c - 'A' + 10;
2272
2273                         return -1;
2274                 }
2275
2276                 private static int GetChar (string str, int offset, int length, bool safe)
2277                 {
2278                         int val = 0;
2279                         int end = length + offset;
2280                         for (int i = offset; i < end; i++) {
2281                                 char c = str [i];
2282                                 if (c > 127)
2283                                         return -1;
2284
2285                                 int current = GetInt ((byte) c);
2286                                 if (current == -1)
2287                                         return -1;
2288                                 val = (val << 4) + current;
2289                         }
2290
2291                         if (!safe)
2292                                 return val;
2293
2294                         switch ((char) val) {
2295                         case '%':
2296                         case '#':
2297                         case '?':
2298                         case '/':
2299                         case '\\':
2300                         case '@':
2301                         case '&': // not documented
2302                                 return -1;
2303                         default:
2304                                 return val;
2305                         }
2306                 }
2307
2308                 private static char [] GetChars (MemoryStream b, Encoding e)
2309                 {
2310                         return e.GetChars (b.GetBuffer (), 0, (int) b.Length);
2311                 }
2312                 
2313
2314                 private void EnsureAbsoluteUri ()
2315                 {
2316                         if (!IsAbsoluteUri)
2317                                 throw new InvalidOperationException ("This operation is not supported for a relative URI.");
2318                 }
2319         }
2320 }