From ee8571824b55fb17af02330d80527518866de4d0 Mon Sep 17 00:00:00 2001 From: Mirko Caserta Date: Thu, 5 Dec 2013 12:58:15 +0100 Subject: [PATCH] CDATA support --- .../handler/codec/xml/XmlFrameDecoder.java | 55 ++++++++++--- .../codec/xml/XmlFrameDecoderTest.java | 44 +++++++--- .../io/netty/handler/codec/xml/sample-05.xml | 81 +++++++++++++++++++ .../io/netty/handler/codec/xml/sample-06.xml | 62 ++++++++++++++ 4 files changed, 217 insertions(+), 25 deletions(-) create mode 100644 codec/src/test/resources/io/netty/handler/codec/xml/sample-05.xml create mode 100644 codec/src/test/resources/io/netty/handler/codec/xml/sample-06.xml diff --git a/codec/src/main/java/io/netty/handler/codec/xml/XmlFrameDecoder.java b/codec/src/main/java/io/netty/handler/codec/xml/XmlFrameDecoder.java index 08e502df70..ae499e30a0 100644 --- a/codec/src/main/java/io/netty/handler/codec/xml/XmlFrameDecoder.java +++ b/codec/src/main/java/io/netty/handler/codec/xml/XmlFrameDecoder.java @@ -82,6 +82,7 @@ public class XmlFrameDecoder extends ByteToMessageDecoder { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { boolean openingBracketFound = false; boolean atLeastOneXmlElementFound = false; + boolean inCDATASection = false; long openBracketsCount = 0; int length = 0; int leadingWhiteSpaceCount = 0; @@ -104,7 +105,7 @@ public class XmlFrameDecoder extends ByteToMessageDecoder { fail(ctx); in.skipBytes(in.readableBytes()); return; - } else if (readByte == '<') { + } else if (!inCDATASection && readByte == '<') { openingBracketFound = true; if (i < bufferLength - 1) { @@ -117,17 +118,21 @@ public class XmlFrameDecoder extends ByteToMessageDecoder { // char after < is a valid xml element start char, // incrementing openBracketsCount openBracketsCount++; - } else if (peekAheadByte == '!' && i < bufferLength - 3 - && in.getByte(i + 2) == '-' - && in.getByte(i + 3) == '-') { - // start found - openBracketsCount++; + } else if (peekAheadByte == '!') { + if (isCommentBlockStart(in, i)) { + // start found + openBracketsCount++; + } else if (isCDATABlockStart(in, i)) { + // start found openBracketsCount++; } } - } else if (readByte == '/') { + } else if (!inCDATASection && readByte == '/') { if (i < bufferLength - 1 && in.getByte(i + 1) == '>') { // found />, decrementing openBracketsCount openBracketsCount--; @@ -138,12 +143,18 @@ public class XmlFrameDecoder extends ByteToMessageDecoder { if (i - 1 > -1) { final byte peekBehindByte = in.getByte(i - 1); - if (peekBehindByte == '?') { - // an tag was closed - openBracketsCount--; - } else if (peekBehindByte == '-' && i - 2 > -1 && in.getByte(i - 2) == '-') { - // a was closed + if (!inCDATASection) { + if (peekBehindByte == '?') { + // an tag was closed + openBracketsCount--; + } else if (peekBehindByte == '-' && i - 2 > -1 && in.getByte(i - 2) == '-') { + // a was closed + openBracketsCount--; + } + } else if (peekBehindByte == ']' && i - 2 > -1 && in.getByte(i - 2) == ']') { + // a block was closed openBracketsCount--; + inCDATASection = false; } } @@ -157,7 +168,7 @@ public class XmlFrameDecoder extends ByteToMessageDecoder { final int readerIndex = in.readerIndex(); if (openBracketsCount == 0 && length > 0) { - if (length >= in.writerIndex()) { + if (length >= bufferLength) { length = in.readableBytes(); } final ByteBuf frame = @@ -201,4 +212,22 @@ public class XmlFrameDecoder extends ByteToMessageDecoder { private static boolean isValidStartCharForXmlElement(final byte b) { return b >= 'a' && b <= 'z' || b >= 'A' && b <= 'Z' || b == ':' || b == '_'; } + + private static boolean isCommentBlockStart(final ByteBuf in, final int i) { + return i < in.writerIndex() - 3 + && in.getByte(i + 2) == '-' + && in.getByte(i + 3) == '-'; + } + + private static boolean isCDATABlockStart(final ByteBuf in, final int i) { + return i < in.writerIndex() - 8 + && in.getByte(i + 2) == '[' + && in.getByte(i + 3) == 'C' + && in.getByte(i + 4) == 'D' + && in.getByte(i + 5) == 'A' + && in.getByte(i + 6) == 'T' + && in.getByte(i + 7) == 'A' + && in.getByte(i + 8) == '['; + } + } diff --git a/codec/src/test/java/io/netty/handler/codec/xml/XmlFrameDecoderTest.java b/codec/src/test/java/io/netty/handler/codec/xml/XmlFrameDecoderTest.java index 3bd9d6cafc..0fff24e825 100644 --- a/codec/src/test/java/io/netty/handler/codec/xml/XmlFrameDecoderTest.java +++ b/codec/src/test/java/io/netty/handler/codec/xml/XmlFrameDecoderTest.java @@ -44,7 +44,10 @@ public class XmlFrameDecoderTest { private final List xmlSamples; public XmlFrameDecoderTest() throws IOException, URISyntaxException { - xmlSamples = Arrays.asList(sample("01"), sample("02"), sample("03"), sample("04")); + xmlSamples = Arrays.asList( + sample("01"), sample("02"), sample("03"), + sample("04"), sample("05"), sample("06") + ); } @Test(expected = IllegalArgumentException.class) @@ -98,19 +101,36 @@ public class XmlFrameDecoderTest { testDecodeWithXml(" \n\r \t\ttrash", "", CorruptedFrameException.class); } + @Test + public void testDecodeWithCDATABlock() { + final String xml = "" + + "" + + ""; + testDecodeWithXml(xml, xml); + } + + @Test + public void testDecodeWithCDATABlockContainingNestedUnbalancedXml() { + //
isn't closed, also should have been + final String xml = "" + + "ACME Inc.]]>" + + ""; + testDecodeWithXml(xml, xml); + } + @Test public void testDecodeWithTwoMessages() { - testDecodeWithXml( - "\n" + - '\n' + - "\n\n", - "", - "\n" + - "\n" + - "" - ); + final String input = "\n\n" + + "\n\n"; + final String frame1 = ""; + final String frame2 = "\n\n"; + testDecodeWithXml(input, frame1, frame2); } @Test diff --git a/codec/src/test/resources/io/netty/handler/codec/xml/sample-05.xml b/codec/src/test/resources/io/netty/handler/codec/xml/sample-05.xml new file mode 100644 index 0000000000..427cbf824d --- /dev/null +++ b/codec/src/test/resources/io/netty/handler/codec/xml/sample-05.xml @@ -0,0 +1,81 @@ + + + + Rocket Launching + + + Configuration + + + Configuring rockets should look very familiar if you're used + to Jakarta Commons-Rocket or Commons-Space. You will first + create a normal + ContextSource + then wrap it in a + RocketContextSource + . + + + ... + + + + + + + + + + + ... + +]]> + + In a real world example you would probably configure the + rocket options and enable connection validation; the above + serves as an example to demonstrate the general idea. + + + + Validation Configuration + + + Adding validation and a few rocket configuration tweaks to + the above example is straight forward. Inject a + RocketContextValidator + and set when validation should occur and the rocket is + ready to go. + + + ... + + + + + + + + + + + + + + + + ... + +]]> + + The above example will test each + RocketContext + before it is passed to the client application and test + RocketContext + s that have been sitting idle in orbit. + + + + \ No newline at end of file diff --git a/codec/src/test/resources/io/netty/handler/codec/xml/sample-06.xml b/codec/src/test/resources/io/netty/handler/codec/xml/sample-06.xml new file mode 100644 index 0000000000..ecf5f8d7a0 --- /dev/null +++ b/codec/src/test/resources/io/netty/handler/codec/xml/sample-06.xml @@ -0,0 +1,62 @@ + + + + Rocket Launching + + + Configuration + + + Configuring rockets should look very familiar if you're used + to Jakarta Commons-Rocket or Commons-Space. You will first + create a normal + ContextSource + then wrap it in a + RocketContextSource + . + + + ... + + + ... +]]> + + In a real world example you would probably configure the + rocket options and enable connection validation; the above + serves as an example to demonstrate the general idea. + + + + Validation Configuration + + + Adding validation and a few rocket configuration tweaks to + the above example is straight forward. Inject a + RocketContextValidator + and set when validation should occur and the rocket is + ready to go. + + + ... + + + ... + + + ... +]]> + + The above example will test each + RocketContext + before it is passed to the client application and test + RocketContext + s that have been sitting idle in orbit. + + + + \ No newline at end of file