blob: fa64e24a780d36c51c6e786821792639e3ec114b [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.servlets;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.URIUtil;
/**
* PutFilter
*
* A Filter that handles PUT, DELETE and MOVE methods.
* Files are hidden during PUT operations, so that 404's result.
*
* The following init parameters pay be used:<ul>
* <li><b>baseURI</b> - The file URI of the document root for put content.
* <li><b>delAllowed</b> - boolean, if true DELETE and MOVE methods are supported.
* <li><b>putAtomic</b> - boolean, if true PUT files are written to a temp location and moved into place.
* </ul>
*
*/
public class PutFilter implements Filter
{
public final static String __PUT="PUT";
public final static String __DELETE="DELETE";
public final static String __MOVE="MOVE";
public final static String __OPTIONS="OPTIONS";
Set<String> _operations = new HashSet<String>();
private ConcurrentMap<String,String> _hidden = new ConcurrentHashMap<String, String>();
private ServletContext _context;
private String _baseURI;
private boolean _delAllowed;
private boolean _putAtomic;
private File _tmpdir;
/* ------------------------------------------------------------ */
public void init(FilterConfig config) throws ServletException
{
_context=config.getServletContext();
_tmpdir=(File)_context.getAttribute("javax.servlet.context.tempdir");
if (_context.getRealPath("/")==null)
throw new UnavailableException("Packed war");
String b = config.getInitParameter("baseURI");
if (b != null)
{
_baseURI=b;
}
else
{
File base=new File(_context.getRealPath("/"));
_baseURI=base.toURI().toString();
}
_delAllowed = getInitBoolean(config,"delAllowed");
_putAtomic = getInitBoolean(config,"putAtomic");
_operations.add(__OPTIONS);
_operations.add(__PUT);
if (_delAllowed)
{
_operations.add(__DELETE);
_operations.add(__MOVE);
}
}
/* ------------------------------------------------------------ */
private boolean getInitBoolean(FilterConfig config,String name)
{
String value = config.getInitParameter(name);
return value != null && value.length() > 0 && (value.startsWith("t") || value.startsWith("T") || value.startsWith("y") || value.startsWith("Y") || value.startsWith("1"));
}
/* ------------------------------------------------------------ */
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
{
HttpServletRequest request=(HttpServletRequest)req;
HttpServletResponse response=(HttpServletResponse)res;
String servletPath =request.getServletPath();
String pathInfo = request.getPathInfo();
String pathInContext = URIUtil.addPaths(servletPath, pathInfo);
String resource = URIUtil.addPaths(_baseURI,pathInContext);
String method = request.getMethod();
boolean op = _operations.contains(method);
if (op)
{
File file = null;
try
{
if (method.equals(__OPTIONS))
handleOptions(chain,request, response);
else
{
file=new File(new URI(resource));
boolean exists = file.exists();
if (exists && !passConditionalHeaders(request, response, file))
return;
if (method.equals(__PUT))
handlePut(request, response,pathInContext, file);
else if (method.equals(__DELETE))
handleDelete(request, response, pathInContext, file);
else if (method.equals(__MOVE))
handleMove(request, response, pathInContext, file);
else
throw new IllegalStateException();
}
}
catch(Exception e)
{
_context.log(e.toString(),e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
else
{
if (isHidden(pathInContext))
response.sendError(HttpServletResponse.SC_NOT_FOUND);
else
chain.doFilter(request,response);
return;
}
}
/* ------------------------------------------------------------ */
private boolean isHidden(String pathInContext)
{
return _hidden.containsKey(pathInContext);
}
/* ------------------------------------------------------------ */
public void destroy()
{
}
/* ------------------------------------------------------------------- */
public void handlePut(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException
{
boolean exists = file.exists();
if (pathInContext.endsWith("/"))
{
if (!exists)
{
if (!file.mkdirs())
response.sendError(HttpServletResponse.SC_FORBIDDEN);
else
{
response.setStatus(HttpServletResponse.SC_CREATED);
response.flushBuffer();
}
}
else
{
response.setStatus(HttpServletResponse.SC_OK);
response.flushBuffer();
}
}
else
{
boolean ok=false;
try
{
_hidden.put(pathInContext,pathInContext);
File parent = file.getParentFile();
parent.mkdirs();
int toRead = request.getContentLength();
InputStream in = request.getInputStream();
if (_putAtomic)
{
File tmp=File.createTempFile(file.getName(),null,_tmpdir);
OutputStream out = new FileOutputStream(tmp,false);
if (toRead >= 0)
IO.copy(in, out, toRead);
else
IO.copy(in, out);
out.close();
if (!tmp.renameTo(file))
throw new IOException("rename from "+tmp+" to "+file+" failed");
}
else
{
OutputStream out = new FileOutputStream(file,false);
if (toRead >= 0)
IO.copy(in, out, toRead);
else
IO.copy(in, out);
out.close();
}
response.setStatus(exists ? HttpServletResponse.SC_OK : HttpServletResponse.SC_CREATED);
response.flushBuffer();
ok=true;
}
catch (Exception ex)
{
_context.log(ex.toString(),ex);
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
finally
{
if (!ok)
{
try
{
if (file.exists())
file.delete();
}
catch(Exception e)
{
_context.log(e.toString(),e);
}
}
_hidden.remove(pathInContext);
}
}
}
/* ------------------------------------------------------------------- */
public void handleDelete(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException
{
try
{
// delete the file
if (file.delete())
{
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
response.flushBuffer();
}
else
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
catch (SecurityException sex)
{
_context.log(sex.toString(),sex);
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
}
/* ------------------------------------------------------------------- */
public void handleMove(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file)
throws ServletException, IOException, URISyntaxException
{
String newPath = URIUtil.canonicalPath(request.getHeader("new-uri"));
if (newPath == null)
{
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
String contextPath = request.getContextPath();
if (contextPath != null && !newPath.startsWith(contextPath))
{
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
return;
}
String newInfo = newPath;
if (contextPath != null)
newInfo = newInfo.substring(contextPath.length());
String new_resource = URIUtil.addPaths(_baseURI,newInfo);
File new_file=new File(new URI(new_resource));
file.renameTo(new_file);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
response.flushBuffer();
}
/* ------------------------------------------------------------ */
public void handleOptions(FilterChain chain, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
chain.doFilter(request,new HttpServletResponseWrapper(response)
{
@Override
public void setHeader(String name, String value)
{
if ("Allow".equalsIgnoreCase(name))
{
Set<String> options = new HashSet<String>();
options.addAll(Arrays.asList(value.split(" *, *")));
options.addAll(_operations);
value=null;
for (String o : options)
value=value==null?o:(value+", "+o);
}
super.setHeader(name,value);
}
});
}
/* ------------------------------------------------------------ */
/*
* Check modification date headers.
*/
protected boolean passConditionalHeaders(HttpServletRequest request, HttpServletResponse response, File file) throws IOException
{
long date = 0;
if ((date = request.getDateHeader("if-unmodified-since")) > 0)
{
if (file.lastModified() / 1000 > date / 1000)
{
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
return false;
}
}
if ((date = request.getDateHeader("if-modified-since")) > 0)
{
if (file.lastModified() / 1000 <= date / 1000)
{
response.reset();
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.flushBuffer();
return false;
}
}
return true;
}
}