CDATA support

This commit is contained in:
Mirko Caserta 2013-12-05 12:58:15 +01:00 committed by Trustin Lee
parent 086dbd1ba1
commit ee8571824b
4 changed files with 217 additions and 25 deletions

View File

@ -82,6 +82,7 @@ public class XmlFrameDecoder extends ByteToMessageDecoder {
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> 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) == '-') {
// <!-- comment --> start found
openBracketsCount++;
} else if (peekAheadByte == '!') {
if (isCommentBlockStart(in, i)) {
// <!-- comment --> start found
openBracketsCount++;
} else if (isCDATABlockStart(in, i)) {
// <![CDATA[ start found
openBracketsCount++;
inCDATASection = true;
}
} else if (peekAheadByte == '?') {
// <?xml ?> 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 <?xml ?> tag was closed
openBracketsCount--;
} else if (peekBehindByte == '-' && i - 2 > -1 && in.getByte(i - 2) == '-') {
// a <!-- comment --> was closed
if (!inCDATASection) {
if (peekBehindByte == '?') {
// an <?xml ?> tag was closed
openBracketsCount--;
} else if (peekBehindByte == '-' && i - 2 > -1 && in.getByte(i - 2) == '-') {
// a <!-- comment --> was closed
openBracketsCount--;
}
} else if (peekBehindByte == ']' && i - 2 > -1 && in.getByte(i - 2) == ']') {
// a <![CDATA[...]]> 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) == '[';
}
}

View File

