/*
Copyright 2011-2014 Red Hat, Inc
This file is part of PressGang CCMS.
PressGang CCMS 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 3 of the License, or
(at your option) any later version.
PressGang CCMS 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 PressGang CCMS. If not, see <http://www.gnu.org/licenses/>.
*/
package net.java.dev.webdav.interop;
import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static net.java.dev.webdav.interop.HttpMethod.LOCK;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Logger;
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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
/**
* This Servlet-Filter should several problems, which were found when working with Windows-Clients and
* a WebDAV-compliant Server.
* Microsoft needs at least a DAV-enabled response, when trying a request to the Root of your Server (http://example.com/).
* For a successful PROPFIND request, the returned XML has to use XML-Namespaces. Due the fact that JAX-B
* doesn't require Namespaces, we use XSLT to transform it to a "correct" XML.
* If you don't want to use the filter for that you can add an empty class to your JAXB-Context. This will force
* JAXB to create namespaces.
*
* @author Daniel MANZKE (daniel.manzke@googlemail.com)
* @author Markus KARG (mkarg@java.net)
*/
public class WindowsRedirectorPatchResourceFilter implements Filter {
public static final String MS_AUTHOR_VIA = "MS-Author-Via";
public static final String DAV = "DAV";
public static final String ROOT_RESOURCE = "/";
private static final Logger logger = Logger.getLogger(WindowsRedirectorPatchResourceFilter.class.getName());
private ServletContext context;
private String webdavClass;
private Set<String> userAgents;
private int responseWrapperBufferSize;
@Override
public void destroy() {
this.templates = null;
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
/*
* This filter is only able to handle HTTP, so we bypass anything else.
*/
if (!(servletRequest instanceof HttpServletRequest)
|| !(servletResponse instanceof HttpServletResponse)) {
chain.doFilter(servletRequest, servletResponse);
return;
}
logger.finer("doFilter(..) - called");
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
final String agent = request.getHeader("user-agent");
logger.fine("doFilter(..) - user-agent: " + agent+" - method: "+request.getMethod()+" uri: "+request.getRequestURI());
/*
* MiniRedir will be tested seperatly because it will be send with a complex version number and until now all of them
* have the OPTIONS and Namespace-"Bug"
*/
final boolean enableHook = (agent != null && (agent.contains("MiniRedir") || userAgents.contains(agent)));
if (enableHook) {
HttpMethod method;
try {
method = HttpMethod.valueOf(request.getMethod().toUpperCase());
} catch (IllegalArgumentException e) {
method = HttpMethod.UNKOWN;
}
logger.fine("doFilter(..) - method: " + method + " - original: "+request.getMethod());
switch (method) {
case OPTIONS:
logger.fine("doFilter(..) - OPTIONS");
final String uri = request.getRequestURI();
final boolean isRoot = uri.equals(ROOT_RESOURCE);
logger.fine("doFilter(..) - URI: " + uri + " isRoot? " + isRoot);
chain.doFilter(request, response);
if (isRoot) {
logger.fine("doFilter(..) - procssing isRoot");
/*
* For Windows interoperability the server has to return for an OPTIONS request on ROOT "/"
* a 204 for no content, a DAV-Header for the class which gets implemented and the Microsoft specific
* MS-Author-Via header
*/
response.setStatus(SC_OK);
if(!response.containsHeader(DAV))
response.addHeader(DAV, webdavClass);
}
if(!response.containsHeader(MS_AUTHOR_VIA))
response.addHeader(MS_AUTHOR_VIA, DAV);
break;
case LOCK:
case PROPFIND:
logger.fine("doFilter(..) - PROPFIND or LOCK");
HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(response, responseWrapperBufferSize);
logger.finest("doFilter(..) - delegating service request");
chain.doFilter(request, responseWrapper);
logger.finest("doFilter(..) - get response back");
/*
* Vista and Windows 7 are sending a LOCK even if the resource
* is not supporting locking. Unfortunately, if the LOCK is not
* answered with a locktoken, Vista and Windows 7 will fail.
*/
if (method == LOCK && responseWrapper.getStatusCode() == SC_METHOD_NOT_ALLOWED) {
final PrintWriter out = response.getWriter();
try {
out.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?><d:prop xmlns:d=\"DAV:\"><d:lockdiscovery><d:activelock><d:lockscope><d:shared/></d:lockscope><d:locktype><d:write/></d:locktype><d:depth>0</d:depth><d:timeout>Second-1</d:timeout><d:owner>dam</d:owner><d:locktoken><d:href>opaquelocktoken:");
out.print(UUID.randomUUID());
out.print("</d:href></d:locktoken></d:activelock></d:lockdiscovery></d:prop>");
response.setHeader("Content-Type","text/xml;charset=UTF-8");
response.setStatus(SC_OK);
} finally {
out.flush();
out.close();
}
break;
}
byte[] responseMsg = responseWrapper.getByteArray();
if(responseMsg.length > 0){
ByteArrayInputStream sr = new ByteArrayInputStream(responseMsg);
Source xmlSource = new StreamSource(sr);
final OutputStream out = response.getOutputStream();
try {
final Transformer transformer = this.templates
.newTransformer();
StreamResult result = new StreamResult(out);
transformer.transform(xmlSource, result);
} catch (Exception ex) {
context.log("Error while transforming the XML with XSLT.", ex);
out.write(responseMsg);
} finally {
out.flush();
out.close();
}
}
break;
default:
logger.finest("doFilter(..) - delegating service request");
chain.doFilter(request, response);
logger.finest("doFilter(..) - get response back");
break;
}
}else{
logger.finest("doFilter(..) - delegating service request");
chain.doFilter(request, response);
logger.finest("doFilter(..) - get response back");
}
}
/**
* Precompiled XSLT Style Sheet (for improved performance).
*/
private Templates templates;
@Override
public void init(FilterConfig config) throws ServletException {
try {
context = config.getServletContext();
String param = config.getInitParameter("interop-xslt");
if (param == null) {
param = "xml/prefix.xsl";
}
this.templates = TransformerFactory.newInstance().newTemplates(
new StreamSource(getClass().getClassLoader()
.getResourceAsStream(param)));
param = config.getInitParameter("interop-webdav-class");
if (param == null) {
param = "1";
}
this.webdavClass = param;
userAgents = new HashSet<String>(Arrays.asList(
"Microsoft Data Access Internet Publishing Provider Protocol", /* Office 2003 */
"Microsoft Data Access Internet Publishing Provider DAV", /* Office 2003 */
"Microsoft Data Access Internet Publishing Provider DAV 1.1", /* Office 2003 */
"Microsoft Data Access Internet Publishing Provider Protocol Discovery", /* Office 2003 */
"DavClnt" /* Windows 7 */
));
param = config.getInitParameter("interop-filter-buffersize");
if(param == null){
responseWrapperBufferSize = 368;
}else{
try {
responseWrapperBufferSize = Integer.parseInt(param);
} catch (NumberFormatException e) {
responseWrapperBufferSize = 368;
}
}
} catch (final TransformerConfigurationException e) {
throw new ServletException(e);
} catch (final TransformerFactoryConfigurationError e) {
throw new ServletException(e);
}
}
}