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