Package com.elibom.jogger.http.servlet.multipart

Source Code of com.elibom.jogger.http.servlet.multipart.Multipart

package com.elibom.jogger.http.servlet.multipart;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.elibom.jogger.http.FileItem;

/**
* Provides methods to check and parse multipart requests.
*
* @author German Escobar
*/
public class Multipart {

  /**
   * HTTP content type header name.
   */
  public static final String CONTENT_TYPE = "Content-Type";

  /**
   * HTTP content disposition header name.
   */
  public static final String CONTENT_DISPOSITION = "Content-Disposition";

  /**
   * HTTP content length header name.
   */
  public static final String CONTENT_LENGTH = "Content-Length";

  /**
   * Content-disposition value for form data.
   */
  public static final String FORM_DATA = "form-data";

  /**
   * Content-disposition value for file attachment.
   */
  public static final String ATTACHMENT = "attachment";

  /**
   * Part of HTTP content type header.
   */
  public static final String MULTIPART = "multipart/";

  /**
   * HTTP content type header for multiple uploads.
   */
  public static final String MULTIPART_MIXED = "multipart/mixed";

  /**
   * Tells if a request is multipart or not.
   *
   * @param request the javax.servlet.http.HttpServletRequest that we are going to check.
   *
   * @return true if the request is multipart, false otherwise.
   */
  public static boolean isMultipartContent(HttpServletRequest request) {
    if (!"post".equals(request.getMethod().toLowerCase())) {
      return false;
    }

    String contentType = request.getContentType();
    if (contentType == null) {
      return false;
    }
    if (contentType.toLowerCase().startsWith(MULTIPART)) {
      return true;
    }

    return false;
  }

  /**
   * Parses a multipart request calling the {@link PartHandler} callbacks when a part is found.
   *
   * @param request the javax.servlet.http.HttpServletRequest that we are going to parse.
   * @param partHandler a callback handler that will be called when a part is found.
   *
   * @throws IOException if there is a problem parsing the request.
   * @throws MultipartException if the request is not multipart or if there is an error parsing the multipart
   *       request.
   */
  public void parse(HttpServletRequest request, PartHandler partHandler) throws IOException, MultipartException {
    if (!isMultipartContent(request)) {
      throw new MultipartException("Not a multipart content. The HTTP method should be 'POST' and the " +
          "Content-Type 'multipart/form-data' or 'multipart/mixed'.");
    }

    InputStream inputStream = request.getInputStream();

    String contentType = request.getContentType();
    String charEncoding = request.getCharacterEncoding();

    byte[] boundary = getBoundary(contentType);
    if (boundary == null) {
      throw new MultipartException("the request was rejected because no multipart boundary was found");
    }

    // create a multipart reader
    MultipartReader multipartReader = new MultipartReader(inputStream, boundary);
    multipartReader.setHeaderEncoding(charEncoding);

    String currentFieldName = null;
    boolean skipPreamble = true;

    for (;;) {
      boolean nextPart;
      if (skipPreamble) {
        nextPart = multipartReader.skipPreamble();
      } else {
        nextPart = multipartReader.readBoundary();
      }
      if (!nextPart) {
        if (currentFieldName == null) {
          // outer multipart terminated -> no more data
          return;
        }
        // inner multipart terminated -> return to parsing the outer
        multipartReader.setBoundary(boundary);
        currentFieldName = null;
        continue;
      }

      String headersString = multipartReader.readHeaders();
      Map<String,String> headers = getHeadersMap(headersString);

      if (currentFieldName == null) {

        // we're parsing the outer multipart
        String fieldName = getFieldName( headers.get(CONTENT_DISPOSITION) );
        if (fieldName != null) {

          String partContentType = headers.get(CONTENT_TYPE);
          if (partContentType != null &&  partContentType.toLowerCase().startsWith(MULTIPART_MIXED)) {

            // multiple files associated with this field name
            currentFieldName = fieldName;
            multipartReader.setBoundary( getBoundary(partContentType));
            skipPreamble = true;

            continue;
          }

          String fileName = getFileName( headers.get(CONTENT_DISPOSITION) );
          if (fileName == null) {
            // call the part handler
            String value = Streams.asString( multipartReader.newInputStream() );
            partHandler.handleFormItem(fieldName, value);
          } else {

            // create the temp file
            File tempFile = createTempFile(multipartReader);

            // call the part handler
            FileItem fileItem = new FileItem(fieldName, fileName, partContentType, tempFile.length(), tempFile, headers);
            partHandler.handleFileItem(fieldName, fileItem);
          }

          continue;
        }
      } else {
        String fileName = getFileName( headers.get(CONTENT_DISPOSITION) );
        String partContentType = headers.get(CONTENT_TYPE);
        if (fileName != null) {

          // create the temp file
          File tempFile = createTempFile(multipartReader);

          // call the part handler
          FileItem fileItem = new FileItem(currentFieldName, fileName, partContentType, tempFile.length(),
              tempFile, headers);
          partHandler.handleFileItem(currentFieldName, fileItem);
          continue;
        }
      }
      multipartReader.discardBodyData();
    }

  }

