/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * 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.pentaho.di.core.xml; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.math.BigDecimal; import java.util.Calendar; import java.util.GregorianCalendar; import org.junit.Test; import org.pentaho.di.core.Const; import org.pentaho.di.core.exception.KettleXMLException; import org.xml.sax.SAXParseException; import javax.xml.parsers.DocumentBuilder; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertEquals; /** */ public class XMLHandlerUnitTest { /** * @see <a href="https://en.wikipedia.org/wiki/Billion_laughs" /> */ private static final String MALICIOUS_XML = "<?xml version=\"1.0\"?>\n" + "<!DOCTYPE lolz [\n" + " <!ENTITY lol \"lol\">\n" + " <!ELEMENT lolz (#PCDATA)>\n" + " <!ENTITY lol1 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">\n" + " <!ENTITY lol2 \"&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;\">\n" + " <!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">\n" + " <!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">\n" + " <!ENTITY lol5 \"&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;\">\n" + " <!ENTITY lol6 \"&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;\">\n" + " <!ENTITY lol7 \"&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;\">\n" + " <!ENTITY lol8 \"&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;\">\n" + " <!ENTITY lol9 \"&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;\">\n" + "]>\n" + "<lolz>&lol9;</lolz>"; private static final String cr = Const.CR; @Test public void openTagWithNotNull() { assertEquals( "<qwerty>", XMLHandler.openTag( "qwerty" ) ); } @Test public void openTagWithNull() { assertEquals( "<null>", XMLHandler.openTag( null ) ); } @Test public void openTagWithExternalBuilder() { StringBuilder builder = new StringBuilder( "qwe" ); XMLHandler.openTag( builder, "rty" ); assertEquals( "qwe<rty>", builder.toString() ); } @Test public void closeTagWithNotNull() { assertEquals( "</qwerty>", XMLHandler.closeTag( "qwerty" ) ); } @Test public void closeTagWithNull() { assertEquals( "</null>", XMLHandler.closeTag( null ) ); } @Test public void closeTagWithExternalBuilder() { StringBuilder builder = new StringBuilder( "qwe" ); XMLHandler.closeTag( builder, "rty" ); assertEquals( "qwe</rty>", builder.toString() ); } @Test public void buildCdataWithNotNull() { assertEquals( "<![CDATA[qwerty]]>", XMLHandler.buildCDATA( "qwerty" ) ); } @Test public void buildCdataWithNull() { assertEquals( "<![CDATA[]]>", XMLHandler.buildCDATA( null ) ); } @Test public void buildCdataWithExternalBuilder() { StringBuilder builder = new StringBuilder( "qwe" ); XMLHandler.buildCDATA( builder, "rty" ); assertEquals( "qwe<![CDATA[rty]]>", builder.toString() ); } @Test public void timestamp2stringTest() { String actual = XMLHandler.timestamp2string( null ); assertNull( actual ); } @Test public void date2stringTest() { String actual = XMLHandler.date2string( null ); assertNull( actual ); } @Test public void addTagValueBigDecimal() { BigDecimal input = new BigDecimal( "1234567890123456789.01" ); assertEquals( "<bigdec>1234567890123456789.01</bigdec>" + cr, XMLHandler.addTagValue( "bigdec", input ) ); assertEquals( "<bigdec>1234567890123456789.01</bigdec>" + cr, XMLHandler.addTagValue( "bigdec", input, true ) ); assertEquals( "<bigdec>1234567890123456789.01</bigdec>", XMLHandler.addTagValue( "bigdec", input, false ) ); } @Test public void addTagValueBoolean() { assertEquals( "<abool>Y</abool>" + cr, XMLHandler.addTagValue( "abool", true ) ); assertEquals( "<abool>Y</abool>" + cr, XMLHandler.addTagValue( "abool", true, true ) ); assertEquals( "<abool>Y</abool>", XMLHandler.addTagValue( "abool", true, false ) ); assertEquals( "<abool>N</abool>" + cr, XMLHandler.addTagValue( "abool", false ) ); assertEquals( "<abool>N</abool>" + cr, XMLHandler.addTagValue( "abool", false, true ) ); assertEquals( "<abool>N</abool>", XMLHandler.addTagValue( "abool", false, false ) ); } @Test public void addTagValueDate() { String result = "2014/12/29 15:59:45.789"; Calendar aDate = new GregorianCalendar(); aDate.set( 2014, ( 12 - 1 ), 29, 15, 59, 45 ); aDate.set( Calendar.MILLISECOND, 789 ); assertEquals( "<adate>" + result + "</adate>" + cr, XMLHandler.addTagValue( "adate", aDate.getTime() ) ); assertEquals( "<adate>" + result + "</adate>" + cr, XMLHandler.addTagValue( "adate", aDate.getTime(), true ) ); assertEquals( "<adate>" + result + "</adate>", XMLHandler.addTagValue( "adate", aDate.getTime(), false ) ); } @Test public void addTagValueLong() { long input = 123; assertEquals( "<along>123</along>" + cr, XMLHandler.addTagValue( "along", input ) ); assertEquals( "<along>123</along>" + cr, XMLHandler.addTagValue( "along", input, true ) ); assertEquals( "<along>123</along>", XMLHandler.addTagValue( "along", input, false ) ); assertEquals( "<along>" + String.valueOf( Long.MAX_VALUE ) + "</along>", XMLHandler.addTagValue( "along", Long.MAX_VALUE, false ) ); assertEquals( "<along>" + String.valueOf( Long.MIN_VALUE ) + "</along>", XMLHandler.addTagValue( "along", Long.MIN_VALUE, false ) ); } @Test public void addTagValueInt() { int input = 456; assertEquals( "<anint>456</anint>" + cr, XMLHandler.addTagValue( "anint", input ) ); assertEquals( "<anint>456</anint>" + cr, XMLHandler.addTagValue( "anint", input, true ) ); assertEquals( "<anint>456</anint>", XMLHandler.addTagValue( "anint", input, false ) ); assertEquals( "<anint>" + String.valueOf( Integer.MAX_VALUE ) + "</anint>", XMLHandler.addTagValue( "anint", Integer.MAX_VALUE, false ) ); assertEquals( "<anint>" + String.valueOf( Integer.MIN_VALUE ) + "</anint>", XMLHandler.addTagValue( "anint", Integer.MIN_VALUE, false ) ); } @Test public void addTagValueDouble() { double input = 123.45; assertEquals( "<adouble>123.45</adouble>" + cr, XMLHandler.addTagValue( "adouble", input ) ); assertEquals( "<adouble>123.45</adouble>" + cr, XMLHandler.addTagValue( "adouble", input, true ) ); assertEquals( "<adouble>123.45</adouble>", XMLHandler.addTagValue( "adouble", input, false ) ); assertEquals( "<adouble>" + String.valueOf( Double.MAX_VALUE ) + "</adouble>", XMLHandler.addTagValue( "adouble", Double.MAX_VALUE, false ) ); assertEquals( "<adouble>" + String.valueOf( Double.MIN_VALUE ) + "</adouble>", XMLHandler.addTagValue( "adouble", Double.MIN_VALUE, false ) ); assertEquals( "<adouble>" + String.valueOf( Double.MIN_NORMAL ) + "</adouble>", XMLHandler.addTagValue( "adouble", Double.MIN_NORMAL, false ) ); } @Test public void addTagValueBinary() throws IOException { byte[] input = "Test Data".getBytes(); String result = "H4sIAAAAAAAAAAtJLS5RcEksSQQAL4PL8QkAAAA="; assertEquals( "<bytedata>" + result + "</bytedata>" + cr, XMLHandler.addTagValue( "bytedata", input ) ); assertEquals( "<bytedata>" + result + "</bytedata>" + cr, XMLHandler.addTagValue( "bytedata", input, true ) ); assertEquals( "<bytedata>" + result + "</bytedata>", XMLHandler.addTagValue( "bytedata", input, false ) ); } @Test public void addTagValueWithSurrogateCharacters() throws Exception { String expected = "<testTag attributeTest=\"test attribute value \uD842\uDFB7\" >a\uD800\uDC01\uD842\uDFB7ﻉDtest \uD802\uDF44<</testTag>"; String tagValueWithSurrogates = "a\uD800\uDC01\uD842\uDFB7ﻉDtest \uD802\uDF44<"; String attributeValueWithSurrogates = "test attribute value \uD842\uDFB7"; String result = XMLHandler.addTagValue( "testTag", tagValueWithSurrogates, false, "attributeTest", attributeValueWithSurrogates ); assertEquals( expected, result ); DocumentBuilder builder = XMLHandler.createDocumentBuilder( false, false ); builder.parse( new ByteArrayInputStream( result.getBytes() ) ); } @Test public void testEscapingXmlBagCharacters() throws Exception { String testString = "[value_start (\"\'<&>) value_end]"; String expectedStrAfterConversion = "<[value_start ("'<&>) value_end] " + "[value_start ("'<&>) value_end]=\"" + "[value_start ("'<&>) value_end]\" >" + "[value_start ("'<&>) value_end]" + "</[value_start ("'<&>) value_end]>"; String result = XMLHandler.addTagValue( testString, testString, false, testString, testString ); assertEquals( expectedStrAfterConversion, result ); } @Test( expected = SAXParseException.class ) public void createdDocumentBuilderThrowsExceptionWhenParsingXmlWithABigAmountOfExternalEntities() throws Exception { DocumentBuilder builder = XMLHandler.createDocumentBuilder( false, false ); builder.parse( new ByteArrayInputStream( MALICIOUS_XML.getBytes() ) ); } @Test( expected = KettleXMLException.class ) public void loadingXmlFromStreamThrowsExceptionWhenParsingXmlWithBigAmountOfExternalEntities() throws Exception { XMLHandler.loadXMLFile( new ByteArrayInputStream( MALICIOUS_XML.getBytes() ), "<def>", false, false ); } @Test( expected = KettleXMLException.class ) public void loadingXmlFromURLThrowsExceptionWhenParsingXmlWithBigAmountOfExternalEntities() throws Exception { File tmpFile = createTmpFile( MALICIOUS_XML ); XMLHandler.loadXMLFile( tmpFile.toURI().toURL() ); } private File createTmpFile( String content ) throws Exception { File tmpFile = File.createTempFile( "XMLHandlerUnitTest", ".xml" ); tmpFile.deleteOnExit(); try ( PrintWriter writer = new PrintWriter( tmpFile ) ) { writer.write( content ); } return tmpFile; } }