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);
256 string cStr = str.Substring (iStart, i-iStart);
257 s.Append (FormatChar (x, surrogate, cStr, scheme, uriKind, component, uriFormat, formatFlags));
261 s.Append (FormatChar (c, char.MinValue, "" + c, scheme, uriKind, component, uriFormat, formatFlags));
264 return s.ToString ();
267 private static string FormatChar (char c, char surrogate, string cStr, UriSchemes scheme, UriKind uriKind,
268 UriComponents component, UriFormat uriFormat, FormatFlags formatFlags)
270 var isEscaped = cStr.Length != 1;
272 var userEscaped = (formatFlags & FormatFlags.UserEscaped) != 0;
273 if (!isEscaped && !userEscaped && NeedToEscape (c, scheme, component, uriKind, uriFormat, formatFlags))
274 return HexEscapeMultiByte (c);
277 (userEscaped && c < 0xFF) ||
278 !NeedToUnescape (c, scheme, component, uriKind, uriFormat, formatFlags))) {
280 (c == '<' || c == '>' || c == '^' || c == '{' || c == '|' || c == '}' || c > 0x7F) &&
281 (formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0)
282 return cStr.ToUpperInvariant (); //Upper case escape
284 return cStr; //Keep original case
287 if ((formatFlags & FormatFlags.NoSlashReplace) == 0 &&
288 c == '\\' && component == UriComponents.Path) {
289 if (!IriParsing && uriFormat != UriFormat.UriEscaped &&
290 SchemeContains (scheme, UriSchemes.Http | UriSchemes.Https))
293 if (SchemeContains (scheme, UriSchemes.Http | UriSchemes.Https | UriSchemes.Ftp | UriSchemes.CustomWithHost))
294 return (isEscaped && uriFormat != UriFormat.UriEscaped) ? "\\" : "/";
296 if (SchemeContains (scheme, UriSchemes.NetPipe | UriSchemes.NetTcp | UriSchemes.File))
299 if (SchemeContains (scheme, UriSchemes.Custom) &&
300 (formatFlags & FormatFlags.HasWindowsPath) == 0)
304 var ret = c.ToString (CultureInfo.InvariantCulture);
305 if (surrogate != char.MinValue)
306 ret += surrogate.ToString (CultureInfo.InvariantCulture);
311 private static bool NeedToUnescape (char c, UriSchemes scheme, UriComponents component, UriKind uriKind,
312 UriFormat uriFormat, FormatFlags formatFlags)
314 if ((formatFlags & FormatFlags.IPv6Host) != 0)
317 if (uriFormat == UriFormat.Unescaped)
320 UriSchemes sDecoders = UriSchemes.NetPipe | UriSchemes.NetTcp;
323 sDecoders |= UriSchemes.Http | UriSchemes.Https;
325 if (c == '/' || c == '\\') {
326 if (!IriParsing && uriKind == UriKind.Absolute && uriFormat != UriFormat.UriEscaped &&
327 uriFormat != UriFormat.SafeUnescaped)
330 if (SchemeContains (scheme, UriSchemes.File)) {
331 return component != UriComponents.Fragment &&
332 (component != UriComponents.Query || !IriParsing);
335 return component != UriComponents.Query && component != UriComponents.Fragment &&
336 SchemeContains (scheme, sDecoders);
340 //Avoid creating new query
341 if (SupportsQuery (scheme) && component == UriComponents.Path)
344 if (!IriParsing && uriFormat == ToStringUnescape) {
345 if (SupportsQuery (scheme))
346 return component == UriComponents.Query || component == UriComponents.Fragment;
348 return component == UriComponents.Fragment;
357 if (uriFormat == ToStringUnescape && !IriParsing) {
358 if (uriKind == UriKind.Relative)
372 if (c < 0x20 || c == 0x7f)
376 if (uriFormat == UriFormat.SafeUnescaped || uriFormat == ToStringUnescape) {
397 return uriKind != UriKind.Relative ||
398 (IriParsing && (formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0);
402 return uriKind != UriKind.Relative;
405 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
414 if (uriFormat == UriFormat.UriEscaped) {
417 if (SchemeContains (scheme, UriSchemes.File))
418 return component != UriComponents.Fragment;
420 return component != UriComponents.Query && component != UriComponents.Fragment &&
421 SchemeContains (scheme, sDecoders);
435 if ((formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0) {
449 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
458 private static bool NeedToEscape (char c, UriSchemes scheme, UriComponents component, UriKind uriKind,
459 UriFormat uriFormat, FormatFlags formatFlags)
461 if ((formatFlags & FormatFlags.IPv6Host) != 0)
465 if (uriFormat == UriFormat.Unescaped)
468 if (!SupportsQuery (scheme))
469 return component != UriComponents.Fragment;
475 //Avoid removing fragment
476 if (component == UriComponents.Path || component == UriComponents.Query)
479 if (component == UriComponents.Fragment &&
480 (uriFormat == ToStringUnescape || uriFormat == UriFormat.SafeUnescaped) &&
481 (formatFlags & FormatFlags.HasFragmentPercentage) != 0)
487 if (uriFormat == UriFormat.SafeUnescaped || uriFormat == ToStringUnescape) {
489 return uriKind != UriKind.Relative;
492 if (uriFormat == UriFormat.SafeUnescaped) {
493 if (c < 0x20 || c == 0x7F)
497 if (uriFormat == UriFormat.UriEscaped) {
498 if (c < 0x20 || c >= 0x7F)
499 return component != UriComponents.Host;
517 return component != UriComponents.Path ||
518 SchemeContains (scheme,
519 UriSchemes.Gopher | UriSchemes.Ldap | UriSchemes.Mailto | UriSchemes.Nntp |
520 UriSchemes.Telnet | UriSchemes.News | UriSchemes.Custom);
527 // This is called "compacting" in the MSDN documentation
528 internal static string Reduce (string path, bool trimDots)
530 // quick out, allocation-free, for a common case
534 bool endWithSlash = false;
536 List<string> result = new List<string> ();
538 string[] segments = path.Split ('/');
539 int lastSegmentIndex = segments.Length - 1;
540 for (var i = 0; i <= lastSegmentIndex; i++) {
541 string segment = segments [i];
543 if (i == lastSegmentIndex &&
544 (segment.Length == 0 || segment == ".." || segment == "."))
547 if ((i == 0 || i == lastSegmentIndex) && segment.Length == 0)
550 if (segment == "..") {
551 int resultCount = result.Count;
552 // in 2.0 profile, skip leading ".." parts
553 if (resultCount == 0)
556 result.RemoveAt (resultCount - 1);
560 if (segment == "." ||
561 (trimDots && segment.EndsWith (".", StringComparison.Ordinal))) {
562 segment = segment.TrimEnd ('.');
563 if (segment == "" && i < lastSegmentIndex)
567 endWithSlash = false;
569 result.Add (segment);
572 if (result.Count == 0)
575 StringBuilder res = new StringBuilder ();
581 foreach (string part in result) {
590 if (path [path.Length - 1] == '/' || endWithSlash)
593 return res.ToString ();