@@ -883,11 +883,6 @@ private Stream InstallHelper(string packageName, NuGetVersion version, out Error
883883 {
884884 debugMsgs . Enqueue ( "In V3ServerAPICalls::InstallHelper()" ) ;
885885 Stream pkgStream = null ;
886- bool getLatestVersion = true ;
887- if ( version != null )
888- {
889- getLatestVersion = false ;
890- }
891886
892887 string [ ] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource ( packageName , packageContentProperty , isSearch : false , out errRecord , errorMsgs , debugMsgs , verboseMsgs ) ;
893888 if ( errRecord != null )
@@ -906,28 +901,10 @@ private Stream InstallHelper(string packageName, NuGetVersion version, out Error
906901 return null ;
907902 }
908903
909- string pkgContentUrl = String . Empty ;
910- if ( getLatestVersion )
911- {
912- pkgContentUrl = versionedResponses [ 0 ] ;
913- }
914- else
915- {
916- // loop through responses to find one containing required version
917- foreach ( string response in versionedResponses )
918- {
919- // Response will be "packageContent" element value that looks like: "{packageBaseAddress}/{packageName}/{normalizedVersion}/{packageName}.{normalizedVersion}.nupkg"
920- // Ex: https://api.nuget.org/v3-flatcontainer/test_module/1.0.0/test_module.1.0.0.nupkg
921- if ( response . Contains ( version . ToNormalizedString ( ) ) )
922- {
923- pkgContentUrl = response ;
924- break ;
925- }
926- }
927- }
928-
904+ string pkgContentUrl = GetPackageContentUrl ( versionedResponses , version ) ;
929905 if ( String . IsNullOrEmpty ( pkgContentUrl ) )
930906 {
907+ debugMsgs . Enqueue ( $ "No registration entry for package '{ packageName } ' had a 'catalogEntry' version matching '{ version } ' and a 'packageContent' URL.") ;
931908 errRecord = new ErrorRecord (
932909 new Exception ( $ "Package with name '{ packageName } ' and version '{ version } ' could not be found in repository '{ Repository . Name } '") ,
933910 "InstallFailure" ,
@@ -966,11 +943,6 @@ private async Task<Stream> InstallHelperAsync(string packageName, NuGetVersion v
966943 {
967944 debugMsgs . Enqueue ( "In V3ServerAPICalls::InstallHelperAsync()" ) ;
968945 Stream pkgStream = null ;
969- bool getLatestVersion = true ;
970- if ( version != null )
971- {
972- getLatestVersion = false ;
973- }
974946
975947 string [ ] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource ( packageName , packageContentProperty , isSearch : false , out ErrorRecord errRecord , errorMsgs , debugMsgs , verboseMsgs ) ;
976948 if ( errRecord != null )
@@ -990,28 +962,10 @@ private async Task<Stream> InstallHelperAsync(string packageName, NuGetVersion v
990962 return null ;
991963 }
992964
993- string pkgContentUrl = String . Empty ;
994- if ( getLatestVersion )
995- {
996- pkgContentUrl = versionedResponses [ 0 ] ;
997- }
998- else
999- {
1000- // loop through responses to find one containing required version
1001- foreach ( string response in versionedResponses )
1002- {
1003- // Response will be "packageContent" element value that looks like: "{packageBaseAddress}/{packageName}/{normalizedVersion}/{packageName}.{normalizedVersion}.nupkg"
1004- // Ex: https://api.nuget.org/v3-flatcontainer/test_module/1.0.0/test_module.1.0.0.nupkg
1005- if ( response . Contains ( version . ToNormalizedString ( ) ) )
1006- {
1007- pkgContentUrl = response ;
1008- break ;
1009- }
1010- }
1011- }
1012-
965+ string pkgContentUrl = GetPackageContentUrl ( versionedResponses , version ) ;
1013966 if ( String . IsNullOrEmpty ( pkgContentUrl ) )
1014967 {
968+ debugMsgs . Enqueue ( $ "No registration entry for package '{ packageName } ' had a 'catalogEntry' version matching '{ version } ' and a 'packageContent' URL.") ;
1015969 errorMsgs . Enqueue ( new ErrorRecord (
1016970 new Exception ( $ "Package with name '{ packageName } ' and version '{ version } ' could not be found in repository '{ Repository . Name } '") ,
1017971 "InstallFailure" ,
@@ -1039,6 +993,87 @@ private async Task<Stream> InstallHelperAsync(string packageName, NuGetVersion v
1039993 return pkgStream ;
1040994 }
1041995
996+ /// <summary>
997+ /// Selects the "packageContent" URL (i.e the .nupkg download URL) of the registration entry for the required version,
998+ /// or of the latest version if the required version is null.
999+ /// The version is taken from the entry's "catalogEntry" > "version" property and compared as a NuGetVersion,
1000+ /// rather than being inferred from the URL text (a substring search for '1.2.3' would match the URL for '1.2.30').
1001+ /// </summary>
1002+ internal static string GetPackageContentUrl ( string [ ] registrationEntries , NuGetVersion requiredVersion )
1003+ {
1004+ string pkgContentUrl = String . Empty ;
1005+ if ( registrationEntries == null )
1006+ {
1007+ return pkgContentUrl ;
1008+ }
1009+
1010+ NuGetVersion latestVersion = null ;
1011+ foreach ( string registrationEntry in registrationEntries )
1012+ {
1013+ if ( ! TryGetPackageContentEntry ( registrationEntry , out NuGetVersion entryVersion , out string entryPkgContentUrl ) )
1014+ {
1015+ continue ;
1016+ }
1017+
1018+ if ( requiredVersion != null )
1019+ {
1020+ if ( entryVersion == requiredVersion )
1021+ {
1022+ return entryPkgContentUrl ;
1023+ }
1024+ }
1025+ else if ( latestVersion == null || entryVersion > latestVersion )
1026+ {
1027+ latestVersion = entryVersion ;
1028+ pkgContentUrl = entryPkgContentUrl ;
1029+ }
1030+ }
1031+
1032+ return pkgContentUrl ;
1033+ }
1034+
1035+ /// <summary>
1036+ /// Gets the version and "packageContent" URL from a registration entry, which looks like:
1037+ /// { "catalogEntry": { "version": "1.0.0", ... }, "packageContent": "https://api.nuget.org/v3-flatcontainer/test_module/1.0.0/test_module.1.0.0.nupkg", ... }
1038+ /// </summary>
1039+ private static bool TryGetPackageContentEntry ( string registrationEntry , out NuGetVersion version , out string pkgContentUrl )
1040+ {
1041+ version = null ;
1042+ pkgContentUrl = String . Empty ;
1043+ if ( String . IsNullOrWhiteSpace ( registrationEntry ) )
1044+ {
1045+ return false ;
1046+ }
1047+
1048+ try
1049+ {
1050+ using ( JsonDocument registrationEntryJson = JsonDocument . Parse ( registrationEntry ) )
1051+ {
1052+ JsonElement rootDom = registrationEntryJson . RootElement ;
1053+ if ( rootDom . ValueKind != JsonValueKind . Object ||
1054+ ! rootDom . TryGetProperty ( packageContentProperty , out JsonElement pkgContentElement ) ||
1055+ pkgContentElement . ValueKind != JsonValueKind . String ||
1056+ ! rootDom . TryGetProperty ( catalogEntryProperty , out JsonElement catalogEntryElement ) ||
1057+ catalogEntryElement . ValueKind != JsonValueKind . Object ||
1058+ ! catalogEntryElement . TryGetProperty ( versionName , out JsonElement versionElement ) ||
1059+ ! NuGetVersion . TryParse ( versionElement . ToString ( ) , out version ) )
1060+ {
1061+ version = null ;
1062+ return false ;
1063+ }
1064+
1065+ pkgContentUrl = pkgContentElement . GetString ( ) ;
1066+ }
1067+ }
1068+ catch ( JsonException )
1069+ {
1070+ version = null ;
1071+ return false ;
1072+ }
1073+
1074+ return ! String . IsNullOrWhiteSpace ( pkgContentUrl ) ;
1075+ }
1076+
10421077 /// <summary>
10431078 /// Gets the versioned package entries from the RegistrationsBaseUrl resource
10441079 /// i.e when the package Name being searched for does not contain wildcard
@@ -1451,8 +1486,9 @@ private string[] GetMetadataElementsFromResponse(string response, string propert
14511486
14521487 if ( metadataElement . ValueKind == JsonValueKind . String )
14531488 {
1454- // This is when property is "packageContent"
1455- versionedPkgResponses . Add ( metadataElement . ToString ( ) ) ;
1489+ // This is when property is "packageContent".
1490+ // The whole entry is returned so the version can be read from its "catalogEntry" rather than inferred from the URL.
1491+ versionedPkgResponses . Add ( item . ToString ( ) ) ;
14561492 }
14571493 else if ( metadataElement . ValueKind == JsonValueKind . Object )
14581494 {
@@ -1494,7 +1530,8 @@ private string[] GetMetadataElementsFromResponse(string response, string propert
14941530 /// This contains an inner items element (containing the package metadata) and the packageContent element (containing URI through which the .nupkg can be downloaded)
14951531 /// <param name="property"> This can be the "catalogEntry" or "packageContent" property.
14961532 /// The "catalogEntry" property is used for search, and the value is package metadata.
1497- /// The "packageContent" property is used for download, and the value is a URI for the .nupkg file.
1533+ /// The "packageContent" property is used for download, and the value is the whole registration entry,
1534+ /// containing both the "catalogEntry" (with the package version) and the "packageContent" URI for the .nupkg file.
14981535 /// </param>
14991536 /// <summary>
15001537 private string [ ] GetVersionedResponsesFromRegistrationsResource ( string registrationsBaseUrl , string packageName , string property , bool isSearch , out ErrorRecord errRecord , ConcurrentQueue < ErrorRecord > errorMsgs , ConcurrentQueue < string > debugMsgs , ConcurrentQueue < string > verboseMsgs )
@@ -1526,25 +1563,16 @@ private string[] GetVersionedResponsesFromRegistrationsResource(string registrat
15261563 }
15271564
15281565 // Reverse array of versioned responses, if needed, so that version entries are in descending order.
1529- if ( String . IsNullOrEmpty ( upperVersion ) )
1566+ // Install selects its entry by comparing versions, so it does not depend on the order.
1567+ if ( String . IsNullOrEmpty ( upperVersion ) || ! isSearch )
15301568 {
15311569 // add write Debug and use these results
15321570 return versionedResponseArr ;
15331571 }
15341572
1535- if ( isSearch )
1573+ if ( ! IsLatestVersionFirstForSearch ( versionedResponseArr , out errRecord , errorMsgs , debugMsgs , verboseMsgs ) )
15361574 {
1537- if ( ! IsLatestVersionFirstForSearch ( versionedResponseArr , out errRecord , errorMsgs , debugMsgs , verboseMsgs ) )
1538- {
1539- Array . Reverse ( versionedResponseArr ) ;
1540- }
1541- }
1542- else
1543- {
1544- if ( ! IsLatestVersionFirstForInstall ( versionedResponseArr , upperVersion , out errRecord , errorMsgs , debugMsgs , verboseMsgs ) )
1545- {
1546- Array . Reverse ( versionedResponseArr ) ;
1547- }
1575+ Array . Reverse ( versionedResponseArr ) ;
15481576 }
15491577
15501578 return versionedResponseArr ;
@@ -1649,33 +1677,6 @@ private bool IsLatestVersionFirstForSearch(string[] versionedResponses, out Erro
16491677 return latestVersionFirst ;
16501678 }
16511679
1652- /// <summary>
1653- /// Returns true if the nupkg URI entries for each package version are arranged in descending order with respect to the package's version.
1654- /// ADO feeds usually return version entries in descending order, but Nuget.org repository returns them in ascending order.
1655- /// Entries do not reflect prerelease preference so all versions (including prerelease) are being considered here, so upper version (including prerelease) can be used for comparison.
1656- /// </summary>
1657- private bool IsLatestVersionFirstForInstall ( string [ ] versionedResponses , string upperVersion , out ErrorRecord errRecord , ConcurrentQueue < ErrorRecord > errorMsgs , ConcurrentQueue < string > debugMsgs , ConcurrentQueue < string > verboseMsgs )
1658- {
1659- debugMsgs . Enqueue ( "In V3ServerAPICalls::IsLatestVersionFirstForInstall()" ) ;
1660- errRecord = null ;
1661- bool latestVersionFirst = true ;
1662-
1663- // We don't need to perform this check if no responses, or single response
1664- if ( versionedResponses . Length < 2 )
1665- {
1666- return latestVersionFirst ;
1667- }
1668-
1669- string firstResponse = versionedResponses [ 0 ] ;
1670- // for Install, response will be a URI value for the package .nupkg, not JSON
1671- if ( ! firstResponse . Contains ( upperVersion ) )
1672- {
1673- latestVersionFirst = false ;
1674- }
1675-
1676- return latestVersionFirst ;
1677- }
1678-
16791680 /// <summary>
16801681 /// Helper method that determines if specified tags are present in package's tags.
16811682 /// </summary>
0 commit comments