<commit_before>/*
 * This file is part of Dependency-Track.
 *
 * 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.
 *
 * Copyright (c) <NAME>. All Rights Reserved.
 */
package org.dependencytrack.tasks.scanners;

import alpine.event.framework.Event;
import alpine.event.framework.Subscriber;
import alpine.logging.Logger;
import com.github.packageurl.PackageURL;
import org.dependencytrack.common.UnirestFactory;
import org.dependencytrack.parser.npm.NpmAuditParser;
import org.json.JSONObject;
import org.dependencytrack.event.MetricsUpdateEvent;
import org.dependencytrack.event.NpmAuditAnalysisEvent;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.Vulnerability;
import org.dependencytrack.parser.npm.model.Advisory;
import org.dependencytrack.persistence.QueryManager;
import org.dependencytrack.util.NotificationUtil;
import unirest.HttpResponse;
import unirest.JsonNode;
import unirest.UnirestException;
import unirest.UnirestInstance;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Subscriber task that performs an analysis of component using NPM Audit API.
 *
 * @author <NAME>
 * @since 3.0.0
 */
public class NpmAuditAnalysisTask extends BaseComponentAnalyzerTask implements Subscriber {

    private static final String API_BASE_URL = "https://registry.npmjs.org/-/npm/v1/security/audits";
    private static final Logger LOGGER = Logger.getLogger(NpmAuditAnalysisTask.class);

    /**
     * {@inheritDoc}
     */
    public void inform(final Event e) {
        if (e instanceof NpmAuditAnalysisEvent) {
            if (!super.isEnabled(ConfigPropertyConstants.SCANNER_NPMAUDIT_ENABLED)) {
                return;
            }
            final NpmAuditAnalysisEvent event = (NpmAuditAnalysisEvent)e;
            LOGGER.info("Starting Node Audit analysis task");
            if (event.getComponents().size() > 0) {
                analyze(event.getComponents());
            } else {
                super.analyze();
            }
            LOGGER.info("Node Audit analysis complete");
        }
    }

    /**
     * Determines if the {@link NpmAuditAnalysisTask} is suitable for analysis based on the PackageURL.
     *
     * @param purl the PackageURL to analyze
     * @return true if NpmAuditAnalysisTask should analyze, false if not
     */
    public boolean shouldAnalyze(final PackageURL purl) {
        if (purl == null) {
            return false;
        }
        if (purl.getType().equalsIgnoreCase("npm")) {
            return true;
        }
        return false;
    }

