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 {
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||||
boolean openingBracketFound = false;
|
boolean openingBracketFound = false;
|
||||||
boolean atLeastOneXmlElementFound = false;
|
boolean atLeastOneXmlElementFound = false;
|
||||||
|
boolean inCDATASection = false;
|
||||||
long openBracketsCount = 0;
|
long openBracketsCount = 0;
|
||||||
int length = 0;
|
int length = 0;
|
||||||
int leadingWhiteSpaceCount = 0;
|
int leadingWhiteSpaceCount = 0;
|
||||||
@ -104,7 +105,7 @@ public class XmlFrameDecoder extends ByteToMessageDecoder {
|
|||||||
fail(ctx);
|
fail(ctx);
|
||||||
in.skipBytes(in.readableBytes());
|
in.skipBytes(in.readableBytes());
|
||||||
return;
|
return;
|
||||||
} else if (readByte == '<') {
|
} else if (!inCDATASection && readByte == '<') {
|
||||||
openingBracketFound = true;
|
openingBracketFound = true;
|
||||||
|
|
||||||
if (i < bufferLength - 1) {
|
if (i < bufferLength - 1) {
|
||||||
@ -117,17 +118,21 @@ public class XmlFrameDecoder extends ByteToMessageDecoder {
|
|||||||
// char after < is a valid xml element start char,
|
// char after < is a valid xml element start char,
|
||||||
// incrementing openBracketsCount
|
// incrementing openBracketsCount
|
||||||
openBracketsCount++;
|
openBracketsCount++;
|
||||||
} else if (peekAheadByte == '!' && i < bufferLength - 3
|
} else if (peekAheadByte == '!') {
|
||||||
&& in.getByte(i + 2) == '-'
|
if (isCommentBlockStart(in, i)) {
|
||||||
&& in.getByte(i + 3) == '-') {
|
|
||||||
// <!-- comment --> start found
|
// <!-- comment --> start found
|
||||||
openBracketsCount++;
|
openBracketsCount++;
|
||||||
|
} else if (isCDATABlockStart(in, i)) {
|
||||||
|
// <![CDATA[ start found
|
||||||
|
openBracketsCount++;
|
||||||
|
inCDATASection = true;
|
||||||
|
}
|
||||||
} else if (peekAheadByte == '?') {
|
} else if (peekAheadByte == '?') {
|
||||||
// <?xml ?> start found
|
// <?xml ?> start found
|
||||||
openBracketsCount++;
|
openBracketsCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (readByte == '/') {
|
} else if (!inCDATASection && readByte == '/') {
|
||||||
if (i < bufferLength - 1 && in.getByte(i + 1) == '>') {
|
if (i < bufferLength - 1 && in.getByte(i + 1) == '>') {
|
||||||
// found />, decrementing openBracketsCount
|
// found />, decrementing openBracketsCount
|
||||||
openBracketsCount--;
|
openBracketsCount--;
|
||||||
@ -138,6 +143,7 @@ public class XmlFrameDecoder extends ByteToMessageDecoder {
|
|||||||
if (i - 1 > -1) {
|
if (i - 1 > -1) {
|
||||||
final byte peekBehindByte = in.getByte(i - 1);
|
final byte peekBehindByte = in.getByte(i - 1);
|
||||||
|
|
||||||
|
if (!inCDATASection) {
|
||||||
if (peekBehindByte == '?') {
|
if (peekBehindByte == '?') {
|
||||||
// an <?xml ?> tag was closed
|
// an <?xml ?> tag was closed
|
||||||
openBracketsCount--;
|
openBracketsCount--;
|
||||||
@ -145,6 +151,11 @@ public class XmlFrameDecoder extends ByteToMessageDecoder {
|
|||||||
// a <!-- comment --> was closed
|
// a <!-- comment --> was closed
|
||||||
openBracketsCount--;
|
openBracketsCount--;
|
||||||
}
|
}
|
||||||
|
} else if (peekBehindByte == ']' && i - 2 > -1 && in.getByte(i - 2) == ']') {
|
||||||
|
// a <![CDATA[...]]> block was closed
|
||||||
|
openBracketsCount--;
|
||||||
|
inCDATASection = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (atLeastOneXmlElementFound && openBracketsCount == 0) {
|
if (atLeastOneXmlElementFound && openBracketsCount == 0) {
|
||||||
@ -157,7 +168,7 @@ public class XmlFrameDecoder extends ByteToMessageDecoder {
|
|||||||
final int readerIndex = in.readerIndex();
|
final int readerIndex = in.readerIndex();
|
||||||
|
|
||||||
if (openBracketsCount == 0 && length > 0) {
|
if (openBracketsCount == 0 && length > 0) {
|
||||||
if (length >= in.writerIndex()) {
|
if (length >= bufferLength) {
|
||||||
length = in.readableBytes();
|
length = in.readableBytes();
|
||||||
}
|
}
|
||||||
final ByteBuf frame =
|
final ByteBuf frame =
|
||||||
@ -201,4 +212,22 @@ public class XmlFrameDecoder extends ByteToMessageDecoder {
|
|||||||
private static boolean isValidStartCharForXmlElement(final byte b) {
|
private static boolean isValidStartCharForXmlElement(final byte b) {
|
||||||
return b >= 'a' && b <= 'z' || b >= 'A' && b <= 'Z' || b == ':' || 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;
|
private final List<String> xmlSamples;
|
||||||
|
|
||||||
public XmlFrameDecoderTest() throws IOException, URISyntaxException {
|
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)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
@ -98,19 +101,36 @@ public class XmlFrameDecoderTest {
|
|||||||
testDecodeWithXml(" \n\r \t<xxx/>\ttrash", "<xxx/>", CorruptedFrameException.class);
|
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
|
@Test
|
||||||
public void testDecodeWithTwoMessages() {
|
public void testDecodeWithTwoMessages() {
|
||||||
testDecodeWithXml(
|
final String input = "<root xmlns=\"http://www.acme.com/acme\" status=\"loginok\" " +
|
||||||
"<root xmlns=\"http://www.acme.com/acme\" status=\"loginok\" timestamp=\"1362410583776\"/>\n" +
|
"timestamp=\"1362410583776\"/>\n\n" +
|
||||||
'\n' +
|
|
||||||
"<root xmlns=\"http://www.acme.com/acme\" status=\"start\" time=\"0\" " +
|
"<root xmlns=\"http://www.acme.com/acme\" status=\"start\" time=\"0\" " +
|
||||||
"timestamp=\"1362410584794\">\n<child active=\"1\" status=\"started\" id=\"935449\" " +
|
"timestamp=\"1362410584794\">\n<child active=\"1\" status=\"started\" id=\"935449\" " +
|
||||||
"msgnr=\"2\"/>\n</root>",
|
"msgnr=\"2\"/>\n</root>";
|
||||||
"<root xmlns=\"http://www.acme.com/acme\" status=\"loginok\" timestamp=\"1362410583776\"/>",
|
final String frame1 = "<root xmlns=\"http://www.acme.com/acme\" status=\"loginok\" " +
|
||||||
"<root xmlns=\"http://www.acme.com/acme\" status=\"start\" time=\"0\" timestamp=\"1362410584794\">\n" +
|
"timestamp=\"1362410583776\"/>";
|
||||||
"<child active=\"1\" status=\"started\" id=\"935449\" msgnr=\"2\"/>\n" +
|
final String frame2 = "<root xmlns=\"http://www.acme.com/acme\" status=\"start\" time=\"0\" " +
|
||||||
"</root>"
|
"timestamp=\"1362410584794\">\n<child active=\"1\" status=\"started\" id=\"935449\" " +
|
||||||
);
|
"msgnr=\"2\"/>\n</root>";
|
||||||
|
testDecodeWithXml(input, frame1, frame2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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