1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.xml;
17
18 import static io.netty.util.internal.ObjectUtil.checkPositive;
19
20 import io.netty.buffer.ByteBuf;
21 import io.netty.channel.ChannelHandlerContext;
22 import io.netty.handler.codec.ByteToMessageDecoder;
23 import io.netty.handler.codec.CorruptedFrameException;
24 import io.netty.handler.codec.TooLongFrameException;
25
26 import java.util.List;
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 public class XmlFrameDecoder extends ByteToMessageDecoder {
79
80 private final int maxFrameLength;
81
82 public XmlFrameDecoder(int maxFrameLength) {
83 this.maxFrameLength = checkPositive(maxFrameLength, "maxFrameLength");
84 }
85
86 @Override
87 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
88 boolean openingBracketFound = false;
89 boolean atLeastOneXmlElementFound = false;
90 boolean inCDATASection = false;
91 long openBracketsCount = 0;
92 int length = 0;
93 int leadingWhiteSpaceCount = 0;
94 final int bufferLength = in.writerIndex();
95
96 if (bufferLength > maxFrameLength) {
97
98 in.skipBytes(in.readableBytes());
99 fail(bufferLength);
100 return;
101 }
102
103 for (int i = in.readerIndex(); i < bufferLength; i++) {
104 final byte readByte = in.getByte(i);
105 if (!openingBracketFound && Character.isWhitespace(readByte)) {
106
107 leadingWhiteSpaceCount++;
108 } else if (!openingBracketFound && readByte != '<') {
109
110 fail(ctx);
111 in.skipBytes(in.readableBytes());
112 return;
113 } else if (!inCDATASection && readByte == '<') {
114 openingBracketFound = true;
115
116 if (i < bufferLength - 1) {
117 final byte peekAheadByte = in.getByte(i + 1);
118 if (peekAheadByte == '/') {
119
120 int peekFurtherAheadIndex = i + 2;
121 while (peekFurtherAheadIndex <= bufferLength - 1) {
122
123 if (in.getByte(peekFurtherAheadIndex) == '>') {
124 openBracketsCount--;
125 break;
126 }
127 peekFurtherAheadIndex++;
128 }
129 } else if (isValidStartCharForXmlElement(peekAheadByte)) {
130 atLeastOneXmlElementFound = true;
131
132
133 openBracketsCount++;
134 } else if (peekAheadByte == '!') {
135 if (isCommentBlockStart(in, i)) {
136
137 openBracketsCount++;
138 } else if (isCDATABlockStart(in, i)) {
139
140 openBracketsCount++;
141 inCDATASection = true;
142 }
143 } else if (peekAheadByte == '?') {
144
145 openBracketsCount++;
146 }
147 }
148 } else if (!inCDATASection && readByte == '/') {
149 if (i < bufferLength - 1 && in.getByte(i + 1) == '>') {
150
151 openBracketsCount--;
152 }
153 } else if (readByte == '>') {
154 length = i + 1;
155
156 if (i - 1 > -1) {
157 final byte peekBehindByte = in.getByte(i - 1);
158
159 if (!inCDATASection) {
160 if (peekBehindByte == '?') {
161
162 openBracketsCount--;
163 } else if (peekBehindByte == '-' && i - 2 > -1 && in.getByte(i - 2) == '-') {
164
165 openBracketsCount--;
166 }
167 } else if (peekBehindByte == ']' && i - 2 > -1 && in.getByte(i - 2) == ']') {
168
169 openBracketsCount--;
170 inCDATASection = false;
171 }
172 }
173
174 if (atLeastOneXmlElementFound && openBracketsCount == 0) {
175
176 break;
177 }
178 }
179 }
180
181 final int readerIndex = in.readerIndex();
182 int xmlElementLength = length - readerIndex;
183
184 if (openBracketsCount == 0 && xmlElementLength > 0) {
185 if (readerIndex + xmlElementLength >= bufferLength) {
186 xmlElementLength = in.readableBytes();
187 }
188 final ByteBuf frame =
189 extractFrame(in, readerIndex + leadingWhiteSpaceCount, xmlElementLength - leadingWhiteSpaceCount);
190 in.skipBytes(xmlElementLength);
191 out.add(frame);
192 }
193 }
194
195 private void fail(long frameLength) {
196 if (frameLength > 0) {
197 throw new TooLongFrameException(
198 "frame length exceeds " + maxFrameLength + ": " + frameLength + " - discarded");
199 } else {
200 throw new TooLongFrameException(
201 "frame length exceeds " + maxFrameLength + " - discarding");
202 }
203 }
204
205 private static void fail(ChannelHandlerContext ctx) {
206 ctx.fireExceptionCaught(new CorruptedFrameException("frame contains content before the xml starts"));
207 }
208
209 private static ByteBuf extractFrame(ByteBuf buffer, int index, int length) {
210 return buffer.copy(index, length);
211 }
212
213
214
215
216
217
218
219
220
221
222
223
224 private static boolean isValidStartCharForXmlElement(final byte b) {
225 return b >= 'a' && b <= 'z' || b >= 'A' && b <= 'Z' || b == ':' || b == '_';
226 }
227
228 private static boolean isCommentBlockStart(final ByteBuf in, final int i) {
229 return i < in.writerIndex() - 3
230 && in.getByte(i + 2) == '-'
231 && in.getByte(i + 3) == '-';
232 }
233
234 private static boolean isCDATABlockStart(final ByteBuf in, final int i) {
235 return i < in.writerIndex() - 8
236 && in.getByte(i + 2) == '['
237 && in.getByte(i + 3) == 'C'
238 && in.getByte(i + 4) == 'D'
239 && in.getByte(i + 5) == 'A'
240 && in.getByte(i + 6) == 'T'
241 && in.getByte(i + 7) == 'A'
242 && in.getByte(i + 8) == '[';
243 }
244
245 }