All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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