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)
11 // Stephane Delcroix <stephane@delcroix.org>
13 // (C) 2001 Garrett Rooney
14 // (C) 2003 Ian MacLean
15 // (C) 2003 Ben Maurer
16 // (C) 2003 Novell inc.
17 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
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:
27 // The above copyright notice and this permission notice shall be
28 // included in all copies or substantial portions of the Software.
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.
42 using System
.Collections
;
43 using System
.Globalization
;
45 // See RFC 2396 for more info on URI's.
47 // TODO: optimize by parsing host string only once
49 namespace Mono
.Security
{
70 // o scheme excludes the scheme delimiter
71 // o port is -1 to indicate no port is defined
72 // o path is empty or starts with / when scheme delimiter == "://"
73 // o query is empty or starts with ? char, escaped.
74 // o fragment is empty or starts with # char, unescaped.
75 // o all class variables are in escaped format when they are escapable,
76 // except cachedToString.
77 // o UNC is supported, as starts with "\\" for windows,
80 private bool isUnixFilePath
= false;
81 private string source
;
82 private string scheme
= String
.Empty
;
83 private string host
= String
.Empty
;
84 private int port
= -1;
85 private string path
= String
.Empty
;
86 private string query
= String
.Empty
;
87 private string fragment
= String
.Empty
;
88 private string userinfo
= String
.Empty
;
89 private bool isUnc
= false;
90 private bool isOpaquePart
= false;
92 private string [] segments
;
94 private bool userEscaped
= false;
95 private string cachedAbsoluteUri
= null;
96 private string cachedToString
= null;
97 private string cachedLocalPath
= null;
98 private int cachedHashCode
= 0;
99 private bool reduce
= true;
101 private static readonly string hexUpperChars
= "0123456789ABCDEF";
105 public static readonly string SchemeDelimiter
= "://";
106 public static readonly string UriSchemeFile
= "file";
107 public static readonly string UriSchemeFtp
= "ftp";
108 public static readonly string UriSchemeGopher
= "gopher";
109 public static readonly string UriSchemeHttp
= "http";
110 public static readonly string UriSchemeHttps
= "https";
111 public static readonly string UriSchemeMailto
= "mailto";
112 public static readonly string UriSchemeNews
= "news";
113 public static readonly string UriSchemeNntp
= "nntp";
117 public Uri (string uriString
) : this (uriString
, false)
121 public Uri (string uriString
, bool dontEscape
)
123 userEscaped
= dontEscape
;
128 public Uri (string uriString
, bool dontEscape
, bool reduce
)
130 userEscaped
= dontEscape
;
132 this.reduce
= reduce
;
136 public Uri (Uri baseUri
, string relativeUri
)
137 : this (baseUri
, relativeUri
, false)
141 public Uri (Uri baseUri
, string relativeUri
, bool dontEscape
)
144 throw new NullReferenceException ("baseUri");
146 // See RFC 2396 Par 5.2 and Appendix C
148 userEscaped
= dontEscape
;
150 if (relativeUri
== null)
151 throw new NullReferenceException ("relativeUri");
153 // Check Windows UNC (for // it is scheme/host separator)
154 if (relativeUri
.StartsWith ("\\\\")) {
155 source
= relativeUri
;
160 int pos
= relativeUri
.IndexOf (':');
163 int pos2
= relativeUri
.IndexOfAny (new char [] {'/', '\\', '?'}
);
165 // pos2 < 0 ... e.g. mailto
166 // pos2 > pos ... to block ':' in query part
167 if (pos2
> pos
|| pos2
< 0) {
168 // equivalent to new Uri (relativeUri, dontEscape)
169 source
= relativeUri
;
176 this.scheme
= baseUri
.scheme
;
177 this.host
= baseUri
.host
;
178 this.port
= baseUri
.port
;
179 this.userinfo
= baseUri
.userinfo
;
180 this.isUnc
= baseUri
.isUnc
;
181 this.isUnixFilePath
= baseUri
.isUnixFilePath
;
182 this.isOpaquePart
= baseUri
.isOpaquePart
;
184 if (relativeUri
== String
.Empty
) {
185 this.path
= baseUri
.path
;
186 this.query
= baseUri
.query
;
187 this.fragment
= baseUri
.fragment
;
192 // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
193 pos
= relativeUri
.IndexOf ('#');
195 fragment
= relativeUri
.Substring (pos
);
196 // fragment is not escaped.
197 relativeUri
= relativeUri
.Substring (0, pos
);
201 pos
= relativeUri
.IndexOf ('?');
203 query
= relativeUri
.Substring (pos
);
205 query
= EscapeString (query
);
206 relativeUri
= relativeUri
.Substring (0, pos
);
209 if (relativeUri
.Length
> 0 && relativeUri
[0] == '/') {
210 if (relativeUri
.Length
> 1 && relativeUri
[1] == '/') {
211 source
= scheme
+ ':' + relativeUri
;
217 path
= EscapeString (path
);
224 if (relativeUri
.Length
> 0 || query
.Length
> 0) {
225 pos
= path
.LastIndexOf ('/');
227 path
= path
.Substring (0, pos
+ 1);
230 if(relativeUri
.Length
== 0)
239 pos
= path
.IndexOf ("./", startIndex
);
243 path
= path
.Remove (0, 2);
244 else if (path
[pos
- 1] != '.')
245 path
= path
.Remove (pos
, 2);
247 startIndex
= pos
+ 1;
251 if (path
.Length
> 1 &&
252 path
[path
.Length
- 1] == '.' &&
253 path
[path
.Length
- 2] == '/')
254 path
= path
.Remove (path
.Length
- 1, 1);
259 pos
= path
.IndexOf ("/../", startIndex
);
266 int pos2
= path
.LastIndexOf ('/', pos
- 1);
268 startIndex
= pos
+ 1;
270 if (path
.Substring (pos2
+ 1, pos
- pos2
- 1) != "..")
271 path
= path
.Remove (pos2
+ 1, pos
- pos2
+ 3);
273 startIndex
= pos
+ 1;
278 if (path
.Length
> 3 && path
.EndsWith ("/..")) {
279 pos
= path
.LastIndexOf ('/', path
.Length
- 4);
281 if (path
.Substring (pos
+ 1, path
.Length
- pos
- 4) != "..")
282 path
= path
.Remove (pos
+ 1, path
.Length
- pos
- 1);
286 path
= EscapeString (path
);
291 public string AbsolutePath
{
295 public string AbsoluteUri
{
297 if (cachedAbsoluteUri
== null) {
298 cachedAbsoluteUri
= GetLeftPart (UriPartial
.Path
) + query
+ fragment
;
300 return cachedAbsoluteUri
;
304 public string Authority
{
306 return (GetDefaultPort (scheme
) == port
)
307 ? host
: host
+ ":" + port
;
311 public string Fragment
{
312 get { return fragment; }
319 /* public UriHostNameType HostNameType {
321 UriHostNameType ret = CheckHostName (host);
322 if (ret != UriHostNameType.Unknown)
325 // looks it always returns Basic...
326 return UriHostNameType.Basic; //.Unknown;
330 public bool IsDefaultPort
{
331 get { return GetDefaultPort (scheme) == port; }
335 get { return (scheme == UriSchemeFile); }
338 public bool IsLoopback
{
340 if (host
== String
.Empty
)
343 if (host
== "loopback" || host
== "localhost")
347 if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
349 } catch (FormatException) {}
352 return IPv6Address.IsLoopback (IPv6Address.Parse (host));
353 } catch (FormatException) {}
360 // rule: This should be true only if
361 // - uri string starts from "\\", or
362 // - uri string starts from "//" (Samba way)
363 get { return isUnc; }
366 public string LocalPath
{
368 if (cachedLocalPath
!= null)
369 return cachedLocalPath
;
373 bool windows
= (path
.Length
> 3 && path
[1] == ':' &&
374 (path
[2] == '\\' || path
[2] == '/'));
377 string p
= Unescape (path
);
378 if (System
.IO
.Path
.DirectorySeparatorChar
== '\\' || windows
)
379 cachedLocalPath
= p
.Replace ('/', '\\');
383 // support *nix and W32 styles
384 if (path
.Length
> 1 && path
[1] == ':')
385 cachedLocalPath
= Unescape (path
.Replace (Path
.AltDirectorySeparatorChar
, Path
.DirectorySeparatorChar
));
387 // LAMESPEC: ok, now we cannot determine
388 // if such URI like "file://foo/bar" is
389 // Windows UNC or unix file path, so
390 // they should be handled differently.
391 else if (System
.IO
.Path
.DirectorySeparatorChar
== '\\')
392 cachedLocalPath
= "\\\\" + Unescape (host
+ path
.Replace ('/', '\\'));
394 cachedLocalPath
= Unescape (path
);
396 if (cachedLocalPath
== String
.Empty
)
397 cachedLocalPath
= Path
.DirectorySeparatorChar
.ToString ();
398 return cachedLocalPath
;
402 public string PathAndQuery
{
403 get { return path + query; }
410 public string Query
{
411 get { return query; }
414 public string Scheme
{
415 get { return scheme; }
418 public string [] Segments
{
420 if (segments
!= null)
423 if (path
.Length
== 0) {
424 segments
= new string [0];
428 string [] parts
= path
.Split ('/');
430 bool endSlash
= path
.EndsWith ("/");
431 if (parts
.Length
> 0 && endSlash
) {
432 string [] newParts
= new string [parts
.Length
- 1];
433 Array
.Copy (parts
, 0, newParts
, 0, parts
.Length
- 1);
438 if (IsFile
&& path
.Length
> 1 && path
[1] == ':') {
439 string [] newParts
= new string [parts
.Length
+ 1];
440 Array
.Copy (parts
, 1, newParts
, 2, parts
.Length
- 1);
442 parts
[0] = path
.Substring (0, 2);
443 parts
[1] = String
.Empty
;
447 int end
= parts
.Length
;
449 if (i
!= end
- 1 || endSlash
)
457 public bool UserEscaped
{
458 get { return userEscaped; }
461 public string UserInfo
{
462 get { return userinfo; }
468 /* public static UriHostNameType CheckHostName (string name)
470 if (name == null || name.Length == 0)
471 return UriHostNameType.Unknown;
473 if (IsIPv4Address (name))
474 return UriHostNameType.IPv4;
476 if (IsDomainAddress (name))
477 return UriHostNameType.Dns;
480 IPv6Address.Parse (name);
481 return UriHostNameType.IPv6;
482 } catch (FormatException) {}
484 return UriHostNameType.Unknown;
487 internal static bool IsIPv4Address (string name
)
489 string [] captures
= name
.Split (new char [] {'.'}
);
490 if (captures
.Length
!= 4)
492 for (int i
= 0; i
< 4; i
++) {
494 int d
= Int32
.Parse (captures
[i
], CultureInfo
.InvariantCulture
);
495 if (d
< 0 || d
> 255)
497 } catch (Exception
) {
504 internal static bool IsDomainAddress (string name
)
506 int len
= name
.Length
;
508 if (name
[len
- 1] == '.')
512 for (int i
= 0; i
< len
; i
++) {
515 if (!Char
.IsLetterOrDigit (c
))
517 } else if (c
== '.') {
519 } else if (!Char
.IsLetterOrDigit (c
) && c
!= '-' && c
!= '_') {
529 public static bool CheckSchemeName (string schemeName
)
531 if (schemeName
== null || schemeName
.Length
== 0)
534 if (!Char
.IsLetter (schemeName
[0]))
537 int len
= schemeName
.Length
;
538 for (int i
= 1; i
< len
; i
++) {
539 char c
= schemeName
[i
];
540 if (!Char
.IsLetterOrDigit (c
) && c
!= '.' && c
!= '+' && c
!= '-')
547 public override bool Equals (object comparant
)
549 if (comparant
== null)
552 Uri uri
= comparant
as Uri
;
554 string s
= comparant
as String
;
560 CultureInfo inv
= CultureInfo
.InvariantCulture
;
561 return ((this.scheme
.ToLower (inv
) == uri
.scheme
.ToLower (inv
)) &&
562 (this.userinfo
.ToLower (inv
) == uri
.userinfo
.ToLower (inv
)) &&
563 (this.host
.ToLower (inv
) == uri
.host
.ToLower (inv
)) &&
564 (this.port
== uri
.port
) &&
565 (this.path
== uri
.path
) &&
566 (this.query
.ToLower (inv
) == uri
.query
.ToLower (inv
)));
569 public override int GetHashCode ()
571 if (cachedHashCode
== 0)
572 cachedHashCode
= scheme
.GetHashCode ()
573 + userinfo
.GetHashCode ()
574 + host
.GetHashCode ()
576 + path
.GetHashCode ()
577 + query
.GetHashCode ();
578 return cachedHashCode
;
581 public string GetLeftPart (UriPartial part
)
585 case UriPartial
.Scheme
:
586 return scheme
+ GetOpaqueWiseSchemeDelimiter ();
587 case UriPartial
.Authority
:
588 if (host
== String
.Empty
||
589 scheme
== Uri
.UriSchemeMailto
||
590 scheme
== Uri
.UriSchemeNews
)
593 StringBuilder s
= new StringBuilder ();
595 s
.Append (GetOpaqueWiseSchemeDelimiter ());
596 if (path
.Length
> 1 && path
[1] == ':' && (Uri
.UriSchemeFile
== scheme
))
597 s
.Append ('/'); // win32 file
598 if (userinfo
.Length
> 0)
599 s
.Append (userinfo
).Append ('@');
601 defaultPort
= GetDefaultPort (scheme
);
602 if ((port
!= -1) && (port
!= defaultPort
))
603 s
.Append (':').Append (port
);
604 return s
.ToString ();
605 case UriPartial
.Path
:
606 StringBuilder sb
= new StringBuilder ();
608 sb
.Append (GetOpaqueWiseSchemeDelimiter ());
609 if (path
.Length
> 1 && path
[1] == ':' && (Uri
.UriSchemeFile
== scheme
))
610 sb
.Append ('/'); // win32 file
611 if (userinfo
.Length
> 0)
612 sb
.Append (userinfo
).Append ('@');
614 defaultPort
= GetDefaultPort (scheme
);
615 if ((port
!= -1) && (port
!= defaultPort
))
616 sb
.Append (':').Append (port
);
618 return sb
.ToString ();
623 public static int FromHex (char digit
)
625 if ('0' <= digit
&& digit
<= '9') {
626 return (int) (digit
- '0');
629 if ('a' <= digit
&& digit
<= 'f')
630 return (int) (digit
- 'a' + 10);
632 if ('A' <= digit
&& digit
<= 'F')
633 return (int) (digit
- 'A' + 10);
635 throw new ArgumentException ("digit");
638 public static string HexEscape (char character
)
640 if (character
> 255) {
641 throw new ArgumentOutOfRangeException ("character");
644 return "%" + hexUpperChars
[((character
& 0xf0) >> 4)]
645 + hexUpperChars
[((character
& 0x0f))];
648 public static char HexUnescape (string pattern
, ref int index
)
651 throw new ArgumentException ("pattern");
653 if (index
< 0 || index
>= pattern
.Length
)
654 throw new ArgumentOutOfRangeException ("index");
659 if (((index
+ 3) > pattern
.Length
) ||
660 (pattern
[index
] != '%') ||
661 !IsHexDigit (pattern
[index
+ 1]) ||
662 !IsHexDigit (pattern
[index
+ 2]))
665 return pattern
[index
++];
670 int msb
= FromHex (pattern
[index
++]);
671 int lsb
= FromHex (pattern
[index
++]);
672 int b
= (msb
<< 4) + lsb
;
680 } else if (b
< 0xF0) {
683 } else if (b
< 0xF8) {
686 } else if (b
< 0xFB) {
689 } else if (b
< 0xFE) {
693 c
<<= (stage
- 1) * 6;
696 c
+= (b
- 0x80) << ((stage
- 1) * 6);
697 //Console.WriteLine ("stage {0}: {5:X04} <-- {1:X02}|{2:X01},{3:X01} {4}", new object [] {stage, b, msb, lsb, pattern.Substring (index), c});
704 public static bool IsHexDigit (char digit
)
706 return (('0' <= digit
&& digit
<= '9') ||
707 ('a' <= digit
&& digit
<= 'f') ||
708 ('A' <= digit
&& digit
<= 'F'));
711 public static bool IsHexEncoding (string pattern
, int index
)
713 if ((index
+ 3) > pattern
.Length
)
716 return ((pattern
[index
++] == '%') &&
717 IsHexDigit (pattern
[index
++]) &&
718 IsHexDigit (pattern
[index
]));
721 public string MakeRelative (Uri toUri
)
723 if ((this.Scheme
!= toUri
.Scheme
) ||
724 (this.Authority
!= toUri
.Authority
))
725 return toUri
.ToString ();
727 if (this.path
== toUri
.path
)
730 string [] segments
= this.Segments
;
731 string [] segments2
= toUri
.Segments
;
734 int max
= System
.Math
.Min (segments
.Length
, segments2
.Length
);
736 if (segments
[k
] != segments2
[k
])
739 string result
= String
.Empty
;
740 for (int i
= k
+ 1; i
< segments
.Length
; i
++)
742 for (int i
= k
; i
< segments2
.Length
; i
++)
743 result
+= segments2
[i
];
748 public override string ToString ()
750 if (cachedToString
!= null)
751 return cachedToString
;
752 string q
= query
.StartsWith ("?") ? '?' + Unescape (query
.Substring (1)) : Unescape (query
);
753 cachedToString
= Unescape (GetLeftPart (UriPartial
.Path
), true) + q
+ fragment
;
754 return cachedToString
;
757 /* void ISerializable.GetObjectData (SerializationInfo info,
758 StreamingContext context)
760 info.AddValue ("AbsoluteUri", this.AbsoluteUri);
766 protected void Escape ()
768 path
= EscapeString (path
);
771 protected static string EscapeString (string str
)
773 return EscapeString (str
, false, true, true);
776 internal static string EscapeString (string str
, bool escapeReserved
, bool escapeHex
, bool escapeBrackets
)
781 StringBuilder s
= new StringBuilder ();
782 int len
= str
.Length
;
783 for (int i
= 0; i
< len
; i
++) {
784 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
785 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
786 // control = <US-ASCII coded characters 00-1F and 7F hexadecimal>
787 // space = <US-ASCII coded character 20 hexadecimal>
788 // delims = "<" | ">" | "#" | "%" | <">
789 // unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
791 // check for escape code already placed in str,
792 // i.e. for encoding that follows the pattern
793 // "%hexhex" in a string, where "hex" is a digit from 0-9
794 // or a letter from A-F (case-insensitive).
795 if (IsHexEncoding (str
,i
)) {
796 // if ,yes , copy it as is
797 s
.Append(str
.Substring (i
, 3));
802 byte [] data
= Encoding
.UTF8
.GetBytes (new char[] {str[i]}
);
803 int length
= data
.Length
;
804 for (int j
= 0; j
< length
; j
++) {
805 char c
= (char) data
[j
];
806 if ((c
<= 0x20) || (c
>= 0x7f) ||
807 ("<>%\"{}|\\^`".IndexOf (c
) != -1) ||
808 (escapeHex
&& (c
== '#')) ||
809 (escapeBrackets
&& (c
== '[' || c
== ']')) ||
810 (escapeReserved
&& (";/?:@&=+$,".IndexOf (c
) != -1))) {
811 s
.Append (HexEscape (c
));
818 return s
.ToString ();
821 // This method is called from .ctor(). When overriden, we can
822 // avoid the "absolute uri" constraints of the .ctor() by
823 // overriding with custom code.
824 protected void Parse ()
831 host
= EscapeString (host
, false, true, false);
832 path
= EscapeString (path
);
835 protected string Unescape (string str
)
837 return Unescape (str
, false);
840 internal string Unescape (string str
, bool excludeSharp
)
844 StringBuilder s
= new StringBuilder ();
845 int len
= str
.Length
;
846 for (int i
= 0; i
< len
; i
++) {
849 char x
= HexUnescape (str
, ref i
);
850 if (excludeSharp
&& x
== '#')
858 return s
.ToString ();
864 private void ParseAsWindowsUNC (string uriString
)
866 scheme
= UriSchemeFile
;
868 fragment
= String
.Empty
;
869 query
= String
.Empty
;
872 uriString
= uriString
.TrimStart (new char [] {'\\'}
);
873 int pos
= uriString
.IndexOf ('\\');
875 path
= uriString
.Substring (pos
);
876 host
= uriString
.Substring (0, pos
);
877 } else { // "\\\\server"
881 path
= path
.Replace ("\\", "/");
884 private void ParseAsWindowsAbsoluteFilePath (string uriString
)
886 if (uriString
.Length
> 2 && uriString
[2] != '\\'
887 && uriString
[2] != '/')
888 throw new FormatException ("Relative file path is not allowed.");
889 scheme
= UriSchemeFile
;
892 path
= uriString
.Replace ("\\", "/");
893 fragment
= String
.Empty
;
894 query
= String
.Empty
;
897 private void ParseAsUnixAbsoluteFilePath (string uriString
)
899 isUnixFilePath
= true;
900 scheme
= UriSchemeFile
;
902 fragment
= String
.Empty
;
903 query
= String
.Empty
;
907 if (uriString
.StartsWith ("//")) {
908 uriString
= uriString
.TrimStart (new char [] {'/'}
);
909 // Now we don't regard //foo/bar as "foo" host.
911 int pos = uriString.IndexOf ('/');
913 path = '/' + uriString.Substring (pos + 1);
914 host = uriString.Substring (0, pos);
915 } else { // "///server"
920 path
= '/' + uriString
;
926 // this parse method is as relaxed as possible about the format
927 // it will hardly ever throw a UriFormatException
928 private void Parse (string uriString
)
933 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
937 if (uriString
== null)
938 throw new ArgumentNullException ("uriString");
940 int len
= uriString
.Length
;
942 throw new FormatException ();
947 // Identify Windows path, unix path, or standard URI.
948 pos
= uriString
.IndexOf (':');
950 // It must be Unix file path or Windows UNC
951 if (uriString
[0] == '/')
952 ParseAsUnixAbsoluteFilePath (uriString
);
953 else if (uriString
.StartsWith ("\\\\"))
954 ParseAsWindowsUNC (uriString
);
956 throw new FormatException ("URI scheme was not recognized, nor input string is not recognized as an absolute file path.");
960 if (!Char
.IsLetter (uriString
[0]))
961 throw new FormatException ("URI scheme must start with alphabet character.");
962 // This means 'a:' == windows full path.
963 ParseAsWindowsAbsoluteFilePath (uriString
);
968 scheme
= uriString
.Substring (0, pos
).ToLower (CultureInfo
.InvariantCulture
);
969 // Check scheme name characters as specified in RFC2396.
970 if (!Char
.IsLetter (scheme
[0]))
971 throw new FormatException ("URI scheme must start with alphabet character.");
972 for (int i
= 1; i
< scheme
.Length
; i
++) {
973 if (!Char
.IsLetterOrDigit (scheme
, i
)) {
974 switch (scheme
[i
]) {
980 throw new FormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
984 uriString
= uriString
.Substring (pos
+ 1);
987 pos
= uriString
.IndexOf ('#');
988 if (!IsUnc
&& pos
!= -1) {
989 fragment
= uriString
.Substring (pos
);
990 uriString
= uriString
.Substring (0, pos
);
994 pos
= uriString
.IndexOf ('?');
996 query
= uriString
.Substring (pos
);
997 uriString
= uriString
.Substring (0, pos
);
999 query
= EscapeString (query
);
1003 bool unixAbsPath
= scheme
== UriSchemeFile
&& uriString
.StartsWith ("///");
1004 if (uriString
.StartsWith ("//")) {
1005 if (uriString
.StartsWith ("////"))
1006 unixAbsPath
= false;
1007 uriString
= uriString
.TrimStart (new char [] {'/'}
);
1008 if (uriString
.Length
> 1 && uriString
[1] == ':')
1009 unixAbsPath
= false;
1010 } else if (!IsPredefinedScheme (scheme
)) {
1012 isOpaquePart
= true;
1017 pos
= uriString
.IndexOfAny (new char[] {'/'}
);
1021 if ((scheme
!= Uri
.UriSchemeMailto
) &&
1022 (scheme
!= Uri
.UriSchemeNews
) &&
1023 (scheme
!= Uri
.UriSchemeFile
))
1026 path
= uriString
.Substring (pos
);
1027 uriString
= uriString
.Substring (0, pos
);
1031 pos
= uriString
.IndexOf ("@");
1033 userinfo
= uriString
.Substring (0, pos
);
1034 uriString
= uriString
.Remove (0, pos
+ 1);
1039 pos
= uriString
.LastIndexOf (":");
1042 if (pos
!= -1 && pos
!= (uriString
.Length
- 1)) {
1043 string portStr
= uriString
.Remove (0, pos
+ 1);
1044 if (portStr
.Length
> 1 && portStr
[portStr
.Length
- 1] != ']') {
1046 port
= (int) UInt32
.Parse (portStr
, CultureInfo
.InvariantCulture
);
1047 uriString
= uriString
.Substring (0, pos
);
1048 } catch (Exception
) {
1049 throw new FormatException ("Invalid URI: invalid port number");
1054 port
= GetDefaultPort (scheme
);
1059 /* if (host.Length > 1 && host [0] == '[' && host [host.Length - 1] == ']') {
1061 host = "[" + IPv6Address.Parse (host).ToString () + "]";
1062 } catch (Exception) {
1063 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1068 path
= '/' + uriString
;
1069 host
= String
.Empty
;
1070 } else if (host
.Length
== 2 && host
[1] == ':') {
1073 host
= String
.Empty
;
1074 } else if (isUnixFilePath
) {
1075 uriString
= "//" + uriString
;
1076 host
= String
.Empty
;
1077 } else if (host
.Length
== 0) {
1078 throw new FormatException ("Invalid URI: The hostname could not be parsed");
1079 } else if (scheme
== UriSchemeFile
) {
1083 if ((scheme
!= Uri
.UriSchemeMailto
) &&
1084 (scheme
!= Uri
.UriSchemeNews
) &&
1085 (scheme
!= Uri
.UriSchemeFile
))
1088 path
= Reduce (path
);
1091 private static string Reduce (string path
)
1093 path
= path
.Replace ('\\','/');
1094 string [] parts
= path
.Split ('/');
1095 ArrayList result
= new ArrayList ();
1097 int end
= parts
.Length
;
1098 for (int i
= 0; i
< end
; i
++) {
1099 string current
= parts
[i
];
1100 if (current
.Length
== 0 || current
== "." )
1103 if (current
== "..") {
1104 if (result
.Count
== 0) {
1105 if (i
== 1) // see bug 52599
1107 throw new Exception ("Invalid path.");
1110 result
.RemoveAt (result
.Count
- 1);
1114 result
.Add (current
);
1117 if (result
.Count
== 0)
1120 result
.Insert (0, String
.Empty
);
1122 string res
= String
.Join ("/", (string []) result
.ToArray (typeof (string)));
1123 if (path
.EndsWith ("/"))
1129 private struct UriScheme
1131 public string scheme
;
1132 public string delimiter
;
1133 public int defaultPort
;
1135 public UriScheme (string s
, string d
, int p
)
1143 static UriScheme
[] schemes
= new UriScheme
[] {
1144 new UriScheme (UriSchemeHttp
, SchemeDelimiter
, 80),
1145 new UriScheme (UriSchemeHttps
, SchemeDelimiter
, 443),
1146 new UriScheme (UriSchemeFtp
, SchemeDelimiter
, 21),
1147 new UriScheme (UriSchemeFile
, SchemeDelimiter
, -1),
1148 new UriScheme (UriSchemeMailto
, ":", 25),
1149 new UriScheme (UriSchemeNews
, ":", -1),
1150 new UriScheme (UriSchemeNntp
, SchemeDelimiter
, 119),
1151 new UriScheme (UriSchemeGopher
, SchemeDelimiter
, 70),
1154 internal static string GetSchemeDelimiter (string scheme
)
1156 for (int i
= 0; i
< schemes
.Length
; i
++)
1157 if (schemes
[i
].scheme
== scheme
)
1158 return schemes
[i
].delimiter
;
1159 return Uri
.SchemeDelimiter
;
1162 internal static int GetDefaultPort (string scheme
)
1164 for (int i
= 0; i
< schemes
.Length
; i
++)
1165 if (schemes
[i
].scheme
== scheme
)
1166 return schemes
[i
].defaultPort
;
1170 private string GetOpaqueWiseSchemeDelimiter ()
1175 return GetSchemeDelimiter (scheme
);
1178 protected bool IsBadFileSystemCharacter (char ch
)
1180 // It does not always overlap with InvalidPathChars.
1181 int chInt
= (int) ch
;
1182 if (chInt
< 32 || (chInt
< 64 && chInt
> 57))
1201 protected static bool IsExcludedCharacter (char ch
)
1203 if (ch
<= 32 || ch
>= 127)
1206 if (ch
== '"' || ch
== '#' || ch
== '%' || ch
== '<' ||
1207 ch
== '>' || ch
== '[' || ch
== '\\' || ch
== ']' ||
1208 ch
== '^' || ch
== '`' || ch
== '{' || ch
== '|' ||
1214 private static bool IsPredefinedScheme (string scheme
)
1231 protected bool IsReservedCharacter (char ch
)
1233 if (ch
== '$' || ch
== '&' || ch
== '+' || ch
== ',' ||
1234 ch
== '/' || ch
== ':' || ch
== ';' || ch
== '=' ||