NuGet Analyzer Improvements

* Fixes Artifactory compatibility including outdated component detection
* Drops the PackageBaseAddress / flat-container approach to version
  number retrieval. The old approach was simpler and faster but
  Artifactory didn't support it and it doesn't exclude unlisted versions
* Can specify a "fully qualified" repository URL including "index.json"
  to support Artifactory and Nexus URLs (#5040)
* Suppresses pre-release versions (#1711) unless no stable release
  versions exist in accordance with #5075 behaviour
* Suppresses unlisted versions to avoid recommending versions which may
  have been resolved due to critical bugs
* Caches the RegistrationsBaseUrl for up to 15 minutes to remove the
  lookup overhead when performing bulk version checking
* Replaces SUPPORTED_DATE_FORMATS with thread-safe DateTimeFormatter
* Expanded test coverage, removed old flat-container based tests
* Removed printStackTrace call from AbstractMetaAnalyzer - throwable is
  passed to logger.error call

Signed-off-by: colinfyfe <colinfyfe@protonmail.com>
This commit is contained in:
colinfyfe 2025-09-15 16:11:06 +01:00
parent f27488fdf8
commit a522553690
24 changed files with 30482 additions and 299 deletions

View file

@ -88,7 +88,9 @@ public abstract class AbstractMetaAnalyzer implements IMetaAnalyzer {
final String statusText, final Component component) {
logger.debug("HTTP Status : " + statusCode + " " + statusText);
logger.debug(" - RepositoryType URL : " + url);
if(component != null && component.getPurl() != null) {
logger.debug(" - Package URL : " + component.getPurl().canonicalize());
}
Notification.dispatch(new Notification()
.scope(NotificationScope.SYSTEM)
.group(NotificationGroup.REPOSITORY)
@ -100,7 +102,6 @@ public abstract class AbstractMetaAnalyzer implements IMetaAnalyzer {
protected void handleRequestException(final Logger logger, final Exception e) {
logger.error("Request failure for repository URL: " + baseUrl, e);
e.printStackTrace();
Notification.dispatch(new Notification()
.scope(NotificationScope.SYSTEM)
.group(NotificationGroup.REPOSITORY)

View file

@ -51,4 +51,14 @@ public class MetaModel {
public void setPublishedTimestamp(final Date publishedTimestamp) {
this.publishedTimestamp = publishedTimestamp;
}
@Override
public String toString() {
return "MetaModel{" +
"component=" + component +
", latestVersion='" + latestVersion + '\'' +
", publishedTimestamp=" + publishedTimestamp +
'}';
}
}

View file

@ -19,6 +19,8 @@
package org.dependencytrack.tasks.repositories;
import alpine.common.logging.Logger;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.packageurl.PackageURL;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
@ -31,50 +33,110 @@ import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* An IMetaAnalyzer implementation that supports Nuget.
* <p>
* Excludes pre-release and unlisted versions. Pre-release versions will be considered if no stable release exists.
* Unlisted versions are excluded because they have been deliberately hidden for a specific safety reason.
* To quote <a href="https://learn.microsoft.com/en-us/nuget/nuget-org/policies/deleting-packages">the Microsoft Nuget
* docs</a>:
* <blockquote>"Unlisting a package version hides it from search and from nuget.org package details page. This allows
* existing users of the package to continue using it but reduces new adoption since the package is not visible in
* search".</blockquote>
* For an example package, see
* <a href="https://www.nuget.org/packages/Microsoft.Data.SqlClient/6.1.0">Microsoft.Data.SqlClient/6.1.0</a>
* which states:
* <blockquote>This package has been deprecated as it has critical bugs</blockquote> and
* <blockquote>The owner has unlisted this package. This could mean that the package is deprecated, has security
* vulnerabilities or shouldn't be used anymore.</blockquote>
* Dependency Track won't prevent users from using an unlisted package, but it won't encourage it.
* <p>
* We do not use the PackageBaseAddress resource to retrieve version information - it would be faster and simpler
* but, to <a href="https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource">quote Microsoft</a>:
* <blockquote>This list contains both listed and unlisted package versions.</blockquote>
* Artifactory doesn't provide the PackageBaseAddress resource,
* <a href="https://learn.microsoft.com/en-us/nuget/api/overview">despite Microsoft stating it is required</a>. JFrog
* <a href="https://jfrog.com/help/r/jfrog-artifactory-documentation/nuget-repositories">state here it is not supported
* as at September 2025./a>.
* <p>
* Artifactory also doesn't provide "published" data as of August 2025 so no dates will be included in the response from
* those feeds. Other third party feeds may also omit the published dates.
*
* @author Steve Springett
* @since 3.4.0
*/
public class NugetMetaAnalyzer extends AbstractMetaAnalyzer {
public static final DateFormat[] SUPPORTED_DATE_FORMATS = new DateFormat[]{
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
};
private static final Logger LOGGER = Logger.getLogger(NugetMetaAnalyzer.class);
private static final String DEFAULT_BASE_URL = "https://api.nuget.org";
private static final String INDEX_URL = "/v3/index.json";
private static final String DEFAULT_BASE_URL = "https://api.nuget.org/v3/index.json";
private static final String NUGET_KEY_UPPER = "upper";
private static final String NUGET_KEY_ITEMS = "items";
private static final String DEFAULT_VERSION_QUERY_ENDPOINT = "/v3-flatcontainer/%s/index.json";
private static final Cache<String, String> REPO_REGISTRATION_URL_CACHE = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(15, TimeUnit.MINUTES)
.build();
private static final String DEFAULT_REGISTRATION_ENDPOINT = "/v3/registration5-gz-semver2/%s/%s.json";
private String serviceIndexUrl;
private String registrationsBaseUrl = "";
private String versionQueryUrl;
private String registrationUrl;
NugetMetaAnalyzer() {
this.baseUrl = DEFAULT_BASE_URL;
// Set defaults that work with NuGet.org just in case the index endpoint is not available
this.versionQueryUrl = baseUrl + DEFAULT_VERSION_QUERY_ENDPOINT;
this.registrationUrl = baseUrl + DEFAULT_REGISTRATION_ENDPOINT;
}
/**
* Sets the repository base URL which will then be used to retrieve and parse the service index. If the user has
* specified a repo URL ending with index.json, it should be considered "fully qualified" and used as is to maximise
* compatability with non-nuget.org repos such as Artifactory. If not, preserve the previous Dependency Track
* behaviour of appending the nuget.org index to the supplied URL.
* @param baseUrl the base URL to the repository
*/
@Override
public void setRepositoryBaseUrl(String baseUrl) {
super.setRepositoryBaseUrl(baseUrl);
initializeEndpoints();
if (baseUrl.toLowerCase().endsWith("index.json")) {
this.serviceIndexUrl = baseUrl;
} else {
this.serviceIndexUrl = stripTrailingSlash(baseUrl) + "/v3/index.json";
}
this.setRegistrationsBaseUrl(REPO_REGISTRATION_URL_CACHE.get(this.serviceIndexUrl, key -> findRegistrationsBaseUrl()));
}
/**
* Sets the RegistrationsBaseUrl to be used to retrieve package metadata. This is primarily intended
* for testing as setRepositoryBaseUrl should find and set the best URL automatically. Call this method AFTER
* setRepositoryBaseUrl to ensure your value is not overridden.
*
* @param registrationsBaseUrlStem Registrations URL to be set
*/
public void setRegistrationsBaseUrl(String registrationsBaseUrlStem) {
if(registrationsBaseUrlStem == null || registrationsBaseUrlStem.isBlank()) {
return;
}
this.registrationsBaseUrl = stripTrailingSlash(registrationsBaseUrlStem) + "/%s/%s.json";
}
private static String stripTrailingSlash(String input) {
if (input == null || input.isBlank()) {
return input;
}
return input.endsWith("/") ? input.substring(0, input.length() - 1) : input;
}
/**
@ -95,117 +157,313 @@ public class NugetMetaAnalyzer extends AbstractMetaAnalyzer {
* {@inheritDoc}
*/
public MetaModel analyze(final Component component) {
if(component == null) {
throw new IllegalArgumentException("Component cannot be null");
}
final MetaModel meta = new MetaModel(component);
if (component.getPurl() != null) {
if (performVersionCheck(meta, component)) {
performLastPublishedCheck(meta, component);
}
LOGGER.debug("Analyzing component: " + component.getPurl().getName() + " (Internal: " + component.isInternal() + ")");
performVersionCheck(meta, component);
}
LOGGER.debug("Results: " + meta);
return meta;
}
private boolean performVersionCheck(final MetaModel meta, final Component component) {
final String url = String.format(versionQueryUrl, urlEncode(component.getPurl().getName().toLowerCase()));
try (final CloseableHttpResponse response = processHttpRequest(url)) {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
if (response.getEntity() != null) {
String responseString = EntityUtils.toString(response.getEntity());
var jsonObject = new JSONObject(responseString);
final JSONArray versions = jsonObject.getJSONArray("versions");
/**
* Attempts to find the latest version of the supplied component and return its published date, if one exists.
* Ignores pre-release and unlisted versions.
* @param meta {@link MetaModel} to be updated with detected version information
* @param component {@link Component} to be looked up in the NuGet repo
*/
private void performVersionCheck(final MetaModel meta, final Component component) {
String latest = findLatestVersion(filterPreReleaseVersions(versions));
if (latest == null) {
latest = findLatestVersion(versions);
if (registrationsBaseUrl == null || registrationsBaseUrl.isBlank()) {
LOGGER.debug("Registration URL not defined for repo " + this.serviceIndexUrl + " - skipping version check");
return;
}
meta.setLatestVersion(latest);
LOGGER.debug("Performing version check for: " + component.getPurl().getName() + " using " + registrationsBaseUrl);
try {
final var packageRegistrationRoot = fetchPackageRegistrationIndex(registrationsBaseUrl, component);
if(packageRegistrationRoot == null) {
return;
}
return true;
} else {
handleUnexpectedHttpResponse(LOGGER, url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), component);
// Search for a release version first...
AbridgedNugetCatalogEntry abridgedNugetCatalogEntry = findLatestViaRegistrations(packageRegistrationRoot, false);
// ... then try again if none found, looking for the latest pre-release version
if (abridgedNugetCatalogEntry == null) {
abridgedNugetCatalogEntry = findLatestViaRegistrations(packageRegistrationRoot, true);
}
if (abridgedNugetCatalogEntry != null) {
meta.setLatestVersion(abridgedNugetCatalogEntry.getVersion());
meta.setPublishedTimestamp(abridgedNugetCatalogEntry.getPublishedTimestamp());
}
} catch (IOException e) {
handleRequestException(LOGGER, e);
} catch (Exception ex) {
throw new MetaAnalyzerException(ex);
}
return false;
}
private String findLatestVersion(JSONArray versions) {
/**
* Retrieves the package registration index for the specified component
* (e.g. https://api.nuget.org/v3/registration5-gz-semver2/microsoft.data.sqlclient/index.json) and converts to JSON
* @param registrationsBaseUrl Registration base URL to look up package info
* @param component Component for which retrieve package registration data should be retrieved
* @return JSONObject containing package data if found or null if data not found
* @throws IOException if HTTP request errors
*/
private JSONObject fetchPackageRegistrationIndex(final String registrationsBaseUrl, final Component component) throws IOException {
final String idLower = urlEncode(component.getPurl().getName().toLowerCase());
final String indexUrl = String.format(registrationsBaseUrl, idLower, "index");
if (versions.isEmpty()) {
try (final CloseableHttpResponse resp = processHttpRequest(indexUrl)) {
if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK || resp.getEntity() == null) {
handleUnexpectedHttpResponse(LOGGER, indexUrl, resp.getStatusLine().getStatusCode(), resp.getStatusLine().getReasonPhrase(), component);
return null;
}
return new JSONObject(EntityUtils.toString(resp.getEntity()));
}
}
/**
* Parses the NuGet Registrations to find latest version information. Handles both inline items and paged items.
* Sorts pages in descending order by the upper version number - if a listed, final version can be found in that
* page, it will be returned or pages will be searched in descending order until a match is found.
* @param registrationData Registrations to be searched
* @return Version metadata if a suitable version found, or null if not
* @throws IOException if network error occurs
*/
private AbridgedNugetCatalogEntry findLatestViaRegistrations(final JSONObject registrationData, final boolean includePreRelease) throws IOException {
final JSONArray pages = registrationData == null ? null : registrationData.optJSONArray(NUGET_KEY_ITEMS);
if (pages == null || pages.isEmpty()) {
return null;
}
ComparableVersion latestVersion = new ComparableVersion(versions.getString(0));
for (int i = 1; i < versions.length(); i++) {
ComparableVersion version = new ComparableVersion(versions.getString(i));
if (version.compareTo(latestVersion) > 0) {
latestVersion = version;
// Build a list of pages sorted by descending "upper" property.
final List<JSONObject> pageUpperBounds = new ArrayList<>();
for (int i = 0; i < pages.length(); i++) {
final JSONObject page = pages.optJSONObject(i);
if (page != null && page.has(NUGET_KEY_UPPER)) {
pageUpperBounds.add(page);
}
}
return latestVersion.toString();
}
// Sort upper page bounds in descending order to get newest page first e.g, [ "6.1.0", "5.1.0" ]
pageUpperBounds.sort((pageOne, pageTwo) -> {
final ComparableVersion pageOneUpper = new ComparableVersion(pageOne.optString(NUGET_KEY_UPPER, "0"));
final ComparableVersion pageTwoUpper = new ComparableVersion(pageTwo.optString(NUGET_KEY_UPPER, "0"));
return pageTwoUpper.compareTo(pageOneUpper); // descending
});
private JSONArray filterPreReleaseVersions(JSONArray versions) {
JSONArray filteredVersions = new JSONArray();
for (int i = 0; i < versions.length(); i++) {
if (!versions.getString(i).contains("-")) {
filteredVersions.put(versions.getString(i));
}
}
return filteredVersions;
}
private boolean performLastPublishedCheck(final MetaModel meta, final Component component) {
final String url = String.format(registrationUrl, urlEncode(component.getPurl().getName().toLowerCase()), urlEncode(meta.getLatestVersion()));
try (final CloseableHttpResponse response = processHttpRequest(url)) {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
if (response.getEntity() != null) {
String stringResponse = EntityUtils.toString(response.getEntity());
if (!stringResponse.equalsIgnoreCase("") && !stringResponse.equalsIgnoreCase("{}")) {
JSONObject jsonResponse = new JSONObject(stringResponse);
final String updateTime = jsonResponse.optString("published", null);
if (updateTime != null) {
meta.setPublishedTimestamp(parseUpdateTime(updateTime));
}
return true;
}
}
} else {
handleUnexpectedHttpResponse(LOGGER, url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), component);
}
} catch (IOException e) {
handleRequestException(LOGGER, e);
} catch (Exception ex) {
throw new MetaAnalyzerException(ex);
}
return false;
}
private void initializeEndpoints() {
final String url = baseUrl + INDEX_URL;
for (final JSONObject page : pageUpperBounds) {
try {
try (final CloseableHttpResponse response = processHttpRequest(url)) {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
if(response.getEntity()!=null){
String responseString = EntityUtils.toString(response.getEntity());
JSONObject responseJson = new JSONObject(responseString);
final JSONArray resources = responseJson.getJSONArray("resources");
final JSONObject packageBaseResource = findResourceByType(resources, "PackageBaseAddress");
final JSONObject registrationsBaseResource = findResourceByType(resources, "RegistrationsBaseUrl/Versioned");
if (packageBaseResource != null && registrationsBaseResource != null) {
versionQueryUrl = packageBaseResource.getString("@id") + "%s/index.json";
registrationUrl = registrationsBaseResource.getString("@id") + "%s/%s.json";
final JSONArray leaves = resolveLeaves(page);
final AbridgedNugetCatalogEntry bestOnPage = findHighestVersionFromLeaves(leaves, includePreRelease);
if (bestOnPage != null) {
return bestOnPage;
}
} catch (MetaAnalyzerException ex) {
// Reporting handled in resolveLeaves - return null to avoid returning incorrect version from any page
return null;
}
}
// No suitable version found
return null;
}
/**
* Parse the page JSON to find item leaves, retrieving data from the repo if needed. Returns null if neither
* inline items nor a fetchable @id exist/succeed.
* @param page Page to be parsed
* @return JSONArray containing leaf data if available, null if not
* @throws IOException if network error occurs
* @throws MetaAnalyzerException if the repo returns an unexpected result
*/
private JSONArray resolveLeaves(final JSONObject page) throws IOException, MetaAnalyzerException {
if (page == null) {
return null;
}
final JSONArray inline = page.optJSONArray(NUGET_KEY_ITEMS);
if (inline != null && !inline.isEmpty()) {
LOGGER.trace("Processing inline catalog entries");
return inline;
}
final String pageUrl = page.optString("@id", null);
if (pageUrl == null || pageUrl.isBlank()) {
return null;
}
LOGGER.trace("Retrieving catalog entry page " + pageUrl);
try (final CloseableHttpResponse resp = processHttpRequest(pageUrl)) {
if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK && resp.getEntity() != null) {
final var pageJson = new JSONObject(EntityUtils.toString(resp.getEntity()));
return pageJson.optJSONArray(NUGET_KEY_ITEMS);
} else {
handleUnexpectedHttpResponse(LOGGER, pageUrl, resp.getStatusLine().getStatusCode(), resp.getStatusLine().getReasonPhrase(), null);
throw new MetaAnalyzerException("Could not retrieve catalog entry page when processing " + pageUrl);
}
}
}
/**
* Scan the supplied leaves to extract the latest listed version. NuGet does not guarantee release order
* so scan the entire array although, anecdotally, the collection does generally appear to be in ascending order
* @param leaves Items to be scanned
* @param includePreRelease include pre-release versions in latest version lookup
* @return {@link AbridgedNugetCatalogEntry containing the latest version found in the leaves collection
*/
private AbridgedNugetCatalogEntry findHighestVersionFromLeaves(final JSONArray leaves, final boolean includePreRelease) {
if (leaves == null || leaves.isEmpty()) {
return null;
}
AbridgedNugetCatalogEntry bestEntry = null;
ComparableVersion newestVersionFound = null;
for (int i = 0; i < leaves.length(); i++) {
final JSONObject leaf = leaves.optJSONObject(i);
AbridgedNugetCatalogEntry entry = null;
if (leaf.has("catalogEntry")) {
entry = parseCatalogEntry(leaf.optJSONObject("catalogEntry"));
}
if (entry == null || entry.getVersion() == null || (isPreRelease(entry.getVersion()) && !includePreRelease)) {
continue;
}
final ComparableVersion entryVersion = new ComparableVersion(entry.getVersion());
if (newestVersionFound == null || entryVersion.compareTo(newestVersionFound) > 0) {
newestVersionFound = entryVersion;
bestEntry = entry;
}
}
return bestEntry;
}
/**
* Parse a single catalog entry to extract the version and published information. Could be extended to include other
* fields (such as listed) if required. Returns null immediately if the entry is unlisted.
* @param catalogEntry Catalog entry to be parsed
* @return {@link AbridgedNugetCatalogEntry} if version is valid, null if not
*/
private AbridgedNugetCatalogEntry parseCatalogEntry(final JSONObject catalogEntry) {
// Listed is optional so assume package is listed unless explicitly hidden
boolean listed = catalogEntry.optBoolean("listed", true);
if(!listed) {
return null;
}
var version = catalogEntry.optString("version", null);
if (version == null || version.isBlank()) {
return null;
}
AbridgedNugetCatalogEntry entry = new AbridgedNugetCatalogEntry();
entry.setVersion(version);
var updateTime = catalogEntry.optString("published", null);
if (updateTime != null) {
entry.setPublishedTimestamp(parseUpdateTime(updateTime));
}
return entry;
}
/**
* NuGet considers a version string with any suffix after a hyphen to be pre-release according to <a
* href="https://learn.microsoft.com/en-us/nuget/concepts/package-versioning?tabs=semver20sort#pre-release-versions">
* the documentation</a>. This method could be expanded if we need to cover other rules.
* @param version Version string to be tested
* @return True if version matches pre-release conventions, false otherwise
*/
private boolean isPreRelease(final String version) {
return version.contains("-");
}
/**
* Connects to the NuGet repo, retrieves the service index and attempts to find the best RegistrationsBaseUrl
* @return RegistrationsBaseUrl if found, null otherwise
*/
private String findRegistrationsBaseUrl() {
JSONObject responseJson = null;
try (final CloseableHttpResponse resp = processHttpRequest(this.serviceIndexUrl)) {
if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK && resp.getEntity() != null) {
responseJson = new JSONObject(EntityUtils.toString(resp.getEntity()));
} else {
handleUnexpectedHttpResponse(LOGGER, this.serviceIndexUrl, resp.getStatusLine().getStatusCode(), resp.getStatusLine().getReasonPhrase(), null);
throw new MetaAnalyzerException("Could not initialize NugetMetaAnalyzer - unexpected response from NuGet service. Response code was " + resp.getStatusLine().getStatusCode() + ".");
}
} catch (IOException e) {
handleRequestException(LOGGER, e);
}
if (responseJson != null) {
final JSONArray resources = responseJson.optJSONArray("resources");
final String regBaseUrl = extractRegistrationBaseUrlFromJson(resources);
if (regBaseUrl != null) {
LOGGER.debug("RegistrationsBaseUrl selected: " + regBaseUrl);
return regBaseUrl;
}
}
LOGGER.debug("Could not find the RegistrationsBaseUrl at " + this.serviceIndexUrl);
return null;
}
/**
* Attempts to find the "best" RegistrationsBaseUrl from the NuGet service index preferring SemVer 2 with
* compression, SemVer 2 without compression then non-compressed, non-SemVer2.
* See <a href="https://learn.microsoft.com/en-us/nuget/api/registration-base-url-resource>for versioning
* details</a>
* @param serviceIndexJson JSONArray containing the NuGet repo service index
* @return RegistrationsBaseUrl or null if none found
*/
private String extractRegistrationBaseUrlFromJson(JSONArray serviceIndexJson) {
if (serviceIndexJson == null) {
return null;
}
// Prefer SemVer2 registrations if available
final JSONObject regsSemver2 = findResourceByType(serviceIndexJson, "RegistrationsBaseUrl/3.6.0");
if (regsSemver2 != null) {
return regsSemver2.optString("@id", null);
}
// Failing that, check for the gzipped registration hive
final JSONObject regsGzipped = findResourceByType(serviceIndexJson, "RegistrationsBaseUrl/3.4.0");
if (regsGzipped != null) {
return regsGzipped.optString("@id", null);
}
// Worst case, check for the non-gzipped version
final JSONObject fallback = findResourceByType(serviceIndexJson, "RegistrationsBaseUrl");
if (fallback != null) {
return fallback.optString("@id", null);
}
return null;
}
private JSONObject findResourceByType(JSONArray resources, String type) {
@ -219,20 +477,66 @@ public class NugetMetaAnalyzer extends AbstractMetaAnalyzer {
return null;
}
private Date parseUpdateTime(String updateTime) {
if (updateTime == null) {
/**
* Attempts to parse a NuGet date time string to a {@link Date}. NuGet repositories may use differing date formats
* so this method tries a couple of attempts. The
* <a href="https://learn.microsoft.com/en-us/nuget/api/registration-base-url-resource#catalog-entry">MS
* spec states that ISO8601 should be used</a> but that standard is flexible when it comes to timezone info.
* <p>
* The ISO_INSTANT formatter handles time with timezone and milliseconds ("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") and
* UTC-only ("yyyy-MM-dd'T'HH:mm:ss'Z'"). A fallback LocalDateTime parser handles cases without a timezone
* ("yyyy-MM-dd'T'HH:mm:ss").
* @param nugetDateTimeString Date time string in one of NuGet's permitted formats
* @return Date if input could be parsed, null if date could not be parsed
*/
protected Date parseUpdateTime(String nugetDateTimeString) {
if (nugetDateTimeString == null) {
return null;
}
// NuGet repositories may use differing date formats, so we try a few date formats that are commonly used until the right one is found.
for (DateFormat dateFormat : SUPPORTED_DATE_FORMATS) {
try {
return dateFormat.parse(updateTime);
} catch (ParseException e) {
LOGGER.warn("An error occurred while parsing upload time for a NuGet component - Repo returned: " + updateTime);
TemporalAccessor ta = DateTimeFormatter.ISO_INSTANT.parse(nugetDateTimeString);
return Date.from(Instant.from(ta));
} catch (DateTimeParseException e) {
try {
LocalDateTime localDateTime = LocalDateTime.parse(nugetDateTimeString);
return Date.from(localDateTime.atOffset(ZoneOffset.UTC).toInstant());
} catch (DateTimeParseException e2) {
return null;
}
}
}
return null;
/**
* Internal class to collate useful version information from the larger NuGet catalog entry
*/
private static class AbridgedNugetCatalogEntry {
private String version;
private Date publishedTimestamp;
private String getVersion() {
return version;
}
private void setVersion(String version) {
this.version = version;
}
private Date getPublishedTimestamp() {
return publishedTimestamp;
}
private void setPublishedTimestamp(Date publishedTimestamp) {
this.publishedTimestamp = publishedTimestamp;
}
@Override
public String toString() {
return "AbridgedNugetCatalogEntry{" +
"version='" + version + '\'' +
", publishedTimestamp=" + publishedTimestamp +
'}';
}
}
}

View file

@ -22,148 +22,550 @@ import com.github.packageurl.PackageURL;
import org.apache.http.HttpHeaders;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.RepositoryType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockserver.client.MockServerClient;
import org.mockserver.integration.ClientAndServer;
import java.util.stream.Stream;
import org.mockserver.model.Header;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static org.dependencytrack.tasks.repositories.NugetMetaAnalyzer.SUPPORTED_DATE_FORMATS;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
class NugetMetaAnalyzerTest {
public static final String LOCALHOST_REPO_INDEX = "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/index.json";
private static ClientAndServer mockServer;
@BeforeAll
public static void beforeClass() {
static void beforeClass() throws Exception {
mockServer = ClientAndServer.startClientAndServer(1080);
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/index.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.v3-index.json",
null, "application/json", 200
);
}
private static void setupMockServerClient(
String path,
String responseFile,
String encodedBasicHeader
) throws Exception {
setupMockServerClient(path, responseFile, encodedBasicHeader, "application/json", 200);
}
private static void setupMockServerClient(
String path,
String responseFile,
String encodedBasicHeader,
String contentType,
int statusCode
) throws Exception {
List<Header> headers = new ArrayList<>();
if (encodedBasicHeader != null) {
headers.add(new Header("Authorization", encodedBasicHeader));
}
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("GET")
.withPath(path)
.withHeaders(headers)
)
.respond(
response()
.withStatusCode(statusCode)
.withHeader(HttpHeaders.CONTENT_TYPE, contentType)
.withBody(Files.readString(Paths.get(NugetMetaAnalyzerTest.class.getResource(responseFile).toURI())))
);
}
@AfterAll
public static void afterClass() {
static void afterClass() {
mockServer.stop();
}
// This test is to check if the analyzer is:
// * excluding pre-release versions if a release version exists,
// * including pre-release versions if no release version exists
// The test is transient depending on the current version of the package
// retrieved from the repository at the time of running.
// For example, when it was created, the latest released version of:
// * Microsoft.Extensions.DependencyInjection was 9.0.0-preview.1.24080.9
// * OpenTelemetry.Instrumentation.SqlClient was 1.12.0-beta.2 (no release version exists)
@ParameterizedTest
@MethodSource("testAnalyzerData")
void testAnalyzer(String purl, boolean isLatestVersionPreRelease) throws Exception {
Component component = new Component();
component.setPurl(new PackageURL(purl));
NugetMetaAnalyzer analyzer = new NugetMetaAnalyzer();
/**
* Various tests to confirm error handling behaviour when, e.g. the repo or package cannot
* be found. The analyzer should still return a MetaModel in these cases with null version and
* published. The analyzer should NOT crash.
*/
@Nested
class ErrorHandlingTests {
@Test
void testBaseUrlNotFoundBehaviourWhenSettingRepoUrl() {
Assertions.assertDoesNotThrow(() -> {
var analyzer = new NugetMetaAnalyzer();
analyzer.setRepositoryBaseUrl("http://no-such-api.this-host-does-not-exist-nuget-repo.invalid");
});
}
@Test
void testBaseUrlNotFoundBehaviourWhenCallingAnalyze() {
Assertions.assertDoesNotThrow(() -> {
var analyzer = new NugetMetaAnalyzer();
analyzer.setRepositoryBaseUrl("http://no-such-api.this-host-does-not-exist-nuget-repo.invalid");
Component component = new Component();
component.setPurl(new PackageURL("pkg:nuget/CycloneDX.Core@5.4.0"));
MetaModel metaModel = analyzer.analyze(component);
Assertions.assertTrue(analyzer.isApplicable(component));
assertMetaModelExistsButEmpty(analyzer, metaModel);
});
}
@Test
void testRepoValidButPackageNotFoundBehaviour() throws Exception {
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/testing.no.such.package/index.json",
"/unit/tasks/repositories/https---nuget.org.no-such-package.xml",
null,
"application/xml",
404
);
Component component = new Component();
component.setPurl(new PackageURL("pkg:nuget/Testing.No.Such.Package@8.0.0"));
var analyzer = new NugetMetaAnalyzer();
analyzer.setRepositoryBaseUrl(LOCALHOST_REPO_INDEX);
MetaModel metaModel = analyzer.analyze(component);
assertMetaModelExistsButEmpty(analyzer, metaModel);
}
@Test
void testErrorBetweenPageRequestsReturnsNullData() throws Exception {
var analyzer = new NugetMetaAnalyzer();
analyzer.setRepositoryBaseUrl(LOCALHOST_REPO_INDEX);
var component = new Component();
component.setName("Microsoft.Data.SqlClient");
component.setPurl(new PackageURL("pkg:nuget/Microsoft.Data.SqlClient@5.1.0"));
mockServer.reset();
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/index.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.v3-index.json",
null, "application/json", 200
);
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/index.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver2.mds.index.json",
null
);
// Page 2
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("GET")
.withPath("/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/5.1.1/6.1.0.json")
)
.respond(
response()
.withStatusCode(401)
.withHeader(HttpHeaders.CONTENT_TYPE, "application/xml")
.withBody("<?xml version=\"1.0\" encoding=\"utf-8\"?><Error><Code>TestError</Code><Message>Not Authorised</Message></Error>")
);
// Page 1
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/1.0.19123.2-preview/5.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver2.mds.page1.json",
null
);
MetaModel metaModel = analyzer.analyze(component);
Assertions.assertNull(metaModel.getLatestVersion());
Assertions.assertNull(metaModel.getPublishedTimestamp());
}
@Test
void testNullComponentThrowsIllegalArgumentException() {
var analyzer = new NugetMetaAnalyzer();
analyzer.setRepositoryBaseUrl(LOCALHOST_REPO_INDEX);
Assertions.assertThrows(IllegalArgumentException.class, () -> analyzer.analyze(null));
}
}
private void assertMetaModelExistsButEmpty(NugetMetaAnalyzer analyzer, MetaModel metaModel) {
Assertions.assertEquals(RepositoryType.NUGET, analyzer.supportedRepositoryType());
Assertions.assertNull(metaModel.getLatestVersion());
Assertions.assertNull(metaModel.getPublishedTimestamp());
}
/**
* Tests against JSON files captured from nuget.org to avoid making live calls and to control test data state. Main
* difference between Nuget and the Artifactory tests is Nuget includes a published date value. To run these tests
* against the live nuget feed, simply change the URL in the setup method to api.nuget.org - you can ignore the
* MockServerClient calls because they won't be invoked.
*/
@Nested
class NugetTests {
private Component component;
private NugetMetaAnalyzer analyzer;
@BeforeEach
void setUp() throws Exception {
this.component = new Component();
this.component.setInternal(false);
this.component.setName("Microsoft.Data.SqlClient");
this.component.setPurl(new PackageURL("pkg:nuget/Microsoft.Data.SqlClient@5.0.1"));
this.analyzer = new NugetMetaAnalyzer();
this.analyzer.setRepositoryBaseUrl("https://api.nuget.org");
}
@Test
void testAnalyzerWithMultipleInlinePages() throws Exception {
// This test also effectively covers pre-release versions (e.g. 6.1.0-preview2.25178.5)
// and unlisted versions (6.1.0) by returning 6.0.2
setupMockServerClient(
"/v3/index.json",
"/unit/tasks/repositories/https---nuget.org.v3-index.json",
null
);
setupMockServerClient(
"/v3/registration5-gz-semver2/microsoft.data.sqlclient/index.json",
"/unit/tasks/repositories/https---nuget.org.registration-semver2.mds.index-inline-pages.json",
null
);
this.analyzer.setRepositoryBaseUrl("http://localhost:1080/v3/index.json");
analyzer.setRepositoryBaseUrl("https://api.nuget.org");
MetaModel metaModel = analyzer.analyze(component);
Assertions.assertTrue(analyzer.isApplicable(component));
Assertions.assertEquals(RepositoryType.NUGET, analyzer.supportedRepositoryType());
Assertions.assertNotNull(metaModel.getLatestVersion());
Assertions.assertEquals("6.0.2", metaModel.getLatestVersion());
// nuget feeds should return a published date
Assertions.assertNotNull(metaModel.getPublishedTimestamp());
Assertions.assertEquals(isLatestVersionPreRelease, metaModel.getLatestVersion().contains("-"));
Date expected = analyzer.parseUpdateTime("2025-04-25T21:29:47.897+00:00");
Assertions.assertEquals(expected, metaModel.getPublishedTimestamp());
}
static Stream<Arguments> testAnalyzerData() {
return Stream.of(
Arguments.of("pkg:nuget/CycloneDX.Core@5.4.0", false),
Arguments.of("pkg:nuget/Microsoft.Extensions.DependencyInjection@8.0.0", false),
Arguments.of("pkg:nuget/Microsoft.Extensions.DependencyInjection@8.0.0-beta.21301.5", false),
Arguments.of("pkg:nuget/OpenTelemetry.Instrumentation.SqlClient@1.12.0-beta.1", true)
}
/**
* Artifactory doesn't provide published dates and favours (only uses?) paged registration data.
* This collection uses the service index to find the best RegistrationsBaseUrl, in this case
* for semver2
*/
@Nested
class ArtifactoryTestsSemver2Tests {
NugetMetaAnalyzer analyzer;
Component component;
@BeforeEach
void setUp() throws Exception {
this.analyzer = new NugetMetaAnalyzer();
this.analyzer.setRepositoryBaseUrl(LOCALHOST_REPO_INDEX);
this.component = new Component();
this.component.setName("Microsoft.Data.SqlClient");
this.component.setPurl(new PackageURL("pkg:nuget/Microsoft.Data.SqlClient@5.1.0"));
mockServer.reset();
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/index.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.v3-index.json",
null, "application/json", 200
);
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/index.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver2.mds.index.json",
null
);
}
@Test
void testAnalyzerWithPrivatePackageRepository() throws Exception {
String mockIndexResponse = readResourceFileToString("/unit/tasks/repositories/https---localhost-1080-v3-index.json");
new MockServerClient("localhost", mockServer.getPort())
.when(
request()
.withMethod("GET")
.withPath("/v3/index.json")
)
.respond(
response()
.withStatusCode(200)
.withHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.withBody(mockIndexResponse)
);
String encodedBasicHeader = "Basic OnBhc3N3b3Jk";
void testAnalyzerWithMultipageRegistrationInfo() throws Exception {
String mockVersionResponse = readResourceFileToString("/unit/tasks/repositories/https---localhost-1080-v3-flat2" +
"-nunitprivate-index.json");
new MockServerClient("localhost", mockServer.getPort())
.when(
request()
.withMethod("GET")
.withPath("/v3/flat2/nunitprivate/index.json")
.withHeader("Authorization", encodedBasicHeader)
)
.respond(
response()
.withStatusCode(200)
.withHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.withBody(mockVersionResponse)
// Page 2
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/5.1.1/6.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver2.mds.page2.json",
null
);
String mockRegistrationResponse = readResourceFileToString("/unit/tasks/repositories/https---localhost-1080-v3" +
"-registrations2-nunitprivate-502.json");
new MockServerClient("localhost", mockServer.getPort())
.when(
request()
.withMethod("GET")
.withPath("/v3/registrations2-semver2/nunitprivate/5.0.2.json")
.withHeader("Authorization", encodedBasicHeader)
)
.respond(
response()
.withStatusCode(200)
.withHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.withBody(mockRegistrationResponse)
// Page 1
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/1.0.19123.2-preview/5.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver2.mds.page1.json",
null
);
Component component = new Component();
component.setPurl(new PackageURL("pkg:nuget/NUnitPrivate@5.0.1"));
NugetMetaAnalyzer analyzer = new NugetMetaAnalyzer();
analyzer.setRepositoryUsernameAndPassword(null, "password");
analyzer.setRepositoryBaseUrl("http://localhost:1080");
MetaModel metaModel = analyzer.analyze(component);
Assertions.assertEquals("5.0.2", metaModel.getLatestVersion());
Assertions.assertNotNull(metaModel.getPublishedTimestamp());
Assertions.assertEquals("6.0.2", metaModel.getLatestVersion());
Assertions.assertNull(metaModel.getPublishedTimestamp());
}
@Test
void testPublishedDateTimeFormat() throws ParseException {
Date dateParsed = null;
for (DateFormat dateFormat : SUPPORTED_DATE_FORMATS) {
try {
dateParsed = dateFormat.parse("1900-01-01T00:00:00+00:00");
} catch (ParseException e) {}
}
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Assertions.assertEquals(dateFormat.parse("1900-01-01T00:00:00+00:00"), dateParsed);
void testAnalyzerWithMultipageRegistrationInfoIgnorePreReleaseAndUnlisted() throws Exception {
// Page 2
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/5.1.1/6.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver2.mds.page2-check-pre-release.json",
null
);
MetaModel metaModel = analyzer.analyze(component);
Assertions.assertEquals("5.1.2", metaModel.getLatestVersion());
Assertions.assertNull(metaModel.getPublishedTimestamp());
}
private String readResourceFileToString(String fileName) throws Exception {
return Files.readString(Paths.get(getClass().getResource(fileName).toURI()));
@Test
void testAnalyzerWithMultipageRegistrationInfoWhenPage2AllUnlisted() throws Exception {
// Page 2
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/5.1.1/6.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver2.mds.page2-all-unlisted.json",
null
);
// Page 1
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/1.0.19123.2-preview/5.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver2.mds.page1.json",
null
);
MetaModel metaModel = analyzer.analyze(component);
Assertions.assertEquals("5.1.0", metaModel.getLatestVersion());
Assertions.assertNull(metaModel.getPublishedTimestamp());
}
@Test
void testAnalyzerWithMultipageRegistrationInfoWhenPage2AllPreRelease() throws Exception {
// Page 2
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/5.1.1/6.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver2.mds.page2-all-pre-release.json",
null
);
// Page 1
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/1.0.19123.2-preview/5.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver2.mds.page1.json",
null
);
MetaModel metaModel = analyzer.analyze(component);
Assertions.assertEquals("5.1.0", metaModel.getLatestVersion());
Assertions.assertNull(metaModel.getPublishedTimestamp());
}
@Test
void testAnalyzerWithPreReleaseOnlyVersionsReturnsLatestPreReleaseVersion() throws Exception {
// Test for log warning covered in 5075 - ensure no errors are thrown / logged
// when no release versions exist
setupMockServerClient(
"/v3/index.json",
"/unit/tasks/repositories/https---nuget.org.v3-index.json",
null
);
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration-semver2/opentelemetry.instrumentation.sqlclient/index.json",
"/unit/tasks/repositories/https---nuget.org.registration-semver2.beta-releases-only.index-inline-pages.json",
null
);
var betaOnlyComponent = new Component();
betaOnlyComponent.setInternal(false);
betaOnlyComponent.setName("OpenTelemetry.Instrumentation.SqlClient");
betaOnlyComponent.setPurl(new PackageURL("pkg:nuget/OpenTelemetry.Instrumentation.SqlClient@1.12.0-beta.2"));
MetaModel metaModel = analyzer.analyze(betaOnlyComponent);
Assertions.assertTrue(analyzer.isApplicable(betaOnlyComponent));
Assertions.assertEquals(RepositoryType.NUGET, analyzer.supportedRepositoryType());
Assertions.assertNotNull(metaModel.getLatestVersion());
Assertions.assertEquals("1.12.0-beta.2", metaModel.getLatestVersion());
Date expected = analyzer.parseUpdateTime("2025-07-15T04:42:33.33+00:00");
Assertions.assertEquals(expected, metaModel.getPublishedTimestamp());
}
}
/**
* Artifactory doesn't provide published dates and favours (only uses?) paged registration data.
* This collection forces a semver1 RegistrationsBaseUrl. The chosen test package,
* microsoft.data.sqlclient, returns the same number of items (64) as the semver2 version but the
* results appear on a single page instead of 2 with the semver2 version.
*/
@Nested
class ArtifactoryTestsSemver1Tests {
NugetMetaAnalyzer analyzer;
Component component;
@BeforeEach
void setUp() throws Exception {
this.analyzer = new NugetMetaAnalyzer();
this.analyzer.setRepositoryBaseUrl(LOCALHOST_REPO_INDEX);
this.analyzer.setRegistrationsBaseUrl("http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration/");
this.component = new Component();
this.component.setName("Microsoft.Data.SqlClient");
this.component.setPurl(new PackageURL("pkg:nuget/Microsoft.Data.SqlClient@5.1.0"));
mockServer.reset();
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/index.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.v3-index.json",
null, "application/json", 200
);
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration/microsoft.data.sqlclient/index.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver1.mds.index.json",
null
);
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration/microsoft.data.sqlclient/page/1.0.19123.2-preview/1.1.2.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver1.mds.page1.json",
null
);
}
@Test
void testAnalyzerWithMultipageRegistrationInfo() throws Exception {
// Page 2
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration/microsoft.data.sqlclient/page/5.2.2/6.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver1.mds.page2.json",
null
);
MetaModel metaModel = analyzer.analyze(component);
Assertions.assertEquals("6.0.2", metaModel.getLatestVersion());
Assertions.assertNull(metaModel.getPublishedTimestamp());
}
@Test
void testAnalyzerWithMultipageRegistrationInfoIgnorePreReleaseAndUnlisted() throws Exception {
// Page 2
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration/microsoft.data.sqlclient/page/5.2.2/6.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver1.mds.page2-check-pre-release.json",
null
);
MetaModel metaModel = analyzer.analyze(component);
Assertions.assertEquals("6.0.1", metaModel.getLatestVersion());
Assertions.assertNull(metaModel.getPublishedTimestamp());
}
@Test
void testAnalyzerWithMultipageRegistrationInfoWhenPage2AllUnlisted() throws Exception {
// Page 2
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration/microsoft.data.sqlclient/page/5.2.2/6.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver1.mds.page2-all-unlisted.json",
null
);
MetaModel metaModel = analyzer.analyze(component);
Assertions.assertEquals("1.1.2", metaModel.getLatestVersion());
Assertions.assertNull(metaModel.getPublishedTimestamp());
}
@Test
void testAnalyzerWithMultipageRegistrationInfoWhenPage2AllPreRelease() throws Exception {
// Page 2
setupMockServerClient(
"/artifactory/api/nuget/v3/nuget-repo/registration/microsoft.data.sqlclient/page/5.2.2/6.1.0.json",
"/unit/tasks/repositories/https---localhost-nuget-artifactory.registration-semver1.mds.page2-all-pre-release.json",
null
);
MetaModel metaModel = analyzer.analyze(component);
Assertions.assertEquals("1.1.2", metaModel.getLatestVersion());
Assertions.assertNull(metaModel.getPublishedTimestamp());
}
}
@Nested
class DateParserTests {
NugetMetaAnalyzer analyzer = new NugetMetaAnalyzer();
@ParameterizedTest
@ValueSource(strings = {
"1900-01-01T00:00:00+00:00",
"2025-08-13T23:22:21.20+01:00",
"2025-08-13T23:22:21Z",
"2020-08-04T10:39:03.7136823",
"2025-08-13T23:22:21",
"2020-08-04T10:39:03.7136823",
"2023-03-28T22:26:40.43+00:00",
"2025-08-14T08:12:23.8207879Z"
})
void shouldParseValidDateFormats(String dateString) {
Date result = this.analyzer.parseUpdateTime(dateString);
Assertions.assertNotNull(result);
}
@Test
void shouldReturnNullForBlankString() {
Assertions.assertNull(this.analyzer.parseUpdateTime(" "));
}
@Test
void shouldReturnNullForInvalidDate() {
Assertions.assertNull(this.analyzer.parseUpdateTime("not-a-date"));
}
@Test
void shouldReturnNullForNullInput() {
Assertions.assertNull(this.analyzer.parseUpdateTime(null));
}
}
}

View file

@ -1,6 +0,0 @@
{
"versions": [
"5.0.2",
"5.0.1"
]
}

View file

@ -1,40 +0,0 @@
{
"@context": {
"@vocab": "http://schema.nuget.org/services#",
"comment": "http://www.w3.org/2000/01/rdf-schema#comment",
"label": "http://www.w3.org/2000/01/rdf-schema#label"
},
"resources": [
{
"@id": "http://localhost:1080/v2/",
"@type": "PackagePublish/2.0.0"
},
{
"@id": "http://localhost:1080/v2/",
"@type": "LegacyGallery/2.0.0"
},
{
"@id": "http://localhost:1080/v3/registrations2/",
"@type": "RegistrationsBaseUrl/3.0.0-beta"
},
{
"@id": "http://localhost:1080/v3/registrations2-semver2/",
"@type": "RegistrationsBaseUrl/3.6.0",
"comment": "This base URL includes SemVer 2.0.0 packages."
},
{
"@id": "http://localhost:1080/v3/registrations2-semver2/",
"@type": "RegistrationsBaseUrl/Versioned",
"comment": "This base URL includes SemVer 2.0.0 packages."
},
{
"@id": "http://localhost:1080/v3/query2/",
"@type": "SearchQueryService/3.0.0-beta"
},
{
"@id": "http://localhost:1080/v3/flat2/",
"@type": "PackageBaseAddress/3.0.0"
}
],
"version": "3.0.0-beta"
}

View file

@ -1,28 +0,0 @@
{
"@id": "http://localhost:1080/v3/registrations2/nugetprivate/5.0.2.json",
"@type": [
"Package",
"http://schema.nuget.org/catalog#Permalink"
],
"catalogEntry": "http://localhost:1080/commitLog/82a44df5f3474a989c4ffc1fba9ffff4",
"listed": true,
"packageContent": "http://localhost:1080/v3/flat2/nugetprivate/5.0.2/nugetprivate.5.0.2.nupkg",
"published": "2022-04-13T13:30:25Z",
"registration": "http://localhost:1080/v3/registrations2/nugetprivate/index.json",
"@context": {
"@vocab": "http://schema.nuget.org/schema#",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"catalogEntry": {
"@type": "@id"
},
"registration": {
"@type": "@id"
},
"packageContent": {
"type": "@id"
},
"published": {
"@type": "xsd:dateTime"
}
}
}

View file

@ -0,0 +1,18 @@
{
"count": 2,
"items": [
{
"count": 10,
"lower": "1.0.19123.2-Preview",
"upper": "1.1.2",
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration/microsoft.data.sqlclient/page/1.0.19123.2-preview/1.1.2.json"
},
{
"count": 6,
"lower": "5.2.2",
"upper": "6.1.0",
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration/microsoft.data.sqlclient/page/5.2.2/6.1.0.json"
}
],
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration/microsoft.data.sqlclient/index.json"
}

View file

@ -0,0 +1,18 @@
{
"count": 2,
"items": [
{
"count": 64,
"lower": "1.0.19123.2-Preview",
"upper": "5.1.0",
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/1.0.19123.2-preview/5.1.0.json"
},
{
"count": 25,
"lower": "5.1.1",
"upper": "6.1.0",
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/5.1.1/6.1.0.json"
}
],
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/index.json"
}

View file

@ -0,0 +1,924 @@
{
"count": 3,
"lower": "5.1.1",
"parent": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/index.json",
"upper": "6.1.0",
"items": [
{
"catalogEntry": {
"authors": "Microsoft",
"description": "Provides the data provider for SQL Server. These classes provide access to versions of SQL Server and encapsulate database-specific protocols, including tabular data stream (TDS)\n \nCommonly Used Types:\nMicrosoft.Data.SqlClient.SqlConnection\nMicrosoft.Data.SqlClient.SqlException\nMicrosoft.Data.SqlClient.SqlParameter\nMicrosoft.Data.SqlClient.SqlDataReader\nMicrosoft.Data.SqlClient.SqlCommand\nMicrosoft.Data.SqlClient.SqlTransaction\nMicrosoft.Data.SqlClient.SqlParameterCollection\nMicrosoft.Data.SqlClient.SqlClientFactory\n\nWhen using NuGet 3.x this package requires at least version 3.4.",
"iconUrl": "https://api.nuget.org/v3-flatcontainer/microsoft.data.sqlclient/5.1.1/icon",
"deprecation": {
"reasons": [
"CriticalBugs"
],
"message": "An important security issue exists in this version of the package. It is recommended to update to a newer version.\nhttps://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-0056"
},
"language": "",
"licenseUrl": "https://www.nuget.org/packages/Microsoft.Data.SqlClient/5.1.1/license",
"listed": true,
"packageContent": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/Download/microsoft.data.sqlclient/5.1.1",
"projectUrl": "https://aka.ms/sqlclientproject",
"requireLicenseAcceptance": true,
"summary": "",
"tags": [
"sqlclient",
"microsoft.data.sqlclient"
],
"title": "Microsoft.Data.SqlClient",
"version": "5.1.1-pre-release-test",
"dependencyGroups": [
{
"dependencies": [
{
"range": "[5.1.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni/index.json",
"id": "Microsoft.Data.SqlClient.SNI"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[4.5.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.buffers/index.json",
"id": "System.Buffers"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[4.3.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.interopservices.runtimeinformation/index.json",
"id": "System.Runtime.InteropServices.RuntimeInformation"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
}
],
"targetFramework": ".NETFramework4.6.2"
},
{
"dependencies": [
{
"range": "[5.1.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni.runtime/index.json",
"id": "Microsoft.Data.SqlClient.SNI.runtime"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[1.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.sqlserver.server/index.json",
"id": "Microsoft.SqlServer.Server"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.diagnostics.diagnosticsource/index.json",
"id": "System.Diagnostics.DiagnosticSource"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.caching/index.json",
"id": "System.Runtime.Caching"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encoding.codepages/index.json",
"id": "System.Text.Encoding.CodePages"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.cryptography.cng/index.json",
"id": "System.Security.Cryptography.Cng"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.principal.windows/index.json",
"id": "System.Security.Principal.Windows"
}
],
"targetFramework": "net6.0"
},
{
"dependencies": [
{
"range": "[5.1.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni.runtime/index.json",
"id": "Microsoft.Data.SqlClient.SNI.runtime"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[1.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.sqlserver.server/index.json",
"id": "Microsoft.SqlServer.Server"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.win32.registry/index.json",
"id": "Microsoft.Win32.Registry"
},
{
"range": "[4.5.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.buffers/index.json",
"id": "System.Buffers"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.caching/index.json",
"id": "System.Runtime.Caching"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encoding.codepages/index.json",
"id": "System.Text.Encoding.CodePages"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
},
{
"range": "[4.3.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.loader/index.json",
"id": "System.Runtime.Loader"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.cryptography.cng/index.json",
"id": "System.Security.Cryptography.Cng"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.principal.windows/index.json",
"id": "System.Security.Principal.Windows"
}
],
"targetFramework": ".NETStandard2.0"
},
{
"dependencies": [
{
"range": "[5.1.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni.runtime/index.json",
"id": "Microsoft.Data.SqlClient.SNI.runtime"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[1.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.sqlserver.server/index.json",
"id": "Microsoft.SqlServer.Server"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.win32.registry/index.json",
"id": "Microsoft.Win32.Registry"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.caching/index.json",
"id": "System.Runtime.Caching"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encoding.codepages/index.json",
"id": "System.Text.Encoding.CodePages"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
},
{
"range": "[4.3.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.loader/index.json",
"id": "System.Runtime.Loader"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.cryptography.cng/index.json",
"id": "System.Security.Cryptography.Cng"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.principal.windows/index.json",
"id": "System.Security.Principal.Windows"
}
],
"targetFramework": ".NETStandard2.1"
}
],
"id": "Microsoft.Data.SqlClient"
},
"packageContent": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/Download/microsoft.data.sqlclient/5.1.1",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient/index.json",
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient/5.1.1.json"
},
{
"catalogEntry": {
"authors": "Microsoft",
"description": "Provides the data provider for SQL Server. These classes provide access to versions of SQL Server and encapsulate database-specific protocols, including tabular data stream (TDS)\n \nCommonly Used Types:\nMicrosoft.Data.SqlClient.SqlConnection\nMicrosoft.Data.SqlClient.SqlException\nMicrosoft.Data.SqlClient.SqlParameter\nMicrosoft.Data.SqlClient.SqlDataReader\nMicrosoft.Data.SqlClient.SqlCommand\nMicrosoft.Data.SqlClient.SqlTransaction\nMicrosoft.Data.SqlClient.SqlParameterCollection\nMicrosoft.Data.SqlClient.SqlClientFactory\n\nWhen using NuGet 3.x this package requires at least version 3.4.",
"iconUrl": "https://api.nuget.org/v3-flatcontainer/microsoft.data.sqlclient/5.1.2/icon",
"deprecation": {
"reasons": [
"CriticalBugs"
],
"message": "An important security issue exists in this version of the package. It is recommended to update to a newer version.\nhttps://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-0056"
},
"language": "",
"licenseUrl": "https://www.nuget.org/packages/Microsoft.Data.SqlClient/5.1.2/license",
"listed": true,
"packageContent": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/Download/microsoft.data.sqlclient/5.1.2",
"projectUrl": "https://aka.ms/sqlclientproject",
"requireLicenseAcceptance": true,
"summary": "",
"tags": [
"sqlclient",
"microsoft.data.sqlclient"
],
"title": "Microsoft.Data.SqlClient",
"version": "5.1.2-pre-release-test",
"dependencyGroups": [
{
"dependencies": [
{
"range": "[5.1.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni/index.json",
"id": "Microsoft.Data.SqlClient.SNI"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[4.5.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.buffers/index.json",
"id": "System.Buffers"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[4.3.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.interopservices.runtimeinformation/index.json",
"id": "System.Runtime.InteropServices.RuntimeInformation"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
}
],
"targetFramework": ".NETFramework4.6.2"
},
{
"dependencies": [
{
"range": "[5.1.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni.runtime/index.json",
"id": "Microsoft.Data.SqlClient.SNI.runtime"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[1.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.sqlserver.server/index.json",
"id": "Microsoft.SqlServer.Server"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.diagnostics.diagnosticsource/index.json",
"id": "System.Diagnostics.DiagnosticSource"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.caching/index.json",
"id": "System.Runtime.Caching"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encoding.codepages/index.json",
"id": "System.Text.Encoding.CodePages"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.cryptography.cng/index.json",
"id": "System.Security.Cryptography.Cng"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.principal.windows/index.json",
"id": "System.Security.Principal.Windows"
}
],
"targetFramework": "net6.0"
},
{
"dependencies": [
{
"range": "[5.1.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni.runtime/index.json",
"id": "Microsoft.Data.SqlClient.SNI.runtime"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[1.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.sqlserver.server/index.json",
"id": "Microsoft.SqlServer.Server"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.win32.registry/index.json",
"id": "Microsoft.Win32.Registry"
},
{
"range": "[4.5.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.buffers/index.json",
"id": "System.Buffers"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.caching/index.json",
"id": "System.Runtime.Caching"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encoding.codepages/index.json",
"id": "System.Text.Encoding.CodePages"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
},
{
"range": "[4.3.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.loader/index.json",
"id": "System.Runtime.Loader"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.cryptography.cng/index.json",
"id": "System.Security.Cryptography.Cng"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.principal.windows/index.json",
"id": "System.Security.Principal.Windows"
}
],
"targetFramework": ".NETStandard2.0"
},
{
"dependencies": [
{
"range": "[5.1.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni.runtime/index.json",
"id": "Microsoft.Data.SqlClient.SNI.runtime"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[1.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.sqlserver.server/index.json",
"id": "Microsoft.SqlServer.Server"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.win32.registry/index.json",
"id": "Microsoft.Win32.Registry"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.caching/index.json",
"id": "System.Runtime.Caching"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encoding.codepages/index.json",
"id": "System.Text.Encoding.CodePages"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
},
{
"range": "[4.3.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.loader/index.json",
"id": "System.Runtime.Loader"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.cryptography.cng/index.json",
"id": "System.Security.Cryptography.Cng"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.principal.windows/index.json",
"id": "System.Security.Principal.Windows"
}
],
"targetFramework": ".NETStandard2.1"
}
],
"id": "Microsoft.Data.SqlClient"
},
"packageContent": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/Download/microsoft.data.sqlclient/5.1.2",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient/index.json",
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient/5.1.2.json"
},
{
"catalogEntry": {
"authors": "Microsoft",
"description": "Provides the data provider for SQL Server. These classes provide access to versions of SQL Server and encapsulate database-specific protocols, including tabular data stream (TDS)\n \nCommonly Used Types:\nMicrosoft.Data.SqlClient.SqlConnection\nMicrosoft.Data.SqlClient.SqlException\nMicrosoft.Data.SqlClient.SqlParameter\nMicrosoft.Data.SqlClient.SqlDataReader\nMicrosoft.Data.SqlClient.SqlCommand\nMicrosoft.Data.SqlClient.SqlTransaction\nMicrosoft.Data.SqlClient.SqlParameterCollection\nMicrosoft.Data.SqlClient.SqlClientFactory\n\nWhen using NuGet 3.x this package requires at least version 3.4.",
"iconUrl": "https://api.nuget.org/v3-flatcontainer/microsoft.data.sqlclient/5.1.3/icon",
"language": "",
"licenseUrl": "https://www.nuget.org/packages/Microsoft.Data.SqlClient/5.1.3/license",
"listed": true,
"packageContent": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/Download/microsoft.data.sqlclient/5.1.3",
"projectUrl": "https://aka.ms/sqlclientproject",
"requireLicenseAcceptance": true,
"summary": "",
"tags": [
"sqlclient",
"microsoft.data.sqlclient"
],
"title": "Microsoft.Data.SqlClient",
"version": "5.1.3-pre-release-test",
"dependencyGroups": [
{
"dependencies": [
{
"range": "[5.1.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni/index.json",
"id": "Microsoft.Data.SqlClient.SNI"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[4.5.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.buffers/index.json",
"id": "System.Buffers"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[4.3.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.interopservices.runtimeinformation/index.json",
"id": "System.Runtime.InteropServices.RuntimeInformation"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
}
],
"targetFramework": ".NETFramework4.6.2"
},
{
"dependencies": [
{
"range": "[5.1.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni.runtime/index.json",
"id": "Microsoft.Data.SqlClient.SNI.runtime"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[1.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.sqlserver.server/index.json",
"id": "Microsoft.SqlServer.Server"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.diagnostics.diagnosticsource/index.json",
"id": "System.Diagnostics.DiagnosticSource"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.caching/index.json",
"id": "System.Runtime.Caching"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encoding.codepages/index.json",
"id": "System.Text.Encoding.CodePages"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.cryptography.cng/index.json",
"id": "System.Security.Cryptography.Cng"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.principal.windows/index.json",
"id": "System.Security.Principal.Windows"
}
],
"targetFramework": "net6.0"
},
{
"dependencies": [
{
"range": "[5.1.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni.runtime/index.json",
"id": "Microsoft.Data.SqlClient.SNI.runtime"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[1.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.sqlserver.server/index.json",
"id": "Microsoft.SqlServer.Server"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.win32.registry/index.json",
"id": "Microsoft.Win32.Registry"
},
{
"range": "[4.5.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.buffers/index.json",
"id": "System.Buffers"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.caching/index.json",
"id": "System.Runtime.Caching"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encoding.codepages/index.json",
"id": "System.Text.Encoding.CodePages"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
},
{
"range": "[4.3.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.loader/index.json",
"id": "System.Runtime.Loader"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.cryptography.cng/index.json",
"id": "System.Security.Cryptography.Cng"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.principal.windows/index.json",
"id": "System.Security.Principal.Windows"
}
],
"targetFramework": ".NETStandard2.0"
},
{
"dependencies": [
{
"range": "[5.1.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient.sni.runtime/index.json",
"id": "Microsoft.Data.SqlClient.SNI.runtime"
},
{
"range": "[1.7.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/azure.identity/index.json",
"id": "Azure.Identity"
},
{
"range": "[4.47.2, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identity.client/index.json",
"id": "Microsoft.Identity.Client"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.protocols.openidconnect/index.json",
"id": "Microsoft.IdentityModel.Protocols.OpenIdConnect"
},
{
"range": "[6.24.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.identitymodel.jsonwebtokens/index.json",
"id": "Microsoft.IdentityModel.JsonWebTokens"
},
{
"range": "[1.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.sqlserver.server/index.json",
"id": "Microsoft.SqlServer.Server"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.win32.registry/index.json",
"id": "Microsoft.Win32.Registry"
},
{
"range": "[6.0.1, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.configuration.configurationmanager/index.json",
"id": "System.Configuration.ConfigurationManager"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.caching/index.json",
"id": "System.Runtime.Caching"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encoding.codepages/index.json",
"id": "System.Text.Encoding.CodePages"
},
{
"range": "[6.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.text.encodings.web/index.json",
"id": "System.Text.Encodings.Web"
},
{
"range": "[4.3.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.runtime.loader/index.json",
"id": "System.Runtime.Loader"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.cryptography.cng/index.json",
"id": "System.Security.Cryptography.Cng"
},
{
"range": "[5.0.0, )",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/system.security.principal.windows/index.json",
"id": "System.Security.Principal.Windows"
}
],
"targetFramework": ".NETStandard2.1"
}
],
"id": "Microsoft.Data.SqlClient"
},
"packageContent": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/Download/microsoft.data.sqlclient/5.1.3",
"registration": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient/index.json",
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/registration-semver2/microsoft.data.sqlclient/5.1.3.json"
}
],
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/microsoft.data.sqlclient/page/5.1.1/6.1.0.json"
}

View file

@ -0,0 +1,77 @@
{
"version": "3.0.0",
"resources": [
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/query",
"@type": "SearchQueryService",
"comment": "Query endpoint of NuGet Search service (primary)"
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration/",
"@type": "RegistrationsBaseUrl",
"comment": "Base URL of Azure storage where NuGet package registration info is stored"
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/nuget-repo",
"@type": "LegacyGallery"
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/nuget-repo",
"@type": "LegacyGallery/2.0.0"
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/nuget-repo",
"@type": "PackagePublish/2.0.0"
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/symbols",
"@type": "SymbolPackagePublish/4.9.0"
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/query",
"@type": "SearchQueryService/3.0.0-rc",
"comment": "Query endpoint of NuGet Search service (primary) used by RC clients"
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration/",
"@type": "RegistrationsBaseUrl/3.0.0-rc",
"comment": "Base URL of Azure storage where NuGet package registration info is stored used by RC clients. This base URL does not include SemVer 2.0.0 packages."
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration/{id-lower}/index.json",
"@type": "PackageDisplayMetadataUriTemplate/3.0.0-rc",
"comment": "URI template used by NuGet Client to construct display metadata for Packages using ID"
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration/{id-lower}/{version-lower}.json",
"@type": "PackageVersionDisplayMetadataUriTemplate/3.0.0-rc",
"comment": "URI template used by NuGet Client to construct display metadata for Packages using ID, Version"
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/query",
"@type": "SearchQueryService/3.0.0-beta",
"comment": "Query endpoint of NuGet Search service (primary) used by beta clients"
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration/",
"@type": "RegistrationsBaseUrl/3.0.0-beta",
"comment": "Base URL of Azure storage where NuGet package registration info is stored used by Beta clients. This base URL does not include SemVer 2.0.0 packages."
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration/",
"@type": "RegistrationsBaseUrl/3.4.0",
"comment": "Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL does not include SemVer 2.0.0 packages."
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/",
"@type": "RegistrationsBaseUrl/3.6.0",
"comment": "Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."
},
{
"@id": "http://localhost:1080/artifactory/api/nuget/v3/nuget-repo/registration-semver2/",
"@type": "RegistrationsBaseUrl/Versioned",
"clientVersion": "4.3.0-alpha",
"comment": "Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."
}
]
}

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?><Error><Code>BlobNotFound</Code><Message>The specified blob does not exist.
RequestId:21575f5c-3b02-45a4-81a6-5e09fd2099db
Time:2025-08-12T23:22:21.2000000Z</Message></Error>

View file

@ -0,0 +1,207 @@
{
"version": "3.0.0",
"resources": [
{
"@id": "https://azuresearch-usnc.nuget.org/query",
"@type": "SearchQueryService",
"comment": "Query endpoint of NuGet Search service (primary)"
},
{
"@id": "https://azuresearch-ussc.nuget.org/query",
"@type": "SearchQueryService",
"comment": "Query endpoint of NuGet Search service (secondary)"
},
{
"@id": "https://azuresearch-usnc.nuget.org/autocomplete",
"@type": "SearchAutocompleteService",
"comment": "Autocomplete endpoint of NuGet Search service (primary)"
},
{
"@id": "https://azuresearch-ussc.nuget.org/autocomplete",
"@type": "SearchAutocompleteService",
"comment": "Autocomplete endpoint of NuGet Search service (secondary)"
},
{
"@id": "https://azuresearch-usnc.nuget.org/",
"@type": "SearchGalleryQueryService/3.0.0-rc",
"comment": "Azure Website based Search Service used by Gallery (primary)"
},
{
"@id": "https://azuresearch-ussc.nuget.org/",
"@type": "SearchGalleryQueryService/3.0.0-rc",
"comment": "Azure Website based Search Service used by Gallery (secondary)"
},
{
"@id": "http://localhost:1080/v3/registration5-semver1/",
"@type": "RegistrationsBaseUrl",
"comment": "Base URL of Azure storage where NuGet package registration info is stored"
},
{
"@id": "http://localhost:1080/v3-flatcontainer/",
"@type": "PackageBaseAddress/3.0.0",
"comment": "Base URL of where NuGet packages are stored, in the format http://localhost:1080/v3-flatcontainer/{id-lower}/{version-lower}/{id-lower}.{version-lower}.nupkg"
},
{
"@id": "https://www.nuget.org/api/v2",
"@type": "LegacyGallery"
},
{
"@id": "https://www.nuget.org/api/v2",
"@type": "LegacyGallery/2.0.0"
},
{
"@id": "https://www.nuget.org/api/v2/package",
"@type": "PackagePublish/2.0.0"
},
{
"@id": "https://www.nuget.org/api/v2/symbolpackage",
"@type": "SymbolPackagePublish/4.9.0",
"comment": "The gallery symbol publish endpoint."
},
{
"@id": "https://azuresearch-usnc.nuget.org/query",
"@type": "SearchQueryService/3.0.0-rc",
"comment": "Query endpoint of NuGet Search service (primary) used by RC clients"
},
{
"@id": "https://azuresearch-ussc.nuget.org/query",
"@type": "SearchQueryService/3.0.0-rc",
"comment": "Query endpoint of NuGet Search service (secondary) used by RC clients"
},
{
"@id": "https://azuresearch-usnc.nuget.org/query",
"@type": "SearchQueryService/3.5.0",
"comment": "Query endpoint of NuGet Search service (primary) that supports package type filtering"
},
{
"@id": "https://azuresearch-ussc.nuget.org/query",
"@type": "SearchQueryService/3.5.0",
"comment": "Query endpoint of NuGet Search service (secondary) that supports package type filtering"
},
{
"@id": "https://azuresearch-usnc.nuget.org/autocomplete",
"@type": "SearchAutocompleteService/3.0.0-rc",
"comment": "Autocomplete endpoint of NuGet Search service (primary) used by RC clients"
},
{
"@id": "https://azuresearch-ussc.nuget.org/autocomplete",
"@type": "SearchAutocompleteService/3.0.0-rc",
"comment": "Autocomplete endpoint of NuGet Search service (secondary) used by RC clients"
},
{
"@id": "https://azuresearch-usnc.nuget.org/autocomplete",
"@type": "SearchAutocompleteService/3.5.0",
"comment": "Autocomplete endpoint of NuGet Search service (primary) that supports package type filtering"
},
{
"@id": "https://azuresearch-ussc.nuget.org/autocomplete",
"@type": "SearchAutocompleteService/3.5.0",
"comment": "Autocomplete endpoint of NuGet Search service (secondary) that supports package type filtering"
},
{
"@id": "http://localhost:1080/v3/registration5-semver1/",
"@type": "RegistrationsBaseUrl/3.0.0-rc",
"comment": "Base URL of Azure storage where NuGet package registration info is stored used by RC clients. This base URL does not include SemVer 2.0.0 packages."
},
{
"@id": "https://www.nuget.org/packages/{id}/{version}/ReportAbuse",
"@type": "ReportAbuseUriTemplate/3.0.0-rc",
"comment": "URI template used by NuGet Client to construct Report Abuse URL for packages used by RC clients"
},
{
"@id": "http://localhost:1080/v3/registration5-semver1/{id-lower}/index.json",
"@type": "PackageDisplayMetadataUriTemplate/3.0.0-rc",
"comment": "URI template used by NuGet Client to construct display metadata for Packages using ID"
},
{
"@id": "http://localhost:1080/v3/registration5-semver1/{id-lower}/{version-lower}.json",
"@type": "PackageVersionDisplayMetadataUriTemplate/3.0.0-rc",
"comment": "URI template used by NuGet Client to construct display metadata for Packages using ID, Version"
},
{
"@id": "https://azuresearch-usnc.nuget.org/query",
"@type": "SearchQueryService/3.0.0-beta",
"comment": "Query endpoint of NuGet Search service (primary) used by beta clients"
},
{
"@id": "https://azuresearch-ussc.nuget.org/query",
"@type": "SearchQueryService/3.0.0-beta",
"comment": "Query endpoint of NuGet Search service (secondary) used by beta clients"
},
{
"@id": "https://azuresearch-usnc.nuget.org/autocomplete",
"@type": "SearchAutocompleteService/3.0.0-beta",
"comment": "Autocomplete endpoint of NuGet Search service (primary) used by beta clients"
},
{
"@id": "https://azuresearch-ussc.nuget.org/autocomplete",
"@type": "SearchAutocompleteService/3.0.0-beta",
"comment": "Autocomplete endpoint of NuGet Search service (secondary) used by beta clients"
},
{
"@id": "http://localhost:1080/v3/registration5-semver1/",
"@type": "RegistrationsBaseUrl/3.0.0-beta",
"comment": "Base URL of Azure storage where NuGet package registration info is stored used by Beta clients. This base URL does not include SemVer 2.0.0 packages."
},
{
"@id": "https://www.nuget.org/packages/{id}/{version}/ReportAbuse",
"@type": "ReportAbuseUriTemplate/3.0.0-beta",
"comment": "URI template used by NuGet Client to construct Report Abuse URL for packages"
},
{
"@id": "https://www.nuget.org/packages/{id}/{version}?_src=template",
"@type": "PackageDetailsUriTemplate/5.1.0",
"comment": "URI template used by NuGet Client to construct details URL for packages"
},
{
"@id": "https://www.nuget.org/profiles/{owner}?_src=template",
"@type": "OwnerDetailsUriTemplate/6.11.0",
"comment": "URI template used by NuGet Client to construct owner URL for packages"
},
{
"@id": "http://localhost:1080/v3/registration5-gz-semver1/",
"@type": "RegistrationsBaseUrl/3.4.0",
"comment": "Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL does not include SemVer 2.0.0 packages."
},
{
"@id": "http://localhost:1080/v3/registration5-gz-semver2/",
"@type": "RegistrationsBaseUrl/3.6.0",
"comment": "Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."
},
{
"@id": "http://localhost:1080/v3/registration5-gz-semver2/",
"@type": "RegistrationsBaseUrl/Versioned",
"clientVersion": "4.3.0-alpha",
"comment": "Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."
},
{
"@id": "http://localhost:1080/v3-index/repository-signatures/4.7.0/index.json",
"@type": "RepositorySignatures/4.7.0",
"comment": "The endpoint for discovering information about this package source's repository signatures."
},
{
"@id": "http://localhost:1080/v3-index/repository-signatures/5.0.0/index.json",
"@type": "RepositorySignatures/5.0.0",
"comment": "The endpoint for discovering information about this package source's repository signatures."
},
{
"@id": "http://localhost:1080/v3/vulnerabilities/index.json",
"@type": "VulnerabilityInfo/6.7.0",
"comment": "The endpoint for discovering information about vulnerabilities of packages in this package source."
},
{
"@id": "http://localhost:1080/v3/catalog0/index.json",
"@type": "Catalog/3.0.0",
"comment": "Index of the NuGet package catalog."
},
{
"@id": "http://localhost:1080/v3-flatcontainer/{lower_id}/{lower_version}/readme",
"@type": "ReadmeUriTemplate/6.13.0",
"comment": "URI template used by NuGet Client to construct a URL for downloading a package's README."
}
],
"@context": {
"@vocab": "http://schema.nuget.org/services#",
"comment": "http://www.w3.org/2000/01/rdf-schema#comment"
}
}