KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > struts > upload > MultipartBoundaryInputStream


1 /*
2  * $Id: MultipartBoundaryInputStream.java 54929 2004-10-16 16:38:42Z germuska $
3  *
4  * Copyright 1999-2004 The Apache Software Foundation.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18
19 package org.apache.struts.upload;
20
21 import java.io.InputStream JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.io.File JavaDoc;
24
25 /**
26  * This class encapsulates parsing functionality for RFC1867, multipart/form-data. See MultipartBoundaryInputStreamTest
27  * and MultipartIterator for usage examples.
28  *
29  *
30  * @deprecated Use the Commons FileUpload based multipart handler instead. This
31  * class will be removed after Struts 1.2.
32  */

33 public class MultipartBoundaryInputStream extends InputStream JavaDoc
34 {
35     private static final byte NEWLINE_BYTE = ((byte) '\n');
36
37     private static final byte CARRIAGE_RETURN = ((byte) '\r');
38
39     private static final byte[] CRLF = new byte[] {CARRIAGE_RETURN, NEWLINE_BYTE};
40
41     private static final String JavaDoc DOUBLE_DASH_STRING = "--";
42
43     private static final int DEFAULT_LINE_SIZE = 4096;
44
45     private static final String JavaDoc TOKEN_EQUALS = "=";
46
47     private static final char TOKEN_QUOTE = '\"';
48
49     private static final char TOKEN_COLON = ':';
50
51     private static final char TOKEN_SEMI_COLON = ';';
52
53     private static final char TOKEN_SPACE = ' ';
54
55     private static final String JavaDoc DEFAULT_CONTENT_DISPOSITION = "form-data";
56
57     private static final String JavaDoc PARAMETER_NAME = "name";
58
59     private static final String JavaDoc PARAMETER_FILENAME = "filename";
60
61     private static final String JavaDoc PARAMETER_CHARSET = "charset";
62
63     private static final String JavaDoc CONTENT_TYPE_TEXT_PLAIN = "text/plain";
64
65     private static final String JavaDoc CONTENT_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream";
66
67     private static final String JavaDoc MESSAGE_INVALID_START = "Multipart data doesn't start with boundary";
68
69     /**
70      * The InputStream to read from.
71      */

72     protected InputStream JavaDoc inputStream;
73
74     /**
75      * The boundary.
76      */

77     protected String JavaDoc boundary;
78
79     /**
80      * Whether or not the boundary has been encountered.
81      */

82     protected boolean boundaryEncountered;
83
84     /**
85      * Whether or not the final boundary has been encountered.
86      */

87     protected boolean finalBoundaryEncountered;
88
89     /**
90      * Whether or not the end of the stream has been read.
91      */

92     protected boolean endOfStream;
93
94     /**
95      * The Content-Disposition for the current form element being read.
96      */

97     protected String JavaDoc elementContentDisposition;
98
99     /**
100      * The name of the current form element being read.
101      */

102     protected String JavaDoc elementName;
103
104     /**
105      * The Content-Type of the current form element being read.
106      */

107     protected String JavaDoc elementContentType;
108
109     /**
110      * The filename of the current form element being read, <code>null</code> if the current form element is
111      * text data.
112      */

113     protected String JavaDoc elementFileName;
114
115     /**
116      * The character encoding of the element, specified in the element's Content-Type header.
117      */

118     protected String JavaDoc elementCharset;
119
120     /**
121      * The maximum length in bytes to read from the stream at a time, or -1 for unlimited length.
122      */

123     protected long maxLength;
124
125     /**
126      * Whether or not the maximum length has been met.
127      */

128     protected boolean maxLengthMet;
129
130     /**
131      * The total number of bytes read so far.
132      */

133     protected long bytesRead;
134
135     private byte[] boundaryBytes;
136
137     private byte[] finalBoundaryBytes;
138
139     private byte[] line;
140
141     private int lineSize;
142
143     private int lineLength;
144
145     private boolean lineHasNewline;
146
147     private boolean lineHasCarriage;
148
149     private int lineIndex;
150
151     public MultipartBoundaryInputStream()
152     {
153         this.lineSize = DEFAULT_LINE_SIZE;
154         this.maxLength = -1;
155         resetStream();
156     }
157
158     /**
159      * Sets the boundary that terminates the data for the stream, after adding the prefix "--"
160      */

161     public void setBoundary(String JavaDoc boundary)
162     {
163         this.boundary = DOUBLE_DASH_STRING + boundary;
164         this.boundaryBytes = this.boundary.getBytes();
165         this.finalBoundaryBytes = (this.boundary + DOUBLE_DASH_STRING).getBytes();
166     }
167
168     /**
169      * Resets this stream for use with the next element, to be used after a boundary is encountered.
170      */

171     public void resetForNextBoundary() throws IOException JavaDoc
172     {
173         if (!this.finalBoundaryEncountered)
174         {
175             this.boundaryEncountered = false;
176             resetCrlf();
177             fillLine();
178             readElementHeaders();
179         }
180     }
181
182     /**
183      * Sets the input stream used to read multipart data. For efficiency purposes, make sure that the stream
184      * you set on this class is buffered. The way this class reads lines is that it continually calls the read()
185      * method until it reaches a newline character. That would be terrible if you were to set a socket's input stream
186      * here, but not as bad on a buffered stream.
187      */

188     public void setInputStream(InputStream JavaDoc stream) throws IOException JavaDoc
189     {
190         this.inputStream = stream;
191         resetStream();
192         readFirstElement();
193     }
194
195     /**
196      * Reads from the stream. Returns -1 if it's the end of the stream or if a boundary is encountered.
197      */

198     public int read() throws IOException JavaDoc
199     {
200         if (!this.maxLengthMet)
201         {
202             if (!this.boundaryEncountered)
203             {
204                 return readFromLine();
205             }
206         }
207         return -1;
208     }
209
210     public int read(byte[] buffer) throws IOException JavaDoc
211     {
212         return read(buffer, 0, buffer.length);
213     }
214
215     public int read(byte[] buffer, int offset, int length) throws IOException JavaDoc
216     {
217         if (length > 0)
218         {
219             int read = read();
220             if ((read == -1) && (this.endOfStream || this.boundaryEncountered))
221             {
222                 return -1;
223             }
224             int bytesRead = 1;
225             buffer[offset++] = (byte) read;
226
227             while ((bytesRead < length) && (((read = read())!= -1) || ((read == -1) &&
228                     (!this.boundaryEncountered))) && !this.maxLengthMet)
229             {
230                 buffer[offset++] = (byte) read;
231                 bytesRead++;
232             }
233             return bytesRead;
234         }
235         return -1;
236     }
237
238     /**
239      * Marks the underlying stream.
240      */

241     public synchronized void mark(int i)
242     {
243         this.inputStream.mark(i);
244     }
245
246     /**
247      * Resets the underlying input stream.
248      */

249     public synchronized void reset() throws IOException JavaDoc
250     {
251         this.inputStream.reset();
252     }
253
254     /**
255      * Set the maximum length in bytes to read, or -1 for an unlimited length.
256      */

257     public void setMaxLength(long maxLength)
258     {
259         this.maxLength = maxLength;
260     }
261
262     public long getMaxLength()
263     {
264         return maxLength;
265     }
266
267     /**
268      * Whether or not the maximum length has been met.
269      */

270     public boolean isMaxLengthMet()
271     {
272         return maxLengthMet;
273     }
274
275     /**
276      * Gets the value for the "Content-Dispositio" header for the current multipart element.
277      * Usually "form-data".
278      */

279     public String JavaDoc getElementContentDisposition()
280     {
281         return this.elementContentDisposition;
282     }
283
284     /**
285      * Gets the name of the current element. The name corresponds to the value of
286      * the "name" attribute of the form element.
287      */

288     public String JavaDoc getElementName()
289     {
290         return this.elementName;
291     }
292
293     /**
294      * Gets the character encoding of the current element. The character encoding would have been specified
295      * in the Content-Type header for this element, if it wasn't this is null.
296      */

297     public String JavaDoc getElementCharset()
298     {
299         return this.elementCharset;
300     }
301
302     /**
303      * Gets the "Content-Type" of the current element. If this is a text element,
304      * the content type will probably be "text/plain", otherwise it will be the
305      * content type of the file element.
306      */

307     public String JavaDoc getElementContentType()
308     {
309         return this.elementContentType;
310     }
311
312     /**
313      * Gets the filename of the current element, which will be null if the current element
314      * isn't a file.
315      */

316     public String JavaDoc getElementFileName()
317     {
318         return this.elementFileName;
319     }
320
321     /**
322      * Gets whether or not the current form element being read is a file.
323      */

324     public boolean isElementFile()
325     {
326         return (this.elementFileName != null);
327     }
328
329     /**
330      * Returns whether or not the boundary has been encountered while reading data.
331      */

332     public boolean isBoundaryEncountered()
333     {
334         return this.boundaryEncountered;
335     }
336
337     /**
338      * Returns whether or not the final boundary has been encountered.
339      */

340     public boolean isFinalBoundaryEncountered()
341     {
342         return this.finalBoundaryEncountered;
343     }
344
345     /**
346      * Whether or not an EOF has been read on the stream.
347      */

348     public boolean isEndOfStream()
349     {
350         return this.endOfStream;
351     }
352
353     public void setLineSize(int size)
354     {
355         this.lineSize = size;
356     }
357
358     public long getBytesRead()
359     {
360         return this.bytesRead;
361     }
362
363     private final void readFirstElement() throws IOException JavaDoc
364     {
365         fillLine();
366         if (!this.boundaryEncountered)
367         {
368             throw new IOException JavaDoc(MESSAGE_INVALID_START);
369         }
370         fillLine();
371         readElementHeaders();
372     }
373
374     private final void readElementHeaders() throws IOException JavaDoc
375     {
376         readContentDisposition();
377         resetCrlf();
378         boolean hadContentType = readContentType();
379         resetCrlf();
380         if (hadContentType)
381         {
382             skipCurrentLineIfBlank();
383         }
384     }
385
386     private final void readContentDisposition() throws IOException JavaDoc
387     {
388         String JavaDoc line = readLine();
389         if (line != null)
390         {
391             int colonIndex = line.indexOf(TOKEN_COLON);
392             if (colonIndex != -1)
393             {
394                 int firstSemiColonIndex = line.indexOf(TOKEN_SEMI_COLON);
395                 if (firstSemiColonIndex != -1)
396                 {
397                     this.elementContentDisposition = line.substring(colonIndex+1, firstSemiColonIndex).trim();
398                 }
399             }
400             else
401             {
402                 this.elementContentDisposition = DEFAULT_CONTENT_DISPOSITION;
403             }
404             this.elementName = parseForParameter(PARAMETER_NAME, line);
405             this.elementFileName = parseForParameter(PARAMETER_FILENAME, line);
406             //do platform-specific checking
407
if (this.elementFileName != null)
408             {
409                 this.elementFileName = checkAndFixFilename(this.elementFileName);
410             }
411         }
412     }
413
414     private final String JavaDoc checkAndFixFilename(String JavaDoc filename)
415     {
416         filename = new File JavaDoc(filename).getName();
417
418         //check for windows filenames,
419
//from linux jdk's the entire filepath
420
//isn't parsed correctly from File.getName()
421
int colonIndex = filename.indexOf(":");
422         if (colonIndex == -1) {
423             //check for Window's SMB server file paths
424
colonIndex = filename.indexOf("\\\\");
425         }
426         int slashIndex = filename.lastIndexOf("\\");
427
428         if ((colonIndex > -1) && (slashIndex > -1)) {
429             //then consider this filename to be a full
430
//windows filepath, and parse it accordingly
431
//to retrieve just the file name
432
filename = filename.substring(slashIndex+1, filename.length());
433         }
434         return filename;
435     }
436
437     private final String JavaDoc parseForParameter(String JavaDoc parameter, String JavaDoc parseString)
438     {
439         int nameIndex = parseString.indexOf(parameter + TOKEN_EQUALS);
440         if (nameIndex != -1)
441         {
442             nameIndex += parameter.length() + 1;
443             int startIndex = -1;
444             int endIndex = -1;
445             if (parseString.charAt(nameIndex) == TOKEN_QUOTE)
446             {
447                 startIndex = nameIndex + 1;
448                 int endQuoteIndex = parseString.indexOf(TOKEN_QUOTE, startIndex);
449                 if (endQuoteIndex != -1)
450                 {
451                     endIndex = endQuoteIndex;
452                 }
453             }
454             else
455             {
456                 startIndex = nameIndex;
457                 int spaceIndex = parseString.indexOf(TOKEN_SPACE, startIndex);
458                 if (spaceIndex != -1)
459                 {
460                     endIndex = spaceIndex;
461                 }
462                 else
463                 {
464                     int carriageIndex = parseString.indexOf(CARRIAGE_RETURN, startIndex);
465                     if (carriageIndex != -1)
466                     {
467                         endIndex = carriageIndex;
468                     }
469                     else
470                     {
471                         endIndex = parseString.length();
472                     }
473                 }
474             }
475             if ((startIndex != -1) && (endIndex != -1))
476             {
477                 return parseString.substring(startIndex, endIndex);
478             }
479         }
480         return null;
481     }
482
483     private final boolean readContentType() throws IOException JavaDoc
484     {
485         String JavaDoc line = readLine();
486         if (line != null)
487         {
488             //if it's not a blank line (a blank line has just a CRLF)
489
if (line.length() > 2)
490             {
491                 this.elementContentType = parseHeaderValue(line);
492                 if (this.elementContentType == null)
493                 {
494                     this.elementContentType = CONTENT_TYPE_APPLICATION_OCTET_STREAM;
495                 }
496                 this.elementCharset = parseForParameter(PARAMETER_CHARSET, line);
497                 return true;
498             }
499             //otherwise go to the default content type
500
this.elementContentType = CONTENT_TYPE_TEXT_PLAIN;
501         }
502         return false;
503     }
504
505     private final String JavaDoc parseHeaderValue(String JavaDoc headerLine)
506     {
507         //get the index of the colon
508
int colonIndex = headerLine.indexOf(TOKEN_COLON);
509         if (colonIndex != -1)
510         {
511             int endLineIndex;
512             //see if there's a semi colon
513
int semiColonIndex = headerLine.indexOf(TOKEN_SEMI_COLON, colonIndex);
514             if (semiColonIndex != -1)
515             {
516                 endLineIndex = semiColonIndex;
517             }
518             else
519             {
520                 //get the index of where the carriage return is, past the colon
521
endLineIndex = headerLine.indexOf(CARRIAGE_RETURN, colonIndex);
522             }
523             if (endLineIndex == -1)
524             {
525                 //make index the last character in the line
526
endLineIndex = headerLine.length();
527             }
528             //and return a substring representing everything after the "headerName: "
529
return headerLine.substring(colonIndex+1, endLineIndex).trim();
530         }
531         return null;
532     }
533
534     private final void skipCurrentLineIfBlank() throws IOException JavaDoc
535     {
536         boolean fill = false;
537         if (this.lineLength == 1)
538         {
539             if (this.line[0] == NEWLINE_BYTE)
540             {
541                 fill = true;
542             }
543         }
544         else if (this.lineLength == 2)
545         {
546             if (equals(this.line, 0, 2, CRLF))
547             {
548                 fill = true;
549             }
550         }
551         if (fill && !this.endOfStream)
552         {
553             fillLine();
554         }
555     }
556
557     private final void resetCrlf()
558     {
559         this.lineHasCarriage = false;
560         this.lineHasNewline = false;
561     }
562
563     private final void resetStream()
564     {
565         this.line = new byte[this.lineSize];
566         this.lineIndex = 0;
567         this.lineLength = 0;
568         this.lineHasCarriage = false;
569         this.lineHasNewline = false;
570         this.boundaryEncountered = false;
571         this.finalBoundaryEncountered = false;
572         this.endOfStream = false;
573         this.maxLengthMet = false;
574         this.bytesRead = 0;
575     }
576
577     private final String JavaDoc readLine() throws IOException JavaDoc
578     {
579         String JavaDoc line = null;
580         if (availableInLine() > 0)
581         {
582             line = new String JavaDoc(this.line, 0, this.lineLength);
583             if (!this.endOfStream)
584             {
585                 fillLine();
586             }
587         }
588         else
589         {
590             if (!this.endOfStream)
591             {
592                 fillLine();
593                 line = readLine();
594             }
595         }
596         return line;
597     }
598
599     private final int readFromLine() throws IOException JavaDoc
600     {
601         if (!this.boundaryEncountered)
602         {
603             if (availableInLine() > 0)
604             {
605                 return this.line[this.lineIndex++];
606             }
607             else
608             {
609                 if (!this.endOfStream)
610                 {
611                     fillLine();
612                     return readFromLine();
613                 }
614             }
615         }
616         return -1;
617     }
618
619     private final int availableInLine()
620     {
621         return (this.lineLength - this.lineIndex);
622     }
623
624     private final void fillLine() throws IOException JavaDoc
625     {
626         resetLine();
627         if (!this.finalBoundaryEncountered && !this.endOfStream)
628         {
629             fillLineBuffer();
630             checkForBoundary();
631         }
632     }
633
634     private final void resetLine()
635     {
636         this.lineIndex = 0;
637     }
638
639     private final void fillLineBuffer() throws IOException JavaDoc
640     {
641         int read = 0;
642         int index = 0;
643
644         //take care of any CRLF's from the previous read
645
if (this.lineHasCarriage)
646         {
647             this.line[index++] = CARRIAGE_RETURN;
648             this.lineHasCarriage = false;
649         }
650         if (this.lineHasNewline)
651         {
652             this.line[index++] = NEWLINE_BYTE;
653             this.lineHasNewline = false;
654         }
655         while ((index < this.line.length) && (!this.maxLengthMet))
656         {
657             read = this.inputStream.read();
658             byteRead();
659             if ((read != -1) || ((read == -1) && (this.inputStream.available() > 0)) && !this.maxLengthMet)
660             {
661                 this.line[index++] = (byte) read;
662                 if (read == NEWLINE_BYTE)
663                 {
664                     //flag the newline, but don't put in buffer
665
this.lineHasNewline= true;
666                     index--;
667                     if (index > 0)
668                     {
669                         //flag the carriage return, but don't put in buffer
670
if (this.line[index - 1] == CARRIAGE_RETURN)
671                         {
672                             this.lineHasCarriage = true;
673                             index--;
674                         }
675                     }
676                     break;
677                 }
678             }
679             else
680             {
681                 this.endOfStream = true;
682                 break;
683             }
684         }
685         this.lineLength = index;
686     }
687
688     private final void byteRead()
689     {
690         this.bytesRead++;
691         if (this.maxLength > -1)
692         {
693             if (this.bytesRead >= this.maxLength)
694             {
695                 this.maxLengthMet = true;
696                 this.endOfStream = true;
697             }
698         }
699     }
700
701     private final void checkForBoundary()
702     {
703         this.boundaryEncountered = false;
704         int actualLength = this.lineLength;
705         int startIndex;
706         if ((this.line[0] == CARRIAGE_RETURN) || (this.line[0] == NEWLINE_BYTE))
707         {
708             actualLength--;
709         }
710         if (this.line[1] == NEWLINE_BYTE)
711         {
712             actualLength--;
713         }
714         startIndex = (this.lineLength - actualLength);
715         if (actualLength == this.boundaryBytes.length)
716         {
717             if (equals(this.line, startIndex, this.boundaryBytes.length, this.boundaryBytes))
718             {
719                 this.boundaryEncountered = true;
720             }
721         }
722         else if (actualLength == (this.boundaryBytes.length + 2))
723         {
724             if (equals(this.line, startIndex, this.finalBoundaryBytes.length, this.finalBoundaryBytes))
725             {
726                 this.boundaryEncountered = true;
727                 this.finalBoundaryEncountered = true;
728                 this.endOfStream = true;
729             }
730         }
731     }
732
733     /**
734      * Checks bytes for equality. Two byte arrays are equal if each of their elements are
735      * the same. This method checks comp[offset] with source[0] to source[length-1] with
736      * comp[offset + length - 1]
737      * @param comp The byte to compare to <code>source</code>
738      * @param offset The offset to start at in <code>comp</code>
739      * @param length The length of <code>comp</code> to compare to
740      * @param source The reference byte array to test for equality
741      */

742     private final boolean equals(byte[] comp, int offset, int length, byte[] source)
743     {
744         if ((length != source.length) || (comp.length - offset < length))
745         {
746             return false;
747         }
748         for (int i = 0; i < length; i++)
749         {
750             if (comp[offset+i] != source[i])
751             {
752                 return false;
753             }
754         }
755         return true;
756     }
757
758
759
760
761
762
763
764
765
766 }
767
Popular Tags