From 1d84b3dc2ba0ffbf287579d0a0ed7524ce590f7f Mon Sep 17 00:00:00 2001 From: TheSnoozer Date: Thu, 4 May 2023 20:22:18 +0200 Subject: [PATCH] https://github.com/git-commit-id/git-commit-id-plugin-core/issues/18: add support for xml --- .../core/CommitIdPropertiesOutputFormat.java | 4 + .../core/PropertiesFileGenerator.java | 48 +++++--- .../pl/project13/core/util/XmlManager.java | 113 ++++++++++++++++++ .../GitCommitIdPluginIntegrationTest.java | 31 +++++ 4 files changed, 181 insertions(+), 15 deletions(-) create mode 100644 src/main/java/pl/project13/core/util/XmlManager.java diff --git a/src/main/java/pl/project13/core/CommitIdPropertiesOutputFormat.java b/src/main/java/pl/project13/core/CommitIdPropertiesOutputFormat.java index 972fe53..b0309b9 100644 --- a/src/main/java/pl/project13/core/CommitIdPropertiesOutputFormat.java +++ b/src/main/java/pl/project13/core/CommitIdPropertiesOutputFormat.java @@ -30,4 +30,8 @@ public enum CommitIdPropertiesOutputFormat { * Indicator to generate a json file. */ JSON, + /** + * Indicator to generate a xml file. + */ + XML, } diff --git a/src/main/java/pl/project13/core/PropertiesFileGenerator.java b/src/main/java/pl/project13/core/PropertiesFileGenerator.java index b1d9d7a..33564d0 100644 --- a/src/main/java/pl/project13/core/PropertiesFileGenerator.java +++ b/src/main/java/pl/project13/core/PropertiesFileGenerator.java @@ -22,6 +22,7 @@ import pl.project13.core.util.BuildFileChangeListener; import pl.project13.core.util.JsonManager; import pl.project13.core.util.PropertyManager; +import pl.project13.core.util.XmlManager; import javax.annotation.Nonnull; import java.io.*; @@ -56,20 +57,27 @@ public void maybeGeneratePropertiesFile( ) throws GitCommitIdExecutionException { try { final File gitPropsFile = craftPropertiesOutputFile(projectDir, propsFile); - final boolean isJsonFormat = CommitIdPropertiesOutputFormat.JSON.equals(propertiesOutputFormat); - boolean shouldGenerate = true; if (gitPropsFile.exists()) { final Properties persistedProperties; try { - if (isJsonFormat) { - log.info(String.format("Reading existing json file [%s] (for module %s)...", gitPropsFile.getAbsolutePath(), projectName)); - persistedProperties = JsonManager.readJsonProperties(gitPropsFile, sourceCharset); - } else { - log.info(String.format("Reading existing properties file [%s] (for module %s)...", gitPropsFile.getAbsolutePath(), projectName)); - persistedProperties = PropertyManager.readProperties(gitPropsFile); + switch (propertiesOutputFormat) { + case JSON: + log.info(String.format("Reading existing json file [%s] (for module %s)...", gitPropsFile.getAbsolutePath(), projectName)); + persistedProperties = JsonManager.readJsonProperties(gitPropsFile, sourceCharset); + break; + case PROPERTIES: + log.info(String.format("Reading existing properties file [%s] (for module %s)...", gitPropsFile.getAbsolutePath(), projectName)); + persistedProperties = PropertyManager.readProperties(gitPropsFile); + break; + case XML: + log.info(String.format("Reading existing xml file [%s] (for module %s)...", gitPropsFile.getAbsolutePath(), projectName)); + persistedProperties = XmlManager.readXmlProperties(gitPropsFile, sourceCharset); + break; + default: + throw new GitCommitIdExecutionException("Not implemented:" + propertiesOutputFormat); } final Properties propertiesCopy = (Properties) localProperties.clone(); @@ -92,13 +100,23 @@ public void maybeGeneratePropertiesFile( try (OutputStream outputStream = new FileOutputStream(gitPropsFile)) { OrderedProperties sortedLocalProperties = PropertiesFileGenerator.createOrderedProperties(); localProperties.forEach((key, value) -> sortedLocalProperties.setProperty((String) key, (String) value)); - if (isJsonFormat) { - log.info(String.format("Writing json file to [%s] (for module %s)...", gitPropsFile.getAbsolutePath(), projectName)); - JsonManager.dumpJson(outputStream, sortedLocalProperties, sourceCharset); - } else { - log.info(String.format("Writing properties file to [%s] (for module %s)...", gitPropsFile.getAbsolutePath(), projectName)); - // using outputStream directly instead of outputWriter this way the UTF-8 characters appears in unicode escaped form - PropertyManager.dumpProperties(outputStream, sortedLocalProperties, escapeUnicode); + switch (propertiesOutputFormat) { + case JSON: + log.info(String.format("Writing json file to [%s] (for module %s)...", gitPropsFile.getAbsolutePath(), projectName)); + JsonManager.dumpJson(outputStream, sortedLocalProperties, sourceCharset); + break; + case PROPERTIES: + log.info(String.format("Writing properties file to [%s] (for module %s)...", gitPropsFile.getAbsolutePath(), projectName)); + // using outputStream directly instead of outputWriter this way the UTF-8 characters appears in unicode escaped form + PropertyManager.dumpProperties(outputStream, sortedLocalProperties, escapeUnicode); + break; + case XML: + log.info(String.format("Writing xml file to [%s] (for module %s)...", gitPropsFile.getAbsolutePath(), projectName)); + // using outputStream directly instead of outputWriter this way the UTF-8 characters appears in unicode escaped form + XmlManager.dumpXml(outputStream, sortedLocalProperties, sourceCharset); + break; + default: + throw new GitCommitIdExecutionException("Not implemented:" + propertiesOutputFormat); } } catch (final IOException ex) { throw new RuntimeException("Cannot create custom git properties file: " + gitPropsFile, ex); diff --git a/src/main/java/pl/project13/core/util/XmlManager.java b/src/main/java/pl/project13/core/util/XmlManager.java new file mode 100644 index 0000000..5a8aad0 --- /dev/null +++ b/src/main/java/pl/project13/core/util/XmlManager.java @@ -0,0 +1,113 @@ +/* + * This file is part of git-commit-id-plugin-core by Konrad 'ktoso' Malawski + * + * git-commit-id-plugin-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * git-commit-id-plugin-core 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with git-commit-id-plugin-core. If not, see . + */ + +package pl.project13.core.util; + +import nu.studer.java.util.OrderedProperties; +import pl.project13.core.CannotReadFileException; + +import javax.annotation.Nonnull; +import javax.xml.XMLConstants; +import javax.xml.stream.*; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Properties; + +public class XmlManager { + public static void dumpXml(OutputStream outputStream, OrderedProperties sortedLocalProperties, Charset sourceCharset) throws IOException { + /* + TODO get rid of custom indents and use + https://ewernli.com/2009/06/18/stax-pretty-printer/ + https://stackoverflow.com/a/38371920 + */ + XMLOutputFactory fac = XMLOutputFactory.newInstance(); + + try (Writer outputWriter = new OutputStreamWriter(outputStream, sourceCharset)) { + XMLStreamWriter writer = fac.createXMLStreamWriter(outputWriter); + // + writer.writeStartDocument(StandardCharsets.UTF_8.toString(), "1.0"); + + writer.writeStartElement("gitCommitIdPlugin"); + writer.writeCharacters("\n"); // indents + + for (Map.Entry e : sortedLocalProperties.entrySet()) { + writer.writeCharacters(" "); // indents + // + writer.writeEmptyElement("property"); + writer.writeAttribute("key", e.getKey().toString()); + writer.writeAttribute("value", e.getValue().toString()); + writer.writeCharacters("\n"); // indents + } + writer.writeCharacters("\n"); // indents + writer.writeEndElement(); // + + writer.writeEndDocument(); + writer.flush(); + writer.close(); + } catch (XMLStreamException e) { + throw new RuntimeException(e); + } + + } + + public static Properties readXmlProperties(@Nonnull File xmlFile, Charset sourceCharset) throws CannotReadFileException { + Properties retVal = new Properties(); + + try (FileInputStream fis = new FileInputStream(xmlFile)) { + try (InputStreamReader reader = new InputStreamReader(fis, sourceCharset)) { + XMLInputFactory factory = XMLInputFactory.newInstance(); + + // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#Java + // factory.setProperty("http://apache.org/xml/features/disallow-doctype-decl", true); // not supported :/ + // https://docs.oracle.com/en/java/javase/11/security/java-api-xml-processing-jaxp-security-guide.html#JSSEC-GUID-88B04BE2-35EF-4F61-B4FA-57A0E9102342 + // Feature for Secure Processing (FSP) + // factory.setProperty(XMLConstants.FEATURE_SECURE_PROCESSING, true); // not supported :/ + // https://rules.sonarsource.com/java/RSPEC-2755 + // to be compliant, completely disable DOCTYPE declaration: + factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); + // or completely disable external entities declarations: + factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); + // or prohibit the use of all protocols by external entities: + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + + // Other things we don't need + factory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE); + factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE); + factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE); + + XMLStreamReader xmlReader = factory.createXMLStreamReader(reader); + while (xmlReader.hasNext()) { + if (xmlReader.next() == XMLStreamConstants.START_ELEMENT) { + if (xmlReader.getLocalName().equals("property")) { + String key = xmlReader.getAttributeValue(null, "key"); + String value = xmlReader.getAttributeValue(null, "value"); + retVal.setProperty(key, value); + } + } + } + } catch (XMLStreamException ex) { + throw new CannotReadFileException(ex); + } + } catch (IOException e) { + throw new CannotReadFileException(e); + } + return retVal; + } +} diff --git a/src/test/java/pl/project13/core/GitCommitIdPluginIntegrationTest.java b/src/test/java/pl/project13/core/GitCommitIdPluginIntegrationTest.java index bd737fb..9375cc1 100644 --- a/src/test/java/pl/project13/core/GitCommitIdPluginIntegrationTest.java +++ b/src/test/java/pl/project13/core/GitCommitIdPluginIntegrationTest.java @@ -26,6 +26,7 @@ import org.junit.runner.RunWith; import pl.project13.core.git.GitDescribeConfig; import pl.project13.core.util.JsonManager; +import pl.project13.core.util.XmlManager; import javax.annotation.Nonnull; import java.io.File; @@ -460,8 +461,38 @@ public void shouldGenerateCustomPropertiesFileJson(boolean useNativeGit) throws assertThat(targetFilePath).exists(); Properties p = JsonManager.readJsonProperties(targetFilePath, StandardCharsets.UTF_8); assertThat(p.size() > 10); + Assert.assertEquals(p, properties); } + @Test + @Parameters(method = "useNativeGit") + public void shouldGenerateCustomPropertiesFileXml(boolean useNativeGit) throws Exception { + // given + File dotGitDirectory = createTmpDotGitDirectory(AvailableGitTestRepo.WITH_ONE_COMMIT_WITH_SPECIAL_CHARACTERS); + + File targetFilePath = sandbox.resolve("custom-git.xml").toFile(); + targetFilePath.delete(); + + GitCommitIdPlugin.Callback cb = + new GitCommitIdTestCallback() + .setDotGitDirectory(dotGitDirectory) + .setUseNativeGit(useNativeGit) + .setShouldGenerateGitPropertiesFile(true) + .setGenerateGitPropertiesFilename(targetFilePath) + .setPropertiesOutputFormat(CommitIdPropertiesOutputFormat.XML) + .build(); + Properties properties = new Properties(); + + // when + GitCommitIdPlugin.runPlugin(cb, properties); + // then + assertThat(targetFilePath).exists(); + Properties p = XmlManager.readXmlProperties(targetFilePath, StandardCharsets.UTF_8); + assertThat(p.size() > 10); + Assert.assertEquals(p, properties); + } + + @Test @Parameters(method = "useNativeGit") public void shouldGenerateDescribeWithTagOnlyWhenForceLongFormatIsFalse(boolean useNativeGit) throws Exception {