1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jboss.netty.handler.codec.http2;
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
383
384
385
386
387
388 if (code >= 100 && code < 200) {
389 if (code == 101 && !res.containsHeader(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT)) {
390
391 return false;
392 }
393 return true;
394 }
395
396 switch (code) {
397 case 204: case 205: case 304:
398 return true;
399 }
400 }
401 return false;
402 }
403
404 private Object reset() {
405 HttpMessage message = this.message;
406 ChannelBuffer content = this.content;
407
408 if (content != null) {
409 message.setContent(content);
410 this.content = null;
411 }
412 this.message = null;
413
414 checkpoint(State.SKIP_CONTROL_CHARS);
415 return message;
416 }
417
418 private void skipControlCharacters(ChannelBuffer buffer) {
419 for (;;) {
420 char c = (char) buffer.readUnsignedByte();
421 if (!Character.isISOControl(c) &&
422 !Character.isWhitespace(c)) {
423 buffer.readerIndex(buffer.readerIndex() - 1);
424 break;
425 }
426 }
427 }
428
429 private void readFixedLengthContent(ChannelBuffer buffer) {
430 long length = HttpHeaders.getContentLength(message, -1);
431 assert length <= Integer.MAX_VALUE;
432
433 if (content == null) {
434 content = buffer.readBytes((int) length);
435 } else {
436 content.writeBytes(buffer.readBytes((int) length));
437 }
438 }
439
440 private State readHeaders(ChannelBuffer buffer) throws TooLongFrameException {
441 headerSize = 0;
442 final HttpMessage message = this.message;
443 String line = readHeader(buffer);
444 String name = null;
445 String value = null;
446 if (line.length() != 0) {
447 message.clearHeaders();
448 do {
449 char firstChar = line.charAt(0);
450 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
451 value = value + ' ' + line.trim();
452 } else {
453 if (name != null) {
454 message.addHeader(name, value);
455 }
456 String[] header = splitHeader(line);
457 name = header[0];
458 value = header[1];
459 }
460
461 line = readHeader(buffer);
462 } while (line.length() != 0);
463
464
465 if (name != null) {
466 message.addHeader(name, value);
467 }
468 }
469
470 State nextState;
471
472 if (isContentAlwaysEmpty(message)) {
473 nextState = State.SKIP_CONTROL_CHARS;
474 } else if (message.isChunked()) {
475
476
477
478
479
480
481 nextState = State.READ_CHUNK_SIZE;
482 } else if (HttpHeaders.getContentLength(message, -1) >= 0) {
483 nextState = State.READ_FIXED_LENGTH_CONTENT;
484 } else {
485 nextState = State.READ_VARIABLE_LENGTH_CONTENT;
486 }
487 return nextState;
488 }
489
490 private HttpChunkTrailer readTrailingHeaders(ChannelBuffer buffer) throws TooLongFrameException {
491 headerSize = 0;
492 String line = readHeader(buffer);
493 String lastHeader = null;
494 if (line.length() != 0) {
495 HttpChunkTrailer trailer = new DefaultHttpChunkTrailer();
496 do {
497 char firstChar = line.charAt(0);
498 if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
499 List<String> current = trailer.getHeaders(lastHeader);
500 if (current.size() != 0) {
501 int lastPos = current.size() - 1;
502 String newString = current.get(lastPos) + line.trim();
503 current.set(lastPos, newString);
504 } else {
505
506 }
507 } else {
508 String[] header = splitHeader(line);
509 String name = header[0];
510 if (!name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) &&
511 !name.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING) &&
512 !name.equalsIgnoreCase(HttpHeaders.Names.TRAILER)) {
513 trailer.addHeader(name, header[1]);
514 }
515 lastHeader = name;
516 }
517
518 line = readHeader(buffer);
519 } while (line.length() != 0);
520
521 return trailer;
522 }
523
524 return HttpChunk.LAST_CHUNK;
525 }
526
527 private String readHeader(ChannelBuffer buffer) throws TooLongFrameException {
528 StringBuilder sb = new StringBuilder(64);
529 int headerSize = this.headerSize;
530
531 loop:
532 for (;;) {
533 char nextByte = (char) buffer.readByte();
534 headerSize ++;
535
536 switch (nextByte) {
537 case HttpCodecUtil.CR:
538 nextByte = (char) buffer.readByte();
539 headerSize ++;
540 if (nextByte == HttpCodecUtil.LF) {
541 break loop;
542 }
543 break;
544 case HttpCodecUtil.LF:
545 break loop;
546 }
547
548
549 if (headerSize >= maxHeaderSize) {
550
551
552
553
554 throw new TooLongFrameException(
555 "HTTP header is larger than " +
556 maxHeaderSize + " bytes.");
557
558 }
559
560 sb.append(nextByte);
561 }
562
563 this.headerSize = headerSize;
564 return sb.toString();
565 }
566
567 protected abstract boolean isDecodingRequest();
568 protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
569
570 private int getChunkSize(String hex) {
571 hex = hex.trim();
572 for (int i = 0; i < hex.length(); i ++) {
573 char c = hex.charAt(i);
574 if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) {
575 hex = hex.substring(0, i);
576 break;
577 }
578 }
579
580 return Integer.parseInt(hex, 16);
581 }
582
583 private String readLine(ChannelBuffer buffer, int maxLineLength) throws TooLongFrameException {
584 StringBuilder sb = new StringBuilder(64);
585 int lineLength = 0;
586 while (true) {
587 byte nextByte = buffer.readByte();
588 if (nextByte == HttpCodecUtil.CR) {
589 nextByte = buffer.readByte();
590 if (nextByte == HttpCodecUtil.LF) {
591 return sb.toString();
592 }
593 }
594 else if (nextByte == HttpCodecUtil.LF) {
595 return sb.toString();
596 }
597 else {
598 if (lineLength >= maxLineLength) {
599
600
601
602
603 throw new TooLongFrameException(
604 "An HTTP line is larger than " + maxLineLength +
605 " bytes.");
606 }
607 lineLength ++;
608 sb.append((char) nextByte);
609 }
610 }
611 }
612
613 private String[] splitInitialLine(String sb) {
614 int aStart;
615 int aEnd;
616 int bStart;
617 int bEnd;
618 int cStart;
619 int cEnd;
620
621 aStart = findNonWhitespace(sb, 0);
622 aEnd = findWhitespace(sb, aStart);
623
624 bStart = findNonWhitespace(sb, aEnd);
625 bEnd = findWhitespace(sb, bStart);
626
627 cStart = findNonWhitespace(sb, bEnd);
628 cEnd = findEndOfString(sb);
629
630 return new String[] {
631 sb.substring(aStart, aEnd),
632 sb.substring(bStart, bEnd),
633 cStart < cEnd? sb.substring(cStart, cEnd) : "" };
634 }
635
636 private String[] splitHeader(String sb) {
637 final int length = sb.length();
638 int nameStart;
639 int nameEnd;
640 int colonEnd;
641 int valueStart;
642 int valueEnd;
643
644 nameStart = findNonWhitespace(sb, 0);
645 for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
646 char ch = sb.charAt(nameEnd);
647 if (ch == ':' || Character.isWhitespace(ch)) {
648 break;
649 }
650 }
651
652 for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
653 if (sb.charAt(colonEnd) == ':') {
654 colonEnd ++;
655 break;
656 }
657 }
658
659 valueStart = findNonWhitespace(sb, colonEnd);
660 if (valueStart == length) {
661 return new String[] {
662 sb.substring(nameStart, nameEnd),
663 ""
664 };
665 }
666
667 valueEnd = findEndOfString(sb);
668 return new String[] {
669 sb.substring(nameStart, nameEnd),
670 sb.substring(valueStart, valueEnd)
671 };
672 }
673
674 private int findNonWhitespace(String sb, int offset) {
675 int result;
676 for (result = offset; result < sb.length(); result ++) {
677 if (!Character.isWhitespace(sb.charAt(result))) {
678 break;
679 }
680 }
681 return result;
682 }
683
684 private int findWhitespace(String sb, int offset) {
685 int result;
686 for (result = offset; result < sb.length(); result ++) {
687 if (Character.isWhitespace(sb.charAt(result))) {
688 break;
689 }
690 }
691 return result;
692 }
693
694 private int findEndOfString(String sb) {
695 int result;
696 for (result = sb.length(); result > 0; result --) {
697 if (!Character.isWhitespace(sb.charAt(result - 1))) {
698 break;
699 }
700 }
701 return result;
702 }
703 }