All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wings.session.MultipartRequest Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2000,2005 wingS development team.
 *
 * This file is part of wingS (http://wingsframework.org).
 *
 * wingS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * Please see COPYING for the complete licence.
 */
package org.wings.session;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wings.UploadFilterManager;
import org.wings.util.LocaleCharSet;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.net.URLEncoder;
import java.util.*;

/**
 * A utility class to handle multipart/form-data requests,
 * the kind of requests that support file uploads.  This class can
 * receive arbitrarily large files (up to an artificial limit you can set),
 * and fairly efficiently too. And it knows and works around several browser
 * bugs that don't know how to upload files correctly.
 * 

* A client can upload files using an HTML form with the following structure. * Note that not all browsers support file uploads. *

 * <FORM ACTION="/servlet/Handler" METHOD=POST
 *          ENCTYPE="multipart/form-data">
 * What is your name? <INPUT TYPE=TEXT NAME=submitter> <BR>
 * Which file to upload? <INPUT TYPE=FILE NAME=file> <BR>
 * <INPUT TYPE=SUBMIT>
 * </FORM>
 * 
*

* The full file upload specification is contained in experimental RFC 1867, * available at * http://ds.internic.net/rfc/rfc1867.txt. * * @author Holger Engels */ public final class MultipartRequest extends HttpServletRequestWrapper { private final transient static Logger log = LoggerFactory.getLogger(MultipartRequest.class); private static final int DEFAULT_MAX_POST_SIZE = 1024 * 1024; // 1 Meg private int maxSize; private boolean urlencodedRequest; private Map parameters; // name - value private Map files; // name - UploadedFile private Map parameterMap; // name - values /** * @param request the servlet request * @throws IOException if the uploaded content is larger than 1 Megabyte * or there's a problem reading or parsing the request */ public MultipartRequest(HttpServletRequest request) throws IOException { this(request, DEFAULT_MAX_POST_SIZE); } /** * @param request the servlet request * @param maxPostSize the maximum size of the POST content * @throws IOException if the uploaded content is larger than * maxPostSize or there's a problem reading or parsing the request */ public MultipartRequest(HttpServletRequest request, int maxPostSize) throws IOException { super(request); if (request == null) throw new IllegalArgumentException("request cannot be null"); if (maxPostSize <= 0) throw new IllegalArgumentException("maxPostSize must be positive"); maxSize = maxPostSize; processRequest(request); } /** * Returns the names of all the parameters as an Enumeration of * Strings. It returns an empty Enumeration if there are no parameters. * * @return the names of all the parameters as an Enumeration of Strings */ @Override public Enumeration getParameterNames() { if (urlencodedRequest) return super.getParameterNames(); final Iterator iter = parameters.keySet().iterator(); return new MyEnumeration(iter); } /** * 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. * * @return the names of all the uploaded files as an Enumeration of Strings */ public Iterator getFileNames() { if (urlencodedRequest) return Collections.emptySet().iterator(); return files.keySet().iterator(); } @Override public String getParameter(String name) { if (urlencodedRequest) return super.getParameter(name); List v = parameters.get(name); if ( v == null || v.isEmpty() ) return null; return v.get( 0 ); } @Override public String[] getParameterValues(String name) { if (urlencodedRequest) return super.getParameterValues(name); List v = parameters.get(name); if (v == null) return null; String result[] = new String[v.size()]; return (String[]) v.toArray(result); } @Override public Map getParameterMap() { if (urlencodedRequest) return super.getParameterMap(); if (parameterMap == null) { parameterMap = new HashMap<>(); for (Map.Entry entry : parameters.entrySet()) { List list = entry.getValue(); String[] values = (String[]) list.toArray(new String[list.size()]); parameterMap.put(entry.getKey(), values); } } return parameterMap; } /** * Returns the filename of the specified file, or null if the * file was not included in the upload. The filename is the name * specified by the user. It is not the name under which the file is * actually saved. * * @param name the file name * @return the filesystem name of the file */ public String getFileName(String name) { try { return files.get(name).getFileName(); } catch (Exception e) { return null; } } /** * Returns the fileid of the specified file, or null if the * file was not included in the upload. The fileid is the name * under which the file is saved in the filesystem. * * @param name the file name * @return the filesystem name of the file */ public String getFileId(String name) { try { return files.get(name).getId(); } 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. * * @param name the file name * @return the content type of the file */ public String getContentType(String name) { try { return files.get(name).getContentType(); } 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. * * @param name the file name * @return a File object for the named file */ public File getFile(String name) { try { return files.get(name).getFile(); } catch (Exception e) { return null; } } /** * Indicates if this class was successfully able to parse request as multipart request. */ public final boolean isMultipart() { return !urlencodedRequest; } /** * Store exception as request parameter. */ protected void setException(String param, Exception ex) { if (!urlencodedRequest) { // I'm not 100% sure if it's ok to comment out the following line! // However, if we delete all parameters that have been set before // the occurence of the exception, the only component that will get // triggered during event processing is the filechooser. But since // a filechooser provides no ability to register any listeners, the // developer has no chance to get informed about the exception in // the application code. There is only one reason I can imaging why // someone set this line: if other components have been placed below // the filechooser on the GUI, their parts won't get processed by // the MultipartRequest, the according parameters won't be set and // therefore no event processing of such components is done. If we // process components above the exception raising filechooser but // not components below it, we might end up in an inconsitent state. // Anyway, I think it's the better solution to leave it out here!!! // // -- stephan // // parameters.clear(); files.clear(); } putParameter(param, "exception"); putParameter(param, ex.getMessage()); } /** * Parses passed request and stores contained parameters. * * @throws IOException On unrecoverable parsing bugs due to old Tomcat version. */ protected void processRequest(HttpServletRequest req) throws IOException { String type = req.getContentType(); if (type == null || !type.toLowerCase().startsWith("multipart/form-data")) { urlencodedRequest = true; return; } urlencodedRequest = false; parameters = new HashMap<>(); files = new HashMap<>(); for (Map.Entry stringEntry : req.getParameterMap().entrySet()) { Map.Entry entry = (Map.Entry) stringEntry; parameters.put((String) entry.getKey(), new ArrayList(Arrays.asList((String[]) entry.getValue()))); } String boundaryToken = extractBoundaryToken(type); if (boundaryToken == null) { /* * this could happen due to a bug in Tomcat 3.2.2 in combination * with Opera. * Opera sends the boundary on a separate line, which is perfectly * correct regarding the way header may be constructed * (multiline headers). Alas, Tomcat fails to read the header in * the content type line and thus we cannot read it.. haven't * checked later versions of Tomcat, but upgrading is * definitly needed, since we cannot do anything about it here. * (JServ works fine, BTW.) (Henner) */ throw new IOException("Separation boundary was not specified (BUG in Tomcat 3.* with Opera?)"); } MultipartInputStream mimeStream = null; String currentParam = null; File uploadFile = null; OutputStream fileStream = null; try { mimeStream = new MultipartInputStream(req.getInputStream(), req.getContentLength(), maxSize); int last = -1; int currentTransformByte = 0; int currentPos = 0; int currentByte = 0; HashMap headers = null; StringBuilder content = new StringBuilder(); while (currentByte != -1) { // Read MIME part header line boolean done = false; ByteArrayOutputStream headerByteArray = new ByteArrayOutputStream(); while ((currentByte = mimeStream.read()) != -1 && !done) { headerByteArray.write(currentByte); done = (last == '\n' && currentByte == '\r'); last = currentByte; } if (currentByte == -1) break; headers = parseHeader(headerByteArray.toString(req.getCharacterEncoding())); headerByteArray.reset(); currentParam = (String) headers.get("name"); if (headers.size() == 1) { // .. it's not a file byte[] bytes = new byte[req.getContentLength()]; currentPos = 0; while ((currentByte = mimeStream.read()) != -1) { bytes[currentPos] = (byte) currentByte; currentPos++; if (currentPos >= boundaryToken.length()) { int i; for (i = 0; i < boundaryToken.length(); i++) { if (boundaryToken.charAt(boundaryToken.length() - i - 1) != bytes[currentPos - i - 1]) { i = 0; break; } } if (i == boundaryToken.length()) { // end of part .. ByteArrayInputStream bais = new ByteArrayInputStream(bytes, 0, currentPos - boundaryToken.length() - 4); InputStreamReader ir; if (req.getCharacterEncoding() != null) // It's common behaviour of browsers to encode their form input in the character // encoding of the page, though they don't declare the used characterset explicetly // for backward compatibility. ir = new InputStreamReader(bais, req.getCharacterEncoding()); else ir = new InputStreamReader(bais); content.setLength(0); while ((currentTransformByte = ir.read()) != -1) { content.append((char) currentTransformByte); } putParameter(currentParam, content.toString()); break; } } } } else { // .. it's a file String filename = (String) headers.get("filename"); if (filename != null && filename.length() != 0) { // 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); } String name = (String) headers.get("name"); String contentType = (String) headers.get("content-type"); try { uploadFile = File.createTempFile("wings_uploaded", "tmp"); } catch (IOException e) { log.error("couldn't create temp file in '" + System.getProperty("java.io.tmpdir") + "' (CATALINA_TMPDIR set correctly?)", e); throw e; } UploadedFile upload = new UploadedFile(filename, contentType, uploadFile); fileStream = new FileOutputStream(uploadFile); fileStream = UploadFilterManager.createFilterInstance(name, fileStream); AccessibleByteArrayOutputStream byteArray = new AccessibleByteArrayOutputStream(); int blength = boundaryToken.length(); while ((currentByte = mimeStream.read()) != -1) { byteArray.write(currentByte); int i; for (i = 0; i < blength; i++) { if (boundaryToken.charAt(blength - i - 1) != byteArray.charAt(-i - 1)) { i = 0; if (byteArray.size() > 512 + blength + 2) byteArray.writeTo(fileStream, 512); break; } } if (i == blength) // end of part .. break; } byte[] bytes = byteArray.toByteArray(); fileStream.write(bytes, 0, bytes.length - blength - 4); fileStream.close(); files.put(name, upload); putParameter(name, upload.toString()); } else { // workaround for some netscape bug int blength = boundaryToken.length(); while ((currentByte = mimeStream.read()) != -1) { content.append((char) currentByte); if (content.length() >= blength) { int i; for (i = 0; i < blength; i++) { if (boundaryToken.charAt(blength - i - 1) != content.charAt(content.length() - i - 1)) { i = 0; break; } } if (i == blength) break; } } } } currentByte = mimeStream.read(); if (currentByte == '\r' && mimeStream.read() != '\n') log.error("No line return char?"); if (currentByte == '-' && mimeStream.read() != '-') log.error("?? No clue"); } } catch (IOException ex) { // cleanup and store the exception for notification of SFileChooser log.warn("upload", ex); if (uploadFile != null) uploadFile.delete(); setException(currentParam, ex); } finally { try { fileStream.close(); } catch (Exception ign) {} try { mimeStream.close(); } catch (Exception ign) {} } } private static class AccessibleByteArrayOutputStream extends ByteArrayOutputStream { public byte charAt(int index) { if (count + index < 0) { log.warn("count: " + count + ", index: " + index + ", buffer: " + new String(buf)); return -1; } if (index < 0) return buf[count + index]; if (index < count) return buf[index]; return -1; } public byte[] getBuffer() { return buf; } public void writeTo(OutputStream out, int num) throws IOException { out.write(buf, 0, num); System.arraycopy(buf, num, buf, 0, count - num); count = count - num; } } private static HashMap parseHeader(String header) { StringTokenizer stLines = new StringTokenizer(header, "\r\n", false); String[] headerLines = new String[stLines.countTokens()]; // Get all the header lines int lastHeader = -1; while (stLines.hasMoreTokens()) { String hLine = stLines.nextToken(); if (hLine.length() == 0) continue; /* if the first character is a space, then * this line is a header continuation. * (opera sends multiline headers..) */ if (lastHeader >= 0 && Character.isWhitespace(hLine.charAt(0))) headerLines[lastHeader] += hLine; else headerLines[++lastHeader] = hLine; } HashMap nameValuePairs = new HashMap(); for (int i = 0; i <= lastHeader; ++i) { String currentHeader = headerLines[i]; if (currentHeader.startsWith("Content-Type")) { String contentType = currentHeader .substring(currentHeader.indexOf(':') + 1); int semiColonPos = contentType.indexOf(';'); if (semiColonPos != -1) contentType = contentType.substring(0, semiColonPos); nameValuePairs.put("content-type", contentType.trim()); continue; } if (!currentHeader.startsWith("Content-Disposition")) continue; StringTokenizer stTokens = new StringTokenizer(currentHeader, ";", false); // Get all the tokens from each line if (stTokens.countTokens() > 1) { stTokens.nextToken(); // Skip fist Token Content-Disposition: form-data StringTokenizer stnameValue = new StringTokenizer(stTokens.nextToken(), "=", false); nameValuePairs.put(stnameValue.nextToken().trim(), trim(stnameValue.nextToken(), "\"")); // This is a file if (stTokens.hasMoreTokens()) { stnameValue = new StringTokenizer(stTokens.nextToken(), "=", false); String formType = stnameValue.nextToken().trim(); // String Object default function String filePath = trim(stnameValue.nextToken(), "\""); // Our own trim function. // If is a DOS file get rid of drive letter and colon "e:" if (filePath.contains(":")) filePath = filePath.substring((filePath.indexOf(':') + 1)); // get rid of PATH filePath = filePath.substring(filePath.lastIndexOf(File.separator) + 1); nameValuePairs.put(formType, filePath); } } } return nameValuePairs; } /** * This method gets the substring enclosed in trimChar ; "string" returns string */ private static String trim(String source, String trimChar) { //Blank space from both sides source = source.trim(); // Make sure a substring is enclosed between specified characters String target = ""; if (source.contains(trimChar) && (source.lastIndexOf(trimChar) >= (source.indexOf(trimChar) + 1))) // Remove double character from both sides target = source.substring(source.indexOf(trimChar) + 1, source.lastIndexOf(trimChar)); return target; } private static class MultipartInputStream extends InputStream { ServletInputStream istream = null; int len, pos, maxLength; public MultipartInputStream(ServletInputStream istream, int len, int maxLength) { this.istream = istream; this.len = len; this.pos = 0; this.maxLength = maxLength; } /** * @return bytes available in stream. */ @Override public int available() throws IOException { return len - pos - 1; } /** * @return Next byte in Request. * @throws IOException */ @Override public int read() throws IOException { if (pos >= maxLength) throw new IOException("Size (" + len + ") exceeds maxlength " + maxLength); if (pos >= len) return -1; pos++; return istream.read(); } @Override public int read(byte b[]) throws IOException { return read(b, 0, b.length); } @Override public int read(byte b[], int off, int num) throws IOException { if (off > 0) istream.skip(off); if (pos >= len) return -1; if (num > len - pos) num = len - pos; num = istream.read(b, 0, num); pos += num; if (pos >= maxLength) throw new IOException("Size (" + len + ") exceeds maxlength " + maxLength); return num; } @Override public long skip(long num) throws IOException { if (pos >= len) return -1; if (num > len - pos) num = len - pos; num = istream.skip(num); pos += num; if (pos >= maxLength) throw new IOException("Size (" + len + ") exceeds maxlength " + maxLength); return num; } @Override public void close() throws IOException { //Ignore closing of the input stream .. } } /** * Stores a parameter identified in this request. */ protected void putParameter(String name, String value) { ArrayList v = (ArrayList) parameters.get(name); // there is no Parameter yet; create one if (v == null) { v = new ArrayList(2); parameters.put(name, v); } v.add(value); } /** * Extracts and returns the boundary token from a line. */ private static String extractBoundaryToken(String line) { int index = line.indexOf("boundary="); if (index == -1) { return null; } String boundary = line.substring(index + 9); // 9 for "boundary=" // The real boundary is always preceeded by an extra "--" //boundary = "--" + boundary; return boundary; } /** * Extracts and returns the content type from a line, or null if the line was empty. * * @throws IOException if the line is malformatted. */ private static String extractContentType(String line) throws IOException { // Convert the line to a lowercase string String origline = line; line = origline.toLowerCase(); // Get the content type, if any String contentType = null; if (line.startsWith("content-type")) { int start = line.indexOf(' '); if (start == -1) { throw new IOException("Content type corrupt: " + origline); } contentType = line.substring(start + 1); } else if (line.length() != 0) { // no content type, so should be empty throw new IOException("Malformed line after disposition: " + origline); } return contentType; } private static long uniqueId = 0; private static final synchronized String uniqueId() { uniqueId++; return System.currentTimeMillis() + "." + uniqueId; } private static class MyEnumeration implements Enumeration { private final Iterator iter; public MyEnumeration(Iterator iter) { this.iter = iter; } @Override public boolean hasMoreElements() { return iter.hasNext(); } @Override public Object nextElement() { return iter.next(); } } /** * A class to hold information about an uploaded file. */ class UploadedFile { private String fileName; private String type; private File uploadedFile; UploadedFile(String fileName, String type, File f) { this.uploadedFile = f; this.fileName = fileName; this.type = type; } /** * @return Path of uploaded file */ public String getDir() { if (uploadedFile != null) return uploadedFile.getParentFile().getPath(); else return null; } /** * @return Filename passed by browser */ public String getFileName() { return fileName; } /** * @return MIME type passed by browser */ public String getContentType() { return type; } /** * @return Uploaded file */ public File getFile() { return uploadedFile; } /** * @return Uploaded file name */ public String getId() { if (uploadedFile != null) return uploadedFile.getName(); else return null; } /** * create a URL-encoded form of this uploaded file, that contains * all parameters important for this file. The parameters returned * are 'dir', 'name', 'type' and 'id' *

    *
  • 'dir' contains the directory in the filesystem, the file * has been stored into.
  • *
  • 'name' contains the filename as provided by the user
  • *
  • 'type' contains the mime-type of this file.
  • *
  • 'id' contains the internal name of the file in the * filesystem.
  • *
*/ public String toString() { String encoding = getRequest().getCharacterEncoding() != null ? getRequest().getCharacterEncoding() : LocaleCharSet.DEFAULT_ENCODING; try { StringBuilder buffer = new StringBuilder(); buffer.append("dir="); buffer.append(URLEncoder.encode(getDir(), encoding)); if (fileName != null) { buffer.append("&name="); buffer.append(URLEncoder.encode(fileName, encoding)); } if (type != null) { buffer.append("&type="); buffer.append(URLEncoder.encode(type, encoding)); } buffer.append("&id="); buffer.append(URLEncoder.encode(getId(), encoding)); return buffer.toString(); } catch (UnsupportedEncodingException e) { log.error(getClass().getName(), e); return null; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy