Skip to content

Commit 2312379

Browse files
michaelnebelCopilot
andcommitted
C#: Fix NuGet CLI fallback and registry URI matching
Filter private candidates and defaults before choosing the fallback, and compare registry URI identities while preserving path case. Add regressions for missing or unreachable folder feeds and equivalent registry URLs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 3badc71 commit 2312379

2 files changed

Lines changed: 58 additions & 5 deletions

File tree

‎csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FeedManager.cs‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,15 @@ public List<string> MakeRestoreFeeds(string path)
252252
/// </summary>
253253
public List<string> MakeNugetExeRestoreFeeds(string path)
254254
{
255-
var feedsToUse = FeedsToUse(path).ToList();
256-
var defaultFeeds = CheckNugetFeedResponsiveness ? ReachableDefaultFeeds : DefaultFeeds;
255+
bool IsPrivateFeed(string feed) =>
256+
privateRegistryFeeds.Contains(feed) ||
257+
(Uri.TryCreate(feed, UriKind.Absolute, out var uri) &&
258+
privateRegistryFeeds.Any(registry =>
259+
Uri.TryCreate(registry, UriKind.Absolute, out var registryUri) && uri.Equals(registryUri)));
260+
261+
var feedsToUse = FeedsToUse(path).Where(feed => !IsPrivateFeed(feed)).ToList();
262+
var defaultFeeds = (CheckNugetFeedResponsiveness ? ReachableDefaultFeeds : DefaultFeeds)
263+
.Where(feed => !IsPrivateFeed(feed)).ToList();
257264
var useDefaultFeeds = feedsToUse.Count == 0 && defaultFeeds.Count > 0;
258265

259266
if (!CheckNugetFeedResponsiveness && !HasPrivateRegistryFeeds && !useDefaultFeeds)
@@ -266,8 +273,7 @@ public List<string> MakeNugetExeRestoreFeeds(string path)
266273
feedsToUse.AddRange(defaultFeeds);
267274
}
268275

269-
// Filter after selecting defaults so private base feeds cannot be reintroduced.
270-
return RestoreFeeds(feedsToUse.Where(feed => !privateRegistryFeeds.Contains(feed)));
276+
return RestoreFeeds(feedsToUse);
271277
}
272278

273279
private (int initialTimeout, int tryCount) GetFeedRequestSettings(bool isFallback)

‎csharp/extractor/Semmle.Extraction.Tests/FeedManager.cs‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ public void TestNugetExeRestoreFeedsExcludePrivateBaseRegistries()
293293
}
294294

295295
/// <summary>
296-
/// Verify that NuGet CLI restore uses an empty local source when only private registries are configured,
296+
/// Verify that NuGet CLI restore uses an empty local source when private registries replace the default feeds,
297297
/// regardless of their reachability.
298298
/// </summary>
299299
[Theory]
@@ -355,6 +355,53 @@ public void TestNugetExeRestoreFeedsUseDefaults()
355355
Assert.Equal(["https://api.nuget.org/v3/index.json"], feeds);
356356
}
357357

358+
/// <summary>
359+
/// Verify that non-base private registries do not prevent NuGet CLI restore from falling back to NuGet.org
360+
/// when no usable folder feeds remain.
361+
/// </summary>
362+
[Theory]
363+
[InlineData(false)]
364+
[InlineData(true)]
365+
public void TestNugetExeRestoreFeedsUseDefaultsWithPrivateRegistries(bool unreachableFolderFeed)
366+
{
367+
// Setup
368+
var logger = new LoggerStub();
369+
var dotnet = new DotNetStub([], [], [],
370+
unreachableFolderFeed ? ["E https://feed.from/folder1"] : []);
371+
using var feedManager = new FeedManager(logger, dotnet, new RegistryProxyStub(),
372+
new FileProviderStub(), new FeedManagerIOStub(["https://feed.from/folder1"]));
373+
374+
// Execute
375+
var feeds = feedManager.MakeNugetExeRestoreFeeds("/path/to/packages.config");
376+
377+
// Verify
378+
Assert.Equal(["https://api.nuget.org/v3/index.json"], feeds);
379+
}
380+
381+
/// <summary>
382+
/// Verify that NuGet CLI restore identifies equivalent private registry URLs regardless of host case
383+
/// or explicit default ports, while preserving case-sensitive registry paths.
384+
/// </summary>
385+
[Theory]
386+
[InlineData("https://EXAMPLE.COM/registry2", true)]
387+
[InlineData("https://example.com:443/registry2", true)]
388+
[InlineData("https://EXAMPLE.COM:443/registry2", true)]
389+
[InlineData("https://example.com/Registry2", false)]
390+
public void TestNugetExeRestoreFeedsCompareRegistryUris(string folderFeed, bool isPrivate)
391+
{
392+
// Setup
393+
var logger = new LoggerStub();
394+
var dotnet = new DotNetStub([], [], [], [$"E {folderFeed}"]);
395+
using var feedManager = new FeedManager(logger, dotnet, new RegistryProxyStub(),
396+
new FileProviderStub(), new FeedManagerIOStub([]));
397+
398+
// Execute
399+
var feeds = feedManager.MakeNugetExeRestoreFeeds("/path/to/packages.config");
400+
401+
// Verify
402+
Assert.Equal([isPrivate ? "https://api.nuget.org/v3/index.json" : folderFeed], feeds);
403+
}
404+
358405
/// <summary>
359406
/// Verify that `FeedManager` correctly computes the default feeds and reachable default feeds
360407
/// when no private registries are configured.

0 commit comments

Comments
 (0)