Merge branch 'master' of github.com:mono/mono
[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                         if ((components & UriComponents.Scheme) != 0) {
121                                 sb.Append (scheme);
122                                 sb.Append (Uri.GetSchemeDelimiter (scheme));
123                         }
124
125                         if ((components & UriComponents.UserInfo) != 0)
126                                 sb.Append (am.Groups [1].Value);
127
128                         if ((components & UriComponents.Host) != 0)
129                                 sb.Append (am.Groups [3].Value);
130
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);
136                         }
137
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);
142                         }
143
144                         if ((components & UriComponents.Path) != 0)
145                                 sb.Append (m.Groups [5]);
146
147                         if ((components & UriComponents.Query) != 0)
148                                 sb.Append (m.Groups [6]);
149
150                         if ((components & UriComponents.Fragment) != 0)
151                                 sb.Append (m.Groups [8]);
152
153                         return Format (sb.ToString (), format);
154                 }
155
156                 protected internal virtual void InitializeAndValidate (Uri uri, out UriFormatException parsingError)
157                 {
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");
162                         else
163                                 parsingError = null;
164                 }
165
166 #if NET_2_0
167                 protected internal virtual bool IsBaseOf (Uri baseUri, Uri relativeUri)
168                 {
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)
171                                 return false;
172
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);
176                 }
177
178                 protected internal virtual bool IsWellFormedOriginalString (Uri uri)
179                 {
180                         // well formed according to RFC2396 and RFC2732
181                         // see Uri.IsWellFormedOriginalString for some docs
182
183                         // Though this class does not seem to do anything. Even null arguments aren't checked :/
184                         return uri.IsWellFormedOriginalString ();
185                 }
186 #endif
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, 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);
243                         default:
244                                 throw new ArgumentOutOfRangeException ("format");
245                         }
246                 }
247
248                 // static methods
249
250                 private static void CreateDefaults ()
251                 {
252                         if (table != null)
253                                 return;
254
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);
262 #if NET_2_0
263                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeNetPipe, -1);
264                         InternalRegister (newtable, new DefaultUriParser (), Uri.UriSchemeNetTcp, -1);
265 #endif
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);
270                         
271                         lock (lock_object) {
272                                 if (table == null)
273                                         table = newtable;
274                                 else
275                                         newtable = null;
276                         }
277                 }
278
279                 public static bool IsKnownScheme (string schemeName)
280                 {
281                         if (schemeName == null)
282                                 throw new ArgumentNullException ("schemeName");
283                         if (schemeName.Length == 0)
284                                 throw new ArgumentOutOfRangeException ("schemeName");
285
286                         CreateDefaults ();
287                         string lc = schemeName.ToLower (CultureInfo.InvariantCulture);
288                         return (table [lc] != null);
289                 }
290
291                 // *no* check version
292                 private static void InternalRegister (Hashtable table, UriParser uriParser, string schemeName, int defaultPort)
293                 {
294                         uriParser.SchemeName = schemeName;
295                         uriParser.DefaultPort = defaultPort;
296
297                         // FIXME: MS doesn't seems to call most inherited parsers
298                         if (uriParser is GenericUriParser) {
299                                 table.Add (schemeName, uriParser);
300                         } else {
301                                 DefaultUriParser parser = new DefaultUriParser ();
302                                 parser.SchemeName = schemeName;
303                                 parser.DefaultPort = defaultPort;
304                                 table.Add (schemeName, parser);
305                         }
306
307                         // note: we cannot set schemeName and defaultPort inside OnRegister
308                         uriParser.OnRegister (schemeName, defaultPort);
309                 }
310
311                 [SecurityPermission (SecurityAction.Demand, Infrastructure = true)]
312                 public static void Register (UriParser uriParser, string schemeName, int defaultPort)
313                 {
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");
320
321                         CreateDefaults ();
322
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);
327                         }
328                         InternalRegister (table, uriParser, lc, defaultPort);
329                 }
330
331                 internal static UriParser GetParser (string schemeName)
332                 {
333                         if (schemeName == null)
334                                 return null;
335
336                         CreateDefaults ();
337
338                         string lc = schemeName.ToLower (CultureInfo.InvariantCulture);
339                         return (UriParser) table [lc];
340                 }
341         }
342 }
343