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));
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)
366 if (c == '$' || c == '&' || c == '+' || c == ',' || c == ';' || c == '=' || c == '@')
369 if (c < 0x20 || c == 0x7f)
373 if (uriFormat == UriFormat.SafeUnescaped || uriFormat == ToStringUnescape) {
374 if (c == '-' || c == '.' || c == '_' || c == '~')
377 if (c == ' ' || c == '!' || c == '"' || c == '\'' || c == '(' || c == ')' || c == '*' ||
378 c == '<' || c == '>' || c == '^' || c == '`' || c == '{' || c == '}' || c == '|')
379 return uriKind != UriKind.Relative ||
380 (IriParsing && (formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0);
382 if (c == ':' || c == '[' || c == ']')
383 return uriKind != UriKind.Relative;
385 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
394 if (uriFormat == UriFormat.UriEscaped) {
397 if (SchemeContains (scheme, UriSchemes.File))
398 return component != UriComponents.Fragment;
400 return component != UriComponents.Query && component != UriComponents.Fragment &&
401 SchemeContains (scheme, sDecoders);
407 if (c == '-' || c == '.' || c == '_' || c == '~')
410 if ((formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0 &&
411 (c == '!' || c == '\'' || c == '(' || c == ')' || c == '*' ||
412 c == ':' || c == '[' || c == ']'))
415 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
424 private static bool NeedToEscape (char c, UriSchemes scheme, UriComponents component, UriKind uriKind,
425 UriFormat uriFormat, FormatFlags formatFlags)
427 if ((formatFlags & FormatFlags.IPv6Host) != 0)
431 if (uriFormat == UriFormat.Unescaped)
434 if (!SupportsQuery (scheme))
435 return component != UriComponents.Fragment;
441 //Avoid removing fragment
442 if (component == UriComponents.Path || component == UriComponents.Query)
445 if (component == UriComponents.Fragment &&
446 (uriFormat == ToStringUnescape || uriFormat == UriFormat.SafeUnescaped) &&
447 (formatFlags & FormatFlags.HasFragmentPercentage) != 0)
453 return uriFormat == UriFormat.UriEscaped ||
454 (uriFormat != UriFormat.Unescaped && (formatFlags & FormatFlags.HasComponentCharactersToNormalize) != 0);
457 if (uriFormat == UriFormat.SafeUnescaped || uriFormat == ToStringUnescape) {
459 return uriKind != UriKind.Relative;
462 if (uriFormat == UriFormat.SafeUnescaped) {
463 if (c < 0x20 || c == 0x7F)
467 if (uriFormat == UriFormat.UriEscaped) {
468 if (c < 0x20 || c >= 0x7F)
469 return component != UriComponents.Host;
471 if (c == ' ' || c == '"' || c == '%' || c == '<' || c == '>' || c == '^' ||
472 c == '`' || c == '{' || c == '}' || c == '|')
475 if (c == '[' || c == ']')
479 return component != UriComponents.Path ||
480 SchemeContains (scheme,
481 UriSchemes.Gopher | UriSchemes.Ldap | UriSchemes.Mailto | UriSchemes.Nntp |
482 UriSchemes.Telnet | UriSchemes.News | UriSchemes.Custom);
489 // This is called "compacting" in the MSDN documentation
490 internal static string Reduce (string path, bool trimDots)
492 // quick out, allocation-free, for a common case
496 bool endWithSlash = false;
498 List<string> result = new List<string> ();
501 for (int startpos = 0; startpos < path.Length; ) {
504 int endpos = path.IndexOf ('/', startpos);
506 endpos = path.Length;
507 string current = path.Substring (startpos, endpos-startpos);
508 startpos = endpos + 1;
509 if (begin && current.Length == 0) {
515 if (current == "..") {
516 int resultCount = result.Count;
517 // in 2.0 profile, skip leading ".." parts
518 if (resultCount == 0) {
522 result.RemoveAt (resultCount - 1);
526 if (current == "." ||
527 (trimDots && current.EndsWith("."))) {
528 current = current.TrimEnd('.');
529 if (current == "" && endpos < path.Length)
533 endWithSlash = false;
535 result.Add (current);
538 if (result.Count == 0)
541 StringBuilder res = new StringBuilder ();
547 foreach (string part in result) {
556 if (path [path.Length - 1] == '/' || endWithSlash)
559 return res.ToString();