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

com.metaeffekt.artifact.analysis.version.AllCategorizedPartsVersionImpl 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.ConditionalCuratedVersionPartsExtractor;
import com.metaeffekt.artifact.analysis.version.curation.ConditionalCuratedVersionPartsExtractorCollection;
import com.metaeffekt.artifact.analysis.version.curation.ExtractedCuratedVersionParts;
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.version.token.VersionTokenizer;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.VersionComparator;
import com.metaeffekt.artifact.enrichment.matching.VulnerabilitiesFromCpeEnrichment;
import org.apache.commons.lang3.ObjectUtils;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import us.springett.parsers.cpe.Cpe;

import java.util.List;
import java.util.Optional;

import static com.metaeffekt.artifact.analysis.version.VersionModifier.NEUTRAL_TOKEN;

public class AllCategorizedPartsVersionImpl implements Version {

    protected final String rawVersion;
    protected final String rawUpdate;

    protected final VersionToken specVersion;
    protected final VersionToken semVersion;
    protected final VersionToken buildVersion;
    protected final VersionToken otherVersionPart;
    protected final VersionToken versionModifier;
    protected final VersionToken afterAllPart;

    protected AllCategorizedPartsVersionImpl(String version, String update, VersionContext versionContext) {
        this.rawVersion = version;
        this.rawUpdate = update;

        final List matchingExtractors = ConditionalCuratedVersionPartsExtractorCollection.findExtractors(VersionTokenizer.buildEffectiveVersionString(version, update), versionContext);
        final Optional firstExtractor = matchingExtractors.stream().findFirst();

        final Optional curatedResult = ConditionalCuratedVersionPartsExtractorCollection.applyFirstStep(firstExtractor, version, versionContext);

        final boolean isCuratedResultPresent = curatedResult.isPresent();
        final boolean curatedResultHasPreprocessedVersion = isCuratedResultPresent && curatedResult.get().isPreprocessorType();

        final ExtractedCuratedVersionParts effectiveResult;

        if (isCuratedResultPresent && !curatedResultHasPreprocessedVersion) {
            effectiveResult = curatedResult.get();

        } else {
            final List tokens;
            if (curatedResultHasPreprocessedVersion) {
                tokens = VersionTokenizer.tokenize(curatedResult.get().getPreprocessedVersionToBeTokenized());
            } else {
                tokens = VersionTokenizer.tokenize(version, update);
            }

            effectiveResult = new ExtractedCuratedVersionParts();

            // if there are more/equal to 3 string version parts (such as DUK-l09c10b187 --> l, c, b), then they are
            // most likely not used as update parts, but as some kind of separators. In this case, we do not want to
            // add them to the version parts.
            boolean containsThreeOrMoreStringVersionParts = tokens.stream().filter(t -> t.getType() == VersionTokenType.STRING && t.isComparableByString()).count() >= 3;

            for (int i = 0; i < tokens.size(); i++) {
                final VersionToken token = tokens.get(i);

                // skip single character strings at the beginning
                if (i == 0 && token.getType() == VersionTokenType.STRING && token.getValue().length() == 1) {
                    continue;
                }

                if (token.getType() == VersionTokenType.VERSION_MODIFIER) {
                    if (effectiveResult.getVersionModifier() == null) {
                        effectiveResult.setVersionModifier(token);
                    }

                } else if (token.isComparableByString() && !(containsThreeOrMoreStringVersionParts && token.getType() == VersionTokenType.STRING)) {
                    if (effectiveResult.getVersionModifier() != null && effectiveResult.getAfterAllPart() == null) {
                        effectiveResult.setAfterAllPart(token);
                    } else if (effectiveResult.getSpecVersion() == null) {
                        effectiveResult.setSpecVersion(token);
                    } else if (effectiveResult.getSemVersion() == null) {
                        effectiveResult.setSemVersion(token);
                    } else if (effectiveResult.getBuildVersion() == null) {
                        effectiveResult.setBuildVersion(token);
                    } else if (effectiveResult.getOtherVersionPart() == null) {
                        effectiveResult.setOtherVersionPart(token);
                    }
                }
            }

            // potentially rearrange the version parts, push all parts one back if the other part is null
            if (effectiveResult.getOtherVersionPart() == null && effectiveResult.getSpecVersion() != null
                    && (effectiveResult.getSpecVersion().getType() == VersionTokenType.NUMBER_OR_SEMVER || effectiveResult.getSpecVersion().getType() == VersionTokenType.DATE)) {

                effectiveResult.setOtherVersionPart(effectiveResult.getBuildVersion());
                effectiveResult.setBuildVersion(effectiveResult.getSemVersion());
                effectiveResult.setSemVersion(effectiveResult.getSpecVersion());
                effectiveResult.setSpecVersion(null);
            }

            // check if build part is a string >= 8 chars and if so, move it to other part if other is null
            if (effectiveResult.getOtherVersionPart() == null && effectiveResult.getBuildVersion() != null && effectiveResult.getBuildVersion().getValue().length() >= 8 && effectiveResult.getBuildVersion().getType() != VersionTokenType.NUMBER_OR_SEMVER) {
                effectiveResult.setOtherVersionPart(effectiveResult.getBuildVersion());
                effectiveResult.setBuildVersion(null);
            }

            // check if sem part is 4 dot separated parts long and if so, move the last part to the build part if build is null
            if (effectiveResult.getBuildVersion() == null && effectiveResult.getSemVersion() != null && effectiveResult.getSemVersion().getValue().split("\\.").length == 4) {
                final String[] parts = effectiveResult.getSemVersion().getValue().split("\\.");
                effectiveResult.setBuildVersion(new VersionToken(parts[3], VersionTokenType.NUMBER_OR_SEMVER));
                effectiveResult.setSemVersion(new VersionToken(org.apache.commons.lang3.StringUtils.join(parts, ".", 0, 3), VersionTokenType.NUMBER_OR_SEMVER));
            }

            // check if the current sem part does not contain "." and find if any other field after that contains a "." and if so, replace the sem part with it
            if (effectiveResult.getSemVersion() != null && !effectiveResult.getSemVersion().getValue().contains(".")) {
                if (effectiveResult.getBuildVersion() != null && effectiveResult.getBuildVersion().getValue().contains(".")) {
                    effectiveResult.setSemVersion(effectiveResult.getBuildVersion());
                    effectiveResult.setBuildVersion(null);
                } else if (effectiveResult.getOtherVersionPart() != null && effectiveResult.getOtherVersionPart().getValue().contains(".")) {
                    effectiveResult.setSemVersion(effectiveResult.getOtherVersionPart());
                    effectiveResult.setOtherVersionPart(null);
                }
            }
        }

        for (ConditionalCuratedVersionPartsExtractor extractor : matchingExtractors) {
            extractor.applySecondStep(effectiveResult);
        }

        this.specVersion = effectiveResult.getSpecVersion();
        this.semVersion = effectiveResult.getSemVersion();
        this.buildVersion = effectiveResult.getBuildVersion();
        this.versionModifier = effectiveResult.getVersionModifier();
        this.otherVersionPart = effectiveResult.getOtherVersionPart();
        this.afterAllPart = effectiveResult.getAfterAllPart();
    }

