/*
Paros and its related class files.
Paros is an HTTP/HTTPS proxy for assessing web application security.
Copyright (C) 2003-2004 Chinotec Technologies Company

This program is free software; you can redistribute it and/or
modify it under the terms of the Clarified Artistic License
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Clarified Artistic License for more details.

You should have received a copy of the Clarified Artistic License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
// ZAP: 2011/11/04 Correct entityEncode
// ZAP: 2011/12/19 Escape invalid XML characters
// ZAP: 2012/08/07 synchronize calls on staticDateFormat
// ZAP: 2013/01/23 Clean up of exception handling/logging.
// ZAP: 2013/09/26 Issue 802: XML report generated by API differs from GUI report
// ZAP: 2015/08/19 Issue 1804: Disable processing of XML external entities by default
// ZAP: 2015/11/16 Issue 1555: Rework inclusion of HTML tags in reports
// ZAP: 2016/04/18 Issue 2127: Added return statements & GUI dialogs after IOExceptions
// ZAP: 2016/06/25 pull 2561: It specifies the Charset of stringToHtml to UTF-8
// ZAP: 2017/06/21 Issue 3559: Support JSON format
// ZAP: 2018/07/04 Allow to use a custom StreamSource for the XSL.
// ZAP: 2018/12/27 Use always an array for sites in JSON format (Issue 5162).
// ZAP: 2019/06/01 Normalise line endings.
// ZAP: 2019/06/05 Normalise format/style.
// ZAP: 2020/08/06 Issue 6084: Added date time to html report.
// ZAP: 2020/10/29 Issue 6267: Fix bug to allow writing reports with file path containing '#'.
// ZAP: 2020/11/26 Use Log4j 2 classes for logging.
// ZAP: 2021/04/19 Use try-with-resource statements.
// ZAP: 2021/06/23 Copied from the ZAP core prior to being deleted from there
package org.zaproxy.zap.extension.codedx;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.JOptionPane;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.xml.XMLSerializer;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.view.View;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.zaproxy.zap.utils.XMLStringUtil;
import org.zaproxy.zap.utils.XmlUtils;

public class ReportGenerator {

    private static final Logger LOGGER = LogManager.getLogger(ReportGenerator.class);

    // private static Pattern patternWindows = Pattern.compile("window", Pattern.CASE_INSENSITIVE);
    // private static Pattern patternLinux = Pattern.compile("linux", Pattern.CASE_INSENSITIVE);

    private static final SimpleDateFormat staticDateFormat =
            new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss");

    public static File XMLToHtml(Document xmlDocument, String infilexsl, File outFile) {
        File stylesheet = null;

        outFile = new File(outFile.getAbsolutePath());
        try {
            stylesheet = new File(infilexsl);

            DOMSource source = new DOMSource(xmlDocument);

            // Use a Transformer for output
            TransformerFactory tFactory = TransformerFactory.newInstance();
            StreamSource stylesource = new StreamSource(stylesheet);
            Transformer transformer = tFactory.newTransformer(stylesource);

            // Make the transformation and write to the output file
            StreamResult result = new StreamResult(outFile.getPath());
            transformer.transform(source, result);

        } catch (TransformerException e) {
            LOGGER.error(e.getMessage(), e);
        }

        return outFile;
    }

    public static File stringToHtml(String inxml, String infilexsl, String outfilename) {
        return stringToHtml(
                inxml,
                infilexsl != null ? new StreamSource(new File(infilexsl)) : null,
                outfilename);
    }

    public static File stringToHtml(String inxml, StreamSource stylesource, String outfilename) {
        if (stylesource != null) {
            Document doc = null;

            // factory.setNamespaceAware(true);
            // factory.setValidating(true);
            File outfile = null;
            StringReader inReader = new StringReader(inxml);
            String tempOutfilename = outfilename + ".temp";

            try {
                outfile = new File(tempOutfilename);

                DocumentBuilder builder =
                        XmlUtils.newXxeDisabledDocumentBuilderFactory().newDocumentBuilder();
                doc = builder.parse(new InputSource(inReader));

                // Use a Transformer for output
                TransformerFactory tFactory = TransformerFactory.newInstance();
                Transformer transformer = tFactory.newTransformer(stylesource);
                transformer.setParameter("datetime", getCurrentDateTimeString());

                DOMSource source = new DOMSource(doc);
                StreamResult result = new StreamResult(outfile.getPath());
                transformer.transform(source, result);

            } catch (TransformerException
                    | SAXException
                    | ParserConfigurationException
                    | IOException e) {
                LOGGER.error(e.getMessage(), e);
                // Save the xml for diagnosing the problem
                BufferedWriter bw = null;
                showDialogForGUI();
                try {
                    bw =
                            Files.newBufferedWriter(
                                    new File(outfilename + "-orig.xml").toPath(),
                                    StandardCharsets.UTF_8);
                    bw.write(inxml);
                } catch (IOException e2) {
                    LOGGER.error("Failed to write debug XML file", e);
                    return new File(outfilename);
                } finally {
                    try {
                        if (bw != null) {
                            bw.close();
                        }
                    } catch (IOException ex) {
                    }
                }
                return new File(outfilename);
            } finally {

            }
            // Replace the escaped tags used to make the report look slightly better.
            // This is a temp fix to ensure reports always get generated
            // we should really adopt something other than XSLT ;)
            String line;

            try (BufferedReader br =
                            Files.newBufferedReader(
                                    new File(tempOutfilename).toPath(), StandardCharsets.UTF_8);
                    BufferedWriter bw =
                            Files.newBufferedWriter(
                                    new File(outfilename).toPath(), StandardCharsets.UTF_8)) {

                while ((line = br.readLine()) != null) {
                    bw.write(line.replace("&lt;p&gt;", "<p>").replace("&lt;/p&gt;", "</p>"));
                    bw.newLine();
                }

            } catch (IOException e) {
                showDialogForGUI();
                LOGGER.error(e.getMessage(), e);
            }
            // Remove the temporary file
            outfile.delete();
        } else {
            // No XSLT file specified, just output the XML straight to the file
            BufferedWriter bw = null;

            try {
                bw =
                        Files.newBufferedWriter(
                                new File(outfilename).toPath(), StandardCharsets.UTF_8);
                bw.write(inxml);
            } catch (IOException e2) {
                showDialogForGUI();
                LOGGER.error(e2.getMessage(), e2);
            } finally {
                try {
                    if (bw != null) {
                        bw.close();
                    }
                } catch (IOException ex) {
                }
            }
        }

        return new File(outfilename);
    }

    public static File stringToJson(String inxml, String outfilename) {
        BufferedWriter bw = null;
        try {
            bw = Files.newBufferedWriter(new File(outfilename).toPath(), StandardCharsets.UTF_8);
            bw.write(stringToJson(inxml));
        } catch (IOException e2) {
            showDialogForGUI();
            LOGGER.error(e2.getMessage(), e2);
        } finally {
            try {
                if (bw != null) {
                    bw.close();
                }
            } catch (IOException ex) {
            }
        }

        return new File(outfilename);
    }

    public static String stringToHtml(String inxml, String infilexsl) {
        return stringToHtml(inxml, new StreamSource(new File(infilexsl)));
    }

    public static String stringToHtml(String inxml, StreamSource stylesource) {
        Document doc = null;

        StringReader inReader = new StringReader(inxml);
        StringWriter writer = new StringWriter();

        try {

            DocumentBuilder builder =
                    XmlUtils.newXxeDisabledDocumentBuilderFactory().newDocumentBuilder();
            doc = builder.parse(new InputSource(inReader));

            // Use a Transformer for output
            TransformerFactory tFactory = TransformerFactory.newInstance();
            Transformer transformer = tFactory.newTransformer(stylesource);
            transformer.setParameter("datetime", getCurrentDateTimeString());

            DOMSource source = new DOMSource(doc);
            StreamResult result = new StreamResult(writer);
            transformer.transform(source, result);

        } catch (TransformerException
                | SAXException
                | ParserConfigurationException
                | IOException e) {
            showDialogForGUI();
            LOGGER.error(e.getMessage(), e);
        } finally {

        }

        // Replace the escaped tags used to make the report look slightly better.
        // This is a temp fix to ensure reports always get generated
        // we should really adopt something other than XSLT ;)
        return writer.toString().replace("&lt;p&gt;", "<p>").replace("&lt;/p&gt;", "</p>");
    }

    public static File fileToHtml(String infilexml, String infilexsl, String outfilename) {
        Document doc = null;

        // factory.setNamespaceAware(true);
        // factory.setValidating(true);
        File stylesheet = null;
        File datafile = null;
        File outfile = null;

        try {
            stylesheet = new File(infilexsl);
            datafile = new File(infilexml);
            outfile = new File(outfilename);

            DocumentBuilder builder =
                    XmlUtils.newXxeDisabledDocumentBuilderFactory().newDocumentBuilder();
            doc = builder.parse(datafile);

            // Use a Transformer for output
            TransformerFactory tFactory = TransformerFactory.newInstance();
            StreamSource stylesource = new StreamSource(stylesheet);
            Transformer transformer = tFactory.newTransformer(stylesource);
            transformer.setParameter("datetime", getCurrentDateTimeString());

            DOMSource source = new DOMSource(doc);
            StreamResult result = new StreamResult(outfile.getPath());
            transformer.transform(source, result);

        } catch (TransformerException
                | SAXException
                | ParserConfigurationException
                | IOException e) {
            showDialogForGUI();
            LOGGER.error(e.getMessage(), e);
        } finally {

        }

        return outfile;
    }

    public static String stringToJson(String inxml) {
        JSONObject report = (JSONObject) new XMLSerializer().read(inxml);
        Object site = report.get("site");
        if (!(site instanceof JSONArray)) {
            JSONArray siteArray = new JSONArray();
            if (site != null) {
                siteArray.add(site);
            }
            report.put("site", siteArray);
        }
        return report.toString();
    }

    /** Encode entity for HTML or XML output. */
    public static String entityEncode(String text) {
        String result = text;

        if (result == null) {
            return result;
        }

        // The escapeXml function doesn't cope with some 'special' chrs

        return StringEscapeUtils.escapeXml10(XMLStringUtil.escapeControlChrs(result));
    }

    /** Get today's date string. */
    public static String getCurrentDateTimeString() {
        Date dateTime = new Date(System.currentTimeMillis());
        return getDateTimeString(dateTime);
    }

    public static String getDateTimeString(Date dateTime) {
        // ZAP: fix unsafe call to DateFormats
        synchronized (staticDateFormat) {
            return staticDateFormat.format(dateTime);
        }
    }

    public static void addChildTextNode(
            Document doc, Element parent, String nodeName, String text) {
        Element child = doc.createElement(nodeName);
        child.appendChild(doc.createTextNode(text));
        parent.appendChild(child);
    }

    public static String getDebugXMLString(Document doc) throws TransformerException {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        StringWriter writer = new StringWriter();
        transformer.transform(new DOMSource(doc), new StreamResult(writer));
        return writer.getBuffer().toString().replaceAll("\n|\r", "");
    }

    private static void showDialogForGUI() {
        if (View.isInitialised()) {
            JOptionPane.showMessageDialog(
                    null, Constant.messages.getString("report.write.dialog.message"));
        }
    }
}
