1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  package io.netty.handler.codec.http.multipart;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.Unpooled;
20  import io.netty.handler.codec.http.HttpConstants;
21  import io.netty.handler.codec.http.HttpContent;
22  import io.netty.handler.codec.http.HttpRequest;
23  import io.netty.handler.codec.http.LastHttpContent;
24  import io.netty.handler.codec.http.QueryStringDecoder;
25  import io.netty.handler.codec.http.HttpHeaderValues;
26  import io.netty.handler.codec.http.HttpHeaderNames;
27  import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize;
28  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
29  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
30  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.MultiPartStatus;
31  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
32  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooManyFormFieldsException;
33  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooLongFormFieldException;
34  import io.netty.util.ByteProcessor;
35  import io.netty.util.internal.PlatformDependent;
36  import io.netty.util.internal.StringUtil;
37  
38  import java.io.IOException;
39  import java.nio.charset.Charset;
40  import java.util.ArrayList;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.TreeMap;
44  
45  import static io.netty.util.internal.ObjectUtil.*;
46  
47  
48  
49  
50  
51  
52  
53  public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestDecoder {
54  
55      
56  
57  
58      private final HttpDataFactory factory;
59  
60      
61  
62  
63      private final HttpRequest request;
64  
65      
66  
67  
68      private final Charset charset;
69  
70      
71  
72  
73      private final int maxFields;
74  
75      
76  
77  
78      private final int maxBufferedBytes;
79  
80      
81  
82  
83      private boolean isLastChunk;
84  
85      
86  
87  
88      private final List<InterfaceHttpData> bodyListHttpData = new ArrayList<InterfaceHttpData>();
89  
90      
91  
92  
93      private final Map<String, List<InterfaceHttpData>> bodyMapHttpData = new TreeMap<String, List<InterfaceHttpData>>(
94              CaseIgnoringComparator.INSTANCE);
95  
96      
97  
98  
99      private ByteBuf undecodedChunk;
100 
101     
102 
103 
104     private int bodyListHttpDataRank;
105 
106     
107 
108 
109     private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED;
110 
111     
112 
113 
114     private Attribute currentAttribute;
115 
116     private boolean destroyed;
117 
118     private int discardThreshold = HttpPostRequestDecoder.DEFAULT_DISCARD_THRESHOLD;
119 
120     
121 
122 
123 
124 
125 
126 
127 
128 
129 
130     public HttpPostStandardRequestDecoder(HttpRequest request) {
131         this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
132     }
133 
134     
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146     public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request) {
147         this(factory, request, HttpConstants.DEFAULT_CHARSET);
148     }
149 
150     
151 
152 
153 
154 
155 
156 
157 
158 
159 
160 
161 
162 
163 
164     public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
165         this(factory, request, charset, HttpPostRequestDecoder.DEFAULT_MAX_FIELDS,
166                 HttpPostRequestDecoder.DEFAULT_MAX_BUFFERED_BYTES);
167     }
168 
169     
170 
171 
172 
173 
174 
175 
176 
177 
178 
179 
180 
181 
182 
183 
184 
185 
186 
187     public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
188                                           int maxFields, int maxBufferedBytes) {
189         this.request = checkNotNull(request, "request");
190         this.charset = checkNotNull(charset, "charset");
191         this.factory = checkNotNull(factory, "factory");
192         this.maxFields = maxFields;
193         this.maxBufferedBytes = maxBufferedBytes;
194         try {
195             if (request instanceof HttpContent) {
196                 
197                 
198                 offer((HttpContent) request);
199             } else {
200                 parseBody();
201             }
202         } catch (Throwable e) {
203             destroy();
204             PlatformDependent.throwException(e);
205         }
206     }
207 
208     private void checkDestroyed() {
209         if (destroyed) {
210             throw new IllegalStateException(HttpPostStandardRequestDecoder.class.getSimpleName()
211                     + " was destroyed already");
212         }
213     }
214 
215     
216 
217 
218 
219 
220     @Override
221     public boolean isMultipart() {
222         checkDestroyed();
223         return false;
224     }
225 
226     
227 
228 
229 
230 
231     @Override
232     public void setDiscardThreshold(int discardThreshold) {
233         this.discardThreshold = checkPositiveOrZero(discardThreshold, "discardThreshold");
234     }
235 
236     
237 
238 
239     @Override
240     public int getDiscardThreshold() {
241         return discardThreshold;
242     }
243 
244     
245 
246 
247 
248 
249 
250 
251 
252 
253 
254     @Override
255     public List<InterfaceHttpData> getBodyHttpDatas() {
256         checkDestroyed();
257 
258         if (!isLastChunk) {
259             throw new NotEnoughDataDecoderException();
260         }
261         return bodyListHttpData;
262     }
263 
264     
265 
266 
267 
268 
269 
270 
271 
272 
273 
274 
275     @Override
276     public List<InterfaceHttpData> getBodyHttpDatas(String name) {
277         checkDestroyed();
278 
279         if (!isLastChunk) {
280             throw new NotEnoughDataDecoderException();
281         }
282         return bodyMapHttpData.get(name);
283     }
284 
285     
286 
287 
288 
289 
290 
291 
292 
293 
294 
295 
296 
297     @Override
298     public InterfaceHttpData getBodyHttpData(String name) {
299         checkDestroyed();
300 
301         if (!isLastChunk) {
302             throw new NotEnoughDataDecoderException();
303         }
304         List<InterfaceHttpData> list = bodyMapHttpData.get(name);
305         if (list != null) {
306             return list.get(0);
307         }
308         return null;
309     }
310 
311     
312 
313 
314 
315 
316 
317 
318 
319 
320     @Override
321     public HttpPostStandardRequestDecoder offer(HttpContent content) {
322         checkDestroyed();
323 
324         if (content instanceof LastHttpContent) {
325             isLastChunk = true;
326         }
327 
328         ByteBuf buf = content.content();
329         if (undecodedChunk == null) {
330             undecodedChunk =
331                     
332                     
333                     
334                     
335                     buf.alloc().buffer(buf.readableBytes()).writeBytes(buf);
336         } else {
337             undecodedChunk.writeBytes(buf);
338         }
339         parseBody();
340         if (maxBufferedBytes > 0 && undecodedChunk != null && undecodedChunk.readableBytes() > maxBufferedBytes) {
341             throw new TooLongFormFieldException();
342         }
343         if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
344             if (undecodedChunk.refCnt() == 1) {
345                 
346                 undecodedChunk.discardReadBytes();
347             } else {
348                 
349                 
350                 ByteBuf buffer = undecodedChunk.alloc().buffer(undecodedChunk.readableBytes());
351                 buffer.writeBytes(undecodedChunk);
352                 undecodedChunk.release();
353                 undecodedChunk = buffer;
354             }
355         }
356         return this;
357     }
358 
359     
360 
361 
362 
363 
364 
365 
366 
367 
368 
369     @Override
370     public boolean hasNext() {
371         checkDestroyed();
372 
373         if (currentStatus == MultiPartStatus.EPILOGUE) {
374             
375             if (bodyListHttpDataRank >= bodyListHttpData.size()) {
376                 throw new EndOfDataDecoderException();
377             }
378         }
379         return !bodyListHttpData.isEmpty() && bodyListHttpDataRank < bodyListHttpData.size();
380     }
381 
382     
383 
384 
385 
386 
387 
388 
389 
390 
391 
392 
393 
394     @Override
395     public InterfaceHttpData next() {
396         checkDestroyed();
397 
398         if (hasNext()) {
399             return bodyListHttpData.get(bodyListHttpDataRank++);
400         }
401         return null;
402     }
403 
404     @Override
405     public InterfaceHttpData currentPartialHttpData() {
406         return currentAttribute;
407     }
408 
409     
410 
411 
412 
413 
414 
415 
416     private void parseBody() {
417         if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
418             if (isLastChunk) {
419                 currentStatus = MultiPartStatus.EPILOGUE;
420             }
421             return;
422         }
423         parseBodyAttributes();
424     }
425 
426     
427 
428 
429     protected void addHttpData(InterfaceHttpData data) {
430         if (data == null) {
431             return;
432         }
433         if (maxFields > 0 && bodyListHttpData.size() >= maxFields) {
434             throw new TooManyFormFieldsException();
435         }
436         List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
437         if (datas == null) {
438             datas = new ArrayList<InterfaceHttpData>(1);
439             bodyMapHttpData.put(data.getName(), datas);
440         }
441         datas.add(data);
442         bodyListHttpData.add(data);
443     }
444 
445     
446 
447 
448 
449 
450 
451 
452 
453     private void parseBodyAttributesStandard() {
454         int firstpos = undecodedChunk.readerIndex();
455         int currentpos = firstpos;
456         int equalpos;
457         int ampersandpos;
458         if (currentStatus == MultiPartStatus.NOTSTARTED) {
459             currentStatus = MultiPartStatus.DISPOSITION;
460         }
461         boolean contRead = true;
462         try {
463             while (undecodedChunk.isReadable() && contRead) {
464                 char read = (char) undecodedChunk.readUnsignedByte();
465                 currentpos++;
466                 switch (currentStatus) {
467                 case DISPOSITION:
468                     if (read == '=') {
469                         currentStatus = MultiPartStatus.FIELD;
470                         equalpos = currentpos - 1;
471                         String key = decodeAttribute(undecodedChunk.toString(firstpos, equalpos - firstpos, charset),
472                                 charset);
473                         currentAttribute = factory.createAttribute(request, key);
474                         firstpos = currentpos;
475                     } else if (read == '&' ||
476                             (isLastChunk && !undecodedChunk.isReadable() && hasFormBody())) { 
477                         currentStatus = MultiPartStatus.DISPOSITION;
478                         ampersandpos = read == '&' ? currentpos - 1 : currentpos;
479                         String key = decodeAttribute(
480                                 undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
481                         
482                         
483                         
484                         
485                         if (!key.isEmpty()) {
486                             currentAttribute = factory.createAttribute(request, key);
487                             currentAttribute.setValue(""); 
488                             addHttpData(currentAttribute);
489                         }
490                         currentAttribute = null;
491                         firstpos = currentpos;
492                         contRead = true;
493                     }
494                     break;
495                 case FIELD:
496                     if (read == '&') {
497                         currentStatus = MultiPartStatus.DISPOSITION;
498                         ampersandpos = currentpos - 1;
499                         setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
500                         firstpos = currentpos;
501                         contRead = true;
502                     } else if (read == HttpConstants.CR) {
503                         if (undecodedChunk.isReadable()) {
504                             read = (char) undecodedChunk.readUnsignedByte();
505                             currentpos++;
506                             if (read == HttpConstants.LF) {
507                                 currentStatus = MultiPartStatus.PREEPILOGUE;
508                                 ampersandpos = currentpos - 2;
509                                 setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
510                                 firstpos = currentpos;
511                                 contRead = false;
512                             } else {
513                                 
514                                 throw new ErrorDataDecoderException("Bad end of line");
515                             }
516                         } else {
517                             currentpos--;
518                         }
519                     } else if (read == HttpConstants.LF) {
520                         currentStatus = MultiPartStatus.PREEPILOGUE;
521                         ampersandpos = currentpos - 1;
522                         setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
523                         firstpos = currentpos;
524                         contRead = false;
525                     }
526                     break;
527                 default:
528                     
529                     contRead = false;
530                 }
531             }
532             if (isLastChunk && currentAttribute != null) {
533                 
534                 ampersandpos = currentpos;
535                 if (ampersandpos > firstpos) {
536                     setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
537                 } else if (!currentAttribute.isCompleted()) {
538                     setFinalBuffer(Unpooled.EMPTY_BUFFER);
539                 }
540                 firstpos = currentpos;
541                 currentStatus = MultiPartStatus.EPILOGUE;
542             } else if (contRead && currentAttribute != null && currentStatus == MultiPartStatus.FIELD) {
543                 
544                 currentAttribute.addContent(undecodedChunk.retainedSlice(firstpos, currentpos - firstpos),
545                                             false);
546                 firstpos = currentpos;
547             }
548             undecodedChunk.readerIndex(firstpos);
549         } catch (ErrorDataDecoderException e) {
550             
551             undecodedChunk.readerIndex(firstpos);
552             throw e;
553         } catch (IOException e) {
554             
555             undecodedChunk.readerIndex(firstpos);
556             throw new ErrorDataDecoderException(e);
557         } catch (IllegalArgumentException e) {
558             
559             undecodedChunk.readerIndex(firstpos);
560             throw new ErrorDataDecoderException(e);
561         }
562     }
563 
564     
565 
566 
567 
568 
569 
570 
571 
572     private void parseBodyAttributes() {
573         if (undecodedChunk == null) {
574             return;
575         }
576         parseBodyAttributesStandard();
577     }
578 
579     private void setFinalBuffer(ByteBuf buffer) throws IOException {
580         currentAttribute.addContent(buffer, true);
581         ByteBuf decodedBuf = decodeAttribute(currentAttribute.getByteBuf(), charset);
582         if (decodedBuf != null) { 
583             currentAttribute.setContent(decodedBuf);
584         }
585         addHttpData(currentAttribute);
586         currentAttribute = null;
587     }
588 
589     
590 
591 
592 
593 
594     private static String decodeAttribute(String s, Charset charset) {
595         try {
596             return QueryStringDecoder.decodeComponent(s, charset);
597         } catch (IllegalArgumentException e) {
598             throw new ErrorDataDecoderException("Bad string: '" + s + '\'', e);
599         }
600     }
601 
602     private static ByteBuf decodeAttribute(ByteBuf b, Charset charset) {
603         int firstEscaped = b.forEachByte(new UrlEncodedDetector());
604         if (firstEscaped == -1) {
605             return null; 
606         }
607 
608         ByteBuf buf = b.alloc().buffer(b.readableBytes());
609         UrlDecoder urlDecode = new UrlDecoder(buf);
610         int idx = b.forEachByte(urlDecode);
611         if (urlDecode.nextEscapedIdx != 0) { 
612             if (idx == -1) {
613                 idx = b.readableBytes() - 1;
614             }
615             idx -= urlDecode.nextEscapedIdx - 1;
616             buf.release();
617             throw new ErrorDataDecoderException(
618                 String.format("Invalid hex byte at index '%d' in string: '%s'", idx, b.toString(charset)));
619         }
620 
621         return buf;
622     }
623 
624     
625 
626 
627 
628     @Override
629     public void destroy() {
630         
631         cleanFiles();
632         
633         for (InterfaceHttpData httpData : bodyListHttpData) {
634             
635             if (httpData.refCnt() > 0) {
636                 httpData.release();
637             }
638         }
639 
640         destroyed = true;
641 
642         if (undecodedChunk != null && undecodedChunk.refCnt() > 0) {
643             undecodedChunk.release();
644             undecodedChunk = null;
645         }
646     }
647 
648     
649 
650 
651     @Override
652     public void cleanFiles() {
653         checkDestroyed();
654 
655         factory.cleanRequestHttpData(request);
656     }
657 
658     
659 
660 
661     @Override
662     public void removeHttpDataFromClean(InterfaceHttpData data) {
663         checkDestroyed();
664 
665         factory.removeHttpDataFromClean(request, data);
666     }
667 
668     
669 
670 
671     private boolean hasFormBody() {
672         String contentHeaderValue = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
673         if (contentHeaderValue == null) {
674             return false;
675         }
676         return HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.contentEquals(contentHeaderValue)
677                 || HttpHeaderValues.MULTIPART_FORM_DATA.contentEquals(contentHeaderValue);
678     }
679 
680     private static final class UrlEncodedDetector implements ByteProcessor {
681         @Override
682         public boolean process(byte value) throws Exception {
683             return value != '%' && value != '+';
684         }
685     }
686 
687     private static final class UrlDecoder implements ByteProcessor {
688 
689         private final ByteBuf output;
690         private int nextEscapedIdx;
691         private byte hiByte;
692 
693         UrlDecoder(ByteBuf output) {
694             this.output = output;
695         }
696 
697         @Override
698         public boolean process(byte value) {
699             if (nextEscapedIdx != 0) {
700                 if (nextEscapedIdx == 1) {
701                     hiByte = value;
702                     ++nextEscapedIdx;
703                 } else {
704                     int hi = StringUtil.decodeHexNibble((char) hiByte);
705                     int lo = StringUtil.decodeHexNibble((char) value);
706                     if (hi == -1 || lo == -1) {
707                         ++nextEscapedIdx;
708                         return false;
709                     }
710                     output.writeByte((hi << 4) + lo);
711                     nextEscapedIdx = 0;
712                 }
713             } else if (value == '%') {
714                 nextEscapedIdx = 1;
715             } else if (value == '+') {
716                 output.writeByte(' ');
717             } else {
718                 output.writeByte(value);
719             }
720             return true;
721         }
722     }
723 }