[core] Fix PMD's XMLRenderer to escape CDATA

Processing errors might contain inside their details
message a CDATA section. This is output itself as
a CDATA section, but XMLStreamWriter#writeCData doesn't
escape it automatically - it just outputs the string
as is. This results in invalid XML.

Fixes #5059
This commit is contained in:
Andreas Dangel 2024-09-12 09:44:54 +02:00
parent 48c2e325dc
commit 6d1fb3e4cd
No known key found for this signature in database
GPG Key ID: 93450DF2DF9A3FA3
4 changed files with 51 additions and 1 deletions

View File

@ -20,6 +20,8 @@ This is a {{ site.pmd.release_type }} release.
(ApexCRUDViolation, CognitiveComplexity, OperationWithLimitsInLoop)
* [#5163](https://github.com/pmd/pmd/issues/5163): \[apex] Parser error when using toLabel in SOSL query
* [#5182](https://github.com/pmd/pmd/issues/5182): \[apex] Parser error when using GROUPING in a SOQL query
* core
* [#5059](https://github.com/pmd/pmd/issues/5059): \[core] xml output doesn't escape CDATA inside its own CDATA
* java
* [#5190](https://github.com/pmd/pmd/issues/5190): \[java] NPE in type inference

View File

@ -196,7 +196,14 @@ public class XMLRenderer extends AbstractIncrementingRenderer {
xmlWriter.writeAttribute("filename", determineFileName(pe.getFileId()));
xmlWriter.writeAttribute("msg", pe.getMsg());
writeNewLine();
xmlWriter.writeCData(pe.getDetail());
// in case the message contains itself some CDATA sections, they need to be handled
// in order to not produce invalid XML...
String detail = pe.getDetail();
// split "]]>" into "]]" and ">" into two cdata sections
detail = detail.replace("]]>", "]]]]><![CDATA[>");
xmlWriter.writeCData(detail);
writeNewLine();
xmlWriter.writeEndElement();
}

View File

@ -7,6 +7,7 @@ package net.sourceforge.pmd.cpd;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.ByteArrayInputStream;
@ -15,6 +16,7 @@ import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collections;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
@ -316,6 +318,29 @@ class XMLRendererTest {
assertEquals(processingError.getDetail(), textContent);
}
/**
* Note, that CPD's processing error isn't output as a CDATA section at the moment.
* This test just makes sure, the XML is valid, in case {@code <error>} is changed into CDATA.
* Currently, {@code >>]} is automatically escaped into {@code ]]&gt;}.
*
* @see <a href="https://github.com/pmd/pmd/issues/5059">[core] xml output doesn't escape CDATA inside its own CDATA</a>
*/
@Test
void cdataSectionInError() throws Exception {
FileId fileId = FileId.fromPathLikeString("file1.txt");
Report.ProcessingError processingError = new Report.ProcessingError(
new LexException(2, 1, fileId, "test exception", new RuntimeException("Invalid source: '<![CDATA[ ... ]]> ...'")),
fileId);
CPDReportRenderer renderer = new XMLRenderer();
StringWriter sw = new StringWriter();
renderer.render(CpdTestUtils.makeReport(Collections.emptyList(), Collections.emptyMap(), Collections.singletonList(processingError)), sw);
String report = sw.toString();
assertReportIsValidSchema(report);
DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
assertDoesNotThrow(() -> documentBuilder.parse(new InputSource(new StringReader(report))));
}
private static void assertReportIsValidSchema(String report) throws SAXException, ParserConfigurationException, IOException {
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(new StreamSource(XMLRenderer.class.getResourceAsStream("/cpd-report_1_0_0.xsd")));

View File

@ -4,6 +4,7 @@
package net.sourceforge.pmd.renderers;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -15,6 +16,7 @@ import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.junit.jupiter.api.Test;
@ -26,6 +28,7 @@ import org.xml.sax.InputSource;
import net.sourceforge.pmd.FooRule;
import net.sourceforge.pmd.PMDVersion;
import net.sourceforge.pmd.internal.util.IOUtil;
import net.sourceforge.pmd.lang.ast.ParseException;
import net.sourceforge.pmd.lang.document.FileId;
import net.sourceforge.pmd.lang.document.FileLocation;
import net.sourceforge.pmd.lang.document.TextRange2d;
@ -161,6 +164,19 @@ class XMLRendererTest extends AbstractRendererTest {
});
}
/**
* @see <a href="https://github.com/pmd/pmd/issues/5059">[core] xml output doesn't escape CDATA inside its own CDATA</a>
*/
@Test
void cdataSectionInError() throws Exception {
ProcessingError processingError = new ProcessingError(new ParseException("Invalid source: '<![CDATA[ ... ]]> ...'"),
FileId.fromPathLikeString("dummy.txt"));
String result = renderReport(getRenderer(), it -> it.onError(processingError), StandardCharsets.UTF_8);
DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
assertDoesNotThrow(() -> documentBuilder.parse(new InputSource(new StringReader(result))));
}
private String renderTempFile(Renderer renderer, Report report, Charset expectedCharset) throws IOException {
File reportFile = folder.resolve("report.out").toFile();