diff --git a/src/Microsoft.Diagnostics.Runtime.Tests/src/SymbolGroupTests.cs b/src/Microsoft.Diagnostics.Runtime.Tests/src/SymbolGroupTests.cs new file mode 100644 index 000000000..be0168bc1 --- /dev/null +++ b/src/Microsoft.Diagnostics.Runtime.Tests/src/SymbolGroupTests.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.Runtime.Implementation; +using Xunit; + +namespace Microsoft.Diagnostics.Runtime.Tests +{ + /// + /// Tests for SymbolGroup.EnumerateEntries + /// + public class SymbolGroupTests + { + [Fact] + public void TestSymsrv() + { + const string sympathWithDll = "symsrv*symstore.dll*https://msdl.microsoft.com/download/symbols"; + (string cache, string[] servers) = SymbolGroup.EnumerateEntries(sympathWithDll); + Assert.Null(cache); + Assert.Equal(["https://msdl.microsoft.com/download/symbols"], servers); + + const string sympathWithoutDll = "symsrv*https://msdl.microsoft.com/download/symbols"; + (cache, servers) = SymbolGroup.EnumerateEntries(sympathWithoutDll); + Assert.Null(cache); + Assert.Equal(["https://msdl.microsoft.com/download/symbols"], servers); + } + + [Fact] + public void TestServerWithCache() + { + const string sympath = "srv*d:\\cache*https://msdl.microsoft.com/download/symbols"; + (string cache, string[] servers) = SymbolGroup.EnumerateEntries(sympath); + Assert.Equal("d:\\cache", cache); + Assert.Equal(["https://msdl.microsoft.com/download/symbols"], servers); + + const string sympathWithDll = "srv*d:\\cache*https://msdl.microsoft.com/download/symbols*https://msdl.microsoft.com/download/symbols2"; + (cache, servers) = SymbolGroup.EnumerateEntries(sympathWithDll); + Assert.Equal("d:\\cache", cache); + Assert.Equal(["https://msdl.microsoft.com/download/symbols", "https://msdl.microsoft.com/download/symbols2"], servers); + + const string sympathWithoutCache = "srv*https://msdl.microsoft.com/download/symbols*https://msdl.microsoft.com/download/symbols2"; + (cache, servers) = SymbolGroup.EnumerateEntries(sympathWithoutCache); + Assert.Null(cache); + Assert.Equal(["https://msdl.microsoft.com/download/symbols", "https://msdl.microsoft.com/download/symbols2"], servers); + } + + [Fact] + public void TestCache() + { + const string sympath = "cache*d:\\cache"; + (string cache, string[] servers) = SymbolGroup.EnumerateEntries(sympath); + Assert.Equal("d:\\cache", cache); + Assert.Empty(servers); + + const string sympathWithDll = "cache*d:\\cache*https://msdl.microsoft.com/download/symbols"; + (cache, servers) = SymbolGroup.EnumerateEntries(sympathWithDll); + Assert.Equal("d:\\cache", cache); + Assert.Equal(["https://msdl.microsoft.com/download/symbols"], servers); + + const string sympathWithDll2 = "cache*d:\\cache*https://msdl.microsoft.com/download/symbols*https://msdl.microsoft.com/download/symbols2"; + (cache, servers) = SymbolGroup.EnumerateEntries(sympathWithDll2); + Assert.Equal("d:\\cache", cache); + Assert.Equal(["https://msdl.microsoft.com/download/symbols", "https://msdl.microsoft.com/download/symbols2"], servers); + } + } +} diff --git a/src/Microsoft.Diagnostics.Runtime/Implementation/SymbolGroup.cs b/src/Microsoft.Diagnostics.Runtime/Implementation/SymbolGroup.cs index 45abe7e13..74fceaf93 100644 --- a/src/Microsoft.Diagnostics.Runtime/Implementation/SymbolGroup.cs +++ b/src/Microsoft.Diagnostics.Runtime/Implementation/SymbolGroup.cs @@ -112,7 +112,7 @@ public static IFileLocator CreateFromSymbolPath(string symbolPath, TokenCredenti foreach (string server in Servers) { - if (server.StartsWith("http:", StringComparison.OrdinalIgnoreCase) || server.StartsWith("https:", StringComparison.OrdinalIgnoreCase)) + if (IsUrl(server)) { SymbolServer symSvr = new(cache, server, credential); locators.Add(symSvr); @@ -147,49 +147,67 @@ public static IFileLocator CreateFromSymbolPath(string symbolPath, TokenCredenti return new SymbolGroup(locators); } - private static (string? Cache, string[] Servers) EnumerateEntries(string part) + internal static (string? Cache, string[] Servers) EnumerateEntries(string part) { - if (!part.Contains('*')) - return (null, new string[] { part }); - - string[] split = part.Split('*'); - DebugOnly.Assert(split.Length > 1); - - if (split[0].Equals("cache")) - return (split[1], split.Skip(2).ToArray()); - - - if (split[0].Equals("symsrv", StringComparison.OrdinalIgnoreCase)) + string? cache = null; + List servers = []; + foreach (string entry in EnumerateParts(part)) { - // We don't really support this, but we'll make it work...ish. - // Convert symsrv*symstore.dll*DownStream*server -> srv*DownStream*server - - if (split.Length < 3) - return (null, new string[] { part }); - - split = new string[] { "srv" }.Concat(split.Skip(2)).ToArray(); + if (cache is null && servers.Count == 0 && !IsUrl(entry)) + cache = entry; + else + servers.Add(entry); } + return (cache, servers.ToArray()); + } - if (split[0].Equals("svr", StringComparison.OrdinalIgnoreCase) || split[0].Equals("srv", StringComparison.OrdinalIgnoreCase)) + private static IEnumerable EnumerateParts(string path) + { + bool possiblySkipNextDll = false; + int curr = 0; + for (int i = 0; i < path.Length; i++) { - string? cache = split[1]; - - if (string.IsNullOrWhiteSpace(cache)) - cache = null; - - // e.g. "svr*http://symbols.com/" - if (split.Length == 2) + if (path[i] == '*') { - if (cache is null) - return (split[1], split.Skip(2).ToArray()); + ReadOnlySpan part = path.AsSpan(curr, i - curr).Trim(); + curr = i + 1; - return (null, new string[] { cache }); + if (part.Equals("cache".AsSpan(), StringComparison.OrdinalIgnoreCase) + || part.Equals("svr".AsSpan(), StringComparison.OrdinalIgnoreCase) + || part.Equals("srv".AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + // Don't yield this. + } + else if (part.Equals("symsrv".AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + possiblySkipNextDll = true; + } + else + { + bool skip = possiblySkipNextDll && part.EndsWith(".dll".AsSpan(), StringComparison.OrdinalIgnoreCase); + possiblySkipNextDll = false; + + if (!skip && part.Length > 0) + yield return part.ToString(); + } } } - // Ok, so we have * but it didn't start with srv or svr, so what now? - return (null, split.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray()); + if (curr < path.Length) + { + ReadOnlySpan part = path.AsSpan(curr).Trim(); + bool skip = possiblySkipNextDll && part.EndsWith(".dll".AsSpan(), StringComparison.OrdinalIgnoreCase); + if (!skip && part.Length > 0) + yield return part.ToString(); + } + } + + private static bool IsUrl(string path) + { + bool result = Uri.TryCreate(path, UriKind.Absolute, out Uri? uriResult) + && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); + return result; } } } \ No newline at end of file diff --git a/src/Microsoft.Diagnostics.Runtime/Implementation/SymbolServer.cs b/src/Microsoft.Diagnostics.Runtime/Implementation/SymbolServer.cs index 63778825c..90d718882 100644 --- a/src/Microsoft.Diagnostics.Runtime/Implementation/SymbolServer.cs +++ b/src/Microsoft.Diagnostics.Runtime/Implementation/SymbolServer.cs @@ -32,7 +32,7 @@ internal SymbolServer(FileSymbolCache cache, string server, TokenCredential? cre throw new ArgumentNullException(nameof(cache)); _cache = cache; - _accessToken = default;Server = server; + Server = server; if (IsSymweb(server)) {