2 // System.UriParser abstract class
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System
.Collections
;
30 using System
.Globalization
;
31 using System
.Security
.Permissions
;
33 using System
.Text
.RegularExpressions
;
39 abstract class UriParser
{
41 static object lock_object
= new object ();
42 static Hashtable table
;
44 internal string scheme_name
;
45 private int default_port
;
47 // Regexp from RFC 2396
49 readonly static Regex uri_regex
= new Regex (@"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?");
51 // Groups: 12 3 4 5 6 7 8 9
52 readonly static Regex uri_regex
= new Regex (@"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?", RegexOptions
.Compiled
);
56 readonly static Regex auth_regex
= new Regex (@"^(([^@]+)@)?(.*?)(:([0-9]+))?$");
58 protected UriParser ()
62 static Match
ParseAuthority (Group g
)
64 return auth_regex
.Match (g
.Value
);
68 protected internal virtual string GetComponents (Uri uri
, UriComponents components
, UriFormat format
)
70 if ((format
< UriFormat
.UriEscaped
) || (format
> UriFormat
.SafeUnescaped
))
71 throw new ArgumentOutOfRangeException ("format");
73 Match m
= uri_regex
.Match (uri
.OriginalString
);
75 string scheme
= scheme_name
;
76 int dp
= default_port
;
78 if ((scheme
== null) || (scheme
== "*")) {
79 scheme
= m
.Groups
[2].Value
;
80 dp
= Uri
.GetDefaultPort (scheme
);
81 } else if (String
.Compare (scheme
, m
.Groups
[2].Value
, true) != 0) {
82 throw new SystemException ("URI Parser: scheme mismatch: " + scheme
+ " vs. " + m
.Groups
[2].Value
);
85 // it's easier to answer some case directly (as the output isn't identical
86 // when mixed with others components, e.g. leading slash, # ...)
88 case UriComponents
.Scheme
:
90 case UriComponents
.UserInfo
:
91 return ParseAuthority (m
.Groups
[4]).Groups
[2].Value
;
92 case UriComponents
.Host
:
93 return ParseAuthority (m
.Groups
[4]).Groups
[3].Value
;
94 case UriComponents
.Port
: {
95 string p
= ParseAuthority (m
.Groups
[4]).Groups
[5].Value
;
96 if (p
!= null && p
!= String
.Empty
&& p
!= dp
.ToString ())
100 case UriComponents
.Path
:
101 return Format (IgnoreFirstCharIf (m
.Groups
[5].Value
, '/'), format
);
102 case UriComponents
.Query
:
103 return Format (m
.Groups
[7].Value
, format
);
104 case UriComponents
.Fragment
:
105 return Format (m
.Groups
[9].Value
, format
);
106 case UriComponents
.StrongPort
: {
107 Group g
= ParseAuthority (m
.Groups
[4]).Groups
[5];
108 return g
.Success
? g
.Value
: dp
.ToString ();
110 case UriComponents
.SerializationInfoString
:
111 components
= UriComponents
.AbsoluteUri
;
115 Match am
= ParseAuthority (m
.Groups
[4]);
117 // now we deal with multiple flags...
119 StringBuilder sb
= new StringBuilder ();
120 if ((components
& UriComponents
.Scheme
) != 0) {
122 sb
.Append (Uri
.GetSchemeDelimiter (scheme
));
125 if ((components
& UriComponents
.UserInfo
) != 0)
126 sb
.Append (am
.Groups
[1].Value
);
128 if ((components
& UriComponents
.Host
) != 0)
129 sb
.Append (am
.Groups
[3].Value
);
131 // for StrongPort always show port - even if -1
132 // otherwise only display if ut's not the default port
133 if ((components
& UriComponents
.StrongPort
) != 0) {
134 Group g
= am
.Groups
[4];
135 sb
.Append (g
.Success
? g
.Value
: ":" + dp
);
138 if ((components
& UriComponents
.Port
) != 0) {
139 string p
= am
.Groups
[5].Value
;
140 if (p
!= null && p
!= String
.Empty
&& p
!= dp
.ToString ())
141 sb
.Append (am
.Groups
[4].Value
);
144 if ((components
& UriComponents
.Path
) != 0)
145 sb
.Append (m
.Groups
[5]);
147 if ((components
& UriComponents
.Query
) != 0)
148 sb
.Append (m
.Groups
[6]);
150 if ((components
& UriComponents
.Fragment
) != 0)
151 sb
.Append (m
.Groups
[8]);
153 return Format (sb
.ToString (), format
);
156 protected internal virtual void InitializeAndValidate (Uri uri
, out UriFormatException parsingError
)
158 // bad boy, it should check null arguments.
159 if ((uri
.Scheme
!= scheme_name
) && (scheme_name
!= "*"))
160 // Here .NET seems to return "The Authority/Host could not be parsed", but it does not make sense.
161 parsingError
= new UriFormatException ("The argument Uri's scheme does not match");
167 protected internal virtual bool IsBaseOf (Uri baseUri
, Uri relativeUri
)
169 // compare, not case sensitive, the scheme, host and port (+ user informations)
170 if (Uri
.Compare (baseUri
, relativeUri
, UriComponents
.SchemeAndServer
| UriComponents
.UserInfo
, UriFormat
.Unescaped
, StringComparison
.InvariantCultureIgnoreCase
) != 0)
173 string base_string
= baseUri
.LocalPath
;
174 int last_slash
= base_string
.LastIndexOf ('/') + 1; // keep the slash
175 return (String
.Compare (base_string
, 0, relativeUri
.LocalPath
, 0, last_slash
, StringComparison
.InvariantCultureIgnoreCase
) == 0);
178 protected internal virtual bool IsWellFormedOriginalString (Uri uri
)
180 // well formed according to RFC2396 and RFC2732
181 // see Uri.IsWellFormedOriginalString for some docs
183 // Though this class does not seem to do anything. Even null arguments aren't checked :/
184 return uri
.IsWellFormedOriginalString ();
187 protected internal virtual UriParser
OnNewUri ()
189 // nice time for init
194 protected virtual void OnRegister (string schemeName
, int defaultPort
)
196 // unit tests shows that schemeName and defaultPort aren't usable from here
200 protected internal virtual string Resolve (Uri baseUri
, Uri relativeUri
, out UriFormatException parsingError
)
202 // used by Uri.ctor and Uri.TryCreate
203 throw new NotImplementedException ();
206 // internal properties
208 internal string SchemeName
{
209 get { return scheme_name; }
210 set { scheme_name = value; }
213 internal int DefaultPort
{
214 get { return default_port; }
215 set { default_port = value; }
220 private string IgnoreFirstCharIf (string s
, char c
)
225 return s
.Substring (1);
229 private string Format (string s
, UriFormat format
)
235 case UriFormat
.UriEscaped
:
236 return Uri
.EscapeString (s
, false, true, true);
237 case UriFormat
.SafeUnescaped
:
238 // TODO subset of escape rules
239 s
= Uri
.Unescape (s
, false);
240 return s
; //Uri.EscapeString (s, false, true, true);
241 case UriFormat
.Unescaped
:
242 return Uri
.Unescape (s
, false);
244 throw new ArgumentOutOfRangeException ("format");
250 private static void CreateDefaults ()
255 Hashtable newtable
= new Hashtable ();
256 InternalRegister (newtable
, new DefaultUriParser (), Uri
.UriSchemeFile
, -1);
257 InternalRegister (newtable
, new DefaultUriParser (), Uri
.UriSchemeFtp
, 21);
258 InternalRegister (newtable
, new DefaultUriParser (), Uri
.UriSchemeGopher
, 70);
259 InternalRegister (newtable
, new DefaultUriParser (), Uri
.UriSchemeHttp
, 80);
260 InternalRegister (newtable
, new DefaultUriParser (), Uri
.UriSchemeHttps
, 443);
261 InternalRegister (newtable
, new DefaultUriParser (), Uri
.UriSchemeMailto
, 25);
263 InternalRegister (newtable
, new DefaultUriParser (), Uri
.UriSchemeNetPipe
, -1);
264 InternalRegister (newtable
, new DefaultUriParser (), Uri
.UriSchemeNetTcp
, -1);
266 InternalRegister (newtable
, new DefaultUriParser (), Uri
.UriSchemeNews
, 119);
267 InternalRegister (newtable
, new DefaultUriParser (), Uri
.UriSchemeNntp
, 119);
268 // not defined in Uri.UriScheme* but a parser class exists
269 InternalRegister (newtable
, new DefaultUriParser (), "ldap", 389);
279 public static bool IsKnownScheme (string schemeName
)
281 if (schemeName
== null)
282 throw new ArgumentNullException ("schemeName");
283 if (schemeName
.Length
== 0)
284 throw new ArgumentOutOfRangeException ("schemeName");
287 string lc
= schemeName
.ToLower (CultureInfo
.InvariantCulture
);
288 return (table
[lc
] != null);
291 // *no* check version
292 private static void InternalRegister (Hashtable table
, UriParser uriParser
, string schemeName
, int defaultPort
)
294 uriParser
.SchemeName
= schemeName
;
295 uriParser
.DefaultPort
= defaultPort
;
297 // FIXME: MS doesn't seems to call most inherited parsers
298 if (uriParser
is GenericUriParser
) {
299 table
.Add (schemeName
, uriParser
);
301 DefaultUriParser parser
= new DefaultUriParser ();
302 parser
.SchemeName
= schemeName
;
303 parser
.DefaultPort
= defaultPort
;
304 table
.Add (schemeName
, parser
);
307 // note: we cannot set schemeName and defaultPort inside OnRegister
308 uriParser
.OnRegister (schemeName
, defaultPort
);
311 [SecurityPermission (SecurityAction
.Demand
, Infrastructure
= true)]
312 public static void Register (UriParser uriParser
, string schemeName
, int defaultPort
)
314 if (uriParser
== null)
315 throw new ArgumentNullException ("uriParser");
316 if (schemeName
== null)
317 throw new ArgumentNullException ("schemeName");
318 if ((defaultPort
< -1) || (defaultPort
>= UInt16
.MaxValue
))
319 throw new ArgumentOutOfRangeException ("defaultPort");
323 string lc
= schemeName
.ToLower (CultureInfo
.InvariantCulture
);
324 if (table
[lc
] != null) {
325 string msg
= Locale
.GetText ("Scheme '{0}' is already registred.");
326 throw new InvalidOperationException (msg
);
328 InternalRegister (table
, uriParser
, lc
, defaultPort
);
331 internal static UriParser
GetParser (string schemeName
)
333 if (schemeName
== null)
338 string lc
= schemeName
.ToLower (CultureInfo
.InvariantCulture
);
339 return (UriParser
) table
[lc
];