diff --git a/CHANGELOG.md b/CHANGELOG.md index edae9f3..0f6b732 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added GetAttributeAsEnum xml utils method - Adding short parsing methods to XML utils + - Added methods to IcdUriBuilder for appending path ## [8.0.0] - 2018-11-20 ### Added diff --git a/ICD.Common.Utils/IcdUriBuilder.cs b/ICD.Common.Utils/IcdUriBuilder.cs index a7dda90..4a385f0 100644 --- a/ICD.Common.Utils/IcdUriBuilder.cs +++ b/ICD.Common.Utils/IcdUriBuilder.cs @@ -1,5 +1,9 @@ using System; +#if !SIMPLSHARP +using System.Linq; +#endif using System.Text; +using System.Text.RegularExpressions; using ICD.Common.Utils.Extensions; namespace ICD.Common.Utils @@ -159,6 +163,125 @@ namespace ICD.Common.Utils } return builder.ToString(); + } + + /// + /// Appends the given path to the current path, ensuring only one separator between parts. + /// + /// + /// + public void AppendPath(params string[] parts) + { + parts = parts.Prepend(Path).ToArray(parts.Length + 1); + Path = Combine(parts); } + + #region Flurl + + // The following region is taken from Flurl https://github.com/tmenier/Flurl + + /* + MIT License + + Copyright (c) 2018 Todd Menier + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + + /// + /// Basically a Path.Combine for URLs. Ensures exactly one '/' seperates each segment, + /// and exactly on '&' seperates each query parameter. + /// URL-encodes illegal characters but not reserved characters. + /// + /// URL parts to combine. + public static string Combine(params string[] parts) + { + if (parts == null) + throw new ArgumentNullException("parts"); + + string result = ""; + bool inQuery = false, inFragment = false; + + foreach (var part in parts) + { + if (string.IsNullOrEmpty(part)) + continue; + + if (result.EndsWith("?") || part.StartsWith("?")) + result = CombineEnsureSingleSeperator(result, part, '?'); + else if (result.EndsWith("#") || part.StartsWith("#")) + result = CombineEnsureSingleSeperator(result, part, '#'); + else if (inFragment) + result += part; + else if (inQuery) + result = CombineEnsureSingleSeperator(result, part, '&'); + else + result = CombineEnsureSingleSeperator(result, part, '/'); + + if (part.Contains("#")) + { + inQuery = false; + inFragment = true; + } + else if (!inFragment && part.Contains("?")) + { + inQuery = true; + } + } + return EncodeIllegalCharacters(result, false); + } + + private static string CombineEnsureSingleSeperator(string a, string b, char seperator) + { + if (string.IsNullOrEmpty(a)) return b; + if (string.IsNullOrEmpty(b)) return a; + return a.TrimEnd(seperator) + seperator + b.TrimStart(seperator); + } + + /// + /// URL-encodes characters in a string that are neither reserved nor unreserved. Avoids encoding reserved characters such as '/' and '?'. Avoids encoding '%' if it begins a %-hex-hex sequence (i.e. avoids double-encoding). + /// + /// The string to encode. + /// If true, spaces will be encoded as + signs. Otherwise, they'll be encoded as %20. + /// The encoded URL. + private static string EncodeIllegalCharacters(string s, bool encodeSpaceAsPlus) { + 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; + }); + } + + #endregion } }