/*
 * Copyright (c) 2022 the Eclipse Milo Authors
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.milo.opcua.stack.core.util;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerFactory;

import org.slf4j.LoggerFactory;

public class SecureXmlUtil {

    private SecureXmlUtil() {}

    /**
     * A shared {@link DocumentBuilderFactory} that has been configured securely to prevent XXE attacks.
     */
    public static final DocumentBuilderFactory SHARED_DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();

    /**
     * A shared {@link TransformerFactory} that has been configured securely to prevent XXE attacks.
     */
    public static final TransformerFactory SHARED_TRANSFORMER_FACTORY = TransformerFactory.newInstance();

    static {
        SHARED_DOCUMENT_BUILDER_FACTORY.setCoalescing(true);
        SHARED_DOCUMENT_BUILDER_FACTORY.setNamespaceAware(true);

        // XXE Prevention
        // https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet

        SHARED_DOCUMENT_BUILDER_FACTORY.setExpandEntityReferences(false);
        SHARED_DOCUMENT_BUILDER_FACTORY.setXIncludeAware(false);

        trySetFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        trySetFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        trySetFeature("http://xml.org/sax/features/external-general-entities", false);
        trySetFeature("http://xml.org/sax/features/external-parameter-entities", false);
        trySetFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

        trySetAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        trySetAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
    }

    private static void trySetFeature(String feature, boolean value) {
        try {
            SHARED_DOCUMENT_BUILDER_FACTORY.setFeature(feature, value);
        } catch (Exception e) {
            LoggerFactory.getLogger(SecureXmlUtil.class)
                .warn("Error configuring feature: " + feature + "=" + value, e);
        }
    }

    private static void trySetAttribute(String attribute, Object value) {
        try {
            SHARED_TRANSFORMER_FACTORY.setAttribute(attribute, value);
        } catch (Exception e) {
            LoggerFactory.getLogger(SecureXmlUtil.class)
                .warn("Error configuring attribute: " + attribute + "=" + value, e);
        }
    }

}
