11// Copyright (c) .NET Foundation. All rights reserved.
22// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33
4+ #nullable enable
5+
46using System ;
57using System . Collections . Generic ;
68using System . Globalization ;
@@ -48,28 +50,62 @@ public static async Task<int> RunAsync(PackageDownloadArgs args, CancellationTok
4850
4951 public static async Task < int > RunAsync ( PackageDownloadArgs args , ILoggerWithColor logger , IReadOnlyList < PackageSource > packageSources , ISettings settings , CancellationToken token )
5052 {
51- // Check for insecure sources
52- if ( DetectAndReportInsecureSources ( args . AllowInsecureConnections , packageSources , logger ) )
53+ bool hasSourcesArg = args . Sources ? . Count > 0 ;
54+ PackageSourceMapping ? packageSourceMapping = null ;
55+ if ( ! hasSourcesArg )
56+ {
57+ packageSourceMapping = PackageSourceMapping . GetPackageSourceMapping ( settings ) ;
58+ }
59+
60+ bool ignorePackageSourceMapping =
61+ hasSourcesArg
62+ || packageSourceMapping is null
63+ || ! packageSourceMapping . IsEnabled ;
64+
65+ // When package source mapping is disabled, validate all configured sources upfront.
66+ // When mapping is enabled, source validation is deferred to the per-package resolution step,
67+ // since each package may map to a different subset of sources.
68+ if ( ignorePackageSourceMapping && DetectAndReportInsecureSources ( args . AllowInsecureConnections , packageSources , logger ) )
5369 {
5470 return ExitCodeError ;
5571 }
5672
5773 string outputDirectory = args . OutputDirectory ?? Directory . GetCurrentDirectory ( ) ;
5874 var cache = new SourceCacheContext ( ) ;
59- IReadOnlyList < SourceRepository > sourceRepositories = GetSourceRepositories ( packageSources ) ;
75+ IReadOnlyList < SourceRepository > allRepositories = GetSourceRepositories ( packageSources ) ;
6076 bool downloadedAllSuccessfully = true ;
6177
62- foreach ( var package in args . Packages )
78+ foreach ( var package in args . Packages ?? [ ] )
6379 {
6480 logger . LogMinimal ( string . Format (
6581 CultureInfo . CurrentCulture ,
6682 Strings . PackageDownloadCommand_Starting ,
6783 package . Id ,
6884 string . IsNullOrEmpty ( package . NuGetVersion ? . ToNormalizedString ( ) ) ? Strings . PackageDownloadCommand_LatestVersion : package . NuGetVersion . ToNormalizedString ( ) ) ) ;
6985
86+ // Resolve which repositories to use for this package
87+ IReadOnlyList < SourceRepository > sourceRepositories ;
88+ if ( ignorePackageSourceMapping )
89+ {
90+ sourceRepositories = allRepositories ;
91+ }
92+ else
93+ {
94+ if ( ! TryGetRepositoriesForPackage (
95+ package . Id ,
96+ args ,
97+ packageSourceMapping ! ,
98+ allRepositories ,
99+ logger ,
100+ out sourceRepositories ) )
101+ {
102+ return ExitCodeError ;
103+ }
104+ }
105+
70106 try
71107 {
72- ( NuGetVersion version , SourceRepository downloadRepository ) =
108+ ( NuGetVersion ? version , SourceRepository ? downloadRepository ) =
73109 await ResolvePackageDownloadVersion (
74110 package ,
75111 sourceRepositories ,
@@ -88,7 +124,7 @@ await ResolvePackageDownloadVersion(
88124 bool success = await DownloadPackageAsync (
89125 package . Id ,
90126 version ,
91- downloadRepository ,
127+ downloadRepository ! ,
92128 cache ,
93129 settings ,
94130 outputDirectory ,
@@ -127,16 +163,16 @@ await ResolvePackageDownloadVersion(
127163 return downloadedAllSuccessfully ? ExitCodeSuccess : ExitCodeError ;
128164 }
129165
130- internal static async Task < ( NuGetVersion , SourceRepository ) > ResolvePackageDownloadVersion (
166+ internal static async Task < ( NuGetVersion ? , SourceRepository ? ) > ResolvePackageDownloadVersion (
131167 PackageWithNuGetVersion packageWithNuGetVersion ,
132- IEnumerable < SourceRepository > sourceRepositories ,
168+ IReadOnlyList < SourceRepository > sourceRepositories ,
133169 SourceCacheContext cache ,
134170 ILoggerWithColor logger ,
135171 bool includePrerelease ,
136172 CancellationToken token )
137173 {
138- NuGetVersion versionToDownload = null ;
139- SourceRepository downloadSourceRepository = null ;
174+ NuGetVersion ? versionToDownload = null ;
175+ SourceRepository ? downloadSourceRepository = null ;
140176 bool versionSpecified = packageWithNuGetVersion . NuGetVersion != null ;
141177
142178 foreach ( var repo in sourceRepositories )
@@ -188,6 +224,69 @@ await ResolvePackageDownloadVersion(
188224 return ( versionToDownload , downloadSourceRepository ) ;
189225 }
190226
227+ /// <summary>
228+ /// Builds the set of SourceRepository objects to use for a given package,
229+ /// applying package source mapping
230+ /// validating HTTP usage only on the *effective* sources.
231+ /// </summary>
232+ private static bool TryGetRepositoriesForPackage (
233+ string packageId ,
234+ PackageDownloadArgs args ,
235+ PackageSourceMapping packageSourceMapping ,
236+ IReadOnlyList < SourceRepository > allRepos ,
237+ ILoggerWithColor logger ,
238+ out IReadOnlyList < SourceRepository > repositories )
239+ {
240+ var mappedNames = packageSourceMapping . GetConfiguredPackageSources ( packageId ) ;
241+
242+ // Only validate insecure sources when mapping produced something
243+ if ( mappedNames . Count > 0 )
244+ {
245+ var mappedRepos = new List < SourceRepository > ( mappedNames . Count ) ;
246+ foreach ( var mappedName in mappedNames )
247+ {
248+ SourceRepository ? repo = null ;
249+ for ( int i = 0 ; i < allRepos . Count ; i ++ )
250+ {
251+ if ( string . Equals ( allRepos [ i ] . PackageSource . Name , mappedName , StringComparison . OrdinalIgnoreCase ) )
252+ {
253+ repo = allRepos [ i ] ;
254+ break ;
255+ }
256+ }
257+
258+ if ( repo != null )
259+ {
260+ mappedRepos . Add ( repo ) ;
261+ }
262+ else
263+ {
264+ logger . LogVerbose (
265+ string . Format (
266+ CultureInfo . CurrentCulture ,
267+ Strings . PackageDownloadCommand_PackageSourceMapping_NoSuchSource ,
268+ mappedName ,
269+ packageId ) ) ;
270+ }
271+ }
272+
273+ if ( DetectAndReportInsecureSources ( args . AllowInsecureConnections , mappedRepos . Select ( repo => repo . PackageSource ) , logger ) )
274+ {
275+ repositories = [ ] ;
276+ return false ;
277+ }
278+
279+ repositories = mappedRepos ;
280+ return true ;
281+ }
282+ else
283+ {
284+ // No mapping for this package: fall back to all sources
285+ repositories = allRepos ;
286+ return true ;
287+ }
288+ }
289+
191290 private static async Task < bool > DownloadPackageAsync (
192291 string id ,
193292 NuGetVersion version ,
@@ -239,7 +338,7 @@ private static async Task<bool> DownloadPackageAsync(
239338 return success ;
240339 }
241340
242- private static IReadOnlyList < PackageSource > GetPackageSources ( IList < string > sources , IPackageSourceProvider sourceProvider )
341+ private static IReadOnlyList < PackageSource > GetPackageSources ( IList < string > ? sources , IPackageSourceProvider sourceProvider )
243342 {
244343 IEnumerable < PackageSource > configuredSources = sourceProvider . LoadPackageSources ( )
245344 . Where ( s => s . IsEnabled ) ;
0 commit comments