1
17 package org.apache.coyote.http11.filters;
18
19 import java.io.EOFException;
20 import java.io.IOException;
21 import java.nio.ByteBuffer;
22 import java.nio.charset.StandardCharsets;
23 import java.util.Locale;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.apache.coyote.InputBuffer;
28 import org.apache.coyote.Request;
29 import org.apache.coyote.http11.Constants;
30 import org.apache.coyote.http11.InputFilter;
31 import org.apache.tomcat.util.buf.ByteChunk;
32 import org.apache.tomcat.util.buf.HexUtils;
33 import org.apache.tomcat.util.net.ApplicationBufferHandler;
34 import org.apache.tomcat.util.res.StringManager;
35
36
42 public class ChunkedInputFilter implements InputFilter, ApplicationBufferHandler {
43
44 private static final StringManager sm = StringManager.getManager(
45 ChunkedInputFilter.class.getPackage().getName());
46
47
48
49
50 protected static final String ENCODING_NAME = "chunked";
51 protected static final ByteChunk ENCODING = new ByteChunk();
52
53
54
55
56 static {
57 ENCODING.setBytes(ENCODING_NAME.getBytes(StandardCharsets.ISO_8859_1),
58 0, ENCODING_NAME.length());
59 }
60
61
62
63
64
67 protected InputBuffer buffer;
68
69
70
73 protected int remaining = 0;
74
75
76
79 protected ByteBuffer readChunk;
80
81
82
85 protected boolean endChunk = false;
86
87
88
91 protected final ByteChunk trailingHeaders = new ByteChunk();
92
93
94
98 protected boolean needCRLFParse = false;
99
100
101
104 private Request request;
105
106
107
110 private final long maxExtensionSize;
111
112
113
116 private final int maxTrailerSize;
117
118
119
122 private long extensionSize;
123
124
125 private final int maxSwallowSize;
126
127
128
131 private boolean error;
132
133
134 private final Set<String> allowedTrailerHeaders;
135
136
137
138 public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders,
139 int maxExtensionSize, int maxSwallowSize) {
140 this.trailingHeaders.setLimit(maxTrailerSize);
141 this.allowedTrailerHeaders = allowedTrailerHeaders;
142 this.maxExtensionSize = maxExtensionSize;
143 this.maxTrailerSize = maxTrailerSize;
144 this.maxSwallowSize = maxSwallowSize;
145 }
146
147
148
149
150 @Override
151 public int doRead(ApplicationBufferHandler handler) throws IOException {
152 if (endChunk) {
153 return -1;
154 }
155
156 checkError();
157
158 if(needCRLFParse) {
159 needCRLFParse = false;
160 parseCRLF(false);
161 }
162
163 if (remaining <= 0) {
164 if (!parseChunkHeader()) {
165 throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
166 }
167 if (endChunk) {
168 parseEndChunk();
169 return -1;
170 }
171 }
172
173 int result = 0;
174
175 if (readChunk == null || readChunk.position() >= readChunk.limit()) {
176 if (readBytes() < 0) {
177 throwIOException(sm.getString("chunkedInputFilter.eos"));
178 }
179 }
180
181 if (remaining > readChunk.remaining()) {
182 result = readChunk.remaining();
183 remaining = remaining - result;
184 if (readChunk != handler.getByteBuffer()) {
185 handler.setByteBuffer(readChunk.duplicate());
186 }
187 readChunk.position(readChunk.limit());
188 } else {
189 result = remaining;
190 if (readChunk != handler.getByteBuffer()) {
191 handler.setByteBuffer(readChunk.duplicate());
192 handler.getByteBuffer().limit(readChunk.position() + remaining);
193 }
194 readChunk.position(readChunk.position() + remaining);
195 remaining = 0;
196
197 if ((readChunk.position() + 1) >= readChunk.limit()) {
198
199
200 needCRLFParse = true;
201 } else {
202 parseCRLF(false);
203 }
204 }
205
206 return result;
207 }
208
209
210
211
212
215 @Override
216 public void setRequest(Request request) {
217 this.request = request;
218 }
219
220
221
224 @Override
225 public long end() throws IOException {
226 long swallowed = 0;
227 int read = 0;
228
229 while ((read = doRead(this)) >= 0) {
230 swallowed += read;
231 if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
232 throwIOException(sm.getString("inputFilter.maxSwallow"));
233 }
234 }
235
236
237 return readChunk.remaining();
238 }
239
240
241
244 @Override
245 public int available() {
246 return readChunk != null ? readChunk.remaining() : 0;
247 }
248
249
250
253 @Override
254 public void setBuffer(InputBuffer buffer) {
255 this.buffer = buffer;
256 }
257
258
259
262 @Override
263 public void recycle() {
264 remaining = 0;
265 if (readChunk != null) {
266 readChunk.position(0).limit(0);
267 }
268 endChunk = false;
269 needCRLFParse = false;
270 trailingHeaders.recycle();
271 trailingHeaders.setLimit(maxTrailerSize);
272 extensionSize = 0;
273 error = false;
274 }
275
276
277
281 @Override
282 public ByteChunk getEncodingName() {
283 return ENCODING;
284 }
285
286
287 @Override
288 public boolean isFinished() {
289 return endChunk;
290 }
291
292
293
294
295
300 protected int readBytes() throws IOException {
301 return buffer.doRead(this);
302 }
303
304
305
319 protected boolean parseChunkHeader() throws IOException {
320
321 int result = 0;
322 boolean eol = false;
323 int readDigit = 0;
324 boolean extension = false;
325
326 while (!eol) {
327
328 if (readChunk == null || readChunk.position() >= readChunk.limit()) {
329 if (readBytes() <= 0)
330 return false;
331 }
332
333 byte chr = readChunk.get(readChunk.position());
334 if (chr == Constants.CR || chr == Constants.LF) {
335 parseCRLF(false);
336 eol = true;
337 } else if (chr == Constants.SEMI_COLON && !extension) {
338
339
340
341 extension = true;
342 extensionSize++;
343 } else if (!extension) {
344
345 int charValue = HexUtils.getDec(chr);
346 if (charValue != -1 && readDigit < 8) {
347 readDigit++;
348 result = (result << 4) | charValue;
349 } else {
350
351
352 return false;
353 }
354 } else {
355
356
357
358 extensionSize++;
359 if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
360 throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
361 }
362 }
363
364
365 if (!eol) {
366 readChunk.position(readChunk.position() + 1);
367 }
368 }
369
370 if (readDigit == 0 || result < 0) {
371 return false;
372 }
373
374 if (result == 0) {
375 endChunk = true;
376 }
377
378 remaining = result;
379 return true;
380 }
381
382
383
391 protected void parseCRLF(boolean tolerant) throws IOException {
392
393 boolean eol = false;
394 boolean crfound = false;
395
396 while (!eol) {
397 if (readChunk == null || readChunk.position() >= readChunk.limit()) {
398 if (readBytes() <= 0) {
399 throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
400 }
401 }
402
403 byte chr = readChunk.get(readChunk.position());
404 if (chr == Constants.CR) {
405 if (crfound) {
406 throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
407 }
408 crfound = true;
409 } else if (chr == Constants.LF) {
410 if (!tolerant && !crfound) {
411 throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
412 }
413 eol = true;
414 } else {
415 throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
416 }
417
418 readChunk.position(readChunk.position() + 1);
419 }
420 }
421
422
423
427 protected void parseEndChunk() throws IOException {
428
429 while (parseHeader()) {
430
431 }
432 }
433
434
435 private boolean parseHeader() throws IOException {
436
437 Map<String,String> headers = request.getTrailerFields();
438
439 byte chr = 0;
440
441
442 if (readChunk == null || readChunk.position() >= readChunk.limit()) {
443 if (readBytes() <0) {
444 throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
445 }
446 }
447
448
449 chr = readChunk.get(readChunk.position());
450
451
452 if (chr == Constants.CR || chr == Constants.LF) {
453 parseCRLF(false);
454 return false;
455 }
456
457
458 int startPos = trailingHeaders.getEnd();
459
460
461
462
463
464
465 boolean colon = false;
466 while (!colon) {
467
468
469 if (readChunk == null || readChunk.position() >= readChunk.limit()) {
470 if (readBytes() <0) {
471 throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
472 }
473 }
474
475
476 chr = readChunk.get(readChunk.position());
477 if ((chr >= Constants.A) && (chr <= Constants.Z)) {
478 chr = (byte) (chr - Constants.LC_OFFSET);
479 }
480
481 if (chr == Constants.COLON) {
482 colon = true;
483 } else {
484 trailingHeaders.append(chr);
485 }
486
487 readChunk.position(readChunk.position() + 1);
488
489 }
490 int colonPos = trailingHeaders.getEnd();
491
492
493
494
495
496 boolean eol = false;
497 boolean validLine = true;
498 int lastSignificantChar = 0;
499
500 while (validLine) {
501
502 boolean space = true;
503
504
505 while (space) {
506
507
508 if (readChunk == null || readChunk.position() >= readChunk.limit()) {
509 if (readBytes() <0) {
510 throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
511 }
512 }
513
514 chr = readChunk.get(readChunk.position());
515 if ((chr == Constants.SP) || (chr == Constants.HT)) {
516 readChunk.position(readChunk.position() + 1);
517
518
519 int newlimit = trailingHeaders.getLimit() -1;
520 if (trailingHeaders.getEnd() > newlimit) {
521 throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
522 }
523 trailingHeaders.setLimit(newlimit);
524 } else {
525 space = false;
526 }
527
528 }
529
530
531 while (!eol) {
532
533
534 if (readChunk == null || readChunk.position() >= readChunk.limit()) {
535 if (readBytes() <0) {
536 throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
537 }
538 }
539
540 chr = readChunk.get(readChunk.position());
541 if (chr == Constants.CR || chr == Constants.LF) {
542 parseCRLF(true);
543 eol = true;
544 } else if (chr == Constants.SP) {
545 trailingHeaders.append(chr);
546 } else {
547 trailingHeaders.append(chr);
548 lastSignificantChar = trailingHeaders.getEnd();
549 }
550
551 if (!eol) {
552 readChunk.position(readChunk.position() + 1);
553 }
554 }
555
556
557
558
559
560 if (readChunk == null || readChunk.position() >= readChunk.limit()) {
561 if (readBytes() <0) {
562 throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
563 }
564 }
565
566 chr = readChunk.get(readChunk.position());
567 if ((chr != Constants.SP) && (chr != Constants.HT)) {
568 validLine = false;
569 } else {
570 eol = false;
571
572
573 trailingHeaders.append(chr);
574 }
575
576 }
577
578 String headerName = new String(trailingHeaders.getBytes(), startPos,
579 colonPos - startPos, StandardCharsets.ISO_8859_1);
580
581 headerName = headerName.toLowerCase(Locale.ENGLISH);
582
583 if (allowedTrailerHeaders.contains(headerName)) {
584
585 String value = new String(trailingHeaders.getBytes(), colonPos,
586 lastSignificantChar - colonPos, StandardCharsets.ISO_8859_1);
587
588 headers.put(headerName, value);
589 }
590
591 return true;
592 }
593
594
595 private void throwIOException(String msg) throws IOException {
596 error = true;
597 throw new IOException(msg);
598 }
599
600
601 private void throwEOFException(String msg) throws IOException {
602 error = true;
603 throw new EOFException(msg);
604 }
605
606
607 private void checkError() throws IOException {
608 if (error) {
609 throw new IOException(sm.getString("chunkedInputFilter.error"));
610 }
611 }
612
613
614 @Override
615 public void setByteBuffer(ByteBuffer buffer) {
616 readChunk = buffer;
617 }
618
619
620 @Override
621 public ByteBuffer getByteBuffer() {
622 return readChunk;
623 }
624
625
626 @Override
627 public void expand(int size) {
628
629 }
630 }
631