KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > servlet > multipart > MultipartParser


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

16 package org.apache.cocoon.servlet.multipart;
17
18 import java.io.BufferedInputStream JavaDoc;
19 import java.io.ByteArrayInputStream JavaDoc;
20 import java.io.ByteArrayOutputStream JavaDoc;
21 import java.io.File JavaDoc;
22 import java.io.FileOutputStream JavaDoc;
23 import java.io.IOException JavaDoc;
24 import java.io.InputStream JavaDoc;
25 import java.io.OutputStream JavaDoc;
26 import java.io.PushbackInputStream JavaDoc;
27 import java.util.Enumeration JavaDoc;
28 import java.util.Hashtable JavaDoc;
29 import java.util.StringTokenizer JavaDoc;
30 import java.util.Vector JavaDoc;
31
32 import javax.servlet.http.HttpServletRequest JavaDoc;
33
34 import org.apache.cocoon.util.NullOutputStream;
35
36 /**
37  * This class is used to implement a multipart request wrapper.
38  * It will parse the http post stream and and fill it's hashtable with values.
39  *
40  * The hashtable will contain:
41  * Vector: inline part values
42  * FilePart: file part
43  *
44  * @author <a HREF="mailto:j.tervoorde@home.nl">Jeroen ter Voorde</a>
45  * @version CVS $Id: MultipartParser.java 280876 2005-09-14 15:44:29Z sylvain $
46  */

