(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / corlib / Mono.Security / Uri.cs
blob5d7b3e8f736b97213367c0ba8ff7e480b6348292
1 //
2 // Mono.Security.Uri
3 // Adapted from System.Uri (in System.dll assembly) for its use in corlib
4 //
5 // Authors:
6 // Lawrence Pit (loz@cable.a2000.nl)
7 // Garrett Rooney (rooneg@electricjellyfish.net)
8 // Ian MacLean (ianm@activestate.com)
9 // Ben Maurer (bmaurer@users.sourceforge.net)
10 // Atsushi Enomoto (atsushi@ximian.com)
12 // (C) 2001 Garrett Rooney
13 // (C) 2003 Ian MacLean
14 // (C) 2003 Ben Maurer
15 // (C) 2003 Novell inc.
16 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
18 // Permission is hereby granted, free of charge, to any person obtaining
19 // a copy of this software and associated documentation files (the
20 // "Software"), to deal in the Software without restriction, including
21 // without limitation the rights to use, copy, modify, merge, publish,
22 // distribute, sublicense, and/or sell copies of the Software, and to
23 // permit persons to whom the Software is furnished to do so, subject to
24 // the following conditions:
25 //
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
28 //
29 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 using System;
39 using System.IO;
40 using System.Text;
41 using System.Collections;
42 using System.Globalization;
44 // See RFC 2396 for more info on URI's.
46 // TODO: optimize by parsing host string only once
48 namespace Mono.Security {
50 #if INSIDE_CORLIB
51 internal
52 #else
53 public
54 #endif
55 enum UriPartial {
56 Scheme = 0,
57 Authority = 1,
58 Path = 2,
61 #if INSIDE_CORLIB
62 internal
63 #else
64 public
65 #endif
66 class Uri {
68 // NOTES:
69 // o scheme excludes the scheme delimiter
70 // o port is -1 to indicate no port is defined
71 // o path is empty or starts with / when scheme delimiter == "://"
72 // o query is empty or starts with ? char, escaped.
73 // o fragment is empty or starts with # char, unescaped.
74 // o all class variables are in escaped format when they are escapable,
75 // except cachedToString.
76 // o UNC is supported, as starts with "\\" for windows,
77 // or "//" with unix.
79 private bool isUnixFilePath = false;
80 private string source;
81 private string scheme = String.Empty;
82 private string host = String.Empty;
83 private int port = -1;
84 private string path = String.Empty;
85 private string query = String.Empty;
86 private string fragment = String.Empty;
87 private string userinfo = String.Empty;
88 private bool isUnc = false;
89 private bool isOpaquePart = false;
91 private string [] segments;
93 private bool userEscaped = false;
94 private string cachedAbsoluteUri = null;
95 private string cachedToString = null;
96 private string cachedLocalPath = null;
97 private int cachedHashCode = 0;
98 private bool reduce = true;
100 private static readonly string hexUpperChars = "0123456789ABCDEF";
102 // Fields
104 public static readonly string SchemeDelimiter = "://";
105 public static readonly string UriSchemeFile = "file";
106 public static readonly string UriSchemeFtp = "ftp";
107 public static readonly string UriSchemeGopher = "gopher";
108 public static readonly string UriSchemeHttp = "http";
109 public static readonly string UriSchemeHttps = "https";
110 public static readonly string UriSchemeMailto = "mailto";
111 public static readonly string UriSchemeNews = "news";
112 public static readonly string UriSchemeNntp = "nntp";
114 // Constructors
116 public Uri (string uriString) : this (uriString, false)
120 public Uri (string uriString, bool dontEscape)
122 userEscaped = dontEscape;
123 source = uriString;
124 Parse ();
127 public Uri (string uriString, bool dontEscape, bool reduce)
129 userEscaped = dontEscape;
130 source = uriString;
131 this.reduce = reduce;
132 Parse ();
135 public Uri (Uri baseUri, string relativeUri)
136 : this (baseUri, relativeUri, false)
140 public Uri (Uri baseUri, string relativeUri, bool dontEscape)
142 if (baseUri == null)
143 throw new NullReferenceException ("baseUri");
145 // See RFC 2396 Par 5.2 and Appendix C
147 userEscaped = dontEscape;
149 if (relativeUri == null)
150 throw new NullReferenceException ("relativeUri");
152 // Check Windows UNC (for // it is scheme/host separator)
153 if (relativeUri.StartsWith ("\\\\")) {
154 source = relativeUri;
155 Parse ();
156 return;
159 int pos = relativeUri.IndexOf (':');
160 if (pos != -1) {
162 int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
164 // pos2 < 0 ... e.g. mailto
165 // pos2 > pos ... to block ':' in query part
166 if (pos2 > pos || pos2 < 0) {
167 // equivalent to new Uri (relativeUri, dontEscape)
168 source = relativeUri;
169 Parse ();
171 return;
175 this.scheme = baseUri.scheme;
176 this.host = baseUri.host;
177 this.port = baseUri.port;
178 this.userinfo = baseUri.userinfo;
179 this.isUnc = baseUri.isUnc;
180 this.isUnixFilePath = baseUri.isUnixFilePath;
181 this.isOpaquePart = baseUri.isOpaquePart;
183 if (relativeUri == String.Empty) {
184 this.path = baseUri.path;
185 this.query = baseUri.query;
186 this.fragment = baseUri.fragment;
187 return;
190 // 8 fragment
191 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
192 pos = relativeUri.IndexOf ('#');
193 if (pos != -1) {
194 fragment = relativeUri.Substring (pos);
195 // fragment is not escaped.
196 relativeUri = relativeUri.Substring (0, pos);
199 // 6 query
200 pos = relativeUri.IndexOf ('?');
201 if (pos != -1) {
202 query = relativeUri.Substring (pos);
203 if (!userEscaped)
204 query = EscapeString (query);
205 relativeUri = relativeUri.Substring (0, pos);
208 if (relativeUri.Length > 0 && relativeUri [0] == '/') {
209 if (relativeUri.Length > 1 && relativeUri [1] == '/') {
210 source = scheme + ':' + relativeUri;
211 Parse ();
212 return;
213 } else {
214 path = relativeUri;
215 if (!userEscaped)
216 path = EscapeString (path);
217 return;
221 // par 5.2 step 6 a)
222 path = baseUri.path;
223 if (relativeUri.Length > 0 || query.Length > 0) {
224 pos = path.LastIndexOf ('/');
225 if (pos >= 0)
226 path = path.Substring (0, pos + 1);
229 if(relativeUri.Length == 0)
230 return;
232 // 6 b)
233 path += relativeUri;
235 // 6 c)
236 int startIndex = 0;
237 while (true) {
238 pos = path.IndexOf ("./", startIndex);
239 if (pos == -1)
240 break;
241 if (pos == 0)
242 path = path.Remove (0, 2);
243 else if (path [pos - 1] != '.')
244 path = path.Remove (pos, 2);
245 else
246 startIndex = pos + 1;
249 // 6 d)
250 if (path.Length > 1 &&
251 path [path.Length - 1] == '.' &&
252 path [path.Length - 2] == '/')
253 path = path.Remove (path.Length - 1, 1);
255 // 6 e)
256 startIndex = 0;
257 while (true) {
258 pos = path.IndexOf ("/../", startIndex);
259 if (pos == -1)
260 break;
261 if (pos == 0) {
262 startIndex = 3;
263 continue;
265 int pos2 = path.LastIndexOf ('/', pos - 1);
266 if (pos2 == -1) {
267 startIndex = pos + 1;
268 } else {
269 if (path.Substring (pos2 + 1, pos - pos2 - 1) != "..")
270 path = path.Remove (pos2 + 1, pos - pos2 + 3);
271 else
272 startIndex = pos + 1;
276 // 6 f)
277 if (path.Length > 3 && path.EndsWith ("/..")) {
278 pos = path.LastIndexOf ('/', path.Length - 4);
279 if (pos != -1)
280 if (path.Substring (pos + 1, path.Length - pos - 4) != "..")
281 path = path.Remove (pos + 1, path.Length - pos - 1);
284 if (!userEscaped)
285 path = EscapeString (path);
288 // Properties
290 public string AbsolutePath {
291 get { return path; }
294 public string AbsoluteUri {
295 get {
296 if (cachedAbsoluteUri == null) {
297 cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
299 return cachedAbsoluteUri;
303 public string Authority {
304 get {
305 return (GetDefaultPort (scheme) == port)
306 ? host : host + ":" + port;
310 public string Fragment {
311 get { return fragment; }
314 public string Host {
315 get { return host; }
318 /* public UriHostNameType HostNameType {
319 get {
320 UriHostNameType ret = CheckHostName (host);
321 if (ret != UriHostNameType.Unknown)
322 return ret;
324 // looks it always returns Basic...
325 return UriHostNameType.Basic; //.Unknown;
329 public bool IsDefaultPort {
330 get { return GetDefaultPort (scheme) == port; }
333 public bool IsFile {
334 get { return (scheme == UriSchemeFile); }
337 public bool IsLoopback {
338 get {
339 if (host == String.Empty)
340 return false;
342 if (host == "loopback" || host == "localhost")
343 return true;
345 try {
346 if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
347 return true;
348 } catch (FormatException) {}
350 try {
351 return IPv6Address.IsLoopback (IPv6Address.Parse (host));
352 } catch (FormatException) {}
354 return false;
358 public bool IsUnc {
359 // rule: This should be true only if
360 // - uri string starts from "\\", or
361 // - uri string starts from "//" (Samba way)
362 get { return isUnc; }
365 public string LocalPath {
366 get {
367 if (cachedLocalPath != null)
368 return cachedLocalPath;
369 if (!IsFile)
370 return AbsolutePath;
372 bool windows = (path.Length > 3 && path [1] == ':' &&
373 (path [2] == '\\' || path [2] == '/'));
375 if (!IsUnc) {
376 string p = Unescape (path);
377 if (System.IO.Path.DirectorySeparatorChar == '\\' || windows)
378 cachedLocalPath = p.Replace ('/', '\\');
379 else
380 cachedLocalPath = p;
381 } else {
382 // support *nix and W32 styles
383 if (path.Length > 1 && path [1] == ':')
384 cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
386 // LAMESPEC: ok, now we cannot determine
387 // if such URI like "file://foo/bar" is
388 // Windows UNC or unix file path, so
389 // they should be handled differently.
390 else if (System.IO.Path.DirectorySeparatorChar == '\\')
391 cachedLocalPath = "\\\\" + Unescape (host + path.Replace ('/', '\\'));
392 else
393 cachedLocalPath = Unescape (path);
395 if (cachedLocalPath == String.Empty)
396 cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
397 return cachedLocalPath;
401 public string PathAndQuery {
402 get { return path + query; }
405 public int Port {
406 get { return port; }
409 public string Query {
410 get { return query; }
413 public string Scheme {
414 get { return scheme; }
417 public string [] Segments {
418 get {
419 if (segments != null)
420 return segments;
422 if (path == "") {
423 segments = new string [0];
424 return segments;
427 string [] parts = path.Split ('/');
428 segments = parts;
429 bool endSlash = path.EndsWith ("/");
430 if (parts.Length > 0 && endSlash) {
431 string [] newParts = new string [parts.Length - 1];
432 Array.Copy (parts, 0, newParts, 0, parts.Length - 1);
433 parts = newParts;
436 int i = 0;
437 if (IsFile && path.Length > 1 && path [1] == ':') {
438 string [] newParts = new string [parts.Length + 1];
439 Array.Copy (parts, 1, newParts, 2, parts.Length - 1);
440 parts = newParts;
441 parts [0] = path.Substring (0, 2);
442 parts [1] = "";
443 i++;
446 int end = parts.Length;
447 for (; i < end; i++)
448 if (i != end - 1 || endSlash)
449 parts [i] += '/';
451 segments = parts;
452 return segments;
456 public bool UserEscaped {
457 get { return userEscaped; }
460 public string UserInfo {
461 get { return userinfo; }
465 // Methods
467 /* public static UriHostNameType CheckHostName (string name)
469 if (name == null || name.Length == 0)
470 return UriHostNameType.Unknown;
472 if (IsIPv4Address (name))
473 return UriHostNameType.IPv4;
475 if (IsDomainAddress (name))
476 return UriHostNameType.Dns;
478 try {
479 IPv6Address.Parse (name);
480 return UriHostNameType.IPv6;
481 } catch (FormatException) {}
483 return UriHostNameType.Unknown;
486 internal static bool IsIPv4Address (string name)
488 string [] captures = name.Split (new char [] {'.'});
489 if (captures.Length != 4)
490 return false;
491 for (int i = 0; i < 4; i++) {
492 try {
493 int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
494 if (d < 0 || d > 255)
495 return false;
496 } catch (Exception) {
497 return false;
500 return true;
503 internal static bool IsDomainAddress (string name)
505 int len = name.Length;
507 if (name [len - 1] == '.')
508 return false;
510 int count = 0;
511 for (int i = 0; i < len; i++) {
512 char c = name [i];
513 if (count == 0) {
514 if (!Char.IsLetterOrDigit (c))
515 return false;
516 } else if (c == '.') {
517 count = 0;
518 } else if (!Char.IsLetterOrDigit (c) && c != '-' && c != '_') {
519 return false;
521 if (++count == 64)
522 return false;
525 return true;
528 /* [MonoTODO ("Find out what this should do")]
529 protected virtual void Canonicalize ()
533 public static bool CheckSchemeName (string schemeName)
535 if (schemeName == null || schemeName.Length == 0)
536 return false;
538 if (!Char.IsLetter (schemeName [0]))
539 return false;
541 int len = schemeName.Length;
542 for (int i = 1; i < len; i++) {
543 char c = schemeName [i];
544 if (!Char.IsLetterOrDigit (c) && c != '.' && c != '+' && c != '-')
545 return false;
548 return true;
551 /* [MonoTODO ("Find out what this should do")]
552 protected virtual void CheckSecurity ()
556 public override bool Equals (object comparant)
558 if (comparant == null)
559 return false;
561 Uri uri = comparant as Uri;
562 if (uri == null) {
563 string s = comparant as String;
564 if (s == null)
565 return false;
566 uri = new Uri (s);
569 CultureInfo inv = CultureInfo.InvariantCulture;
570 return ((this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)) &&
571 (this.userinfo.ToLower (inv) == uri.userinfo.ToLower (inv)) &&
572 (this.host.ToLower (inv) == uri.host.ToLower (inv)) &&
573 (this.port == uri.port) &&
574 (this.path == uri.path) &&
575 (this.query.ToLower (inv) == uri.query.ToLower (inv)));
578 public override int GetHashCode ()
580 if (cachedHashCode == 0)
581 cachedHashCode = scheme.GetHashCode ()
582 + userinfo.GetHashCode ()
583 + host.GetHashCode ()
584 + port
585 + path.GetHashCode ()
586 + query.GetHashCode ();
587 return cachedHashCode;
590 public string GetLeftPart (UriPartial part)
592 int defaultPort;
593 switch (part) {
594 case UriPartial.Scheme :
595 return scheme + GetOpaqueWiseSchemeDelimiter ();
596 case UriPartial.Authority :
597 if (host == String.Empty ||
598 scheme == Uri.UriSchemeMailto ||
599 scheme == Uri.UriSchemeNews)
600 return String.Empty;
602 StringBuilder s = new StringBuilder ();
603 s.Append (scheme);
604 s.Append (GetOpaqueWiseSchemeDelimiter ());
605 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
606 s.Append ('/'); // win32 file
607 if (userinfo.Length > 0)
608 s.Append (userinfo).Append ('@');
609 s.Append (host);
610 defaultPort = GetDefaultPort (scheme);
611 if ((port != -1) && (port != defaultPort))
612 s.Append (':').Append (port);
613 return s.ToString ();
614 case UriPartial.Path :
615 StringBuilder sb = new StringBuilder ();
616 sb.Append (scheme);
617 sb.Append (GetOpaqueWiseSchemeDelimiter ());
618 if (path.Length > 1 && path [1] == ':' && (Uri.UriSchemeFile == scheme))
619 sb.Append ('/'); // win32 file
620 if (userinfo.Length > 0)
621 sb.Append (userinfo).Append ('@');
622 sb.Append (host);
623 defaultPort = GetDefaultPort (scheme);
624 if ((port != -1) && (port != defaultPort))
625 sb.Append (':').Append (port);
626 sb.Append (path);
627 return sb.ToString ();
629 return null;
632 public static int FromHex (char digit)
634 if ('0' <= digit && digit <= '9') {
635 return (int) (digit - '0');
638 if ('a' <= digit && digit <= 'f')
639 return (int) (digit - 'a' + 10);
641 if ('A' <= digit && digit <= 'F')
642 return (int) (digit - 'A' + 10);
644 throw new ArgumentException ("digit");
647 public static string HexEscape (char character)
649 if (character > 255) {
650 throw new ArgumentOutOfRangeException ("character");
653 return "%" + hexUpperChars [((character & 0xf0) >> 4)]
654 + hexUpperChars [((character & 0x0f))];
657 public static char HexUnescape (string pattern, ref int index)
659 if (pattern == null)
660 throw new ArgumentException ("pattern");
662 if (index < 0 || index >= pattern.Length)
663 throw new ArgumentOutOfRangeException ("index");
665 int stage = 0;
666 int c = 0;
667 do {
668 if (((index + 3) > pattern.Length) ||
669 (pattern [index] != '%') ||
670 !IsHexDigit (pattern [index + 1]) ||
671 !IsHexDigit (pattern [index + 2]))
673 if (stage == 0)
674 return pattern [index++];
675 break;
678 index++;
679 int msb = FromHex (pattern [index++]);
680 int lsb = FromHex (pattern [index++]);
681 int b = (msb << 4) + lsb;
683 if (stage == 0) {
684 if (b < 0xc0)
685 return (char) b;
686 else if (b < 0xE0) {
687 c = b - 0xc0;
688 stage = 2;
689 } else if (b < 0xF0) {
690 c = b - 0xe0;
691 stage = 3;
692 } else if (b < 0xF8) {
693 c = b - 0xf0;
694 stage = 4;
695 } else if (b < 0xFB) {
696 c = b - 0xf8;
697 stage = 5;
698 } else if (b < 0xFE) {
699 c = b - 0xfc;
700 stage = 6;
702 c <<= (stage - 1) * 6;
704 else
705 c += (b - 0x80) << ((stage - 1) * 6);
706 //Console.WriteLine ("stage {0}: {5:X04} <-- {1:X02}|{2:X01},{3:X01} {4}", new object [] {stage, b, msb, lsb, pattern.Substring (index), c});
707 stage--;
708 } while (stage > 0);
710 return (char) c;
713 public static bool IsHexDigit (char digit)
715 return (('0' <= digit && digit <= '9') ||
716 ('a' <= digit && digit <= 'f') ||
717 ('A' <= digit && digit <= 'F'));
720 public static bool IsHexEncoding (string pattern, int index)
722 if ((index + 3) > pattern.Length)
723 return false;
725 return ((pattern [index++] == '%') &&
726 IsHexDigit (pattern [index++]) &&
727 IsHexDigit (pattern [index]));
730 public string MakeRelative (Uri toUri)
732 if ((this.Scheme != toUri.Scheme) ||
733 (this.Authority != toUri.Authority))
734 return toUri.ToString ();
736 if (this.path == toUri.path)
737 return String.Empty;
739 string [] segments = this.Segments;
740 string [] segments2 = toUri.Segments;
742 int k = 0;
743 int max = System.Math.Min (segments.Length, segments2.Length);
744 for (; k < max; k++)
745 if (segments [k] != segments2 [k])
746 break;
748 string result = String.Empty;
749 for (int i = k + 1; i < segments.Length; i++)
750 result += "../";
751 for (int i = k; i < segments2.Length; i++)
752 result += segments2 [i];
754 return result;
757 public override string ToString ()
759 if (cachedToString != null)
760 return cachedToString;
761 string q = query.StartsWith ("?") ? '?' + Unescape (query.Substring (1)) : Unescape (query);
762 cachedToString = Unescape (GetLeftPart (UriPartial.Path), true) + q + fragment;
763 return cachedToString;
766 /* void ISerializable.GetObjectData (SerializationInfo info,
767 StreamingContext context)
769 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
773 // Internal Methods
775 protected void Escape ()
777 path = EscapeString (path);
780 protected static string EscapeString (string str)
782 return EscapeString (str, false, true, true);
785 internal static string EscapeString (string str, bool escapeReserved, bool escapeHex, bool escapeBrackets)
787 if (str == null)
788 return String.Empty;
790 byte [] data = Encoding.UTF8.GetBytes (str.ToCharArray ());
791 StringBuilder s = new StringBuilder ();
792 int len = data.Length;
793 for (int i = 0; i < len; i++) {
794 char c = (char) data [i];
795 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
796 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
797 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
798 // space = <US-ASCII coded character 20 hexadecimal>
799 // delims = "<" | ">" | "#" | "%" | <">
800 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
802 // check for escape code already placed in str,
803 // i.e. for encoding that follows the pattern
804 // "%hexhex" in a string, where "hex" is a digit from 0-9
805 // or a letter from A-F (case-insensitive).
806 if('%'.Equals(c) && IsHexEncoding(str,i))
808 // if ,yes , copy it as is
809 s.Append(c);
810 s.Append(str[++i]);
811 s.Append(str[++i]);
812 continue;
815 if ((c <= 0x20) || (c >= 0x7f) ||
816 ("<>%\"{}|\\^`".IndexOf (c) != -1) ||
817 (escapeHex && (c == '#')) ||
818 (escapeBrackets && (c == '[' || c == ']')) ||
819 (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
820 s.Append (HexEscape (c));
821 continue;
825 s.Append (c);
828 return s.ToString ();
831 // This method is called from .ctor(). When overriden, we can
832 // avoid the "absolute uri" constraints of the .ctor() by
833 // overriding with custom code.
834 protected void Parse ()
836 Parse (source);
838 if (userEscaped)
839 return;
841 host = EscapeString (host, false, true, false);
842 path = EscapeString (path);
845 protected string Unescape (string str)
847 return Unescape (str, false);
850 internal string Unescape (string str, bool excludeSharp)
852 if (str == null)
853 return String.Empty;
854 StringBuilder s = new StringBuilder ();
855 int len = str.Length;
856 for (int i = 0; i < len; i++) {
857 char c = str [i];
858 if (c == '%') {
859 char x = HexUnescape (str, ref i);
860 if (excludeSharp && x == '#')
861 s.Append ("%23");
862 else
863 s.Append (x);
864 i--;
865 } else
866 s.Append (c);
868 return s.ToString ();
872 // Private Methods
874 private void ParseAsWindowsUNC (string uriString)
876 scheme = UriSchemeFile;
877 port = -1;
878 fragment = String.Empty;
879 query = String.Empty;
880 isUnc = true;
882 uriString = uriString.TrimStart (new char [] {'\\'});
883 int pos = uriString.IndexOf ('\\');
884 if (pos > 0) {
885 path = uriString.Substring (pos);
886 host = uriString.Substring (0, pos);
887 } else { // "\\\\server"
888 host = uriString;
889 path = String.Empty;
891 path = path.Replace ("\\", "/");
894 private void ParseAsWindowsAbsoluteFilePath (string uriString)
896 if (uriString.Length > 2 && uriString [2] != '\\'
897 && uriString [2] != '/')
898 throw new FormatException ("Relative file path is not allowed.");
899 scheme = UriSchemeFile;
900 host = String.Empty;
901 port = -1;
902 path = uriString.Replace ("\\", "/");
903 fragment = String.Empty;
904 query = String.Empty;
907 private void ParseAsUnixAbsoluteFilePath (string uriString)
909 isUnixFilePath = true;
910 scheme = UriSchemeFile;
911 port = -1;
912 fragment = String.Empty;
913 query = String.Empty;
914 host = String.Empty;
915 path = null;
917 if (uriString.StartsWith ("//")) {
918 uriString = uriString.TrimStart (new char [] {'/'});
919 // Now we don't regard //foo/bar as "foo" host.
921 int pos = uriString.IndexOf ('/');
922 if (pos > 0) {
923 path = '/' + uriString.Substring (pos + 1);
924 host = uriString.Substring (0, pos);
925 } else { // "///server"
926 host = uriString;
927 path = String.Empty;
930 path = '/' + uriString;
932 if (path == null)
933 path = uriString;
936 // this parse method is as relaxed as possible about the format
937 // it will hardly ever throw a UriFormatException
938 private void Parse (string uriString)
941 // From RFC 2396 :
943 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
944 // 12 3 4 5 6 7 8 9
947 if (uriString == null)
948 throw new ArgumentNullException ("uriString");
950 int len = uriString.Length;
951 if (len <= 1)
952 throw new FormatException ();
954 int pos = 0;
956 // 1, 2
957 // Identify Windows path, unix path, or standard URI.
958 pos = uriString.IndexOf (':');
959 if (pos < 0) {
960 // It must be Unix file path or Windows UNC
961 if (uriString [0] == '/')
962 ParseAsUnixAbsoluteFilePath (uriString);
963 else if (uriString.StartsWith ("\\\\"))
964 ParseAsWindowsUNC (uriString);
965 else
966 throw new FormatException ("URI scheme was not recognized, nor input string is not recognized as an absolute file path.");
967 return;
969 else if (pos == 1) {
970 if (!Char.IsLetter (uriString [0]))
971 throw new FormatException ("URI scheme must start with alphabet character.");
972 // This means 'a:' == windows full path.
973 ParseAsWindowsAbsoluteFilePath (uriString);
974 return;
977 // scheme
978 scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
979 // Check scheme name characters as specified in RFC2396.
980 if (!Char.IsLetter (scheme [0]))
981 throw new FormatException ("URI scheme must start with alphabet character.");
982 for (int i = 1; i < scheme.Length; i++) {
983 if (!Char.IsLetterOrDigit (scheme, i)) {
984 switch (scheme [i]) {
985 case '+':
986 case '-':
987 case '.':
988 break;
989 default:
990 throw new FormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
994 uriString = uriString.Substring (pos + 1);
996 // 8 fragment
997 pos = uriString.IndexOf ('#');
998 if (!IsUnc && pos != -1) {
999 fragment = uriString.Substring (pos);
1000 uriString = uriString.Substring (0, pos);
1003 // 6 query
1004 pos = uriString.IndexOf ('?');
1005 if (pos != -1) {
1006 query = uriString.Substring (pos);
1007 uriString = uriString.Substring (0, pos);
1008 if (!userEscaped)
1009 query = EscapeString (query);
1012 // 3
1013 bool unixAbsPath = scheme == UriSchemeFile && uriString.StartsWith ("///");
1014 if (uriString.StartsWith ("//")) {
1015 if (uriString.StartsWith ("////"))
1016 unixAbsPath = false;
1017 uriString = uriString.TrimStart (new char [] {'/'});
1018 if (uriString.Length > 1 && uriString [1] == ':')
1019 unixAbsPath = false;
1020 } else if (!IsPredefinedScheme (scheme)) {
1021 path = uriString;
1022 isOpaquePart = true;
1023 return;
1026 // 5 path
1027 pos = uriString.IndexOfAny (new char[] {'/'});
1028 if (unixAbsPath)
1029 pos = -1;
1030 if (pos == -1) {
1031 if ((scheme != Uri.UriSchemeMailto) &&
1032 (scheme != Uri.UriSchemeNews) &&
1033 (scheme != Uri.UriSchemeFile))
1034 path = "/";
1035 } else {
1036 path = uriString.Substring (pos);
1037 uriString = uriString.Substring (0, pos);
1040 // 4.a user info
1041 pos = uriString.IndexOf ("@");
1042 if (pos != -1) {
1043 userinfo = uriString.Substring (0, pos);
1044 uriString = uriString.Remove (0, pos + 1);
1047 // 4.b port
1048 port = -1;
1049 pos = uriString.LastIndexOf (":");
1050 if (unixAbsPath)
1051 pos = -1;
1052 if (pos != -1 && pos != (uriString.Length - 1)) {
1053 string portStr = uriString.Remove (0, pos + 1);
1054 if (portStr.Length > 1 && portStr [portStr.Length - 1] != ']') {
1055 try {
1056 port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
1057 uriString = uriString.Substring (0, pos);
1058 } catch (Exception) {
1059 throw new FormatException ("Invalid URI: invalid port number");
1063 if (port == -1) {
1064 port = GetDefaultPort (scheme);
1067 // 4 authority
1068 host = uriString;
1069 /* if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']') {
1070 try {
1071 host = "[" + IPv6Address.Parse (host).ToString () + "]";
1072 } catch (Exception) {
1073 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1077 if (unixAbsPath) {
1078 path = '/' + uriString;
1079 host = String.Empty;
1080 } else if (host.Length == 2 && host [1] == ':') {
1081 // windows filepath
1082 path = host + path;
1083 host = String.Empty;
1084 } else if (isUnixFilePath) {
1085 uriString = "//" + uriString;
1086 host = String.Empty;
1087 } else if (host.Length == 0) {
1088 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1089 } else if (scheme == UriSchemeFile) {
1090 isUnc = true;
1093 if ((scheme != Uri.UriSchemeMailto) &&
1094 (scheme != Uri.UriSchemeNews) &&
1095 (scheme != Uri.UriSchemeFile))
1097 if (reduce)
1098 path = Reduce (path);
1101 private static string Reduce (string path)
1103 path = path.Replace ('\\','/');
1104 string [] parts = path.Split ('/');
1105 ArrayList result = new ArrayList ();
1107 int end = parts.Length;
1108 for (int i = 0; i < end; i++) {
1109 string current = parts [i];
1110 if (current == "" || current == "." )
1111 continue;
1113 if (current == "..") {
1114 if (result.Count == 0) {
1115 if (i == 1) // see bug 52599
1116 continue;
1117 throw new Exception ("Invalid path.");
1120 result.RemoveAt (result.Count - 1);
1121 continue;
1124 result.Add (current);
1127 if (result.Count == 0)
1128 return "/";
1130 result.Insert (0, "");
1132 string res = String.Join ("/", (string []) result.ToArray (typeof (string)));
1133 if (path.EndsWith ("/"))
1134 res += '/';
1136 return res;
1139 private struct UriScheme
1141 public string scheme;
1142 public string delimiter;
1143 public int defaultPort;
1145 public UriScheme (string s, string d, int p)
1147 scheme = s;
1148 delimiter = d;
1149 defaultPort = p;
1153 static UriScheme [] schemes = new UriScheme [] {
1154 new UriScheme (UriSchemeHttp, SchemeDelimiter, 80),
1155 new UriScheme (UriSchemeHttps, SchemeDelimiter, 443),
1156 new UriScheme (UriSchemeFtp, SchemeDelimiter, 21),
1157 new UriScheme (UriSchemeFile, SchemeDelimiter, -1),
1158 new UriScheme (UriSchemeMailto, ":", 25),
1159 new UriScheme (UriSchemeNews, ":", -1),
1160 new UriScheme (UriSchemeNntp, SchemeDelimiter, 119),
1161 new UriScheme (UriSchemeGopher, SchemeDelimiter, 70),
1164 internal static string GetSchemeDelimiter (string scheme)
1166 for (int i = 0; i < schemes.Length; i++)
1167 if (schemes [i].scheme == scheme)
1168 return schemes [i].delimiter;
1169 return Uri.SchemeDelimiter;
1172 internal static int GetDefaultPort (string scheme)
1174 for (int i = 0; i < schemes.Length; i++)
1175 if (schemes [i].scheme == scheme)
1176 return schemes [i].defaultPort;
1177 return -1;
1180 private string GetOpaqueWiseSchemeDelimiter ()
1182 if (isOpaquePart)
1183 return ":";
1184 else
1185 return GetSchemeDelimiter (scheme);
1188 protected bool IsBadFileSystemCharacter (char ch)
1190 // It does not always overlap with InvalidPathChars.
1191 int chInt = (int) ch;
1192 if (chInt < 32 || (chInt < 64 && chInt > 57))
1193 return true;
1194 switch (chInt) {
1195 case 0:
1196 case 34: // "
1197 case 38: // &
1198 case 42: // *
1199 case 44: // ,
1200 case 47: // /
1201 case 92: // \
1202 case 94: // ^
1203 case 124: // |
1204 return true;
1207 return false;
1211 protected static bool IsExcludedCharacter (char ch)
1213 if (ch <= 32 || ch >= 127)
1214 return true;
1216 if (ch == '"' || ch == '#' || ch == '%' || ch == '<' ||
1217 ch == '>' || ch == '[' || ch == '\\' || ch == ']' ||
1218 ch == '^' || ch == '`' || ch == '{' || ch == '|' ||
1219 ch == '}')
1220 return true;
1221 return false;
1224 private static bool IsPredefinedScheme (string scheme)
1226 switch (scheme) {
1227 case "http":
1228 case "https":
1229 case "file":
1230 case "ftp":
1231 case "nntp":
1232 case "gopher":
1233 case "mailto":
1234 case "news":
1235 return true;
1236 default:
1237 return false;
1241 protected bool IsReservedCharacter (char ch)
1243 if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
1244 ch == '/' || ch == ':' || ch == ';' || ch == '=' ||
1245 ch == '@')
1246 return true;
1247 return false;