package egovframework.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import javax.annotation.PostConstruct;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.validation.SchemaFactory;

@Slf4j
@Configuration
public class XxeConfig {

    private void documentBuilderFactory() {
        log.info("XxeConfig.documentBuilderFactory.");
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance("com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", null);

        try {
            // to be compliant, completely disable DOCTYPE declaration:
            factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            // or completely disable external entities declarations:
            factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
            factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            // or prohibit the use of all protocols by external entities:
            //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
            //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
            // or disable entity expansion but keep in mind that this doesn't prevent fetching external entities
            // and this solution is not correct for OpenJDK < 13 due to a bug: https://bugs.openjdk.java.net/browse/JDK-8206132
            factory.setExpandEntityReferences(false);
            // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
            factory.setXIncludeAware(false);
        } catch (ParserConfigurationException e) {
            log.error("XxeConfig.documentBuilderFactory: ParserConfigurationException.");
        }
    }

    private void saxParserFactory() {
        log.info("XxeConfig.saxParserFactory.");
        SAXParserFactory factory = SAXParserFactory.newInstance();
        try {
            // to be compliant, completely disable DOCTYPE declaration:
            factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            // or completely disable external entities declarations:
            factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
            factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            // or prohibit the use of all protocols by external entities:
            SAXParser parser = null; // Noncompliant
            parser = factory.newSAXParser();
            parser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
            parser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
        } catch (SAXNotRecognizedException e) {
            log.error("XxeConfig.saxParserFactory: SAXNotRecognizedException.");
        } catch (SAXNotSupportedException e) {
            log.error("XxeConfig.saxParserFactory: SAXNotSupportedException.");
        } catch (ParserConfigurationException e) {
            log.error("XxeConfig.saxParserFactory: ParserConfigurationException.");
        } catch (SAXException e) {
            log.error("XxeConfig.saxParserFactory: SAXException.");
        }
    }

    private void xmlInputFactory() {
        log.info("XxeConfig.xmlInputFactory.");
        XMLInputFactory factory = XMLInputFactory.newInstance();
        // to be compliant, completely disable DOCTYPE declaration:
        factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
        // or completely disable external entities declarations:
        factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
        factory.setProperty("javax.xml.stream.isSupportingExternalEntities", false);
        // or prohibit the use of all protocols by external entities:
        factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
    }

    private void transformerFactory() {
        log.info("XxeConfig.transformerFactory.");
        TransformerFactory factory = TransformerFactory.newInstance();
        // to be compliant, prohibit the use of all protocols by external entities:
        //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
    }

    private void schemaFactory() {
        log.info("XxeConfig.schemaFactory.");
        SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        // to be compliant, completely disable DOCTYPE declaration:
        try {
            factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            // or prohibit the use of all protocols by external entities:
            factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
            factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
        } catch (SAXNotRecognizedException e) {
            log.error("XxeConfig.schemaFactory: SAXNotRecognizedException.");
        } catch (SAXNotSupportedException e) {
            log.error("XxeConfig.schemaFactory: SAXNotSupportedException.");
        }
    }

//    private void saxReader() {
//        log.info("XxeConfig.saxReader.");
//        SAXReader xmlReader = new SAXReader();
//        try {
//            xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
//        } catch (SAXException e) {
//            log.error("XxeConfig.saxReader: SAXException.");
//        }
//    }

    private void saxTransformerFactory() {
//        log.info("XxeConfig.saxTransformerFactory.");
//        SAXTransformerFactory sf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
//        sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
//        sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
    }

    private void oracleDomParser() {
//        // Extend oracle.xml.parser.v2.XMLParser
//        DOMParser domParser = new DOMParser();
//        // Do not expand entity references
//        domParser.setAttribute(DOMParser.EXPAND_ENTITYREF, false);
//        // dtdObj is an instance of oracle.xml.parser.v2.DTD
//        domParser.setAttribute(DOMParser.DTD_OBJECT, dtdObj);
//        // Do not allow more than 11 levels of entity expansion
//        domParser.setAttribute(DOMParser.ENTITY_EXPANSION_DEPTH, 12);
    }

    private void saxBuilder() {
//        SAXBuilder builder = new SAXBuilder();
//        builder.setFeature("http://xml.org/sax/features/external-general-entities", false);
//        builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
//        builder.setExpandEntities(false);
//        builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
//        builder.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
//        builder.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
    }

    private void xmlReader() {
//        log.info("XxeConfig.xmlReader.");
//        try {
//            XMLReader reader = (XMLReader) XMLReaderFactory.createXMLReader();
//            reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
//            reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
//            reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
//            reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
//        } catch (SAXException e) {
//            log.error("XxeConfig.xmlReader: SAXException.");
//        } catch (jdk.internal.org.xml.sax.SAXNotRecognizedException e) {
//            log.error("XxeConfig.xmlReader: SAXNotRecognizedException.");
//        } catch (jdk.internal.org.xml.sax.SAXNotSupportedException e) {
//            log.error("XxeConfig.xmlReader: SAXNotSupportedException.");
//        }
    }

    @PostConstruct
    private void init() {
        System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");

        documentBuilderFactory();
        //saxParserFactory();
        xmlInputFactory();
        transformerFactory();
        //schemaFactory();
//        saxReader();
        saxTransformerFactory();
        oracleDomParser();
        saxBuilder();
        xmlReader();
    }

}
