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