11using System ;
22using System . Collections . Generic ;
33using System . Collections . Immutable ;
4- using System . IO ;
54using System . Linq ;
6- using System . Net ;
7- using System . Net . Http ;
8- using System . Security . Cryptography . X509Certificates ;
9- using System . Text ;
105using System . Text . RegularExpressions ;
11- using System . Threading ;
12- using System . Threading . Tasks ;
136using Semmle . Util ;
147using Semmle . Util . Logging ;
158
@@ -21,10 +14,10 @@ internal sealed partial class FeedManager : IDisposable
2114
2215 private readonly ILogger logger ;
2316 private readonly IDotNet dotnet ;
24- private readonly FileProvider fileProvider ;
25- private readonly DependabotProxy ? dependabotProxy ;
17+ private readonly IFileProvider fileProvider ;
2618 private readonly DependencyDirectory emptyPackageDirectory ;
2719 private readonly ImmutableHashSet < string > privateRegistryFeeds ;
20+ private readonly IFeedManagerIO feedManagerIo ;
2821
2922 /// <summary>
3023 /// Gets whether there are private package registries configured for C#.
@@ -79,12 +72,12 @@ internal sealed partial class FeedManager : IDisposable
7972 /// </summary>
8073 public ImmutableHashSet < string > ReachableFallbackFeeds => lazyReachableFallbackFeeds . Value ;
8174
82- public FeedManager ( ILogger logger , IDotNet dotnet , DependabotProxy ? dependabotProxy , FileProvider fileProvider )
75+ public FeedManager ( ILogger logger , IDotNet dotnet , IDependabotProxy ? dependabotProxy , IFileProvider fileProvider , IFeedManagerIO feedManagerIo )
8376 {
8477 this . logger = logger ;
8578 this . dotnet = dotnet ;
86- this . dependabotProxy = dependabotProxy ;
8779 this . fileProvider = fileProvider ;
80+ this . feedManagerIo = feedManagerIo ;
8881 privateRegistryFeeds = dependabotProxy ? . RegistryURLs . ToImmutableHashSet ( ) ?? [ ] ;
8982 HasPrivateRegistryFeeds = privateRegistryFeeds . Count > 0 ;
9083 emptyPackageDirectory = new DependencyDirectory ( "empty" , "empty package" , logger ) ;
@@ -105,17 +98,9 @@ public FeedManager(ILogger logger, IDotNet dotnet, DependabotProxy? dependabotPr
10598 } ) ;
10699 }
107100
108- private string ? GetDirectoryName ( string path )
101+ public FeedManager ( ILogger logger , IDotNet dotnet , IDependabotProxy ? dependabotProxy , IFileProvider fileProvider )
102+ : this ( logger , dotnet , dependabotProxy , fileProvider , new FeedManagerIO ( logger , dependabotProxy ) )
109103 {
110- try
111- {
112- return new FileInfo ( path ) . Directory ? . FullName ;
113- }
114- catch ( Exception exc )
115- {
116- logger . LogWarning ( $ "Failed to get directory of '{ path } ': { exc } ") ;
117- }
118- return null ;
119104 }
120105
121106 private IEnumerable < string > GetFeeds ( Func < IList < string > > getNugetFeeds )
@@ -157,19 +142,16 @@ private IEnumerable<string> GetFeedsFromNugetConfig(string nugetConfigPath) =>
157142 /// If there are no feeds, a dummy source argument is added to override any default feeds that `restore` would use.
158143 /// </summary>
159144 /// <param name="feeds">The list of feeds to use for the restore command.</param>
160- /// <param name="sourceArgumentPrefix">The prefix to use for each source argument (e.g., "-s").</param>
161145 /// <returns>The list of NuGet sources arguments for the restore command.</returns>
162- public List < string > FeedsToRestoreArgument ( IEnumerable < string > feeds , string sourceArgumentPrefix )
146+ public List < string > RestoreFeeds ( IEnumerable < string > feeds )
163147 {
164148 // If there are no feeds, we want to override any default feeds that `restore` would use by passing a dummy source argument.
165149 if ( ! feeds . Any ( ) )
166150 {
167- return [ sourceArgumentPrefix , emptyPackageDirectory . DirInfo . FullName ] ;
151+ return [ emptyPackageDirectory . DirInfo . FullName ] ;
168152 }
169153
170- // Add package sources. If any are present, they override all sources specified in
171- // the configuration file(s).
172- return feeds . SelectMany < string , string > ( feed => [ sourceArgumentPrefix , feed ] ) . ToList ( ) ;
154+ return feeds . ToList ( ) ;
173155 }
174156
175157 private IEnumerable < string > FeedsToUseAux ( HashSet < string > feedsToConsider )
@@ -196,30 +178,20 @@ private IEnumerable<string> FeedsToUseAux(HashSet<string> feedsToConsider)
196178 public IEnumerable < string > FeedsToUse ( string path )
197179 {
198180 // Find the path specific feeds.
199- var folder = GetDirectoryName ( path ) ;
181+ var folder = feedManagerIo . GetDirectoryName ( path ) ;
200182 var feedsToConsider = folder is not null ? GetFeedsFromFolder ( folder ) . ToHashSet ( ) : new HashSet < string > ( ) ;
201183
202184 return FeedsToUseAux ( feedsToConsider ) ;
203185 }
204186
205- /// <summary>
206- /// Constructs the NuGet sources argument for the `dotnet restore` command based on the given feeds.
207- /// </summary>
208- /// <param name="feeds">The list of NuGet feeds to use for the restore command.</param>
209- /// <returns>A list representing the NuGet sources arguments for the `dotnet restore` command.</returns>
210- public List < string > FeedsToDotnetRestoreArgument ( IEnumerable < string > feeds )
211- {
212- return FeedsToRestoreArgument ( feeds , "-s" ) ;
213- }
214-
215187 /// <summary>
216188 /// Constructs the list of NuGet sources to use for dotnet restore.
217189 /// (1) Use the feeds we get from `dotnet nuget list source`
218190 /// (2) Use private registries, if they are configured
219191 /// </summary>
220192 /// <param name="path">Path to project/solution</param>
221193 /// <returns>A list representing the NuGet sources arguments for the `dotnet restore` command.</returns>
222- public List < string > MakeDotnetRestoreSourcesArguments ( string path )
194+ public List < string > MakeRestoreFeeds ( string path )
223195 {
224196 // Do not construct a set of explicit NuGet sources to use for restore.
225197 if ( ! CheckNugetFeedResponsiveness && ! HasPrivateRegistryFeeds )
@@ -229,7 +201,7 @@ public List<string> MakeDotnetRestoreSourcesArguments(string path)
229201
230202 var feedsToUse = FeedsToUse ( path ) ;
231203
232- return FeedsToDotnetRestoreArgument ( feedsToUse ) ;
204+ return RestoreFeeds ( feedsToUse ) ;
233205 }
234206
235207 private ( int initialTimeout , int tryCount ) GetFeedRequestSettings ( bool isFallback )
@@ -251,76 +223,6 @@ public List<string> MakeDotnetRestoreSourcesArguments(string path)
251223 return ( timeoutMilliSeconds , tryCount ) ;
252224 }
253225
254- private static async Task < HttpResponseMessage > ExecuteGetRequest ( string address , HttpClient httpClient , CancellationToken cancellationToken )
255- {
256- return await httpClient . GetAsync ( address , HttpCompletionOption . ResponseHeadersRead , cancellationToken ) ;
257- }
258-
259- private bool IsFeedReachable ( string feed , int timeoutMilliSeconds , int tryCount )
260- {
261- logger . LogInfo ( $ "Checking if NuGet feed '{ feed } ' is reachable...") ;
262-
263- // Configure the HttpClient to be aware of the Dependabot Proxy, if used.
264- HttpClientHandler httpClientHandler = new ( ) ;
265- if ( dependabotProxy != null )
266- {
267- httpClientHandler . Proxy = new WebProxy ( dependabotProxy . Address ) ;
268-
269- if ( dependabotProxy . Certificate != null )
270- {
271- httpClientHandler . ServerCertificateCustomValidationCallback = ( message , cert , chain , _ ) =>
272- {
273- if ( chain is null || cert is null )
274- {
275- var msg = cert is null && chain is null
276- ? "certificate and chain"
277- : chain is null
278- ? "chain"
279- : "certificate" ;
280- logger . LogWarning ( $ "Dependabot proxy certificate validation failed due to missing { msg } ") ;
281- return false ;
282- }
283- chain . ChainPolicy . TrustMode = X509ChainTrustMode . CustomRootTrust ;
284- chain . ChainPolicy . CustomTrustStore . Add ( dependabotProxy . Certificate ) ;
285- return chain . Build ( cert ) ;
286- } ;
287- }
288- }
289-
290- using HttpClient client = new ( httpClientHandler ) ;
291-
292- for ( var i = 0 ; i < tryCount ; i ++ )
293- {
294- using var cts = new CancellationTokenSource ( ) ;
295- cts . CancelAfter ( timeoutMilliSeconds ) ;
296- try
297- {
298- logger . LogInfo ( $ "Attempt { i + 1 } /{ tryCount } to reach NuGet feed '{ feed } '.") ;
299- using var response = ExecuteGetRequest ( feed , client , cts . Token ) . GetAwaiter ( ) . GetResult ( ) ;
300- response . EnsureSuccessStatusCode ( ) ;
301- logger . LogInfo ( $ "Querying NuGet feed '{ feed } ' succeeded.") ;
302- return true ;
303- }
304- catch ( Exception exc )
305- {
306- if ( exc is TaskCanceledException tce &&
307- tce . CancellationToken == cts . Token &&
308- cts . Token . IsCancellationRequested )
309- {
310- logger . LogInfo ( $ "Didn't receive answer from NuGet feed '{ feed } ' in { timeoutMilliSeconds } ms.") ;
311- timeoutMilliSeconds *= 2 ;
312- continue ;
313- }
314-
315- logger . LogInfo ( $ "Querying NuGet feed '{ feed } ' failed. The reason for the failure: { exc . Message } ") ;
316- return false ;
317- }
318- }
319-
320- logger . LogWarning ( $ "Didn't receive answer from NuGet feed '{ feed } '. Tried it { tryCount } times.") ;
321- return false ;
322- }
323-
324226 /// <summary>
325227 /// Retrieves a list of excluded NuGet feeds from the corresponding environment variable.
326228 /// </summary>
@@ -374,7 +276,7 @@ public bool IsDefaultFeedReachable()
374276 if ( CheckNugetFeedResponsiveness )
375277 {
376278 var ( initialTimeout , tryCount ) = GetFeedRequestSettings ( isFallback : false ) ;
377- return IsFeedReachable ( PublicNugetOrgFeed , initialTimeout , tryCount ) ;
279+ return feedManagerIo . IsFeedReachable ( PublicNugetOrgFeed , initialTimeout , tryCount ) ;
378280 }
379281
380282 return true ;
@@ -393,7 +295,7 @@ private List<string> GetReachableNuGetFeeds(HashSet<string> feedsToCheck, bool i
393295
394296 var ( initialTimeout , tryCount ) = GetFeedRequestSettings ( isFallback ) ;
395297 var reachableFeeds = feedsToCheck
396- . Where ( feed => IsFeedReachable ( feed , initialTimeout , tryCount ) )
298+ . Where ( feed => feedManagerIo . IsFeedReachable ( feed , initialTimeout , tryCount ) )
397299 . ToList ( ) ;
398300
399301 if ( reachableFeeds . Count == 0 )
@@ -477,7 +379,7 @@ private ImmutableHashSet<string> GetAllFeeds()
477379 if ( nugetConfigs . Count > 0 )
478380 {
479381 var nugetConfigFeeds = nugetConfigs
480- . Select ( GetDirectoryName )
382+ . Select ( feedManagerIo . GetDirectoryName )
481383 . Where ( folder => folder != null )
482384 . SelectMany ( folder => GetFeedsFromFolder ( folder ! ) )
483385 . ToHashSet ( ) ;
0 commit comments