2 using System.Globalization;
4 using System.Collections.Generic;
7 internal 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,
23 internal enum UriSchemes {
38 CustomWithHost = 1 << 14,
43 private static UriSchemes GetScheme (string schemeName)
46 return UriSchemes.None;
47 if (schemeName == Uri.UriSchemeHttp)
48 return UriSchemes.Http;
49 if (schemeName == Uri.UriSchemeHttps)
50 return UriSchemes.Https;
51 if (schemeName == Uri.UriSchemeFile)
52 return UriSchemes.File;
53 if (schemeName == Uri.UriSchemeFtp)
54 return UriSchemes.Ftp;
55 if (schemeName == Uri.UriSchemeGopher)
56 return UriSchemes.Gopher;
57 if (schemeName == Uri.UriSchemeLdap)
58 return UriSchemes.Ldap;
59 if (schemeName == Uri.UriSchemeMailto)
60 return UriSchemes.Mailto;
61 if (schemeName == Uri.UriSchemeNetPipe)
62 return UriSchemes.NetPipe;
63 if (schemeName == Uri.UriSchemeNetTcp)
64 return UriSchemes.NetTcp;
65 if (schemeName == Uri.UriSchemeNews)
66 return UriSchemes.News;
67 if (schemeName == Uri.UriSchemeNntp)
68 return UriSchemes.Nntp;
69 if (schemeName == Uri.UriSchemeTelnet)
70 return UriSchemes.Telnet;
71 if (schemeName == Uri.UriSchemeUuid)
72 return UriSchemes.Uuid;
74 return UriSchemes.Custom;
77 internal static bool SchemeContains (UriSchemes keys, UriSchemes flag)
79 return (keys & flag) != 0;
82 internal static bool IsKnownScheme(string scheme)
84 return GetScheme(scheme) != UriSchemes.Custom;
87 internal static string HexEscapeMultiByte (char character)
89 const string hex_upper_chars = "0123456789ABCDEF";
91 byte [] bytes = Encoding.UTF8.GetBytes (new [] {character});
92 foreach (byte b in bytes)
93 ret += "%" + hex_upper_chars [((b & 0xf0) >> 4)] + hex_upper_chars [((b & 0x0f))];
98 internal static bool SupportsQuery (string scheme)
100 return SupportsQuery (GetScheme (scheme));
103 internal static bool SupportsQuery(UriSchemes scheme)
105 if (SchemeContains (scheme, UriSchemes.File))
108 return !SchemeContains (scheme, UriSchemes.Ftp | UriSchemes.Gopher | UriSchemes.Nntp | UriSchemes.Telnet | UriSchemes.News);
111 internal static bool HasCharactersToNormalize(string str)
113 int len = str.Length;
114 for (int i = 0; i < len; i++) {
121 char x = Uri.HexUnescapeMultiByte (str, ref i, out surrogate);
123 bool isEscaped = i - iStart > 1;
127 if ((x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z') || (x >= '0' && x <= '9') ||
128 x == '-' || x == '.' || x == '_' || x == '~')
138 internal static string FormatAbsolute (string str, string schemeName,
139 UriComponents component, UriFormat uriFormat, FormatFlags formatFlags = FormatFlags.None)
141 return Format (str, schemeName, UriKind.Absolute, component, uriFormat, formatFlags);
144 internal static string FormatRelative (string str, string schemeName, UriFormat uriFormat)
146 var formatFlags = FormatFlags.None;
147 if (HasCharactersToNormalize (str))
148 formatFlags |= FormatFlags.HasUriCharactersToNormalize;
150 return Format (str, schemeName, UriKind.Relative, UriComponents.Path, uriFormat, formatFlags);
153 private static string Format (string str, string schemeName, UriKind uriKind,
154 UriComponents component, UriFormat uriFormat, FormatFlags formatFlags)
156 if (string.IsNullOrEmpty (str))
159 if (UriHelper.HasCharactersToNormalize (str))
160 formatFlags |= UriHelper.FormatFlags.HasComponentCharactersToNormalize;
162 UriSchemes scheme = GetScheme (schemeName);
164 if (scheme == UriSchemes.Custom && (formatFlags & FormatFlags.HasHost) != 0)
165 scheme = UriSchemes.CustomWithHost;
167 var reduceAfter = UriSchemes.Http | UriSchemes.Https | UriSchemes.File | UriSchemes.NetPipe | UriSchemes.NetTcp;
170 reduceAfter |= UriSchemes.Ftp;
171 } else if (component == UriComponents.Path) {
172 if(scheme == UriSchemes.Ftp)
173 str = Reduce (str.Replace ('\\', '/'), !IriParsing);
174 if (scheme == UriSchemes.CustomWithHost)
175 str = Reduce (str.Replace ('\\', '/'), false);
178 str = FormatString (str, scheme, uriKind, component, uriFormat, formatFlags);
180 if (component == UriComponents.Path) {
181 if (SchemeContains (scheme, reduceAfter))
182 str = Reduce (str, !IriParsing);
183 if(IriParsing && scheme == UriSchemes.CustomWithHost)
184 str = Reduce (str, false);
190 private static string FormatString (string str, UriSchemes scheme, UriKind uriKind,
191 UriComponents component, UriFormat uriFormat, FormatFlags formatFlags)
193 var s = new StringBuilder ();
194 int len = str.Length;
195 for (int i = 0; i < len; i++) {
200 char x = Uri.HexUnescapeMultiByte (str, ref i, out surrogate);
202 bool isEscaped = i - iStart > 1;
203 s.Append (FormatChar (x, isEscaped, scheme, uriKind, component, uriFormat, formatFlags));
204 if (surrogate != char.MinValue)
205 s.Append (surrogate);
209 s.Append (FormatChar (c, false, scheme, uriKind, component, uriFormat, formatFlags));
215 private static string FormatChar (char c, bool isEscaped, UriSchemes scheme, UriKind uriKind,
216 UriComponents component, UriFormat uriFormat, FormatFlags formatFlags)
218 if (!isEscaped && NeedToEscape (c, scheme, component, uriKind, uriFormat, formatFlags) ||
219 isEscaped && !NeedToUnescape (c, scheme, component, uriKind, uriFormat, formatFlags))
220 return HexEscapeMultiByte (c);
222 if (c == '\\' && component == UriComponents.Path) {
223 if (!IriParsing && uriFormat != UriFormat.UriEscaped &&
224 SchemeContains (scheme, UriSchemes.Http | UriSchemes.Https))
227 if (SchemeContains (scheme, UriSchemes.Http | UriSchemes.Https | UriSchemes.Ftp | UriSchemes.CustomWithHost))
228 return (isEscaped && uriFormat != UriFormat.UriEscaped) ? "\\" : "/";
230 if (SchemeContains (scheme, UriSchemes.NetPipe | UriSchemes.NetTcp | UriSchemes.File))
234 return c.ToString (CultureInfo.InvariantCulture);
237 private static bool NeedToUnescape (char c, UriSchemes scheme, UriComponents component, UriKind uriKind,
238 UriFormat uriFormat, FormatFlags formatFlags)
240 string cStr = c.ToString (CultureInfo.InvariantCulture);
242 if (uriFormat == UriFormat.Unescaped)
245 UriSchemes sDecoders = UriSchemes.NetPipe | UriSchemes.NetTcp;
248 sDecoders |= UriSchemes.Http | UriSchemes.Https;
250 if (c == '/' || c == '\\') {
251 if (!IriParsing && uriKind == UriKind.Absolute && uriFormat != UriFormat.UriEscaped &&
252 uriFormat != UriFormat.SafeUnescaped)
255 if (SchemeContains (scheme, UriSchemes.File)) {
256 return component != UriComponents.Fragment &&
257 (component != UriComponents.Query || !IriParsing);
260 return component != UriComponents.Query && component != UriComponents.Fragment &&
261 SchemeContains (scheme, sDecoders);
265 //Avoid creating new query
266 if (SupportsQuery (scheme) && component == UriComponents.Path)
269 if (!IriParsing && uriFormat == ToStringUnescape) {
270 if (SupportsQuery (scheme))
271 return component == UriComponents.Query || component == UriComponents.Fragment;
273 return component == UriComponents.Fragment;
280 //Avoid creating new fragment
281 if (component == UriComponents.Path || component == UriComponents.Query)
287 if (uriFormat == ToStringUnescape && !IriParsing) {
288 if (uriKind == UriKind.Relative)
291 if ("$&+,;=@".Contains (cStr))
294 if (c < 0x20 || c == 0x7f)
298 if (uriFormat == UriFormat.SafeUnescaped || uriFormat == ToStringUnescape) {
299 if ("-._~".Contains (cStr))
302 if (" !\"'()*<>^`{}|".Contains (cStr))
303 return uriKind != UriKind.Relative ||
304 (IriParsing && (formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0);
306 if (":[]".Contains (cStr))
307 return uriKind != UriKind.Relative;
309 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
318 if (uriFormat == UriFormat.UriEscaped) {
320 if (".".Contains (cStr)) {
321 if (SchemeContains (scheme, UriSchemes.File))
322 return component != UriComponents.Fragment;
324 return component != UriComponents.Query && component != UriComponents.Fragment &&
325 SchemeContains (scheme, sDecoders);
331 if ("-._~".Contains (cStr))
334 if ((formatFlags & FormatFlags.HasUriCharactersToNormalize) != 0 &&
335 "!'()*:[]".Contains (cStr))
338 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
347 private static bool NeedToEscape (char c, UriSchemes scheme, UriComponents component, UriKind uriKind,
348 UriFormat uriFormat, FormatFlags formatFlags)
350 string cStr = c.ToString (CultureInfo.InvariantCulture);
353 if (uriFormat == UriFormat.Unescaped)
356 if (!SupportsQuery (scheme))
357 return component != UriComponents.Fragment;
359 //Avoid removing query
360 if (component == UriComponents.Path)
367 //Avoid removing fragment
368 if (component == UriComponents.Path || component == UriComponents.Query)
374 return uriFormat == UriFormat.UriEscaped ||
375 (uriFormat != UriFormat.Unescaped && (formatFlags & FormatFlags.HasComponentCharactersToNormalize) != 0);
378 if (uriFormat == UriFormat.SafeUnescaped || uriFormat == ToStringUnescape) {
379 if ("%".Contains (cStr))
380 return uriKind != UriKind.Relative;
383 if (uriFormat == UriFormat.SafeUnescaped) {
384 if (c < 0x20 || c == 0x7F)
388 if (uriFormat == UriFormat.UriEscaped) {
389 if (c < 0x20 || c >= 0x7F)
392 if (" \"%<>^`{}|".Contains (cStr))
395 if ("[]".Contains (cStr))
399 return component != UriComponents.Path ||
400 SchemeContains (scheme,
401 UriSchemes.Gopher | UriSchemes.Ldap | UriSchemes.Mailto | UriSchemes.Nntp |
402 UriSchemes.Telnet | UriSchemes.News | UriSchemes.Custom);
409 // This is called "compacting" in the MSDN documentation
410 private static string Reduce (string path, bool trimDots)
412 // quick out, allocation-free, for a common case
416 List<string> result = new List<string> ();
419 for (int startpos = 0; startpos < path.Length; ) {
420 int endpos = path.IndexOf ('/', startpos);
422 endpos = path.Length;
423 string current = path.Substring (startpos, endpos-startpos);
424 startpos = endpos + 1;
425 if (begin && current.Length == 0) {
431 if (current == "..") {
432 int resultCount = result.Count;
433 // in 2.0 profile, skip leading ".." parts
434 if (resultCount == 0) {
438 result.RemoveAt (resultCount - 1);
443 current = current.TrimEnd('.');
448 if (current == "" && startpos < path.Length)
451 result.Add (current);
454 if (result.Count == 0)
457 StringBuilder res = new StringBuilder ();
463 foreach (string part in result) {
472 if (path [path.Length - 1] == '/')
475 return res.ToString();