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.Trim ());
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 ();
121 if ((components & UriComponents.Scheme) != 0) {
123 sb.Append (Uri.GetSchemeDelimiter (scheme));
126 if ((components & UriComponents.UserInfo) != 0)
127 sb.Append (am.Groups [1].Value);
129 if ((components & UriComponents.Host) != 0)
130 sb.Append (am.Groups [3].Value);
132 // for StrongPort always show port - even if -1
133 // otherwise only display if ut's not the default port
134 if ((components & UriComponents.StrongPort) != 0) {
135 Group g = am.Groups [4];
136 sb.Append (g.Success ? g.Value : ":" + dp);
139 if ((components & UriComponents.Port) != 0) {
140 string p = am.Groups [5].Value;
141 if (p != null && p != String.Empty && p != dp.ToString ())
142 sb.Append (am.Groups [4].Value);
145 if ((components & UriComponents.Path) != 0) {
146 if ((components & UriComponents.PathAndQuery) != 0 &&
147 (m.Groups [5].Value == null || !m.Groups [5].Value.StartsWith ("/")))
149 sb.Append (m.Groups [5]);
152 if ((components & UriComponents.Query) != 0)
153 sb.Append (m.Groups [6]);
155 if ((components & UriComponents.Fragment) != 0)
156 sb.Append (m.Groups [8]);
158 return Format (sb.ToString (), format);
161 protected internal virtual void InitializeAndValidate (Uri uri, out UriFormatException parsingError)
163 // bad boy, it should check null arguments.
164 if ((uri.Scheme != scheme_name) && (scheme_name != "*"))
165 // Here .NET seems to return "The Authority/Host could not be parsed", but it does not make sense.
166 parsingError = new UriFormatException ("The argument Uri's scheme does not match");
172 protected internal virtual bool IsBaseOf (Uri baseUri, Uri relativeUri)
174 // compare, not case sensitive, the scheme, host and port (+ user informations)
175 if (Uri.Compare (baseUri, relativeUri, UriComponents.SchemeAndServer | UriComponents.UserInfo, UriFormat.Unescaped, StringComparison.InvariantCultureIgnoreCase) != 0)
178 string base_string = baseUri.LocalPath;
179 int last_slash = base_string.LastIndexOf ('/') + 1; // keep the slash
180 return (String.Compare (base_string, 0, relativeUri.LocalPath, 0, last_slash, StringComparison.InvariantCultureIgnoreCase) == 0);
183 protected internal virtual bool IsWellFormedOriginalString (Uri uri)
185 // well formed according to RFC2396 and RFC2732
186 // see Uri.IsWellFormedOriginalString for some docs
188 // Though this class does not seem to do anything. Even null arguments aren't checked :/
189 return uri.IsWellFormedOriginalString ();
192 protected internal virtual UriParser OnNewUri ()
194 // nice time for init
199 protected virtual void OnRegister (string schemeName, int defaultPort)
201 // unit tests shows that schemeName and defaultPort aren't usable from here
205 protected internal virtual string Resolve (Uri baseUri, Uri relativeUri, out UriFormatException parsingError)
207 // used by Uri.ctor and Uri.TryCreate
208 throw new NotImplementedException ();
211 // internal properties
213 internal string SchemeName {
214 get { return scheme_name; }
215 set { scheme_name = value; }
218 internal int DefaultPort {
219 get { return default_port; }
220 set { default_port = value; }
225 private string IgnoreFirstCharIf (string s, char c)
230 return s.Substring (1);
234 private string Format (string s, UriFormat format)
240 case UriFormat.UriEscaped:
241 return Uri.EscapeString (s, Uri.EscapeCommonHexBrackets);
242 case UriFormat.SafeUnescaped:
243 // TODO subset of escape rules
244 s = Uri.Unescape (s, false);
245 return s; //Uri.EscapeString (s, false, true, true);
246 case UriFormat.Unescaped:
247 return Uri.Unescape (s, false);
249 throw new ArgumentOutOfRangeException ("format");
255 private static void CreateDefaults ()
260 Hashtable newtable = new Hashtable ();
261 InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeFile, -1);
262 InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeFtp, 21);
263 InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeGopher, 70);
264 InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeHttp, 80);
265 InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeHttps, 443);
266 InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeMailto, 25);
267 InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeNetPipe, -1);
268 InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeNetTcp, -1);
269 InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeNews, -1);
270 InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeNntp, 119);
271 // not defined in Uri.UriScheme* but a parser class exists
272 InternalRegister (newtable, new DefaultUriParser (), "ldap", 389);
282 public static bool IsKnownScheme (string schemeName)
284 if (schemeName == null)
285 throw new ArgumentNullException ("schemeName");
286 if (schemeName.Length == 0)
287 throw new ArgumentOutOfRangeException ("schemeName");
290 string lc = schemeName.ToLower (CultureInfo.InvariantCulture);
291 return (table [lc] != null);
294 // *no* check version
295 private static void InternalRegister (Hashtable table, UriParser uriParser, string schemeName, int defaultPort)
297 uriParser.SchemeName = schemeName;
298 uriParser.DefaultPort = defaultPort;
300 // FIXME: MS doesn't seems to call most inherited parsers
301 if (uriParser is GenericUriParser) {
302 table.Add (schemeName, uriParser);
304 DefaultUriParser parser = new DefaultUriParser ();
305 parser.SchemeName = schemeName;
306 parser.DefaultPort = defaultPort;
307 table.Add (schemeName, parser);
310 // note: we cannot set schemeName and defaultPort inside OnRegister
311 uriParser.OnRegister (schemeName, defaultPort);
314 [SecurityPermission (SecurityAction.Demand, Infrastructure = true)]
315 public static void Register (UriParser uriParser, string schemeName, int defaultPort)
317 if (uriParser == null)
318 throw new ArgumentNullException ("uriParser");
319 if (schemeName == null)
320 throw new ArgumentNullException ("schemeName");
321 if ((defaultPort < -1) || (defaultPort >= UInt16.MaxValue))
322 throw new ArgumentOutOfRangeException ("defaultPort");
326 string lc = schemeName.ToLower (CultureInfo.InvariantCulture);
327 if (table [lc] != null) {
328 string msg = Locale.GetText ("Scheme '{0}' is already registred.");
329 throw new InvalidOperationException (msg);
331 InternalRegister (table, uriParser, lc, defaultPort);
334 internal static UriParser GetParser (string schemeName)
336 if (schemeName == null)
341 string lc = schemeName.ToLower (CultureInfo.InvariantCulture);
342 return (UriParser) table [lc];