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)
11 // (C) 2001 Garrett Rooney
12 // (C) 2003 Ian MacLean
13 // (C) 2003 Ben Maurer
14 // (C) 2003 Novell inc.
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.
39 using System
.Runtime
.Serialization
;
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
51 public class Uri
: MarshalByRefObject
, ISerializable
54 // o scheme excludes the scheme delimiter
55 // o port is -1 to indicate no port is defined
56 // o path is empty or starts with / when scheme delimiter == "://"
57 // o query is empty or starts with ? char, escaped.
58 // o fragment is empty or starts with # char, unescaped.
59 // o all class variables are in escaped format when they are escapable,
60 // except cachedToString.
61 // o UNC is supported, as starts with "\\" for windows,
64 private bool isUnixFilePath
= false;
65 private string source
;
66 private string scheme
= String
.Empty
;
67 private string host
= String
.Empty
;
68 private int port
= -1;
69 private string path
= String
.Empty
;
70 private string query
= String
.Empty
;
71 private string fragment
= String
.Empty
;
72 private string userinfo
= String
.Empty
;
73 private bool isUnc
= false;
74 private bool isOpaquePart
= false;
76 private string [] segments
;
78 private bool userEscaped
= false;
79 private string cachedAbsoluteUri
= null;
80 private string cachedToString
= null;
81 private string cachedLocalPath
= null;
82 private int cachedHashCode
= 0;
84 private static readonly string hexUpperChars
= "0123456789ABCDEF";
88 public static readonly string SchemeDelimiter
= "://";
89 public static readonly string UriSchemeFile
= "file";
90 public static readonly string UriSchemeFtp
= "ftp";
91 public static readonly string UriSchemeGopher
= "gopher";
92 public static readonly string UriSchemeHttp
= "http";
93 public static readonly string UriSchemeHttps
= "https";
94 public static readonly string UriSchemeMailto
= "mailto";
95 public static readonly string UriSchemeNews
= "news";
96 public static readonly string UriSchemeNntp
= "nntp";
100 public Uri (string uriString
) : this (uriString
, false)
104 protected Uri (SerializationInfo serializationInfo
,
105 StreamingContext streamingContext
) :
106 this (serializationInfo
.GetString ("AbsoluteUri"), true)
110 public Uri (string uriString
, bool dontEscape
)
112 userEscaped
= dontEscape
;
117 public Uri (Uri baseUri
, string relativeUri
)
118 : this (baseUri
, relativeUri
, false)
122 public Uri (Uri baseUri
, string relativeUri
, bool dontEscape
)
125 throw new NullReferenceException ("baseUri");
127 // See RFC 2396 Par 5.2 and Appendix C
129 userEscaped
= dontEscape
;
131 if (relativeUri
== null)
132 throw new NullReferenceException ("relativeUri");
134 // Check Windows UNC (for // it is scheme/host separator)
135 if (relativeUri
.StartsWith ("\\\\")) {
136 source
= relativeUri
;
141 int pos
= relativeUri
.IndexOf (':');
144 int pos2
= relativeUri
.IndexOfAny (new char [] {'/', '\\', '?'}
);
146 // pos2 < 0 ... e.g. mailto
147 // pos2 > pos ... to block ':' in query part
148 if (pos2
> pos
|| pos2
< 0) {
149 // equivalent to new Uri (relativeUri, dontEscape)
150 source
= relativeUri
;
157 this.scheme
= baseUri
.scheme
;
158 this.host
= baseUri
.host
;
159 this.port
= baseUri
.port
;
160 this.userinfo
= baseUri
.userinfo
;
161 this.isUnc
= baseUri
.isUnc
;
162 this.isUnixFilePath
= baseUri
.isUnixFilePath
;
163 this.isOpaquePart
= baseUri
.isOpaquePart
;
165 if (relativeUri
== String
.Empty
) {
166 this.path
= baseUri
.path
;
167 this.query
= baseUri
.query
;
168 this.fragment
= baseUri
.fragment
;
173 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
174 pos
= relativeUri
.IndexOf ('#');
176 fragment
= relativeUri
.Substring (pos
);
177 // fragment is not escaped.
178 relativeUri
= relativeUri
.Substring (0, pos
);
182 pos
= relativeUri
.IndexOf ('?');
184 query
= relativeUri
.Substring (pos
);
186 query
= EscapeString (query
);
187 relativeUri
= relativeUri
.Substring (0, pos
);
190 if (relativeUri
.Length
> 0 && relativeUri
[0] == '/') {
191 if (relativeUri
.Length
> 1 && relativeUri
[1] == '/') {
192 source
= scheme
+ ':' + relativeUri
;
198 path
= EscapeString (path
);
205 if (relativeUri
.Length
> 0 || query
.Length
> 0) {
206 pos
= path
.LastIndexOf ('/');
208 path
= path
.Substring (0, pos
+ 1);
211 if(relativeUri
.Length
== 0)
220 pos
= path
.IndexOf ("./", startIndex
);
224 path
= path
.Remove (0, 2);
225 else if (path
[pos
- 1] != '.')
226 path
= path
.Remove (pos
, 2);
228 startIndex
= pos
+ 1;
232 if (path
.Length
> 1 &&
233 path
[path
.Length
- 1] == '.' &&
234 path
[path
.Length
- 2] == '/')
235 path
= path
.Remove (path
.Length
- 1, 1);
240 pos
= path
.IndexOf ("/../", startIndex
);
247 int pos2
= path
.LastIndexOf ('/', pos
- 1);
249 startIndex
= pos
+ 1;
251 if (path
.Substring (pos2
+ 1, pos
- pos2
- 1) != "..")
252 path
= path
.Remove (pos2
+ 1, pos
- pos2
+ 3);
254 startIndex
= pos
+ 1;
259 if (path
.Length
> 3 && path
.EndsWith ("/..")) {
260 pos
= path
.LastIndexOf ('/', path
.Length
- 4);
262 if (path
.Substring (pos
+ 1, path
.Length
- pos
- 4) != "..")
263 path
= path
.Remove (pos
+ 1, path
.Length
- pos
- 1);
267 path
= EscapeString (path
);
272 public string AbsolutePath
{
276 public string AbsoluteUri
{
278 if (cachedAbsoluteUri
== null) {
279 cachedAbsoluteUri
= GetLeftPart (UriPartial
.Path
) + query
+ fragment
;
281 return cachedAbsoluteUri
;
285 public string Authority
{
287 return (GetDefaultPort (scheme
) == port
)
288 ? host
: host
+ ":" + port
;
292 public string Fragment
{
293 get { return fragment; }
300 public UriHostNameType HostNameType
{
302 UriHostNameType ret
= CheckHostName (host
);
303 if (ret
!= UriHostNameType
.Unknown
)
306 // looks it always returns Basic...
307 return UriHostNameType
.Basic
; //.Unknown;
311 public bool IsDefaultPort
{
312 get { return GetDefaultPort (scheme) == port; }
316 get { return (scheme == UriSchemeFile); }
319 public bool IsLoopback
{
321 if (host
== String
.Empty
)
324 if (host
== "loopback" || host
== "localhost")
328 if (IPAddress
.Loopback
.Equals (IPAddress
.Parse (host
)))
330 } catch (FormatException
) {}
333 return IPv6Address
.IsLoopback (IPv6Address
.Parse (host
));
334 } catch (FormatException
) {}
341 // rule: This should be true only if
342 // - uri string starts from "\\", or
343 // - uri string starts from "//" (Samba way)
344 get { return isUnc; }
347 public string LocalPath
{
349 if (cachedLocalPath
!= null)
350 return cachedLocalPath
;
354 bool windows
= (path
.Length
> 3 && path
[1] == ':' &&
355 (path
[2] == '\\' || path
[2] == '/'));
358 string p
= Unescape (path
);
359 if (System
.IO
.Path
.DirectorySeparatorChar
== '\\' || windows
)
360 cachedLocalPath
= p
.Replace ('/', '\\');
364 // support *nix and W32 styles
365 if (path
.Length
> 1 && path
[1] == ':')
366 cachedLocalPath
= Unescape (path
.Replace (Path
.AltDirectorySeparatorChar
, Path
.DirectorySeparatorChar
));
368 // LAMESPEC: ok, now we cannot determine
369 // if such URI like "file://foo/bar" is
370 // Windows UNC or unix file path, so
371 // they should be handled differently.
372 else if (System
.IO
.Path
.DirectorySeparatorChar
== '\\')
373 cachedLocalPath
= "\\\\" + Unescape (host
+ path
.Replace ('/', '\\'));
375 cachedLocalPath
= Unescape (path
);
377 if (cachedLocalPath
== String
.Empty
)
378 cachedLocalPath
= Path
.DirectorySeparatorChar
.ToString ();
379 return cachedLocalPath
;
383 public string PathAndQuery
{
384 get { return path + query; }
391 public string Query
{
392 get { return query; }
395 public string Scheme
{
396 get { return scheme; }
399 public string [] Segments
{
401 if (segments
!= null)
405 segments
= new string [0];
409 string [] parts
= path
.Split ('/');
411 bool endSlash
= path
.EndsWith ("/");
412 if (parts
.Length
> 0 && endSlash
) {
413 string [] newParts
= new string [parts
.Length
- 1];
414 Array
.Copy (parts
, 0, newParts
, 0, parts
.Length
- 1);
419 if (IsFile
&& path
.Length
> 1 && path
[1] == ':') {
420 string [] newParts
= new string [parts
.Length
+ 1];
421 Array
.Copy (parts
, 1, newParts
, 2, parts
.Length
- 1);
423 parts
[0] = path
.Substring (0, 2);
428 int end
= parts
.Length
;
430 if (i
!= end
- 1 || endSlash
)
438 public bool UserEscaped
{
439 get { return userEscaped; }
442 public string UserInfo
{
443 get { return userinfo; }
449 public static UriHostNameType
CheckHostName (string name
)
451 if (name
== null || name
.Length
== 0)
452 return UriHostNameType
.Unknown
;
454 if (IsIPv4Address (name
))
455 return UriHostNameType
.IPv4
;
457 if (IsDomainAddress (name
))
458 return UriHostNameType
.Dns
;
461 IPv6Address
.Parse (name
);
462 return UriHostNameType
.IPv6
;
463 } catch (FormatException
) {}
465 return UriHostNameType
.Unknown
;
468 internal static bool IsIPv4Address (string name
)
470 string [] captures
= name
.Split (new char [] {'.'}
);
471 if (captures
.Length
!= 4)
473 for (int i
= 0; i
< 4; i
++) {
475 int d
= Int32
.Parse (captures
[i
], CultureInfo
.InvariantCulture
);
476 if (d
< 0 || d
> 255)
478 } catch (Exception
) {
485 internal static bool IsDomainAddress (string name
)
487 int len
= name
.Length
;
489 if (name
[len
- 1] == '.')
493 for (int i
= 0; i
< len
; i
++) {
496 if (!Char
.IsLetterOrDigit (c
))
498 } else if (c
== '.') {
500 } else if (!Char
.IsLetterOrDigit (c
) && c
!= '-' && c
!= '_') {
510 [MonoTODO ("Find out what this should do")]
511 protected virtual void Canonicalize ()
515 public static bool CheckSchemeName (string schemeName
)
517 if (schemeName
== null || schemeName
.Length
== 0)
520 if (!Char
.IsLetter (schemeName
[0]))
523 int len
= schemeName
.Length
;
524 for (int i
= 1; i
< len
; i
++) {
525 char c
= schemeName
[i
];
526 if (!Char
.IsLetterOrDigit (c
) && c
!= '.' && c
!= '+' && c
!= '-')
533 [MonoTODO ("Find out what this should do")]
534 protected virtual void CheckSecurity ()
538 public override bool Equals (object comparant
)
540 if (comparant
== null)
543 Uri uri
= comparant
as Uri
;
545 string s
= comparant
as String
;
551 CultureInfo inv
= CultureInfo
.InvariantCulture
;
552 return ((this.scheme
.ToLower (inv
) == uri
.scheme
.ToLower (inv
)) &&
553 (this.userinfo
.ToLower (inv
) == uri
.userinfo
.ToLower (inv
)) &&
554 (this.host
.ToLower (inv
) == uri
.host
.ToLower (inv
)) &&
555 (this.port
== uri
.port
) &&
556 (this.path
== uri
.path
) &&
557 (this.query
.ToLower (inv
) == uri
.query
.ToLower (inv
)));
560 public override int GetHashCode ()
562 if (cachedHashCode
== 0)
563 cachedHashCode
= scheme
.GetHashCode ()
564 + userinfo
.GetHashCode ()
565 + host
.GetHashCode ()
567 + path
.GetHashCode ()
568 + query
.GetHashCode ();
569 return cachedHashCode
;
572 public string GetLeftPart (UriPartial part
)
576 case UriPartial
.Scheme
:
577 return scheme
+ GetOpaqueWiseSchemeDelimiter ();
578 case UriPartial
.Authority
:
579 if (host
== String
.Empty
||
580 scheme
== Uri
.UriSchemeMailto
||
581 scheme
== Uri
.UriSchemeNews
)
584 StringBuilder s
= new StringBuilder ();
586 s
.Append (GetOpaqueWiseSchemeDelimiter ());
587 if (path
.Length
> 1 && path
[1] == ':' && (Uri
.UriSchemeFile
== scheme
))
588 s
.Append ('/'); // win32 file
589 if (userinfo
.Length
> 0)
590 s
.Append (userinfo
).Append ('@');
592 defaultPort
= GetDefaultPort (scheme
);
593 if ((port
!= -1) && (port
!= defaultPort
))
594 s
.Append (':').Append (port
);
595 return s
.ToString ();
596 case UriPartial
.Path
:
597 StringBuilder sb
= new StringBuilder ();
599 sb
.Append (GetOpaqueWiseSchemeDelimiter ());
600 if (path
.Length
> 1 && path
[1] == ':' && (Uri
.UriSchemeFile
== scheme
))
601 sb
.Append ('/'); // win32 file
602 if (userinfo
.Length
> 0)
603 sb
.Append (userinfo
).Append ('@');
605 defaultPort
= GetDefaultPort (scheme
);
606 if ((port
!= -1) && (port
!= defaultPort
))
607 sb
.Append (':').Append (port
);
609 return sb
.ToString ();
614 public static int FromHex (char digit
)
616 if ('0' <= digit
&& digit
<= '9') {
617 return (int) (digit
- '0');
620 if ('a' <= digit
&& digit
<= 'f')
621 return (int) (digit
- 'a' + 10);
623 if ('A' <= digit
&& digit
<= 'F')
624 return (int) (digit
- 'A' + 10);
626 throw new ArgumentException ("digit");
629 public static string HexEscape (char character
)
631 if (character
> 255) {
632 throw new ArgumentOutOfRangeException ("character");
635 return "%" + hexUpperChars
[((character
& 0xf0) >> 4)]
636 + hexUpperChars
[((character
& 0x0f))];
639 public static char HexUnescape (string pattern
, ref int index
)
642 throw new ArgumentException ("pattern");
644 if (index
< 0 || index
>= pattern
.Length
)
645 throw new ArgumentOutOfRangeException ("index");
649 byte [] bytes
= new byte [6];
651 if (((index
+ 3) > pattern
.Length
) ||
652 (pattern
[index
] != '%') ||
653 !IsHexDigit (pattern
[index
+ 1]) ||
654 !IsHexDigit (pattern
[index
+ 2]))
657 return pattern
[index
++];
662 int msb
= FromHex (pattern
[index
++]);
663 int lsb
= FromHex (pattern
[index
++]);
664 int b
= (msb
<< 4) + lsb
;
672 } else if (b
< 0xF0) {
675 } else if (b
< 0xF8) {
678 } else if (b
< 0xFB) {
681 } else if (b
< 0xFE) {
685 c
<<= (stage
- 1) * 6;
688 c
+= (b
- 0x80) << ((stage
- 1) * 6);
689 //Console.WriteLine ("stage {0}: {5:X04} <-- {1:X02}|{2:X01},{3:X01} {4}", new object [] {stage, b, msb, lsb, pattern.Substring (index), c});
696 public static bool IsHexDigit (char digit
)
698 return (('0' <= digit
&& digit
<= '9') ||
699 ('a' <= digit
&& digit
<= 'f') ||
700 ('A' <= digit
&& digit
<= 'F'));
703 public static bool IsHexEncoding (string pattern
, int index
)
705 if ((index
+ 3) > pattern
.Length
)
708 return ((pattern
[index
++] == '%') &&
709 IsHexDigit (pattern
[index
++]) &&
710 IsHexDigit (pattern
[index
]));
713 public string MakeRelative (Uri toUri
)
715 if ((this.Scheme
!= toUri
.Scheme
) ||
716 (this.Authority
!= toUri
.Authority
))
717 return toUri
.ToString ();
719 if (this.path
== toUri
.path
)
722 string [] segments
= this.Segments
;
723 string [] segments2
= toUri
.Segments
;
726 int max
= Math
.Min (segments
.Length
, segments2
.Length
);
728 if (segments
[k
] != segments2
[k
])
731 string result
= String
.Empty
;
732 for (int i
= k
+ 1; i
< segments
.Length
; i
++)
734 for (int i
= k
; i
< segments2
.Length
; i
++)
735 result
+= segments2
[i
];
740 public override string ToString ()
742 if (cachedToString
!= null)
743 return cachedToString
;
744 string q
= query
.StartsWith ("?") ? '?' + Unescape (query
.Substring (1)) : Unescape (query
);
745 cachedToString
= Unescape (GetLeftPart (UriPartial
.Path
), true) + q
+ fragment
;
746 return cachedToString
;
749 void ISerializable
.GetObjectData (SerializationInfo info
,
750 StreamingContext context
)
752 info
.AddValue ("AbsoluteUri", this.AbsoluteUri
);
758 protected virtual void Escape ()
760 path
= EscapeString (path
);
763 protected static string EscapeString (string str
)
765 return EscapeString (str
, false, true, true);
768 internal static string EscapeString (string str
, bool escapeReserved
, bool escapeHex
, bool escapeBrackets
)
773 byte [] data
= Encoding
.UTF8
.GetBytes (str
.ToCharArray ());
774 StringBuilder s
= new StringBuilder ();
775 int len
= data
.Length
;
776 for (int i
= 0; i
< len
; i
++) {
777 char c
= (char) data
[i
];
778 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
779 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
780 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
781 // space = <US-ASCII coded character 20 hexadecimal>
782 // delims = "<" | ">" | "#" | "%" | <">
783 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
785 // check for escape code already placed in str,
786 // i.e. for encoding that follows the pattern
787 // "%hexhex" in a string, where "hex" is a digit from 0-9
788 // or a letter from A-F (case-insensitive).
789 if('%'.Equals(c
) && IsHexEncoding(str
,i
))
791 // if ,yes , copy it as is
798 if ((c
<= 0x20) || (c
>= 0x7f) ||
799 ("<>%\"{}|\\^`".IndexOf (c
) != -1) ||
800 (escapeHex
&& (c
== '#')) ||
801 (escapeBrackets
&& (c
== '[' || c
== ']')) ||
802 (escapeReserved
&& (";/?:@&=+$,".IndexOf (c
) != -1))) {
803 s
.Append (HexEscape (c
));
811 return s
.ToString ();
814 // This method is called from .ctor(). When overriden, we can
815 // avoid the "absolute uri" constraints of the .ctor() by
816 // overriding with custom code.
817 protected virtual void Parse ()
824 host
= EscapeString (host
, false, true, false);
825 path
= EscapeString (path
);
828 protected virtual string Unescape (string str
)
830 return Unescape (str
, false);
833 private string Unescape (string str
, bool excludeSharp
)
837 StringBuilder s
= new StringBuilder ();
838 int len
= str
.Length
;
839 for (int i
= 0; i
< len
; i
++) {
842 char x
= HexUnescape (str
, ref i
);
843 if (excludeSharp
&& x
== '#')
851 return s
.ToString ();
857 private void ParseAsWindowsUNC (string uriString
)
859 scheme
= UriSchemeFile
;
861 fragment
= String
.Empty
;
862 query
= String
.Empty
;
865 uriString
= uriString
.TrimStart (new char [] {'\\'}
);
866 int pos
= uriString
.IndexOf ('\\');
868 path
= uriString
.Substring (pos
);
869 host
= uriString
.Substring (0, pos
);
870 } else { // "\\\\server"
874 path
= path
.Replace ("\\", "/");
877 private void ParseAsWindowsAbsoluteFilePath (string uriString
)
879 if (uriString
.Length
> 2 && uriString
[2] != '\\'
880 && uriString
[2] != '/')
881 throw new UriFormatException ("Relative file path is not allowed.");
882 scheme
= UriSchemeFile
;
885 path
= uriString
.Replace ("\\", "/");
886 fragment
= String
.Empty
;
887 query
= String
.Empty
;
890 private void ParseAsUnixAbsoluteFilePath (string uriString
)
892 isUnixFilePath
= true;
893 scheme
= UriSchemeFile
;
895 fragment
= String
.Empty
;
896 query
= String
.Empty
;
900 if (uriString
.StartsWith ("//")) {
901 uriString
= uriString
.TrimStart (new char [] {'/'}
);
902 // Now we don't regard //foo/bar as "foo" host.
904 int pos = uriString.IndexOf ('/');
906 path = '/' + uriString.Substring (pos + 1);
907 host = uriString.Substring (0, pos);
908 } else { // "///server"
913 path
= '/' + uriString
;
919 // this parse method is as relaxed as possible about the format
920 // it will hardly ever throw a UriFormatException
921 private void Parse (string uriString
)
926 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
930 if (uriString
== null)
931 throw new ArgumentNullException ("uriString");
933 int len
= uriString
.Length
;
935 throw new UriFormatException ();
940 // Identify Windows path, unix path, or standard URI.
941 pos
= uriString
.IndexOf (':');
943 // It must be Unix file path or Windows UNC
944 if (uriString
[0] == '/')
945 ParseAsUnixAbsoluteFilePath (uriString
);
946 else if (uriString
.StartsWith ("\\\\"))
947 ParseAsWindowsUNC (uriString
);
949 throw new UriFormatException ("URI scheme was not recognized, nor input string is not recognized as an absolute file path.");
953 if (!Char
.IsLetter (uriString
[0]))
954 throw new UriFormatException ("URI scheme must start with alphabet character.");
955 // This means 'a:' == windows full path.
956 ParseAsWindowsAbsoluteFilePath (uriString
);
961 scheme
= uriString
.Substring (0, pos
).ToLower (CultureInfo
.InvariantCulture
);
962 // Check scheme name characters as specified in RFC2396.
963 if (!Char
.IsLetter (scheme
[0]))
964 throw new UriFormatException ("URI scheme must start with alphabet character.");
965 for (int i
= 1; i
< scheme
.Length
; i
++) {
966 if (!Char
.IsLetterOrDigit (scheme
, i
)) {
967 switch (scheme
[i
]) {
973 throw new UriFormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
977 uriString
= uriString
.Substring (pos
+ 1);
980 pos
= uriString
.IndexOf ('#');
981 if (!IsUnc
&& pos
!= -1) {
982 fragment
= uriString
.Substring (pos
);
983 uriString
= uriString
.Substring (0, pos
);
987 pos
= uriString
.IndexOf ('?');
989 query
= uriString
.Substring (pos
);
990 uriString
= uriString
.Substring (0, pos
);
992 query
= EscapeString (query
);
996 bool unixAbsPath
= scheme
== UriSchemeFile
&& uriString
.StartsWith ("///");
997 if (uriString
.StartsWith ("//")) {
998 if (uriString
.StartsWith ("////"))
1000 uriString
= uriString
.TrimStart (new char [] {'/'}
);
1001 if (uriString
.Length
> 1 && uriString
[1] == ':')
1002 unixAbsPath
= false;
1003 } else if (!IsPredefinedScheme (scheme
)) {
1005 isOpaquePart
= true;
1010 pos
= uriString
.IndexOfAny (new char[] {'/'}
);
1014 if ((scheme
!= Uri
.UriSchemeMailto
) &&
1015 (scheme
!= Uri
.UriSchemeNews
) &&
1016 (scheme
!= Uri
.UriSchemeFile
))
1019 path
= uriString
.Substring (pos
);
1020 uriString
= uriString
.Substring (0, pos
);
1024 pos
= uriString
.IndexOf ("@");
1026 userinfo
= uriString
.Substring (0, pos
);
1027 uriString
= uriString
.Remove (0, pos
+ 1);
1032 pos
= uriString
.LastIndexOf (":");
1035 if (pos
!= -1 && pos
!= (uriString
.Length
- 1)) {
1036 string portStr
= uriString
.Remove (0, pos
+ 1);
1037 if (portStr
.Length
> 1 && portStr
[portStr
.Length
- 1] != ']') {
1039 port
= (int) UInt32
.Parse (portStr
, CultureInfo
.InvariantCulture
);
1040 uriString
= uriString
.Substring (0, pos
);
1041 } catch (Exception
) {
1042 throw new UriFormatException ("Invalid URI: invalid port number");
1047 port
= GetDefaultPort (scheme
);
1052 if (host
.Length
> 1 && host
[0] == '[' && host
[host
.Length
- 1] == ']') {
1054 host
= "[" + IPv6Address
.Parse (host
).ToString () + "]";
1055 } catch (Exception
) {
1056 throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
1061 path
= '/' + uriString
;
1062 host
= String
.Empty
;
1063 } else if (host
.Length
== 2 && host
[1] == ':') {
1066 host
= String
.Empty
;
1067 } else if (isUnixFilePath
) {
1068 uriString
= "//" + uriString
;
1069 host
= String
.Empty
;
1070 } else if (host
.Length
== 0) {
1071 throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
1072 } else if (scheme
== UriSchemeFile
) {
1076 if ((scheme
!= Uri
.UriSchemeMailto
) &&
1077 (scheme
!= Uri
.UriSchemeNews
) &&
1078 (scheme
!= Uri
.UriSchemeFile
))
1079 path
= Reduce (path
);
1082 private static string Reduce (string path
)
1084 path
= path
.Replace ('\\','/');
1085 string [] parts
= path
.Split ('/');
1086 ArrayList result
= new ArrayList ();
1088 int end
= parts
.Length
;
1089 for (int i
= 0; i
< end
; i
++) {
1090 string current
= parts
[i
];
1091 if (current
== "" || current
== "." )
1094 if (current
== "..") {
1095 if (result
.Count
== 0) {
1096 if (i
== 1) // see bug 52599
1098 throw new Exception ("Invalid path.");
1101 result
.RemoveAt (result
.Count
- 1);
1105 result
.Add (current
);
1108 if (result
.Count
== 0)
1111 result
.Insert (0, "");
1113 string res
= String
.Join ("/", (string []) result
.ToArray (typeof (string)));
1114 if (path
.EndsWith ("/"))
1120 private struct UriScheme
1122 public string scheme
;
1123 public string delimiter
;
1124 public int defaultPort
;
1126 public UriScheme (string s
, string d
, int p
)
1134 static UriScheme
[] schemes
= new UriScheme
[] {
1135 new UriScheme (UriSchemeHttp
, SchemeDelimiter
, 80),
1136 new UriScheme (UriSchemeHttps
, SchemeDelimiter
, 443),
1137 new UriScheme (UriSchemeFtp
, SchemeDelimiter
, 21),
1138 new UriScheme (UriSchemeFile
, SchemeDelimiter
, -1),
1139 new UriScheme (UriSchemeMailto
, ":", 25),
1140 new UriScheme (UriSchemeNews
, ":", -1),
1141 new UriScheme (UriSchemeNntp
, SchemeDelimiter
, 119),
1142 new UriScheme (UriSchemeGopher
, SchemeDelimiter
, 70),
1145 internal static string GetSchemeDelimiter (string scheme
)
1147 for (int i
= 0; i
< schemes
.Length
; i
++)
1148 if (schemes
[i
].scheme
== scheme
)
1149 return schemes
[i
].delimiter
;
1150 return Uri
.SchemeDelimiter
;
1153 internal static int GetDefaultPort (string scheme
)
1155 for (int i
= 0; i
< schemes
.Length
; i
++)
1156 if (schemes
[i
].scheme
== scheme
)
1157 return schemes
[i
].defaultPort
;
1161 private string GetOpaqueWiseSchemeDelimiter ()
1166 return GetSchemeDelimiter (scheme
);
1169 protected virtual bool IsBadFileSystemCharacter (char ch
)
1171 // It does not always overlap with InvalidPathChars.
1172 int chInt
= (int) ch
;
1173 if (chInt
< 32 || (chInt
< 64 && chInt
> 57))
1192 protected static bool IsExcludedCharacter (char ch
)
1194 if (ch
<= 32 || ch
>= 127)
1197 if (ch
== '"' || ch
== '#' || ch
== '%' || ch
== '<' ||
1198 ch
== '>' || ch
== '[' || ch
== '\\' || ch
== ']' ||
1199 ch
== '^' || ch
== '`' || ch
== '{' || ch
== '|' ||
1205 private static bool IsPredefinedScheme (string scheme
)
1222 protected virtual bool IsReservedCharacter (char ch
)
1224 if (ch
== '$' || ch
== '&' || ch
== '+' || ch
== ',' ||
1225 ch
== '/' || ch
== ':' || ch
== ';' || ch
== '=' ||