[Socket] Move Listen related methods to their region
[mono.git] / mcs / class / System / System / UriHelper.cs
1 using System;
2 using System.Globalization;
3 using System.Text;
4 using System.Collections.Generic;
5
6 namespace System {
7         internal static class UriHelper {
8                 internal const UriFormat ToStringUnescape = (UriFormat) 0x7FFF;
9
10                 internal static bool IriParsing {
11                         get { return Uri.IriParsing; }
12                 }
13
14                 [Flags]
15                 internal enum FormatFlags {
16                         None = 0,
17                         HasComponentCharactersToNormalize = 1 << 0,
18                         HasUriCharactersToNormalize = 1 << 1,
19                         HasHost = 1 << 2,
20                         HasFragmentPercentage = 1 << 3,
21                         UserEscaped = 1 << 4,
22                         IPv6Host = 1 << 5,
23                         NoSlashReplace = 1 << 6,
24                         NoReduce = 1 << 7,
25                         HasWindowsPath = 1 << 8,
26                 }
27
28                 [Flags]
29                 internal enum UriSchemes {
30                         Http = 1 << 0,
31                         Https = 1 << 1,
32                         File = 1 << 2,
33                         Ftp = 1 << 3,
34                         Gopher = 1 << 4,
35                         Ldap = 1 << 5,
36                         Mailto = 1 << 6,
37                         NetPipe = 1 << 7,
38                         NetTcp = 1 << 8,
39                         News = 1 << 9,
40                         Nntp = 1 << 10,
41                         Telnet = 1 << 11,
42                         Uuid = 1 << 12,
43                         Custom = 1 << 13,
44                         CustomWithHost = 1 << 14,
45                         All = ~0,
46                         None = 0
47                 }
48
49                 private static UriSchemes GetScheme (string schemeName)
50                 {
51                         schemeName = schemeName.ToLowerInvariant ();
52
53                         if (schemeName == "")
54                                 return UriSchemes.None;
55                         if (schemeName == Uri.UriSchemeHttp)
56                                 return UriSchemes.Http;
57                         if (schemeName == Uri.UriSchemeHttps)
58                                 return UriSchemes.Https;
59                         if (schemeName == Uri.UriSchemeFile)
60                                 return UriSchemes.File;
61                         if (schemeName == Uri.UriSchemeFtp)
62                                 return UriSchemes.Ftp;
63                         if (schemeName == Uri.UriSchemeGopher)
64                                 return UriSchemes.Gopher;
65                         if (schemeName == Uri.UriSchemeLdap)
66                                 return UriSchemes.Ldap;
67                         if (schemeName == Uri.UriSchemeMailto)
68                                 return UriSchemes.Mailto;
69                         if (schemeName == Uri.UriSchemeNetPipe)
70                                 return UriSchemes.NetPipe;
71                         if (schemeName == Uri.UriSchemeNetTcp)
72                                 return UriSchemes.NetTcp;
73                         if (schemeName == Uri.UriSchemeNews)
74                                 return UriSchemes.News;
75                         if (schemeName == Uri.UriSchemeNntp)
76                                 return UriSchemes.Nntp;
77                         if (schemeName == Uri.UriSchemeTelnet)
78                                 return UriSchemes.Telnet;
79                         if (schemeName == Uri.UriSchemeUuid)
80                                 return UriSchemes.Uuid;
81
82                         return UriSchemes.Custom;
83                 }
84
85                 internal static bool SchemeContains (UriSchemes keys, UriSchemes flag)
86                 {
87                         return (keys & flag) != 0;
88                 }
89
90                 internal static bool IsKnownScheme (string scheme)
91                 {
92                         return GetScheme (scheme) != UriSchemes.Custom;
93                 }
94
95                 internal static string HexEscapeMultiByte (char character)
96                 {
97                         const string hex_upper_chars = "0123456789ABCDEF";
98
99                         var sb = new StringBuilder ();
100                         byte [] bytes = Encoding.UTF8.GetBytes (new [] {character});
101                         foreach (byte b in bytes) {
102                                 sb.Append ("%");
103                                 sb.Append (hex_upper_chars [(b & 0xf0) >> 4]);
104                                 sb.Append (hex_upper_chars [b & 0x0f]);
105                         }
106
107                         return sb.ToString ();
108                 }
109
110                 internal static bool SupportsQuery (string scheme)
111                 {
112                         return SupportsQuery (GetScheme (scheme));
113                 }
114
115                 internal static bool SupportsQuery (UriSchemes scheme)
116                 {
117                         if (SchemeContains (scheme, UriSchemes.File))
118                                 return IriParsing;
119
120                         return !SchemeContains (scheme, UriSchemes.Ftp | UriSchemes.Gopher | UriSchemes.Nntp | UriSchemes.Telnet | UriSchemes.News);
121                 }
122
123                 internal static bool HasCharactersToNormalize (string str)
124                 {
125                         int len = str.Length;
126                         for (int i = 0; i < len; i++) {
127                                 char c = str [i];
128                                 if (c != '%')
129                                         continue;
130
131                                 int iStart = i;
132                                 char surrogate;
133                                 char x = Uri.HexUnescapeMultiByte (str, ref i, out surrogate);
134
135                                 bool isEscaped = i - iStart > 1;
136                                 if (!isEscaped)
137                                         continue;
138
139                                 if ((x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z') || (x >= '0' && x <= '9') || 
140                                          x == '-' || x == '.' || x == '_' || x == '~')
141                                         return true;
142
143                                 if (x > 0x7f)
144                                         return true;
145                         }
146
147                         return false;
148                 }
149
150                 internal static bool HasPercentage (string str)
151                 {
152                         int len = str.Length;
153                         for (int i = 0; i < len; i++) {
154                                 char c = str [i];
155                                 if (c != '%')
156                                         continue;
157
158                                 int iStart = i;
159                                 char surrogate;
160                                 char x = Uri.HexUnescapeMultiByte (str, ref i, out surrogate);
161
162                                 if (x == '%')
163                                         return true;
164
165                                 bool isEscaped = i - iStart > 1;
166                                 if (!isEscaped)
167                                         return true;
168                         }
169
170                         return false;
171                 }
172
173                 internal static string FormatAbsolute (string str, string schemeName,
174                         UriComponents component, UriFormat uriFormat, FormatFlags formatFlags = FormatFlags.None)
175                 {
176                         return Format (str, schemeName, UriKind.Absolute, component, uriFormat, formatFlags);
177                 }
178
179                 internal static string FormatRelative (string str, string schemeName, UriFormat uriFormat)
180                 {
181                         return Format (str, schemeName, UriKind.Relative, UriComponents.Path, uriFormat, FormatFlags.None);
182                 }
183
184                 private static string Format (string str, string schemeName, UriKind uriKind,
185                         UriComponents component, UriFormat uriFormat, FormatFlags formatFlags)
186                 {
187                         if (string.IsNullOrEmpty (str))
188                                 return "";
189
190                         if (UriHelper.HasCharactersToNormalize (str))
191                                 formatFlags |= UriHelper.FormatFlags.HasComponentCharactersToNormalize | FormatFlags.HasUriCharactersToNormalize;
192
193                         if (component == UriComponents.Fragment && UriHelper.HasPercentage (str))
194                                 formatFlags |= UriHelper.FormatFlags.HasFragmentPercentage;
195
196                         if (component == UriComponents.Host &&
197                                 str.Length > 1 && str [0] == '[' && str [str.Length - 1] == ']')
198                                  formatFlags |= UriHelper.FormatFlags.IPv6Host;
199
200                         if (component == UriComponents.Path &&
201                                 str.Length >= 2 && str [1] != ':' &&
202                                 ('a' <= str [0] && str [0] <= 'z') || ('A' <= str [0] && str [0] <= 'Z'))
203                                 formatFlags |= UriHelper.FormatFlags.HasWindowsPath;
204
205                         UriSchemes scheme = GetScheme (schemeName);
206
207                         if (scheme == UriSchemes.Custom && (formatFlags & FormatFlags.HasHost) != 0)
208                                 scheme = UriSchemes.CustomWithHost;
209
210                         var reduceAfter = UriSchemes.Http | UriSchemes.Https | UriSchemes.File | UriSchemes.NetPipe | UriSchemes.NetTcp;
211
212                         if (IriParsing) {
213                                 reduceAfter |= UriSchemes.Ftp;
214                         } else if (component == UriComponents.Path &&
215                                 (formatFlags & FormatFlags.NoSlashReplace) == 0) {
216                                 if (scheme == UriSchemes.Ftp)
217                                         str = Reduce (str.Replace ('\\', '/'), !IriParsing);
218                                 if (scheme == UriSchemes.CustomWithHost)
219                                         str = Reduce (str.Replace ('\\', '/'), false);
220                         }
221
222                         str = FormatString (str, scheme, uriKind, component, uriFormat, formatFlags);
223
224                         if (component == UriComponents.Path &&
225                                 (formatFlags & FormatFlags.NoReduce) == 0) {
226                                 if (SchemeContains (scheme, reduceAfter))
227                                         str = Reduce (str, !IriParsing);
228                                 if (IriParsing && scheme == UriSchemes.CustomWithHost)
229                                         str = Reduce (str, false);
230                         }
231
232                         return str;
233                 }
234
235                 private static string FormatString (string str, UriSchemes scheme, UriKind uriKind,
236                         UriComponents component, UriFormat uriFormat, FormatFlags formatFlags)
237                 {
238                         var s = new StringBuilder ();
239                         int len = str.Length;
240                         for (int i = 0; i < len; i++) {
241                                 char c = str [i];
242                                 if (c == '%') {
243                                         int iStart = i;
244                                         char surrogate;
245                                         bool invalidUnescape;
246                                         char x = Uri.HexUnescapeMultiByte (str, ref i, out surrogate, out invalidUnescape);
247
248
249                                         if (invalidUnescape
250                                         ) {
251                                                 s.Append (c);
252                                                 i = iStart;
253                                                 continue;
254                                         }
255
256                                         string cStr = str.Substring (iStart, i-iStart);
257                                         s.Append (FormatChar (x, surrogate, cStr, scheme, uriKind, component, uriFormat, formatFlags));
258
259                                         i--;
260                                 } else
261                                         s.Append (FormatChar (c, char.MinValue, "" + c, scheme, uriKind, component, uriFormat, formatFlags));
262                         }
263                         
264                         return s.ToString ();
265                 }
266
267                 private static string FormatChar (char c, char surrogate, string cStr, UriSchemes scheme, UriKind uriKind,
268                         UriComponents component, UriFormat uriFormat, FormatFlags formatFlags)
269                 {
270                         var isEscaped = cStr.Length != 1;
271
272                         var userEscaped = (formatFlags & FormatFlags.UserEscaped) != 0;
273                         if (!isEscaped && !userEscaped && NeedToEscape (c, scheme, component, uriKind, uriFormat, formatFlags))
274                                 return HexEscapeMultiByte (c);
275
276                         if (isEscaped && (
277                                 (userEscaped && c < 0xFF) ||
278                                 !NeedToUnescape (c, scheme, component, uriKind, uriFormat, formatFlags))) {
279                                 if (IriParsing &&
280                                         (c == '<' || c == '>' || c == '^' || c == '{' || c == '|' || c ==  '}' || c > 0x7F) &&
281                                         (formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0)
282                                         return cStr.ToUpperInvariant (); //Upper case escape
283
284                                 return cStr; //Keep original case
285                         }
286
287                         if ((formatFlags & FormatFlags.NoSlashReplace) == 0 &&
288                                 c == '\\' && component == UriComponents.Path) {
289                                 if (!IriParsing && uriFormat != UriFormat.UriEscaped &&
290                                         SchemeContains (scheme, UriSchemes.Http | UriSchemes.Https))
291                                         return "/";
292
293                                 if (SchemeContains (scheme, UriSchemes.Http | UriSchemes.Https | UriSchemes.Ftp | UriSchemes.CustomWithHost))
294                                         return (isEscaped && uriFormat != UriFormat.UriEscaped) ? "\\" : "/";
295
296                                 if (SchemeContains (scheme, UriSchemes.NetPipe | UriSchemes.NetTcp | UriSchemes.File))
297                                         return "/";
298
299                                 if (SchemeContains (scheme, UriSchemes.Custom) &&
300                                         (formatFlags & FormatFlags.HasWindowsPath) == 0)
301                                         return "/";
302                         }
303
304                         var ret = c.ToString (CultureInfo.InvariantCulture);
305                         if (surrogate != char.MinValue)
306                                 ret += surrogate.ToString (CultureInfo.InvariantCulture);
307
308                         return ret;
309                 }
310
311                 private static bool NeedToUnescape (char c, UriSchemes scheme, UriComponents component, UriKind uriKind,
312                         UriFormat uriFormat, FormatFlags formatFlags)
313                 {
314                         if ((formatFlags & FormatFlags.IPv6Host) != 0)
315                                 return false;
316
317                         if (uriFormat == UriFormat.Unescaped)
318                                 return true;
319
320                         UriSchemes sDecoders = UriSchemes.NetPipe | UriSchemes.NetTcp;
321
322                         if (!IriParsing)
323                                 sDecoders |= UriSchemes.Http | UriSchemes.Https;
324
325                         if (c == '/' || c == '\\') {
326                                 if (!IriParsing && uriKind == UriKind.Absolute && uriFormat != UriFormat.UriEscaped &&
327                                         uriFormat != UriFormat.SafeUnescaped)
328                                         return true;
329
330                                 if (SchemeContains (scheme, UriSchemes.File)) {
331                                         return component != UriComponents.Fragment &&
332                                                    (component != UriComponents.Query || !IriParsing);
333                                 }
334
335                                 return component != UriComponents.Query && component != UriComponents.Fragment &&
336                                            SchemeContains (scheme, sDecoders);
337                         }
338
339                         if (c == '?') {
340                                 //Avoid creating new query
341                                 if (SupportsQuery (scheme) && component == UriComponents.Path)
342                                         return false;
343
344                                 if (!IriParsing && uriFormat == ToStringUnescape) {
345                                         if (SupportsQuery (scheme))
346                                                 return component == UriComponents.Query || component == UriComponents.Fragment;
347
348                                         return component == UriComponents.Fragment;
349                                 }
350
351                                 return false;
352                         }
353
354                         if (c == '#')
355                                 return false;
356
357                         if (uriFormat == ToStringUnescape && !IriParsing) {
358                                 if (uriKind == UriKind.Relative)
359                                         return false;
360
361                                 switch (c) {
362                                 case '$':
363                                 case '&':
364                                 case '+':
365                                 case ',':
366                                 case ';':
367                                 case '=':
368                                 case '@':
369                                         return true;
370                                 }
371
372                                 if (c < 0x20 || c == 0x7f)
373                                         return true;
374                         }
375
376                         if (uriFormat == UriFormat.SafeUnescaped || uriFormat == ToStringUnescape) {
377                                 switch (c) {
378                                 case '-':
379                                 case '.':
380                                 case '_':
381                                 case '~':
382                                         return true;
383                                 case ' ':
384                                 case '!':
385                                 case '"':
386                                 case '\'':
387                                 case '(':
388                                 case ')':
389                                 case '*':
390                                 case '<':
391                                 case '>':
392                                 case '^':
393                                 case '`':
394                                 case '{':
395                                 case '}':
396                                 case '|':
397                                         return uriKind != UriKind.Relative ||
398                                                 (IriParsing && (formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0);
399                                 case ':':
400                                 case '[':
401                                 case ']':
402                                         return uriKind != UriKind.Relative;
403                                 }
404
405                                 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
406                                         return true;
407
408                                 if (c > 0x7f)
409                                         return true;
410
411                                 return false;
412                         }
413
414                         if (uriFormat == UriFormat.UriEscaped) {
415                                 if (!IriParsing) {
416                                         if (c == '.') {
417                                                 if (SchemeContains (scheme, UriSchemes.File))
418                                                         return component != UriComponents.Fragment;
419
420                                                 return component != UriComponents.Query && component != UriComponents.Fragment &&
421                                                            SchemeContains (scheme, sDecoders);
422                                         }
423
424                                         return false;
425                                 }
426                                 
427                                 switch (c) {
428                                 case '-':
429                                 case '.':
430                                 case '_':
431                                 case '~':
432                                         return true;
433                                 }
434
435                                 if ((formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0) {
436                                         switch (c) {
437                                         case '!':
438                                         case '\'':
439                                         case '(':
440                                         case ')':
441                                         case '*':
442                                         case ':':
443                                         case '[':
444                                         case ']':
445                                                 return true;
446                                         }
447                                 }
448
449                                 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
450                                         return true;
451
452                                 return false;
453                         }
454
455                         return false;
456                 }
457
458                 private static bool NeedToEscape (char c, UriSchemes scheme, UriComponents component, UriKind uriKind,
459                         UriFormat uriFormat, FormatFlags formatFlags)
460                 {
461                         if ((formatFlags & FormatFlags.IPv6Host) != 0)
462                                 return false;
463
464                         if (c == '?') {
465                                 if (uriFormat == UriFormat.Unescaped)
466                                         return false;
467
468                                 if (!SupportsQuery (scheme))
469                                         return component != UriComponents.Fragment;
470
471                                 return false;
472                         }
473
474                         if (c == '#') {
475                                 //Avoid removing fragment
476                                 if (component == UriComponents.Path || component == UriComponents.Query)
477                                         return false;
478
479                                 if (component == UriComponents.Fragment &&
480                                         (uriFormat == ToStringUnescape || uriFormat == UriFormat.SafeUnescaped) &&
481                                         (formatFlags & FormatFlags.HasFragmentPercentage) != 0)
482                                         return true;
483
484                                 return false;
485                         }
486
487                         if (uriFormat == UriFormat.SafeUnescaped || uriFormat == ToStringUnescape) {
488                                 if (c == '%')
489                                         return uriKind != UriKind.Relative;
490                         }
491
492                         if (uriFormat == UriFormat.SafeUnescaped) {
493                                 if (c < 0x20 || c == 0x7F)
494                                         return true;
495                         }
496
497                         if (uriFormat == UriFormat.UriEscaped) {
498                                 if (c < 0x20 || c >= 0x7F)
499                                         return component != UriComponents.Host;
500
501                                 switch (c) {
502                                 case ' ':
503                                 case '"':
504                                 case '%':
505                                 case '<':
506                                 case '>':
507                                 case '^':
508                                 case '`':
509                                 case '{':
510                                 case '}':
511                                 case '|':
512                                         return true;
513                                 case '[':
514                                 case ']':
515                                         return !IriParsing;
516                                 case '\\':
517                                         return component != UriComponents.Path ||
518                                                    SchemeContains (scheme,
519                                                            UriSchemes.Gopher | UriSchemes.Ldap | UriSchemes.Mailto | UriSchemes.Nntp |
520                                                            UriSchemes.Telnet | UriSchemes.News | UriSchemes.Custom);
521                                 }
522                         }
523
524                         return false;
525                 }
526
527                 // This is called "compacting" in the MSDN documentation
528                 internal static string Reduce (string path, bool trimDots)
529                 {
530                         // quick out, allocation-free, for a common case
531                         if (path == "/")
532                                 return path;
533
534                         bool endWithSlash = false;
535
536                         List<string> result = new List<string> ();
537
538                         string[] segments = path.Split ('/');
539                         int lastSegmentIndex = segments.Length - 1;
540                         for (var i = 0; i <= lastSegmentIndex; i++) {
541                                 string segment = segments [i];
542
543                                 if (i == lastSegmentIndex &&
544                                         (segment.Length == 0 || segment == ".." || segment == "."))
545                                         endWithSlash = true;
546
547                                 if ((i == 0 || i == lastSegmentIndex) && segment.Length == 0)
548                                         continue;
549
550                                 if (segment == "..") {
551                                         int resultCount = result.Count;
552                                         // in 2.0 profile, skip leading ".." parts
553                                         if (resultCount == 0)
554                                                 continue;
555
556                                         result.RemoveAt (resultCount - 1);
557                                         continue;
558                                 }
559
560                                 if (segment == "." ||
561                                         (trimDots && segment.EndsWith (".", StringComparison.Ordinal))) {
562                                         segment = segment.TrimEnd ('.');
563                                         if (segment == "" && i < lastSegmentIndex)
564                                                 continue;
565                                 }
566
567                                 endWithSlash = false;
568
569                                 result.Add (segment);
570                         }
571
572                         if (result.Count == 0)
573                                 return "/";
574
575                         StringBuilder res = new StringBuilder ();
576
577                         if (path [0] == '/')
578                                 res.Append ('/');
579
580                         bool first = true;
581                         foreach (string part in result) {
582                                 if (first) {
583                                         first = false;
584                                 } else {
585                                         res.Append ('/');
586                                 }
587                                 res.Append (part);
588                         }
589
590                         if (path [path.Length - 1] == '/' || endWithSlash)
591                                 res.Append ('/');
592                                 
593                         return res.ToString ();
594                 }
595         }
596 }