diff --git a/sdk/src/Core/Amazon.Util/AWSSDKUtils.cs b/sdk/src/Core/Amazon.Util/AWSSDKUtils.cs index 7c2a13fecdc5..e25e74bc7fc6 100644 --- a/sdk/src/Core/Amazon.Util/AWSSDKUtils.cs +++ b/sdk/src/Core/Amazon.Util/AWSSDKUtils.cs @@ -195,8 +195,31 @@ private static string DetermineValidPathCharacters() /// public static string GetExtension(string path) { - if (path == null) + if (path is null) return null; + +#if NET8_0_OR_GREATER + // LastIndexOf and LastIndexOfAny is vectorized on .NET8+ and is + // significantly faster for cases where 'path' does not end with a short file + // extension, such as GUIDs + ReadOnlySpan pathSpan = path.AsSpan(); + int extensionIndex = pathSpan.LastIndexOf('.'); + if (extensionIndex == -1) + { + return string.Empty; + } + + int directoryIndex = pathSpan.LastIndexOfAny('/', '\\', ':'); + + // extension separator is found and exists before path separator or path separator doesn't exist + // AND it's not the last one in the string + if (directoryIndex < extensionIndex && extensionIndex < pathSpan.Length - 1) + { + return pathSpan.Slice(extensionIndex).ToString(); + } + + return string.Empty; +#else int length = path.Length; int index = length; @@ -213,15 +236,16 @@ public static string GetExtension(string path) else if (IsPathSeparator(ch)) break; } + return string.Empty; - } - // Checks if the character is one \ / : - private static bool IsPathSeparator(char ch) - { - return (ch == '\\' || - ch == '/' || - ch == ':'); + bool IsPathSeparator(char ch) + { + return (ch == '\\' || + ch == '/' || + ch == ':'); + } +#endif } /* diff --git a/sdk/test/NetStandard/UnitTests/Core/AWSSDKUtilsTests.cs b/sdk/test/NetStandard/UnitTests/Core/AWSSDKUtilsTests.cs index 943692170f4b..8723e0b53785 100644 --- a/sdk/test/NetStandard/UnitTests/Core/AWSSDKUtilsTests.cs +++ b/sdk/test/NetStandard/UnitTests/Core/AWSSDKUtilsTests.cs @@ -84,5 +84,22 @@ public void UrlEncodeWithPath(string input, string expected) Assert.Equal(expected, encoded); } + + [Theory] + [InlineData(null, null)] + [InlineData("no-delimiters-at-all", "")] + [InlineData("delimiter-end-of-string.", "")] + [InlineData("relative-path/no-file-extension", "")] + [InlineData("relative-path\\no-file-extension", "")] + [InlineData("relative-path:no-file-extension", "")] + [InlineData("simple-file.pdf", ".pdf")] + [InlineData("relative-path/with-file-extension.pdf", ".pdf")] + [InlineData("relative-path.with-dot/with-file-extension.pdf", ".pdf")] + public void GetExtension(string input, string expected) + { + var actual = AWSSDKUtils.GetExtension(input); + + Assert.Equal(expected, actual); + } } } diff --git a/sdk/test/UnitTests/Custom/Util/AWSSDKUtilsTests.cs b/sdk/test/UnitTests/Custom/Util/AWSSDKUtilsTests.cs index d81f11cc2b45..3cd8c6982c21 100644 --- a/sdk/test/UnitTests/Custom/Util/AWSSDKUtilsTests.cs +++ b/sdk/test/UnitTests/Custom/Util/AWSSDKUtilsTests.cs @@ -12,13 +12,11 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ +using Amazon.Util; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; -using Amazon.Util; using System.Reflection; -using Moq; -using Amazon.Util.Internal; using System.Text; namespace AWSSDK.UnitTests @@ -178,5 +176,24 @@ public void ToHex(string input, bool lowercase, string expectedResult) Assert.AreEqual(expectedResult, hexString); } + + [TestCategory("UnitTest")] + [TestCategory("Util")] + [DataTestMethod] + [DataRow(null, null)] + [DataRow("no-delimiters-at-all", "")] + [DataRow("delimiter-end-of-string.", "")] + [DataRow("relative-path/no-file-extension", "")] + [DataRow("relative-path\\no-file-extension", "")] + [DataRow("relative-path:no-file-extension", "")] + [DataRow("simple-file.pdf", ".pdf")] + [DataRow("relative-path/with-file-extension.pdf", ".pdf")] + [DataRow("relative-path.with-dot/with-file-extension.pdf", ".pdf")] + public void GetExtension(string input, string expected) + { + var actual = AWSSDKUtils.GetExtension(input); + + Assert.AreEqual(expected, actual); + } } }