1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jboss.netty.handler.codec.http;
17
18 import java.util.List;
19
20 import org.jboss.netty.buffer.ChannelBuffer;
21 import org.jboss.netty.buffer.ChannelBuffers;
22 import org.jboss.netty.channel.Channel;
23 import org.jboss.netty.channel.ChannelHandlerContext;
24 import org.jboss.netty.channel.ChannelPipeline;
25 import org.jboss.netty.handler.codec.frame.TooLongFrameException;
26 import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDecoder.State> {
108
109 private final int maxInitialLineLength;
110 private final int maxHeaderSize;
111 private final int maxChunkSize;
112 private HttpMessage message;
113 private ChannelBuffer content;
114 private long chunkSize;
115 private int headerSize;
116
117
118
119
120
121
122
123
124
125
126
127 protected static enum State {
128 SKIP_CONTROL_CHARS,
129 READ_INITIAL,
130 READ_HEADER,
131 READ_VARIABLE_LENGTH_CONTENT,
132 READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS,
133 READ_FIXED_LENGTH_CONTENT,
134 READ_FIXED_LENGTH_CONTENT_AS_CHUNKS,
135 READ_CHUNK_SIZE,
136 READ_CHUNKED_CONTENT,
137 READ_CHUNKED_CONTENT_AS_CHUNKS,
138 READ_CHUNK_DELIMITER,
139 READ_CHUNK_FOOTER;
140 }
141
142
143
144
145
146
147 protected HttpMessageDecoder() {
148 this(4096, 8192, 8192);
149 }
150
151
152
153
154 protected HttpMessageDecoder(
155 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
156
157 super(State.SKIP_CONTROL_CHARS, true);
158
159 if (maxInitialLineLength <= 0) {
160 throw new IllegalArgumentException(
161 "maxInitialLineLength must be a positive integer: " +
162 maxInitialLineLength);
163 }
164 if (maxHeaderSize <= 0) {
165 throw new IllegalArgumentException(
166 "maxHeaderSize must be a positive integer: " +
167 maxHeaderSize);
168 }
169 if (maxChunkSize < 0) {
170 throw new IllegalArgumentException(
171 "maxChunkSize must be a positive integer: " +
172 maxChunkSize);
173 }
174 this.maxInitialLineLength = maxInitialLineLength;
175 this.maxHeaderSize = maxHeaderSize;
176 this.maxChunkSize = maxChunkSize;
177 }
178
179 @Override
180 protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, State state) throws Exception {
181 switch (state) {
182 case SKIP_CONTROL_CHARS: {
183 try {
184 skipControlCharacters(buffer);
185 checkpoint(State.READ_INITIAL);
186 } finally {
187 checkpoint();
188 }
189 }
190 case READ_INITIAL: {
191 String[] initialLine = splitInitialLine(readLine(buffer, maxInitialLineLength));
192 if (initialLine.length < 3) {
193
194 checkpoint(State.SKIP_CONTROL_CHARS);
195 return null;
196 }
197
198 message = createMessage(initialLine);
199 checkpoint(State.READ_HEADER);
200 }
201 case READ_HEADER: {
202 State nextState = readHeaders(buffer);
203 checkpoint(nextState);
204 if (nextState == State.READ_CHUNK_SIZE) {
205
206 message.setChunked(true);
207
208 return message;
209 } else if (nextState == State.SKIP_CONTROL_CHARS) {
210
211
212
213 message.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
214 return message;
215 } else {
216 long contentLength = HttpHeaders.getContentLength(message, -1);
217 if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
218 content = ChannelBuffers.EMPTY_BUFFER;
219 return reset();
220 }
221
222 switch (nextState) {
223 case READ_FIXED_LENGTH_CONTENT:
224 if (contentLength > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
225
226 checkpoint(State.READ_FIXED_LENGTH_CONTENT_AS_CHUNKS);
227 message.setChunked(true);
228
229
230 chunkSize = HttpHeaders.getContentLength(message, -1);
231 return message;
232 }
233 break;
234 case READ_VARIABLE_LENGTH_CONTENT:
235 if (buffer.readableBytes() > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
236
237 checkpoint(State.READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS);
238 message.setChunked(true);
239 return message;
240 }
241 break;
242 default:
243 throw new IllegalStateException("Unexpected state: " + nextState);
244 }
245 }
246
247 return null;
248 }
249 case READ_VARIABLE_LENGTH_CONTENT: {
250 if (content == null) {
251 content = ChannelBuffers.dynamicBuffer(channel.getConfig().getBufferFactory());
252 }
253
254 content.writeBytes(buffer.readBytes(buffer.readableBytes()));
255 return reset();
256 }
257 case READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS: {
258
259 int chunkSize = Math.min(maxChunkSize, buffer.readableBytes());
260 HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes(chunkSize));
261
262 if (!buffer.readable()) {
263
264 reset();
265 if (!chunk.isLast()) {
266
267 return new Object[] { chunk, HttpChunk.LAST_CHUNK };
268 }
269 }
270 return chunk;
271 }
272 case READ_FIXED_LENGTH_CONTENT: {
273
274 readFixedLengthContent(buffer);
275 return reset();
276 }
277 case READ_FIXED_LENGTH_CONTENT_AS_CHUNKS: {
278 long chunkSize = this.chunkSize;
279 HttpChunk chunk;
280 if (chunkSize > maxChunkSize) {
281 chunk = new DefaultHttpChunk(buffer.readBytes(maxChunkSize));
282 chunkSize -= maxChunkSize;
283 } else {
284 assert chunkSize <= Integer.MAX_VALUE;
285 chunk = new DefaultHttpChunk(buffer.readBytes((int) chunkSize));
286 chunkSize = 0;
287 }
288 this.chunkSize = chunkSize;
289
290 if (chunkSize == 0) {
291
292 reset();
293 if (!chunk.isLast()) {
294
295 return new Object[] { chunk, HttpChunk.LAST_CHUNK };
296 }
297 }
298 return chunk;
299 }
300
301
302
303
304 case READ_CHUNK_SIZE: {
305 String line = readLine(buffer, maxInitialLineLength);
306 int chunkSize = getChunkSize(line);
307 this.chunkSize = chunkSize;
308 if (chunkSize == 0) {
309 checkpoint(State.READ_CHUNK_FOOTER);
310 return null;
311 } else if (chunkSize > maxChunkSize) {
312
313 checkpoint(State.READ_CHUNKED_CONTENT_AS_CHUNKS);
314 } else {
315 checkpoint(State.READ_CHUNKED_CONTENT);
316 }
317 }
318 case READ_CHUNKED_CONTENT: {
319 assert chunkSize <= Integer.MAX_VALUE;
320 HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes((int) chunkSize));
321 checkpoint(State.READ_CHUNK_DELIMITER);
322 return chunk;
323 }
324 case READ_CHUNKED_CONTENT_AS_CHUNKS: {
325 long chunkSize = this.chunkSize;
326 HttpChunk chunk;
327 if (chunkSize > maxChunkSize) {
328 chunk = new DefaultHttpChunk(buffer.readBytes(maxChunkSize));
329 chunkSize -= maxChunkSize;
330 } else {
331 assert chunkSize <= Integer.MAX_VALUE;
332 chunk = new DefaultHttpChunk(buffer.readBytes((int) chunkSize));
333 chunkSize = 0;
334 }
335 this.chunkSize = chunkSize;
336
337 if (chunkSize == 0) {
338
339 checkpoint(State.READ_CHUNK_DELIMITER);
340 }
341
342 if (!chunk.isLast()) {
343 return chunk;
344 }
345 }
346 case READ_CHUNK_DELIMITER: {
347 for (;;) {
348 byte next = buffer.readByte();
349 if (next == HttpCodecUtil.CR) {
350 if (buffer.readByte() == HttpCodecUtil.LF) {
351 checkpoint(State.READ_CHUNK_SIZE);
352 return null;
353 }
354 } else if (next == HttpCodecUtil.LF) {
355 checkpoint(State.READ_CHUNK_SIZE);
356 return null;
357 }
358 }
359 }
360 case READ_CHUNK_FOOTER: {
361 HttpChunkTrailer trailer = readTrailingHeaders(buffer);
362 if (maxChunkSize == 0) {
363
364 return reset();
365 } else {
366 reset();
367
368 return trailer;
369 }
370 }
371 default: {
372 throw new Error("Shouldn't reach here.");
373 }
374
375 }
376 }
377
378 protected boolean isContentAlwaysEmpty(HttpMessage msg) {
379 if (msg instanceof HttpResponse) {
380 HttpResponse res = (HttpResponse) msg;
381 int code = res.getStatus().getCode();
382 if (code < 200) {
383 return true;
384 }
385 switch (code) {
386 case 204: case 205: case 304:
387 return true;
388 }
389 }
390 return false;
391 }
392
393 private Object reset() {
394 HttpMessage message = this.message;
395 ChannelBuffer content = this.content;
396
397 if (content != null) {
398 message.setContent(content);
399 this.content = null;
400 }
401 this.message = null;
402
403 checkpoint(State.SKIP_CONTROL_CHARS);
404 return message;
405 }
406
407 private void skipControlCharacters(ChannelBuffer buffer) {
408 for (;;) {
409 char c = (char) buffer.readUnsignedByte();
410 if (!Character.isISOControl(c) &&
411 !Character.isWhitespace(c)) {
412 buffer.readerIndex(buffer.readerIndex() - 1);
413 break;
414 }
415 }
416 }
417
418 private void readFixedLengthContent(ChannelBuffer buffer) {
419 long length = HttpHeaders.getContentLength(message, -1);
420 assert length <= Integer.MAX_VALUE;
421
422 if (content == null) {
423 content = buffer.readBytes((int) length);
424 } else {
425 content.writeBytes(buffer.readBytes((int) length));
426 }
427 }
428
429 private State readHeaders(ChannelBuffer buffer) throws TooLongFrameException {
430 headerSize = 0;
431 final HttpMessage message = this.message;
432 String line = readHeader(buffer);
433 String name = null;
434 String value = null;
435 if (line.length() != 0) {
436 message.clearHeaders();
437 do {
438 char firstChar = line.charAt(0);
439 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
440 value = value + ' ' + line.trim();
441 } else {
442 if (name != null) {
443 message.addHeader(name, value);
444 }
445 String[] header = splitHeader(line);
446 name = header[0];
447 value = header[1];
448 }
449
450 line = readHeader(buffer);
451 } while (line.length() != 0);
452
453
454 if (name != null) {
455 message.addHeader(name, value);
456 }
457 }
458
459 State nextState;
460
461 if (isContentAlwaysEmpty(message)) {
462 nextState = State.SKIP_CONTROL_CHARS;
463 } else if (message.isChunked()) {
464
465
466
467
468
469
470 nextState = State.READ_CHUNK_SIZE;
471 } else if (HttpHeaders.getContentLength(message, -1) >= 0) {
472 nextState = State.READ_FIXED_LENGTH_CONTENT;
473 } else {
474 nextState = State.READ_VARIABLE_LENGTH_CONTENT;
475 }
476 return nextState;
477 }
478
479 private HttpChunkTrailer readTrailingHeaders(ChannelBuffer buffer) throws TooLongFrameException {
480 headerSize = 0;
481 String line = readHeader(buffer);
482 String lastHeader = null;
483 if (line.length() != 0) {
484 HttpChunkTrailer trailer = new DefaultHttpChunkTrailer();
485 do {
486 char firstChar = line.charAt(0);
487 if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
488 List<String> current = trailer.getHeaders(lastHeader);
489 if (current.size() != 0) {
490 int lastPos = current.size() - 1;
491 String newString = current.get(lastPos) + line.trim();
492 current.set(lastPos, newString);
493 } else {
494
495 }
496 } else {
497 String[] header = splitHeader(line);
498 String name = header[0];
499 if (!name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) &&
500 !name.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING) &&
501 !name.equalsIgnoreCase(HttpHeaders.Names.TRAILER)) {
502 trailer.addHeader(name, header[1]);
503 }
504 lastHeader = name;
505 }
506
507 line = readHeader(buffer);
508 } while (line.length() != 0);
509
510 return trailer;
511 }
512
513 return HttpChunk.LAST_CHUNK;
514 }
515
516 private String readHeader(ChannelBuffer buffer) throws TooLongFrameException {
517 StringBuilder sb = new StringBuilder(64);
518 int headerSize = this.headerSize;
519
520 loop:
521 for (;;) {
522 char nextByte = (char) buffer.readByte();
523 headerSize ++;
524
525 switch (nextByte) {
526 case HttpCodecUtil.CR:
527 nextByte = (char) buffer.readByte();
528 headerSize ++;
529 if (nextByte == HttpCodecUtil.LF) {
530 break loop;
531 }
532 break;
533 case HttpCodecUtil.LF:
534 break loop;
535 }
536
537
538 if (headerSize >= maxHeaderSize) {
539
540
541
542
543 throw new TooLongFrameException(
544 "HTTP header is larger than " +
545 maxHeaderSize + " bytes.");
546
547 }
548
549 sb.append(nextByte);
550 }
551
552 this.headerSize = headerSize;
553 return sb.toString();
554 }
555
556 protected abstract boolean isDecodingRequest();
557 protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
558
559 private int getChunkSize(String hex) {
560 hex = hex.trim();
561 for (int i = 0; i < hex.length(); i ++) {
562 char c = hex.charAt(i);
563 if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) {
564 hex = hex.substring(0, i);
565 break;
566 }
567 }
568
569 return Integer.parseInt(hex, 16);
570 }
571
572 private String readLine(ChannelBuffer buffer, int maxLineLength) throws TooLongFrameException {
573 StringBuilder sb = new StringBuilder(64);
574 int lineLength = 0;
575 while (true) {
576 byte nextByte = buffer.readByte();
577 if (nextByte == HttpCodecUtil.CR) {
578 nextByte = buffer.readByte();
579 if (nextByte == HttpCodecUtil.LF) {
580 return sb.toString();
581 }
582 }
583 else if (nextByte == HttpCodecUtil.LF) {
584 return sb.toString();
585 }
586 else {
587 if (lineLength >= maxLineLength) {
588
589
590
591
592 throw new TooLongFrameException(
593 "An HTTP line is larger than " + maxLineLength +
594 " bytes.");
595 }
596 lineLength ++;
597 sb.append((char) nextByte);
598 }
599 }
600 }
601
602 private String[] splitInitialLine(String sb) {
603 int aStart;
604 int aEnd;
605 int bStart;
606 int bEnd;
607 int cStart;
608 int cEnd;
609
610 aStart = findNonWhitespace(sb, 0);
611 aEnd = findWhitespace(sb, aStart);
612
613 bStart = findNonWhitespace(sb, aEnd);
614 bEnd = findWhitespace(sb, bStart);
615
616 cStart = findNonWhitespace(sb, bEnd);
617 cEnd = findEndOfString(sb);
618
619 return new String[] {
620 sb.substring(aStart, aEnd),
621 sb.substring(bStart, bEnd),
622 cStart < cEnd? sb.substring(cStart, cEnd) : "" };
623 }
624
625 private String[] splitHeader(String sb) {
626 final int length = sb.length();
627 int nameStart;
628 int nameEnd;
629 int colonEnd;
630 int valueStart;
631 int valueEnd;
632
633 nameStart = findNonWhitespace(sb, 0);
634 for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
635 char ch = sb.charAt(nameEnd);
636 if (ch == ':' || Character.isWhitespace(ch)) {
637 break;
638 }
639 }
640
641 for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
642 if (sb.charAt(colonEnd) == ':') {
643 colonEnd ++;
644 break;
645 }
646 }
647
648 valueStart = findNonWhitespace(sb, colonEnd);
649 if (valueStart == length) {
650 return new String[] {
651 sb.substring(nameStart, nameEnd),
652 ""
653 };
654 }
655
656 valueEnd = findEndOfString(sb);
657 return new String[] {
658 sb.substring(nameStart, nameEnd),
659 sb.substring(valueStart, valueEnd)
660 };
661 }
662
663 private int findNonWhitespace(String sb, int offset) {
664 int result;
665 for (result = offset; result < sb.length(); result ++) {
666 if (!Character.isWhitespace(sb.charAt(result))) {
667 break;
668 }
669 }
670 return result;
671 }
672
673 private int findWhitespace(String sb, int offset) {
674 int result;
675 for (result = offset; result < sb.length(); result ++) {
676 if (Character.isWhitespace(sb.charAt(result))) {
677 break;
678 }
679 }
680 return result;
681 }
682
683 private int findEndOfString(String sb) {
684 int result;
685 for (result = sb.length(); result > 0; result --) {
686 if (!Character.isWhitespace(sb.charAt(result - 1))) {
687 break;
688 }
689 }
690 return result;
691 }
692 }