    public AllCategorizedPartsVersionImpl(String version, VersionContext versionContext) {
        this(version, null, versionContext);
    }

    @Override
    public int compareTo(Version o) {
        if (!(o instanceof AllCategorizedPartsVersionImpl)) {
            return 1;
        }

        return compareTo((AllCategorizedPartsVersionImpl) o);
    }

    public int compareTo(AllCategorizedPartsVersionImpl o) {
        if (o == null) {
            return 1;
        }

        // compare part by part (spec, sem, build, other, modifier)
        int result = compareByPart(specVersion, o.specVersion);
        if (result == 0) {
            result = compareByPart(semVersion, o.semVersion);
        }
        if (result == 0) {
            result = compareByPart(buildVersion, o.buildVersion);
        }
        if (result == 0) {
            result = compareByPart(otherVersionPart, o.otherVersionPart);
        }
        if (result == 0) {
            result = compareByPart(versionModifier, o.versionModifier);
        }
        if (result == 0) {
            result = compareByPart(afterAllPart, o.afterAllPart);
        }

        return result;
    }

    private int compareByPart(VersionToken o1, VersionToken o2) {
        if (o1 == null && o2 == null) {
            return 0;

        } else if (o1 == null || o2 == null) {
            // find the default value for the type that is non-null
            final VersionToken nonNull = ObjectUtils.firstNonNull(o1, o2);
            final VersionToken defaultValue;
            switch (nonNull.getType()) {
                case NUMBER_OR_SEMVER:
                    defaultValue = DEFAULT_NUMBER_OR_SEMVER;
                    break;
                case DATE:
                    defaultValue = DEFAULT_DATE;
                    break;
                case STRING:
                    defaultValue = DEFAULT_STRING;
                    break;
                case VERSION_MODIFIER:
                    defaultValue = DEFAULT_VERSION_MODIFIER;
                    break;
                default:
                    throw new IllegalStateException("Unknown version token type: " + nonNull.getType());
            }

            if (o1 == null) {
                o1 = defaultValue;
            } else {
                o2 = defaultValue;
            }
        }


        boolean is1VersionModifier = o1.getType() == VersionTokenType.VERSION_MODIFIER;
        boolean is2VersionModifier = o2.getType() == VersionTokenType.VERSION_MODIFIER;

        if (is1VersionModifier && is2VersionModifier) {
            return VersionToken.VERSION_MODIFIER_COMPARATOR.compare(o1, o2);
        } else if (!is1VersionModifier && !is2VersionModifier) {
            if (o1.isLettersOnly() || o2.isLettersOnly()) {
                return o1.getValue().compareTo(o2.getValue());
            } else {
                return VersionComparator.INSTANCE.compare(o1.getValue(), o2.getValue());
            }
        }

        return is1VersionModifier ? 1 : -1;
    }

