2 using System.Globalization;
4 using System.Collections.Generic;
7 internal static class UriHelper {
8 internal const UriFormat ToStringUnescape = (UriFormat) 0x7FFF;
10 internal static bool IriParsing {
11 get { return Uri.IriParsing; }
15 internal enum FormatFlags {
17 HasComponentCharactersToNormalize = 1 << 0,
18 HasUriCharactersToNormalize = 1 << 1,
20 HasFragmentPercentage = 1 << 3,
23 NoSlashReplace = 1 << 6,
25 HasWindowsPath = 1 << 8,
29 internal enum UriSchemes {
44 CustomWithHost = 1 << 14,
49 private static UriSchemes GetScheme (string schemeName)
51 schemeName = schemeName.ToLowerInvariant ();
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;
82 return UriSchemes.Custom;
85 internal static bool SchemeContains (UriSchemes keys, UriSchemes flag)
87 return (keys & flag) != 0;
90 internal static bool IsKnownScheme (string scheme)
92 return GetScheme (scheme) != UriSchemes.Custom;
95 internal static string HexEscapeMultiByte (char character)
97 const string hex_upper_chars = "0123456789ABCDEF";
99 var sb = new StringBuilder ();
100 byte [] bytes = Encoding.UTF8.GetBytes (new [] {character});
101 foreach (byte b in bytes) {
103 sb.Append (hex_upper_chars [(b & 0xf0) >> 4]);
104 sb.Append (hex_upper_chars [b & 0x0f]);
107 return sb.ToString ();
110 internal static bool SupportsQuery (string scheme)
112 return SupportsQuery (GetScheme (scheme));
115 internal static bool SupportsQuery (UriSchemes scheme)
117 if (SchemeContains (scheme, UriSchemes.File))
120 return !SchemeContains (scheme, UriSchemes.Ftp | UriSchemes.Gopher | UriSchemes.Nntp | UriSchemes.Telnet | UriSchemes.News);
123 internal static bool HasCharactersToNormalize (string str)
125 int len = str.Length;
126 for (int i = 0; i < len; i++) {
133 char x = Uri.HexUnescapeMultiByte (str, ref i, out surrogate);
135 bool isEscaped = i - iStart > 1;
139 if ((x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z') || (x >= '0' && x <= '9') ||
140 x == '-' || x == '.' || x == '_' || x == '~')
150 internal static bool HasPercentage (string str)
152 int len = str.Length;
153 for (int i = 0; i < len; i++) {
160 char x = Uri.HexUnescapeMultiByte (str, ref i, out surrogate);
165 bool isEscaped = i - iStart > 1;
173 internal static string FormatAbsolute (string str, string schemeName,
174 UriComponents component, UriFormat uriFormat, FormatFlags formatFlags = FormatFlags.None)
176 return Format (str, schemeName, UriKind.Absolute, component, uriFormat, formatFlags);
179 internal static string FormatRelative (string str, string schemeName, UriFormat uriFormat)
181 return Format (str, schemeName, UriKind.Relative, UriComponents.Path, uriFormat, FormatFlags.None);
184 private static string Format (string str, string schemeName, UriKind uriKind,
185 UriComponents component, UriFormat uriFormat, FormatFlags formatFlags)
187 if (string.IsNullOrEmpty (str))
190 if (UriHelper.HasCharactersToNormalize (str))
191 formatFlags |= UriHelper.FormatFlags.HasComponentCharactersToNormalize | FormatFlags.HasUriCharactersToNormalize;
193 if (component == UriComponents.Fragment && UriHelper.HasPercentage (str))
194 formatFlags |= UriHelper.FormatFlags.HasFragmentPercentage;
196 if (component == UriComponents.Host &&
197 str.Length > 1 && str [0] == '[' && str [str.Length - 1] == ']')
198 formatFlags |= UriHelper.FormatFlags.IPv6Host;
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;
205 UriSchemes scheme = GetScheme (schemeName);
207 if (scheme == UriSchemes.Custom && (formatFlags & FormatFlags.HasHost) != 0)
208 scheme = UriSchemes.CustomWithHost;
210 var reduceAfter = UriSchemes.Http | UriSchemes.Https | UriSchemes.File | UriSchemes.NetPipe | UriSchemes.NetTcp;
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);
222 str = FormatString (str, scheme, uriKind, component, uriFormat, formatFlags);
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);
235 private static string FormatString (string str, UriSchemes scheme, UriKind uriKind,
236 UriComponents component, UriFormat uriFormat, FormatFlags formatFlags)
238 var s = new StringBuilder ();
239 int len = str.Length;
240 for (int i = 0; i < len; i++) {
245 bool invalidUnescape;
246 char x = Uri.HexUnescapeMultiByte (str, ref i, out surrogate, out invalidUnescape);
251 && uriFormat == UriFormat.SafeUnescaped && char.IsControl (x)
259 string cStr = str.Substring (iStart, i-iStart);
260 s.Append (FormatChar (x, surrogate, cStr, scheme, uriKind, component, uriFormat, formatFlags));
264 s.Append (FormatChar (c, char.MinValue, "" + c, scheme, uriKind, component, uriFormat, formatFlags));
267 return s.ToString ();
270 private static string FormatChar (char c, char surrogate, string cStr, UriSchemes scheme, UriKind uriKind,
271 UriComponents component, UriFormat uriFormat, FormatFlags formatFlags)
273 var isEscaped = cStr.Length != 1;
275 var userEscaped = (formatFlags & FormatFlags.UserEscaped) != 0;
276 if (!isEscaped && !userEscaped && NeedToEscape (c, scheme, component, uriKind, uriFormat, formatFlags))
277 return HexEscapeMultiByte (c);
281 (userEscaped && c < 0xFF) ||
283 !NeedToUnescape (c, scheme, component, uriKind, uriFormat, formatFlags))) {
285 (c == '<' || c == '>' || c == '^' || c == '{' || c == '|' || c == '}' || c > 0x7F) &&
286 (formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0)
287 return cStr.ToUpperInvariant (); //Upper case escape
289 return cStr; //Keep original case
292 if ((formatFlags & FormatFlags.NoSlashReplace) == 0 &&
293 c == '\\' && component == UriComponents.Path) {
294 if (!IriParsing && uriFormat != UriFormat.UriEscaped &&
295 SchemeContains (scheme, UriSchemes.Http | UriSchemes.Https))
298 if (SchemeContains (scheme, UriSchemes.Http | UriSchemes.Https | UriSchemes.Ftp | UriSchemes.CustomWithHost))
299 return (isEscaped && uriFormat != UriFormat.UriEscaped) ? "\\" : "/";
301 if (SchemeContains (scheme, UriSchemes.NetPipe | UriSchemes.NetTcp | UriSchemes.File))
304 if (SchemeContains (scheme, UriSchemes.Custom) &&
305 (formatFlags & FormatFlags.HasWindowsPath) == 0)
309 var ret = c.ToString (CultureInfo.InvariantCulture);
310 if (surrogate != char.MinValue)
311 ret += surrogate.ToString (CultureInfo.InvariantCulture);
316 private static bool NeedToUnescape (char c, UriSchemes scheme, UriComponents component, UriKind uriKind,
317 UriFormat uriFormat, FormatFlags formatFlags)
319 if ((formatFlags & FormatFlags.IPv6Host) != 0)
322 if (uriFormat == UriFormat.Unescaped)
325 UriSchemes sDecoders = UriSchemes.NetPipe | UriSchemes.NetTcp;
328 sDecoders |= UriSchemes.Http | UriSchemes.Https;
330 if (c == '/' || c == '\\') {
331 if (!IriParsing && uriKind == UriKind.Absolute && uriFormat != UriFormat.UriEscaped &&
332 uriFormat != UriFormat.SafeUnescaped)
335 if (SchemeContains (scheme, UriSchemes.File)) {
336 return component != UriComponents.Fragment &&
337 (component != UriComponents.Query || !IriParsing);
340 return component != UriComponents.Query && component != UriComponents.Fragment &&
341 SchemeContains (scheme, sDecoders);
345 //Avoid creating new query
346 if (SupportsQuery (scheme) && component == UriComponents.Path)
349 if (!IriParsing && uriFormat == ToStringUnescape) {
350 if (SupportsQuery (scheme))
351 return component == UriComponents.Query || component == UriComponents.Fragment;
353 return component == UriComponents.Fragment;
362 if (uriFormat == ToStringUnescape && !IriParsing) {
363 if (uriKind == UriKind.Relative)
377 if (c < 0x20 || c == 0x7f)
381 if (uriFormat == UriFormat.SafeUnescaped || uriFormat == ToStringUnescape) {
402 return uriKind != UriKind.Relative ||
403 (IriParsing && (formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0);
407 return uriKind != UriKind.Relative;
410 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
419 if (uriFormat == UriFormat.UriEscaped) {
422 if (SchemeContains (scheme, UriSchemes.File))
423 return component != UriComponents.Fragment;
425 return component != UriComponents.Query && component != UriComponents.Fragment &&
426 SchemeContains (scheme, sDecoders);
440 if ((formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0) {
454 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
463 private static bool NeedToEscape (char c, UriSchemes scheme, UriComponents component, UriKind uriKind,
464 UriFormat uriFormat, FormatFlags formatFlags)
466 if ((formatFlags & FormatFlags.IPv6Host) != 0)
470 if (uriFormat == UriFormat.Unescaped)
473 if (!SupportsQuery (scheme))
474 return component != UriComponents.Fragment;
480 //Avoid removing fragment
481 if (component == UriComponents.Path || component == UriComponents.Query)
484 if (component == UriComponents.Fragment &&
485 (uriFormat == ToStringUnescape || uriFormat == UriFormat.SafeUnescaped) &&
486 (formatFlags & FormatFlags.HasFragmentPercentage) != 0)
492 return uriFormat == UriFormat.UriEscaped ||
493 (uriFormat != UriFormat.Unescaped && (formatFlags & FormatFlags.HasComponentCharactersToNormalize) != 0);
497 if (uriFormat == UriFormat.SafeUnescaped || uriFormat == ToStringUnescape) {
499 return uriKind != UriKind.Relative;
502 if (uriFormat == UriFormat.SafeUnescaped) {
503 if (c < 0x20 || c == 0x7F)
507 if (uriFormat == UriFormat.UriEscaped) {
508 if (c < 0x20 || c >= 0x7F)
509 return component != UriComponents.Host;
527 return component != UriComponents.Path ||
528 SchemeContains (scheme,
529 UriSchemes.Gopher | UriSchemes.Ldap | UriSchemes.Mailto | UriSchemes.Nntp |
530 UriSchemes.Telnet | UriSchemes.News | UriSchemes.Custom);
537 // This is called "compacting" in the MSDN documentation
538 internal static string Reduce (string path, bool trimDots)
540 // quick out, allocation-free, for a common case
544 bool endWithSlash = false;
546 List<string> result = new List<string> ();
548 string[] segments = path.Split ('/');
549 int lastSegmentIndex = segments.Length - 1;
550 for (var i = 0; i <= lastSegmentIndex; i++) {
551 string segment = segments [i];
553 if (i == lastSegmentIndex &&
554 (segment.Length == 0 || segment == ".." || segment == "."))
557 if ((i == 0 || i == lastSegmentIndex) && segment.Length == 0)
560 if (segment == "..") {
561 int resultCount = result.Count;
562 // in 2.0 profile, skip leading ".." parts
563 if (resultCount == 0)
566 result.RemoveAt (resultCount - 1);
570 if (segment == "." ||
571 (trimDots && segment.EndsWith (".", StringComparison.Ordinal))) {
572 segment = segment.TrimEnd ('.');
573 if (segment == "" && i < lastSegmentIndex)
577 endWithSlash = false;
579 result.Add (segment);
582 if (result.Count == 0)
585 StringBuilder res = new StringBuilder ();
591 foreach (string part in result) {
600 if (path [path.Length - 1] == '/' || endWithSlash)
603 return res.ToString ();