Facilitate the merge
[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 using System.Text.RegularExpressions;
34
35 namespace System {
36 #if NET_2_0
37         public
38 #endif
39         abstract class UriParser {
40
41                 static object lock_object = new object ();
42                 static Hashtable table;
43
44                 internal string scheme_name;
45                 private int default_port;
46
47                 // Regexp from RFC 2396
48 #if NET_2_1
49                 readonly static Regex uri_regex = new Regex (@"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?");
50 #else
51                 // Groups:                                      12            3  4          5       6  7        8 9
52                 readonly static Regex uri_regex = new Regex (@"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?", RegexOptions.Compiled);
53 #endif
54
55                 // Groups:                                       12         3    4 5
56                 readonly static Regex auth_regex = new Regex (@"^(([^@]+)@)?(.*?)(:([0-9]+))?$");
57
58                 protected UriParser ()
59                 {
60                 }
61
62                 static Match ParseAuthority (Group g)
63                 {
64                         return auth_regex.Match (g.Value);
65                 }
66
67                 // protected methods
68                 protected internal virtual string GetComponents (Uri uri, UriComponents components, UriFormat format)
69                 {
70                         if ((format < UriFormat.UriEscaped) || (format > UriFormat.SafeUnescaped))
71                                 throw new ArgumentOutOfRangeException ("format");
72
73                         Match m = uri_regex.Match (uri.OriginalString.Trim ());
74
75                         string scheme = scheme_name;
76                         int dp = default_port;
77
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);
83                         }
84
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, # ...)
87                         switch (components) {
88                         case UriComponents.Scheme:
89                                 return 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 ())
97                                         return p;
98                                 return String.Empty;
99                         }
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 ();
109                         }
110                         case UriComponents.SerializationInfoString:
111                                 components = UriComponents.AbsoluteUri;
112                                 break;
113                         }
114
115                         Match am = ParseAuthority (m.Groups [4]);
116
117                         // now we deal with multiple flags...
118
119                         StringBuilder sb = new StringBuilder ();
120
121                         if ((components & UriComponents.Scheme) != 0) {
122                                 sb.Append (scheme);
123                                 sb.Append (Uri.GetSchemeDelimiter (scheme));
124                         }
125
126                         if ((components & UriComponents.UserInfo) != 0)
127                                 sb.Append (am.Groups [1].Value);
128
129                         if ((components & UriComponents.Host) != 0)
130                                 sb.Append (am.Groups [3].Value);
131
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);
137                         }
138
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);
143                         }
144
145                         if ((components & UriComponents.Path) != 0) {
146                                 if ((components & UriComponents.PathAndQuery) != 0 &&
147                                         (m.Groups [5].Value == null || !m.Groups [5].Value.StartsWith ("/")))
148                                         sb.Append ("/");
149                                 sb.Append (m.Groups [5]);
150                         }
151
152                         if ((components & UriComponents.Query) != 0)
153                                 sb.Append (m.Groups [6]);
154
155                         if ((components & UriComponents.Fragment) != 0)
156                                 sb.Append (m.Groups [8]);
157
158                         return Format (sb.ToString (), format);
159                 }
160
161                 protected internal virtual void InitializeAndValidate (Uri uri, out UriFormatException parsingError)
162                 {
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");
167                         else
168                                 parsingError = null;
169                 }
170
171 #if NET_2_0
172                 protected internal virtual bool IsBaseOf (Uri baseUri, Uri relativeUri)
173                 {
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)
176                                 return false;
177
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);
181                 }
182
183                 protected internal virtual bool IsWellFormedOriginalString (Uri uri)
184                 {
185                         // well formed according to RFC2396 and RFC2732
186                         // see Uri.IsWellFormedOriginalString for some docs
187
188                         // Though this class does not seem to do anything. Even null arguments aren't checked :/
189                         return uri.IsWellFormedOriginalString ();
190                 }
191 #endif
192                 protected internal virtual UriParser OnNewUri ()
193                 {
194                         // nice time for init
195                         return this;
196                 }
197
198                 [MonoTODO]
199                 protected virtual void OnRegister (string schemeName, int defaultPort)
200                 {
201                         // unit tests shows that schemeName and defaultPort aren't usable from here
202                 }
203
204                 [MonoTODO]
205                 protected internal virtual string Resolve (Uri baseUri, Uri relativeUri, out UriFormatException parsingError)
206                 {
207                         // used by Uri.ctor and Uri.TryCreate
208                         throw new NotImplementedException ();
209                 }
210
211                 // internal properties
212
213                 internal string SchemeName {
214                         get { return scheme_name; }
215                         set { scheme_name = value; }
216                 }
217
218                 internal int DefaultPort {
219                         get { return default_port; }
220                         set { default_port = value; }
221                 }
222
223                 // private stuff
224
225                 private string IgnoreFirstCharIf (string s, char c)
226                 {
227                         if (s.Length == 0)
228                                 return String.Empty;
229                         if (s[0] == c)
230                                 return s.Substring (1);
231                         return s;
232                 }
233
234                 private string Format (string s, UriFormat format)
235                 {
236                         if (s.Length == 0)
237                                 return String.Empty;
238
239                         switch (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);
248                         default:
249                                 throw new ArgumentOutOfRangeException ("format");
250                         }
251                 }
252
253                 // static methods
254
255                 private static void CreateDefaults ()
256                 {
257                         if (table != null)
258                                 return;
259
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);
273                         
274                         lock (lock_object) {
275                                 if (table == null)
276                                         table = newtable;
277                                 else
278                                         newtable = null;
279                         }
280                 }
281
282                 public static bool IsKnownScheme (string schemeName)
283                 {
284                         if (schemeName == null)
285                                 throw new ArgumentNullException ("schemeName");
286                         if (schemeName.Length == 0)
287                                 throw new ArgumentOutOfRangeException ("schemeName");
288
289                         CreateDefaults ();
290                         string lc = schemeName.ToLower (CultureInfo.InvariantCulture);
291                         return (table [lc] != null);
292                 }
293
294                 // *no* check version
295                 private static void InternalRegister (Hashtable table, UriParser uriParser, string schemeName, int defaultPort)
296                 {
297                         uriParser.SchemeName = schemeName;
298                         uriParser.DefaultPort = defaultPort;
299
300                         // FIXME: MS doesn't seems to call most inherited parsers
301                         if (uriParser is GenericUriParser) {
302                                 table.Add (schemeName, uriParser);
303                         } else {
304                                 DefaultUriParser parser = new DefaultUriParser ();
305                                 parser.SchemeName = schemeName;
306                                 parser.DefaultPort = defaultPort;
307                                 table.Add (schemeName, parser);
308                         }
309
310                         // note: we cannot set schemeName and defaultPort inside OnRegister
311                         uriParser.OnRegister (schemeName, defaultPort);
312                 }
313
314                 [SecurityPermission (SecurityAction.Demand, Infrastructure = true)]
315                 public static void Register (UriParser uriParser, string schemeName, int defaultPort)
316                 {
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");
323
324                         CreateDefaults ();
325
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);
330                         }
331                         InternalRegister (table, uriParser, lc, defaultPort);
332                 }
333
334                 internal static UriParser GetParser (string schemeName)
335                 {
336                         if (schemeName == null)
337                                 return null;
338
339                         CreateDefaults ();
340
341                         string lc = schemeName.ToLower (CultureInfo.InvariantCulture);
342                         return (UriParser) table [lc];
343                 }
344         }
345 }
346