Fix bugs in sizing TableLayoutPanel (Xamarin bug 18638)
[mono.git] / mcs / class / System / System / UriParseComponents.cs
1 //
2 // Internal UriParseComponents class
3 //
4 // Author:
5 //      Vinicius Jarina  <vinicius.jarina@xamarin.com>
6 //
7 // Copyright (C) 2012 Xamarin, Inc (http://www.xamarin.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.IO;
30 using System.Net;
31 using System.Text;
32 using System.Globalization;
33
34 namespace System {
35         
36         internal class ParserState
37         {
38                 public ParserState (string uri, UriKind kind)
39                 {
40                         remaining = uri;
41                         this.kind = kind;
42                         elements  = new UriElements ();
43                 }
44                 
45                 public string remaining;
46                 public UriKind kind;
47                 public UriElements elements;
48                 public string error;
49         }
50         
51         // Parse Uri components (scheme, userinfo, host, query, fragment)
52         // http://www.ietf.org/rfc/rfc3986.txt
53         internal static class UriParseComponents
54         {
55                 public static UriElements ParseComponents (string uri, UriKind kind)
56                 {
57                         UriElements elements;
58                         string error;
59
60                         if (!TryParseComponents (uri, kind, out elements, out error))
61                                 throw new UriFormatException (error);
62
63                         return elements;
64                 }
65
66                 public static bool TryParseComponents (string uri, UriKind kind, out UriElements elements, out string error)
67                 {
68                         uri = uri.Trim ();
69
70                         ParserState state = new ParserState (uri, kind);
71                         elements = state.elements;
72                         error = null;
73
74                         if (uri.Length == 0 && (kind == UriKind.Relative || kind == UriKind.RelativeOrAbsolute)){
75                                 state.elements.isAbsoluteUri = false;
76                                 return true;
77                         }
78                         
79                         if (uri.Length <= 1 && kind == UriKind.Absolute) {
80                                 error = "Absolute URI is too short";
81                                 return false;
82                         }
83
84                         bool ok = ParseFilePath (state) &&
85                                 ParseScheme (state);
86
87                         var scheme = state.elements.scheme;
88                         UriParser parser = null;
89                         if (!string.IsNullOrEmpty (scheme)) {
90                                 parser = UriParser.GetParser (scheme);
91                                 if (parser != null && !(parser is DefaultUriParser))
92                                         return true;
93                         }
94
95                         ok = ok &&
96                                 ParseAuthority (state) &&
97                                 ParsePath (state) &&
98                                 ParseQuery (state) &&
99                                 ParseFragment (state);
100
101                         if (string.IsNullOrEmpty (state.elements.host) &&
102                                 (scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeGopher || scheme == Uri.UriSchemeNntp ||
103                                 scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeFtp))
104                                 state.error = "Invalid URI: The Authority/Host could not be parsed.";
105
106                         if (!string.IsNullOrEmpty (state.elements.host) &&
107                                 Uri.CheckHostName (state.elements.host) == UriHostNameType.Unknown)
108                                 state.error = "Invalid URI: The hostname could not be parsed.";
109
110                         if (!string.IsNullOrEmpty (state.error)) {
111                                 elements = null;
112                                 error = state.error;
113                                 return false;
114                         }
115                         
116                         return true;
117                 }
118
119                                 // ALPHA
120                 private static bool IsAlpha (char ch)
121                 {
122                         return (('a' <= ch) && (ch <= 'z')) ||
123                                    (('A' <= ch) && (ch <= 'Z'));
124                 }
125
126                 private static bool ParseFilePath (ParserState state)
127                 {
128                         return ParseWindowsFilePath (state) &&
129                                 ParseWindowsUNC (state) &&
130                                 ParseUnixFilePath (state);
131                 }
132
133                 private static bool ParseWindowsFilePath (ParserState state)
134                 {
135                         var scheme = state.elements.scheme;
136
137                         if (!string.IsNullOrEmpty (scheme) &&
138                                  scheme != Uri.UriSchemeFile && UriHelper.IsKnownScheme (scheme))
139                                 return state.remaining.Length > 0;
140
141                         string part = state.remaining;
142
143                         if (part.Length > 0 && (part [0] == '/' || part [0] == '\\'))
144                                 part = part.Substring (1);
145
146                         if (part.Length < 2 || part [1] != ':')
147                                 return state.remaining.Length > 0;
148
149                         if (!IsAlpha (part [0])) {
150                                 if (state.kind == UriKind.Absolute) {
151                                         state.error = "Invalid URI: The URI scheme is not valid.";
152                                         return false;
153                                 }
154                                 state.elements.isAbsoluteUri = false;
155                                 state.elements.path = part;
156                                 return false;
157                         }
158
159                         if (part.Length > 2 && part [2] != '\\' && part [2] != '/') {
160                                 state.error = "Relative file path is not allowed.";
161                                 return false;
162                         }
163
164                         if (string.IsNullOrEmpty (scheme)) {
165                                 state.elements.scheme = Uri.UriSchemeFile;
166                                 state.elements.delimiter = "://";
167                         }
168
169                         state.elements.path = part.Replace ("\\", "/");
170
171                         return false;
172                 }
173
174                 private static bool ParseWindowsUNC (ParserState state)
175                 {
176                         string part = state.remaining;
177
178                         if (part.Length < 2 || part [0] != '\\' || part [1] != '\\')
179                                 return state.remaining.Length > 0;
180
181                         state.elements.scheme = Uri.UriSchemeFile;
182                         state.elements.delimiter = "://";
183                         state.elements.isUnc = true;
184
185                         part = part.TrimStart ('\\');
186                         int pos = part.IndexOf ('\\');
187                         if (pos > 0) {
188                                 state.elements.path = part.Substring (pos);
189                                 state.elements.host = part.Substring (0, pos);
190                         } else { // "\\\\server"
191                                 state.elements.host = part;
192                                 state.elements.path = String.Empty;
193                         }
194                         state.elements.path = state.elements.path.Replace ("\\", "/");
195
196                         return false;
197                 }
198
199                 private static bool ParseUnixFilePath (ParserState state)
200                 {
201                         string part = state.remaining;
202
203                         if (part.Length < 1 || part [0] != '/' || Path.DirectorySeparatorChar != '/')
204                                 return state.remaining.Length > 0;
205
206                         state.elements.scheme = Uri.UriSchemeFile;
207                         state.elements.delimiter = "://";
208                         state.elements.isUnixFilePath = true;
209                         state.elements.isAbsoluteUri = (state.kind == UriKind.Relative)? false : true;
210
211                         if (part.Length >= 2 && part [0] == '/' && part [1] == '/') {
212                                 part = part.TrimStart (new char [] {'/'});
213                                 state.elements.path = '/' + part;
214                         } else
215                                 state.elements.path = part;
216
217                         return false;
218                 }
219                 
220                 // 3.1) scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
221                 private static bool ParseScheme (ParserState state)
222                 {
223                         string part = state.remaining;
224                         
225                         StringBuilder sb = new StringBuilder ();
226                         sb.Append (part [0]);
227                         
228                         int index;
229                         for (index = 1; index < part.Length; index++ ) {
230                                 char ch = part [index];
231                                 if (ch != '.' && ch != '-' && ch != '+' && !IsAlpha (ch) && !Char.IsDigit (ch))
232                                         break;
233                                 
234                                 sb.Append (ch);
235                         }
236                         
237                         if (index == 0 || index >= part.Length) {
238                                 if (state.kind == UriKind.Absolute) {
239                                         state.error = "Invalid URI: The format of the URI could not be determined.";
240                                         return false;
241                                 }
242
243                                 state.elements.isAbsoluteUri = false;
244                                 return state.remaining.Length > 0;
245                         }
246
247                         if (part [index] != ':') {
248                                 if (state.kind == UriKind.Absolute) {
249                                         state.error = "Invalid URI: The URI scheme is not valid.";
250                                         return false;
251                                 }
252
253                                 state.elements.isAbsoluteUri = false;
254                                 return state.remaining.Length > 0;
255                         }
256
257                         state.elements.scheme = sb.ToString ().ToLowerInvariant ();
258                         state.remaining = part.Substring (index);
259
260                         // Check scheme name characters as specified in RFC2396.
261                         // Note: different checks in 1.x and 2.0
262                         if (!Uri.CheckSchemeName (state.elements.scheme)) {
263                                 if (state.kind == UriKind.Absolute) {
264                                         state.error = "Invalid URI: The URI scheme is not valid.";
265                                         return false;
266                                 }
267
268                                 state.elements.isAbsoluteUri = false;
269                                 return state.remaining.Length > 0;
270                         }
271
272                         return ParseDelimiter (state);
273                 }
274
275                 private static bool ParseDelimiter (ParserState state)
276                 {
277                         var delimiter = Uri.GetSchemeDelimiter (state.elements.scheme);
278
279                         if (!state.remaining.StartsWith (delimiter, StringComparison.Ordinal)) {
280                                 if (UriHelper.IsKnownScheme (state.elements.scheme)) {
281                                         state.error = "Invalid URI: The Authority/Host could not be parsed.";
282                                         return false;
283                                 }
284
285                                 delimiter = ":";
286                         }
287                                 
288                         state.elements.delimiter = delimiter;
289
290                         state.remaining = state.remaining.Substring (delimiter.Length);
291
292                         return state.remaining.Length > 0;
293                 }
294                 
295                 private static bool ParseAuthority (ParserState state)
296                 {
297                         if (state.elements.delimiter != Uri.SchemeDelimiter && state.elements.scheme != Uri.UriSchemeMailto)
298                                 return state.remaining.Length > 0;
299                         
300                         return ParseUser (state) &&
301                                 ParseHost (state) &&
302                                 ParsePort (state);
303                 }
304
305                 static bool IsUnreserved (char ch)
306                 {
307                         return ch == '-' || ch == '.' || ch == '_' || ch == '~';
308                 }
309
310
311                 static bool IsSubDelim (char ch)
312                 {
313                         return ch == '!' || ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' ||
314                                 ch == '*' || ch == '+' || ch == ',' || ch == ';' || ch == '=';
315                 }
316                 
317                 // userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )
318                 private static bool ParseUser (ParserState state)
319                 {
320                         string part = state.remaining;
321                         StringBuilder sb = null;
322
323                         int index;
324                         for (index = 0; index < part.Length; index++) {
325                                 char ch = part [index];
326
327                                 if (ch == '%'){
328                                         if (!Uri.IsHexEncoding (part, index))
329                                                 return false;
330                                         var oldIndex = index;
331                                         ch = Uri.HexUnescape (part, ref index);
332                                         index--;
333                                         if (ch == '@') {
334                                                 sb.Append (part.Substring (oldIndex, index - oldIndex + 1));
335                                                 continue;
336                                         }
337                                 }
338
339                                 if (Char.IsLetterOrDigit (ch) || IsUnreserved (ch) || IsSubDelim (ch) || ch == ':'){
340                                         if (sb == null)
341                                                 sb = new StringBuilder ();
342                                         sb.Append (ch);
343                                 } else
344                                         break;
345                         }
346
347                         if (index + 1 <= part.Length && part [index] == '@') {
348                                 if (state.elements.scheme == Uri.UriSchemeFile) {
349                                         state.error = "Invalid URI: The hostname could not be parsed.";
350                                         return false;
351                                 }
352
353                                 state.elements.user = sb == null ? "" : sb.ToString ();
354                                 state.remaining = state.remaining.Substring (index + 1);
355                         }
356                                 
357                         return state.remaining.Length > 0;
358                 }
359                 
360                 // host        = IP-literal / IPv4address / reg-name
361                 private static bool ParseHost (ParserState state)
362                 {
363                         string part = state.remaining;
364
365                         if (state.elements.scheme == Uri.UriSchemeFile && part.Length >= 2 &&
366                                 (part [0] == '\\' || part [0] == '/') && part [1] == part [0]) {
367                                 part = part.TrimStart (part [0]);
368                                 state.remaining = part;
369                         }
370
371                         if (!ParseWindowsFilePath (state))
372                                 return false;
373
374                         StringBuilder sb = new StringBuilder ();
375                         
376                         var tmpHost = "";
377
378                         var possibleIpv6 = false;
379
380                         int index;
381                         for (index = 0; index < part.Length; index++) { 
382                                 
383                                 char ch = part [index];
384                                 
385                                 if (ch == '/' || ch == '#' || ch == '?')
386                                         break;
387
388                                 // Possible IPv6
389                                 if (string.IsNullOrEmpty (tmpHost) && ch == ':') {
390                                         tmpHost = sb.ToString ();
391                                         possibleIpv6 = true;
392                                 }
393                                 
394                                 sb.Append (ch);
395
396                                 if (possibleIpv6 && ch == ']')
397                                         break;
398                         }
399                         
400                         if (possibleIpv6) {
401                                 IPv6Address ipv6addr;
402                                 if (IPv6Address.TryParse (sb.ToString (), out ipv6addr)) {
403 #if NET_4_5
404                                         var ipStr = ipv6addr.ToString (false);
405 #else
406                                         var ipStr = ipv6addr.ToString (true);
407 #endif
408                                         //remove scope
409                                         ipStr = ipStr.Split ('%') [0];
410
411                                         state.elements.host = "[" + ipStr + "]";
412                                         state.elements.scopeId = ipv6addr.ScopeId;
413
414                                         state.remaining = part.Substring (sb.Length);
415                                         return state.remaining.Length > 0;
416                                 }
417                                 state.elements.host = tmpHost;
418                         } else
419                                 state.elements.host = sb.ToString ();
420
421                         state.elements.host = state.elements.host.ToLowerInvariant ();
422
423                         state.remaining = part.Substring (state.elements.host.Length);
424
425                         if (state.elements.scheme == Uri.UriSchemeFile &&
426                                 state.elements.host != "") {
427                                 // under Windows all file://host URI are considered UNC, which is not the case other MacOS (e.g. Silverlight)
428 #if BOOTSTRAP_BASIC
429                                 state.elements.isUnc = (Path.DirectorySeparatorChar == '\\');
430 #else
431                                 state.elements.isUnc = Environment.IsRunningOnWindows;
432 #endif
433                         }
434
435                         return state.remaining.Length > 0;
436                 }
437                 
438                 // port          = *DIGIT
439                 private static bool ParsePort (ParserState state)
440                 {
441                         string part = state.remaining;
442                         if (part.Length == 0 || part [0] != ':')
443                                 return part.Length > 0;
444                         
445                         StringBuilder sb = new StringBuilder ();
446                         
447                         int index;
448                         for (index = 1; index < part.Length; index++ ) {
449                                 char ch = part [index];
450                                 
451                                 if (!char.IsDigit (ch)) {
452                                         if (ch == '/' || ch == '#' || ch == '?')
453                                                 break;
454
455                                         state.error = "Invalid URI: Invalid port specified.";
456                                         return false;
457                                 }
458                                 
459                                 sb.Append (ch);
460                         }
461
462                         if (index <= part.Length)
463                                 state.remaining = part.Substring (index);
464
465                         if (sb.Length == 0)
466                                 return state.remaining.Length > 0;
467                         
468                         int port;
469                         if (!Int32.TryParse (sb.ToString (), NumberStyles.None, CultureInfo.InvariantCulture, out port) ||
470                                 port < 0 || port > UInt16.MaxValue) {
471                                 state.error = "Invalid URI: Invalid port number";
472                                 return false;
473                         }
474
475                         state.elements.port = port;
476                                 
477                         return state.remaining.Length > 0;
478                 }
479                 
480                 private static bool ParsePath (ParserState state)
481                 {
482                         string part = state.remaining;
483                         StringBuilder sb = new StringBuilder ();
484                         
485                         int index;
486                         for (index = 0; index < part.Length; index++) {
487                                 
488                                 char ch = part [index];
489                                 
490                                 var supportsQuery = UriHelper.SupportsQuery (state.elements.scheme);
491
492                                 if (ch == '#' || (supportsQuery && ch == '?'))
493                                         break;
494                                 
495                                 sb.Append (ch);
496                         }
497                         
498                         if (index <= part.Length)
499                                 state.remaining = part.Substring (index);
500                         
501                         state.elements.path  = sb.ToString ();
502                                 
503                         return state.remaining.Length > 0;
504                 }
505                 
506                 private static bool ParseQuery (ParserState state)
507                 {
508                         string part = state.remaining;
509
510                         if (!UriHelper.SupportsQuery (state.elements.scheme))
511                                 return part.Length > 0;
512                         
513                         if (part.Length == 0 || part [0] != '?')
514                                 return part.Length > 0;
515                         
516                         StringBuilder sb = new StringBuilder ();
517                         
518                         int index;
519                         for (index = 1; index < part.Length; index++) {
520                                 
521                                 char ch = part [index];
522                                 
523                                 if (ch == '#')
524                                         break;
525                                 
526                                 sb.Append (ch);
527                         }
528                         
529                         if (index <= part.Length)
530                                 state.remaining = part.Substring (index);
531                         
532                         state.elements.query  = sb.ToString ();
533                                 
534                         return state.remaining.Length > 0;
535                 }
536                 
537                 private static bool ParseFragment (ParserState state)
538                 {
539                         string part = state.remaining;
540                         
541                         if (part.Length == 0 || part [0] != '#')
542                                 return part.Length > 0;
543                         
544                         StringBuilder sb = new StringBuilder ();
545                         
546                         int index;
547                         for (index = 1; index < part.Length; index++) { 
548                                 
549                                 char ch = part [index];
550                                 
551                                 sb.Append (ch);
552                         }
553                         
554                         state.elements.fragment = sb.ToString ();
555                         
556                         return false;
557                 }
558         }
559 }