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

org.tinystruct.http.servlet.MultipartFormData Maven / Gradle / Ivy

Go to download

A simple framework for Java development. Simple is hard, Complex is easy. Better thinking, better design.

There is a newer version: 1.3.4
Show newest version
/*******************************************************************************
 * Copyright  (c) 2013, 2023 James Mover Zhou
 *
 * Licensed 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.tinystruct.http.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import org.tinystruct.http.Header;
import org.tinystruct.http.Request;
import org.tinystruct.transfer.http.upload.ContentDisposition;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

/**
 * The MultipartFormData class is responsible for reading the
 * input data of a multipart request and splitting it up into
 * input elements, wrapped inside of a
 * {@link org.tinystruct.transfer.http.upload.ContentDisposition ContentDisposition}
 * for easy definition.  To use this class, create a new instance
 * of MultipartFormData passing it a HttpServletRequest in the
 * constructor.  Then use the {@link #getNextPart() getNextPart}
 * method until it returns null, then you're finished.  Example: 
*
 *      MultipartFormData iterator = new MultipartFormData(request);
 *      ContentDisposition element;
 *
 *      while ((element = iterator.getNextPart()) != null) {
 *           //do something with element
 *      }
 * 
* * @see org.tinystruct.transfer.http.upload.ContentDisposition */ public class MultipartFormData { /** * The request instance for this class */ protected final Request request; /** * The input stream instance for this class */ protected ServletInputStream inputStream; /** * The boundary for this multipart request */ protected String boundary; /** * Whether or not the input stream is finished */ protected boolean end = false; /** * The amount of data read from a request at a time. * This also represents the maximum size in bytes of * a line read from the request which defaults to 4 * 1024 (4 KB) */ protected int bufferSize = 4 * 1024; public MultipartFormData(Request request) throws ServletException { this.request = request; parseRequest(); } /** * Parses a content-type String for the boundary. Appends a * "--" to the beginning of the boundary, because thats the * real boundary as opposed to the shortened one in the * content type. * * @param contentType content type * @return boundary */ public static String parseBoundary(String contentType) { if (contentType.lastIndexOf("boundary=") != -1) { String _boundary = "--" + contentType.substring(contentType.lastIndexOf("boundary=") + 9); if (_boundary.endsWith("\n")) { //strip it off return _boundary.substring(0, _boundary.length() - 1); } return _boundary; } return null; } /** * Parses the "Content-Type" line of a multipart form for a content type * * @param contentTypeString A String representing the Content-Type line, * with a trailing "\n" * @return The content type specified, or null if one can't be * found. */ public static String parseContentType(String contentTypeString) { int nameIndex = contentTypeString.indexOf("Content-Type: "); if (nameIndex != -1) { int endLineIndex = contentTypeString.indexOf("\n"); if (endLineIndex != -1) { return contentTypeString.substring(nameIndex + 14, endLineIndex); } } return null; } /** * Retrieves the "name" attribute from a content disposition line * * @param dispositionString The entire "Content-disposition" string * @return null if no name could be found, otherwise, * returns the name * @see #parseForAttribute(String, String) */ public static String parseDispositionName(String dispositionString) { return parseForAttribute("name", dispositionString); } /** * Retrieves the "filename" attribute from a content disposition line * * @param dispositionString The entire "Content-disposition" string * @return null if no filename could be found, otherwise, * returns the filename * @see #parseForAttribute(String, String) */ public static String parseDispositionFilename(String dispositionString) { return parseForAttribute("filename", dispositionString); } /** * Parses a string looking for a attribute-value pair, and returns the value. * For example: *
     *      String parseString = "Content-Disposition: filename=\"bob\" name=\"jack\"";
     *      MultipartFormData.parseForAttribute(parseString, "name");
     * 
* That will return "bob". * * @param attribute The name of the attribute you're trying to get * @param parseString The string to retrieve the value from * @return The value of the attribute, or null if none could be found */ public static String parseForAttribute(String attribute, String parseString) { int nameIndex = parseString.indexOf(attribute + "=\""); if (nameIndex != -1) { int endQuoteIndex = parseString.indexOf("\"", nameIndex + attribute.length() + 3); if (endQuoteIndex != -1) { return parseString.substring(nameIndex + attribute.length() + 2, endQuoteIndex); } } return null; } /** * Retrieves the next element in the iterator if one exists. * * @return a {@link org.tinystruct.transfer.http.upload.ContentDisposition ContentDisposition} * representing the next element in the request data */ public ContentDisposition getNextPart() { //retrieve the "Content-Disposition" header //and parse String disposition = readLine(); if ((disposition != null) && (disposition.startsWith("Content-Disposition"))) { //convert line into byte form for Content-Disposition filename and name disposition = new String(disposition.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); String name = parseDispositionName(disposition); String filename = parseDispositionFilename(disposition); String contentType = null; byte[] data; if (filename != null) { filename = new File(filename).getName(); //check for windows filenames, //from linux jdk's the entire filepath //isn't parsed correctly from File.getName() int colonIndex = filename.indexOf(":"); int slashIndex = filename.lastIndexOf("\\"); if ((colonIndex > -1) && (slashIndex > -1)) { //then consider this filename to be a full //windows filepath, and parse it accordingly //to retrieve just the file name filename = filename.substring(slashIndex + 1); } //get the content type contentType = readLine(); contentType = parseContentType(contentType); } //read data into String form, then convert to bytes //for both normal text and file StringBuilder textData = new StringBuilder(); String line; //ignore next line (whitespace) readLine(); //parse for text data line = readLine(); while ((line != null) && (!line.startsWith(boundary))) { textData.append(line); line = readLine(); } String text = textData.toString(); //remove the "\r\n" if it's there if (text.endsWith("\r\n")) { textData.setLength(textData.length() - 2); } //remove the "\n" if it's there if (text.endsWith("\n")) { textData.setLength(textData.length() - 1); } text = textData.toString(); //convert data into byte form for ContentDisposition data = text.getBytes(StandardCharsets.ISO_8859_1); return new ContentDisposition(name, filename, contentType, data); } return null; } /** * Get the maximum amount of bytes read from a line at one time * * @return buffer size * @see jakarta.servlet.ServletInputStream#readLine(byte[], int, int) */ public int getBufferSize() { return bufferSize; } /** * Set the maximum amount of bytes read from a line at one time * * @param bufferSize buffer size * @see jakarta.servlet.ServletInputStream#readLine(byte[], int, int) */ public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } /** * Handles retrieving the boundary and setting the input stream * * @throws ServletException servlet exception */ protected void parseRequest() throws ServletException { // validate the content type header if (request.headers().get(Header.CONTENT_TYPE) == null) throw new ServletException("MultipartFormData: invalid multipart request data"); // set boundary boundary = parseBoundary(request.headers().get(Header.CONTENT_TYPE).toString()); // set the input stream inputStream = request.stream(); if ((boundary == null) || (boundary.length() < 1)) { // try retrieving the header through more "normal" means boundary = parseBoundary(request.headers().get(Header.CONTENT_TYPE).toString()); } if ((boundary == null) || (boundary.length() < 1)) { throw new ServletException("MultipartFormData: cannot retrieve boundary for multipart request"); } // read first line if (!readLine().startsWith(boundary)) { throw new ServletException("MultipartFormData: invalid multipart request data"); } } /** * Reads the input stream until it reaches a new line * * @return one line */ protected String readLine() { byte[] bufferByte = new byte[bufferSize]; int bytesRead; try { bytesRead = inputStream.readLine(bufferByte, 0, bufferSize); } catch (IOException ioe) { return null; } if (bytesRead == -1) { end = true; return null; } return new String(bufferByte, 0, bytesRead, StandardCharsets.ISO_8859_1); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy