com.metaeffekt.artifact.analysis.version.CommonPartsVersionImpl Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.metaeffekt.artifact.analysis.version;
import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.version.curation.VersionContext;
import com.metaeffekt.artifact.analysis.version.token.VersionToken;
import com.metaeffekt.artifact.analysis.version.token.VersionTokenType;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.VersionComparator;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import static org.metaeffekt.core.inventory.processor.model.Constants.ASTERISK;
public class CommonPartsVersionImpl extends AllCategorizedPartsVersionImpl {
public CommonPartsVersionImpl(String version, String update, VersionContext versionContext) {
super(version, update, versionContext);
}
public CommonPartsVersionImpl(String version, VersionContext versionContext) {
this(version, null, versionContext);
}
CommonPartsVersionImpl(String version, String update) {
super(version, update, VersionContext.EMPTY);
}
CommonPartsVersionImpl(String version) {
this(version, null, VersionContext.EMPTY);
}
public int compareTo(CommonPartsVersionImpl o) {
if (o == null) {
return 1;
}
// the other parts are then compared as long as both have non-empty values. as soon as they become empty, comparison stops and the previous result is returned
final VersionTokenComparison specVersionResult = new VersionTokenComparison(this.specVersion, o.specVersion);
final VersionTokenComparison semVerResult = new VersionTokenComparison(this.semVersion, o.semVersion);
final VersionTokenComparison buildVersionResult = new VersionTokenComparison(this.buildVersion, o.buildVersion);
final VersionTokenComparison versionModifierResult = new VersionTokenComparison(this.versionModifier, o.versionModifier);
final VersionTokenComparison otherVersionPartResult = new VersionTokenComparison(this.otherVersionPart, o.otherVersionPart);
final VersionTokenComparison afterAllPartResult = new VersionTokenComparison(this.afterAllPart, o.afterAllPart);
final List nonEmptyParts = VersionTokenComparison.getAllPartsWhereBothAreNonEmpty(
specVersionResult, semVerResult, buildVersionResult, versionModifierResult, otherVersionPartResult, afterAllPartResult
);
if (nonEmptyParts.isEmpty()) {
return 0;
}
for (VersionTokenComparison nonEmptyPart : nonEmptyParts) {
if (nonEmptyPart.getResult() != 0) {
return nonEmptyPart.getResult();
}
}
return 0;
}
private static int compareByPart(VersionToken o1, VersionToken o2) {
if (o1 == null || o2 == null) {
return 0;
}
boolean is1VersionModifier = o1.getType() == VersionTokenType.VERSION_MODIFIER;
boolean is2VersionModifier = o2.getType() == VersionTokenType.VERSION_MODIFIER;
if (is1VersionModifier && is2VersionModifier) {
return VersionToken.VERSION_MODIFIER_COMPARATOR_COMMON_PARTS.compare(o1, o2);
} else if (!is1VersionModifier && !is2VersionModifier) {
if (o1.isLettersOnly() || o2.isLettersOnly()) {
return o1.getValue().compareTo(o2.getValue());
} else {
return VersionComparator.INSTANCE_COMMON_PARTS.compare(o1.getValue(), o2.getValue());
}
}
return is1VersionModifier ? 1 : -1;
}
public int getPartsCount() {
return countParts(specVersion, semVersion, buildVersion, versionModifier, otherVersionPart, afterAllPart);
}
public int getSemVerPartsCount() {
return semVersion == null || StringUtils.isEmpty(semVersion.getValue()) ? 0 : semVersion.getValue().split("\\.").length + 1;
}
public int getModifierPartsCount() {
return versionModifier == null ? 0 : versionModifier.getSubTokenCount();
}
private int countParts(VersionToken... tokens) {
int count = 0;
for (VersionToken token : tokens) {
if (!VersionToken.isEmpty(token)) count++;
}
return count;
}
@Override
public int compareTo(Version o) {
if (o == null) {
return 1;
}
if (o instanceof CommonPartsVersionImpl) {
return compareTo((CommonPartsVersionImpl) o);
}
return compareTo(new CommonPartsVersionImpl(o.getVersion(), o.getUpdate()));
}
public boolean matchesVersionOf(final String checkVersion, final String checkUpdate,
final String versionEndIncluding, final String versionEndExcluding,
final String versionStartExcluding, final String versionStartIncluding,
VersionContext context
) {
// if the version is unknown, match any vulnerable software version
if (this.getVersion() != null && this.getUpdate() == null && (this.getVersion().equals(ASTERISK) || this.getVersion().equals("-"))) {
return true;
}
// FIXME: I would say that this is the correct interpretation of the documentation.
// https://cpe.mitre.org/files/cpe-specification_2.2.pdf page 9
// a dash in the version or update part means that only versions with a "*" or "" will match that part.
// if a concrete version or update part is given, it shall not match the version.
if ("-".equals(checkVersion)) {
if (StringUtils.hasText(this.getVersion()) && !(this.getVersion().equals(ASTERISK) || this.getVersion().equals("-"))) {
return false;
}
} else if ("-".equals(checkUpdate)) {
if (StringUtils.hasText(this.getUpdate()) && !(this.getUpdate().equals(ASTERISK) || this.getUpdate().equals("-"))) {
return false;
}
}
final String normalisedCheckVersion = normalizePart(checkVersion);
final String normalisedCheckUpdate = normalizePart(checkUpdate);
final boolean hasVsVersion = StringUtils.hasText(normalisedCheckVersion);
final String normalizedCheckUpdateAppendixForRangeLimits = StringUtils.hasText(normalisedCheckUpdate) && !hasVsVersion ? normalisedCheckUpdate : null;
final Version vsVersion = Version.of(normalisedCheckVersion, normalisedCheckUpdate, context, CommonPartsVersionImpl::new);
if (hasVsVersion) {
if (vsVersion == null || vsVersion.equals(this)) {
return true;
}
}
if (normalizePart(this.getVersion()) != null) {
final String normalisedVersionEndIncluding = joinIfFirstNonNull(normalizePart(versionEndIncluding), normalizedCheckUpdateAppendixForRangeLimits);
final String normalisedVersionEndExcluding = joinIfFirstNonNull(normalizePart(versionEndExcluding), normalizedCheckUpdateAppendixForRangeLimits);
final String normalisedVersionStartExcluding = joinIfFirstNonNull(normalizePart(versionStartExcluding), normalizedCheckUpdateAppendixForRangeLimits);
final String normalisedVersionStartIncluding = joinIfFirstNonNull(normalizePart(versionStartIncluding), normalizedCheckUpdateAppendixForRangeLimits);
final int partsCountThis = this.getPartsCount();
final int semVerPartsCountThis = this.getSemVerPartsCount();
final int modifierPartsCountThis = this.getModifierPartsCount();
if (normalisedVersionEndIncluding == null && normalisedVersionEndExcluding == null && normalisedVersionStartExcluding == null && normalisedVersionStartIncluding == null) {
if (vsVersion == null || vsVersion.isEmpty()) {
return true;
}
return false;
}
final CommonPartsVersionImpl vsVersionStartExcluding = Version.of(normalisedVersionStartExcluding, context, CommonPartsVersionImpl::new);
if (vsVersionStartExcluding != null && vsVersionStartExcluding.isNotEmpty()) {
final int partsCountStartExcluding = vsVersionStartExcluding.getPartsCount();
final int semVerPartsCountStartExcluding = vsVersionStartExcluding.getSemVerPartsCount();
final int modifierPartsCountStartExcluding = vsVersionStartExcluding.getModifierPartsCount();
// the <= has been carefully chosen, only modify it if you know what you are doing
if (partsCountThis <= partsCountStartExcluding && modifierPartsCountThis <= modifierPartsCountStartExcluding && semVerPartsCountThis <= semVerPartsCountStartExcluding) {
if (!this.after(vsVersionStartExcluding)) {
return false;
}
} else {
if (!this.afterOrEqual(vsVersionStartExcluding)) {
return false;
}
}
}
final CommonPartsVersionImpl vsVersionEndExcluding = Version.of(normalisedVersionEndExcluding, context, CommonPartsVersionImpl::new);
if (vsVersionEndExcluding != null && vsVersionEndExcluding.isNotEmpty()) {
final int partsCountEndExcluding = vsVersionEndExcluding.getPartsCount();
final int semVerPartsCountEndExcluding = vsVersionEndExcluding.getSemVerPartsCount();
final int modifierPartsCountEndExcluding = vsVersionEndExcluding.getModifierPartsCount();
// the >= has been carefully chosen, only modify it if you know what you are doing
if (partsCountThis >= partsCountEndExcluding && modifierPartsCountThis >= modifierPartsCountEndExcluding && semVerPartsCountThis >= semVerPartsCountEndExcluding) {
if (!this.before(vsVersionEndExcluding)) {
return false;
}
} else {
if (!this.beforeOrEqual(vsVersionEndExcluding)) {
return false;
}
}
}
final Version vsVersionStartIncluding = Version.of(StringUtils.nonNull(normalisedVersionStartIncluding, normalisedVersionStartExcluding, normalisedCheckVersion), context, CommonPartsVersionImpl::new);
if (vsVersionStartIncluding != null && vsVersionStartIncluding.isNotEmpty() && !this.afterOrEqual(vsVersionStartIncluding)) {
return false;
}
final Version vsVersionEndIncluding = Version.of(StringUtils.nonNull(normalisedVersionEndIncluding, normalisedVersionEndExcluding, normalisedCheckVersion), context, CommonPartsVersionImpl::new);
if (vsVersionEndIncluding != null && vsVersionEndIncluding.isNotEmpty() && !this.beforeOrEqual(vsVersionEndIncluding)) {
return false;
}
}
return true;
}
private String joinIfFirstNonNull(String str1, String str2) {
if (StringUtils.hasText(str1)) {
return str1 + (StringUtils.hasText(str2) ? "_" + str2 : "");
}
return null;
}
private static class VersionTokenComparison {
private final boolean isNotEmpty1;
private final boolean isNotEmpty2;
private final int result;
public VersionTokenComparison(VersionToken o1, VersionToken o2) {
this.isNotEmpty1 = !VersionToken.isEmpty(o1);
this.isNotEmpty2 = !VersionToken.isEmpty(o2);
if (isBothNotEmpty()) {
this.result = CommonPartsVersionImpl.compareByPart(o1, o2);
} else {
this.result = 0;
}
}
public boolean isNotEmpty1() {
return isNotEmpty1;
}
public boolean isNotEmpty2() {
return isNotEmpty2;
}
public int getResult() {
return result;
}
public boolean isBothNotEmpty() {
return isNotEmpty1 && isNotEmpty2;
}
public static List getAllPartsWhereBothAreNonEmpty(VersionTokenComparison... versions) {
return Arrays.stream(versions)
.filter(VersionTokenComparison::isBothNotEmpty)
.collect(Collectors.toList());
}
@Override
public String toString() {
return "VersionTokenComparison{" +
"isNotEmpty1=" + isNotEmpty1 +
", isNotEmpty2=" + isNotEmpty2 +
", result=" + result +
'}';
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy