diff --git a/addOns/exim/CHANGELOG.md b/addOns/exim/CHANGELOG.md index 2c58e880a5e..32def4d66e4 100644 --- a/addOns/exim/CHANGELOG.md +++ b/addOns/exim/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +### Fixed +- Sites Tree export now includes full raw bodies for all HTTP methods (Issue 8941). ## [0.14.0] - 2025-03-25 ### Changed diff --git a/addOns/exim/src/main/java/org/zaproxy/addon/exim/sites/SitesTreeHandler.java b/addOns/exim/src/main/java/org/zaproxy/addon/exim/sites/SitesTreeHandler.java index ae6a0f56045..211a54bd395 100644 --- a/addOns/exim/src/main/java/org/zaproxy/addon/exim/sites/SitesTreeHandler.java +++ b/addOns/exim/src/main/java/org/zaproxy/addon/exim/sites/SitesTreeHandler.java @@ -53,16 +53,13 @@ import org.parosproxy.paros.model.Model; import org.parosproxy.paros.model.SiteMap; import org.parosproxy.paros.model.SiteNode; -import org.parosproxy.paros.network.HtmlParameter.Type; import org.parosproxy.paros.network.HttpHeader; import org.parosproxy.paros.network.HttpMalformedHeaderException; import org.parosproxy.paros.network.HttpMessage; -import org.parosproxy.paros.network.HttpRequestHeader; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.zaproxy.addon.exim.ExporterResult; import org.zaproxy.addon.exim.ExtensionExim; -import org.zaproxy.zap.model.NameValuePair; import org.zaproxy.zap.utils.Stats; public class SitesTreeHandler { @@ -219,32 +216,19 @@ public void serialize(SiteNode value, JsonGenerator gen, SerializerProvider prov gen.writeNumberField(EximSiteNode.STATUS_CODE_KEY, href.getStatusCode()); } - if (HttpRequestHeader.POST.equals(href.getMethod())) { - try { - HttpMessage msg = href.getHttpMessage(); - String contentType = - msg.getRequestHeader().getHeader(HttpHeader.CONTENT_TYPE); - if (contentType == null - || !contentType.startsWith( - HttpHeader.FORM_MULTIPART_CONTENT_TYPE)) { - List params = - Model.getSingleton().getSession().getParameters(msg, Type.form); - StringBuilder sb = new StringBuilder(); - params.forEach( - nvp -> { - if (sb.length() > 0) { - sb.append('&'); - } - sb.append( - URLEncoder.encode( - nvp.getName(), StandardCharsets.UTF_8)); - sb.append("="); - }); - gen.writeStringField(EximSiteNode.DATA_KEY, sb.toString()); + try { + HttpMessage msg = href.getHttpMessage(); + String contentType = msg.getRequestHeader().getHeader(HttpHeader.CONTENT_TYPE); + if (contentType == null + || !contentType.startsWith(HttpHeader.FORM_MULTIPART_CONTENT_TYPE)) { + String body = msg.getRequestBody().toString(); + if (!body.isEmpty()) { + String encodedBody = URLEncoder.encode(body, StandardCharsets.UTF_8); + gen.writeStringField(EximSiteNode.DATA_KEY, encodedBody); } - } catch (IOException | DatabaseException e) { - LOGGER.error(e.getMessage(), e); } + } catch (IOException | DatabaseException e) { + LOGGER.error(e.getMessage(), e); } } diff --git a/addOns/exim/src/test/java/org/zaproxy/addon/exim/sites/SiteTreeHandlerUnitTest.java b/addOns/exim/src/test/java/org/zaproxy/addon/exim/sites/SiteTreeHandlerUnitTest.java index bd28bab418d..a97c91f3e2f 100644 --- a/addOns/exim/src/test/java/org/zaproxy/addon/exim/sites/SiteTreeHandlerUnitTest.java +++ b/addOns/exim/src/test/java/org/zaproxy/addon/exim/sites/SiteTreeHandlerUnitTest.java @@ -157,7 +157,7 @@ void shouldOutputNodeWithData() throws Exception { + " method: POST\n" + " responseLength: 61\n" + " statusCode: 200\n" - + " data: eee=&ggg=\n"; + + " data: eee%3Dfff%26ggg%3Dhhh\n"; HttpMessage msg = new HttpMessage( "POST https://www.example.com?aa=bb&cc=dd HTTP/1.1\r\n" @@ -188,7 +188,7 @@ void shouldOutputNodeWithDataButNoContentType() throws Exception { + " method: POST\n" + " responseLength: 61\n" + " statusCode: 200\n" - + " data: eee=&ggg=\n"; + + " data: eee%3Dfff%26ggg%3Dhhh\n"; HttpMessage msg = new HttpMessage( "POST https://www.example.com?aa=bb&cc=dd HTTP/1.1\r\n", @@ -222,7 +222,7 @@ void shouldOutputNodes() throws Exception { + " method: POST\n" + " responseLength: 61\n" + " statusCode: 200\n" - + " data: aaa=\n" + + " data: aaa%3Dbbb\n" + " - node: PUT:aaa\n" + " url: https://www.example.com/aaa\n" + " method: PUT\n"; @@ -281,6 +281,83 @@ void shouldOutputNodeWithMultipartFormData() throws Exception { assertThat(result.getCount(), is(3)); } + @Test + void shouldOutputNodeWithXmlBody() throws Exception { + // Given + String xmlBody = + """ + + + value + + """; + HttpMessage msg = + new HttpMessage( + "POST https://www.example.com/ HTTP/1.1\r\n" + + "Content-Type: application/xml\r\n", + xmlBody.getBytes(), + "HTTP/1.1 200 OK\r\n" + "content-length: 20", + "12345678901234567890".getBytes()); + siteMap.addPath(getHref(msg)); + StringWriter sw = new StringWriter(); + ExporterResult result = new ExporterResult(); + + // When + SitesTreeHandler.exportSitesTree(sw, siteMap, result); + + // Then + assertThat( + sw.toString(), containsString(URLEncoder.encode(xmlBody, StandardCharsets.UTF_8))); + assertThat(result.getCount(), is(3)); + } + + @Test + void shouldOutputNodeWithJsonBody() throws Exception { + // Given + String jsonBody = "{\"key\":\"value\",\"array\":[1,2,3]}"; + HttpMessage msg = + new HttpMessage( + "POST https://www.example.com/ HTTP/1.1\r\n" + + "Content-Type: application/json\r\n", + jsonBody.getBytes(), + "HTTP/1.1 200 OK\r\n" + "content-length: 20", + "12345678901234567890".getBytes()); + siteMap.addPath(getHref(msg)); + StringWriter sw = new StringWriter(); + ExporterResult result = new ExporterResult(); + + // When + SitesTreeHandler.exportSitesTree(sw, siteMap, result); + + // Then + assertThat( + sw.toString(), containsString(URLEncoder.encode(jsonBody, StandardCharsets.UTF_8))); + assertThat(result.getCount(), is(3)); + } + + @Test + void shouldOutputNodeWithPutBody() throws Exception { + // Given + String body = "This is a PUT request body"; + HttpMessage msg = + new HttpMessage( + "PUT https://www.example.com/ HTTP/1.1\r\n" + + "Content-Type: text/plain\r\n", + body.getBytes(), + "HTTP/1.1 200 OK\r\n" + "content-length: 20", + "12345678901234567890".getBytes()); + siteMap.addPath(getHref(msg)); + StringWriter sw = new StringWriter(); + ExporterResult result = new ExporterResult(); + + // When + SitesTreeHandler.exportSitesTree(sw, siteMap, result); + + // Then + assertThat(sw.toString(), containsString(URLEncoder.encode(body, StandardCharsets.UTF_8))); + assertThat(result.getCount(), is(3)); + } + @Test void shouldOutputDdnNode() throws Exception { // Given @@ -556,7 +633,7 @@ void shouldHandleSpecialCharactersInParameterNames( sw.toString(), containsString( """ - data: '%s=' + data: '%s%%3Dfff' """ .formatted(persistedParameterName))); }