001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * Portions Copyrighted 2007 Sun Microsystems, Inc.
027: */
028:
029: package org.netbeans.lib.uihandler;
030:
031: /*
032: * MultipartHandler:
033: * A utility class to handle content of multipart/form-data type used in form uploads.
034: *
035: * Parses and provides accessor functions to extract the form fields and the uploaded
036: * file content parts separated by a boundary string.
037: * See http://www.ietf.org/rfc/rfc1867.txt.
038: */
039:
040: import java.io.BufferedOutputStream;
041: import java.io.ByteArrayOutputStream;
042: import java.io.File;
043: import java.io.FileOutputStream;
044: import java.io.FilterInputStream;
045: import java.io.IOException;
046: import java.io.InputStream;
047: import java.io.OutputStream;
048: import java.util.Enumeration;
049: import java.util.Hashtable;
050: import java.util.HashMap;
051: import java.util.Vector;
052: import java.util.zip.GZIPInputStream;
053:
054: public class MultiPartHandler {
055: public interface InputFacade {
056: public int readLine(byte[] arr, int off, int len)
057: throws IOException;
058:
059: public InputStream getInputStream();
060: }
061:
062: public interface RequestFacade {
063: public int getContentLength();
064:
065: public String getContentType();
066:
067: public InputFacade getInput() throws IOException;
068: }
069:
070: private static final int DEFAULT_MAX_UPLOAD_SIZE = 1024 * 1024; // 1Mb
071:
072: protected Hashtable<String, Vector<String>> formFields = new Hashtable<String, Vector<String>>();
073: Hashtable<String, OneUpload> uploadFiles = new Hashtable<String, OneUpload>();
074:
075: /** servlet request */
076: private RequestFacade req;
077:
078: /** input stream to read parts from */
079: private InputFacade in;
080:
081: /** MIME boundary that delimits parts */
082: private String boundary;
083:
084: /** buffer for readLine method */
085: private byte[] buf = new byte[8 * 1024];
086:
087: /** upload directory */
088: private File uploadDir;
089:
090: /** encoding used for the from fields */
091: private String fieldEncoding = "ISO-8859-1";
092:
093: // i18n StringManager
094: /*private static StringManager localStrings =
095: StringManager.getManager( OneUpload.class );*/
096:
097: /**
098: * Instantiate a new multipart handler with default
099: */
100: public MultiPartHandler(RequestFacade request, String tmpDirectory)
101: throws IOException {
102: this (request, tmpDirectory, DEFAULT_MAX_UPLOAD_SIZE,
103: "ISO-8859-1");
104: }
105:
106: public MultiPartHandler(RequestFacade request, String tmpDirectory,
107: int maxUploadSize) throws IOException {
108: this (request, tmpDirectory, maxUploadSize, "ISO-8859-1");
109: }
110:
111: /**
112: * Instantiate a new OneUpload to handle the given request,
113: * saving any uploaded files to the given directory and limiting the
114: * upload size to maxUploadSize.
115: *
116: * An IOException is thrown when the request content-type doesn't match
117: * with multipart/form-data or if the upload size exceeds the given limit.
118: *
119: * call parseMultipartUpload() to parse various parts of the posted data and then
120: * call getParameter(), getParameters(), getParameterNames() and getParameterValues()
121: * functions to access form field names and their values.
122: * call getFile(), getFileType() to access uploaded file and its content-type.
123: */
124: public MultiPartHandler(RequestFacade request, String tmpDirectory,
125: int maxUploadSize, String fieldEncoding) throws IOException {
126:
127: // Ensure we are passed legal arguments
128: if (request == null) {
129: //String msg = localStrings.getString( "admin.server.gui.servlet.request_cannot_be_null" );
130: throw new IllegalArgumentException("request is null");
131: }
132: if (tmpDirectory == null) {
133: //String msg = localStrings.getString( "admin.server.gui.servlet.tmpdirectory_cannot_be_null" );
134: throw new IllegalArgumentException("tmp Dir is null");
135: }
136: if (maxUploadSize <= 0) {
137: //String msg = localStrings.getString( "admin.server.gui.servlet.maxpostsize_must_be_positive" );
138: throw new IllegalArgumentException("Max size is < 0");
139: }
140:
141: // Ensure that the directory exists and is writable (this should be a temp directory)
142: uploadDir = new File(tmpDirectory);
143: if (!uploadDir.isDirectory()) {
144: //String msg = localStrings.getString( "admin.server.gui.servlet.not_directory", tmpDirectory );
145: throw new IllegalArgumentException("Not a Directory");
146: }
147: if (!uploadDir.canWrite()) {
148: //String msg = localStrings.getString( "admin.server.gui.servlet.not_writable", tmpDirectory );
149: throw new IllegalArgumentException("write protected");
150: }
151: /*
152: int length = request.getContentLength();
153: //commented this code to remove the restriction on the file upload size.
154: /*if (length > maxUploadSize) {
155: //String msg = localStrings.getString( "admin.server.gui.servlet.posted_content_length_exceeds_limit", new Integer(length), new Integer(maxUploadSize) );
156: throw new IOException( msg );
157: }*/
158: // Check the content type to make sure it's "multipart/form-data"
159: String type = request.getContentType();
160: if (type == null
161: || !type.toLowerCase()
162: .startsWith("multipart/form-data")) {
163: //String msg = localStrings.getString( "admin.server.gui.servlet.posted_content_type_not_multipart" );
164: throw new IOException("type null");
165: }
166:
167: // Check the content length to prevent denial of service attacks
168: this .fieldEncoding = fieldEncoding;
169: this .req = request;
170: }
171:
172: /* parseMultipartUpload:
173: *
174: * This function parses the multipart/form-data and throws an IOException
175: * if there's any problem reading or parsing the request or if the posted
176: * content is larger than the maximum permissible size.
177: */
178: public void parseMultipartUpload() throws IOException {
179: // setup the initial buffered input stream, boundary string that separates
180: // various parts in the stream.
181: startMultipartParse();
182:
183: HashMap partHeaders = parsePartHeaders();
184: while (partHeaders != null) {
185:
186: String fieldName = (String) partHeaders.get("fieldName");
187: String fileName = (String) partHeaders.get("fileName");
188:
189: if (fileName != null) {
190: // This is a file upload part
191: if (fileName.equals("")) {
192: fileName = null; // empty filename, probably an "empty" file param
193: }
194:
195: if (fileName != null) {
196: // a filename was actually specified
197: String content = (String) partHeaders
198: .get("content-type");
199: fileName = saveUploadFile(fileName, content);
200:
201: uploadFiles.put(fieldName, new OneUpload(uploadDir
202: .toString(), fileName, content));
203: } else {
204: uploadFiles.put(fieldName, new OneUpload(null,
205: null, null));
206: }
207: } else {
208: // this is a parameters list part
209: byte[] valueBytes = parseFormFieldBytes();
210: String value = new String(valueBytes, fieldEncoding);
211:
212: Vector<String> existingValues = formFields
213: .get(fieldName);
214: if (existingValues == null) {
215: existingValues = new Vector<String>();
216: formFields.put(fieldName, existingValues);
217: }
218: existingValues.addElement(value);
219: }
220:
221: partHeaders.clear();
222: partHeaders = parsePartHeaders();
223: }
224: }
225:
226: private void startMultipartParse() throws IOException {
227: // Get the boundary string; it's included in the content type.
228: // Should look something like "------------------------12012133613061"
229: String boundary = parseBoundary(req.getContentType());
230: if (boundary == null) {
231: //String msg = localStrings.getString( "admin.server.gui.servlet.separation_boundary_not_specified" );
232: throw new IOException("boundary is nul");
233: }
234:
235: this .in = req.getInput();
236: this .boundary = boundary;
237:
238: // Read the first line, should be the first boundary
239: String line = readLine();
240: if (line == null) {
241: //String msg = localStrings.getString( "admin.server.gui.servlet.corrupt_form_data_premature_ending" );
242: throw new IOException("line is null");
243: }
244:
245: // Verify that the line is the boundary
246: if (!line.startsWith(boundary)) {
247: //String msg = localStrings.getString( "admin.server.gui.servlet.corrupt_form_data_no_leading_boundary", line, boundary );
248: throw new IOException("not start with boundary");
249: }
250: }
251:
252: /**
253: * parse the headers of the individual part; they look like this:
254: * Content-Disposition: form-data; name="field1"; filename="file1.txt"
255: * Content-Type: type/subtype
256: * Content-Transfer-Encoding: binary
257: */
258: private HashMap parsePartHeaders() throws IOException {
259: HashMap<String, String> partHeaders = new HashMap<String, String>();
260:
261: Vector<String> headers = new Vector<String>();
262: String line = readLine();
263: if (line == null) {
264: // No parts left, we're done
265: return null;
266: } else if (line.length() == 0) {
267: // IE4 on Mac sends an empty line at the end; treat that as the end.
268: return null;
269: }
270: headers.addElement(line);
271:
272: // Read the following header lines we hit an empty line
273: while ((line = readLine()) != null && (line.length() > 0)) {
274: headers.addElement(line);
275: }
276:
277: // If we got a null above, it's the end
278: if (line == null) {
279: return null;
280: }
281:
282: // default part content type (rfc1867)
283: partHeaders.put("content-type", "text/plain");
284:
285: Enumeration ee = headers.elements();
286: while (ee.hasMoreElements()) {
287: String headerline = (String) ee.nextElement();
288:
289: if (headerline.toLowerCase().startsWith(
290: "content-disposition:")) {
291: // Parse the content-disposition line
292: parseContentDisposition(headerline, partHeaders);
293: } else if (headerline.toLowerCase().startsWith(
294: "content-type:")) {
295: // Get the content type, or null if none specified
296: parseContentType(headerline, partHeaders);
297: }
298: }
299:
300: return partHeaders;
301: }
302:
303: /**
304: * parses and returns the boundary token from a line.
305: */
306: private String parseBoundary(String line) {
307: // Use lastIndexOf() because IE 4.01 on Win98 has been known to send the
308: // "boundary=" string multiple times.
309: int index = line.lastIndexOf("boundary=");
310: if (index == -1) {
311: return null;
312: }
313: String boundary = line.substring(index + 9); // 9 for "boundary="
314: if (boundary.charAt(0) == '"') {
315: // The boundary is enclosed in quotes, strip them
316: index = boundary.lastIndexOf('"');
317: boundary = boundary.substring(1, index);
318: }
319:
320: // The real boundary is always preceeded by an extra "--"
321: boundary = "--" + boundary;
322:
323: return boundary;
324: }
325:
326: /**
327: * parses and returns content-disposition header and stores the values
328: * in the partHeaders.
329: *
330: * throws IOException if the line is malformatted.
331: */
332: private void parseContentDisposition(String line,
333: HashMap<String, String> partHeaders) throws IOException {
334:
335: // Convert the line to a lowercase string without the ending \r\n
336: // Keep the original line for error messages and for variable names.
337: String origline = line;
338: line = origline.toLowerCase();
339:
340: // Get the content disposition, should be "form-data"
341: int start = line.indexOf("content-disposition: ");
342: int end = line.indexOf(";");
343: if (start == -1 || end == -1) {
344: //String msg = localStrings.getString( "admin.server.gui.servlet.content_disposition_corrupt", origline );
345: throw new IOException("end reached");
346: }
347: String disposition = line.substring(start + 21, end);
348: if (!disposition.equals("form-data")) {
349: //String msg = localStrings.getString( "admin.server.gui.servlet.invalid_content_disposition", disposition );
350: throw new IOException("fome-data not match");
351: }
352:
353: // Get the field name
354: start = line.indexOf("name=\"", end); // start at last semicolon
355: end = line.indexOf("\"", start + 7); // skip name=\"
356: if (start == -1 || end == -1) {
357: //String msg = localStrings.getString( "admin.server.gui.servlet.content_disposition_corrupt", origline );
358: throw new IOException("data corrupt");
359: }
360:
361: String name = origline.substring(start + 6, end);
362:
363: // Get the fileName, if given
364: String fileName = null;
365: String origFileName = null;
366: start = line.indexOf("filename=\"", end + 2); // start after name
367: end = line.indexOf("\"", start + 10); // skip filename=\"
368:
369: if (start != -1 && end != -1) { // note the !=
370: fileName = origline.substring(start + 10, end);
371: origFileName = fileName;
372: // The filename may contain a full path. Cut to just the filename.
373: int slash = Math.max(fileName.lastIndexOf('/'), fileName
374: .lastIndexOf('\\'));
375: if (slash > -1) {
376: fileName = fileName.substring(slash + 1); // past last slash
377: }
378: }
379:
380: // fill in the part parameters map: disposition, name, filename
381: // empty fileName denotes no file posted!
382: partHeaders.put("disposition", disposition);
383: partHeaders.put("fieldName", name);
384: partHeaders.put("fileName", fileName);
385: partHeaders.put("filePath", origFileName);
386: }
387:
388: /**
389: * parse and returns the content type from a line, or null if the
390: * line was empty.
391: */
392: private void parseContentType(String line,
393: HashMap<String, String> partHeaders) throws IOException {
394: String contentType = null;
395:
396: // Convert the line to a lowercase string
397: String origline = line;
398: line = origline.toLowerCase();
399:
400: // Get the content type, if any
401: if (line.startsWith("content-type")) {
402: int start = line.indexOf(" ");
403:
404: if (start == -1) {
405: //String msg = localStrings.getString( "admin.server.gui.servlet.corrupt_content_type", origline );
406: throw new IOException("no start");
407: }
408: contentType = line.substring(start + 1);
409:
410: partHeaders.put("content-type", contentType);
411: } else if (line.length() != 0) { // no content type, so should be empty
412: //String msg = localStrings.getString( "admin.server.gui.servlet.malformed_line_after_disposition", origline );
413: throw new IOException("length 0");
414: }
415: }
416:
417: /** parse contents of a form field parameter; uses the encoding set by the user
418: */
419: private byte[] parseFormFieldBytes() throws IOException {
420:
421: // Copy the part's contents into a byte array
422: MultipartInputStream pis = new MultipartInputStream(in,
423: boundary);
424:
425: ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
426: byte[] buf = new byte[128];
427: int read;
428: while ((read = pis.read(buf)) != -1) {
429: baos.write(buf, 0, read);
430: }
431: pis.close();
432: baos.close();
433:
434: // get the value bytes
435: return baos.toByteArray();
436: }
437:
438: /**
439: * Read the next line of input.
440: *
441: * @return a String containing the next line of input from the stream,
442: * or null to indicate the end of the stream.
443: * @exception IOException if an input or output exception has occurred.
444: */
445: private String readLine() throws IOException {
446: StringBuffer sbuf = new StringBuffer();
447: int result;
448: String line;
449:
450: do {
451: result = in.readLine(buf, 0, buf.length); // does +=
452: if (result != -1) {
453: sbuf.append(new String(buf, 0, result, "ISO-8859-1"));
454: }
455: } while (result == buf.length); // loop only if the buffer was filled
456:
457: if (sbuf.length() == 0) {
458: return null; // nothing read, must be at the end of stream
459: }
460:
461: // Cut off the trailing \n or \r\n
462: // It should always be \r\n but IE5 sometimes does just \n
463: int len = sbuf.length();
464: if (len >= 2 && sbuf.charAt(len - 2) == '\r') {
465: sbuf.setLength(len - 2); // cut \r\n
466: } else {
467: sbuf.setLength(len - 1); // cut \n
468: }
469: return sbuf.toString();
470: }
471:
472: /**
473: * Write this file part to the specified directory.
474: */
475: private String saveUploadFile(String fileName, String content)
476: throws IOException {
477:
478: long written = 0;
479: OutputStream fileOut = null;
480:
481: try {
482: // Only do something if this part contains a file
483: File file = new File(uploadDir, fileName);
484: for (int i = 0; file.exists(); i++) {
485: if (!file.exists()) {
486: break;
487: }
488: file = new File(uploadDir, fileName + "." + i);
489: }
490: fileName = file.getName();
491:
492: fileOut = new BufferedOutputStream(new FileOutputStream(
493: file));
494: int numBytes;
495: byte[] buf = new byte[8 * 1024];
496:
497: InputStream partInput;
498: if (content.equals("x-application/gzip")) { // NOI18N
499: // sending from NetBeans UI Gestures Collector
500: partInput = in.getInputStream();
501: } else {
502: /** input stream containing file data */
503: partInput = new MultipartInputStream(in, boundary);
504: }
505: while ((numBytes = partInput.read(buf)) != -1) {
506: fileOut.write(buf, 0, numBytes);
507: written += numBytes;
508: }
509: partInput.close();
510: } finally {
511: if (fileOut != null)
512: fileOut.close();
513: }
514:
515: return fileName;
516: }
517:
518: /**
519: * Returns the names of all the parameters as an Enumeration of
520: * Strings. It returns an empty Enumeration if there are no parameters.
521: *
522: */
523: public Enumeration getParameterNames() {
524: return formFields.keys();
525: }
526:
527: /**
528: * Returns the names of all the uploaded files as an Enumeration of
529: * Strings. It returns an empty Enumeration if there are no uploaded
530: * files. Each file name is the name specified by the form, not by
531: * the user.
532: *
533: */
534: public Enumeration getFileNames() {
535: return uploadFiles.keys();
536: }
537:
538: /**
539: * Returns the value of the named parameter as a String, or null if
540: * the parameter was not sent or was sent without a value. The value
541: * is guaranteed to be in its normal, decoded form. If the parameter
542: * has multiple values, only the last one is returned (for backward
543: * compatibility). For parameters with multiple values, it's possible
544: * the last "value" may be null.
545: *
546: */
547: public String getParameter(String name) {
548: try {
549: Vector values = (Vector) formFields.get(name);
550: if (values == null || values.size() == 0) {
551: return null;
552: }
553: String value = (String) values.elementAt(values.size() - 1);
554: return value;
555: } catch (Exception e) {
556: return null;
557: }
558: }
559:
560: /**
561: * Returns the values of the named parameter as a String array, or null if
562: * the parameter was not sent. The array has one entry for each parameter
563: * field sent. If any field was sent without a value that entry is stored
564: * in the array as a null. The values are guaranteed to be in their
565: * normal, decoded form. A single value is returned as a one-element array.
566: *
567: */
568: public String[] getParameterValues(String name) {
569: try {
570: Vector values = (Vector) formFields.get(name);
571: if (values == null || values.size() == 0) {
572: return null;
573: }
574: String[] valuesArray = new String[values.size()];
575: values.copyInto(valuesArray);
576: return valuesArray;
577: } catch (Exception e) {
578: return null;
579: }
580: }
581:
582: /**
583: * Returns the filesystem name of the specified file, or null if the
584: * file was not included in the upload. A filesystem name is the name
585: * specified by the user. It is also the name under which the file is
586: * actually saved.
587: *
588: */
589: public String getFileName(String name) {
590: try {
591: OneUpload file = uploadFiles.get(name);
592: return file.getFileName(); // may be null
593: } catch (Exception e) {
594: return null;
595: }
596: }
597:
598: /**
599: * Returns the content type of the specified file (as supplied by the
600: * client browser), or null if the file was not included in the upload.
601: *
602: */
603: public String getFileType(String name) {
604: try {
605: OneUpload file = uploadFiles.get(name);
606: return file.getFileType(); // may be null
607: } catch (Exception e) {
608: return null;
609: }
610: }
611:
612: /**
613: * Returns a File object for the specified file saved on the server's
614: * filesystem, or null if the file was not included in the upload.
615: *
616: */
617: public File getFile(String name) {
618: try {
619: OneUpload file = uploadFiles.get(name);
620: return file.getFile(); // may be null
621: } catch (Exception e) {
622: return null;
623: }
624: }
625:
626: /**
627: * close the multi-part form handler
628: */
629: public void close() throws IOException {
630: req = null;
631: in = null;
632: boundary = null;
633: buf = null;
634: uploadDir = null;
635: }
636:
637: /** A class to hold information about an uploaded file. */
638: private static class OneUpload {
639:
640: private String dir;
641: private String filename;
642: private String type;
643:
644: OneUpload(String dir, String filename, String type) {
645: this .dir = dir;
646: this .filename = filename;
647: this .type = type;
648: }
649:
650: public String getFileType() {
651: return type;
652: }
653:
654: public String getFileName() {
655: return filename;
656: }
657:
658: public File getFile() {
659: if (dir == null || filename == null) {
660: return null;
661: } else {
662: return new File(dir + File.separator + filename);
663: }
664: }
665: }
666:
667: /*
668: * providing access to a single MIME part contained with in which ends with
669: * the boundary specified. It uses buffering to provide maximum performance.
670: *
671: */
672: private static class MultipartInputStream extends FilterInputStream {
673: /** boundary which "ends" the stream */
674: private String boundary;
675:
676: /** our buffer */
677: private byte[] buf = new byte[64 * 1024]; // 64k
678:
679: /** number of bytes we've read into the buffer */
680: private int count;
681:
682: /** current position in the buffer */
683: private int pos;
684:
685: /** flag that indicates if we have encountered the boundary */
686: private boolean eof;
687:
688: /** associated facade */
689: private MultiPartHandler.InputFacade facade;
690:
691: // i18n StringManager
692: /*private static StringManager localStrings =
693: StringManager.getManager( MultipartInputStream.class );*/
694:
695: /**
696: * Instantiate a MultipartInputStream which stops at the specified
697: * boundary from an underlying ServletInputStream.
698: *
699: */
700: MultipartInputStream(MultiPartHandler.InputFacade in,
701: String boundary) throws IOException {
702: super (in.getInputStream());
703: this .boundary = boundary;
704: this .facade = in;
705: }
706:
707: /**
708: * Fill up our buffer from the underlying input stream, and check for the
709: * boundary that signifies end-of-file. Users of this method must ensure
710: * that they leave exactly 2 characters in the buffer before calling this
711: * method (except the first time), so that we may only use these characters
712: * if a boundary is not found in the first line read.
713: *
714: * @exception IOException if an I/O error occurs.
715: */
716: private void fill() throws IOException {
717: if (eof)
718: return;
719:
720: // as long as we are not just starting up
721: if (count > 0) {
722: // if the caller left the requisite amount spare in the buffer
723: if (count - pos == 2) {
724: // copy it back to the start of the buffer
725: System.arraycopy(buf, pos, buf, 0, count - pos);
726: count -= pos;
727: pos = 0;
728: } else {
729: // should never happen, but just in case
730: //String msg = localStrings.getString( "admin.server.gui.servlet.fill_detected_illegal_buffer_state" );
731: throw new IllegalStateException(
732: "should never happen");
733: }
734: }
735:
736: // try and fill the entire buffer, starting at count, line by line
737: // but never read so close to the end that we might split a boundary
738: int read = 0;
739: int maxRead = buf.length - boundary.length();
740: while (count < maxRead) {
741: // read a line
742: read = facade.readLine(buf, count, buf.length - count);
743: // check for eof and boundary
744: if (read == -1) {
745: //String msg = localStrings.getString( "admin.server.gui.servlet.unexpected_end_part" );
746: throw new IOException("read is -1");
747: } else {
748: if (read >= boundary.length()) {
749: eof = true;
750: for (int i = 0; i < boundary.length(); i++) {
751: if (boundary.charAt(i) != buf[count + i]) {
752: // Not the boundary!
753: eof = false;
754: break;
755: }
756: }
757: if (eof) {
758: break;
759: }
760: }
761: }
762: // success
763: count += read;
764: }
765: }
766:
767: /**
768: * See the general contract of the read method of InputStream.
769: * Returns -1 (end of file) when the MIME boundary of this part is encountered.
770: *
771: * throws IOException if an I/O error occurs.
772: */
773: public int read() throws IOException {
774: if (count - pos <= 2) {
775: fill();
776: if (count - pos <= 2) {
777: return -1;
778: }
779: }
780: return buf[pos++] & 0xff;
781: }
782:
783: /**
784: * See the general contract of the read method of InputStream.
785: *
786: * Returns -1 (end of file) when the MIME boundary of this part
787: * is encountered.
788: *
789: * throws IOException if an I/O error occurs.
790: */
791: public int read(byte b[]) throws IOException {
792: return read(b, 0, b.length);
793: }
794:
795: /**
796: * See the general contract of the read method of InputStream.
797: *
798: * Returns -1 (end of file) when the MIME boundary of this part is encountered.
799: *
800: * throws IOException if an I/O error occurs.
801: */
802: public int read(byte b[], int off, int len) throws IOException {
803: int total = 0;
804: if (len == 0) {
805: return 0;
806: }
807:
808: int avail = count - pos - 2;
809: if (avail <= 0) {
810: fill();
811: avail = count - pos - 2;
812: if (avail <= 0) {
813: return -1;
814: }
815: }
816: int copy = Math.min(len, avail);
817: System.arraycopy(buf, pos, b, off, copy);
818: pos += copy;
819: total += copy;
820:
821: while (total < len) {
822: fill();
823: avail = count - pos - 2;
824: if (avail <= 0) {
825: return total;
826: }
827: copy = Math.min(len - total, avail);
828: System.arraycopy(buf, pos, b, off + total, copy);
829: pos += copy;
830: total += copy;
831: }
832: return total;
833: }
834:
835: /**
836: * Returns the number of bytes that can be read from this input stream
837: * without blocking. This is a standard InputStream idiom
838: * to deal with buffering gracefully, and is not same as the length of the
839: * part arriving in this stream.
840: *
841: * throws IOException if an I/O error occurs.
842: */
843: public int available() throws IOException {
844: int avail = (count - pos - 2) + in.available();
845: // Never return a negative value
846: return (avail < 0 ? 0 : avail);
847: }
848:
849: /**
850: * Closes this input stream and releases any system resources
851: * associated with the stream. This method will read any unread data
852: * in the MIME part so that the next part starts an an expected place in
853: * the parent InputStream.
854: *
855: * throws IOException if an I/O error occurs.
856: */
857: public void close() throws IOException {
858: if (!eof) {
859: while (read(buf, 0, buf.length) != -1)
860: ; // do nothing
861: }
862: }
863: }
864:
865: }
|