From 8752c721ff08baf50553a2b87bf3e1fa17df1ef9 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Thu, 17 Jul 2014 19:01:55 +0200 Subject: [PATCH] Preventing serialization of incorrect XML characters (MID-1944) --- .../web/component/message/OpResult.java | 9 +- .../midpoint/prism/parser/DomSerializer.java | 8 +- .../midpoint/prism/TestXmlSerialization.java | 84 +++++++++++++++++++ infra/prism/testng.xml | 1 + .../com/evolveum/midpoint/util/DOMUtil.java | 49 ++++++++++- 5 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 infra/prism/src/test/java/com/evolveum/midpoint/prism/TestXmlSerialization.java diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/OpResult.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/OpResult.java index cfbff16189f..5ded9f3a763 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/OpResult.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/OpResult.java @@ -24,6 +24,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectFactory; import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType; +import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.Validate; import java.io.PrintWriter; @@ -34,8 +35,6 @@ import java.util.List; import java.util.Map; -import javax.xml.bind.JAXBException; - /** * @author lazyman */ @@ -104,8 +103,10 @@ public OpResult(OperationResult result) { OperationResultType resultType = result.createOperationResultType(); ObjectFactory of = new ObjectFactory(); xml = getPrismContext().serializeAtomicValue(of.createOperationResult(resultType), PrismContext.LANG_XML); - } catch (SchemaException ex) { - error("Can't create xml: " + ex); + } catch (SchemaException|RuntimeException ex) { + String m = "Can't create xml: " + ex; + error(m); + xml = "" + StringEscapeUtils.escapeXml(m) + ""; } } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/parser/DomSerializer.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/parser/DomSerializer.java index 2847890e132..09058d9cd3b 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/parser/DomSerializer.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/parser/DomSerializer.java @@ -194,7 +194,7 @@ private void serializePrimitiveElementOrAttribute(PrimitiveXNode xprim, Eleme String stringValue = xprim.getStringValue(); if (stringValue != null) { if (asAttribute) { - parentElement.setAttribute(elementOrAttributeName.getLocalPart(), stringValue); + DOMUtil.setAttributeValue(parentElement, elementOrAttributeName.getLocalPart(), stringValue); } else { Element element; try { @@ -203,7 +203,7 @@ private void serializePrimitiveElementOrAttribute(PrimitiveXNode xprim, Eleme throw new DOMException(e.code, e.getMessage() + "; creating element "+elementOrAttributeName+" in element "+DOMUtil.getQName(parentElement)); } parentElement.appendChild(element); - element.setTextContent(stringValue); + DOMUtil.setElementTextContent(element, stringValue); } } return; @@ -259,9 +259,9 @@ private void serializePrimitiveElementOrAttribute(PrimitiveXNode xprim, Eleme String value = xprim.getGuessedFormattedValue(); if (asAttribute) { - parentElement.setAttribute(elementOrAttributeName.getLocalPart(), value); + DOMUtil.setAttributeValue(parentElement, elementOrAttributeName.getLocalPart(), value); } else { - element.setTextContent(value); + DOMUtil.setElementTextContent(element, value); } } diff --git a/infra/prism/src/test/java/com/evolveum/midpoint/prism/TestXmlSerialization.java b/infra/prism/src/test/java/com/evolveum/midpoint/prism/TestXmlSerialization.java new file mode 100644 index 00000000000..84a2f4f4b09 --- /dev/null +++ b/infra/prism/src/test/java/com/evolveum/midpoint/prism/TestXmlSerialization.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2010-2014 Evolveum + * + * 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 com.evolveum.midpoint.prism; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismInternalTestUtil; +import com.evolveum.midpoint.prism.crypto.Protector; +import com.evolveum.midpoint.prism.crypto.TestProtector; +import com.evolveum.midpoint.prism.parser.util.XNodeProcessorUtil; +import com.evolveum.midpoint.prism.util.PrismTestUtil; +import com.evolveum.midpoint.prism.xnode.MapXNode; +import com.evolveum.midpoint.prism.xnode.PrimitiveXNode; +import com.evolveum.midpoint.util.PrettyPrinter; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import org.apache.xml.security.encryption.XMLCipher; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.Test; +import org.xml.sax.SAXException; + +import javax.xml.namespace.QName; +import java.io.IOException; + +import static com.evolveum.midpoint.prism.PrismInternalTestUtil.DEFAULT_NAMESPACE_PREFIX; +import static com.evolveum.midpoint.prism.PrismInternalTestUtil.displayTestTitle; +import static org.testng.Assert.assertTrue; +import static org.testng.AssertJUnit.assertEquals; + +/** + * @author mederly + * + */ +public class TestXmlSerialization { + + @BeforeSuite + public void setupDebug() throws SchemaException, SAXException, IOException { + PrettyPrinter.setDefaultNamespacePrefix(DEFAULT_NAMESPACE_PREFIX); + PrismTestUtil.resetPrismContext(new PrismInternalTestUtil()); + } + + @Test + public void testHandlingInvalidChars() throws Exception { + final String TEST_NAME = "testHandlingInvalidChars"; + displayTestTitle(TEST_NAME); + + // GIVEN + + PrismContext prismContext = PrismTestUtil.getPrismContext(); + + // WHEN + + PrimitiveXNode valOkNode = new PrimitiveXNode<>("abcdef"); + PrimitiveXNode valWrongNode = new PrimitiveXNode<>("abc\1def"); + + // THEN + + String ok = prismContext.getParserDom().serializeToString(valOkNode, new QName("ok")); + System.out.println("correct value serialized to: " + ok); + assertEquals("Wrong serialization", "abcdef", ok.trim()); // todo make this less brittle with regards to serialization style + + try { + String wrong = prismContext.getParserDom().serializeToString(valWrongNode, new QName("wrong")); + System.out.println("wrong value serialized to: " + wrong); + assert false : "Wrong value serialization had to fail but it didn't!"; + } catch (RuntimeException e) { + System.out.println("wrong value was not serialized (as expected): " + e); + assertTrue(e.getMessage().contains("Invalid character"), "Didn't get expected error message"); + } + } +} diff --git a/infra/prism/testng.xml b/infra/prism/testng.xml index eb6814ab207..b03ac0698ad 100644 --- a/infra/prism/testng.xml +++ b/infra/prism/testng.xml @@ -33,6 +33,7 @@ + diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/DOMUtil.java b/infra/util/src/main/java/com/evolveum/midpoint/util/DOMUtil.java index c8d2491c171..503339318d5 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/DOMUtil.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/DOMUtil.java @@ -25,7 +25,6 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; -import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -33,7 +32,6 @@ import java.util.Map.Entry; import java.util.Random; -import javax.xml.XMLConstants; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -46,6 +44,7 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import com.sun.org.apache.xml.internal.utils.XMLChar; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; @@ -529,6 +528,7 @@ private static void setQNameAttribute(Element element, Attr attr, QName attribut attrValue = valuePrefix + ":" + attributeValue.getLocalPart(); } NamedNodeMap attributes = element.getAttributes(); + checkValidXmlChars(attrValue); attr.setValue(attrValue); attributes.setNamedItem(attr); } @@ -543,7 +543,7 @@ public static void setQNameValue(Element element, QName elementValue) { } else { stringValue = valuePrefix + ":" + elementValue.getLocalPart(); } - element.setTextContent(stringValue); + setElementTextContent(element, stringValue); } public static String lookupOrCreateNamespaceDeclaration(Element element, String namespaceUri, @@ -659,6 +659,7 @@ public static void setNamespaceDeclaration(Element element, String prefix, Strin attr = doc .createAttributeNS(W3C_XML_SCHEMA_XMLNS_URI, W3C_XML_SCHEMA_XMLNS_PREFIX + ":" + prefix); } + checkValidXmlChars(namespaceUri); attr.setValue(namespaceUri); attributes.setNamedItem(attr); } @@ -1218,4 +1219,46 @@ public static boolean isEmpty(Attr attr) { return StringUtils.isEmpty(attr.getValue()); } + public static void setAttributeValue(Element element, String name, String value) { + checkValidXmlChars(value); + element.setAttribute(name, value); + } + + public static void setElementTextContent(Element element, String value) { + checkValidXmlChars(value); + element.setTextContent(value); + } + + public static void checkValidXmlChars(String stringValue) { + if (stringValue == null) { + return; + } + for (int i = 0; i < stringValue.length(); i++) { + if (!XMLChar.isValid(stringValue.charAt(i))) { + throw new IllegalStateException("Invalid character with regards to XML (code " + ((int) stringValue.charAt(i)) + ") in '" + makeSafelyPrintable(stringValue, 200) + "'"); + } + } + } + + // todo move to some Util class + private static String makeSafelyPrintable(String text, int maxSize) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (!XMLChar.isValid(c)) { + sb.append('.'); + } else if (Character.isWhitespace(c)) { + sb.append(' '); + } else { + sb.append(c); + } + if (i == maxSize) { + sb.append("..."); + break; + } + } + return sb.toString(); + } + + }