|  |  |  | @ -1,5 +1,7 @@ | 
			
		
	
		
			
				
					|  |  |  |  | using System.Collections.Generic; | 
			
		
	
		
			
				
					|  |  |  |  | using System; | 
			
		
	
		
			
				
					|  |  |  |  | using System.Collections.Generic; | 
			
		
	
		
			
				
					|  |  |  |  | using System.Text; | 
			
		
	
		
			
				
					|  |  |  |  | using System.Text.RegularExpressions; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | namespace ZeroLevel.Services.Web | 
			
		
	
		
			
				
					|  |  |  |  | { | 
			
		
	
	
		
			
				
					|  |  |  | @ -60,6 +62,78 @@ namespace ZeroLevel.Services.Web | 
			
		
	
		
			
				
					|  |  |  |  |             return str == null ? null : UrlEncodeUnicodeStringToStringInternal(str, false); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         /// <summary> | 
			
		
	
		
			
				
					|  |  |  |  |         /// https://github.com/tmenier/Flurl | 
			
		
	
		
			
				
					|  |  |  |  |         /// </summary> | 
			
		
	
		
			
				
					|  |  |  |  |         public static string Combine(params string[] parts) | 
			
		
	
		
			
				
					|  |  |  |  |         { | 
			
		
	
		
			
				
					|  |  |  |  |             if (parts == null) | 
			
		
	
		
			
				
					|  |  |  |  |                 throw new ArgumentNullException(nameof(parts)); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             string result = ""; | 
			
		
	
		
			
				
					|  |  |  |  |             bool inQuery = false, inFragment = false; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             string CombineEnsureSingleSeparator(string a, string b, char separator) | 
			
		
	
		
			
				
					|  |  |  |  |             { | 
			
		
	
		
			
				
					|  |  |  |  |                 if (string.IsNullOrEmpty(a)) return b; | 
			
		
	
		
			
				
					|  |  |  |  |                 if (string.IsNullOrEmpty(b)) return a; | 
			
		
	
		
			
				
					|  |  |  |  |                 return a.TrimEnd(separator) + separator + b.TrimStart(separator); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             foreach (var part in parts) | 
			
		
	
		
			
				
					|  |  |  |  |             { | 
			
		
	
		
			
				
					|  |  |  |  |                 if (string.IsNullOrEmpty(part)) | 
			
		
	
		
			
				
					|  |  |  |  |                     continue; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |                 if (result.EndsWith("?") || part.StartsWith("?")) | 
			
		
	
		
			
				
					|  |  |  |  |                     result = CombineEnsureSingleSeparator(result, part, '?'); | 
			
		
	
		
			
				
					|  |  |  |  |                 else if (result.EndsWith("#") || part.StartsWith("#")) | 
			
		
	
		
			
				
					|  |  |  |  |                     result = CombineEnsureSingleSeparator(result, part, '#'); | 
			
		
	
		
			
				
					|  |  |  |  |                 else if (inFragment) | 
			
		
	
		
			
				
					|  |  |  |  |                     result += part; | 
			
		
	
		
			
				
					|  |  |  |  |                 else if (inQuery) | 
			
		
	
		
			
				
					|  |  |  |  |                     result = CombineEnsureSingleSeparator(result, part, '&'); | 
			
		
	
		
			
				
					|  |  |  |  |                 else | 
			
		
	
		
			
				
					|  |  |  |  |                     result = CombineEnsureSingleSeparator(result, part, '/'); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |                 if (part.Contains("#")) | 
			
		
	
		
			
				
					|  |  |  |  |                 { | 
			
		
	
		
			
				
					|  |  |  |  |                     inQuery = false; | 
			
		
	
		
			
				
					|  |  |  |  |                     inFragment = true; | 
			
		
	
		
			
				
					|  |  |  |  |                 } | 
			
		
	
		
			
				
					|  |  |  |  |                 else if (!inFragment && part.Contains("?")) | 
			
		
	
		
			
				
					|  |  |  |  |                 { | 
			
		
	
		
			
				
					|  |  |  |  |                     inQuery = true; | 
			
		
	
		
			
				
					|  |  |  |  |                 } | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |             return EncodeIllegalCharacters(result); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         /// <summary> | 
			
		
	
		
			
				
					|  |  |  |  |         /// https://github.com/tmenier/Flurl | 
			
		
	
		
			
				
					|  |  |  |  |         /// </summary> | 
			
		
	
		
			
				
					|  |  |  |  |         public static string EncodeIllegalCharacters(string s, bool encodeSpaceAsPlus = false) | 
			
		
	
		
			
				
					|  |  |  |  |         { | 
			
		
	
		
			
				
					|  |  |  |  |             if (string.IsNullOrEmpty(s)) | 
			
		
	
		
			
				
					|  |  |  |  |                 return s; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             if (encodeSpaceAsPlus) | 
			
		
	
		
			
				
					|  |  |  |  |                 s = s.Replace(" ", "+"); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             // Uri.EscapeUriString mostly does what we want - encodes illegal characters only - but it has a quirk | 
			
		
	
		
			
				
					|  |  |  |  |             // in that % isn't illegal if it's the start of a %-encoded sequence https://stackoverflow.com/a/47636037/62600 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             // no % characters, so avoid the regex overhead | 
			
		
	
		
			
				
					|  |  |  |  |             if (!s.Contains("%")) | 
			
		
	
		
			
				
					|  |  |  |  |                 return Uri.EscapeUriString(s); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             // pick out all %-hex-hex matches and avoid double-encoding  | 
			
		
	
		
			
				
					|  |  |  |  |             return Regex.Replace(s, "(.*?)((%[0-9A-Fa-f]{2})|$)", c => { | 
			
		
	
		
			
				
					|  |  |  |  |                 var a = c.Groups[1].Value; // group 1 is a sequence with no %-encoding - encode illegal characters | 
			
		
	
		
			
				
					|  |  |  |  |                 var b = c.Groups[2].Value; // group 2 is a valid 3-character %-encoded sequence - leave it alone! | 
			
		
	
		
			
				
					|  |  |  |  |                 return Uri.EscapeUriString(a) + b; | 
			
		
	
		
			
				
					|  |  |  |  |             }); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         private static string UrlEncodeUnicodeStringToStringInternal(string s, bool ignoreAscii) | 
			
		
	
		
			
				
					|  |  |  |  |         { | 
			
		
	
		
			
				
					|  |  |  |  |             var l = s.Length; | 
			
		
	
	
		
			
				
					|  |  |  | 
 |