Author: remy.maucherat(a)jboss.com
Date: 2009-07-08 12:00:01 -0400 (Wed, 08 Jul 2009)
New Revision: 1130
Added:
trunk/java/org/apache/catalina/filters/
trunk/java/org/apache/catalina/filters/AddDefaultCharsetFilter.java
trunk/java/org/apache/catalina/filters/WebdavFixFilter.java
trunk/java/org/apache/catalina/loader/JdbcLeakPrevention.java
Removed:
trunk/java/org/apache/catalina/valves/WebdavFixValve.java
Modified:
trunk/java/org/apache/catalina/loader/WebappClassLoader.java
trunk/java/org/apache/juli/OneLineFormatter.java
trunk/webapps/docs/changelog.xml
Log:
- Port newer WebDAV workaround filter.
- Port JDBC leak fix.
Added: trunk/java/org/apache/catalina/filters/AddDefaultCharsetFilter.java
===================================================================
--- trunk/java/org/apache/catalina/filters/AddDefaultCharsetFilter.java
(rev 0)
+++ trunk/java/org/apache/catalina/filters/AddDefaultCharsetFilter.java 2009-07-08
16:00:01 UTC (rev 1130)
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.catalina.filters;
+
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+
+/**
+ * Filter that explicitly sets the default character set for media subtypes of
+ * the "text" type to ISO-8859-1. RFC2616 explicitly states that browsers must
+ * use ISO-8859-1 in these circumstances. However, browsers may attempt to
+ * auto-detect the character set. This may be exploited by an attacker to
+ * perform an XSS attack. Internet Explorer has this behaviour by default. Other
+ * browsers have an option to enable it.
+ *
+ * This filter prevents the attack by explicitly setting a character set. Unless
+ * the provided character set is explicitly overridden by the user - in which
+ * case they deserve everything they get - the browser will adhere to an
+ * explicitly set character set, thus preventing the XSS attack.
+ */
+public class AddDefaultCharsetFilter implements Filter {
+
+ public void destroy() {
+ // NOOP
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+
+ // Wrap the response
+ if (response instanceof HttpServletResponse) {
+ ResponseWrapper wrapped =
+ new ResponseWrapper((HttpServletResponse)response);
+ chain.doFilter(request, wrapped);
+ } else {
+ chain.doFilter(request, response);
+ }
+ }
+
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // NOOP
+ }
+
+ /**
+ * Wrapper that adds the default character set for text media types if no
+ * character set is specified.
+ */
+ public class ResponseWrapper extends HttpServletResponseWrapper {
+
+ @Override
+ public void setContentType(String ct) {
+
+ if (ct != null && ct.startsWith("text/") &&
+ ct.indexOf("charset=") < 0) {
+ // Use getCharacterEncoding() in case the charset has already
+ // been set by a separate call.
+ super.setContentType(ct + ";charset=" +
getCharacterEncoding());
+ } else {
+ super.setContentType(ct);
+ }
+
+ }
+
+ public ResponseWrapper(HttpServletResponse response) {
+ super(response);
+ }
+
+ }
+}
Added: trunk/java/org/apache/catalina/filters/WebdavFixFilter.java
===================================================================
--- trunk/java/org/apache/catalina/filters/WebdavFixFilter.java
(rev 0)
+++ trunk/java/org/apache/catalina/filters/WebdavFixFilter.java 2009-07-08 16:00:01 UTC
(rev 1130)
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.catalina.filters;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Filter that attempts to force MS WebDAV clients connecting on port 80 to use
+ * a WebDAV client that actually works. Other workarounds that might help
+ * include:
+ * <ul>
+ * <li>Specifying the port, even if it is port 80, when trying to
connect.</li>
+ * <li>Cancelling the first authentication dialog box and then trying to
+ * reconnect.</li>
+ * </ul>
+ *
+ * Generally each different version of the MS client has a different set of
+ * problems.
+ * <p>
+ * TODO: Update this filter to recognise specific MS clients and apply the
+ * appropriate workarounds for that particular client
+ * <p>
+ * As a filter, this is configured in web.xml like any other Filter. You usually
+ * want to map this filter to whatever your WebDAV servlet is mapped to.
+ * <p>
+ * In addition to the issues fixed by this Filter, the following issues have
+ * also been observed that cannot be fixed by this filter. Where possible the
+ * filter will add an message to the logs.
+ * <p>
+ * XP x64 SP2 (MiniRedir Version 3790)
+ * <ul>
+ * <li>Only connects to port 80</li>
+ * <li>Unknown issue means it doesn't work</li>
+ * </ul>
+ */
+
+public class WebdavFixFilter implements Filter {
+
+ private static final String LOG_MESSAGE_PREAMBLE =
+ "WebdavFixFilter: Detected client problem: ";
+
+ /* Start string for all versions */
+ private static final String UA_MINIDIR_START =
+ "Microsoft-WebDAV-MiniRedir";
+ /* XP 32-bit SP3 */
+ private static final String UA_MINIDIR_5_1_2600 =
+ "Microsoft-WebDAV-MiniRedir/5.1.2600";
+
+ /* XP 64-bit SP2 */
+ private static final String UA_MINIDIR_5_2_3790 =
+ "Microsoft-WebDAV-MiniRedir/5.2.3790";
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ /**
+ * Check for the broken MS WebDAV client and if detected issue a re-direct
+ * that hopefully will cause the non-broken client to be used.
+ */
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ if (!(request instanceof HttpServletRequest) ||
+ !(response instanceof HttpServletResponse)) {
+ chain.doFilter(request, response);
+ return;
+ }
+ HttpServletRequest httpRequest = ((HttpServletRequest) request);
+ HttpServletResponse httpResponse = ((HttpServletResponse) response);
+ String ua = httpRequest.getHeader("User-Agent");
+
+ if (ua == null || ua.length() == 0 ||
+ !ua.startsWith(UA_MINIDIR_START)) {
+ // No UA or starts with non MS value
+ // Hope everything just works...
+ chain.doFilter(request, response);
+ } else if (ua.startsWith(UA_MINIDIR_5_1_2600)) {
+ // XP 32-bit SP3 - needs redirect with explicit port
+ httpResponse.sendRedirect(buildRedirect(httpRequest));
+ } else if (ua.startsWith(UA_MINIDIR_5_2_3790)) {
+ // XP 64-bit SP2
+ if (!"".equals(httpRequest.getContextPath())) {
+ log(request,
+ "XP-x64-SP2 clients only work with the root context");
+ }
+ // Namespace issue maybe
+ // see
http://greenbytes.de/tech/webdav/webdav-redirector-list.html
+ log(request, "XP-x64-SP2 is known not to work with WebDAV Servlet");
+
+ chain.doFilter(request, response);
+ } else {
+ // Don't know which MS client it is - try the redirect with an
+ // explicit port in the hope that it moves the client to a different
+ // WebDAV implementation that works
+ httpResponse.sendRedirect(buildRedirect(httpRequest));
+ }
+ }
+
+ private String buildRedirect(HttpServletRequest request) {
+ StringBuffer location =
+ new StringBuffer(request.getRequestURL().length());
+ location.append(request.getScheme());
+ location.append("://");
+ location.append(request.getServerName());
+ location.append(':');
+ // If we include the port, even if it is 80, then MS clients will use
+ // a WebDAV client that works rather than the MiniRedir that has
+ // problems with BASIC authentication
+ location.append(request.getServerPort());
+ location.append(request.getRequestURI());
+ return location.toString();
+ }
+
+ private void log(ServletRequest request, String msg) {
+ StringBuilder builder = new StringBuilder(LOG_MESSAGE_PREAMBLE);
+ builder.append(msg);
+ request.getServletContext().log(builder.toString());
+ }
+}
Added: trunk/java/org/apache/catalina/loader/JdbcLeakPrevention.java
===================================================================
--- trunk/java/org/apache/catalina/loader/JdbcLeakPrevention.java
(rev 0)
+++ trunk/java/org/apache/catalina/loader/JdbcLeakPrevention.java 2009-07-08 16:00:01 UTC
(rev 1130)
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.catalina.loader;
+
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Enumeration;
+
+import org.apache.catalina.util.StringManager;
+
+/**
+ * This class is loaded by the {@link WebappClassLoader} to enable it to
+ * deregister JDBC drivers forgotten by the web application. There are some
+ * classloading hacks involved - see {@link WebappClassLoader#clearReferences()}
+ * for details - but the short version is do not just create a new instance of
+ * this class with the new keyword.
+ */
+public class JdbcLeakPrevention {
+
+ /**
+ * The logger for this class.
+ */
+ protected static org.jboss.logging.Logger log=
+ org.jboss.logging.Logger.getLogger( JdbcLeakPrevention.class );
+
+ /**
+ * The string manager for this package.
+ */
+ protected static final StringManager sm =
+ StringManager.getManager(Constants.Package);
+
+ public void clearJdbcDriverRegistrations() {
+ // Unregister any JDBC drivers loaded by the class loader that loaded
+ // this class - ie the webapp class loader
+ Enumeration<Driver> drivers = DriverManager.getDrivers();
+ while (drivers.hasMoreElements()) {
+ Driver driver = drivers.nextElement();
+ try {
+ DriverManager.deregisterDriver(driver);
+ } catch (SQLException sqle) {
+ log.warn(sm.getString("jdbcLeakPrevention.jdbcRemoveFailed",
+ driver.toString()), sqle);
+ }
+ }
+
+ }
+}
Modified: trunk/java/org/apache/catalina/loader/WebappClassLoader.java
===================================================================
--- trunk/java/org/apache/catalina/loader/WebappClassLoader.java 2009-07-07 16:54:44 UTC
(rev 1129)
+++ trunk/java/org/apache/catalina/loader/WebappClassLoader.java 2009-07-08 16:00:01 UTC
(rev 1130)
@@ -65,9 +65,6 @@
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.PrivilegedAction;
-import java.sql.Driver;
-import java.sql.DriverManager;
-import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
@@ -1164,15 +1161,52 @@
*/
protected void clearReferences() {
- // Unregister any JDBC drivers loaded by this classloader
- Enumeration drivers = DriverManager.getDrivers();
- while (drivers.hasMoreElements()) {
- Driver driver = (Driver) drivers.nextElement();
- if (driver.getClass().getClassLoader() == this) {
+ /*
+ * Deregister any JDBC drivers registered by the webapp that the webapp
+ * forgot. This is made unnecessary complex because a) DriverManager
+ * checks the class loader of the calling class (it would be much easier
+ * if it checked the context class loader) b) using reflection would
+ * create a dependency on the DriverManager implementation which can,
+ * and has, changed.
+ *
+ * We can't just create an instance of JdbcLeakPrevention as it will be
+ * loaded by the common class loader (since it's .class file is in the
+ * $CATALINA_HOME/lib directory). This would fail DriverManager's check
+ * on the class loader of the calling class. So, we load the bytes via
+ * our parent class loader but define the class with this class loader
+ * so the JdbcLeakPrevention looks like a webapp class to the
+ * DriverManager.
+ *
+ * If only apps cleaned up after themselves...
+ */
+ InputStream is = getResourceAsStream(
+ "org/apache/catalina/loader/JdbcLeakPrevention.class");
+ // Cheat - we know roughly how big the class will be (~1K) but allow
+ // plenty room to grow
+ byte[] classBytes = new byte[4096];
+ int offset = 0;
+ try {
+ int read = is.read(classBytes, offset, 4096-offset);
+ while (read > -1) {
+ offset += read;
+ read = is.read(classBytes, offset, 4096-offset);
+ }
+ Class<?> lpClass =
+ defineClass("org.apache.catalina.loader.JdbcLeakPrevention",
+ classBytes, 0, offset);
+ Object obj = lpClass.newInstance();
+ obj.getClass().getMethod(
+ "clearJdbcDriverRegistrations").invoke(obj);
+ } catch (Exception e) {
+ // So many things to go wrong above...
+ log.warn(sm.getString("webappClassLoader.jdbcRemoveFailed"), e);
+ } finally {
+ if (is != null) {
try {
- DriverManager.deregisterDriver(driver);
- } catch (SQLException e) {
- log.warn("SQL driver deregistration failed", e);
+ is.close();
+ } catch (IOException ioe) {
+ log.warn(sm.getString(
+ "webappClassLoader.jdbcRemoveStreamError"), ioe);
}
}
}
Deleted: trunk/java/org/apache/catalina/valves/WebdavFixValve.java
===================================================================
--- trunk/java/org/apache/catalina/valves/WebdavFixValve.java 2009-07-07 16:54:44 UTC (rev
1129)
+++ trunk/java/org/apache/catalina/valves/WebdavFixValve.java 2009-07-08 16:00:01 UTC (rev
1130)
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.apache.catalina.valves;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-
-import org.apache.catalina.valves.ValveBase;
-import org.apache.catalina.connector.Request;
-import org.apache.catalina.connector.Response;
-
-/**
- * Valve that attempts to force MS WebDAV clients connecting on port 80 to use
- * a WebDAV client that actually works. Other workarounds that might help
- * include:
- * <ul>
- * <li>Specifing the port, even if it is port 80, when trying to
connect.</li>
- * <li>Canceling the first authentication dialog box and then trying to
- * reconnect.</li>
- * </ul>
- * To use this valve add the following <code><Valve
- * className="org.apache.catalina.valves.MicrosoftWebdavFixValve"
/></code>
- * to your <code>Engine</code>, <code>Host</code> or
<code>Context</code> as
- * required. Normally, this valve would be used at the <code>Context</code>
- * level.
- *
- * @version $Revision: 420067 $, $Date: 2006-07-08 09:16:58 +0200 (sub, 08 srp 2006) $
- */
-
-public class WebdavFixValve
- extends ValveBase {
-
- /**
- * Check for the broken MS WebDAV client and if detected issue a re-direct
- * that hopefully will cause the non-broken client to be used.
- */
- public void invoke(Request request, Response response)
- throws IOException, ServletException {
-
- String ua = request.getHeader("User-Agent");
- if (ua != null && ua.contains("MiniRedir")) {
- response.sendRedirect(buildRedirect(request));
- } else {
- getNext().invoke(request, response);
- }
- }
-
- private String buildRedirect(Request request) {
- StringBuffer location =
- new StringBuffer(request.getRequestURL().length());
- location.append(request.getScheme());
- location.append("://");
- location.append(request.getHost().getName());
- location.append(':');
- // If we include the port, even if it is 80, then MS clients will use
- // a WebDAV client that works rather than the MiniRedir that has
- // problems with BASIC authentication
- location.append(request.getServerPort());
- location.append(request.getRequestURI());
- return location.toString();
- }
-}
Modified: trunk/java/org/apache/juli/OneLineFormatter.java
===================================================================
--- trunk/java/org/apache/juli/OneLineFormatter.java 2009-07-07 16:54:44 UTC (rev 1129)
+++ trunk/java/org/apache/juli/OneLineFormatter.java 2009-07-08 16:00:01 UTC (rev 1130)
@@ -47,7 +47,7 @@
private final SimpleDateFormat monthFormatter = new
SimpleDateFormat("MM");
private final SimpleDateFormat yearFormatter = new
SimpleDateFormat("yyyy");
private final SimpleDateFormat timeFormatter =
- new SimpleDateFormat("HH:mm:ss");
+ new SimpleDateFormat("HH:mm:ss.S");
private Date currentDate;
private String currentDateString;
Modified: trunk/webapps/docs/changelog.xml
===================================================================
--- trunk/webapps/docs/changelog.xml 2009-07-07 16:54:44 UTC (rev 1129)
+++ trunk/webapps/docs/changelog.xml 2009-07-08 16:00:01 UTC (rev 1130)
@@ -111,6 +111,9 @@
<fix>
MIME types cleanup. (kkolinko)
</fix>
+ <fix>
+ JDBC driver cleanup fix, using a hack to define the cleaner component in the
webapp classloader. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">