  private File createTempFile(MultipartReader multipartReader) throws IOException {
    File tempFile = File.createTempFile("com.elibom.jogger.file_", null);
    FileOutputStream outputStream = null;
    try {
      outputStream = new FileOutputStream(tempFile);
      copy( multipartReader.newInputStream(), outputStream );
    } finally {
      if (outputStream != null) {
        try { outputStream.close(); } catch (Exception e) {}
      }
    }

    return tempFile;
  }

  private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
  private static final int EOF = -1;

  private long copy(InputStream input, OutputStream output) throws IOException {
    long count = 0;
    int n = 0;
    byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
    while (EOF != (n = input.read(buffer))) {
      output.write(buffer, 0, n);
      count += n;
    }
    return count;
  }

  /**
   * Retreives a map with the headers of a part.
   *
   * @param headerPart a String object with the contents of the part header.
   *
   * @return a Map<String,String> object with the headers of the part.
   */
  protected Map<String,String> getHeadersMap(String headerPart) {
    final int len = headerPart.length();
    final Map<String,String> headers = new HashMap<String,String>();

    int start = 0;
    for (;;) {
      int end = parseEndOfLine(headerPart, start);
      if (start == end) {
        break;
      }
      String header = headerPart.substring(start, end);
      start = end + 2;
      while (start < len) {
        int nonWs = start;
        while (nonWs < len) {
          char c = headerPart.charAt(nonWs);
          if (c != ' '  &&  c != '\t') {
            break;
          }
          ++nonWs;
        }
        if (nonWs == start) {
          break;
        }
        // continuation line found
        end = parseEndOfLine(headerPart, nonWs);
        header += " " + headerPart.substring(nonWs, end);
        start = end + 2;
      }

      // parse header line
      final int colonOffset = header.indexOf(':');
      if (colonOffset == -1) {
        // this header line is malformed, skip it.
        continue;
      }
      String headerName = header.substring(0, colonOffset).trim();
      String headerValue = header.substring(header.indexOf(':') + 1).trim();

      if (headers.containsKey(headerName)) {
        headers.put( headerName, headers.get(headerName) + "," + headerValue );
      } else {
        headers.put(headerName, headerValue);
      }
    }

    return headers;
  }

  /**
   * Skips bytes until the end of the current line.
   *
   * @param headerPart the headers, which are being parsed.
   * @param end index of the last byte, which has yet been processed.
   *
   * @return index of the \r\n sequence, which indicates end of line.
   */
  private int parseEndOfLine(String headerPart, int end) {
    int index = end;
    for (;;) {
      int offset = headerPart.indexOf('\r', index);
      if (offset == -1 || offset + 1 >= headerPart.length()) {
        throw new IllegalStateException("Expected headers to be terminated by an empty line.");
      }
      if (headerPart.charAt(offset + 1) == '\n') {
        return offset;
      }
      index = offset + 1;
    }
  }

  /**
   * Retrieves the name of the field from the Content-Disposition header of the part.
   *
   * @param contentDisposition the value of the Content-Disposition header.
   *
   * @return a String object that holds the name of the field to which this part is associated.
   */
  private String getFieldName(String contentDisposition) {
    String fieldName = null;

    if (contentDisposition != null && contentDisposition.toLowerCase().startsWith(FORM_DATA)) {
      ParameterParser parser = new ParameterParser();
      parser.setLowerCaseNames(true);

      // parameter parser can handle null input
      Map<String,String> params = parser.parse(contentDisposition, ';');
      fieldName = (String) params.get("name");
      if (fieldName != null) {
        fieldName = fieldName.trim();
      }
    }

    return fieldName;
  }

  /**
   * Retrieves the boundary that is used to separate the request parts from the Content-Type header.
   *
   * @param contentType the value of the Content-Type header.
   *
   * @return a byte array with the boundary.
   */
  protected byte[] getBoundary(String contentType) {
    ParameterParser parser = new ParameterParser();
    parser.setLowerCaseNames(true);
    // Parameter parser can handle null input
    Map<String,String> params = parser.parse(contentType, new char[] {';', ','});
    String boundaryStr = (String) params.get("boundary");

    if (boundaryStr == null) {
      return null;
    }

    byte[] boundary;
    try {
      boundary = boundaryStr.getBytes("ISO-8859-1");
    } catch (UnsupportedEncodingException e) {
      boundary = boundaryStr.getBytes();
    }
    return boundary;
  }

  /**
   * Retrieves the file name of a file from the filename attribute of the Content-Disposition header of the part.
   *
   * @param contentDisposition the value of the Content-Disposition header.
   *
   * @return a String object that holds the name of the file.
   */
  private String getFileName(String contentDisposition) {
    String fileName = null;

    if (contentDisposition != null) {
      String cdl = contentDisposition.toLowerCase();

      if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {

        ParameterParser parser = new ParameterParser();
        parser.setLowerCaseNames(true);

        // parameter parser can handle null input
        Map<String,String> params = parser.parse(contentDisposition, ';');
        if (params.containsKey("filename")) {
          fileName = (String) params.get("filename");
          if (fileName != null) {
            fileName = fileName.trim();
          } else {
            // even if there is no value, the parameter is present,
            // so we return an empty file name rather than no file
            // name.
            fileName = "";
          }
        }
      }
    }

    return fileName;
  }

}
TOP

Related Classes of com.elibom.jogger.http.servlet.multipart.Multipart

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.