Merge pull request #629 from pruiz/syswebrouting-fixes2
[mono.git] / mcs / class / System / System / UriParser.cs
1 //
2 // System.UriParser abstract class
3 //
4 // Author:
5 //      Sebastien Pouliot  <sebastien@ximian.com>
6 //
7 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
8 //
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:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
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.
27 //
28
29 using System.Collections;
30 using System.Globalization;
31 using System.Security.Permissions;
32 using System.Text;
33
34 namespace System {
35         public abstract class UriParser {
36
37                 static object lock_object = new object ();
38                 static Hashtable table;
39
40                 internal string scheme_name;
41                 private int default_port;
42
43                 protected UriParser ()
44                 {
45                 }
46
47                 // protected methods
48                 protected internal virtual string GetComponents (Uri uri, UriComponents components, UriFormat format)
49                 {
50                         if ((format < UriFormat.UriEscaped) || (format > UriFormat.SafeUnescaped))
51                                 throw new ArgumentOutOfRangeException ("format");
52
53                         UriElements elements = UriParseComponents.ParseComponents (uri.OriginalString.Trim ());
54
55                         string scheme = scheme_name;
56                         int dp = default_port;
57
58                         if ((scheme == null) || (scheme == "*")) {
59                                 scheme = elements.scheme;
60                                 dp = Uri.GetDefaultPort (scheme);
61                         } else if (String.Compare (scheme, elements.scheme, true) != 0) {
62                                 throw new SystemException ("URI Parser: scheme mismatch: " + scheme + " vs. " + elements.scheme);
63                         }
64
65                         // it's easier to answer some case directly (as the output isn't identical 
66                         // when mixed with others components, e.g. leading slash, # ...)
67                         switch (components) {
68                         case UriComponents.Scheme:
69                                 return scheme;
70                         case UriComponents.UserInfo:
71                                 return elements.user;
72                         case UriComponents.Host:
73                                 return elements.host;
74                         case UriComponents.Port: {
75                                 string p = elements.port;
76                                 if (p != null && p.Length != 0 && p != dp.ToString ())
77                                         return p;
78                                 return String.Empty;
79                         }
80                         case UriComponents.Path:
81                                 return Format (IgnoreFirstCharIf (elements.path, '/'), format);
82                         case UriComponents.Query:
83                                 return Format (elements.query, format);
84                         case UriComponents.Fragment:
85                                 return Format (elements.fragment, format);
86                         case UriComponents.StrongPort: {
87                                 return elements.port.Length != 0 ? elements.port : dp.ToString ();
88                         }
89                         case UriComponents.SerializationInfoString:
90                                 components = UriComponents.AbsoluteUri;
91                                 break;
92                         }
93
94                         // now we deal with multiple flags...
95
96                         StringBuilder sb = new StringBuilder ();
97
98                         if ((components & UriComponents.Scheme) != 0) {
99                                 sb.Append (scheme);
100                                 sb.Append (Uri.GetSchemeDelimiter (scheme));
101                         }
102
103                         if ((components & UriComponents.UserInfo) != 0) {
104                                 string userinfo = elements.user;
105                                 if (!String.IsNullOrEmpty (userinfo)) {
106                                         sb.Append (elements.user);
107                                         sb.Append ('@');
108                                 }
109                         }
110
111                         if ((components & UriComponents.Host) != 0)
112                                 sb.Append (elements.host);
113
114                         // for StrongPort always show port - even if -1
115                         // otherwise only display if ut's not the default port
116                         if ((components & UriComponents.StrongPort) != 0) {
117                                 sb.Append (":");
118                                 if (elements.port.Length != 0) {
119                                         sb.Append (elements.port);
120                                 } else {
121                                         sb.Append (dp);
122                                 }
123                         }
124
125                         if ((components & UriComponents.Port) != 0) {
126                                 string p = elements.port;
127                                 if (p != null && p.Length != 0 && p != dp.ToString ()) {
128                                         sb.Append (":");
129                                         sb.Append (elements.port);
130                                 }
131                         }
132
133                         if ((components & UriComponents.Path) != 0) {
134                                 if ((components & UriComponents.PathAndQuery) != 0 &&
135                                         (elements.path.Length == 0 || !elements.path.StartsWith ("/")))
136                                         sb.Append ("/");
137                                 sb.Append (elements.path);
138                         }
139
140                         if ((components & UriComponents.Query) != 0) {
141                                 string query = elements.query;
142                                 if (!String.IsNullOrEmpty (query)) {
143                                         sb.Append ("?");
144                                         sb.Append (elements.query);
145                                 }
146                         }
147
148                         string result = Format (sb.ToString (), format);
149                         if ((components & UriComponents.Fragment) != 0) {
150                                 string f = elements.fragment;
151                                 if (!String.IsNullOrEmpty (f)) {
152                                         result += "#" + Format (f, format);
153                                 }
154                         }
155                         return result;
156                 }
157
158                 protected internal virtual void InitializeAndValidate (Uri uri, out UriFormatException parsingError)
159                 {
160                         // bad boy, it should check null arguments.
161                         if ((uri.Scheme != scheme_name) && (scheme_name != "*"))
162                                 // Here .NET seems to return "The Authority/Host could not be parsed", but it does not make sense.
163                                 parsingError = new UriFormatException ("The argument Uri's scheme does not match");
164                         else
165                                 parsingError = null;
166                 }
167
168                 protected internal virtual bool IsBaseOf (Uri baseUri, Uri relativeUri)
169                 {
170                         // compare, not case sensitive, the scheme, host and port (+ user informations)
171                         if (Uri.Compare (baseUri, relativeUri, UriComponents.SchemeAndServer | UriComponents.UserInfo, UriFormat.Unescaped, StringComparison.InvariantCultureIgnoreCase) != 0)
172                                 return false;
173
174                         string base_string = baseUri.LocalPath;
175                         int last_slash = base_string.LastIndexOf ('/') + 1; // keep the slash
176                         return (String.Compare (base_string, 0, relativeUri.LocalPath, 0, last_slash, StringComparison.InvariantCultureIgnoreCase) == 0);
177                 }
178
179                 protected internal virtual bool IsWellFormedOriginalString (Uri uri)
180                 {
181                         // well formed according to RFC2396 and RFC2732
182                         // see Uri.IsWellFormedOriginalString for some docs
183
184                         // Though this class does not seem to do anything. Even null arguments aren't checked :/
185                         return uri.IsWellFormedOriginalString ();
186                 }
187                 protected internal virtual UriParser OnNewUri ()
188                 {
189                         // nice time for init
190                         return this;
191                 }
192
193                 [MonoTODO]
194                 protected virtual void OnRegister (string schemeName, int defaultPort)
195                 {
196                         // unit tests shows that schemeName and defaultPort aren't usable from here
197                 }
198
199                 [MonoTODO]
200                 protected internal virtual string Resolve (Uri baseUri, Uri relativeUri, out UriFormatException parsingError)
201                 {
202                         // used by Uri.ctor and Uri.TryCreate
203                         throw new NotImplementedException ();
204                 }
205
206                 // internal properties
207
208                 internal string SchemeName {
209                         get { return scheme_name; }
210                         set { scheme_name = value; }
211                 }
212
213                 internal int DefaultPort {
214                         get { return default_port; }
215                         set { default_port = value; }
216                 }
217
218                 // private stuff
219
220                 private string IgnoreFirstCharIf (string s, char c)
221                 {
222                         if (s.Length == 0)
223                                 return String.Empty;
224                         if (s[0] == c)
225                                 return s.Substring (1);
226                         return s;
227                 }
228
229                 private string Format (string s, UriFormat format)
230                 {
231                         if (s.Length == 0)
232                                 return String.Empty;
233
234                         switch (format) {
235                         case UriFormat.UriEscaped:
236                                 return Uri.EscapeString (s, Uri.EscapeCommonHexBrackets);
237                         case UriFormat.SafeUnescaped:
238                                 return Uri.UnescapeDataString (s, true);
239                         case UriFormat.Unescaped:
240                                 return Uri.Unescape (s, false);
241                         default:
242                                 throw new ArgumentOutOfRangeException ("format");
243                         }
244                 }
245
246                 // static methods
247
248                 private static void CreateDefaults ()
249                 {
250                         if (table != null)
251                                 return;
252
253                         Hashtable newtable = new Hashtable ();
254                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeFile, -1);
255                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeFtp, 21);
256                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeGopher, 70);
257                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeHttp, 80);
258                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeHttps, 443);
259                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeMailto, 25);
260                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeNetPipe, -1);
261                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeNetTcp, -1);
262                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeNews, -1);
263                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeNntp, 119);
264                         // not defined in Uri.UriScheme* but a parser class exists
265                         InternalRegister (newtable, new DefaultUriParser (), "ldap", 389);
266                         
267                         lock (lock_object) {
268                                 if (table == null)
269                                         table = newtable;
270                                 else
271                                         newtable = null;
272                         }
273                 }
274
275                 public static bool IsKnownScheme (string schemeName)
276                 {
277                         if (schemeName == null)
278                                 throw new ArgumentNullException ("schemeName");
279                         if (schemeName.Length == 0)
280                                 throw new ArgumentOutOfRangeException ("schemeName");
281
282                         CreateDefaults ();
283                         string lc = schemeName.ToLower (CultureInfo.InvariantCulture);
284                         return (table [lc] != null);
285                 }
286
287                 // *no* check version
288                 private static void InternalRegister (Hashtable table, UriParser uriParser, string schemeName, int defaultPort)
289                 {
290                         uriParser.SchemeName = schemeName;
291                         uriParser.DefaultPort = defaultPort;
292
293                         // FIXME: MS doesn't seems to call most inherited parsers
294                         if (uriParser is GenericUriParser) {
295                                 table.Add (schemeName, uriParser);
296                         } else {
297                                 DefaultUriParser parser = new DefaultUriParser ();
298                                 parser.SchemeName = schemeName;
299                                 parser.DefaultPort = defaultPort;
300                                 table.Add (schemeName, parser);
301                         }
302
303                         // note: we cannot set schemeName and defaultPort inside OnRegister
304                         uriParser.OnRegister (schemeName, defaultPort);
305                 }
306
307                 [SecurityPermission (SecurityAction.Demand, Infrastructure = true)]
308                 public static void Register (UriParser uriParser, string schemeName, int defaultPort)
309                 {
310                         if (uriParser == null)
311                                 throw new ArgumentNullException ("uriParser");
312                         if (schemeName == null)
313                                 throw new ArgumentNullException ("schemeName");
314                         if ((defaultPort < -1) || (defaultPort >= UInt16.MaxValue))
315                                 throw new ArgumentOutOfRangeException ("defaultPort");
316
317                         CreateDefaults ();
318
319                         string lc = schemeName.ToLower (CultureInfo.InvariantCulture);
320                         if (table [lc] != null) {
321                                 string msg = Locale.GetText ("Scheme '{0}' is already registred.");
322                                 throw new InvalidOperationException (msg);
323                         }
324                         InternalRegister (table, uriParser, lc, defaultPort);
325                 }
326
327                 internal static UriParser GetParser (string schemeName)
328                 {
329                         if (schemeName == null)
330                                 return null;
331
332                         CreateDefaults ();
333
334                         string lc = schemeName.ToLower (CultureInfo.InvariantCulture);
335                         return (UriParser) table [lc];
336                 }
337         }
338 }
339