| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.lib.uihandler; |
| /* |
| * MultipartHandler: |
| * A utility class to handle content of multipart/form-data type used in form uploads. |
| * |
| * Parses and provides accessor functions to extract the form fields and the uploaded |
| * file content parts separated by a boundary string. |
| * See http://www.ietf.org/rfc/rfc1867.txt. |
| */ |
| |
| import java.io.BufferedOutputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.FilterInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.SocketTimeoutException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.HashMap; |
| import java.util.Vector; |
| |
| class MultiPartHandler { |
| public interface InputFacade { |
| public int readLine(byte[] arr, int off, int len) throws IOException; |
| public InputStream getInputStream(); |
| } |
| |
| public interface RequestFacade { |
| public int getContentLength(); |
| public String getContentType(); |
| public InputFacade getInput() throws IOException; |
| } |
| |
| private static final int DEFAULT_MAX_UPLOAD_SIZE = 1024 * 1024; // 1Mb |
| |
| protected Hashtable<String,Vector<String>> formFields = new Hashtable<String,Vector<String>>(); |
| Hashtable<String,OneUpload> uploadFiles = new Hashtable<String,OneUpload>(); |
| |
| /** servlet request */ |
| private RequestFacade req; |
| |
| /** input stream to read parts from */ |
| private InputFacade in; |
| |
| /** MIME boundary that delimits parts */ |
| private String boundary; |
| |
| /** buffer for readLine method */ |
| private byte[] buf = new byte[8 * 1024]; |
| |
| /** upload directory */ |
| private File uploadDir; |
| |
| /** encoding used for the from fields */ |
| private String fieldEncoding = "ISO-8859-1"; |
| |
| // i18n StringManager |
| /*private static StringManager localStrings = |
| StringManager.getManager( OneUpload.class );*/ |
| |
| /** |
| * Instantiate a new multipart handler with default |
| */ |
| public MultiPartHandler(RequestFacade request, |
| String tmpDirectory) throws IOException { |
| this(request, tmpDirectory, DEFAULT_MAX_UPLOAD_SIZE, "ISO-8859-1"); |
| } |
| |
| public MultiPartHandler(RequestFacade request, |
| String tmpDirectory, |
| int maxUploadSize) throws IOException { |
| this(request, tmpDirectory, maxUploadSize, "ISO-8859-1"); |
| } |
| |
| /** |
| * Instantiate a new OneUpload to handle the given request, |
| * saving any uploaded files to the given directory and limiting the |
| * upload size to maxUploadSize. |
| * |
| * An IOException is thrown when the request content-type doesn't match |
| * with multipart/form-data or if the upload size exceeds the given limit. |
| * |
| * call parseMultipartUpload() to parse various parts of the posted data and then |
| * call getParameter(), getParameters(), getParameterNames() and getParameterValues() |
| * functions to access form field names and their values. |
| * call getFile(), getFileType() to access uploaded file and its content-type. |
| */ |
| public MultiPartHandler(RequestFacade request, |
| String tmpDirectory, |
| int maxUploadSize, |
| String fieldEncoding) throws IOException { |
| |
| // Ensure we are passed legal arguments |
| if (request == null) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.request_cannot_be_null" ); |
| throw new IllegalArgumentException( "request is null" ); |
| } |
| if (tmpDirectory == null) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.tmpdirectory_cannot_be_null" ); |
| throw new IllegalArgumentException( "tmp Dir is null" ); |
| } |
| if (maxUploadSize <= 0) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.maxpostsize_must_be_positive" ); |
| throw new IllegalArgumentException( "Max size is < 0" ); |
| } |
| |
| // Ensure that the directory exists and is writable (this should be a temp directory) |
| uploadDir = new File(tmpDirectory); |
| if (!uploadDir.isDirectory()) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.not_directory", tmpDirectory ); |
| throw new IllegalArgumentException( "Not a Directory" ); |
| } |
| if (!uploadDir.canWrite()) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.not_writable", tmpDirectory ); |
| throw new IllegalArgumentException("write protected" ); |
| } |
| /* |
| int length = request.getContentLength(); |
| //commented this code to remove the restriction on the file upload size. |
| /*if (length > maxUploadSize) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.posted_content_length_exceeds_limit", new Integer(length), new Integer(maxUploadSize) ); |
| throw new IOException( msg ); |
| }*/ |
| // Check the content type to make sure it's "multipart/form-data" |
| String type = request.getContentType(); |
| if (type == null || |
| !type.toLowerCase().startsWith("multipart/form-data")) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.posted_content_type_not_multipart" ); |
| throw new IOException( "type null" ); |
| } |
| |
| // Check the content length to prevent denial of service attacks |
| this.fieldEncoding = fieldEncoding; |
| this.req = request; |
| } |
| |
| /* parseMultipartUpload: |
| * |
| * This function parses the multipart/form-data and throws an IOException |
| * if there's any problem reading or parsing the request or if the posted |
| * content is larger than the maximum permissible size. |
| */ |
| public void parseMultipartUpload() |
| throws IOException { |
| // setup the initial buffered input stream, boundary string that separates |
| // various parts in the stream. |
| startMultipartParse(); |
| |
| HashMap partHeaders = parsePartHeaders(); |
| while (partHeaders != null) { |
| |
| String fieldName = (String)partHeaders.get("fieldName"); |
| String fileName = (String)partHeaders.get("fileName"); |
| |
| if (fileName != null) { |
| // This is a file upload part |
| if (fileName.equals("")) { |
| fileName = null; // empty filename, probably an "empty" file param |
| } |
| |
| if (fileName != null) { |
| // a filename was actually specified |
| String content = (String)partHeaders.get("content-type"); |
| fileName = saveUploadFile(fileName, content); |
| |
| uploadFiles.put(fieldName, |
| new OneUpload( |
| uploadDir.toString(), |
| fileName, |
| content |
| ) |
| ); |
| } |
| else { |
| uploadFiles.put(fieldName, new OneUpload(null, null, null)); |
| } |
| } |
| else { |
| // this is a parameters list part |
| byte[] valueBytes = parseFormFieldBytes(); |
| String value = new String(valueBytes, fieldEncoding); |
| |
| Vector<String> existingValues = formFields.get(fieldName); |
| if (existingValues == null) { |
| existingValues = new Vector<String>(); |
| formFields.put(fieldName, existingValues); |
| } |
| existingValues.addElement(value); |
| } |
| |
| partHeaders.clear(); |
| partHeaders = parsePartHeaders(); |
| } |
| } |
| |
| private void startMultipartParse() |
| throws IOException { |
| // Get the boundary string; it's included in the content type. |
| // Should look something like "------------------------12012133613061" |
| String boundary = parseBoundary(req.getContentType()); |
| if (boundary == null) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.separation_boundary_not_specified" ); |
| throw new IOException( "boundary is nul" ); |
| } |
| |
| this.in = req.getInput(); |
| this.boundary = boundary; |
| |
| // Read the first line, should be the first boundary |
| String line = readLine(); |
| if (line == null) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.corrupt_form_data_premature_ending" ); |
| throw new IOException( "line is null" ); |
| } |
| |
| // Verify that the line is the boundary |
| if (!line.startsWith(boundary)) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.corrupt_form_data_no_leading_boundary", line, boundary ); |
| throw new IOException( "not start with boundary" ); |
| } |
| } |
| |
| /** |
| * parse the headers of the individual part; they look like this: |
| * Content-Disposition: form-data; name="field1"; filename="file1.txt" |
| * Content-Type: type/subtype |
| * Content-Transfer-Encoding: binary |
| */ |
| private HashMap parsePartHeaders() throws IOException { |
| HashMap<String,String> partHeaders = new HashMap<String,String>(); |
| |
| Vector<String> headers = new Vector<String>(); |
| String line = readLine(); |
| if (line == null) { |
| // No parts left, we're done |
| return null; |
| } |
| else if (line.length() == 0) { |
| // IE4 on Mac sends an empty line at the end; treat that as the end. |
| return null; |
| } |
| headers.addElement(line); |
| |
| // Read the following header lines we hit an empty line |
| while ((line = readLine()) != null && (line.length() > 0)) { |
| headers.addElement(line); |
| } |
| |
| // If we got a null above, it's the end |
| if (line == null) { |
| return null; |
| } |
| |
| // default part content type (rfc1867) |
| partHeaders.put("content-type", "text/plain"); |
| |
| Enumeration ee = headers.elements(); |
| while (ee.hasMoreElements()) { |
| String headerline = (String) ee.nextElement(); |
| |
| if (headerline.toLowerCase().startsWith("content-disposition:")) { |
| // Parse the content-disposition line |
| parseContentDisposition(headerline, partHeaders); |
| } |
| else if (headerline.toLowerCase().startsWith("content-type:")) { |
| // Get the content type, or null if none specified |
| parseContentType(headerline, partHeaders); |
| } |
| } |
| |
| return partHeaders; |
| } |
| |
| /** |
| * parses and returns the boundary token from a line. |
| */ |
| private String parseBoundary(String line) { |
| // Use lastIndexOf() because IE 4.01 on Win98 has been known to send the |
| // "boundary=" string multiple times. |
| int index = line.lastIndexOf("boundary="); |
| if (index == -1) { |
| return null; |
| } |
| String boundary = line.substring(index + 9); // 9 for "boundary=" |
| if (boundary.charAt(0) == '"') { |
| // The boundary is enclosed in quotes, strip them |
| index = boundary.lastIndexOf('"'); |
| boundary = boundary.substring(1, index); |
| } |
| |
| // The real boundary is always preceeded by an extra "--" |
| boundary = "--" + boundary; |
| |
| return boundary; |
| } |
| |
| /** |
| * parses and returns content-disposition header and stores the values |
| * in the partHeaders. |
| * |
| * throws IOException if the line is malformatted. |
| */ |
| private void parseContentDisposition(String line, HashMap<String,String> partHeaders) |
| throws IOException { |
| |
| // Convert the line to a lowercase string without the ending \r\n |
| // Keep the original line for error messages and for variable names. |
| String origline = line; |
| line = origline.toLowerCase(); |
| |
| // Get the content disposition, should be "form-data" |
| int start = line.indexOf("content-disposition: "); |
| int end = line.indexOf(";"); |
| if (start == -1 || end == -1) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.content_disposition_corrupt", origline ); |
| throw new IOException( "end reached" ); |
| } |
| String disposition = line.substring(start + 21, end); |
| if (!disposition.equals("form-data")) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.invalid_content_disposition", disposition ); |
| throw new IOException( "fome-data not match" ); |
| } |
| |
| // Get the field name |
| start = line.indexOf("name=\"", end); // start at last semicolon |
| end = line.indexOf("\"", start + 7); // skip name=\" |
| if (start == -1 || end == -1) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.content_disposition_corrupt", origline ); |
| throw new IOException( "data corrupt" ); |
| } |
| |
| String name = origline.substring(start + 6, end); |
| |
| // Get the fileName, if given |
| String fileName = null; |
| String origFileName = null; |
| start = line.indexOf("filename=\"", end + 2); // start after name |
| end = line.indexOf("\"", start + 10); // skip filename=\" |
| |
| if (start != -1 && end != -1) { // note the != |
| fileName = origline.substring(start + 10, end); |
| origFileName = fileName; |
| // The filename may contain a full path. Cut to just the filename. |
| int slash = |
| Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\')); |
| if (slash > -1) { |
| fileName = fileName.substring(slash + 1); // past last slash |
| } |
| } |
| |
| // fill in the part parameters map: disposition, name, filename |
| // empty fileName denotes no file posted! |
| partHeaders.put("disposition", disposition); |
| partHeaders.put("fieldName", name); |
| partHeaders.put("fileName", fileName); |
| partHeaders.put("filePath", origFileName); |
| } |
| |
| /** |
| * parse and returns the content type from a line, or null if the |
| * line was empty. |
| */ |
| private void parseContentType(String line, HashMap<String,String> partHeaders) |
| throws IOException { |
| String contentType = null; |
| |
| // Convert the line to a lowercase string |
| String origline = line; |
| line = origline.toLowerCase(); |
| |
| // Get the content type, if any |
| if (line.startsWith("content-type")) { |
| int start = line.indexOf(" "); |
| |
| if (start == -1) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.corrupt_content_type", origline ); |
| throw new IOException( "no start" ); |
| } |
| contentType = line.substring(start + 1); |
| |
| partHeaders.put("content-type", contentType); |
| } |
| else if (line.length() != 0) { // no content type, so should be empty |
| //String msg = localStrings.getString( "admin.server.gui.servlet.malformed_line_after_disposition", origline ); |
| throw new IOException( "length 0" ); |
| } |
| } |
| |
| /** parse contents of a form field parameter; uses the encoding set by the user |
| */ |
| private byte[] parseFormFieldBytes() throws IOException { |
| |
| // Copy the part's contents into a byte array |
| MultipartInputStream pis = new MultipartInputStream(in, boundary); |
| |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(512); |
| byte[] buf = new byte[128]; |
| int read; |
| while ((read = pis.read(buf)) != -1) { |
| baos.write(buf, 0, read); |
| } |
| pis.close(); |
| baos.close(); |
| |
| // get the value bytes |
| return baos.toByteArray(); |
| } |
| |
| /** |
| * Read the next line of input. |
| * |
| * @return a String containing the next line of input from the stream, |
| * or null to indicate the end of the stream. |
| * @exception IOException if an input or output exception has occurred. |
| */ |
| private String readLine() throws IOException { |
| StringBuffer sbuf = new StringBuffer(); |
| int result; |
| String line; |
| |
| do { |
| result = in.readLine(buf, 0, buf.length); // does += |
| if (result != -1) { |
| sbuf.append(new String(buf, 0, result, StandardCharsets.ISO_8859_1)); |
| } |
| } while (result == buf.length); // loop only if the buffer was filled |
| |
| if (sbuf.length() == 0) { |
| return null; // nothing read, must be at the end of stream |
| } |
| |
| // Cut off the trailing \n or \r\n |
| // It should always be \r\n but IE5 sometimes does just \n |
| int len = sbuf.length(); |
| if (len >= 2 && sbuf.charAt(len - 2) == '\r') { |
| sbuf.setLength(len - 2); // cut \r\n |
| } |
| else { |
| sbuf.setLength(len - 1); // cut \n |
| } |
| return sbuf.toString(); |
| } |
| |
| /** |
| * Write this file part to the specified directory. |
| */ |
| private String saveUploadFile(String fileName, String content) |
| throws IOException { |
| |
| long written = 0; |
| OutputStream fileOut = null; |
| |
| File file = new File(uploadDir, fileName); |
| try { |
| // Only do something if this part contains a file |
| for (int i = 0; file.exists(); i++) { |
| if (!file.exists()) { |
| break; |
| } |
| file = new File(uploadDir, fileName + "." + i); |
| } |
| fileName = file.getName(); |
| |
| fileOut = new BufferedOutputStream(new FileOutputStream(file)); |
| int numBytes; |
| byte[] buf = new byte[8 * 1024]; |
| |
| InputStream partInput; |
| boolean canCloseStream = true; |
| if (content.equals("x-application/gzip")) { // NOI18N |
| // sending from NetBeans UI Gestures Collector |
| partInput = in.getInputStream(); |
| canCloseStream = false; |
| } else { |
| /** input stream containing file data */ |
| partInput = new MultipartInputStream(in, boundary); |
| } |
| while((numBytes = partInput.read(buf)) != -1) { |
| fileOut.write(buf, 0, numBytes); |
| written += numBytes; |
| } |
| if (canCloseStream){ |
| partInput.close(); |
| } |
| } catch (SocketTimeoutException ste) { |
| if (file.exists()){ |
| file.delete(); |
| } |
| throw ste; |
| } finally { |
| if (fileOut != null) fileOut.close(); |
| } |
| |
| return fileName; |
| } |
| |
| |
| /** |
| * Returns the names of all the parameters as an Enumeration of |
| * Strings. It returns an empty Enumeration if there are no parameters. |
| * |
| */ |
| public Enumeration getParameterNames() { |
| return formFields.keys(); |
| } |
| |
| /** |
| * Returns the names of all the uploaded files as an Enumeration of |
| * Strings. It returns an empty Enumeration if there are no uploaded |
| * files. Each file name is the name specified by the form, not by |
| * the user. |
| * |
| */ |
| public Enumeration getFileNames() { |
| return uploadFiles.keys(); |
| } |
| |
| /** |
| * Returns the value of the named parameter as a String, or null if |
| * the parameter was not sent or was sent without a value. The value |
| * is guaranteed to be in its normal, decoded form. If the parameter |
| * has multiple values, only the last one is returned (for backward |
| * compatibility). For parameters with multiple values, it's possible |
| * the last "value" may be null. |
| * |
| */ |
| public String getParameter(String name) { |
| try { |
| Vector values = (Vector)formFields.get(name); |
| if (values == null || values.size() == 0) { |
| return null; |
| } |
| String value = (String)values.elementAt(values.size() - 1); |
| return value; |
| } |
| catch (Exception e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the values of the named parameter as a String array, or null if |
| * the parameter was not sent. The array has one entry for each parameter |
| * field sent. If any field was sent without a value that entry is stored |
| * in the array as a null. The values are guaranteed to be in their |
| * normal, decoded form. A single value is returned as a one-element array. |
| * |
| */ |
| public String[] getParameterValues(String name) { |
| try { |
| Vector values = (Vector)formFields.get(name); |
| if (values == null || values.size() == 0) { |
| return null; |
| } |
| String[] valuesArray = new String[values.size()]; |
| values.copyInto(valuesArray); |
| return valuesArray; |
| } |
| catch (Exception e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the filesystem name of the specified file, or null if the |
| * file was not included in the upload. A filesystem name is the name |
| * specified by the user. It is also the name under which the file is |
| * actually saved. |
| * |
| */ |
| public String getFileName(String name) { |
| try { |
| OneUpload file = uploadFiles.get(name); |
| return file.getFileName(); // may be null |
| } |
| catch (Exception e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the content type of the specified file (as supplied by the |
| * client browser), or null if the file was not included in the upload. |
| * |
| */ |
| public String getFileType(String name) { |
| try { |
| OneUpload file = uploadFiles.get(name); |
| return file.getFileType(); // may be null |
| } |
| catch (Exception e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns a File object for the specified file saved on the server's |
| * filesystem, or null if the file was not included in the upload. |
| * |
| */ |
| public File getFile(String name) { |
| try { |
| OneUpload file = uploadFiles.get(name); |
| return file.getFile(); // may be null |
| } |
| catch (Exception e) { |
| return null; |
| } |
| } |
| |
| /** |
| * close the multi-part form handler |
| */ |
| public void close() throws IOException { |
| req = null; |
| in = null; |
| boundary = null; |
| buf = null; |
| uploadDir = null; |
| } |
| |
| |
| /** A class to hold information about an uploaded file. */ |
| private static class OneUpload { |
| |
| private String dir; |
| private String filename; |
| private String type; |
| |
| OneUpload(String dir, String filename, String type) { |
| this.dir = dir; |
| this.filename = filename; |
| this.type = type; |
| } |
| |
| public String getFileType() { |
| return type; |
| } |
| |
| public String getFileName() { |
| return filename; |
| } |
| |
| public File getFile() { |
| if (dir == null || filename == null) { |
| return null; |
| } else { |
| return new File(dir + File.separator + filename); |
| } |
| } |
| } |
| |
| /* |
| * providing access to a single MIME part contained with in which ends with |
| * the boundary specified. It uses buffering to provide maximum performance. |
| * |
| */ |
| private static class MultipartInputStream extends FilterInputStream { |
| /** boundary which "ends" the stream */ |
| private String boundary; |
| |
| /** our buffer */ |
| private byte [] buf = new byte[64*1024]; // 64k |
| |
| /** number of bytes we've read into the buffer */ |
| private int count; |
| |
| /** current position in the buffer */ |
| private int pos; |
| |
| /** flag that indicates if we have encountered the boundary */ |
| private boolean eof; |
| |
| /** associated facade */ |
| private MultiPartHandler.InputFacade facade; |
| |
| // i18n StringManager |
| /*private static StringManager localStrings = |
| StringManager.getManager( MultipartInputStream.class );*/ |
| |
| /** |
| * Instantiate a MultipartInputStream which stops at the specified |
| * boundary from an underlying ServletInputStream. |
| * |
| */ |
| MultipartInputStream(MultiPartHandler.InputFacade in, |
| String boundary) throws IOException { |
| super(in.getInputStream()); |
| this.boundary = boundary; |
| this.facade = in; |
| } |
| |
| /** |
| * Fill up our buffer from the underlying input stream, and check for the |
| * boundary that signifies end-of-file. Users of this method must ensure |
| * that they leave exactly 2 characters in the buffer before calling this |
| * method (except the first time), so that we may only use these characters |
| * if a boundary is not found in the first line read. |
| * |
| * @exception IOException if an I/O error occurs. |
| */ |
| private void fill() throws IOException |
| { |
| if (eof) |
| return; |
| |
| // as long as we are not just starting up |
| if (count > 0) |
| { |
| // if the caller left the requisite amount spare in the buffer |
| if (count - pos == 2) { |
| // copy it back to the start of the buffer |
| System.arraycopy(buf, pos, buf, 0, count - pos); |
| count -= pos; |
| pos = 0; |
| } else { |
| // should never happen, but just in case |
| //String msg = localStrings.getString( "admin.server.gui.servlet.fill_detected_illegal_buffer_state" ); |
| throw new IllegalStateException( "should never happen" ); |
| } |
| } |
| |
| // try and fill the entire buffer, starting at count, line by line |
| // but never read so close to the end that we might split a boundary |
| int read = 0; |
| int maxRead = buf.length - boundary.length(); |
| while (count < maxRead) { |
| // read a line |
| read = facade.readLine(buf, count, buf.length - count); |
| // check for eof and boundary |
| if (read == -1) { |
| //String msg = localStrings.getString( "admin.server.gui.servlet.unexpected_end_part" ); |
| throw new IOException( "read is -1" ); |
| } else { |
| if (read >= boundary.length()) { |
| eof = true; |
| for (int i=0; i < boundary.length(); i++) { |
| if (boundary.charAt(i) != buf[count + i]) { |
| // Not the boundary! |
| eof = false; |
| break; |
| } |
| } |
| if (eof) { |
| break; |
| } |
| } |
| } |
| // success |
| count += read; |
| } |
| } |
| |
| /** |
| * See the general contract of the read method of InputStream. |
| * Returns -1 (end of file) when the MIME boundary of this part is encountered. |
| * |
| * throws IOException if an I/O error occurs. |
| */ |
| public int read() throws IOException { |
| if (count - pos <= 2) { |
| fill(); |
| if (count - pos <= 2) { |
| return -1; |
| } |
| } |
| return buf[pos++] & 0xff; |
| } |
| |
| /** |
| * See the general contract of the read method of InputStream. |
| * |
| * Returns -1 (end of file) when the MIME boundary of this part |
| * is encountered. |
| * |
| * throws IOException if an I/O error occurs. |
| */ |
| public int read(byte b[]) throws IOException { |
| return read(b, 0, b.length); |
| } |
| |
| /** |
| * See the general contract of the read method of InputStream. |
| * |
| * Returns -1 (end of file) when the MIME boundary of this part is encountered. |
| * |
| * throws IOException if an I/O error occurs. |
| */ |
| public int read(byte b[], int off, int len) throws IOException |
| { |
| int total = 0; |
| if (len == 0) { |
| return 0; |
| } |
| |
| int avail = count - pos - 2; |
| if (avail <= 0) { |
| fill(); |
| avail = count - pos - 2; |
| if(avail <= 0) { |
| return -1; |
| } |
| } |
| int copy = Math.min(len, avail); |
| System.arraycopy(buf, pos, b, off, copy); |
| pos += copy; |
| total += copy; |
| |
| while (total < len) { |
| fill(); |
| avail = count - pos - 2; |
| if(avail <= 0) { |
| return total; |
| } |
| copy = Math.min(len - total, avail); |
| System.arraycopy(buf, pos, b, off + total, copy); |
| pos += copy; |
| total += copy; |
| } |
| return total; |
| } |
| |
| /** |
| * Returns the number of bytes that can be read from this input stream |
| * without blocking. This is a standard InputStream idiom |
| * to deal with buffering gracefully, and is not same as the length of the |
| * part arriving in this stream. |
| * |
| * throws IOException if an I/O error occurs. |
| */ |
| public int available() throws IOException { |
| int avail = (count - pos - 2) + in.available(); |
| // Never return a negative value |
| return (avail < 0 ? 0 : avail); |
| } |
| |
| /** |
| * Closes this input stream and releases any system resources |
| * associated with the stream. This method will read any unread data |
| * in the MIME part so that the next part starts an an expected place in |
| * the parent InputStream. |
| * |
| * throws IOException if an I/O error occurs. |
| */ |
| public void close() throws IOException { |
| if (!eof) { |
| while (read(buf, 0, buf.length) != -1) |
| ; // do nothing |
| } |
| } |
| } |
| |
| } |
| |