com.hfg.util.mime.MultipartMimeParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.util.mime;
import com.hfg.util.StringUtil;
import com.hfg.util.io.InputStreamSegment;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.io.*;
//------------------------------------------------------------------------------
/**
Parser for multipart MIME (Multipurpose Internet Mail Extensions).
See [RFC2045]
@author J. Alex Taylor, hairyfatguy.com
*/
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library 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.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
public class MultipartMimeParser
{
//##########################################################################
// PRIVATE FIELDS
//##########################################################################
private String mContentType;
private String mBoundary;
private String mLineSeparator;
private File mFileCacheDir;
private boolean mEndIsHere = false;
private static final String CONTENT_TYPE = "content-type:";
private static final String CONTENT_DISPOSITION = "content-disposition:";
private static Pattern sContentTypePattern = Pattern.compile("^\\s*(\\S+); boundary=(\\S+)$", Pattern.CASE_INSENSITIVE);
//##########################################################################
// PUBLIC METHODS
//##########################################################################
//--------------------------------------------------------------------------
public static boolean isMultipartMimeRequest(HttpServletRequest inRequest)
throws IOException
{
String contentTypeHeader = inRequest.getHeader("Content-Type");
return (contentTypeHeader != null && sContentTypePattern.matcher(contentTypeHeader).matches());
}
//--------------------------------------------------------------------------
public MultipartMimeParser setFileCacheDir(File inValue)
{
mFileCacheDir = inValue;
return this;
}
//--------------------------------------------------------------------------
public List parse(HttpServletRequest inRequest)
throws IOException
{
parseContentTypeFromHeader(inRequest);
return parse(inRequest.getInputStream());
}
//--------------------------------------------------------------------------
public List parse(InputStream inMimeStream)
throws IOException
{
return parse(inMimeStream, null);
}
//--------------------------------------------------------------------------
public List parse(InputStream inMimeStream, String inBoundary)
throws IOException
{
if (inBoundary != null) mBoundary = inBoundary;
List entities = new ArrayList<>();
SpecialBufferedInputStream reader = null;
try
{
reader = new SpecialBufferedInputStream(inMimeStream);
String line;
if (null == mBoundary)
{
do
{
line = reader.readLine();
}
while (line != null
&& !line.toLowerCase().startsWith(CONTENT_TYPE));
if (null == line)
{
throw new MimeException("No content-type line found!");
}
parseContentType(line.substring(line.indexOf(":")));
}
// Read to the first boundary
do
{
line = reader.readLine();
}
while (line != null
&& !line.equals("--" + mBoundary));
if (null == line)
{
throw new MimeException("Problem detecting the initial MIME entity boundary!");
}
MimeEntity entity;
while ((entity = parseNextEntity(reader)) != null)
{
entities.add(entity);
line = reader.readLine();
if (line.equals("--")) break; // Hit the last boundary.
}
}
finally
{
if (reader != null) reader.close();
}
return entities;
}
//##########################################################################
// PRIVATE METHODS
//##########################################################################
//--------------------------------------------------------------------------
private void parseContentTypeFromHeader(HttpServletRequest inRequest)
{
parseContentType(inRequest.getHeader("Content-Type"));
}
//--------------------------------------------------------------------------
private void parseContentType(String inLine)
{
// Content-Type: multipart/form-data; boundary=---------------------------29772313742745
Matcher m = sContentTypePattern.matcher(inLine);
if (m.matches())
{
mContentType = m.group(1);
mBoundary = m.group(2);
}
else
{
throw new MimeException("Failed to parse the content-type line '" + inLine + "'!");
}
}
//--------------------------------------------------------------------------
private MimeEntity parseNextEntity(SpecialBufferedInputStream inReader)
throws IOException
{
MimeEntity entity = new MimeEntity();
// Read the entity's content-disposition
readEntityHeader(entity, inReader);
// Read the entity's content
InputStreamSegment contentStream = new InputStreamSegment(inReader, getEntityEndMarker().getBytes());
if (mFileCacheDir != null
&& entity.getContentDisposition().getFilename() != null)
{
String filename = entity.getContentDisposition().getFilename().trim();
File file = new File(mFileCacheDir, filename);
if (! mFileCacheDir.exists())
{
if (! mFileCacheDir.mkdirs())
{
throw new IOException("Couldn't create dir " + StringUtil.singleQuote(mFileCacheDir.getPath()) + " as user " + System.getProperty("user.name") + "!");
}
}
entity.setCachedContentFile(file);
streamSegmentToFile(contentStream, file);
}
else
{
entity.setContent(streamSegmentToString(contentStream));
}
return entity;
}
//---------------------------------------------------------------------------
private void readEntityHeader(MimeEntity inEntity, SpecialBufferedInputStream inReader)
throws IOException
{
String line;
while ((line = inReader.readLine()) != null
&& line.length() > 0)
{
if (line.toLowerCase().startsWith(CONTENT_TYPE))
{
inEntity.setContentType(parseEntityContentTypeLine(line));
}
else if (line.toLowerCase().startsWith(CONTENT_DISPOSITION))
{
inEntity.setContentDisposition(parseEntityContentDispositionLine(line));
}
}
if (null == inEntity.getContentDisposition())
{
throw new MimeException("Mime entity without a Content-Disposition detected!");
}
}
//--------------------------------------------------------------------------
private MimeType parseEntityContentTypeLine(String inLine)
{
// Content-Type: image/png
MimeType contentType;
Pattern pattern = Pattern.compile("^\\s*Content-Type: (\\S+)$", Pattern.CASE_INSENSITIVE);
Matcher m = pattern.matcher(inLine);
if (m.matches())
{
contentType = MimeType.valueOf(m.group(1));
}
else
{
throw new MimeException("Failed to parse the entity content-type line '" + inLine + "'!");
}
return contentType;
}
//--------------------------------------------------------------------------
private MimeContentDisposition parseEntityContentDispositionLine(String inLine)
{
// Content-Disposition: form-data; name="action"
// Content-Disposition: form-data; name="upload_file"; filename="rss.png"
MimeContentDisposition contentDisposition = new MimeContentDisposition();
Pattern pattern = Pattern.compile("^\\s*Content-Disposition: (\\S+); name=\"([^\"]+)\"(?:; filename=\"([^\"]+)\")?$", Pattern.CASE_INSENSITIVE);
Matcher m = pattern.matcher(inLine);
if (m.matches())
{
contentDisposition.setType(m.group(1));
contentDisposition.setName(m.group(2));
if (m.group(3) != null)
{
contentDisposition.setFilename(m.group(3));
}
}
else
{
throw new MimeException("Failed to parse the entity content-disposition line '" + inLine + "'!");
}
return contentDisposition;
}
//---------------------------------------------------------------------------
private String getEntityEndMarker()
{
return mLineSeparator + "--" + mBoundary;
}
//---------------------------------------------------------------------------
/**
Reads the contents of the specified InputStreamSegment into a String.
*/
private String streamSegmentToString(InputStreamSegment inStream)
throws IOException
{
StringBuilder buffer = new StringBuilder();
byte[] bytes = new byte[1024];
int bytesRead = 0;
while ((bytesRead = inStream.read(bytes)) != -1)
{
buffer.append(new String(bytes, 0, bytesRead));
}
return buffer.toString();
}
//---------------------------------------------------------------------------
/**
Reads the contents of the specified InputStreamSegment into a File.
*/
private void streamSegmentToFile(InputStreamSegment inStream, File inFile)
throws IOException
{
OutputStream fileStream = null;
try
{
fileStream = new BufferedOutputStream(new FileOutputStream(inFile));
byte[] bytes = new byte[1024];
int bytesRead = 0;
while ((bytesRead = inStream.read(bytes)) != -1)
{
fileStream.write(bytes, 0, bytesRead);
}
}
finally
{
if (fileStream != null) fileStream.close();
}
}
//##########################################################################
// INNER CLASS
//##########################################################################
private class SpecialBufferedInputStream extends BufferedInputStream
{
//-----------------------------------------------------------------------
public SpecialBufferedInputStream(InputStream in)
{
super(in);
}
//-----------------------------------------------------------------------
public String readLine()
throws IOException
{
StringBuilder line = new StringBuilder();
if (!mEndIsHere)
{
do
{
int theChar = read();
if (-1 == theChar)
{
mEndIsHere = true;
break;
}
if (theChar != '\n' && theChar != '\r')
{
line.append((char)theChar);
}
else
{
if (null == mLineSeparator) mLineSeparator = (char) theChar + "";
if (mLineSeparator.equals("\r\n")
|| (theChar == '\r' && pos < count && buf[pos] == '\n'))
{
read();
if (mLineSeparator.length() == 1) mLineSeparator = "\r\n";
}
break;
}
} while (true);
}
return (mEndIsHere && 0 == line.length() ? null : line.toString());
}
}
}