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