From a5c1e5dfe50a4a226592c3561af156dabccf3c0b Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 22 Dec 2025 17:16:47 +0300 Subject: [PATCH 1/2] Removed jdom dependency. Signed-off-by: Ahmad Al-Sanie Signed-off-by: Ahmad Signed-off-by: aalsanie --- CHANGELOG.md | 2 + .../zaproxy/clientapi/core/AlertsFile.java | 273 +++++++++++++----- .../zap-clientapi/zap-clientapi.gradle | 3 - 3 files changed, 206 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6bcb1b..bab899b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Removed +- Removed `JDOM` dependency ## [1.17.0] - 2025-12-15 ### Added diff --git a/subprojects/zap-clientapi/src/main/java/org/zaproxy/clientapi/core/AlertsFile.java b/subprojects/zap-clientapi/src/main/java/org/zaproxy/clientapi/core/AlertsFile.java index f2a8f2a..5299864 100644 --- a/subprojects/zap-clientapi/src/main/java/org/zaproxy/clientapi/core/AlertsFile.java +++ b/subprojects/zap-clientapi/src/main/java/org/zaproxy/clientapi/core/AlertsFile.java @@ -3,7 +3,7 @@ * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * - * Copyright 2012 The ZAP Development Team + * Copyright 2025 The ZAP Development Team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,110 +25,245 @@ import java.nio.file.Files; import java.util.ArrayList; import java.util.List; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jdom.input.SAXBuilder; -import org.jdom.output.Format; -import org.jdom.output.XMLOutputter; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +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 org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; public class AlertsFile { + + /** + * Writes the alerts into an XML file with the following structure: + * + *
+     * <alerts>
+     *   <alertsFound alertsFound="N">
+     *     <alert .../>
+     *   </alertsFound>
+     *   <alertsNotFound alertsNotFound="M">
+     *     <alert .../>
+     *   </alertsNotFound>
+     *   <ignoredAlertsFound ignoredAlertsFound="K">
+     *     <alert .../>
+     *   </ignoredAlertsFound>
+     * </alerts>
+     * 
+ */ public static void saveAlertsToFile( List requireAlerts, List reportAlerts, List ignoredAlerts, File outputFile) - throws JDOMException, IOException { - Element alerts = new Element("alerts"); - Document alertsDocument = new Document(alerts); - alertsDocument.setRootElement(alerts); - if (reportAlerts.size() > 0) { - Element alertsFound = new Element("alertsFound"); - alertsFound.setAttribute("alertsFound", Integer.toString(reportAlerts.size())); - for (Alert alert : reportAlerts) { - createAlertXMLElements(alertsFound, alert); - } - alertsDocument.getRootElement().addContent(alertsFound); + throws IOException { + + if (requireAlerts == null) { + requireAlerts = new ArrayList<>(); + } + if (reportAlerts == null) { + reportAlerts = new ArrayList<>(); } + if (ignoredAlerts == null) { + ignoredAlerts = new ArrayList<>(); + } + + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document alertsDocument = builder.newDocument(); + + Element root = alertsDocument.createElement("alerts"); + alertsDocument.appendChild(root); - if (requireAlerts.size() > 0) { - Element alertsNotFound = new Element("alertsNotFound"); - alertsNotFound.setAttribute("alertsNotFound", Integer.toString(requireAlerts.size())); - for (Alert alert : requireAlerts) { - createAlertXMLElements(alertsNotFound, alert); + if (!reportAlerts.isEmpty()) { + Element alertsFound = alertsDocument.createElement("alertsFound"); + alertsFound.setAttribute("alertsFound", Integer.toString(reportAlerts.size())); + for (Alert alert : reportAlerts) { + createAlertXMLElements(alertsDocument, alertsFound, alert); + } + root.appendChild(alertsFound); } - alertsDocument.getRootElement().addContent(alertsNotFound); - } - if (ignoredAlerts.size() > 0) { - Element ignoredAlertsFound = new Element("ignoredAlertsFound"); - ignoredAlertsFound.setAttribute( - "ignoredAlertsFound", Integer.toString(ignoredAlerts.size())); - for (Alert alert : ignoredAlerts) { - createAlertXMLElements(ignoredAlertsFound, alert); + if (!requireAlerts.isEmpty()) { + Element alertsNotFound = alertsDocument.createElement("alertsNotFound"); + alertsNotFound.setAttribute( + "alertsNotFound", Integer.toString(requireAlerts.size())); + for (Alert alert : requireAlerts) { + createAlertXMLElements(alertsDocument, alertsNotFound, alert); + } + root.appendChild(alertsNotFound); } - alertsDocument.getRootElement().addContent(ignoredAlertsFound); - } - writeAlertsToFile(outputFile, alertsDocument); + if (!ignoredAlerts.isEmpty()) { + Element ignoredAlertsFound = alertsDocument.createElement("ignoredAlertsFound"); + ignoredAlertsFound.setAttribute( + "ignoredAlertsFound", Integer.toString(ignoredAlerts.size())); + for (Alert alert : ignoredAlerts) { + createAlertXMLElements(alertsDocument, ignoredAlertsFound, alert); + } + root.appendChild(ignoredAlertsFound); + } + + writeAlertsToFile(outputFile, alertsDocument); + + } catch (ParserConfigurationException | TransformerException e) { + throw new RuntimeException("Failed to save alerts to file: " + outputFile, e); + } } - private static void writeAlertsToFile(File outputFile, Document doc) throws IOException { + private static void writeAlertsToFile(File outputFile, Document doc) + throws IOException, TransformerException { - XMLOutputter xmlOutput = new XMLOutputter(); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); - xmlOutput.setFormat(Format.getPrettyFormat()); try (OutputStream os = Files.newOutputStream(outputFile.toPath())) { - xmlOutput.output(doc, os); + DOMSource source = new DOMSource(doc); + StreamResult result = new StreamResult(os); + transformer.transform(source, result); System.out.println("alert xml report saved to: " + outputFile.getAbsolutePath()); } } - private static void createAlertXMLElements(Element alertsFound, Alert alert) { - Element alertElement = new Element("alert"); + private static void createAlertXMLElements(Document doc, Element alertsParent, Alert alert) { + + Element alertElement = doc.createElement("alert"); + if (alert.getName() != null) { alertElement.setAttribute("name", alert.getName()); // TODO Remove once alert attribute is no longer supported. alertElement.setAttribute("alert", alert.getName()); } - if (alert.getRisk() != null) alertElement.setAttribute("risk", alert.getRisk().name()); - if (alert.getUrl() != null) + + if (alert.getRisk() != null) { + alertElement.setAttribute("risk", alert.getRisk().name()); + } + + if (alert.getConfidence() != null) { alertElement.setAttribute("confidence", alert.getConfidence().name()); - if (alert.getUrl() != null) alertElement.setAttribute("url", alert.getUrl()); - if (alert.getParam() != null) alertElement.setAttribute("param", alert.getParam()); - if (alert.getOther() != null) alertElement.setAttribute("other", alert.getOther()); - if (alert.getAttack() != null) alertElement.setAttribute("attack", alert.getAttack()); - if (alert.getDescription() != null) + } + + if (alert.getUrl() != null) { + alertElement.setAttribute("url", alert.getUrl()); + } + + if (alert.getParam() != null) { + alertElement.setAttribute("param", alert.getParam()); + } + + if (alert.getOther() != null) { + alertElement.setAttribute("other", alert.getOther()); + } + + if (alert.getAttack() != null) { + alertElement.setAttribute("attack", alert.getAttack()); + } + + if (alert.getDescription() != null) { alertElement.setAttribute("description", alert.getDescription()); - if (alert.getSolution() != null) alertElement.setAttribute("solution", alert.getSolution()); - if (alert.getReference() != null) + } + + if (alert.getSolution() != null) { + alertElement.setAttribute("solution", alert.getSolution()); + } + + if (alert.getReference() != null) { alertElement.setAttribute("reference", alert.getReference()); - alertsFound.addContent(alertElement); + } + + alertsParent.appendChild(alertElement); } + /** + * Reads alerts of a given type from the file. + * + * @param file The XML file previously written by {@link #saveAlertsToFile}. + * @param alertType The wrapper element name under <alerts>: + *
    + *
  • "alertsFound" + *
  • "alertsNotFound" + *
  • "ignoredAlertsFound" + *
+ * + * @return list of {@link Alert}s found inside the matching wrapper(s). + */ public static List getAlertsFromFile(File file, String alertType) - throws JDOMException, IOException { + throws IOException { + List alerts = new ArrayList<>(); - SAXBuilder parser = new SAXBuilder(); - Document alertsDoc = parser.build(file); - @SuppressWarnings("unchecked") - List alertElements = alertsDoc.getRootElement().getChildren(alertType); - for (Element element : alertElements) { - String name = element.getAttributeValue("name"); - if (name == null) { - // TODO Remove once alert attribute is no longer supported. - name = element.getAttributeValue("alert"); + + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document alertsDoc = builder.parse(file); + + Element root = alertsDoc.getDocumentElement(); + + NodeList rootChildren = root.getChildNodes(); + for (int i = 0; i < rootChildren.getLength(); i++) { + Node wrapperNode = rootChildren.item(i); + if (wrapperNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element wrapperElem = (Element) wrapperNode; + if (!alertType.equals(wrapperElem.getTagName())) { + continue; + } + + NodeList childNodes = wrapperElem.getChildNodes(); + for (int j = 0; j < childNodes.getLength(); j++) { + Node node = childNodes.item(j); + if (node.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + Element element = (Element) node; + if (!"alert".equals(element.getTagName())) { + continue; + } + + String name = element.getAttribute("name"); + if (name.isEmpty()) { + // TODO Remove once alert attribute is no longer supported. + name = element.getAttribute("alert"); + } + + Alert alert = + new Alert( + emptyToNull(element.getAttribute("url")), + emptyToNull(element.getAttribute("risk")), + emptyToNull(element.getAttribute("confidence")), + emptyToNull(element.getAttribute("param")), + emptyToNull(element.getAttribute("other")), + name); + + alerts.add(alert); + } } - Alert alert = - new Alert( - name, - element.getAttributeValue("url"), - element.getAttributeValue("risk"), - element.getAttributeValue("confidence"), - element.getAttributeValue("param"), - element.getAttributeValue("other")); - alerts.add(alert); + + } catch (ParserConfigurationException | SAXException e) { + throw new RuntimeException("Failed to read alerts from file: " + file, e); } + return alerts; } + + private static String emptyToNull(String value) { + return (value == null || value.isEmpty()) ? null : value; + } } diff --git a/subprojects/zap-clientapi/zap-clientapi.gradle b/subprojects/zap-clientapi/zap-clientapi.gradle index 8fa565e..9ce319b 100644 --- a/subprojects/zap-clientapi/zap-clientapi.gradle +++ b/subprojects/zap-clientapi/zap-clientapi.gradle @@ -9,9 +9,6 @@ sourceSets { examples } assemble.dependsOn examplesClasses dependencies { - // XXX Change to implementation (it's not exposed in public API) when bumping major version. - api 'org.jdom:jdom:1.1.3' - examplesImplementation sourceSets.main.output } From 3a6d03f03b5aa787645b343e575b1040d4bba011 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 22 Dec 2025 17:33:28 +0300 Subject: [PATCH 2/2] Removed jdom dependency. Signed-off-by: Ahmad Al-Sanie Ahmad.alsanie@hotmail.com Signed-off-by: aalsanie --- .../zaproxy/clientapi/core/AlertsFile.java | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/subprojects/zap-clientapi/src/main/java/org/zaproxy/clientapi/core/AlertsFile.java b/subprojects/zap-clientapi/src/main/java/org/zaproxy/clientapi/core/AlertsFile.java index 5299864..dd12146 100644 --- a/subprojects/zap-clientapi/src/main/java/org/zaproxy/clientapi/core/AlertsFile.java +++ b/subprojects/zap-clientapi/src/main/java/org/zaproxy/clientapi/core/AlertsFile.java @@ -43,21 +43,23 @@ public class AlertsFile { /** - * Writes the alerts into an XML file with the following structure: + * Writes alerts to an XML file. The file contains a root element with the following + * wrapper elements: * - *
-     * <alerts>
-     *   <alertsFound alertsFound="N">
-     *     <alert .../>
-     *   </alertsFound>
-     *   <alertsNotFound alertsNotFound="M">
-     *     <alert .../>
-     *   </alertsNotFound>
-     *   <ignoredAlertsFound ignoredAlertsFound="K">
-     *     <alert .../>
-     *   </ignoredAlertsFound>
-     * </alerts>
-     * 
+ *
    + *
  • "alertsFound" – alerts that were reported as found + *
  • "alertsNotFound" – alerts that were required but not found + *
  • "ignoredAlertsFound" – alerts that were found but ignored + *
+ * + * Each wrapper element includes an attribute indicating the number of alerts it contains and + * one or more {@link Alert} elements. + * + * @param requireAlerts Alerts that were required but not found. + * @param reportAlerts Alerts that were found. + * @param ignoredAlerts Alerts that were found but ignored. + * @param outputFile The XML file to write the alerts to. + * @throws IOException If an I/O error occurs while writing the file. */ public static void saveAlertsToFile( List requireAlerts, @@ -200,8 +202,7 @@ private static void createAlertXMLElements(Document doc, Element alertsParent, A * * @return list of {@link Alert}s found inside the matching wrapper(s). */ - public static List getAlertsFromFile(File file, String alertType) - throws IOException { + public static List getAlertsFromFile(File file, String alertType) throws IOException { List alerts = new ArrayList<>(); @@ -210,9 +211,7 @@ public static List getAlertsFromFile(File file, String alertType) factory.setNamespaceAware(false); DocumentBuilder builder = factory.newDocumentBuilder(); Document alertsDoc = builder.parse(file); - Element root = alertsDoc.getDocumentElement(); - NodeList rootChildren = root.getChildNodes(); for (int i = 0; i < rootChildren.getLength(); i++) { Node wrapperNode = rootChildren.item(i);