CDATA support
This commit is contained in:
parent
c8bd76d9a9
commit
5e851460a9
@ -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) == '[';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user