diff --git a/src/DotnetThirdPartyNotices/Extensions/ResolvedFileInfoExtensions.cs b/src/DotnetThirdPartyNotices/Extensions/ResolvedFileInfoExtensions.cs index 4278c48..e1ad53f 100644 --- a/src/DotnetThirdPartyNotices/Extensions/ResolvedFileInfoExtensions.cs +++ b/src/DotnetThirdPartyNotices/Extensions/ResolvedFileInfoExtensions.cs @@ -21,6 +21,9 @@ internal static class ResolvedFileInfoExtensions private static readonly Lazy> ProjectUriLicenseResolvers = new(() => LicenseResolvers.Value.OfType().ToList()); + private static readonly Lazy> RepositoryUriLicenseResolvers = + new(() => LicenseResolvers.Value.OfType().ToList()); + private static readonly Lazy> FileVersionInfoLicenseResolvers = new(() => LicenseResolvers.Value.OfType().ToList()); @@ -35,6 +38,12 @@ private static bool TryFindLicenseUriLicenseResolver(Uri licenseUri, out ILicens return resolver != null; } + private static bool TryFindRepositoryUriLicenseResolver( Uri licenseUri, out IRepositoryUriLicenseResolver resolver ) + { + resolver = RepositoryUriLicenseResolvers.Value.FirstOrDefault(r => r.CanResolve(licenseUri)); + return resolver != null; + } + private static bool TryFindProjectUriLicenseResolver(Uri projectUri, out IProjectUriLicenseResolver resolver) { resolver = ProjectUriLicenseResolvers.Value.FirstOrDefault(r => r.CanResolve(projectUri)); @@ -66,6 +75,7 @@ private static async Task ResolveLicense(NuSpec nuSpec) return LicenseCache[nuSpec.Id]; var licenseUrl = nuSpec.LicenseUrl; + var repositoryUrl = nuSpec.RepositoryUrl; var projectUrl = nuSpec.ProjectUrl; // Try to get the license from license url @@ -83,6 +93,20 @@ private static async Task ResolveLicense(NuSpec nuSpec) } } + // Try to get the license from repository url + if (!string.IsNullOrEmpty(repositoryUrl)) + { + if (LicenseCache.ContainsKey(repositoryUrl )) + return LicenseCache[repositoryUrl]; + var license = await ResolveLicenseFromRepositoryUri(new Uri(repositoryUrl)); + if (license != null) + { + LicenseCache[repositoryUrl] = license; + LicenseCache[nuSpec.Id] = license; + return license; + } + } + // Otherwise try to get the license from project url if (string.IsNullOrEmpty(projectUrl)) return null; @@ -111,6 +135,20 @@ private static async Task ResolveLicenseFromLicenseUri(Uri licenseUri) return await licenseUri.GetPlainText(); } + private static async Task ResolveLicenseFromRepositoryUri(Uri repositoryUri) + { + if (TryFindRepositoryUriLicenseResolver(repositoryUri, out var repositoryUriLicenseResolver)) + return await repositoryUriLicenseResolver.Resolve(repositoryUri); + + // TODO: redirect uris should be checked at the very end to save us from redundant requests (when no resolver for anything can be found) + var redirectUri = await repositoryUri.GetRedirectUri(); + if (redirectUri != null) + return await ResolveLicenseFromLicenseUri(redirectUri); + + // Finally, if no license uri can be found despite all the redirects, try to blindly get it + return await repositoryUri.GetPlainText(); + } + private static async Task ResolveLicenseFromProjectUri(Uri projectUri) { if (TryFindProjectUriLicenseResolver(projectUri, out var projectUriLicenseResolver)) diff --git a/src/DotnetThirdPartyNotices/GithubService.cs b/src/DotnetThirdPartyNotices/GithubService.cs index a061cbb..80b7a1a 100644 --- a/src/DotnetThirdPartyNotices/GithubService.cs +++ b/src/DotnetThirdPartyNotices/GithubService.cs @@ -32,6 +32,8 @@ public async Task GetLicenseContentFromId(string licenseId) public async Task GetLicenseContentFromRepositoryPath(string repositoryPath) { repositoryPath = repositoryPath.TrimEnd('/'); + if (repositoryPath.EndsWith(".git")) + repositoryPath = repositoryPath[..^4]; var json = await _httpClient.GetStringAsync($"repos{repositoryPath}/license"); var jsonDocument = JsonDocument.Parse(json); diff --git a/src/DotnetThirdPartyNotices/LicenseResolvers/GithubLicenseResolver.cs b/src/DotnetThirdPartyNotices/LicenseResolvers/GithubLicenseResolver.cs index 7f95ce7..7024455 100644 --- a/src/DotnetThirdPartyNotices/LicenseResolvers/GithubLicenseResolver.cs +++ b/src/DotnetThirdPartyNotices/LicenseResolvers/GithubLicenseResolver.cs @@ -5,10 +5,11 @@ namespace DotnetThirdPartyNotices.LicenseResolvers; -internal class GithubLicenseResolver : ILicenseUriLicenseResolver, IProjectUriLicenseResolver +internal class GithubLicenseResolver : ILicenseUriLicenseResolver, IProjectUriLicenseResolver, IRepositoryUriLicenseResolver { bool ILicenseUriLicenseResolver.CanResolve(Uri uri) => uri.IsGithubUri(); bool IProjectUriLicenseResolver.CanResolve(Uri uri) => uri.IsGithubUri(); + bool IRepositoryUriLicenseResolver.CanResolve(Uri uri) => uri.IsGithubUri(); Task ILicenseUriLicenseResolver.Resolve(Uri licenseUri) { @@ -22,4 +23,10 @@ async Task IProjectUriLicenseResolver.Resolve(Uri projectUri) using var githubService = new GithubService(); return await githubService.GetLicenseContentFromRepositoryPath(projectUri.AbsolutePath); } + + async Task IRepositoryUriLicenseResolver.Resolve(Uri projectUri) + { + using var githubService = new GithubService(); + return await githubService.GetLicenseContentFromRepositoryPath(projectUri.AbsolutePath); + } } \ No newline at end of file diff --git a/src/DotnetThirdPartyNotices/LicenseResolvers/Interfaces/IRepositoryUriLicenseResolver.cs b/src/DotnetThirdPartyNotices/LicenseResolvers/Interfaces/IRepositoryUriLicenseResolver.cs new file mode 100644 index 0000000..4d395f7 --- /dev/null +++ b/src/DotnetThirdPartyNotices/LicenseResolvers/Interfaces/IRepositoryUriLicenseResolver.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DotnetThirdPartyNotices.LicenseResolvers.Interfaces; + +internal interface IRepositoryUriLicenseResolver : ILicenseResolver +{ + bool CanResolve(Uri projectUri); + Task Resolve(Uri projectUri); +} \ No newline at end of file diff --git a/src/DotnetThirdPartyNotices/LicenseResolvers/LocalPackageLicenseResolver.cs b/src/DotnetThirdPartyNotices/LicenseResolvers/LocalPackageLicenseResolver.cs new file mode 100644 index 0000000..cca802c --- /dev/null +++ b/src/DotnetThirdPartyNotices/LicenseResolvers/LocalPackageLicenseResolver.cs @@ -0,0 +1,32 @@ +using DotnetThirdPartyNotices.LicenseResolvers.Interfaces; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DotnetThirdPartyNotices.LicenseResolvers; + +internal class LocalPackageLicenseResolver : IFileVersionInfoLicenseResolver +{ + public bool CanResolve( FileVersionInfo fileVersionInfo ) => true; + + public async Task Resolve( FileVersionInfo fileVersionInfo ) + { + var packageName = Path.GetFileNameWithoutExtension(fileVersionInfo.FileName); + var directoryParts = Path.GetDirectoryName(fileVersionInfo.FileName ).Split('\\', StringSplitOptions.RemoveEmptyEntries); + for ( var i = 0; i < directoryParts.Length; i++ ) + { + var directoryPath = string.Join('\\', directoryParts.SkipLast(i)); + var licensePath = Directory.EnumerateFiles(directoryPath, "license.txt", SearchOption.TopDirectoryOnly) + .FirstOrDefault(); + if (licensePath != null) + return await File.ReadAllTextAsync(licensePath); + if (directoryPath.EndsWith($"\\{packageName}", StringComparison.OrdinalIgnoreCase)) + break; + } + return null; + } +} diff --git a/src/DotnetThirdPartyNotices/NuSpec.cs b/src/DotnetThirdPartyNotices/NuSpec.cs index 7641684..93dfe4d 100644 --- a/src/DotnetThirdPartyNotices/NuSpec.cs +++ b/src/DotnetThirdPartyNotices/NuSpec.cs @@ -13,6 +13,7 @@ public record NuSpec public string Version { get; init; } public string LicenseUrl { get; init; } public string ProjectUrl { get; init; } + public string RepositoryUrl { get; init; } private static NuSpec FromTextReader(TextReader streamReader) { @@ -29,7 +30,8 @@ private static NuSpec FromTextReader(TextReader streamReader) Id = metadata.Element(ns + "id")?.Value, Version = metadata.Element(ns + "version")?.Value, LicenseUrl = metadata.Element(ns + "licenseUrl")?.Value, - ProjectUrl = metadata.Element(ns + "projectUrl")?.Value + ProjectUrl = metadata.Element(ns + "projectUrl")?.Value, + RepositoryUrl = metadata.Element(ns + "repository")?.Attribute("url")?.Value }; }