391188 Files written with Request.getPart().write(filename) should not be auto-deleted

This commit is contained in:
Jan Bartel 2012-10-08 11:09:41 +11:00
parent 36a2ed10be
commit 31807b7892
4 changed files with 260 additions and 53 deletions

View File

@ -127,7 +127,7 @@ public class Request implements HttpServletRequest
{
public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.multipartConfig";
public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.multiPartInputStream";
public static final String __MULTIPART_CONTEXT = "org.eclipse.multiPartContext";
private static final Logger LOG = Log.getLogger(Request.class);
private static final String __ASYNC_FWD = "org.eclipse.asyncfwd";
@ -144,14 +144,20 @@ public class Request implements HttpServletRequest
MultiPartInputStream mpis = (MultiPartInputStream)sre.getServletRequest().getAttribute(__MULTIPART_INPUT_STREAM);
if (mpis != null)
{
try
{
mpis.deleteParts();
}
catch (MultiException e)
{
sre.getServletContext().log("Errors deleting multipart tmp files", e);
}
ContextHandler.Context context = (ContextHandler.Context)sre.getServletRequest().getAttribute(__MULTIPART_CONTEXT);
//Only do the cleanup if we are exiting from the context in which a servlet parsed the multipart files
if (context == sre.getServletContext())
{
try
{
mpis.deleteParts();
}
catch (MultiException e)
{
sre.getServletContext().log("Errors deleting multipart tmp files", e);
}
}
}
}
@ -1468,24 +1474,6 @@ public class Request implements HttpServletRequest
if (_savedNewSessions != null)
_savedNewSessions.clear();
_savedNewSessions=null;
if (_multiPartInputStream != null)
{
Collection<Part> parts = _multiPartInputStream.getParsedParts();
if (parts != null)
{
for (Part p:parts)
{
try
{
p.delete();
}
catch (IOException e)
{
LOG.warn("Error deleting multipart file", e);
}
}
}
}
_multiPartInputStream = null;
}
@ -2007,10 +1995,16 @@ public class Request implements HttpServletRequest
if (_multiPartInputStream == null)
{
MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
if (config == null)
throw new IllegalStateException("No multipart config for servlet");
_multiPartInputStream = new MultiPartInputStream(getInputStream(),
getContentType(),(MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT),
getContentType(),config,
(_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
setAttribute(__MULTIPART_CONTEXT, _context);
Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing
for (Part p:parts)
{
@ -2039,10 +2033,17 @@ public class Request implements HttpServletRequest
if (_multiPartInputStream == null)
{
MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
if (config == null)
throw new IllegalStateException("No multipart config for servlet");
_multiPartInputStream = new MultiPartInputStream(getInputStream(),
getContentType(),(MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT),
getContentType(), config,
(_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
setAttribute(__MULTIPART_CONTEXT, _context);
Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing
for (Part p:parts)
{

View File

@ -37,7 +37,9 @@ import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.ServletRequestEvent;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -49,6 +51,7 @@ import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartInputStream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.junit.After;
@ -131,7 +134,7 @@ public class RequestTest
}
@Test
public void testMultiPart() throws Exception
public void testMultiPartNoConfig() throws Exception
{
_handler._checker = new RequestTester()
{
@ -140,14 +143,16 @@ public class RequestTest
try
{
Part foo = request.getPart("stuff");
assertNotNull(foo);
String value = request.getParameter("stuff");
byte[] expected = "000000000000000000000000000000000000000000000000000".getBytes("ISO-8859-1");
return value.equals(new String(expected, "ISO-8859-1"));
return false;
}
catch (IllegalStateException e)
{
//expected exception because no multipart config is set up
assertTrue(e.getMessage().startsWith("No multipart config"));
return true;
}
catch (Exception e)
{
e.printStackTrace();
return false;
}
}
@ -174,6 +179,66 @@ public class RequestTest
String responses=_connector.getResponses(request);
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
@Test
public void testMultiPart() throws Exception
{
final File tmpDir = new File (System.getProperty("java.io.tmpdir"));
final File testTmpDir = new File (tmpDir, "reqtest");
testTmpDir.deleteOnExit();
assertTrue(testTmpDir.mkdirs());
assertTrue(testTmpDir.list().length == 0);
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/foo");
contextHandler.setResourceBase(".");
contextHandler.setHandler(new MultiPartRequestHandler(testTmpDir));
contextHandler.addEventListener(new Request.MultiPartCleanerListener()
{
@Override
public void requestDestroyed(ServletRequestEvent sre)
{
MultiPartInputStream m = (MultiPartInputStream)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM);
ContextHandler.Context c = (ContextHandler.Context)sre.getServletRequest().getAttribute(Request.__MULTIPART_CONTEXT);
assertNotNull (m);
assertNotNull (c);
assertTrue(c == sre.getServletContext());
assertTrue(!m.getParsedParts().isEmpty());
assertTrue(testTmpDir.list().length == 2);
super.requestDestroyed(sre);
String[] files = testTmpDir.list();
assertTrue(files.length == 0);
}
});
_server.stop();
_server.setHandler(contextHandler);
_server.start();
String multipart = "--AaB03x\r\n"+
"content-disposition: form-data; name=\"field1\"\r\n"+
"\r\n"+
"Joe Blow\r\n"+
"--AaB03x\r\n"+
"content-disposition: form-data; name=\"stuff\"; filename=\"foo.upload\"\r\n"+
"Content-Type: text/plain;charset=ISO-8859-1\r\n"+
"\r\n"+
"000000000000000000000000000000000000000000000000000\r\n"+
"--AaB03x--\r\n";
String request="GET /foo/x.html HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: multipart/form-data; boundary=\"AaB03x\"\r\n"+
"Content-Length: "+multipart.getBytes().length+"\r\n"+
"\r\n"+
multipart;
String responses=_connector.getResponses(request);
System.err.println(responses);
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
@Test
public void testBadUtf8ParamExtraction() throws Exception
@ -912,4 +977,43 @@ public class RequestTest
}
}
private class MultiPartRequestHandler extends AbstractHandler
{
File tmpDir;
public MultiPartRequestHandler(File tmpDir)
{
this.tmpDir = tmpDir;
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
((Request)request).setHandled(true);
try
{
MultipartConfigElement mpce = new MultipartConfigElement(tmpDir.getAbsolutePath(),-1, -1, 2);
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
Part foo = request.getPart("stuff");
assertNotNull(foo);
assertTrue(foo.getSize() > 0);
response.setStatus(200);
}
catch (IllegalStateException e)
{
//expected exception because no multipart config is set up
assertTrue(e.getMessage().startsWith("No multipart config"));
response.setStatus(200);
}
catch (Exception e)
{
response.sendError(500);
}
}
}
}

View File

@ -74,6 +74,7 @@ public class MultiPartInputStream
protected String _contentType;
protected MultiMap<String> _headers;
protected long _size = 0;
protected boolean _temporary = true;
public MultiPart (String name, String filename)
throws IOException
@ -144,7 +145,6 @@ public class MultiPartInputStream
_file = File.createTempFile("MultiPart", "", MultiPartInputStream.this._tmpDir);
if (_deleteOnExit)
_file.deleteOnExit();
FileOutputStream fos = new FileOutputStream(_file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
@ -207,11 +207,12 @@ public class MultiPartInputStream
{
if (_file != null)
{
//written to a file, whether temporary or not
return new BufferedInputStream (new FileInputStream(_file));
}
else
{
//part content is in a ByteArrayOutputStream
//part content is in memory
return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
}
}
@ -246,10 +247,11 @@ public class MultiPartInputStream
{
if (_file == null)
{
_temporary = false;
//part data is only in the ByteArrayOutputStream and never been written to disk
_file = new File (_tmpDir, fileName);
if (_deleteOnExit)
_file.deleteOnExit();
BufferedOutputStream bos = null;
try
{
@ -267,23 +269,36 @@ public class MultiPartInputStream
else
{
//the part data is already written to a temporary file, just rename it
_temporary = false;
File f = new File(_tmpDir, fileName);
if (_deleteOnExit)
f.deleteOnExit();
if (_file.renameTo(f))
_file = f;
}
}
/**
* Remove the file, whether or not Part.write() was called on it
* (ie no longer temporary)
* @see javax.servlet.http.Part#delete()
*/
public void delete() throws IOException
{
if (_file != null)
if (_file != null && _file.exists())
_file.delete();
}
/**
* Only remove tmp files.
*
* @throws IOException
*/
public void cleanUp() throws IOException
{
if (_temporary && _file != null && _file.exists())
_file.delete();
}
/**
* Get the file, if any, the data has been written to.
@ -327,6 +342,11 @@ public class MultiPartInputStream
_config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
}
/**
* Get the already parsed parts.
*
* @return
*/
public Collection<Part> getParsedParts()
{
if (_parts == null)
@ -342,6 +362,11 @@ public class MultiPartInputStream
return parts;
}
/**
* Delete any tmp storage for parts, and clear out the parts list.
*
* @throws MultiException
*/
public void deleteParts ()
throws MultiException
{
@ -351,17 +376,26 @@ public class MultiPartInputStream
{
try
{
p.delete();
((MultiPartInputStream.MultiPart)p).cleanUp();
}
catch(Exception e)
{
err.add(e);
}
}
_parts.clear();
err.ifExceptionThrowMulti();
}
/**
* Parse, if necessary, the multipart data and return the list of Parts.
*
* @return
* @throws IOException
* @throws ServletException
*/
public Collection<Part> getParts()
throws IOException, ServletException
{
@ -377,6 +411,14 @@ public class MultiPartInputStream
}
/**
* Get the named Part.
*
* @param name
* @return
* @throws IOException
* @throws ServletException
*/
public Part getPart(String name)
throws IOException, ServletException
{
@ -385,6 +427,12 @@ public class MultiPartInputStream
}
/**
* Parse, if necessary, the multipart stream.
*
* @throws IOException
* @throws ServletException
*/
protected void parse ()
throws IOException, ServletException
{

View File

@ -27,6 +27,7 @@ import static org.junit.Assert.assertThat;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
@ -153,6 +154,48 @@ public class MultiPartInputStreamTest extends TestCase
}
}
public void testPartFileNotDeleted () throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
MultiPart part = (MultiPart)mpis.getPart("stuff");
File stuff = ((MultiPartInputStream.MultiPart)part).getFile();
assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file
part.write("tptfd.txt");
File tptfd = new File (_dirname+File.separator+"tptfd.txt");
assertThat(tptfd.exists(), is(true));
assertThat(stuff.exists(), is(false)); //got renamed
part.cleanUp();
assertThat(tptfd.exists(), is(true)); //explicitly written file did not get removed after cleanup
tptfd.deleteOnExit(); //clean up test
}
public void testPartTmpFileDeletion () throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
MultiPart part = (MultiPart)mpis.getPart("stuff");
File stuff = ((MultiPartInputStream.MultiPart)part).getFile();
assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file
assertThat (stuff.exists(), is(true));
part.cleanUp();
assertThat(stuff.exists(), is(false)); //tmp file was removed after cleanup
}
public void testMulti ()
throws Exception
@ -175,7 +218,7 @@ public class MultiPartInputStreamTest extends TestCase
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2));
Part field1 = mpis.getPart("field1");
Part field1 = mpis.getPart("field1"); //field 1 too small to go into tmp file, should be in internal buffer
assertThat(field1,notNullValue());
assertThat(field1.getName(),is("field1"));
InputStream is = field1.getInputStream();
@ -184,17 +227,18 @@ public class MultiPartInputStreamTest extends TestCase
assertEquals("Joe Blow", new String(os.toByteArray()));
assertEquals(8, field1.getSize());
assertNotNull(((MultiPartInputStream.MultiPart)field1).getBytes()); //in internal buffer
assertNotNull(((MultiPartInputStream.MultiPart)field1).getBytes());//in internal buffer
field1.write("field1.txt");
assertNull(((MultiPartInputStream.MultiPart)field1).getBytes()); //no longer in internal buffer
assertNull(((MultiPartInputStream.MultiPart)field1).getBytes());//no longer in internal buffer
File f = new File (_dirname+File.separator+"field1.txt");
assertTrue(f.exists());
field1.write("another_field1.txt");
field1.write("another_field1.txt"); //write after having already written
File f2 = new File(_dirname+File.separator+"another_field1.txt");
assertTrue(f2.exists());
assertFalse(f.exists()); //should have been renamed
field1.delete(); //file should be deleted
assertFalse(f2.exists());
assertFalse(f.exists()); //original file was renamed
assertFalse(f2.exists()); //2nd written file was explicitly deleted
MultiPart stuff = (MultiPart)mpis.getPart("stuff");
assertThat(stuff.getContentDispositionFilename(), is(filename));
@ -204,14 +248,24 @@ public class MultiPartInputStreamTest extends TestCase
assertThat(stuff.getHeader("content-disposition"),is("form-data; name=\"stuff\"; filename=\"" + filename + "\""));
assertThat(stuff.getHeaderNames().size(),is(2));
assertThat(stuff.getSize(),is(51L));
f = ((MultiPartInputStream.MultiPart)stuff).getFile();
assertThat(f,notNullValue()); // longer than 100 bytes, should already be a file
assertThat(((MultiPartInputStream.MultiPart)stuff).getBytes(),nullValue()); //not in internal buffer any more
assertThat(f.exists(),is(true));
assertThat(f.getName(),is(not("stuff with space.txt")));
File tmpfile = ((MultiPartInputStream.MultiPart)stuff).getFile();
assertThat(tmpfile,notNullValue()); // longer than 100 bytes, should already be a tmp file
assertThat(((MultiPartInputStream.MultiPart)stuff).getBytes(),nullValue()); //not in an internal buffer
assertThat(tmpfile.exists(),is(true));
assertThat(tmpfile.getName(),is(not("stuff with space.txt")));
stuff.write(filename);
f = new File(_dirname+File.separator+filename);
assertThat(f.exists(),is(true));
assertThat(tmpfile.exists(), is(false));
try
{
stuff.getInputStream();
}
catch (Exception e)
{
fail("Part.getInputStream() after file rename operation");
}
f.deleteOnExit(); //clean up after test
}
public void testMultiSameNames ()