@ -44,7 +44,10 @@ public class XmlFrameDecoderTest {
private final List<String> 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<xxx/>\ttrash", "<xxx/>", CorruptedFrameException.class);
}
@Test
public void testDecodeWithCDATABlock() {
final String xml = "<book>" +
"<![CDATA[K&R, a.k.a. Kernighan & Ritchie]]>" +
"</book>";
testDecodeWithXml(xml, xml);
}
@Test
public void testDecodeWithCDATABlockContainingNestedUnbalancedXml() {
// <br> isn't closed, also <a> should have been </a>
final String xml = "<info>" +
"<![CDATA[Copyright 2012-2013,<br><a href=\"http://www.acme.com\">ACME Inc.<a>]]>" +
"</info>";
testDecodeWithXml(xml, xml);
}
@Test
public void testDecodeWithTwoMessages() {
testDecodeWithXml(
"<root xmlns=\"http://www.acme.com/acme\" status=\"loginok\" timestamp=\"1362410583776\"/>\n" +
'\n' +
"<root xmlns=\"http://www.acme.com/acme\" status=\"start\" time=\"0\" " +
"timestamp=\"1362410584794\">\n<child active=\"1\" status=\"started\" id=\"935449\" " +
"msgnr=\"2\"/>\n</root>",
"<root xmlns=\"http://www.acme.com/acme\" status=\"loginok\" timestamp=\"1362410583776\"/>",
"<root xmlns=\"http://www.acme.com/acme\" status=\"start\" time=\"0\" timestamp=\"1362410584794\">\n" +
"<child active=\"1\" status=\"started\" id=\"935449\" msgnr=\"2\"/>\n" +
"</root>"
);
final String input = "<root xmlns=\"http://www.acme.com/acme\" status=\"loginok\" " +
"timestamp=\"1362410583776\"/>\n\n" +
"<root xmlns=\"http://www.acme.com/acme\" status=\"start\" time=\"0\" " +
"timestamp=\"1362410584794\">\n<child active=\"1\" status=\"started\" id=\"935449\" " +
"msgnr=\"2\"/>\n</root>";
final String frame1 = "<root xmlns=\"http://www.acme.com/acme\" status=\"loginok\" " +
"timestamp=\"1362410583776\"/>";
final String frame2 = "<root xmlns=\"http://www.acme.com/acme\" status=\"start\" time=\"0\" " +
"timestamp=\"1362410584794\">\n<child active=\"1\" status=\"started\" id=\"935449\" " +
"msgnr=\"2\"/>\n</root>";
testDecodeWithXml(input, frame1, frame2);
}
@Test

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- this sample file tests CDATA sections -->
<chapter id="rockets">
<title>Rocket Launching</title>
<sect1 id="rocket-configuration">
<title>Configuration</title>
<para>
Configuring rockets should look very familiar if you're used
to Jakarta Commons-Rocket or Commons-Space. You will first
create a normal
<literal>ContextSource</literal>
then wrap it in a
<literal>RocketContextSource</literal>
.
<informalexample>
<programlisting><![CDATA[
<beans>
...
<bean id="contextSource" class="org.apache.commons.rocket.factory.RocketContextSource">
<property name="contextSource" ref="contextSourceTarget" />
</bean>
<bean id="contextSourceTarget" class="org.apache.commons.rocket.core.support.OrbitContextSource">
<property name="url" value="orbit://localhost:12345" />
<property name="user" value="neil" />
<property name="password" value="secret" />
<property name="pooled" value="false"/>
</bean>
...
</beans>
]]></programlisting>
</informalexample>
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.
</para>
<sect2 id="rocket-advanced-configuration">
<title>Validation Configuration</title>
<para>
Adding validation and a few rocket configuration tweaks to
the above example is straight forward. Inject a
<literal>RocketContextValidator</literal>
and set when validation should occur and the rocket is
ready to go.
<informalexample>
<programlisting><![CDATA[
<beans>
...
<bean id="contextSource" class="org.apache.commons.rocket.factory.RocketContextSource">
<property name="contextSource" ref="contextSourceTarget" />
<property name="rocketContextValidator" ref="rocketContextValidator" />
<property name="testOnBorrow" value="true" />
<property name="testWhileIdle" value="true" />
</bean>
<bean id="rocketContextValidator"
class="org.apache.commons.space.validation.DefaultRocketContextValidator" />
<bean id="contextSourceTarget" class="org.apache.commons.rocket.core.support.OrbitContextSource">
<property name="url" value="orbit://localhost:12345" />
<property name="user" value="neil" />
<property name="password" value="secret" />
<property name="pooled" value="false"/>
</bean>
...
</beans>
]]></programlisting>
</informalexample>
The above example will test each
<literal>RocketContext</literal>
before it is passed to the client application and test
<literal>RocketContext</literal>
s that have been sitting idle in orbit.
</para>
</sect2>
</sect1>
</chapter>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- this sample file tests CDATA blocks with explicitly unbalanced xml inside -->
<chapter id="rockets">
<title>Rocket Launching</title>
<sect1 id="rocket-configuration">
<title>Configuration</title>
<para>
Configuring rockets should look very familiar if you're used
to Jakarta Commons-Rocket or Commons-Space. You will first
create a normal
<literal>ContextSource</literal>
then wrap it in a
<literal>RocketContextSource</literal>
.
<informalexample>
<programlisting><![CDATA[
<beans>
...
<bean id="contextSource" class="org.apache.commons.rocket.factory.RocketContextSource">
<property name="contextSource" ref="contextSourceTarget" />
...
]]></programlisting>
</informalexample>
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.
</para>
<sect2 id="rocket-advanced-configuration">
<title>Validation Configuration</title>
<para>
Adding validation and a few rocket configuration tweaks to
the above example is straight forward. Inject a
<literal>RocketContextValidator</literal>
and set when validation should occur and the rocket is
ready to go.
<informalexample>
<programlisting><![CDATA[
<beans>
...
<bean id="contextSource" class="org.apache.commons.rocket.factory.RocketContextSource">
<property name="contextSource" ref="contextSourceTarget" />
...
<bean id="rocketContextValidator"
...
<bean id="contextSourceTarget" class="org.apache.commons.rocket.core.support.OrbitContextSource">
<property name="url" value="orbit://localhost:12345" />
...
]]></programlisting>
</informalexample>
The above example will test each
<literal>RocketContext</literal>
before it is passed to the client application and test
<literal>RocketContext</literal>
s that have been sitting idle in orbit.
</para>
</sect2>
</sect1>
</chapter>