DirectoryBuildPropsParser.java

/*
 * This file is part of dependency-check-core.
 *
 * 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) 2023 Jeremy Long. All Rights Reserved.
 */
package org.owasp.dependencycheck.data.nuget;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Parses `Directory.Build.props`.
 *
 * @see
 * <a href="https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2019">Directory.Build.props</a>
 * @author Jeremy Long
 */
public class DirectoryBuildPropsParser {

    /**
     * The collection of imports identified during parsing.
     */
    private Set<String> imports = new HashSet<>();

    /**
     * Returns the imports identified during parsing.
     *
     * @return the imports identified during parsing.
     */
    public Set<String> getImports() {
        return imports;
    }

    /**
     * Parse the properties from the `Directory.Build.props` file InputStream.If
     * any import nodes are found while parsing, the values will be available
     * via `getImports()` after parsing is complete.
     *
     * @param stream the input stream containing the props file to parse.
     * @return the properties.
     * @throws MSBuildProjectParseException thrown if there is a parsing error.
     */
    public Map<String, String> parse(InputStream stream) throws MSBuildProjectParseException {
        try {
            final HashMap<String, String> props = new HashMap<>();

            final DocumentBuilder db = XmlUtils.buildSecureDocumentBuilder();
            final Document d = db.parse(stream);

            final XPath xpath = XPathFactory.newInstance().newXPath();

            //<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
            final NodeList importList = (NodeList) xpath.evaluate("//Import", d, XPathConstants.NODESET);
            if (importList != null) {
                for (int i = 0; i < importList.getLength(); i++) {
                    final Node importNode = importList.item(i);
                    final Node project = importNode.getAttributes().getNamedItem("Project");
                    imports.add(project.getNodeValue());
                }
            }
            final NodeList propertyGroups = (NodeList) xpath.evaluate("//PropertyGroup", d, XPathConstants.NODESET);
            if (propertyGroups != null) {
                for (int i = 0; i < propertyGroups.getLength(); i++) {
                    final Node group = propertyGroups.item(i);
                    final NodeList propertyNodes = group.getChildNodes();
                    for (int x = 0; x < propertyNodes.getLength(); x++) {
                        final Node node = propertyNodes.item(x);
                        if (node instanceof Element) {
                            final Element property = (Element) node;
                            final String name = property.getNodeName();
                            final Node value = property.getChildNodes().item(0);
                            if (value != null) {
                                props.put(name, value.getNodeValue().trim());
                            }
                        }
                    }
                }
            }
            return props;
        } catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException ex) {
            throw new MSBuildProjectParseException("Error parsing Directory.Build.props", ex);
        }
    }
}