    /**
     * Analyzes a list of Components. The NPM Audit API is only capable of analyzing one
     * version of a node module at a time. For example, attempting to analyze three versions
     * of 'serve' for example will result in only the last version in the payload specified
     * being scanned. Therefore, this method uses a Map with the name of the module being the
     * maps key to ensure multiple versions are not sent at the same time. This will result
     * in multiple requests being made to the NPM Audit API.
     * @param components a list of Components
     */
    public void analyze(final List<Component> components) {
        final CopyOnWriteArrayList<Component> backlog = new CopyOnWriteArrayList<>(components);
        final Iterator<Component> backlogIterator = backlog.iterator();
        while (backlog.size() > 0) {
            final Map<String, Component> npmCandidates = new HashMap<>();
            final JSONObject npmRequires = new JSONObject();
            final JSONObject npmDependencies = new JSONObject();

            while (backlogIterator.hasNext()) {
                final Component component = backlogIterator.next();
                final PackageURL purl = component.getPurl();
                if (shouldAnalyze(purl)) {
                    if (!npmCandidates.containsKey(component.getName())) {
                        npmCandidates.put(component.getName(), component);
                        npmRequires.put(purl.getName(), purl.getVersion());
                        npmDependencies.put(purl.getName(), new JSONObject().put("version", purl.getVersion()));
                        backlog.remove(component);
                    }
                } else {
                    backlog.remove(component);
                }
            }

            if (npmRequires.length() > 0) {
                // Build a minimal package-lock.json in memory
                final JSONObject packageJson = new JSONObject();
                packageJson.put("name", "test-package");
                packageJson.put("version", "1.0.0");<commit_msg>#264 - Fixes threading/looping issue<commit_after>/*
 * This file is part of Dependency-Track.
 *
 * 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.
 *
 * Copyright (c) <NAME>. All Rights Reserved.
 */
package org.dependencytrack.tasks.scanners;

import alpine.event.framework.Event;
import alpine.event.framework.Subscriber;
import alpine.logging.Logger;
import com.github.packageurl.PackageURL;
import org.dependencytrack.common.UnirestFactory;
import org.dependencytrack.parser.npm.NpmAuditParser;
import org.json.JSONObject;
import org.dependencytrack.event.MetricsUpdateEvent;
import org.dependencytrack.event.NpmAuditAnalysisEvent;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.Vulnerability;
import org.dependencytrack.parser.npm.model.Advisory;
import org.dependencytrack.persistence.QueryManager;
import org.dependencytrack.util.NotificationUtil;
import unirest.HttpResponse;
import unirest.JsonNode;
import unirest.UnirestException;
import unirest.UnirestInstance;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Subscriber task that performs an analysis of component using NPM Audit API.
 *
 * @author <NAME>
 * @since 3.0.0
 */
public class NpmAuditAnalysisTask extends BaseComponentAnalyzerTask implements Subscriber {

    private static final String API_BASE_URL = "https://registry.npmjs.org/-/npm/v1/security/audits";
    private static final Logger LOGGER = Logger.getLogger(NpmAuditAnalysisTask.class);

    /**
     * {@inheritDoc}
     */
    public void inform(final Event e) {
        if (e instanceof NpmAuditAnalysisEvent) {
            if (!super.isEnabled(ConfigPropertyConstants.SCANNER_NPMAUDIT_ENABLED)) {
                return;
            }
            final NpmAuditAnalysisEvent event = (NpmAuditAnalysisEvent)e;
            LOGGER.info("Starting Node Audit analysis task");
            if (event.getComponents().size() > 0) {
                analyze(event.getComponents());
            } else {
                super.analyze();
            }
            LOGGER.info("Node Audit analysis complete");
        }
    }

    /**
     * Determines if the {@link NpmAuditAnalysisTask} is suitable for analysis based on the PackageURL.
     *
     * @param purl the PackageURL to analyze
     * @return true if NpmAuditAnalysisTask should analyze, false if not
     */
    public boolean shouldAnalyze(final PackageURL purl) {
        if (purl == null) {
            return false;
        }
        return "npm".equals(purl.getType());
    }

    /**
     * Analyzes a list of Components. The NPM Audit API is only capable of analyzing one
     * version of a node module at a time. For example, attempting to analyze three versions
     * of 'serve' for example will result in only the last version in the payload specified
     * being scanned. Therefore, this method uses a Map with the name of the module being the
     * maps key to ensure multiple versions are not sent at the same time. This will result
     * in multiple requests being made to the NPM Audit API.
     * @param components a list of Components
     */
    public void analyze(final List<Component> components) {
        final ArrayList<Component> backlog = new ArrayList<>(components);
        while (!backlog.isEmpty()) {
            // Defines a Map of Components to analyze
            final Map<String, Component> npmCandidates = new HashMap<>();

            // NPM payload objects
            final JSONObject npmRequires = new JSONObject();
            final JSONObject npmDependencies = new JSONObject();

            for (Iterator<Component> iterator = backlog.iterator(); iterator.hasNext();) {
                Component component = iterator.next();
                final PackageURL purl = component.getPurl();
                if (shouldAnalyze(purl)) {
                    if (!npmCandidates.containsKey(component.getName())) {
                        npmCandidates.put(component.getName(), component);
                        npmRequires.put(purl.getName(), purl.getVersion());
                        npmDependencies.put(purl.getName(), new JSONObject().put("version", purl.getVersion()));
                        iterator.remove(); // Remove the current component being iterated on
                    }
                } else {
                    iterator.remove(); // Remove the current component being iterated on
                }
            }

            if (npmRequires.length() > 0) {
                // Build a minimal package-lock.json in memory
                final JSONObject packageJson = new JSONObject();
                packageJson.put("name", "test-package");
                packageJson.put("version", "1.0.0");
{'fraction_non_alphanumeric': 0.08956757261604557, 'fraction_numerical': 0.002801905295601009, 'mean_word_length': 2.724521739130435, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 24, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '19027', 'n_tokens_mistral': 2844, 'n_tokens_neox': 2719, 'n_words': 867}