    @Override
    public String getVersion() {
        return rawVersion;
    }

    @Override
    public String getUpdate() {
        return rawUpdate;
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof Version && equals((Version) obj);
    }

    public VersionToken getSpecVersion() {
        return specVersion;
    }

    public VersionToken getSemVersion() {
        return semVersion;
    }

    public VersionToken getBuildVersion() {
        return buildVersion;
    }

    public VersionToken getOtherVersionPart() {
        return otherVersionPart;
    }

    public VersionToken getVersionModifier() {
        return versionModifier;
    }

    public VersionToken getAfterAllPart() {
        return afterAllPart;
    }

    /**
     * Returns the {@link AllCategorizedPartsVersionImpl#toString()} part that only contains the pre-modifier parts
     * such that:
* spec-sem [build-other]
* This code is used by the * {@link VulnerabilitiesFromCpeEnrichment#deriveQueryVersion(Artifact, Cpe)} * method. * * @return the pre-modifier part of the version */ public String toStringPreModifierPart() { final StringBuilder sb = new StringBuilder(); if (nonNullAndNotEmpty(specVersion)) { sb.append(specVersion); } if (nonNullAndNotEmpty(semVersion)) { if (sb.length() > 0) { sb.append("-"); } sb.append(semVersion); } if (nonNullAndNotEmpty(buildVersion) || nonNullAndNotEmpty(otherVersionPart)) { sb.append(" ["); if (nonNullAndNotEmpty(buildVersion)) { sb.append(buildVersion); } if (nonNullAndNotEmpty(otherVersionPart)) { if (nonNullAndNotEmpty(buildVersion)) { sb.append("-"); } sb.append(otherVersionPart); } sb.append("]"); } return sb.toString().trim(); } /** * Returns the {@link AllCategorizedPartsVersionImpl#toString()} part that only contains the modifier and the * after-all part such that:
* (version modifier) after-all
* This code is used by the * {@link VulnerabilitiesFromCpeEnrichment#deriveQueryVersion(Artifact, Cpe)} * method. * * @return the modifier part of the version */ public String toStringModifierPart() { final StringBuilder sb = new StringBuilder(); if (nonNullAndNotEmpty(versionModifier) && versionModifier != NEUTRAL_TOKEN) { sb.append("(").append(versionModifier).append(")"); } if (nonNullAndNotEmpty(afterAllPart)) { sb.append(" ").append(afterAllPart); } return sb.toString().trim(); } @Override public String toString() { // return rawVersion + (rawUpdate != null ? ":" + rawUpdate : "") + " --> spec:" + specVersion + " sem:" + semVersion + " build:" + buildVersion + " other:" + otherVersionPart + " mod:" + versionModifier + " after-all:" + afterAllPart; return (toStringPreModifierPart() + " " + toStringModifierPart()).trim(); } private static boolean nonNullAndNotEmpty(VersionToken t) { return t != null && StringUtils.hasText(t.getValue()); } private final static VersionToken DEFAULT_NUMBER_OR_SEMVER = new VersionToken("0", VersionTokenType.NUMBER_OR_SEMVER); private final static VersionToken DEFAULT_DATE = new VersionToken("00000000", VersionTokenType.DATE); private final static VersionToken DEFAULT_STRING = new VersionToken("\u0000", VersionTokenType.STRING); private final static VersionToken DEFAULT_VERSION_MODIFIER = new VersionToken(NEUTRAL_TOKEN.getValue(), VersionTokenType.VERSION_MODIFIER); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy