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 }
201 if (nextState == State.SKIP_CONTROL_CHARS) {
202
203
204
205 message.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
206 return message;
207 }
208 long contentLength = HttpHeaders.getContentLength(message, -1);
209 if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
210 content = ChannelBuffers.EMPTY_BUFFER;
211 return reset();
212 }
213
214 switch (nextState) {
215 case READ_FIXED_LENGTH_CONTENT:
216 if (contentLength > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
217
218 checkpoint(State.READ_FIXED_LENGTH_CONTENT_AS_CHUNKS);
219 message.setChunked(true);
220
221
222 chunkSize = HttpHeaders.getContentLength(message, -1);
223 return message;
224 }
225 break;
226 case READ_VARIABLE_LENGTH_CONTENT:
227 if (buffer.readableBytes() > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
228
229 checkpoint(State.READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS);
230 message.setChunked(true);
231 return message;
232 }
233 break;
234 default:
235 throw new IllegalStateException("Unexpected state: " + nextState);
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 long chunkSize = this.chunkSize;
275 int readLimit = actualReadableBytes();
276
277
278
279
280
281
282
283 if (readLimit == 0) {
284 return null;
285 }
286
287 int toRead = readLimit;
288 if (toRead > maxChunkSize) {
289 toRead = maxChunkSize;
290 }
291 if (toRead > chunkSize) {
292 toRead = (int) chunkSize;
293 }
294 HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes(toRead));
295 if (chunkSize > toRead) {
296 chunkSize -= toRead;
297 } else {
298 chunkSize = 0;
299 }
300 this.chunkSize = chunkSize;
301
302 if (chunkSize == 0) {
303
304 reset();
305 if (!chunk.isLast()) {
306
307 return new Object[] { chunk, HttpChunk.LAST_CHUNK };
308 }
309 }
310 return chunk;
311 }
312
313
314
315
316 case READ_CHUNK_SIZE: {
317 String line = readLine(buffer, maxInitialLineLength);
318 int chunkSize = getChunkSize(line);
319 this.chunkSize = chunkSize;
320 if (chunkSize == 0) {
321 checkpoint(State.READ_CHUNK_FOOTER);
322 return null;
323 } else if (chunkSize > maxChunkSize) {
324
325 checkpoint(State.READ_CHUNKED_CONTENT_AS_CHUNKS);
326 } else {
327 checkpoint(State.READ_CHUNKED_CONTENT);
328 }
329 }
330 case READ_CHUNKED_CONTENT: {
331 assert chunkSize <= Integer.MAX_VALUE;
332 HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes((int) chunkSize));
333 checkpoint(State.READ_CHUNK_DELIMITER);
334 return chunk;
335 }
336 case READ_CHUNKED_CONTENT_AS_CHUNKS: {
337 assert chunkSize <= Integer.MAX_VALUE;
338 int chunkSize = (int) this.chunkSize;
339 int readLimit = actualReadableBytes();
340
341
342
343
344
345
346
347 if (readLimit == 0) {
348 return null;
349 }
350
351 int toRead = chunkSize;
352 if (toRead > maxChunkSize) {
353 toRead = maxChunkSize;
354 }
355 if (toRead > readLimit) {
356 toRead = readLimit;
357 }
358 HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes(toRead));
359 if (chunkSize > toRead) {
360 chunkSize -= toRead;
361 } else {
362 chunkSize = 0;
363 }
364 this.chunkSize = chunkSize;
365
366 if (chunkSize == 0) {
367
368 checkpoint(State.READ_CHUNK_DELIMITER);
369 }
370
371 if (!chunk.isLast()) {
372 return chunk;
373 }
374 }
375 case READ_CHUNK_DELIMITER: {
376 for (;;) {
377 byte next = buffer.readByte();
378 if (next == HttpConstants.CR) {
379 if (buffer.readByte() == HttpConstants.LF) {
380 checkpoint(State.READ_CHUNK_SIZE);
381 return null;
382 }
383 } else if (next == HttpConstants.LF) {
384 checkpoint(State.READ_CHUNK_SIZE);
385 return null;
386 }
387 }
388 }
389 case READ_CHUNK_FOOTER: {
390 HttpChunkTrailer trailer = readTrailingHeaders(buffer);
391 if (maxChunkSize == 0) {
392
393 return reset();
394 } else {
395 reset();
396
397 return trailer;
398 }
399 }
400 default: {
401 throw new Error("Shouldn't reach here.");
402 }
403 }
404 }
405
406 protected boolean isContentAlwaysEmpty(HttpMessage msg) {
407 if (msg instanceof HttpResponse) {
408 HttpResponse res = (HttpResponse) msg;
409 int code = res.getStatus().getCode();
410
411
412
413
414
415
416 if (code >= 100 && code < 200) {
417 if (code == 101 && !res.containsHeader(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT)) {
418
419 return false;
420 }
421 return true;
422 }
423
424 switch (code) {
425 case 204: case 205: case 304:
426 return true;
427 }
428 }
429 return false;
430 }
431
432 private Object reset() {
433 HttpMessage message = this.message;
434 ChannelBuffer content = this.content;
435
436 if (content != null) {
437 message.setContent(content);
438 this.content = null;
439 }
440 this.message = null;
441
442 checkpoint(State.SKIP_CONTROL_CHARS);
443 return message;
444 }
445
446 private static void skipControlCharacters(ChannelBuffer buffer) {
447 for (;;) {
448 char c = (char) buffer.readUnsignedByte();
449 if (!Character.isISOControl(c) &&
450 !Character.isWhitespace(c)) {
451 buffer.readerIndex(buffer.readerIndex() - 1);
452 break;
453 }
454 }
455 }
456
457 private Object readFixedLengthContent(ChannelBuffer buffer) {
458
459 long length = HttpHeaders.getContentLength(message, -1);
460 assert length <= Integer.MAX_VALUE;
461 int toRead = (int) length - contentRead;
462 if (toRead > actualReadableBytes()) {
463 toRead = actualReadableBytes();
464 }
465 contentRead += toRead;
466 if (length < contentRead) {
467 if (!message.isChunked()) {
468 message.setChunked(true);
469 return new Object[] {message, new DefaultHttpChunk(buffer.readBytes(toRead))};
470 } else {
471 return new DefaultHttpChunk(buffer.readBytes(toRead));
472 }
473 }
474 if (content == null) {
475 content = buffer.readBytes((int) length);
476 } else {
477 content.writeBytes(buffer, (int) length);
478 }
479 return reset();
480 }
481
482 private State readHeaders(ChannelBuffer buffer) throws TooLongFrameException {
483 headerSize = 0;
484 final HttpMessage message = this.message;
485 String line = readHeader(buffer);
486 String name = null;
487 String value = null;
488 if (line.length() != 0) {
489 message.clearHeaders();
490 do {
491 char firstChar = line.charAt(0);
492 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
493 value = value + ' ' + line.trim();
494 } else {
495 if (name != null) {
496 message.addHeader(name, value);
497 }
498 String[] header = splitHeader(line);
499 name = header[0];
500 value = header[1];
501 }
502
503 line = readHeader(buffer);
504 } while (line.length() != 0);
505
506
507 if (name != null) {
508 message.addHeader(name, value);
509 }
510 }
511
512 State nextState;
513
514 if (isContentAlwaysEmpty(message)) {
515 nextState = State.SKIP_CONTROL_CHARS;
516 } else if (message.isChunked()) {
517
518
519
520
521
522
523 nextState = State.READ_CHUNK_SIZE;
524 } else if (HttpHeaders.getContentLength(message, -1) >= 0) {
525 nextState = State.READ_FIXED_LENGTH_CONTENT;
526 } else {
527 nextState = State.READ_VARIABLE_LENGTH_CONTENT;
528 }
529 return nextState;
530 }
531
532 private HttpChunkTrailer readTrailingHeaders(ChannelBuffer buffer) throws TooLongFrameException {
533 headerSize = 0;
534 String line = readHeader(buffer);
535 String lastHeader = null;
536 if (line.length() != 0) {
537 HttpChunkTrailer trailer = new DefaultHttpChunkTrailer();
538 do {
539 char firstChar = line.charAt(0);
540 if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
541 List<String> current = trailer.getHeaders(lastHeader);
542 if (!current.isEmpty()) {
543 int lastPos = current.size() - 1;
544 String newString = current.get(lastPos) + line.trim();
545 current.set(lastPos, newString);
546 } else {
547
548 }
549 } else {
550 String[] header = splitHeader(line);
551 String name = header[0];
552 if (!name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) &&
553 !name.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING) &&
554 !name.equalsIgnoreCase(HttpHeaders.Names.TRAILER)) {
555 trailer.addHeader(name, header[1]);
556 }
557 lastHeader = name;
558 }
559
560 line = readHeader(buffer);
561 } while (line.length() != 0);
562
563 return trailer;
564 }
565
566 return HttpChunk.LAST_CHUNK;
567 }
568
569 private String readHeader(ChannelBuffer buffer) throws TooLongFrameException {
570 StringBuilder sb = new StringBuilder(64);
571 int headerSize = this.headerSize;
572
573 loop:
574 for (;;) {
575 char nextByte = (char) buffer.readByte();
576 headerSize ++;
577
578 switch (nextByte) {
579 case HttpConstants.CR:
580 nextByte = (char) buffer.readByte();
581 headerSize ++;
582 if (nextByte == HttpConstants.LF) {
583 break loop;
584 }
585 break;
586 case HttpConstants.LF:
587 break loop;
588 }
589
590
591 if (headerSize >= maxHeaderSize) {
592
593
594
595
596 throw new TooLongFrameException(
597 "HTTP header is larger than " +
598 maxHeaderSize + " bytes.");
599 }
600
601 sb.append(nextByte);
602 }
603
604 this.headerSize = headerSize;
605 return sb.toString();
606 }
607
608 protected abstract boolean isDecodingRequest();
609 protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
610
611 private static int getChunkSize(String hex) {
612 hex = hex.trim();
613 for (int i = 0; i < hex.length(); i ++) {
614 char c = hex.charAt(i);
615 if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) {
616 hex = hex.substring(0, i);
617 break;
618 }
619 }
620
621 return Integer.parseInt(hex, 16);
622 }
623
624 private static String readLine(ChannelBuffer buffer, int maxLineLength) throws TooLongFrameException {
625 StringBuilder sb = new StringBuilder(64);
626 int lineLength = 0;
627 while (true) {
628 byte nextByte = buffer.readByte();
629 if (nextByte == HttpConstants.CR) {
630 nextByte = buffer.readByte();
631 if (nextByte == HttpConstants.LF) {
632 return sb.toString();
633 }
634 } else if (nextByte == HttpConstants.LF) {
635 return sb.toString();
636 } else {
637 if (lineLength >= maxLineLength) {
638
639
640
641
642 throw new TooLongFrameException(
643 "An HTTP line is larger than " + maxLineLength +
644 " bytes.");
645 }
646 lineLength ++;
647 sb.append((char) nextByte);
648 }
649 }
650 }
651
652 private static String[] splitInitialLine(String sb) {
653 int aStart;
654 int aEnd;
655 int bStart;
656 int bEnd;
657 int cStart;
658 int cEnd;
659
660 aStart = findNonWhitespace(sb, 0);
661 aEnd = findWhitespace(sb, aStart);
662
663 bStart = findNonWhitespace(sb, aEnd);
664 bEnd = findWhitespace(sb, bStart);
665
666 cStart = findNonWhitespace(sb, bEnd);
667 cEnd = findEndOfString(sb);
668
669 return new String[] {
670 sb.substring(aStart, aEnd),
671 sb.substring(bStart, bEnd),
672 cStart < cEnd? sb.substring(cStart, cEnd) : "" };
673 }
674
675 private static String[] splitHeader(String sb) {
676 final int length = sb.length();
677 int nameStart;
678 int nameEnd;
679 int colonEnd;
680 int valueStart;
681 int valueEnd;
682
683 nameStart = findNonWhitespace(sb, 0);
684 for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
685 char ch = sb.charAt(nameEnd);
686 if (ch == ':' || Character.isWhitespace(ch)) {
687 break;
688 }
689 }
690
691 for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
692 if (sb.charAt(colonEnd) == ':') {
693 colonEnd ++;
694 break;
695 }
696 }
697
698 valueStart = findNonWhitespace(sb, colonEnd);
699 if (valueStart == length) {
700 return new String[] {
701 sb.substring(nameStart, nameEnd),
702 ""
703 };
704 }
705
706 valueEnd = findEndOfString(sb);
707 return new String[] {
708 sb.substring(nameStart, nameEnd),
709 sb.substring(valueStart, valueEnd)
710 };
711 }
712
713 private static int findNonWhitespace(String sb, int offset) {
714 int result;
715 for (result = offset; result < sb.length(); result ++) {
716 if (!Character.isWhitespace(sb.charAt(result))) {
717 break;
718 }
719 }
720 return result;
721 }
722
723 private static int findWhitespace(String sb, int offset) {
724 int result;
725 for (result = offset; result < sb.length(); result ++) {
726 if (Character.isWhitespace(sb.charAt(result))) {
727 break;
728 }
729 }
730 return result;
731 }
732
733 private static int findEndOfString(String sb) {
734 int result;
735 for (result = sb.length(); result > 0; result --) {
736 if (!Character.isWhitespace(sb.charAt(result - 1))) {
737 break;
738 }
739 }
740 return result;
741 }
742 }