1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jboss.netty.handler.codec.http.multipart;
17
18 import org.jboss.netty.buffer.ChannelBuffer;
19 import org.jboss.netty.buffer.ChannelBuffers;
20 import org.jboss.netty.handler.codec.http.HttpChunk;
21 import org.jboss.netty.handler.codec.http.HttpConstants;
22 import org.jboss.netty.handler.codec.http.HttpHeaders;
23 import org.jboss.netty.handler.codec.http.HttpRequest;
24 import org.jboss.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadNoBackArrayException;
25 import org.jboss.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize;
26 import org.jboss.netty.handler.codec.http.multipart.HttpPostBodyUtil.TransferEncodingMechanism;
27 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
28 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
29 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.IncompatibleDataDecoderException;
30 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.MultiPartStatus;
31 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
32 import org.jboss.netty.util.internal.CaseIgnoringComparator;
33 import org.jboss.netty.util.internal.StringUtil;
34
35 import java.io.IOException;
36 import java.nio.charset.Charset;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.TreeMap;
41
42
43
44
45 @SuppressWarnings({ "deprecation", "RedundantThrowsDeclaration" })
46 public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequestDecoder {
47
48
49
50 private final HttpDataFactory factory;
51
52
53
54
55 private final HttpRequest request;
56
57
58
59
60 private Charset charset;
61
62
63
64
65 private boolean isLastChunk;
66
67
68
69
70 private final List<InterfaceHttpData> bodyListHttpData = new ArrayList<InterfaceHttpData>();
71
72
73
74
75 private final Map<String, List<InterfaceHttpData>> bodyMapHttpData = new TreeMap<String, List<InterfaceHttpData>>(
76 CaseIgnoringComparator.INSTANCE);
77
78
79
80
81 private ChannelBuffer undecodedChunk;
82
83
84
85
86 private int bodyListHttpDataRank;
87
88
89
90
91 private String multipartDataBoundary;
92
93
94
95
96
97 private String multipartMixedBoundary;
98
99
100
101
102 private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED;
103
104
105
106
107 private Map<String, Attribute> currentFieldAttributes;
108
109
110
111
112 private FileUpload currentFileUpload;
113
114
115
116
117 private Attribute currentAttribute;
118
119
120
121
122
123
124
125
126 public HttpPostMultipartRequestDecoder(HttpRequest request)
127 throws ErrorDataDecoderException, IncompatibleDataDecoderException {
128 this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE),
129 request, HttpConstants.DEFAULT_CHARSET);
130 }
131
132
133
134
135
136
137
138
139
140 public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request)
141 throws ErrorDataDecoderException, IncompatibleDataDecoderException {
142 this(factory, request, HttpConstants.DEFAULT_CHARSET);
143 }
144
145
146
147
148
149
150
151
152
153
154 public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request,
155 Charset charset) throws ErrorDataDecoderException,
156 IncompatibleDataDecoderException {
157 if (factory == null) {
158 throw new NullPointerException("factory");
159 }
160 if (request == null) {
161 throw new NullPointerException("request");
162 }
163 if (charset == null) {
164 throw new NullPointerException("charset");
165 }
166 this.request = request;
167 this.charset = charset;
168 this.factory = factory;
169
170 setMultipart(this.request.headers().get(HttpHeaders.Names.CONTENT_TYPE));
171 if (!this.request.isChunked()) {
172 undecodedChunk = this.request.getContent();
173 isLastChunk = true;
174 parseBody();
175 }
176 }
177
178
179
180
181 private void setMultipart(String contentType) throws ErrorDataDecoderException {
182 String[] dataBoundary = HttpPostRequestDecoder.getMultipartDataBoundary(contentType);
183 if (dataBoundary != null) {
184 multipartDataBoundary = dataBoundary[0];
185 if (dataBoundary.length > 1 && dataBoundary[1] != null) {
186 charset = Charset.forName(dataBoundary[1]);
187 }
188 } else {
189 multipartDataBoundary = null;
190 }
191 currentStatus = MultiPartStatus.HEADERDELIMITER;
192 }
193
194 public boolean isMultipart() {
195 return true;
196 }
197
198 public List<InterfaceHttpData> getBodyHttpDatas()
199 throws NotEnoughDataDecoderException {
200 if (!isLastChunk) {
201 throw new NotEnoughDataDecoderException();
202 }
203 return bodyListHttpData;
204 }
205
206 public List<InterfaceHttpData> getBodyHttpDatas(String name)
207 throws NotEnoughDataDecoderException {
208 if (!isLastChunk) {
209 throw new NotEnoughDataDecoderException();
210 }
211 return bodyMapHttpData.get(name);
212 }
213
214 public InterfaceHttpData getBodyHttpData(String name)
215 throws NotEnoughDataDecoderException {
216 if (!isLastChunk) {
217 throw new NotEnoughDataDecoderException();
218 }
219 List<InterfaceHttpData> list = bodyMapHttpData.get(name);
220 if (list != null) {
221 return list.get(0);
222 }
223 return null;
224 }
225
226 public void offer(HttpChunk chunk) throws ErrorDataDecoderException {
227 ChannelBuffer chunked = chunk.getContent();
228 if (undecodedChunk == null) {
229 undecodedChunk = chunked;
230 } else {
231
232
233 undecodedChunk = ChannelBuffers.wrappedBuffer(
234 undecodedChunk, chunked);
235 }
236 if (chunk.isLast()) {
237 isLastChunk = true;
238 }
239 parseBody();
240 }
241
242 public boolean hasNext() throws EndOfDataDecoderException {
243 if (currentStatus == MultiPartStatus.EPILOGUE) {
244
245 if (bodyListHttpDataRank >= bodyListHttpData.size()) {
246 throw new EndOfDataDecoderException();
247 }
248 }
249 return !bodyListHttpData.isEmpty() && bodyListHttpDataRank < bodyListHttpData.size();
250 }
251
252 public InterfaceHttpData next() throws EndOfDataDecoderException {
253 if (hasNext()) {
254 return bodyListHttpData.get(bodyListHttpDataRank++);
255 }
256 return null;
257 }
258
259
260
261
262
263
264 private void parseBody() throws ErrorDataDecoderException {
265 if (currentStatus == MultiPartStatus.PREEPILOGUE ||
266 currentStatus == MultiPartStatus.EPILOGUE) {
267 if (isLastChunk) {
268 currentStatus = MultiPartStatus.EPILOGUE;
269 }
270 return;
271 }
272 parseBodyMultipart();
273 }
274
275
276
277
278 private void addHttpData(InterfaceHttpData data) {
279 if (data == null) {
280 return;
281 }
282 List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
283 if (datas == null) {
284 datas = new ArrayList<InterfaceHttpData>(1);
285 bodyMapHttpData.put(data.getName(), datas);
286 }
287 datas.add(data);
288 bodyListHttpData.add(data);
289 }
290
291
292
293
294
295
296 private void parseBodyMultipart() throws ErrorDataDecoderException {
297 if (undecodedChunk == null || undecodedChunk.readableBytes() == 0) {
298
299 return;
300 }
301 InterfaceHttpData data = decodeMultipart(currentStatus);
302 while (data != null) {
303 addHttpData(data);
304 if (currentStatus == MultiPartStatus.PREEPILOGUE ||
305 currentStatus == MultiPartStatus.EPILOGUE) {
306 break;
307 }
308 data = decodeMultipart(currentStatus);
309 }
310 }
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327 private InterfaceHttpData decodeMultipart(MultiPartStatus state)
328 throws ErrorDataDecoderException {
329 switch (state) {
330 case NOTSTARTED:
331 throw new ErrorDataDecoderException(
332 "Should not be called with the current status");
333 case PREAMBLE:
334
335 throw new ErrorDataDecoderException(
336 "Should not be called with the current status");
337 case HEADERDELIMITER: {
338
339 return findMultipartDelimiter(multipartDataBoundary,
340 MultiPartStatus.DISPOSITION, MultiPartStatus.PREEPILOGUE);
341 }
342 case DISPOSITION: {
343
344
345
346
347
348
349
350
351
352 return findMultipartDisposition();
353 }
354 case FIELD: {
355
356 Charset localCharset = null;
357 Attribute charsetAttribute = currentFieldAttributes
358 .get(HttpHeaders.Values.CHARSET);
359 if (charsetAttribute != null) {
360 try {
361 localCharset = Charset.forName(charsetAttribute.getValue());
362 } catch (IOException e) {
363 throw new ErrorDataDecoderException(e);
364 }
365 }
366 Attribute nameAttribute = currentFieldAttributes
367 .get(HttpPostBodyUtil.NAME);
368 if (currentAttribute == null) {
369 try {
370 currentAttribute = factory.createAttribute(request,
371 cleanString(nameAttribute.getValue()));
372 } catch (NullPointerException e) {
373 throw new ErrorDataDecoderException(e);
374 } catch (IllegalArgumentException e) {
375 throw new ErrorDataDecoderException(e);
376 } catch (IOException e) {
377 throw new ErrorDataDecoderException(e);
378 }
379 if (localCharset != null) {
380 currentAttribute.setCharset(localCharset);
381 }
382 }
383
384 try {
385 loadFieldMultipart(multipartDataBoundary);
386 } catch (NotEnoughDataDecoderException e) {
387 return null;
388 }
389 Attribute finalAttribute = currentAttribute;
390 currentAttribute = null;
391 currentFieldAttributes = null;
392
393 currentStatus = MultiPartStatus.HEADERDELIMITER;
394 return finalAttribute;
395 }
396 case FILEUPLOAD: {
397
398 return getFileUpload(multipartDataBoundary);
399 }
400 case MIXEDDELIMITER: {
401
402
403 return findMultipartDelimiter(multipartMixedBoundary,
404 MultiPartStatus.MIXEDDISPOSITION,
405 MultiPartStatus.HEADERDELIMITER);
406 }
407 case MIXEDDISPOSITION: {
408 return findMultipartDisposition();
409 }
410 case MIXEDFILEUPLOAD: {
411
412 return getFileUpload(multipartMixedBoundary);
413 }
414 case PREEPILOGUE:
415 return null;
416 case EPILOGUE:
417 return null;
418 default:
419 throw new ErrorDataDecoderException("Shouldn't reach here.");
420 }
421 }
422
423
424
425
426
427 void skipControlCharacters() throws NotEnoughDataDecoderException {
428 SeekAheadOptimize sao;
429 try {
430 sao = new SeekAheadOptimize(undecodedChunk);
431 } catch (SeekAheadNoBackArrayException e) {
432 try {
433 skipControlCharactersStandard();
434 } catch (IndexOutOfBoundsException e1) {
435 throw new NotEnoughDataDecoderException(e1);
436 }
437 return;
438 }
439
440 while (sao.pos < sao.limit) {
441 char c = (char) (sao.bytes[sao.pos ++] & 0xFF);
442 if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
443 sao.setReadPosition(1);
444 return;
445 }
446 }
447 throw new NotEnoughDataDecoderException("Access out of bounds");
448 }
449 void skipControlCharactersStandard() {
450 for (;;) {
451 char c = (char) undecodedChunk.readUnsignedByte();
452 if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
453 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
454 break;
455 }
456 }
457 }
458
459
460
461
462
463
464
465
466
467 private InterfaceHttpData findMultipartDelimiter(String delimiter,
468 MultiPartStatus dispositionStatus,
469 MultiPartStatus closeDelimiterStatus)
470 throws ErrorDataDecoderException {
471
472 int readerIndex = undecodedChunk.readerIndex();
473 try {
474 skipControlCharacters();
475 } catch (NotEnoughDataDecoderException e1) {
476 undecodedChunk.readerIndex(readerIndex);
477 return null;
478 }
479 skipOneLine();
480 String newline;
481 try {
482 newline = readDelimiter(delimiter);
483 } catch (NotEnoughDataDecoderException e) {
484 undecodedChunk.readerIndex(readerIndex);
485 return null;
486 }
487 if (newline.equals(delimiter)) {
488 currentStatus = dispositionStatus;
489 return decodeMultipart(dispositionStatus);
490 }
491 if (newline.equals(delimiter + "--")) {
492
493 currentStatus = closeDelimiterStatus;
494 if (currentStatus == MultiPartStatus.HEADERDELIMITER) {
495
496
497 currentFieldAttributes = null;
498 return decodeMultipart(MultiPartStatus.HEADERDELIMITER);
499 }
500 return null;
501 }
502 undecodedChunk.readerIndex(readerIndex);
503 throw new ErrorDataDecoderException("No Multipart delimiter found");
504 }
505
506
507
508
509
510
511 private InterfaceHttpData findMultipartDisposition()
512 throws ErrorDataDecoderException {
513 int readerIndex = undecodedChunk.readerIndex();
514 if (currentStatus == MultiPartStatus.DISPOSITION) {
515 currentFieldAttributes = new TreeMap<String, Attribute>(
516 CaseIgnoringComparator.INSTANCE);
517 }
518
519 while (!skipOneLine()) {
520 String newline;
521 try {
522 skipControlCharacters();
523 newline = readLine();
524 } catch (NotEnoughDataDecoderException e) {
525 undecodedChunk.readerIndex(readerIndex);
526 return null;
527 }
528 String[] contents = splitMultipartHeader(newline);
529 if (contents[0].equalsIgnoreCase(HttpPostBodyUtil.CONTENT_DISPOSITION)) {
530 boolean checkSecondArg;
531 if (currentStatus == MultiPartStatus.DISPOSITION) {
532 checkSecondArg = contents[1]
533 .equalsIgnoreCase(HttpPostBodyUtil.FORM_DATA);
534 } else {
535 checkSecondArg = contents[1]
536 .equalsIgnoreCase(HttpPostBodyUtil.ATTACHMENT) ||
537 contents[1]
538 .equalsIgnoreCase(HttpPostBodyUtil.FILE);
539 }
540 if (checkSecondArg) {
541
542 for (int i = 2; i < contents.length; i ++) {
543 String[] values = StringUtil.split(contents[i], '=', 2);
544 Attribute attribute;
545 try {
546 String name = cleanString(values[0]);
547 String value = values[1];
548
549
550 if (HttpPostBodyUtil.FILENAME.equals(name)) {
551
552 value = value.substring(1, value.length() - 1);
553 } else {
554
555 value = cleanString(value);
556 }
557 attribute = factory.createAttribute(request, name, value);
558 } catch (NullPointerException e) {
559 throw new ErrorDataDecoderException(e);
560 } catch (IllegalArgumentException e) {
561 throw new ErrorDataDecoderException(e);
562 }
563 currentFieldAttributes.put(attribute.getName(),
564 attribute);
565 }
566 }
567 } else if (contents[0]
568 .equalsIgnoreCase(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING)) {
569 Attribute attribute;
570 try {
571 attribute = factory.createAttribute(request,
572 HttpHeaders.Names.CONTENT_TRANSFER_ENCODING,
573 cleanString(contents[1]));
574 } catch (NullPointerException e) {
575 throw new ErrorDataDecoderException(e);
576 } catch (IllegalArgumentException e) {
577 throw new ErrorDataDecoderException(e);
578 }
579 currentFieldAttributes.put(
580 HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, attribute);
581 } else if (contents[0]
582 .equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH)) {
583 Attribute attribute;
584 try {
585 attribute = factory.createAttribute(request,
586 HttpHeaders.Names.CONTENT_LENGTH,
587 cleanString(contents[1]));
588 } catch (NullPointerException e) {
589 throw new ErrorDataDecoderException(e);
590 } catch (IllegalArgumentException e) {
591 throw new ErrorDataDecoderException(e);
592 }
593 currentFieldAttributes.put(HttpHeaders.Names.CONTENT_LENGTH,
594 attribute);
595 } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_TYPE)) {
596
597 if (contents[1].equalsIgnoreCase(HttpPostBodyUtil.MULTIPART_MIXED)) {
598 if (currentStatus == MultiPartStatus.DISPOSITION) {
599 String values = StringUtil.substringAfter(contents[2], '=');
600 multipartMixedBoundary = "--" + values;
601 currentStatus = MultiPartStatus.MIXEDDELIMITER;
602 return decodeMultipart(MultiPartStatus.MIXEDDELIMITER);
603 } else {
604 throw new ErrorDataDecoderException(
605 "Mixed Multipart found in a previous Mixed Multipart");
606 }
607 } else {
608 for (int i = 1; i < contents.length; i ++) {
609 if (contents[i].toLowerCase().startsWith(
610 HttpHeaders.Values.CHARSET)) {
611 String values = StringUtil.substringAfter(contents[i], '=');
612 Attribute attribute;
613 try {
614 attribute = factory.createAttribute(request,
615 HttpHeaders.Values.CHARSET,
616 cleanString(values));
617 } catch (NullPointerException e) {
618 throw new ErrorDataDecoderException(e);
619 } catch (IllegalArgumentException e) {
620 throw new ErrorDataDecoderException(e);
621 }
622 currentFieldAttributes.put(HttpHeaders.Values.CHARSET,
623 attribute);
624 } else {
625 Attribute attribute;
626 try {
627 attribute = factory.createAttribute(request,
628 cleanString(contents[0]), contents[i]);
629 } catch (NullPointerException e) {
630 throw new ErrorDataDecoderException(e);
631 } catch (IllegalArgumentException e) {
632 throw new ErrorDataDecoderException(e);
633 }
634 currentFieldAttributes.put(attribute.getName(),
635 attribute);
636 }
637 }
638 }
639 } else {
640 throw new ErrorDataDecoderException("Unknown Params: " +
641 newline);
642 }
643 }
644
645 Attribute filenameAttribute = currentFieldAttributes
646 .get(HttpPostBodyUtil.FILENAME);
647 if (currentStatus == MultiPartStatus.DISPOSITION) {
648 if (filenameAttribute != null) {
649
650 currentStatus = MultiPartStatus.FILEUPLOAD;
651
652 return decodeMultipart(MultiPartStatus.FILEUPLOAD);
653 } else {
654
655 currentStatus = MultiPartStatus.FIELD;
656
657 return decodeMultipart(MultiPartStatus.FIELD);
658 }
659 } else {
660 if (filenameAttribute != null) {
661
662 currentStatus = MultiPartStatus.MIXEDFILEUPLOAD;
663
664 return decodeMultipart(MultiPartStatus.MIXEDFILEUPLOAD);
665 } else {
666
667 throw new ErrorDataDecoderException("Filename not found");
668 }
669 }
670 }
671
672
673
674
675
676
677
678 private InterfaceHttpData getFileUpload(String delimiter)
679 throws ErrorDataDecoderException {
680
681
682 Attribute encoding = currentFieldAttributes
683 .get(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING);
684 Charset localCharset = charset;
685
686 TransferEncodingMechanism mechanism = TransferEncodingMechanism.BIT7;
687 if (encoding != null) {
688 String code;
689 try {
690 code = encoding.getValue().toLowerCase();
691 } catch (IOException e) {
692 throw new ErrorDataDecoderException(e);
693 }
694 if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT7.value())) {
695 localCharset = HttpPostBodyUtil.US_ASCII;
696 } else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT8.value())) {
697 localCharset = HttpPostBodyUtil.ISO_8859_1;
698 mechanism = TransferEncodingMechanism.BIT8;
699 } else if (code
700 .equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value())) {
701
702 mechanism = TransferEncodingMechanism.BINARY;
703 } else {
704 throw new ErrorDataDecoderException(
705 "TransferEncoding Unknown: " + code);
706 }
707 }
708 Attribute charsetAttribute = currentFieldAttributes
709 .get(HttpHeaders.Values.CHARSET);
710 if (charsetAttribute != null) {
711 try {
712 localCharset = Charset.forName(charsetAttribute.getValue());
713 } catch (IOException e) {
714 throw new ErrorDataDecoderException(e);
715 }
716 }
717 if (currentFileUpload == null) {
718 Attribute filenameAttribute = currentFieldAttributes
719 .get(HttpPostBodyUtil.FILENAME);
720 Attribute nameAttribute = currentFieldAttributes
721 .get(HttpPostBodyUtil.NAME);
722 Attribute contentTypeAttribute = currentFieldAttributes
723 .get(HttpHeaders.Names.CONTENT_TYPE);
724 if (contentTypeAttribute == null) {
725
726 contentTypeAttribute = new MemoryAttribute(HttpHeaders.Names.CONTENT_TYPE);
727 try {
728 contentTypeAttribute.setValue(HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE);
729 } catch (IOException e) {
730 throw new ErrorDataDecoderException(
731 "Content-Type is absent but required, and cannot be reverted to default");
732 }
733 }
734 Attribute lengthAttribute = currentFieldAttributes
735 .get(HttpHeaders.Names.CONTENT_LENGTH);
736 long size;
737 try {
738 size = lengthAttribute != null? Long.parseLong(lengthAttribute
739 .getValue()) : 0L;
740 } catch (IOException e) {
741 throw new ErrorDataDecoderException(e);
742 } catch (NumberFormatException e) {
743 size = 0;
744 }
745 try {
746 currentFileUpload = factory.createFileUpload(
747 request,
748 cleanString(nameAttribute.getValue()), cleanString(filenameAttribute.getValue()),
749 contentTypeAttribute.getValue(), mechanism.value(),
750 localCharset, size);
751 } catch (NullPointerException e) {
752 throw new ErrorDataDecoderException(e);
753 } catch (IllegalArgumentException e) {
754 throw new ErrorDataDecoderException(e);
755 } catch (IOException e) {
756 throw new ErrorDataDecoderException(e);
757 }
758 }
759
760 try {
761 readFileUploadByteMultipart(delimiter);
762 } catch (NotEnoughDataDecoderException e) {
763
764
765
766 return null;
767 }
768 if (currentFileUpload.isCompleted()) {
769
770 if (currentStatus == MultiPartStatus.FILEUPLOAD) {
771 currentStatus = MultiPartStatus.HEADERDELIMITER;
772 currentFieldAttributes = null;
773 } else {
774 currentStatus = MultiPartStatus.MIXEDDELIMITER;
775 cleanMixedAttributes();
776 }
777 FileUpload fileUpload = currentFileUpload;
778 currentFileUpload = null;
779 return fileUpload;
780 }
781
782
783
784 return null;
785 }
786
787 public void cleanFiles() {
788 factory.cleanRequestHttpDatas(request);
789 }
790
791 public void removeHttpDataFromClean(InterfaceHttpData data) {
792 factory.removeHttpDataFromClean(request, data);
793 }
794
795
796
797
798 private void cleanMixedAttributes() {
799 currentFieldAttributes.remove(HttpHeaders.Values.CHARSET);
800 currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_LENGTH);
801 currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING);
802 currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_TYPE);
803 currentFieldAttributes.remove(HttpPostBodyUtil.FILENAME);
804 }
805
806
807
808
809
810
811
812 private String readLineStandard() throws NotEnoughDataDecoderException {
813 int readerIndex = undecodedChunk.readerIndex();
814 try {
815 ChannelBuffer line = ChannelBuffers.dynamicBuffer(64);
816
817 while (undecodedChunk.readable()) {
818 byte nextByte = undecodedChunk.readByte();
819 if (nextByte == HttpConstants.CR) {
820
821 nextByte = undecodedChunk.getByte(undecodedChunk.readerIndex());
822 if (nextByte == HttpConstants.LF) {
823
824 undecodedChunk.readByte();
825 return line.toString(charset);
826 } else {
827
828 line.writeByte(HttpConstants.CR);
829 }
830 } else if (nextByte == HttpConstants.LF) {
831 return line.toString(charset);
832 } else {
833 line.writeByte(nextByte);
834 }
835 }
836 } catch (IndexOutOfBoundsException e) {
837 undecodedChunk.readerIndex(readerIndex);
838 throw new NotEnoughDataDecoderException(e);
839 }
840 undecodedChunk.readerIndex(readerIndex);
841 throw new NotEnoughDataDecoderException();
842 }
843
844
845
846
847
848
849 private String readLine() throws NotEnoughDataDecoderException {
850 SeekAheadOptimize sao;
851 try {
852 sao = new SeekAheadOptimize(undecodedChunk);
853 } catch (SeekAheadNoBackArrayException e1) {
854 return readLineStandard();
855 }
856 int readerIndex = undecodedChunk.readerIndex();
857 try {
858 ChannelBuffer line = ChannelBuffers.dynamicBuffer(64);
859 while (sao.pos < sao.limit) {
860 byte nextByte = sao.bytes[sao.pos ++];
861 if (nextByte == HttpConstants.CR) {
862 if (sao.pos < sao.limit) {
863 nextByte = sao.bytes[sao.pos ++];
864 if (nextByte == HttpConstants.LF) {
865 sao.setReadPosition(0);
866 return line.toString(charset);
867 } else {
868
869 sao.pos--;
870 line.writeByte(HttpConstants.CR);
871 }
872 } else {
873 line.writeByte(nextByte);
874 }
875 } else if (nextByte == HttpConstants.LF) {
876 sao.setReadPosition(0);
877 return line.toString(charset);
878 } else {
879 line.writeByte(nextByte);
880 }
881 }
882 } catch (IndexOutOfBoundsException e) {
883 undecodedChunk.readerIndex(readerIndex);
884 throw new NotEnoughDataDecoderException(e);
885 }
886 undecodedChunk.readerIndex(readerIndex);
887 throw new NotEnoughDataDecoderException();
888 }
889
890
891
892
893
894
895
896
897
898
899
900
901 private String readDelimiterStandard(String delimiter) throws NotEnoughDataDecoderException {
902 int readerIndex = undecodedChunk.readerIndex();
903 try {
904 StringBuilder sb = new StringBuilder(64);
905 int delimiterPos = 0;
906 int len = delimiter.length();
907 while (undecodedChunk.readable() && delimiterPos < len) {
908 byte nextByte = undecodedChunk.readByte();
909 if (nextByte == delimiter.charAt(delimiterPos)) {
910 delimiterPos++;
911 sb.append((char) nextByte);
912 } else {
913
914 undecodedChunk.readerIndex(readerIndex);
915 throw new NotEnoughDataDecoderException();
916 }
917 }
918
919 if (undecodedChunk.readable()) {
920 byte nextByte = undecodedChunk.readByte();
921
922 if (nextByte == HttpConstants.CR) {
923 nextByte = undecodedChunk.readByte();
924 if (nextByte == HttpConstants.LF) {
925 return sb.toString();
926 } else {
927
928
929 undecodedChunk.readerIndex(readerIndex);
930 throw new NotEnoughDataDecoderException();
931 }
932 } else if (nextByte == HttpConstants.LF) {
933 return sb.toString();
934 } else if (nextByte == '-') {
935 sb.append('-');
936
937 nextByte = undecodedChunk.readByte();
938 if (nextByte == '-') {
939 sb.append('-');
940
941 if (undecodedChunk.readable()) {
942 nextByte = undecodedChunk.readByte();
943 if (nextByte == HttpConstants.CR) {
944 nextByte = undecodedChunk.readByte();
945 if (nextByte == HttpConstants.LF) {
946 return sb.toString();
947 } else {
948
949
950 undecodedChunk.readerIndex(readerIndex);
951 throw new NotEnoughDataDecoderException();
952 }
953 } else if (nextByte == HttpConstants.LF) {
954 return sb.toString();
955 } else {
956
957
958 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
959 return sb.toString();
960 }
961 }
962
963
964
965 return sb.toString();
966 }
967
968
969 }
970 }
971 } catch (IndexOutOfBoundsException e) {
972 undecodedChunk.readerIndex(readerIndex);
973 throw new NotEnoughDataDecoderException(e);
974 }
975 undecodedChunk.readerIndex(readerIndex);
976 throw new NotEnoughDataDecoderException();
977 }
978
979
980
981
982
983
984
985
986
987
988
989 private String readDelimiter(String delimiter) throws NotEnoughDataDecoderException {
990 SeekAheadOptimize sao;
991 try {
992 sao = new SeekAheadOptimize(undecodedChunk);
993 } catch (SeekAheadNoBackArrayException e1) {
994 return readDelimiterStandard(delimiter);
995 }
996 int readerIndex = undecodedChunk.readerIndex();
997 int delimiterPos = 0;
998 int len = delimiter.length();
999 try {
1000 StringBuilder sb = new StringBuilder(64);
1001
1002 while (sao.pos < sao.limit && delimiterPos < len) {
1003 byte nextByte = sao.bytes[sao.pos ++];
1004 if (nextByte == delimiter.charAt(delimiterPos)) {
1005 delimiterPos++;
1006 sb.append((char) nextByte);
1007 } else {
1008
1009 undecodedChunk.readerIndex(readerIndex);
1010 throw new NotEnoughDataDecoderException();
1011 }
1012 }
1013
1014 if (sao.pos < sao.limit) {
1015 byte nextByte = sao.bytes[sao.pos ++];
1016 if (nextByte == HttpConstants.CR) {
1017
1018 if (sao.pos < sao.limit) {
1019 nextByte = sao.bytes[sao.pos ++];
1020 if (nextByte == HttpConstants.LF) {
1021 sao.setReadPosition(0);
1022 return sb.toString();
1023 } else {
1024
1025
1026 undecodedChunk.readerIndex(readerIndex);
1027 throw new NotEnoughDataDecoderException();
1028 }
1029 } else {
1030
1031
1032 undecodedChunk.readerIndex(readerIndex);
1033 throw new NotEnoughDataDecoderException();
1034 }
1035 } else if (nextByte == HttpConstants.LF) {
1036
1037 sao.setReadPosition(0);
1038 return sb.toString();
1039 } else if (nextByte == '-') {
1040 sb.append('-');
1041
1042 if (sao.pos < sao.limit) {
1043 nextByte = sao.bytes[sao.pos ++];
1044 if (nextByte == '-') {
1045 sb.append('-');
1046
1047 if (sao.pos < sao.limit) {
1048 nextByte = sao.bytes[sao.pos++];
1049 if (nextByte == HttpConstants.CR) {
1050 if (sao.pos < sao.limit) {
1051 nextByte = sao.bytes[sao.pos++];
1052 if (nextByte == HttpConstants.LF) {
1053 sao.setReadPosition(0);
1054 return sb.toString();
1055 } else {
1056
1057
1058 undecodedChunk.readerIndex(readerIndex);
1059 throw new NotEnoughDataDecoderException();
1060 }
1061 } else {
1062
1063
1064 undecodedChunk.readerIndex(readerIndex);
1065 throw new NotEnoughDataDecoderException();
1066 }
1067 } else if (nextByte == HttpConstants.LF) {
1068 sao.setReadPosition(0);
1069 return sb.toString();
1070 } else {
1071
1072
1073 sao.setReadPosition(1);
1074 return sb.toString();
1075 }
1076 }
1077
1078
1079
1080 sao.setReadPosition(0);
1081 return sb.toString();
1082 }
1083
1084
1085 }
1086 }
1087 }
1088 } catch (IndexOutOfBoundsException e) {
1089 undecodedChunk.readerIndex(readerIndex);
1090 throw new NotEnoughDataDecoderException(e);
1091 }
1092 undecodedChunk.readerIndex(readerIndex);
1093 throw new NotEnoughDataDecoderException();
1094 }
1095
1096
1097
1098
1099
1100
1101
1102
1103 private void readFileUploadByteMultipartStandard(String delimiter)
1104 throws NotEnoughDataDecoderException, ErrorDataDecoderException {
1105 int readerIndex = undecodedChunk.readerIndex();
1106
1107 boolean newLine = true;
1108 int index = 0;
1109 int lastPosition = undecodedChunk.readerIndex();
1110 boolean found = false;
1111 while (undecodedChunk.readable()) {
1112 byte nextByte = undecodedChunk.readByte();
1113 if (newLine) {
1114
1115 if (nextByte == delimiter.codePointAt(index)) {
1116 index ++;
1117 if (delimiter.length() == index) {
1118 found = true;
1119 break;
1120 }
1121 continue;
1122 } else {
1123 newLine = false;
1124 index = 0;
1125
1126 if (nextByte == HttpConstants.CR) {
1127 if (undecodedChunk.readable()) {
1128 nextByte = undecodedChunk.readByte();
1129 if (nextByte == HttpConstants.LF) {
1130 newLine = true;
1131 index = 0;
1132 lastPosition = undecodedChunk.readerIndex() - 2;
1133 } else {
1134
1135 lastPosition = undecodedChunk.readerIndex() - 1;
1136
1137
1138 undecodedChunk.readerIndex(lastPosition);
1139 }
1140 }
1141 } else if (nextByte == HttpConstants.LF) {
1142 newLine = true;
1143 index = 0;
1144 lastPosition = undecodedChunk.readerIndex() - 1;
1145 } else {
1146
1147 lastPosition = undecodedChunk.readerIndex();
1148 }
1149 }
1150 } else {
1151
1152 if (nextByte == HttpConstants.CR) {
1153 if (undecodedChunk.readable()) {
1154 nextByte = undecodedChunk.readByte();
1155 if (nextByte == HttpConstants.LF) {
1156 newLine = true;
1157 index = 0;
1158 lastPosition = undecodedChunk.readerIndex() - 2;
1159 } else {
1160
1161 lastPosition = undecodedChunk.readerIndex() - 1;
1162
1163
1164 undecodedChunk.readerIndex(lastPosition);
1165 }
1166 }
1167 } else if (nextByte == HttpConstants.LF) {
1168 newLine = true;
1169 index = 0;
1170 lastPosition = undecodedChunk.readerIndex() - 1;
1171 } else {
1172
1173 lastPosition = undecodedChunk.readerIndex();
1174 }
1175 }
1176 }
1177 ChannelBuffer buffer = undecodedChunk.slice(readerIndex, lastPosition -
1178 readerIndex);
1179 if (found) {
1180
1181 try {
1182 currentFileUpload.addContent(buffer, true);
1183
1184 undecodedChunk.readerIndex(lastPosition);
1185 } catch (IOException e) {
1186 throw new ErrorDataDecoderException(e);
1187 }
1188 } else {
1189
1190 try {
1191 currentFileUpload.addContent(buffer, false);
1192
1193 undecodedChunk.readerIndex(lastPosition);
1194 throw new NotEnoughDataDecoderException();
1195 } catch (IOException e) {
1196 throw new ErrorDataDecoderException(e);
1197 }
1198 }
1199 }
1200
1201
1202
1203
1204
1205
1206
1207
1208 private void readFileUploadByteMultipart(String delimiter)
1209 throws NotEnoughDataDecoderException, ErrorDataDecoderException {
1210 SeekAheadOptimize sao;
1211 try {
1212 sao = new SeekAheadOptimize(undecodedChunk);
1213 } catch (SeekAheadNoBackArrayException e1) {
1214 readFileUploadByteMultipartStandard(delimiter);
1215 return;
1216 }
1217 int readerIndex = undecodedChunk.readerIndex();
1218
1219 boolean newLine = true;
1220 int index = 0;
1221 int lastrealpos = sao.pos;
1222 int lastPosition;
1223 boolean found = false;
1224
1225 while (sao.pos < sao.limit) {
1226 byte nextByte = sao.bytes[sao.pos ++];
1227 if (newLine) {
1228
1229 if (nextByte == delimiter.codePointAt(index)) {
1230 index ++;
1231 if (delimiter.length() == index) {
1232 found = true;
1233 break;
1234 }
1235 continue;
1236 } else {
1237 newLine = false;
1238 index = 0;
1239
1240 if (nextByte == HttpConstants.CR) {
1241 if (sao.pos < sao.limit) {
1242 nextByte = sao.bytes[sao.pos ++];
1243 if (nextByte == HttpConstants.LF) {
1244 newLine = true;
1245 index = 0;
1246 lastrealpos = sao.pos - 2;
1247 } else {
1248
1249 sao.pos--;
1250
1251
1252 lastrealpos = sao.pos;
1253 }
1254 }
1255 } else if (nextByte == HttpConstants.LF) {
1256 newLine = true;
1257 index = 0;
1258 lastrealpos = sao.pos - 1;
1259 } else {
1260
1261 lastrealpos = sao.pos;
1262 }
1263 }
1264 } else {
1265
1266 if (nextByte == HttpConstants.CR) {
1267 if (sao.pos < sao.limit) {
1268 nextByte = sao.bytes[sao.pos ++];
1269 if (nextByte == HttpConstants.LF) {
1270 newLine = true;
1271 index = 0;
1272 lastrealpos = sao.pos - 2;
1273 } else {
1274
1275 sao.pos--;
1276
1277
1278 lastrealpos = sao.pos;
1279 }
1280 }
1281 } else if (nextByte == HttpConstants.LF) {
1282 newLine = true;
1283 index = 0;
1284 lastrealpos = sao.pos - 1;
1285 } else {
1286
1287 lastrealpos = sao.pos;
1288 }
1289 }
1290 }
1291 lastPosition = sao.getReadPosition(lastrealpos);
1292 ChannelBuffer buffer = undecodedChunk.slice(readerIndex, lastPosition - readerIndex);
1293 if (found) {
1294
1295 try {
1296 currentFileUpload.addContent(buffer, true);
1297
1298 undecodedChunk.readerIndex(lastPosition);
1299 } catch (IOException e) {
1300 throw new ErrorDataDecoderException(e);
1301 }
1302 } else {
1303
1304 try {
1305 currentFileUpload.addContent(buffer, false);
1306
1307 undecodedChunk.readerIndex(lastPosition);
1308 throw new NotEnoughDataDecoderException();
1309 } catch (IOException e) {
1310 throw new ErrorDataDecoderException(e);
1311 }
1312 }
1313 }
1314
1315
1316
1317
1318
1319
1320 private void loadFieldMultipartStandard(String delimiter)
1321 throws NotEnoughDataDecoderException, ErrorDataDecoderException {
1322 int readerIndex = undecodedChunk.readerIndex();
1323 try {
1324
1325 boolean newLine = true;
1326 int index = 0;
1327 int lastPosition = undecodedChunk.readerIndex();
1328 boolean found = false;
1329 while (undecodedChunk.readable()) {
1330 byte nextByte = undecodedChunk.readByte();
1331 if (newLine) {
1332
1333 if (nextByte == delimiter.codePointAt(index)) {
1334 index ++;
1335 if (delimiter.length() == index) {
1336 found = true;
1337 break;
1338 }
1339 continue;
1340 } else {
1341 newLine = false;
1342 index = 0;
1343
1344 if (nextByte == HttpConstants.CR) {
1345 if (undecodedChunk.readable()) {
1346 nextByte = undecodedChunk.readByte();
1347 if (nextByte == HttpConstants.LF) {
1348 newLine = true;
1349 index = 0;
1350 lastPosition = undecodedChunk.readerIndex() - 2;
1351 } else {
1352
1353 lastPosition = undecodedChunk.readerIndex() - 1;
1354 undecodedChunk.readerIndex(lastPosition);
1355 }
1356 } else {
1357 lastPosition = undecodedChunk.readerIndex() - 1;
1358 }
1359 } else if (nextByte == HttpConstants.LF) {
1360 newLine = true;
1361 index = 0;
1362 lastPosition = undecodedChunk.readerIndex() - 1;
1363 } else {
1364 lastPosition = undecodedChunk.readerIndex();
1365 }
1366 }
1367 } else {
1368
1369 if (nextByte == HttpConstants.CR) {
1370 if (undecodedChunk.readable()) {
1371 nextByte = undecodedChunk.readByte();
1372 if (nextByte == HttpConstants.LF) {
1373 newLine = true;
1374 index = 0;
1375 lastPosition = undecodedChunk.readerIndex() - 2;
1376 } else {
1377
1378 lastPosition = undecodedChunk.readerIndex() - 1;
1379 undecodedChunk.readerIndex(lastPosition);
1380 }
1381 } else {
1382 lastPosition = undecodedChunk.readerIndex() - 1;
1383 }
1384 } else if (nextByte == HttpConstants.LF) {
1385 newLine = true;
1386 index = 0;
1387 lastPosition = undecodedChunk.readerIndex() - 1;
1388 } else {
1389 lastPosition = undecodedChunk.readerIndex();
1390 }
1391 }
1392 }
1393 if (found) {
1394
1395
1396
1397 try {
1398 currentAttribute.addContent(
1399 undecodedChunk.slice(readerIndex, lastPosition - readerIndex),
1400 true);
1401 } catch (IOException e) {
1402 throw new ErrorDataDecoderException(e);
1403 }
1404 undecodedChunk.readerIndex(lastPosition);
1405 } else {
1406 try {
1407 currentAttribute.addContent(
1408 undecodedChunk.slice(readerIndex, lastPosition - readerIndex),
1409 false);
1410 } catch (IOException e) {
1411 throw new ErrorDataDecoderException(e);
1412 }
1413 undecodedChunk.readerIndex(lastPosition);
1414 throw new NotEnoughDataDecoderException();
1415 }
1416 } catch (IndexOutOfBoundsException e) {
1417 undecodedChunk.readerIndex(readerIndex);
1418 throw new NotEnoughDataDecoderException(e);
1419 }
1420 }
1421
1422
1423
1424
1425
1426
1427 private void loadFieldMultipart(String delimiter)
1428 throws NotEnoughDataDecoderException, ErrorDataDecoderException {
1429 SeekAheadOptimize sao;
1430 try {
1431 sao = new SeekAheadOptimize(undecodedChunk);
1432 } catch (SeekAheadNoBackArrayException e1) {
1433 loadFieldMultipartStandard(delimiter);
1434 return;
1435 }
1436 int readerIndex = undecodedChunk.readerIndex();
1437 try {
1438
1439 boolean newLine = true;
1440 int index = 0;
1441 int lastPosition;
1442 int lastrealpos = sao.pos;
1443 boolean found = false;
1444
1445 while (sao.pos < sao.limit) {
1446 byte nextByte = sao.bytes[sao.pos ++];
1447 if (newLine) {
1448
1449 if (nextByte == delimiter.codePointAt(index)) {
1450 index ++;
1451 if (delimiter.length() == index) {
1452 found = true;
1453 break;
1454 }
1455 continue;
1456 } else {
1457 newLine = false;
1458 index = 0;
1459
1460 if (nextByte == HttpConstants.CR) {
1461 if (sao.pos < sao.limit) {
1462 nextByte = sao.bytes[sao.pos ++];
1463 if (nextByte == HttpConstants.LF) {
1464 newLine = true;
1465 index = 0;
1466 lastrealpos = sao.pos - 2;
1467 } else {
1468
1469 sao.pos--;
1470 lastrealpos = sao.pos;
1471 }
1472 }
1473 } else if (nextByte == HttpConstants.LF) {
1474 newLine = true;
1475 index = 0;
1476 lastrealpos = sao.pos - 1;
1477 } else {
1478 lastrealpos = sao.pos;
1479 }
1480 }
1481 } else {
1482
1483 if (nextByte == HttpConstants.CR) {
1484 if (sao.pos < sao.limit) {
1485 nextByte = sao.bytes[sao.pos ++];
1486 if (nextByte == HttpConstants.LF) {
1487 newLine = true;
1488 index = 0;
1489 lastrealpos = sao.pos - 2;
1490 } else {
1491
1492 sao.pos--;
1493 lastrealpos = sao.pos;
1494 }
1495 }
1496 } else if (nextByte == HttpConstants.LF) {
1497 newLine = true;
1498 index = 0;
1499 lastrealpos = sao.pos - 1;
1500 } else {
1501 lastrealpos = sao.pos;
1502 }
1503 }
1504 }
1505 lastPosition = sao.getReadPosition(lastrealpos);
1506 if (found) {
1507
1508
1509
1510 try {
1511 currentAttribute.addContent(
1512 undecodedChunk.slice(readerIndex, lastPosition - readerIndex), true);
1513 } catch (IOException e) {
1514 throw new ErrorDataDecoderException(e);
1515 }
1516 undecodedChunk.readerIndex(lastPosition);
1517 } else {
1518 try {
1519 currentAttribute.addContent(
1520 undecodedChunk.slice(readerIndex, lastPosition - readerIndex), false);
1521 } catch (IOException e) {
1522 throw new ErrorDataDecoderException(e);
1523 }
1524 undecodedChunk.readerIndex(lastPosition);
1525 throw new NotEnoughDataDecoderException();
1526 }
1527 } catch (IndexOutOfBoundsException e) {
1528 undecodedChunk.readerIndex(readerIndex);
1529 throw new NotEnoughDataDecoderException(e);
1530 }
1531 }
1532
1533
1534
1535
1536
1537 private static String cleanString(String field) {
1538 StringBuilder sb = new StringBuilder(field.length());
1539 for (int i = 0; i < field.length(); i ++) {
1540 char nextChar = field.charAt(i);
1541 if (nextChar == HttpConstants.COLON) {
1542 sb.append(HttpConstants.SP);
1543 } else if (nextChar == HttpConstants.COMMA) {
1544 sb.append(HttpConstants.SP);
1545 } else if (nextChar == HttpConstants.EQUALS) {
1546 sb.append(HttpConstants.SP);
1547 } else if (nextChar == HttpConstants.SEMICOLON) {
1548 sb.append(HttpConstants.SP);
1549 } else if (nextChar == HttpConstants.HT) {
1550 sb.append(HttpConstants.SP);
1551 } else if (nextChar == HttpConstants.DOUBLE_QUOTE) {
1552
1553 } else {
1554 sb.append(nextChar);
1555 }
1556 }
1557 return sb.toString().trim();
1558 }
1559
1560
1561
1562
1563
1564 private boolean skipOneLine() {
1565 if (!undecodedChunk.readable()) {
1566 return false;
1567 }
1568 byte nextByte = undecodedChunk.readByte();
1569 if (nextByte == HttpConstants.CR) {
1570 if (!undecodedChunk.readable()) {
1571 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
1572 return false;
1573 }
1574 nextByte = undecodedChunk.readByte();
1575 if (nextByte == HttpConstants.LF) {
1576 return true;
1577 }
1578 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 2);
1579 return false;
1580 }
1581 if (nextByte == HttpConstants.LF) {
1582 return true;
1583 }
1584 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
1585 return false;
1586 }
1587
1588
1589
1590
1591
1592
1593 private static String[] splitMultipartHeader(String sb) {
1594 ArrayList<String> headers = new ArrayList<String>(1);
1595 int nameStart;
1596 int nameEnd;
1597 int colonEnd;
1598 int valueStart;
1599 int valueEnd;
1600 nameStart = HttpPostBodyUtil.findNonWhitespace(sb, 0);
1601 for (nameEnd = nameStart; nameEnd < sb.length(); nameEnd ++) {
1602 char ch = sb.charAt(nameEnd);
1603 if (ch == ':' || Character.isWhitespace(ch)) {
1604 break;
1605 }
1606 }
1607 for (colonEnd = nameEnd; colonEnd < sb.length(); colonEnd ++) {
1608 if (sb.charAt(colonEnd) == ':') {
1609 colonEnd ++;
1610 break;
1611 }
1612 }
1613 valueStart = HttpPostBodyUtil.findNonWhitespace(sb, colonEnd);
1614 valueEnd = HttpPostBodyUtil.findEndOfString(sb);
1615 headers.add(sb.substring(nameStart, nameEnd));
1616 String svalue = sb.substring(valueStart, valueEnd);
1617 String[] values;
1618 if (svalue.indexOf(';') >= 0) {
1619 values = StringUtil.split(svalue, ';');
1620 } else {
1621 values = StringUtil.split(svalue, ',');
1622 }
1623 for (String value: values) {
1624 headers.add(value.trim());
1625 }
1626 String[] array = new String[headers.size()];
1627 for (int i = 0; i < headers.size(); i ++) {
1628 array[i] = headers.get(i);
1629 }
1630 return array;
1631 }
1632 }