47 public class MultipartParser {
48
49     private final static int FILE_BUFFER_SIZE = 4096;
50
51     private static final int MAX_BOUNDARY_SIZE = 128;
52
53     private boolean saveUploadedFilesToDisk;
54
55     private File JavaDoc uploadDirectory = null;
56
57     private boolean allowOverwrite;
58
59     private boolean silentlyRename;
60     
61     private int maxUploadSize;
62
63     private String JavaDoc characterEncoding;
64     
65     private Hashtable JavaDoc parts;
66     
67     private boolean oversized = false;
68     
69     private int contentLength;
70     
71     /**
72      * Constructor, parses given request
73      *
74      * @param saveUploadedFilesToDisk Write fileparts to the uploadDirectory. If true the corresponding object
75      * in the hashtable will contain a FilePartFile, if false a FilePartArray
76      * @param uploadDirectory The directory to write to if saveUploadedFilesToDisk is true.
77      * @param allowOverwrite Allow existing files to be overwritten.
78      * @param silentlyRename If file exists rename file (using filename+number).
79      * @param maxUploadSize The maximum content length accepted.
80      * @param characterEncoding The character encoding to be used.
81      */

82     public MultipartParser(boolean saveUploadedFilesToDisk,
83                            File JavaDoc uploadDirectory,
84                            boolean allowOverwrite,
85                            boolean silentlyRename,
86                            int maxUploadSize,
87                            String JavaDoc characterEncoding)
88     {
89         this.saveUploadedFilesToDisk = saveUploadedFilesToDisk;
90         this.uploadDirectory = uploadDirectory;
91         this.allowOverwrite = allowOverwrite;
92         this.silentlyRename = silentlyRename;
93         this.maxUploadSize = maxUploadSize;
94         this.characterEncoding = characterEncoding;
95     }
96
97     private void parseParts(int contentLength, String JavaDoc contentType, InputStream JavaDoc requestStream)
98     throws IOException JavaDoc, MultipartException {
99         this.contentLength = contentLength;
100         if (contentLength > this.maxUploadSize) {
101             this.oversized = true;
102         }
103
104         BufferedInputStream JavaDoc bufferedStream = new BufferedInputStream JavaDoc(requestStream);
105         PushbackInputStream JavaDoc pushbackStream = new PushbackInputStream JavaDoc(bufferedStream, MAX_BOUNDARY_SIZE);
106         TokenStream stream = new TokenStream(pushbackStream);
107
108         parseMultiPart(stream, getBoundary(contentType));
109
110     }
111     
112     public Hashtable JavaDoc getParts(int contentLength, String JavaDoc contentType, InputStream JavaDoc requestStream)
113     throws IOException JavaDoc, MultipartException {
114         this.parts = new Hashtable JavaDoc();
115         parseParts(contentLength, contentType, requestStream);
116         return this.parts;
117     }
118     
119     public Hashtable JavaDoc getParts(HttpServletRequest JavaDoc request) throws IOException JavaDoc, MultipartException {
120         this.parts = new Hashtable JavaDoc();
121         
122         // Copy all parameters coming from the request URI to the parts table.
123
// This happens when a form's action attribute has some parameters
124
Enumeration JavaDoc names = request.getParameterNames();
125         while(names.hasMoreElements()) {
126             String JavaDoc name = (String JavaDoc)names.nextElement();
127             String JavaDoc[] values = request.getParameterValues(name);
128             Vector JavaDoc v = new Vector JavaDoc(values.length);
129             for (int i = 0; i < values.length; i++) {
130                 v.add(values[i]);
131             }
132             this.parts.put(name, v);
133         }
134         parseParts(request.getContentLength(), request.getContentType(), request.getInputStream());
135         return this.parts;
136     }
137     
138     /**
139      * Parse a multipart block
140      *
141      * @param ts
142      * @param boundary
143      *
144      * @throws IOException
145      * @throws MultipartException
146      */

147     private void parseMultiPart(TokenStream ts, String JavaDoc boundary)
148             throws IOException JavaDoc, MultipartException {
149
150         ts.setBoundary(boundary.getBytes());
151         ts.read(); // read first boundary away
152
ts.setBoundary(("\r\n" + boundary).getBytes());
153
154         while (ts.getState() == TokenStream.STATE_NEXTPART) {
155             ts.nextPart();
156             parsePart(ts);
157         }
158
159         if (ts.getState() != TokenStream.STATE_ENDMULTIPART) { // sanity check
160
throw new MultipartException("Malformed stream");
161         }
162     }
163
164     /**
165      * Parse a single part
166      *
167      * @param ts
168      *
169      * @throws IOException
170      * @throws MultipartException
171      */

172     private void parsePart(TokenStream ts)
173             throws IOException JavaDoc, MultipartException {
174
175         Hashtable JavaDoc headers = new Hashtable JavaDoc();
176         headers = readHeaders(ts);
177         try {
178             if (headers.containsKey("filename")) {
179                 if (!"".equals(headers.get("filename"))) {
180                     parseFilePart(ts, headers);
181                 } else {
182                     // IE6 sends an empty part with filename="" for
183
// empty upload fields. Just parse away the part
184
byte[] buf = new byte[32];
185                     while(ts.getState() == TokenStream.STATE_READING)
186                         ts.read(buf);
187                 }
188             } else if (((String JavaDoc) headers.get("content-disposition"))
189                     .toLowerCase().equals("form-data")) {
190                 parseInlinePart(ts, headers);
191             }
192
193             // FIXME: multipart/mixed parts are untested.
194
else if (((String JavaDoc) headers.get("content-disposition")).toLowerCase()
195                     .indexOf("multipart") > -1) {
196                 parseMultiPart(new TokenStream(ts, MAX_BOUNDARY_SIZE),
197                         "--" + (String JavaDoc) headers.get("boundary"));
198                 ts.read(); // read past boundary
199
} else {
200                 throw new MultipartException("Unknown part type");
201             }
202         } catch (IOException JavaDoc e) {
203             throw new MultipartException("Malformed stream: " + e.getMessage());
204         } catch (NullPointerException JavaDoc e) {
205             e.printStackTrace();
206             throw new MultipartException("Malformed header");
207         }
208     }
209
210     /**
211      * Parse a file part
212      *
213      * @param in
214      * @param headers
215      *
216      * @throws IOException
217      * @throws MultipartException
218      */

219     private void parseFilePart(TokenStream in, Hashtable JavaDoc headers)
220             throws IOException JavaDoc, MultipartException {
221
222         byte[] buf = new byte[FILE_BUFFER_SIZE];
223         OutputStream JavaDoc out;
224         File JavaDoc file = null;
225
226         if (oversized) {
227             out = new NullOutputStream();
228         } else if (!saveUploadedFilesToDisk) {
229             out = new ByteArrayOutputStream JavaDoc();
230         } else {
231             String JavaDoc fileName = (String JavaDoc) headers.get("filename");
232             if(File.separatorChar == '\\')
233                 fileName = fileName.replace('/','\\');
234             else
235                 fileName = fileName.replace('\\','/');
236
237             String JavaDoc filePath = uploadDirectory.getPath() + File.separator;
238             fileName = new File JavaDoc(fileName).getName();
239             file = new File JavaDoc(filePath + fileName);
240
241             if (!allowOverwrite && !file.createNewFile()) {
242                 if (silentlyRename) {
243                     int c = 0;
244                     do {
245                         file = new File JavaDoc(filePath + c++ + "_" + fileName);
246                     } while (!file.createNewFile());
247                 } else {
248                     throw new MultipartException("Duplicate file '" + file.getName()
249                         + "' in '" + file.getParent() + "'");
250                 }
251             }
252
253             out = new FileOutputStream JavaDoc(file);
254         }
255
256         int length = 0; // Track length for OversizedPart
257
try {
258             int read = 0;
259             while (in.getState() == TokenStream.STATE_READING) { // read data
260
read = in.read(buf);
261                 length += read;
262                 out.write(buf, 0, read);
263             }
264         } catch (IOException JavaDoc ioe) {
265             // don't let incomplete file uploads pile up in the upload dir.
266
// this usually happens with aborted form submits containing very large files.
267
out.close();
268             out = null;
269             if ( file!=null ) file.delete();
270             throw ioe;
271         } finally {
272             if ( out!=null ) out.close();
273         }
274         
275         String JavaDoc name = (String JavaDoc)headers.get("name");
276         if (oversized) {
277             this.parts.put(name, new RejectedPart(headers, length, this.contentLength, this.maxUploadSize));
278         } else if (file == null) {
279             byte[] bytes = ((ByteArrayOutputStream JavaDoc) out).toByteArray();
280             this.parts.put(name, new PartInMemory(headers, new ByteArrayInputStream JavaDoc(bytes), bytes.length));
281         } else {
282             this.parts.put(name, new PartOnDisk(headers, file));
283         }
284     }
285
286     /**
287      * Parse an inline part
288      *
289      * @param in
290      * @param headers
291      *
292      * @throws IOException
293      */

294     private void parseInlinePart(TokenStream in, Hashtable JavaDoc headers)
295             throws IOException JavaDoc {
296
297         // Buffer incoming bytes for proper string decoding (there can be multibyte chars)
298
ByteArrayOutputStream JavaDoc bos = new ByteArrayOutputStream JavaDoc();
299
300         while (in.getState() == TokenStream.STATE_READING) {
301             int c = in.read();
302             if (c != -1) bos.write(c);
303         }
304         
305         String JavaDoc field = (String JavaDoc) headers.get("name");
306         Vector JavaDoc v = (Vector JavaDoc) this.parts.get(field);
307
308         if (v == null) {
309             v = new Vector JavaDoc();
310             this.parts.put(field, v);
311         }
312
313         v.add(new String JavaDoc(bos.toByteArray(), this.characterEncoding));
314     }
315
316     /**
317      * Read part headers
318      *
319      * @param in
320      *
321      * @return
322      *
323      * @throws IOException
324      */

325     private Hashtable JavaDoc readHeaders(TokenStream in) throws IOException JavaDoc {
326
327         Hashtable JavaDoc headers = new Hashtable JavaDoc();
328         String JavaDoc hdrline = readln(in);
329
330         while (!"".equals(hdrline)) {
331             StringTokenizer JavaDoc tokenizer = new StringTokenizer JavaDoc(hdrline);
332
333             headers.put(tokenizer.nextToken(" :").toLowerCase(),
334                     tokenizer.nextToken(" :;"));
335
336             // The extra tokenizer.hasMoreTokens() in headers.put
337
// handles the filename="" case IE6 submits for an empty
338
// upload field.
339
while (tokenizer.hasMoreTokens()) {
340                 headers.put(tokenizer.nextToken(" ;=\""),
341                         tokenizer.hasMoreTokens()?tokenizer.nextToken("=\""):"");
342             }
343
344             hdrline = readln(in);
345         }
346
347         return headers;
348     }
349
350     /**
351      * Get boundary from contentheader
352      *
353      * @param hdr
354      *
355      * @return
356      */

357     private String JavaDoc getBoundary(String JavaDoc hdr) {
358
359         int start = hdr.toLowerCase().indexOf("boundary=");
360         if (start > -1) {
361             return "--" + hdr.substring(start + 9);
362         } else {
363             return null;
364         }
365     }
366
367     /**
368      * Read string until newline or end of stream
369      *
370      * @param in
371      *
372      * @return
373      *
374      * @throws IOException
375      */

376     private String JavaDoc readln(TokenStream in) throws IOException JavaDoc {
377         
378         ByteArrayOutputStream JavaDoc bos = new ByteArrayOutputStream JavaDoc();
379         
380         int b = in.read();
381
382         while ((b != -1) && (b != '\r')) {
383             bos.write(b);
384             b = in.read();
385         }
386
387         if (b == '\r') {
388             in.read(); // read '\n'
389
}
390
391         return new String JavaDoc(bos.toByteArray(), this.characterEncoding);
392     }
393 }
394
Popular Tags