Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions addOns/openapi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ All notable changes to this add-on will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased
### Added
- Support for generating XML request bodies for OpenAPI definitions and associated unit/integration tests (BodyGeneratorXmlUnitTest, OpenApiIntegrationXmlTest).
- Test resource for XML integration: `v3/openapi_xml_integration.yaml`.

### Changed
- Wire XML body generation into the request model conversion and body generator.
- Update help content to indicate XML generation is available (may fail for complex or invalid schemas).
- Dependency updates.

## [46] - 2025-09-10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import io.swagger.v3.oas.models.parameters.RequestBody;
import java.util.List;
import java.util.Map;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.network.HttpHeaderField;
import org.zaproxy.zap.extension.openapi.generators.Generators;
import org.zaproxy.zap.extension.openapi.generators.HeadersGenerator;
Expand Down Expand Up @@ -85,12 +84,34 @@ private String generateBody() {
return generators.getBodyGenerator().generateMultiPart(schema, encoding);
}

if (content.containsKey(CONTENT_APPLICATION_XML)) {
generators.addErrorMessage(
Constant.messages.getString(
"openapi.unsupportedcontent",
operation.getOperationId(),
CONTENT_APPLICATION_XML));
// handle XML media types (application/xml, text/xml, application/*+xml)
if (content.containsKey(CONTENT_APPLICATION_XML)
|| content.containsKey("text/xml")
|| content.keySet().stream().anyMatch(k -> k != null && k.contains("+xml"))) {
// prefer exact application/xml entry if present
io.swagger.v3.oas.models.media.MediaType mediaType = null;
if (content.containsKey(CONTENT_APPLICATION_XML)) {
mediaType = content.get(CONTENT_APPLICATION_XML);
} else if (content.containsKey("text/xml")) {
mediaType = content.get("text/xml");
} else {
// pick the first +xml media type
String key =
content.keySet().stream()
.filter(k -> k != null && k.contains("+xml"))
.findFirst()
.orElse(null);
if (key != null) {
mediaType = content.get(key);
}
}
if (mediaType != null) {
String xml = generators.getBodyGenerator().generateXml(mediaType);
if (xml == null) {
return "";
}
return xml;
}
return "";
}

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<H1>OpenAPI Support</H1>
This add-on allows you to spider and import OpenAPI (Swagger) definitions, versions 1.2, 2.0, and 3.0.
<br>
<strong>Note:</strong> Generation of XML content is currently not supported.
<strong>Note:</strong> The add-on can generate XML request bodies for XML media types (for example, <code>application/xml</code>, <code>text/xml</code>, and <code>application/*+xml</code>). If an explicit example is provided in the OpenAPI media type that example will be used; otherwise the add-on will attempt to generate a best-effort XML example from the schema. The generator attempts to honour XML-specific metadata (such as <code>xml.name</code>, <code>xml.namespace</code>, <code>xml.prefix</code>, <code>xml.attribute</code>, and <code>xml.wrapped</code>). Generation can fail for complex or invalid schemas. If generation fails an error is logged and surfaced, and an empty body may be returned — you can also supply a hand-crafted example in the OpenAPI spec or edit the request body manually.
<br><br>
The add-on will automatically detect any OpenAPI definitions and spider them as long as they are in scope.
<br><br>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.zap.extension.openapi.automation;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.ParseOptions;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.parosproxy.paros.Constant;
import org.zaproxy.zap.extension.openapi.ExtensionOpenApi;
import org.zaproxy.zap.extension.openapi.converter.swagger.OperationModel;
import org.zaproxy.zap.extension.openapi.converter.swagger.RequestModelConverter;
import org.zaproxy.zap.extension.openapi.generators.Generators;
import org.zaproxy.zap.testutils.TestUtils;

class OpenApiIntegrationXmlTest extends TestUtils {

@BeforeEach
void setUp() {
mockMessages(new ExtensionOpenApi());
Constant.messages = null; // leave default initialized by TestUtils/mockMessages
}

@Test
void shouldGenerateXmlRequestBodiesAndNoUnsupportedMessage() throws Exception {
String defn =
IOUtils.toString(
this.getClass()
.getResourceAsStream(
"/org/zaproxy/zap/extension/openapi/v3/openapi_xml_integration.yaml"),
StandardCharsets.UTF_8);

ParseOptions options = new ParseOptions();
options.setResolveFully(true);
OpenAPI openAPI =
new OpenAPIV3Parser().readContents(defn, new ArrayList<>(), options).getOpenAPI();

Generators generators = new Generators(null);
OperationModel operationModel =
new OperationModel("/xml", openAPI.getPaths().get("/xml").getPost(), null);

RequestModelConverter converter = new RequestModelConverter();
String body = converter.convert(operationModel, generators).getBody();

// Body should be non-empty and should look like XML
org.junit.jupiter.api.Assertions.assertNotNull(body);
org.junit.jupiter.api.Assertions.assertFalse(body.isEmpty());
// Quick sanity parse
javax.xml.parsers.DocumentBuilderFactory dbf =
javax.xml.parsers.DocumentBuilderFactory.newInstance();
javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
db.parse(new java.io.ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)));

// There should be no unsupported-content error message for application/xml
assertThat(
generators.getErrorMessages().stream()
.filter(
s ->
s.contains(
"the content type application/xml is not supported"))
.toList(),
empty());

// The overall error messages list may be empty; we've already asserted the
// specific
// unsupported-content message is not present above.
}
}
Loading