3 // Adapted from System.Uri (in System.dll assembly) for its use in corlib
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:
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
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.
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
{
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,
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";
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";
116 public Uri (string uriString
) : this (uriString
, false)
120 public Uri (string uriString
, bool dontEscape
)
122 userEscaped
= dontEscape
;
127 public Uri (string uriString
, bool dontEscape
, bool reduce
)
129 userEscaped
= dontEscape
;
131 this.reduce
= reduce
;
135 public Uri (Uri baseUri
, string relativeUri
)
136 : this (baseUri
, relativeUri
, false)
140 public Uri (Uri baseUri
, string relativeUri
, bool dontEscape
)
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
;
159 int pos
= relativeUri
.IndexOf (':');
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
;
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
;
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 ('#');
194 fragment
= relativeUri
.Substring (pos
);
195 // fragment is not escaped.
196 relativeUri
= relativeUri
.Substring (0, pos
);
200 pos
= relativeUri
.IndexOf ('?');
202 query
= relativeUri
.Substring (pos
);
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
;
216 path
= EscapeString (path
);
223 if (relativeUri
.Length
> 0 || query
.Length
> 0) {
224 pos
= path
.LastIndexOf ('/');
226 path
= path
.Substring (0, pos
+ 1);
229 if(relativeUri
.Length
== 0)
238 pos
= path
.IndexOf ("./", startIndex
);
242 path
= path
.Remove (0, 2);
243 else if (path
[pos
- 1] != '.')
244 path
= path
.Remove (pos
, 2);
246 startIndex
= pos
+ 1;
250 if (path
.Length
> 1 &&
251 path
[path
.Length
- 1] == '.' &&
252 path
[path
.Length
- 2] == '/')
253 path
= path
.Remove (path
.Length
- 1, 1);
258 pos
= path
.IndexOf ("/../", startIndex
);
265 int pos2
= path
.LastIndexOf ('/', pos
- 1);
267 startIndex
= pos
+ 1;
269 if (path
.Substring (pos2
+ 1, pos
- pos2
- 1) != "..")
270 path
= path
.Remove (pos2
+ 1, pos
- pos2
+ 3);
272 startIndex
= pos
+ 1;
277 if (path
.Length
> 3 && path
.EndsWith ("/..")) {
278 pos
= path
.LastIndexOf ('/', path
.Length
- 4);
280 if (path
.Substring (pos
+ 1, path
.Length
- pos
- 4) != "..")
281 path
= path
.Remove (pos
+ 1, path
.Length
- pos
- 1);
285 path
= EscapeString (path
);
290 public string AbsolutePath
{
294 public string AbsoluteUri
{
296 if (cachedAbsoluteUri
== null) {
297 cachedAbsoluteUri
= GetLeftPart (UriPartial
.Path
) + query
+ fragment
;
299 return cachedAbsoluteUri
;
303 public string Authority
{
305 return (GetDefaultPort (scheme
) == port
)
306 ? host
: host
+ ":" + port
;
310 public string Fragment
{
311 get { return fragment; }
318 /* public UriHostNameType HostNameType {
320 UriHostNameType ret = CheckHostName (host);
321 if (ret != UriHostNameType.Unknown)
324 // looks it always returns Basic...
325 return UriHostNameType.Basic; //.Unknown;
329 public bool IsDefaultPort
{
330 get { return GetDefaultPort (scheme) == port; }
334 get { return (scheme == UriSchemeFile); }
337 public bool IsLoopback
{
339 if (host
== String
.Empty
)
342 if (host
== "loopback" || host
== "localhost")
346 if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
348 } catch (FormatException) {}
351 return IPv6Address.IsLoopback (IPv6Address.Parse (host));
352 } catch (FormatException) {}
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
{
367 if (cachedLocalPath
!= null)
368 return cachedLocalPath
;
372 bool windows
= (path
.Length
> 3 && path
[1] == ':' &&
373 (path
[2] == '\\' || path
[2] == '/'));
376 string p
= Unescape (path
);
377 if (System
.IO
.Path
.DirectorySeparatorChar
== '\\' || windows
)
378 cachedLocalPath
= p
.Replace ('/', '\\');
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 ('/', '\\'));
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; }
409 public string Query
{
410 get { return query; }
413 public string Scheme
{
414 get { return scheme; }
417 public string [] Segments
{
419 if (segments
!= null)
423 segments
= new string [0];
427 string [] parts
= path
.Split ('/');
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);
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);
441 parts
[0] = path
.Substring (0, 2);
446 int end
= parts
.Length
;
448 if (i
!= end
- 1 || endSlash
)
456 public bool UserEscaped
{
457 get { return userEscaped; }
460 public string UserInfo
{
461 get { return userinfo; }
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;
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)
491 for (int i
= 0; i
< 4; i
++) {
493 int d
= Int32
.Parse (captures
[i
], CultureInfo
.InvariantCulture
);
494 if (d
< 0 || d
> 255)
496 } catch (Exception
) {
503 internal static bool IsDomainAddress (string name
)
505 int len
= name
.Length
;
507 if (name
[len
- 1] == '.')
511 for (int i
= 0; i
< len
; i
++) {
514 if (!Char
.IsLetterOrDigit (c
))
516 } else if (c
== '.') {
518 } else if (!Char
.IsLetterOrDigit (c
) && c
!= '-' && c
!= '_') {
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)
538 if (!Char
.IsLetter (schemeName
[0]))
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
!= '-')
551 /* [MonoTODO ("Find out what this should do")]
552 protected virtual void CheckSecurity ()
556 public override bool Equals (object comparant
)
558 if (comparant
== null)
561 Uri uri
= comparant
as Uri
;
563 string s
= comparant
as String
;
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 ()
585 + path
.GetHashCode ()
586 + query
.GetHashCode ();
587 return cachedHashCode
;
590 public string GetLeftPart (UriPartial 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
)
602 StringBuilder s
= new StringBuilder ();
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 ('@');
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 ();
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 ('@');
623 defaultPort
= GetDefaultPort (scheme
);
624 if ((port
!= -1) && (port
!= defaultPort
))
625 sb
.Append (':').Append (port
);
627 return sb
.ToString ();
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
)
660 throw new ArgumentException ("pattern");
662 if (index
< 0 || index
>= pattern
.Length
)
663 throw new ArgumentOutOfRangeException ("index");
668 if (((index
+ 3) > pattern
.Length
) ||
669 (pattern
[index
] != '%') ||
670 !IsHexDigit (pattern
[index
+ 1]) ||
671 !IsHexDigit (pattern
[index
+ 2]))
674 return pattern
[index
++];
679 int msb
= FromHex (pattern
[index
++]);
680 int lsb
= FromHex (pattern
[index
++]);
681 int b
= (msb
<< 4) + lsb
;
689 } else if (b
< 0xF0) {
692 } else if (b
< 0xF8) {
695 } else if (b
< 0xFB) {
698 } else if (b
< 0xFE) {
702 c
<<= (stage
- 1) * 6;
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});
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
)
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
)
739 string [] segments
= this.Segments
;
740 string [] segments2
= toUri
.Segments
;
743 int max
= System
.Math
.Min (segments
.Length
, segments2
.Length
);
745 if (segments
[k
] != segments2
[k
])
748 string result
= String
.Empty
;
749 for (int i
= k
+ 1; i
< segments
.Length
; i
++)
751 for (int i
= k
; i
< segments2
.Length
; i
++)
752 result
+= segments2
[i
];
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);
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
)
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
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
));
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 ()
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
)
854 StringBuilder s
= new StringBuilder ();
855 int len
= str
.Length
;
856 for (int i
= 0; i
< len
; i
++) {
859 char x
= HexUnescape (str
, ref i
);
860 if (excludeSharp
&& x
== '#')
868 return s
.ToString ();
874 private void ParseAsWindowsUNC (string uriString
)
876 scheme
= UriSchemeFile
;
878 fragment
= String
.Empty
;
879 query
= String
.Empty
;
882 uriString
= uriString
.TrimStart (new char [] {'\\'}
);
883 int pos
= uriString
.IndexOf ('\\');
885 path
= uriString
.Substring (pos
);
886 host
= uriString
.Substring (0, pos
);
887 } else { // "\\\\server"
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
;
902 path
= uriString
.Replace ("\\", "/");
903 fragment
= String
.Empty
;
904 query
= String
.Empty
;
907 private void ParseAsUnixAbsoluteFilePath (string uriString
)
909 isUnixFilePath
= true;
910 scheme
= UriSchemeFile
;
912 fragment
= String
.Empty
;
913 query
= String
.Empty
;
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 ('/');
923 path = '/' + uriString.Substring (pos + 1);
924 host = uriString.Substring (0, pos);
925 } else { // "///server"
930 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
)
943 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
947 if (uriString
== null)
948 throw new ArgumentNullException ("uriString");
950 int len
= uriString
.Length
;
952 throw new FormatException ();
957 // Identify Windows path, unix path, or standard URI.
958 pos
= uriString
.IndexOf (':');
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
);
966 throw new FormatException ("URI scheme was not recognized, nor input string is not recognized as an absolute file path.");
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
);
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
]) {
990 throw new FormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
994 uriString
= uriString
.Substring (pos
+ 1);
997 pos
= uriString
.IndexOf ('#');
998 if (!IsUnc
&& pos
!= -1) {
999 fragment
= uriString
.Substring (pos
);
1000 uriString
= uriString
.Substring (0, pos
);
1004 pos
= uriString
.IndexOf ('?');
1006 query
= uriString
.Substring (pos
);
1007 uriString
= uriString
.Substring (0, pos
);
1009 query
= EscapeString (query
);
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
)) {
1022 isOpaquePart
= true;
1027 pos
= uriString
.IndexOfAny (new char[] {'/'}
);
1031 if ((scheme
!= Uri
.UriSchemeMailto
) &&
1032 (scheme
!= Uri
.UriSchemeNews
) &&
1033 (scheme
!= Uri
.UriSchemeFile
))
1036 path
= uriString
.Substring (pos
);
1037 uriString
= uriString
.Substring (0, pos
);
1041 pos
= uriString
.IndexOf ("@");
1043 userinfo
= uriString
.Substring (0, pos
);
1044 uriString
= uriString
.Remove (0, pos
+ 1);
1049 pos
= uriString
.LastIndexOf (":");
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] != ']') {
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");
1064 port
= GetDefaultPort (scheme
);
1069 /* if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']') {
1071 host = "[" + IPv6Address.Parse (host).ToString () + "]";
1072 } catch (Exception) {
1073 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1078 path
= '/' + uriString
;
1079 host
= String
.Empty
;
1080 } else if (host
.Length
== 2 && host
[1] == ':') {
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
) {
1093 if ((scheme
!= Uri
.UriSchemeMailto
) &&
1094 (scheme
!= Uri
.UriSchemeNews
) &&
1095 (scheme
!= Uri
.UriSchemeFile
))
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
== "." )
1113 if (current
== "..") {
1114 if (result
.Count
== 0) {
1115 if (i
== 1) // see bug 52599
1117 throw new Exception ("Invalid path.");
1120 result
.RemoveAt (result
.Count
- 1);
1124 result
.Add (current
);
1127 if (result
.Count
== 0)
1130 result
.Insert (0, "");
1132 string res
= String
.Join ("/", (string []) result
.ToArray (typeof (string)));
1133 if (path
.EndsWith ("/"))
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
)
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
;
1180 private string GetOpaqueWiseSchemeDelimiter ()
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))
1211 protected static bool IsExcludedCharacter (char ch
)
1213 if (ch
<= 32 || ch
>= 127)
1216 if (ch
== '"' || ch
== '#' || ch
== '%' || ch
== '<' ||
1217 ch
== '>' || ch
== '[' || ch
== '\\' || ch
== ']' ||
1218 ch
== '^' || ch
== '`' || ch
== '{' || ch
== '|' ||
1224 private static bool IsPredefinedScheme (string scheme
)
1241 protected bool IsReservedCharacter (char ch
)
1243 if (ch
== '$' || ch
== '&' || ch
== '+' || ch
== ',' ||
1244 ch
== '/' || ch
== ':' || ch
== ';' || ch
== '=' ||