JBossWeb SVN: r2115 - in branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306: java/org/apache/catalina/authenticator and 2 other directories.
by jbossweb-commits@lists.jboss.org
Author: aogburn
Date: 2012-10-31 14:38:54 -0400 (Wed, 31 Oct 2012)
New Revision: 2115
Added:
branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/util/ConcurrentMessageDigest.java
Modified:
branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/build.xml
branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/authenticator/DigestAuthenticator.java
branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/util/MD5Encoder.java
branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/webapps/docs/changelog.xml
Log:
[JBPAPP-10306] commit merged changes from branch 2.1.x revision 2107 to improve DigestAuthenticator and decrease its reliance on client nonces
Modified: branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/build.xml
===================================================================
--- branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/build.xml 2012-10-30 21:48:47 UTC (rev 2114)
+++ branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/build.xml 2012-10-31 18:38:54 UTC (rev 2115)
@@ -843,7 +843,7 @@
<target name="build-jasper-jdt-src">
<jar destfile="${jasper-jdt-src.jar}" index="true">
- <fileset dir="${jasper-jdt-src.home}/src/plugins/org.eclipse.jdt.core/model">
+ <fileset dir="${jasper-jdt-src.home}/plugins/org.eclipse.jdt.core/model">
<include name="org/eclipse/jdt/core/compiler/**"/>
<include name="org/eclipse/jdt/internal/compiler/**"/>
<include name="org/eclipse/jdt/internal/core/util/CommentRecorder*"/>
Modified: branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/authenticator/DigestAuthenticator.java
===================================================================
--- branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-10-30 21:48:47 UTC (rev 2114)
+++ branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-10-31 18:38:54 UTC (rev 2115)
@@ -5,30 +5,27 @@
* 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.authenticator;
-
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
import java.security.SecureRandom;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
-import java.security.Principal;
-import java.util.StringTokenizer;
+import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Container;
@@ -38,42 +35,26 @@
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.util.ConcurrentMessageDigest;
import org.apache.catalina.util.MD5Encoder;
import org.jboss.logging.Logger;
-import org.jboss.logging.Logger;
-
/**
* An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP DIGEST
* Authentication (see RFC 2069).
*
* @author Craig R. McClanahan
* @author Remy Maucherat
- * @version $Revision$ $Date$
+ * @version $Id$
*/
-
-public class DigestAuthenticator
- extends AuthenticatorBase {
+public class DigestAuthenticator extends AuthenticatorBase {
private static Logger log = Logger.getLogger(DigestAuthenticator.class);
// -------------------------------------------------------------- Constants
/**
- * The MD5 helper object for this class.
- */
- protected static final MD5Encoder md5Encoder = new MD5Encoder();
-
-
- /**
- * Descriptive information about this implementation.
- */
- protected static final String info =
- "org.apache.catalina.authenticator.DigestAuthenticator/1.0";
-
-
- /**
* Tomcat's DIGEST implementation only supports auth quality of protection.
*/
protected static final String QOP = "auth";
@@ -83,12 +64,13 @@
public DigestAuthenticator() {
super();
+ setCache(false);
try {
- if (md5Helper == null)
+ if (md5Helper == null) {
md5Helper = MessageDigest.getInstance("MD5");
+ }
} catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- throw new IllegalStateException();
+ throw new IllegalStateException(e);
}
}
@@ -98,21 +80,22 @@
/**
* MD5 message digest provider.
+ * @deprecated Unused - will be removed in Tomcat 8.0.x onwards
*/
protected static MessageDigest md5Helper;
/**
- * List of client nonce values currently being tracked
+ * List of server nonce values currently being tracked
*/
- protected Map<String,NonceInfo> cnonces;
+ protected Map<String,NonceInfo> nonces;
/**
- * Maximum number of client nonces to keep in the cache. If not specified,
+ * Maximum number of server nonces to keep in the cache. If not specified,
* the default value of 1000 is used.
*/
- protected int cnonceCacheSize = 1000;
+ protected int nonceCacheSize = 1000;
/**
@@ -142,27 +125,16 @@
// ------------------------------------------------------------- Properties
- /**
- * Return descriptive information about this Valve implementation.
- */
- @Override
- public String getInfo() {
-
- return (info);
-
+ public int getNonceCacheSize() {
+ return nonceCacheSize;
}
- public int getCnonceCacheSize() {
- return cnonceCacheSize;
+ public void setNonceCacheSize(int nonceCacheSize) {
+ this.nonceCacheSize = nonceCacheSize;
}
- public void setCnonceCacheSize(int cnonceCacheSize) {
- this.cnonceCacheSize = cnonceCacheSize;
- }
-
-
public String getKey() {
return key;
}
@@ -213,8 +185,6 @@
*
* @param request Request we are processing
* @param response Response we are creating
- * @param config Login configuration describing how authentication
- * should be performed
*
* @exception IOException if an input/output error occurs
*/
@@ -222,19 +192,18 @@
public boolean authenticate(Request request,
Response response,
LoginConfig config)
- throws IOException {
+ throws IOException {
// Have we already authenticated someone?
Principal principal = request.getUserPrincipal();
//String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
if (principal != null) {
- if (log.isDebugEnabled())
- log.debug("Already authenticated '" + principal.getName() + "'");
// Associate the session with any existing SSO session in order
// to get coordinated session invalidation at logout
String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
- if (ssoId != null)
+ if (ssoId != null) {
associate(ssoId, request.getSessionInternal(true));
+ }
return (true);
}
@@ -266,19 +235,20 @@
// Validate any credentials already included with this request
String authorization = request.getHeader("authorization");
DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
- getKey(), cnonces, isValidateUri());
+ getKey(), nonces, isValidateUri());
if (authorization != null) {
- if (digestInfo.validate(request, authorization, config)) {
- principal = digestInfo.authenticate(context.getRealm());
+ if (digestInfo.parse(request, authorization)) {
+ if (digestInfo.validate(request, config)) {
+ principal = digestInfo.authenticate(context.getRealm());
+ }
+
+ if (principal != null && !digestInfo.isNonceStale()) {
+ register(request, response, principal,
+ HttpServletRequest.DIGEST_AUTH,
+ digestInfo.getUsername(), null);
+ return true;
+ }
}
-
- if (principal != null) {
- String username = parseUsername(authorization);
- register(request, response, principal,
- Constants.DIGEST_METHOD,
- username, null);
- return (true);
- }
}
// Send an "unauthorized" response and an appropriate challenge
@@ -288,11 +258,9 @@
String nonce = generateNonce(request);
setAuthenticateHeader(request, response, config, nonce,
- digestInfo.isNonceStale());
+ principal != null && digestInfo.isNonceStale());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
- // hres.flushBuffer();
- return (false);
-
+ return false;
}
@@ -300,42 +268,6 @@
/**
- * Parse the username from the specified authorization string. If none
- * can be identified, return <code>null</code>
- *
- * @param authorization Authorization string to be parsed
- */
- protected String parseUsername(String authorization) {
-
- // Validate the authorization credentials format
- if (authorization == null)
- return (null);
- if (!authorization.startsWith("Digest "))
- return (null);
- authorization = authorization.substring(7).trim();
-
- StringTokenizer commaTokenizer =
- new StringTokenizer(authorization, ",");
-
- while (commaTokenizer.hasMoreTokens()) {
- String currentToken = commaTokenizer.nextToken();
- int equalSign = currentToken.indexOf('=');
- if (equalSign < 0)
- return null;
- String currentTokenName =
- currentToken.substring(0, equalSign).trim();
- String currentTokenValue =
- currentToken.substring(equalSign + 1).trim();
- if ("username".equals(currentTokenName))
- return (removeQuotes(currentTokenValue));
- }
-
- return (null);
-
- }
-
-
- /**
* Removes the quotes on a string. RFC2617 states quotes are optional for
* all parameters except realm.
*/
@@ -348,7 +280,7 @@
} else if (quotedString.length() > 2) {
return quotedString.substring(1, quotedString.length() - 1);
} else {
- return new String();
+ return "";
}
}
@@ -370,16 +302,19 @@
long currentTime = System.currentTimeMillis();
-
+
String ipTimeKey =
request.getRemoteAddr() + ":" + currentTime + ":" + getKey();
- byte[] buffer;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(ipTimeKey.getBytes());
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(ipTimeKey.getBytes());
+ String nonce = currentTime + ":" + MD5Encoder.encode(buffer);
+
+ NonceInfo info = new NonceInfo(currentTime, 100);
+ synchronized (nonces) {
+ nonces.put(nonce, info);
}
- return currentTime + ":" + md5Encoder.encode(buffer);
+ return nonce;
}
@@ -406,12 +341,10 @@
*
* @param request HTTP Servlet request
* @param response HTTP Servlet response
- * @param config Login configuration describing how authentication
- * should be performed
* @param nonce nonce token
*/
- protected void setAuthenticateHeader(Request request,
- Response response,
+ protected void setAuthenticateHeader(HttpServletRequest request,
+ HttpServletResponse response,
LoginConfig config,
String nonce,
boolean isNonceStale) {
@@ -430,15 +363,15 @@
authenticateHeader = "Digest realm=\"" + realmName + "\", " +
"qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
getOpaque() + "\"";
- }
+ }
- response.setHeader("WWW-Authenticate", authenticateHeader);
+ response.setHeader(AUTH_HEADER_NAME, authenticateHeader);
}
// ------------------------------------------------------- Lifecycle Methods
-
+
@Override
public void start() throws LifecycleException {
super.start();
@@ -447,14 +380,14 @@
if (getKey() == null) {
setKey(generateSessionId());
}
-
+
// Generate the opaque string the same way
if (getOpaque() == null) {
setOpaque(generateSessionId());
}
-
- cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+ nonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+
private static final long serialVersionUID = 1L;
private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
@@ -465,7 +398,7 @@
Map.Entry<String,NonceInfo> eldest) {
// This is called from a sync so keep it simple
long currentTime = System.currentTimeMillis();
- if (size() > getCnonceCacheSize()) {
+ if (size() > getNonceCacheSize()) {
if (lastLog < currentTime &&
currentTime - eldest.getValue().getTimestamp() <
getNonceValidity()) {
@@ -480,13 +413,13 @@
}
};
}
-
+
private static class DigestInfo {
- private String opaque;
- private long nonceValidity;
- private String key;
- private Map<String,NonceInfo> cnonces;
+ private final String opaque;
+ private final long nonceValidity;
+ private final String key;
+ private final Map<String,NonceInfo> nonces;
private boolean validateUri = true;
private String userName = null;
@@ -498,21 +431,27 @@
private String cnonce = null;
private String realmName = null;
private String qop = null;
+ private String opaqueReceived = null;
private boolean nonceStale = false;
public DigestInfo(String opaque, long nonceValidity, String key,
- Map<String,NonceInfo> cnonces, boolean validateUri) {
+ Map<String,NonceInfo> nonces, boolean validateUri) {
this.opaque = opaque;
this.nonceValidity = nonceValidity;
this.key = key;
- this.cnonces = cnonces;
+ this.nonces = nonces;
this.validateUri = validateUri;
}
- public boolean validate(Request request, String authorization,
- LoginConfig config) {
+
+ public String getUsername() {
+ return userName;
+ }
+
+
+ public boolean parse(Request request, String authorization) {
// Validate the authorization credentials format
if (authorization == null) {
return false;
@@ -526,12 +465,12 @@
String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
method = request.getMethod();
- String opaque = null;
for (int i = 0; i < tokens.length; i++) {
String currentToken = tokens[i];
- if (currentToken.length() == 0)
+ if (currentToken.length() == 0) {
continue;
+ }
int equalSign = currentToken.indexOf('=');
if (equalSign < 0) {
@@ -541,26 +480,39 @@
currentToken.substring(0, equalSign).trim();
String currentTokenValue =
currentToken.substring(equalSign + 1).trim();
- if ("username".equals(currentTokenName))
+ if ("username".equals(currentTokenName)) {
userName = removeQuotes(currentTokenValue);
- if ("realm".equals(currentTokenName))
+ }
+ if ("realm".equals(currentTokenName)) {
realmName = removeQuotes(currentTokenValue, true);
- if ("nonce".equals(currentTokenName))
+ }
+ if ("nonce".equals(currentTokenName)) {
nonce = removeQuotes(currentTokenValue);
- if ("nc".equals(currentTokenName))
+ }
+ if ("nc".equals(currentTokenName)) {
nc = removeQuotes(currentTokenValue);
- if ("cnonce".equals(currentTokenName))
+ }
+ if ("cnonce".equals(currentTokenName)) {
cnonce = removeQuotes(currentTokenValue);
- if ("qop".equals(currentTokenName))
+ }
+ if ("qop".equals(currentTokenName)) {
qop = removeQuotes(currentTokenValue);
- if ("uri".equals(currentTokenName))
+ }
+ if ("uri".equals(currentTokenName)) {
uri = removeQuotes(currentTokenValue);
- if ("response".equals(currentTokenName))
+ }
+ if ("response".equals(currentTokenName)) {
response = removeQuotes(currentTokenValue);
- if ("opaque".equals(currentTokenName))
- opaque = removeQuotes(currentTokenValue);
+ }
+ if ("opaque".equals(currentTokenName)) {
+ opaqueReceived = removeQuotes(currentTokenValue);
+ }
}
+ return true;
+ }
+
+ public boolean validate(Request request, LoginConfig config) {
if ( (userName == null) || (realmName == null) || (nonce == null)
|| (uri == null) || (response == null) ) {
return false;
@@ -576,7 +528,23 @@
uriQuery = request.getRequestURI() + "?" + query;
}
if (!uri.equals(uriQuery)) {
- return false;
+ // Some clients (older Android) use an absolute URI for
+ // DIGEST but a relative URI in the request line.
+ // request. 2.3.5 < fixed Android version <= 4.0.3
+ String host = request.getHeader("host");
+ String scheme = request.getScheme();
+ if (host != null && !uriQuery.startsWith(scheme)) {
+ StringBuilder absolute = new StringBuilder();
+ absolute.append(scheme);
+ absolute.append("://");
+ absolute.append(host);
+ absolute.append(uriQuery);
+ if (!uri.equals(absolute.toString())) {
+ return false;
+ }
+ } else {
+ return false;
+ }
}
}
@@ -588,9 +556,9 @@
if (!lcRealm.equals(realmName)) {
return false;
}
-
+
// Validate the opaque string
- if (!this.opaque.equals(opaque)) {
+ if (!opaque.equals(opaqueReceived)) {
return false;
}
@@ -609,15 +577,15 @@
long currentTime = System.currentTimeMillis();
if ((currentTime - nonceTime) > nonceValidity) {
nonceStale = true;
- return false;
+ synchronized (nonces) {
+ nonces.remove(nonce);
+ }
}
String serverIpTimeKey =
request.getRemoteAddr() + ":" + nonceTime + ":" + key;
- byte[] buffer = null;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(serverIpTimeKey.getBytes());
- }
- String md5ServerIpTimeKey = md5Encoder.encode(buffer);
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(
+ serverIpTimeKey.getBytes());
+ String md5ServerIpTimeKey = MD5Encoder.encode(buffer);
if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
return false;
}
@@ -628,7 +596,7 @@
}
// Validate cnonce and nc
- // Check if presence of nc and nonce is consistent with presence of qop
+ // Check if presence of nc and Cnonce is consistent with presence of qop
if (qop == null) {
if (cnonce != null || nc != null) {
return false;
@@ -637,7 +605,9 @@
if (cnonce == null || nc == null) {
return false;
}
- if (nc.length() != 8) {
+ // RFC 2617 says nc must be 8 digits long. Older Android clients
+ // use 6. 2.3.5 < fixed Android version <= 4.0.3
+ if (nc.length() < 6 || nc.length() > 8) {
return false;
}
long count;
@@ -647,21 +617,18 @@
return false;
}
NonceInfo info;
- synchronized (cnonces) {
- info = cnonces.get(cnonce);
+ synchronized (nonces) {
+ info = nonces.get(nonce);
}
if (info == null) {
- info = new NonceInfo();
+ // Nonce is valid but not in cache. It must have dropped out
+ // of the cache - force a re-authentication
+ nonceStale = true;
} else {
- if (count <= info.getCount()) {
+ if (!info.nonceCountValid(count)) {
return false;
}
}
- info.setCount(count);
- info.setTimestamp(currentTime);
- synchronized (cnonces) {
- cnonces.put(cnonce, info);
- }
}
return true;
}
@@ -675,11 +642,9 @@
// MD5(Method + ":" + uri)
String a2 = method + ":" + uri;
- byte[] buffer;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(a2.getBytes());
- }
- String md5a2 = md5Encoder.encode(buffer);
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(
+ a2.getBytes());
+ String md5a2 = MD5Encoder.encode(buffer);
return realm.authenticate(userName, response, nonce, nc, cnonce,
qop, realmName, md5a2);
@@ -688,21 +653,33 @@
}
private static class NonceInfo {
- private volatile long count;
private volatile long timestamp;
-
- public void setCount(long l) {
- count = l;
+ private volatile boolean seen[];
+ private volatile int offset;
+ private volatile int count = 0;
+
+ public NonceInfo(long currentTime, int seenWindowSize) {
+ this.timestamp = currentTime;
+ seen = new boolean[seenWindowSize];
+ offset = seenWindowSize / 2;
}
-
- public long getCount() {
- return count;
+
+ public synchronized boolean nonceCountValid(long nonceCount) {
+ if ((count - offset) >= nonceCount ||
+ (nonceCount > count - offset + seen.length)) {
+ return false;
+ }
+ int checkIndex = (int) ((nonceCount + offset) % seen.length);
+ if (seen[checkIndex]) {
+ return false;
+ } else {
+ seen[checkIndex] = true;
+ seen[count % seen.length] = false;
+ count++;
+ return true;
+ }
}
-
- public void setTimestamp(long l) {
- timestamp = l;
- }
-
+
public long getTimestamp() {
return timestamp;
}
Property changes on: branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/authenticator/DigestAuthenticator.java
___________________________________________________________________
Added: svn:mergeinfo
+ /branches/2.1.x/java/org/apache/catalina/authenticator/DigestAuthenticator.java:2107
Added: branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/util/ConcurrentMessageDigest.java
===================================================================
--- branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/util/ConcurrentMessageDigest.java (rev 0)
+++ branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/util/ConcurrentMessageDigest.java 2012-10-31 18:38:54 UTC (rev 2115)
@@ -0,0 +1,104 @@
+/*
+ * 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.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * A thread safe wrapper around {@link MessageDigest} that does not make use
+ * of ThreadLocal and - broadly - only creates enough MessageDigest objects
+ * to satisfy the concurrency requirements.
+ */
+public class ConcurrentMessageDigest {
+
+ private static final String MD5 = "MD5";
+
+ private static final Map<String,Queue<MessageDigest>> queues =
+ new HashMap<String,Queue<MessageDigest>>();
+
+
+ private ConcurrentMessageDigest() {
+ // Hide default constructor for this utility class
+ }
+
+ static {
+ try {
+ // Init commonly used algorithms
+ init(MD5);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static byte[] digestMD5(byte[] input) {
+ return digest(MD5, input);
+ }
+
+ public static byte[] digest(String algorithm, byte[] input) {
+
+ Queue<MessageDigest> queue = queues.get(algorithm);
+ if (queue == null) {
+ throw new IllegalStateException("Must call init() first");
+ }
+
+ MessageDigest md = queue.poll();
+ if (md == null) {
+ try {
+ md = MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ // Ignore. Impossible if init() has been successfully called
+ // first.
+ throw new IllegalStateException("Must call init() first");
+ }
+ }
+
+ byte[] result = md.digest(input);
+
+ queue.add(md);
+
+ return result;
+ }
+
+
+ /**
+ * Ensures that {@link #digest(String, byte[])} and
+ * {@link #digestAsHex(String, byte[])} will support the specified
+ * algorithm. This method <b>must</b> be called and return successfully
+ * before using {@link #digest(String, byte[])} or
+ * {@link #digestAsHex(String, byte[])}.
+ *
+ * @param algorithm The message digest algorithm to be supported
+ *
+ * @throws NoSuchAlgorithmException If the algorithm is not supported by the
+ * JVM
+ */
+ public static void init(String algorithm) throws NoSuchAlgorithmException {
+ synchronized (queues) {
+ if (!queues.containsKey(algorithm)) {
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ Queue<MessageDigest> queue = new ConcurrentLinkedQueue<MessageDigest>();
+ queue.add(md);
+ queues.put(algorithm, queue);
+ }
+ }
+ }
+}
Modified: branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/util/MD5Encoder.java
===================================================================
--- branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/util/MD5Encoder.java 2012-10-30 21:48:47 UTC (rev 2114)
+++ branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/util/MD5Encoder.java 2012-10-31 18:38:54 UTC (rev 2115)
@@ -50,7 +50,7 @@
* @param binaryData Array containing the digest
* @return Encoded MD5, or null if encoding failed
*/
- public String encode( byte[] binaryData ) {
+ public static String encode( byte[] binaryData ) {
if (binaryData.length != 16)
return null;
Property changes on: branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/java/org/apache/catalina/util/MD5Encoder.java
___________________________________________________________________
Added: svn:mergeinfo
+ /branches/2.1.x/java/org/apache/catalina/util/MD5Encoder.java:2107
Modified: branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/webapps/docs/changelog.xml
===================================================================
--- branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/webapps/docs/changelog.xml 2012-10-30 21:48:47 UTC (rev 2114)
+++ branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/webapps/docs/changelog.xml 2012-10-31 18:38:54 UTC (rev 2115)
@@ -26,6 +26,12 @@
<fix>
<bug>46982</bug>: Correct reporting of DST offset in access logs. (markt)
</fix>
+ <fix>
+ Various improvements to the DIGEST authenticator including
+ <bug>52954</bug>, the disabling caching of an authenticated user in the
+ session by default, tracking server rather than client nonces and better
+ handling of stale nonce values. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">
12 years, 1 month
JBossWeb SVN: r2114 - branches.
by jbossweb-commits@lists.jboss.org
Author: aogburn
Date: 2012-10-30 17:48:47 -0400 (Tue, 30 Oct 2012)
New Revision: 2114
Added:
branches/JBOSSWEB_2_1_12_GA_patch03_JBPAPP-10306/
Log:
Create JBPAPP-10306 branch from JBOSSWEB_2_1_12_GA_patch03 tag
12 years, 1 month
JBossWeb SVN: r2113 - sandbox/webapps/src.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2012-10-26 13:11:40 -0400 (Fri, 26 Oct 2012)
New Revision: 2113
Added:
sandbox/webapps/src/TestAsyncServlet3.java
Log:
A small servlet to test.
Added: sandbox/webapps/src/TestAsyncServlet3.java
===================================================================
--- sandbox/webapps/src/TestAsyncServlet3.java (rev 0)
+++ sandbox/webapps/src/TestAsyncServlet3.java 2012-10-26 17:11:40 UTC (rev 2113)
@@ -0,0 +1,53 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2012, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This 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 software 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+
+// package org.jboss.web.comet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.servlet.annotation.WebServlet;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import javax.servlet.AsyncListener;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+
+/* Asynchronous example */
+@WebServlet(urlPatterns = {"/TestAsyncServlet3"}, asyncSupported = true)
+public class TestAsyncServlet3 extends HttpServlet {
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ response.getWriter().println("ASYNC_STARTED_asyncTest");
+ response.getWriter().println("IsAsyncSupported=" + request.isAsyncSupported());
+ response.getWriter().println("IsAsyncStarted=" + request.isAsyncStarted());
+ response.getWriter().println("DispatcherType=" + request.getDispatcherType());
+ }
+}
12 years, 1 month
JBossWeb SVN: r2112 - sandbox/webapps/src.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2012-10-26 10:37:42 -0400 (Fri, 26 Oct 2012)
New Revision: 2112
Added:
sandbox/webapps/src/TestAsyncServlet2.java
Log:
Test for asyn stuff.
Added: sandbox/webapps/src/TestAsyncServlet2.java
===================================================================
--- sandbox/webapps/src/TestAsyncServlet2.java (rev 0)
+++ sandbox/webapps/src/TestAsyncServlet2.java 2012-10-26 14:37:42 UTC (rev 2112)
@@ -0,0 +1,59 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2012, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This 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 software 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+
+// package org.jboss.web.comet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.servlet.annotation.WebServlet;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import javax.servlet.AsyncListener;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+
+/* Asynchronous example */
+// @WebServlet("/TestAsyncServlet")
+@WebServlet(urlPatterns = {"/TestAsyncServlet2"}, asyncSupported = true)
+public class TestAsyncServlet2 extends HttpServlet {
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+
+ response.getWriter().println("ASYNC_NOT_STARTED_dispatchContextPathTest");
+ response.getWriter().println("IsAsyncSupported=" +
+ request.isAsyncSupported());
+ response.getWriter().println("IsAsyncStarted=" +
+ request.isAsyncStarted());
+ response.getWriter().println("DispatcherType=" +
+ request.getDispatcherType());
+ AsyncContext ac = request.startAsync();
+ }
+}
12 years, 1 month
JBossWeb SVN: r2111 - in branches: 7.0.x/webapps/docs and 2 other directories.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2012-10-23 11:22:47 -0400 (Tue, 23 Oct 2012)
New Revision: 2111
Modified:
branches/7.0.x/java/org/apache/catalina/core/StandardContext.java
branches/7.0.x/webapps/docs/changelog.xml
branches/7.2.x/src/main/java/org/apache/catalina/core/StandardContext.java
branches/7.2.x/webapps/docs/changelog.xml
Log:
AS7-5802: Fix oops recreating the filter config right after initializing it if the corresponding filter registration was used.
Modified: branches/7.0.x/java/org/apache/catalina/core/StandardContext.java
===================================================================
--- branches/7.0.x/java/org/apache/catalina/core/StandardContext.java 2012-10-19 08:44:02 UTC (rev 2110)
+++ branches/7.0.x/java/org/apache/catalina/core/StandardContext.java 2012-10-23 15:22:47 UTC (rev 2111)
@@ -3260,18 +3260,20 @@
Iterator<String> names = filterDefs.keySet().iterator();
while (names.hasNext()) {
String name = names.next();
- if (getLogger().isDebugEnabled())
- getLogger().debug(" Starting filter '" + name + "'");
- ApplicationFilterConfig filterConfig = null;
- try {
- filterConfig = new ApplicationFilterConfig
- (this, (FilterDef) filterDefs.get(name));
- filterConfig.getFilter();
- filterConfigs.put(name, filterConfig);
- } catch (Throwable t) {
- getLogger().error
- (sm.getString("standardContext.filterStart", name), t);
- ok = false;
+ ApplicationFilterConfig filterConfig = filterConfigs.get(name);
+ if (filterConfig == null) {
+ if (getLogger().isDebugEnabled())
+ getLogger().debug(" Starting filter '" + name + "'");
+ try {
+ filterConfig = new ApplicationFilterConfig
+ (this, (FilterDef) filterDefs.get(name));
+ filterConfig.getFilter();
+ filterConfigs.put(name, filterConfig);
+ } catch (Throwable t) {
+ getLogger().error
+ (sm.getString("standardContext.filterStart", name), t);
+ ok = false;
+ }
}
}
Modified: branches/7.0.x/webapps/docs/changelog.xml
===================================================================
--- branches/7.0.x/webapps/docs/changelog.xml 2012-10-19 08:44:02 UTC (rev 2110)
+++ branches/7.0.x/webapps/docs/changelog.xml 2012-10-23 15:22:47 UTC (rev 2111)
@@ -16,6 +16,13 @@
<body>
<section name="JBoss Web 7.0.16.Final (remm)">
+ <subsection name="Catalina">
+ <changelog>
+ <fix>
+ <jboss-jira>AS7-5802</jboss-jira>: Fix problem with duplicated filter instantiation when using a filter registration. (remm)
+ </fix>
+ </changelog>
+ </subsection>
<subsection name="Coyote">
<changelog>
<fix>
Modified: branches/7.2.x/src/main/java/org/apache/catalina/core/StandardContext.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/catalina/core/StandardContext.java 2012-10-19 08:44:02 UTC (rev 2110)
+++ branches/7.2.x/src/main/java/org/apache/catalina/core/StandardContext.java 2012-10-23 15:22:47 UTC (rev 2111)
@@ -3215,17 +3215,19 @@
Iterator<String> names = filterDefs.keySet().iterator();
while (names.hasNext()) {
String name = names.next();
- CatalinaLogger.CORE_LOGGER.startingFilter(name);
- if (getLogger().isDebugEnabled())
- getLogger().debug(" Starting filter '" + name + "'");
- ApplicationFilterConfig filterConfig = null;
- try {
- filterConfig = new ApplicationFilterConfig(this, (FilterDef) filterDefs.get(name));
- filterConfig.getFilter();
- filterConfigs.put(name, filterConfig);
- } catch (Throwable t) {
- getLogger().error(MESSAGES.errorStartingFilter(name), t);
- ok = false;
+ ApplicationFilterConfig filterConfig = filterConfigs.get(name);
+ if (filterConfig == null) {
+ CatalinaLogger.CORE_LOGGER.startingFilter(name);
+ if (getLogger().isDebugEnabled())
+ getLogger().debug(" Starting filter '" + name + "'");
+ try {
+ filterConfig = new ApplicationFilterConfig(this, (FilterDef) filterDefs.get(name));
+ filterConfig.getFilter();
+ filterConfigs.put(name, filterConfig);
+ } catch (Throwable t) {
+ getLogger().error(MESSAGES.errorStartingFilter(name), t);
+ ok = false;
+ }
}
}
Modified: branches/7.2.x/webapps/docs/changelog.xml
===================================================================
--- branches/7.2.x/webapps/docs/changelog.xml 2012-10-19 08:44:02 UTC (rev 2110)
+++ branches/7.2.x/webapps/docs/changelog.xml 2012-10-23 15:22:47 UTC (rev 2111)
@@ -23,6 +23,9 @@
<fix>
<jira>249</jira>: Respect URL session tracking when encoding. (remm)
</fix>
+ <fix>
+ <jboss-jira>AS7-5802</jboss-jira>: Fix problem with duplicated filter instantiation when using a filter registration. (remm)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">
12 years, 2 months
JBossWeb SVN: r2110 - in branches/7.2.x: webapps/docs and 1 other directory.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2012-10-19 04:44:02 -0400 (Fri, 19 Oct 2012)
New Revision: 2110
Modified:
branches/7.2.x/src/main/java/org/apache/jasper/security/SecurityClassLoad.java
branches/7.2.x/webapps/docs/changelog.xml
Log:
JBWEB-251: Cosmetic class preload problem.
Modified: branches/7.2.x/src/main/java/org/apache/jasper/security/SecurityClassLoad.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/jasper/security/SecurityClassLoad.java 2012-10-16 16:06:15 UTC (rev 2109)
+++ branches/7.2.x/src/main/java/org/apache/jasper/security/SecurityClassLoad.java 2012-10-19 08:44:02 UTC (rev 2110)
@@ -104,8 +104,6 @@
loader.loadClass( basePackage +
"servlet.JspServletWrapper");
- loader.loadClass( basePackage +
- "runtime.JspWriterImpl$1");
} catch (ClassNotFoundException ex) {
JasperLogger.ROOT_LOGGER.errorLoadingCoreClass(ex);
}
Modified: branches/7.2.x/webapps/docs/changelog.xml
===================================================================
--- branches/7.2.x/webapps/docs/changelog.xml 2012-10-16 16:06:15 UTC (rev 2109)
+++ branches/7.2.x/webapps/docs/changelog.xml 2012-10-19 08:44:02 UTC (rev 2110)
@@ -32,6 +32,13 @@
</fix>
</changelog>
</subsection>
+ <subsection name="Jasper">
+ <changelog>
+ <fix>
+ <jira>251</jira>: Drop localization related class preload. (remm)
+ </fix>
+ </changelog>
+ </subsection>
</section>
<section name="JBoss Web 7.2.0.Alpha2 (remm)">
12 years, 2 months
JBossWeb SVN: r2109 - in branches/7.2.x: webapps/docs and 1 other directory.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2012-10-16 12:06:15 -0400 (Tue, 16 Oct 2012)
New Revision: 2109
Modified:
branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11NioProcessor.java
branches/7.2.x/webapps/docs/changelog.xml
Log:
JBWEB-250: Fix remote address cut & paste issue.
Modified: branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11NioProcessor.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11NioProcessor.java 2012-10-10 17:55:01 UTC (rev 2108)
+++ branches/7.2.x/src/main/java/org/apache/coyote/http11/Http11NioProcessor.java 2012-10-16 16:06:15 UTC (rev 2109)
@@ -547,7 +547,8 @@
private void requestHostAddressAttr() {
if (remoteAddr == null && (channel != null)) {
try {
- remoteAddr = ((InetSocketAddress) this.channel.getRemoteAddress()).getHostName();
+ remoteAddr = ((InetSocketAddress) this.channel.getRemoteAddress()).getAddress()
+ .getHostAddress();
} catch (Exception e) {
CoyoteLogger.HTTP_LOGGER.errorGettingSocketInformation(e);
}
Modified: branches/7.2.x/webapps/docs/changelog.xml
===================================================================
--- branches/7.2.x/webapps/docs/changelog.xml 2012-10-10 17:55:01 UTC (rev 2108)
+++ branches/7.2.x/webapps/docs/changelog.xml 2012-10-16 16:06:15 UTC (rev 2109)
@@ -25,6 +25,13 @@
</fix>
</changelog>
</subsection>
+ <subsection name="Coyote">
+ <changelog>
+ <fix>
+ <jira>250</jira>: Fix remote address, submitted by Cheng Fang. (remm)
+ </fix>
+ </changelog>
+ </subsection>
</section>
<section name="JBoss Web 7.2.0.Alpha2 (remm)">
12 years, 2 months
JBossWeb SVN: r2108 - in branches/7.2.x: webapps/docs and 1 other directory.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2012-10-10 13:55:01 -0400 (Wed, 10 Oct 2012)
New Revision: 2108
Modified:
branches/7.2.x/pom.xml
branches/7.2.x/webapps/docs/changelog.xml
Log:
Update changelog.
Modified: branches/7.2.x/pom.xml
===================================================================
(Binary files differ)
Modified: branches/7.2.x/webapps/docs/changelog.xml
===================================================================
--- branches/7.2.x/webapps/docs/changelog.xml 2012-10-10 17:50:36 UTC (rev 2107)
+++ branches/7.2.x/webapps/docs/changelog.xml 2012-10-10 17:55:01 UTC (rev 2108)
@@ -16,6 +16,27 @@
<body>
+<section name="JBoss Web 7.2.0.Alpha3 (remm)">
+ <subsection name="General">
+ <subsection name="Catalina">
+ <changelog>
+ <fix>
+ <jira>249</jira>: Respect URL session tracking when encoding. (remm)
+ </fix>
+ </changelog>
+ </subsection>
+</section>
+
+<section name="JBoss Web 7.2.0.Alpha2 (remm)">
+ <subsection name="Coyote">
+ <changelog>
+ <fix>
+ NIO 2 connector should reuse addresses. (remm)
+ </fix>
+ </changelog>
+ </subsection>
+</section>
+
<section name="JBoss Web 7.2.0.Alpha1 (remm)">
<subsection name="General">
<changelog>
12 years, 2 months
JBossWeb SVN: r2107 - in branches: 2.1.x/java/org/apache/catalina/realm and 19 other directories.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2012-10-10 13:50:36 -0400 (Wed, 10 Oct 2012)
New Revision: 2107
Added:
branches/2.1.x/java/org/apache/catalina/util/ConcurrentMessageDigest.java
branches/7.0.x/java/org/apache/catalina/util/ConcurrentMessageDigest.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/util/ConcurrentMessageDigest.java
Modified:
branches/2.1.x/java/org/apache/catalina/authenticator/DigestAuthenticator.java
branches/2.1.x/java/org/apache/catalina/realm/RealmBase.java
branches/2.1.x/java/org/apache/catalina/util/MD5Encoder.java
branches/2.1.x/java/org/apache/coyote/ajp/AjpAprProcessor.java
branches/2.1.x/java/org/apache/coyote/ajp/AjpMessage.java
branches/2.1.x/java/org/apache/coyote/ajp/AjpProcessor.java
branches/2.1.x/java/org/apache/coyote/http11/Http11AprProcessor.java
branches/2.1.x/java/org/apache/coyote/http11/Http11Processor.java
branches/2.1.x/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
branches/2.1.x/java/org/apache/coyote/http11/InternalOutputBuffer.java
branches/2.1.x/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
branches/7.0.x/.classpath
branches/7.0.x/java/org/apache/catalina/authenticator/DigestAuthenticator.java
branches/7.0.x/java/org/apache/catalina/connector/Response.java
branches/7.0.x/java/org/apache/catalina/filters/CsrfPreventionFilter.java
branches/7.0.x/java/org/apache/catalina/realm/RealmBase.java
branches/7.0.x/java/org/apache/catalina/util/MD5Encoder.java
branches/7.0.x/java/org/apache/coyote/ajp/AjpAprProcessor.java
branches/7.0.x/java/org/apache/coyote/ajp/AjpMessage.java
branches/7.0.x/java/org/apache/coyote/ajp/AjpProcessor.java
branches/7.0.x/java/org/apache/coyote/http11/Http11AprProcessor.java
branches/7.0.x/java/org/apache/coyote/http11/Http11Processor.java
branches/7.0.x/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
branches/7.0.x/java/org/apache/coyote/http11/InternalOutputBuffer.java
branches/7.0.x/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/authenticator/DigestAuthenticator.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/realm/RealmBase.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/util/MD5Encoder.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpAprProcessor.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpMessage.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProcessor.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11AprProcessor.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11NioProcessor.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11Processor.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalAprOutputBuffer.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalNioOutputBuffer.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalOutputBuffer.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/filters/ChunkedInputFilter.java
Log:
Port patches to active branches.
Modified: branches/2.1.x/java/org/apache/catalina/authenticator/DigestAuthenticator.java
===================================================================
--- branches/2.1.x/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/2.1.x/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -5,30 +5,27 @@
* 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.authenticator;
-
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
import java.security.SecureRandom;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
-import java.security.Principal;
-import java.util.StringTokenizer;
+import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Container;
@@ -38,42 +35,26 @@
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.util.ConcurrentMessageDigest;
import org.apache.catalina.util.MD5Encoder;
import org.jboss.logging.Logger;
-import org.jboss.logging.Logger;
-
/**
* An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP DIGEST
* Authentication (see RFC 2069).
*
* @author Craig R. McClanahan
* @author Remy Maucherat
- * @version $Revision$ $Date$
+ * @version $Id$
*/
-
-public class DigestAuthenticator
- extends AuthenticatorBase {
+public class DigestAuthenticator extends AuthenticatorBase {
private static Logger log = Logger.getLogger(DigestAuthenticator.class);
// -------------------------------------------------------------- Constants
/**
- * The MD5 helper object for this class.
- */
- protected static final MD5Encoder md5Encoder = new MD5Encoder();
-
-
- /**
- * Descriptive information about this implementation.
- */
- protected static final String info =
- "org.apache.catalina.authenticator.DigestAuthenticator/1.0";
-
-
- /**
* Tomcat's DIGEST implementation only supports auth quality of protection.
*/
protected static final String QOP = "auth";
@@ -83,12 +64,13 @@
public DigestAuthenticator() {
super();
+ setCache(false);
try {
- if (md5Helper == null)
+ if (md5Helper == null) {
md5Helper = MessageDigest.getInstance("MD5");
+ }
} catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- throw new IllegalStateException();
+ throw new IllegalStateException(e);
}
}
@@ -98,21 +80,22 @@
/**
* MD5 message digest provider.
+ * @deprecated Unused - will be removed in Tomcat 8.0.x onwards
*/
protected static MessageDigest md5Helper;
/**
- * List of client nonce values currently being tracked
+ * List of server nonce values currently being tracked
*/
- protected Map<String,NonceInfo> cnonces;
+ protected Map<String,NonceInfo> nonces;
/**
- * Maximum number of client nonces to keep in the cache. If not specified,
+ * Maximum number of server nonces to keep in the cache. If not specified,
* the default value of 1000 is used.
*/
- protected int cnonceCacheSize = 1000;
+ protected int nonceCacheSize = 1000;
/**
@@ -142,27 +125,16 @@
// ------------------------------------------------------------- Properties
- /**
- * Return descriptive information about this Valve implementation.
- */
- @Override
- public String getInfo() {
-
- return (info);
-
+ public int getNonceCacheSize() {
+ return nonceCacheSize;
}
- public int getCnonceCacheSize() {
- return cnonceCacheSize;
+ public void setNonceCacheSize(int nonceCacheSize) {
+ this.nonceCacheSize = nonceCacheSize;
}
- public void setCnonceCacheSize(int cnonceCacheSize) {
- this.cnonceCacheSize = cnonceCacheSize;
- }
-
-
public String getKey() {
return key;
}
@@ -213,8 +185,6 @@
*
* @param request Request we are processing
* @param response Response we are creating
- * @param config Login configuration describing how authentication
- * should be performed
*
* @exception IOException if an input/output error occurs
*/
@@ -222,19 +192,18 @@
public boolean authenticate(Request request,
Response response,
LoginConfig config)
- throws IOException {
+ throws IOException {
// Have we already authenticated someone?
Principal principal = request.getUserPrincipal();
//String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
if (principal != null) {
- if (log.isDebugEnabled())
- log.debug("Already authenticated '" + principal.getName() + "'");
// Associate the session with any existing SSO session in order
// to get coordinated session invalidation at logout
String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
- if (ssoId != null)
+ if (ssoId != null) {
associate(ssoId, request.getSessionInternal(true));
+ }
return (true);
}
@@ -266,19 +235,20 @@
// Validate any credentials already included with this request
String authorization = request.getHeader("authorization");
DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
- getKey(), cnonces, isValidateUri());
+ getKey(), nonces, isValidateUri());
if (authorization != null) {
- if (digestInfo.validate(request, authorization, config)) {
- principal = digestInfo.authenticate(context.getRealm());
+ if (digestInfo.parse(request, authorization)) {
+ if (digestInfo.validate(request, config)) {
+ principal = digestInfo.authenticate(context.getRealm());
+ }
+
+ if (principal != null && !digestInfo.isNonceStale()) {
+ register(request, response, principal,
+ HttpServletRequest.DIGEST_AUTH,
+ digestInfo.getUsername(), null);
+ return true;
+ }
}
-
- if (principal != null) {
- String username = parseUsername(authorization);
- register(request, response, principal,
- Constants.DIGEST_METHOD,
- username, null);
- return (true);
- }
}
// Send an "unauthorized" response and an appropriate challenge
@@ -288,11 +258,9 @@
String nonce = generateNonce(request);
setAuthenticateHeader(request, response, config, nonce,
- digestInfo.isNonceStale());
+ principal != null && digestInfo.isNonceStale());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
- // hres.flushBuffer();
- return (false);
-
+ return false;
}
@@ -300,42 +268,6 @@
/**
- * Parse the username from the specified authorization string. If none
- * can be identified, return <code>null</code>
- *
- * @param authorization Authorization string to be parsed
- */
- protected String parseUsername(String authorization) {
-
- // Validate the authorization credentials format
- if (authorization == null)
- return (null);
- if (!authorization.startsWith("Digest "))
- return (null);
- authorization = authorization.substring(7).trim();
-
- StringTokenizer commaTokenizer =
- new StringTokenizer(authorization, ",");
-
- while (commaTokenizer.hasMoreTokens()) {
- String currentToken = commaTokenizer.nextToken();
- int equalSign = currentToken.indexOf('=');
- if (equalSign < 0)
- return null;
- String currentTokenName =
- currentToken.substring(0, equalSign).trim();
- String currentTokenValue =
- currentToken.substring(equalSign + 1).trim();
- if ("username".equals(currentTokenName))
- return (removeQuotes(currentTokenValue));
- }
-
- return (null);
-
- }
-
-
- /**
* Removes the quotes on a string. RFC2617 states quotes are optional for
* all parameters except realm.
*/
@@ -348,7 +280,7 @@
} else if (quotedString.length() > 2) {
return quotedString.substring(1, quotedString.length() - 1);
} else {
- return new String();
+ return "";
}
}
@@ -370,16 +302,19 @@
long currentTime = System.currentTimeMillis();
-
+
String ipTimeKey =
request.getRemoteAddr() + ":" + currentTime + ":" + getKey();
- byte[] buffer;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(ipTimeKey.getBytes());
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(ipTimeKey.getBytes());
+ String nonce = currentTime + ":" + MD5Encoder.encode(buffer);
+
+ NonceInfo info = new NonceInfo(currentTime, 100);
+ synchronized (nonces) {
+ nonces.put(nonce, info);
}
- return currentTime + ":" + md5Encoder.encode(buffer);
+ return nonce;
}
@@ -406,12 +341,10 @@
*
* @param request HTTP Servlet request
* @param response HTTP Servlet response
- * @param config Login configuration describing how authentication
- * should be performed
* @param nonce nonce token
*/
- protected void setAuthenticateHeader(Request request,
- Response response,
+ protected void setAuthenticateHeader(HttpServletRequest request,
+ HttpServletResponse response,
LoginConfig config,
String nonce,
boolean isNonceStale) {
@@ -430,15 +363,15 @@
authenticateHeader = "Digest realm=\"" + realmName + "\", " +
"qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
getOpaque() + "\"";
- }
+ }
- response.setHeader("WWW-Authenticate", authenticateHeader);
+ response.setHeader(AUTH_HEADER_NAME, authenticateHeader);
}
// ------------------------------------------------------- Lifecycle Methods
-
+
@Override
public void start() throws LifecycleException {
super.start();
@@ -447,14 +380,14 @@
if (getKey() == null) {
setKey(generateSessionId());
}
-
+
// Generate the opaque string the same way
if (getOpaque() == null) {
setOpaque(generateSessionId());
}
-
- cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+ nonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+
private static final long serialVersionUID = 1L;
private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
@@ -465,7 +398,7 @@
Map.Entry<String,NonceInfo> eldest) {
// This is called from a sync so keep it simple
long currentTime = System.currentTimeMillis();
- if (size() > getCnonceCacheSize()) {
+ if (size() > getNonceCacheSize()) {
if (lastLog < currentTime &&
currentTime - eldest.getValue().getTimestamp() <
getNonceValidity()) {
@@ -480,13 +413,13 @@
}
};
}
-
+
private static class DigestInfo {
- private String opaque;
- private long nonceValidity;
- private String key;
- private Map<String,NonceInfo> cnonces;
+ private final String opaque;
+ private final long nonceValidity;
+ private final String key;
+ private final Map<String,NonceInfo> nonces;
private boolean validateUri = true;
private String userName = null;
@@ -498,21 +431,27 @@
private String cnonce = null;
private String realmName = null;
private String qop = null;
+ private String opaqueReceived = null;
private boolean nonceStale = false;
public DigestInfo(String opaque, long nonceValidity, String key,
- Map<String,NonceInfo> cnonces, boolean validateUri) {
+ Map<String,NonceInfo> nonces, boolean validateUri) {
this.opaque = opaque;
this.nonceValidity = nonceValidity;
this.key = key;
- this.cnonces = cnonces;
+ this.nonces = nonces;
this.validateUri = validateUri;
}
- public boolean validate(Request request, String authorization,
- LoginConfig config) {
+
+ public String getUsername() {
+ return userName;
+ }
+
+
+ public boolean parse(Request request, String authorization) {
// Validate the authorization credentials format
if (authorization == null) {
return false;
@@ -526,12 +465,12 @@
String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
method = request.getMethod();
- String opaque = null;
for (int i = 0; i < tokens.length; i++) {
String currentToken = tokens[i];
- if (currentToken.length() == 0)
+ if (currentToken.length() == 0) {
continue;
+ }
int equalSign = currentToken.indexOf('=');
if (equalSign < 0) {
@@ -541,26 +480,39 @@
currentToken.substring(0, equalSign).trim();
String currentTokenValue =
currentToken.substring(equalSign + 1).trim();
- if ("username".equals(currentTokenName))
+ if ("username".equals(currentTokenName)) {
userName = removeQuotes(currentTokenValue);
- if ("realm".equals(currentTokenName))
+ }
+ if ("realm".equals(currentTokenName)) {
realmName = removeQuotes(currentTokenValue, true);
- if ("nonce".equals(currentTokenName))
+ }
+ if ("nonce".equals(currentTokenName)) {
nonce = removeQuotes(currentTokenValue);
- if ("nc".equals(currentTokenName))
+ }
+ if ("nc".equals(currentTokenName)) {
nc = removeQuotes(currentTokenValue);
- if ("cnonce".equals(currentTokenName))
+ }
+ if ("cnonce".equals(currentTokenName)) {
cnonce = removeQuotes(currentTokenValue);
- if ("qop".equals(currentTokenName))
+ }
+ if ("qop".equals(currentTokenName)) {
qop = removeQuotes(currentTokenValue);
- if ("uri".equals(currentTokenName))
+ }
+ if ("uri".equals(currentTokenName)) {
uri = removeQuotes(currentTokenValue);
- if ("response".equals(currentTokenName))
+ }
+ if ("response".equals(currentTokenName)) {
response = removeQuotes(currentTokenValue);
- if ("opaque".equals(currentTokenName))
- opaque = removeQuotes(currentTokenValue);
+ }
+ if ("opaque".equals(currentTokenName)) {
+ opaqueReceived = removeQuotes(currentTokenValue);
+ }
}
+ return true;
+ }
+
+ public boolean validate(Request request, LoginConfig config) {
if ( (userName == null) || (realmName == null) || (nonce == null)
|| (uri == null) || (response == null) ) {
return false;
@@ -576,7 +528,23 @@
uriQuery = request.getRequestURI() + "?" + query;
}
if (!uri.equals(uriQuery)) {
- return false;
+ // Some clients (older Android) use an absolute URI for
+ // DIGEST but a relative URI in the request line.
+ // request. 2.3.5 < fixed Android version <= 4.0.3
+ String host = request.getHeader("host");
+ String scheme = request.getScheme();
+ if (host != null && !uriQuery.startsWith(scheme)) {
+ StringBuilder absolute = new StringBuilder();
+ absolute.append(scheme);
+ absolute.append("://");
+ absolute.append(host);
+ absolute.append(uriQuery);
+ if (!uri.equals(absolute.toString())) {
+ return false;
+ }
+ } else {
+ return false;
+ }
}
}
@@ -588,9 +556,9 @@
if (!lcRealm.equals(realmName)) {
return false;
}
-
+
// Validate the opaque string
- if (!this.opaque.equals(opaque)) {
+ if (!opaque.equals(opaqueReceived)) {
return false;
}
@@ -609,15 +577,15 @@
long currentTime = System.currentTimeMillis();
if ((currentTime - nonceTime) > nonceValidity) {
nonceStale = true;
- return false;
+ synchronized (nonces) {
+ nonces.remove(nonce);
+ }
}
String serverIpTimeKey =
request.getRemoteAddr() + ":" + nonceTime + ":" + key;
- byte[] buffer = null;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(serverIpTimeKey.getBytes());
- }
- String md5ServerIpTimeKey = md5Encoder.encode(buffer);
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(
+ serverIpTimeKey.getBytes());
+ String md5ServerIpTimeKey = MD5Encoder.encode(buffer);
if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
return false;
}
@@ -628,7 +596,7 @@
}
// Validate cnonce and nc
- // Check if presence of nc and nonce is consistent with presence of qop
+ // Check if presence of nc and Cnonce is consistent with presence of qop
if (qop == null) {
if (cnonce != null || nc != null) {
return false;
@@ -637,7 +605,9 @@
if (cnonce == null || nc == null) {
return false;
}
- if (nc.length() != 8) {
+ // RFC 2617 says nc must be 8 digits long. Older Android clients
+ // use 6. 2.3.5 < fixed Android version <= 4.0.3
+ if (nc.length() < 6 || nc.length() > 8) {
return false;
}
long count;
@@ -647,21 +617,18 @@
return false;
}
NonceInfo info;
- synchronized (cnonces) {
- info = cnonces.get(cnonce);
+ synchronized (nonces) {
+ info = nonces.get(nonce);
}
if (info == null) {
- info = new NonceInfo();
+ // Nonce is valid but not in cache. It must have dropped out
+ // of the cache - force a re-authentication
+ nonceStale = true;
} else {
- if (count <= info.getCount()) {
+ if (!info.nonceCountValid(count)) {
return false;
}
}
- info.setCount(count);
- info.setTimestamp(currentTime);
- synchronized (cnonces) {
- cnonces.put(cnonce, info);
- }
}
return true;
}
@@ -675,11 +642,9 @@
// MD5(Method + ":" + uri)
String a2 = method + ":" + uri;
- byte[] buffer;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(a2.getBytes());
- }
- String md5a2 = md5Encoder.encode(buffer);
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(
+ a2.getBytes());
+ String md5a2 = MD5Encoder.encode(buffer);
return realm.authenticate(userName, response, nonce, nc, cnonce,
qop, realmName, md5a2);
@@ -688,21 +653,33 @@
}
private static class NonceInfo {
- private volatile long count;
private volatile long timestamp;
-
- public void setCount(long l) {
- count = l;
+ private volatile boolean seen[];
+ private volatile int offset;
+ private volatile int count = 0;
+
+ public NonceInfo(long currentTime, int seenWindowSize) {
+ this.timestamp = currentTime;
+ seen = new boolean[seenWindowSize];
+ offset = seenWindowSize / 2;
}
-
- public long getCount() {
- return count;
+
+ public synchronized boolean nonceCountValid(long nonceCount) {
+ if ((count - offset) >= nonceCount ||
+ (nonceCount > count - offset + seen.length)) {
+ return false;
+ }
+ int checkIndex = (int) ((nonceCount + offset) % seen.length);
+ if (seen[checkIndex]) {
+ return false;
+ } else {
+ seen[checkIndex] = true;
+ seen[count % seen.length] = false;
+ count++;
+ return true;
+ }
}
-
- public void setTimestamp(long l) {
- timestamp = l;
- }
-
+
public long getTimestamp() {
return timestamp;
}
Modified: branches/2.1.x/java/org/apache/catalina/realm/RealmBase.java
===================================================================
--- branches/2.1.x/java/org/apache/catalina/realm/RealmBase.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/2.1.x/java/org/apache/catalina/realm/RealmBase.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -728,31 +728,6 @@
if (constraints == null || constraints.length == 0)
return (true);
- // Specifically allow access to the form login and form error pages
- // and the "j_security_check" action
- LoginConfig config = context.getLoginConfig();
- if ((config != null) &&
- (Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
- String requestURI = request.getRequestPathMB().toString();
- String loginPage = config.getLoginPage();
- if (loginPage.equals(requestURI)) {
- if (log.isDebugEnabled())
- log.debug(" Allow access to login page " + loginPage);
- return (true);
- }
- String errorPage = config.getErrorPage();
- if (errorPage.equals(requestURI)) {
- if (log.isDebugEnabled())
- log.debug(" Allow access to error page " + errorPage);
- return (true);
- }
- if (requestURI.endsWith(Constants.FORM_ACTION)) {
- if (log.isDebugEnabled())
- log.debug(" Allow access to username/password submission");
- return (true);
- }
- }
-
// Which user principal have we already authenticated?
Principal principal = request.getPrincipal();
boolean status = false;
Added: branches/2.1.x/java/org/apache/catalina/util/ConcurrentMessageDigest.java
===================================================================
--- branches/2.1.x/java/org/apache/catalina/util/ConcurrentMessageDigest.java (rev 0)
+++ branches/2.1.x/java/org/apache/catalina/util/ConcurrentMessageDigest.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -0,0 +1,104 @@
+/*
+ * 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.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * A thread safe wrapper around {@link MessageDigest} that does not make use
+ * of ThreadLocal and - broadly - only creates enough MessageDigest objects
+ * to satisfy the concurrency requirements.
+ */
+public class ConcurrentMessageDigest {
+
+ private static final String MD5 = "MD5";
+
+ private static final Map<String,Queue<MessageDigest>> queues =
+ new HashMap<String,Queue<MessageDigest>>();
+
+
+ private ConcurrentMessageDigest() {
+ // Hide default constructor for this utility class
+ }
+
+ static {
+ try {
+ // Init commonly used algorithms
+ init(MD5);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static byte[] digestMD5(byte[] input) {
+ return digest(MD5, input);
+ }
+
+ public static byte[] digest(String algorithm, byte[] input) {
+
+ Queue<MessageDigest> queue = queues.get(algorithm);
+ if (queue == null) {
+ throw new IllegalStateException("Must call init() first");
+ }
+
+ MessageDigest md = queue.poll();
+ if (md == null) {
+ try {
+ md = MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ // Ignore. Impossible if init() has been successfully called
+ // first.
+ throw new IllegalStateException("Must call init() first");
+ }
+ }
+
+ byte[] result = md.digest(input);
+
+ queue.add(md);
+
+ return result;
+ }
+
+
+ /**
+ * Ensures that {@link #digest(String, byte[])} and
+ * {@link #digestAsHex(String, byte[])} will support the specified
+ * algorithm. This method <b>must</b> be called and return successfully
+ * before using {@link #digest(String, byte[])} or
+ * {@link #digestAsHex(String, byte[])}.
+ *
+ * @param algorithm The message digest algorithm to be supported
+ *
+ * @throws NoSuchAlgorithmException If the algorithm is not supported by the
+ * JVM
+ */
+ public static void init(String algorithm) throws NoSuchAlgorithmException {
+ synchronized (queues) {
+ if (!queues.containsKey(algorithm)) {
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ Queue<MessageDigest> queue = new ConcurrentLinkedQueue<MessageDigest>();
+ queue.add(md);
+ queues.put(algorithm, queue);
+ }
+ }
+ }
+}
Modified: branches/2.1.x/java/org/apache/catalina/util/MD5Encoder.java
===================================================================
--- branches/2.1.x/java/org/apache/catalina/util/MD5Encoder.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/2.1.x/java/org/apache/catalina/util/MD5Encoder.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -50,7 +50,7 @@
* @param binaryData Array containing the digest
* @return Encoded MD5, or null if encoding failed
*/
- public String encode( byte[] binaryData ) {
+ public static String encode( byte[] binaryData ) {
if (binaryData.length != 16)
return null;
Modified: branches/2.1.x/java/org/apache/coyote/ajp/AjpAprProcessor.java
===================================================================
--- branches/2.1.x/java/org/apache/coyote/ajp/AjpAprProcessor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/2.1.x/java/org/apache/coyote/ajp/AjpAprProcessor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -897,7 +897,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/2.1.x/java/org/apache/coyote/ajp/AjpMessage.java
===================================================================
--- branches/2.1.x/java/org/apache/coyote/ajp/AjpMessage.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/2.1.x/java/org/apache/coyote/ajp/AjpMessage.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -251,10 +251,8 @@
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
- if ((c <= 31) && (c != 9)) {
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
c = ' ';
- } else if (c == 127) {
- c = ' ';
}
appendByte(c);
}
Modified: branches/2.1.x/java/org/apache/coyote/ajp/AjpProcessor.java
===================================================================
--- branches/2.1.x/java/org/apache/coyote/ajp/AjpProcessor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/2.1.x/java/org/apache/coyote/ajp/AjpProcessor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -902,7 +902,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/2.1.x/java/org/apache/coyote/http11/Http11AprProcessor.java
===================================================================
--- branches/2.1.x/java/org/apache/coyote/http11/Http11AprProcessor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/2.1.x/java/org/apache/coyote/http11/Http11AprProcessor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -1558,7 +1558,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/2.1.x/java/org/apache/coyote/http11/Http11Processor.java
===================================================================
--- branches/2.1.x/java/org/apache/coyote/http11/Http11Processor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/2.1.x/java/org/apache/coyote/http11/Http11Processor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -1363,7 +1363,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/2.1.x/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
===================================================================
--- branches/2.1.x/java/org/apache/coyote/http11/InternalAprOutputBuffer.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/2.1.x/java/org/apache/coyote/http11/InternalAprOutputBuffer.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -673,10 +673,8 @@
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
- if ((c <= 31) && (c != 9)) {
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
c = ' ';
- } else if (c == 127) {
- c = ' ';
}
buf[pos++] = (byte) c;
}
Modified: branches/2.1.x/java/org/apache/coyote/http11/InternalOutputBuffer.java
===================================================================
--- branches/2.1.x/java/org/apache/coyote/http11/InternalOutputBuffer.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/2.1.x/java/org/apache/coyote/http11/InternalOutputBuffer.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -688,10 +688,8 @@
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
- if ((c <= 31) && (c != 9)) {
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
c = ' ';
- } else if (c == 127) {
- c = ' ';
}
buf[pos++] = (byte) c;
}
Modified: branches/2.1.x/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
===================================================================
--- branches/2.1.x/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/2.1.x/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -266,6 +266,7 @@
int result = 0;
boolean eol = false;
+ boolean crfound = false;
boolean readDigit = false;
boolean trailer = false;
@@ -282,13 +283,18 @@
}
if (buf[pos] == Constants.CR) {
+ if (crfound) throw new IOException("Invalid CRLF, two CR characters encountered.");
+ crfound = true;
} else if (buf[pos] == Constants.LF) {
+ if (!crfound) throw new IOException("Invalid CRLF, no CR character encountered.");
eol = true;
} else if (buf[pos] == Constants.SEMI_COLON) {
trailer = true;
+ } else if (buf[pos] < 0) {
+ throw new IOException("Invalid chunk header");
} else if (!trailer) {
//don't read data after the trailer
- if (HexUtils.DEC[buf[pos]] != -1) {
+ if (HexUtils.DEC[buf[pos] & 0xff] != -1) {
readDigit = true;
result *= 16;
result += HexUtils.DEC[buf[pos]];
Modified: branches/7.0.x/.classpath
===================================================================
--- branches/7.0.x/.classpath 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/.classpath 2012-10-10 17:50:36 UTC (rev 2107)
@@ -2,6 +2,5 @@
<classpath>
<classpathentry excluding="**/.svn/**" kind="src" path="java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry kind="lib" path="output/jars/jasper-jdt.jar"/>
<classpathentry kind="output" path=".settings/output"/>
</classpath>
Modified: branches/7.0.x/java/org/apache/catalina/authenticator/DigestAuthenticator.java
===================================================================
--- branches/7.0.x/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -5,20 +5,17 @@
* 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.authenticator;
-
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -27,7 +24,6 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
-import java.util.StringTokenizer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -38,11 +34,12 @@
import org.apache.catalina.Realm;
import org.apache.catalina.connector.Request;
import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.util.ConcurrentMessageDigest;
import org.apache.catalina.util.MD5Encoder;
+import org.apache.tomcat.util.buf.EncodingToCharset;
import org.jboss.logging.Logger;
-
/**
* An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP DIGEST
* Authentication (see RFC 2069).
@@ -51,28 +48,13 @@
* @author Remy Maucherat
* @version $Id$
*/
-
-public class DigestAuthenticator
- extends AuthenticatorBase {
+public class DigestAuthenticator extends AuthenticatorBase {
private static Logger log = Logger.getLogger(DigestAuthenticator.class);
// -------------------------------------------------------------- Constants
/**
- * The MD5 helper object for this class.
- */
- protected static final MD5Encoder md5Encoder = new MD5Encoder();
-
-
- /**
- * Descriptive information about this implementation.
- */
- protected static final String info =
- "org.apache.catalina.authenticator.DigestAuthenticator/1.0";
-
-
- /**
* Tomcat's DIGEST implementation only supports auth quality of protection.
*/
protected static final String QOP = "auth";
@@ -82,9 +64,11 @@
public DigestAuthenticator() {
super();
+ setCache(false);
try {
- if (md5Helper == null)
+ if (md5Helper == null) {
md5Helper = MessageDigest.getInstance("MD5");
+ }
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
@@ -96,21 +80,22 @@
/**
* MD5 message digest provider.
+ * @deprecated Unused - will be removed in Tomcat 8.0.x onwards
*/
protected static MessageDigest md5Helper;
/**
- * List of client nonce values currently being tracked
+ * List of server nonce values currently being tracked
*/
- protected Map<String,NonceInfo> cnonces;
+ protected Map<String,NonceInfo> nonces;
/**
- * Maximum number of client nonces to keep in the cache. If not specified,
+ * Maximum number of server nonces to keep in the cache. If not specified,
* the default value of 1000 is used.
*/
- protected int cnonceCacheSize = 1000;
+ protected int nonceCacheSize = 1000;
/**
@@ -140,27 +125,16 @@
// ------------------------------------------------------------- Properties
- /**
- * Return descriptive information about this Valve implementation.
- */
- @Override
- public String getInfo() {
-
- return (info);
-
+ public int getNonceCacheSize() {
+ return nonceCacheSize;
}
- public int getCnonceCacheSize() {
- return cnonceCacheSize;
+ public void setNonceCacheSize(int nonceCacheSize) {
+ this.nonceCacheSize = nonceCacheSize;
}
- public void setCnonceCacheSize(int cnonceCacheSize) {
- this.cnonceCacheSize = cnonceCacheSize;
- }
-
-
public String getKey() {
return key;
}
@@ -211,8 +185,6 @@
*
* @param request Request we are processing
* @param response Response we are creating
- * @param config Login configuration describing how authentication
- * should be performed
*
* @exception IOException if an input/output error occurs
*/
@@ -220,19 +192,18 @@
public boolean authenticate(Request request,
HttpServletResponse response,
LoginConfig config)
- throws IOException {
+ throws IOException {
// Have we already authenticated someone?
Principal principal = request.getUserPrincipal();
//String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
if (principal != null) {
- if (log.isDebugEnabled())
- log.debug("Already authenticated '" + principal.getName() + "'");
// Associate the session with any existing SSO session in order
// to get coordinated session invalidation at logout
String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
- if (ssoId != null)
+ if (ssoId != null) {
associate(ssoId, request.getSessionInternal(true));
+ }
return (true);
}
@@ -264,19 +235,20 @@
// Validate any credentials already included with this request
String authorization = request.getHeader("authorization");
DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
- getKey(), cnonces, isValidateUri());
+ getKey(), nonces, isValidateUri());
if (authorization != null) {
- if (digestInfo.validate(request, authorization, config)) {
- principal = digestInfo.authenticate(context.getRealm());
+ if (digestInfo.parse(request, authorization)) {
+ if (digestInfo.validate(request, config)) {
+ principal = digestInfo.authenticate(context.getRealm());
+ }
+
+ if (principal != null && !digestInfo.isNonceStale()) {
+ register(request, response, principal,
+ HttpServletRequest.DIGEST_AUTH,
+ digestInfo.getUsername(), null);
+ return true;
+ }
}
-
- if (principal != null) {
- String username = parseUsername(authorization);
- register(request, response, principal,
- HttpServletRequest.DIGEST_AUTH,
- username, null);
- return (true);
- }
}
// Send an "unauthorized" response and an appropriate challenge
@@ -286,11 +258,9 @@
String nonce = generateNonce(request);
setAuthenticateHeader(request, response, config, nonce,
- digestInfo.isNonceStale());
+ principal != null && digestInfo.isNonceStale());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
- // hres.flushBuffer();
- return (false);
-
+ return false;
}
@@ -298,42 +268,6 @@
/**
- * Parse the username from the specified authorization string. If none
- * can be identified, return <code>null</code>
- *
- * @param authorization Authorization string to be parsed
- */
- protected String parseUsername(String authorization) {
-
- // Validate the authorization credentials format
- if (authorization == null)
- return (null);
- if (!authorization.startsWith("Digest "))
- return (null);
- authorization = authorization.substring(7).trim();
-
- StringTokenizer commaTokenizer =
- new StringTokenizer(authorization, ",");
-
- while (commaTokenizer.hasMoreTokens()) {
- String currentToken = commaTokenizer.nextToken();
- int equalSign = currentToken.indexOf('=');
- if (equalSign < 0)
- return null;
- String currentTokenName =
- currentToken.substring(0, equalSign).trim();
- String currentTokenValue =
- currentToken.substring(equalSign + 1).trim();
- if ("username".equals(currentTokenName))
- return (removeQuotes(currentTokenValue));
- }
-
- return (null);
-
- }
-
-
- /**
* Removes the quotes on a string. RFC2617 states quotes are optional for
* all parameters except realm.
*/
@@ -346,7 +280,7 @@
} else if (quotedString.length() > 2) {
return quotedString.substring(1, quotedString.length() - 1);
} else {
- return new String();
+ return "";
}
}
@@ -368,16 +302,20 @@
long currentTime = System.currentTimeMillis();
-
+
String ipTimeKey =
request.getRemoteAddr() + ":" + currentTime + ":" + getKey();
- byte[] buffer;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(ipTimeKey.getBytes());
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(
+ ipTimeKey.getBytes(EncodingToCharset.ISO_8859_1));
+ String nonce = currentTime + ":" + MD5Encoder.encode(buffer);
+
+ NonceInfo info = new NonceInfo(currentTime, 100);
+ synchronized (nonces) {
+ nonces.put(nonce, info);
}
- return currentTime + ":" + md5Encoder.encode(buffer);
+ return nonce;
}
@@ -404,11 +342,9 @@
*
* @param request HTTP Servlet request
* @param response HTTP Servlet response
- * @param config Login configuration describing how authentication
- * should be performed
* @param nonce nonce token
*/
- protected void setAuthenticateHeader(Request request,
+ protected void setAuthenticateHeader(HttpServletRequest request,
HttpServletResponse response,
LoginConfig config,
String nonce,
@@ -430,13 +366,13 @@
getOpaque() + "\"";
}
- response.setHeader("WWW-Authenticate", authenticateHeader);
+ response.setHeader(AUTH_HEADER_NAME, authenticateHeader);
}
// ------------------------------------------------------- Lifecycle Methods
-
+
@Override
public void start() throws LifecycleException {
super.start();
@@ -460,14 +396,14 @@
if (getKey() == null) {
setKey(generateSessionId(random));
}
-
+
// Generate the opaque string the same way
if (getOpaque() == null) {
setOpaque(generateSessionId(random));
}
-
- cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+ nonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+
private static final long serialVersionUID = 1L;
private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
@@ -478,7 +414,7 @@
Map.Entry<String,NonceInfo> eldest) {
// This is called from a sync so keep it simple
long currentTime = System.currentTimeMillis();
- if (size() > getCnonceCacheSize()) {
+ if (size() > getNonceCacheSize()) {
if (lastLog < currentTime &&
currentTime - eldest.getValue().getTimestamp() <
getNonceValidity()) {
@@ -493,13 +429,13 @@
}
};
}
-
+
private static class DigestInfo {
- private String opaque;
- private long nonceValidity;
- private String key;
- private Map<String,NonceInfo> cnonces;
+ private final String opaque;
+ private final long nonceValidity;
+ private final String key;
+ private final Map<String,NonceInfo> nonces;
private boolean validateUri = true;
private String userName = null;
@@ -511,21 +447,27 @@
private String cnonce = null;
private String realmName = null;
private String qop = null;
+ private String opaqueReceived = null;
private boolean nonceStale = false;
public DigestInfo(String opaque, long nonceValidity, String key,
- Map<String,NonceInfo> cnonces, boolean validateUri) {
+ Map<String,NonceInfo> nonces, boolean validateUri) {
this.opaque = opaque;
this.nonceValidity = nonceValidity;
this.key = key;
- this.cnonces = cnonces;
+ this.nonces = nonces;
this.validateUri = validateUri;
}
- public boolean validate(Request request, String authorization,
- LoginConfig config) {
+
+ public String getUsername() {
+ return userName;
+ }
+
+
+ public boolean parse(Request request, String authorization) {
// Validate the authorization credentials format
if (authorization == null) {
return false;
@@ -539,12 +481,12 @@
String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
method = request.getMethod();
- String opaque = null;
for (int i = 0; i < tokens.length; i++) {
String currentToken = tokens[i];
- if (currentToken.length() == 0)
+ if (currentToken.length() == 0) {
continue;
+ }
int equalSign = currentToken.indexOf('=');
if (equalSign < 0) {
@@ -554,26 +496,39 @@
currentToken.substring(0, equalSign).trim();
String currentTokenValue =
currentToken.substring(equalSign + 1).trim();
- if ("username".equals(currentTokenName))
+ if ("username".equals(currentTokenName)) {
userName = removeQuotes(currentTokenValue);
- if ("realm".equals(currentTokenName))
+ }
+ if ("realm".equals(currentTokenName)) {
realmName = removeQuotes(currentTokenValue, true);
- if ("nonce".equals(currentTokenName))
+ }
+ if ("nonce".equals(currentTokenName)) {
nonce = removeQuotes(currentTokenValue);
- if ("nc".equals(currentTokenName))
+ }
+ if ("nc".equals(currentTokenName)) {
nc = removeQuotes(currentTokenValue);
- if ("cnonce".equals(currentTokenName))
+ }
+ if ("cnonce".equals(currentTokenName)) {
cnonce = removeQuotes(currentTokenValue);
- if ("qop".equals(currentTokenName))
+ }
+ if ("qop".equals(currentTokenName)) {
qop = removeQuotes(currentTokenValue);
- if ("uri".equals(currentTokenName))
+ }
+ if ("uri".equals(currentTokenName)) {
uri = removeQuotes(currentTokenValue);
- if ("response".equals(currentTokenName))
+ }
+ if ("response".equals(currentTokenName)) {
response = removeQuotes(currentTokenValue);
- if ("opaque".equals(currentTokenName))
- opaque = removeQuotes(currentTokenValue);
+ }
+ if ("opaque".equals(currentTokenName)) {
+ opaqueReceived = removeQuotes(currentTokenValue);
+ }
}
+ return true;
+ }
+
+ public boolean validate(Request request, LoginConfig config) {
if ( (userName == null) || (realmName == null) || (nonce == null)
|| (uri == null) || (response == null) ) {
return false;
@@ -589,7 +544,23 @@
uriQuery = request.getRequestURI() + "?" + query;
}
if (!uri.equals(uriQuery)) {
- return false;
+ // Some clients (older Android) use an absolute URI for
+ // DIGEST but a relative URI in the request line.
+ // request. 2.3.5 < fixed Android version <= 4.0.3
+ String host = request.getHeader("host");
+ String scheme = request.getScheme();
+ if (host != null && !uriQuery.startsWith(scheme)) {
+ StringBuilder absolute = new StringBuilder();
+ absolute.append(scheme);
+ absolute.append("://");
+ absolute.append(host);
+ absolute.append(uriQuery);
+ if (!uri.equals(absolute.toString())) {
+ return false;
+ }
+ } else {
+ return false;
+ }
}
}
@@ -601,9 +572,9 @@
if (!lcRealm.equals(realmName)) {
return false;
}
-
+
// Validate the opaque string
- if (!this.opaque.equals(opaque)) {
+ if (!opaque.equals(opaqueReceived)) {
return false;
}
@@ -622,15 +593,15 @@
long currentTime = System.currentTimeMillis();
if ((currentTime - nonceTime) > nonceValidity) {
nonceStale = true;
- return false;
+ synchronized (nonces) {
+ nonces.remove(nonce);
+ }
}
String serverIpTimeKey =
request.getRemoteAddr() + ":" + nonceTime + ":" + key;
- byte[] buffer = null;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(serverIpTimeKey.getBytes());
- }
- String md5ServerIpTimeKey = md5Encoder.encode(buffer);
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(
+ serverIpTimeKey.getBytes(EncodingToCharset.ISO_8859_1));
+ String md5ServerIpTimeKey = MD5Encoder.encode(buffer);
if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
return false;
}
@@ -641,7 +612,7 @@
}
// Validate cnonce and nc
- // Check if presence of nc and nonce is consistent with presence of qop
+ // Check if presence of nc and Cnonce is consistent with presence of qop
if (qop == null) {
if (cnonce != null || nc != null) {
return false;
@@ -650,7 +621,9 @@
if (cnonce == null || nc == null) {
return false;
}
- if (nc.length() != 8) {
+ // RFC 2617 says nc must be 8 digits long. Older Android clients
+ // use 6. 2.3.5 < fixed Android version <= 4.0.3
+ if (nc.length() < 6 || nc.length() > 8) {
return false;
}
long count;
@@ -660,21 +633,18 @@
return false;
}
NonceInfo info;
- synchronized (cnonces) {
- info = cnonces.get(cnonce);
+ synchronized (nonces) {
+ info = nonces.get(nonce);
}
if (info == null) {
- info = new NonceInfo();
+ // Nonce is valid but not in cache. It must have dropped out
+ // of the cache - force a re-authentication
+ nonceStale = true;
} else {
- if (count <= info.getCount()) {
+ if (!info.nonceCountValid(count)) {
return false;
}
}
- info.setCount(count);
- info.setTimestamp(currentTime);
- synchronized (cnonces) {
- cnonces.put(cnonce, info);
- }
}
return true;
}
@@ -688,11 +658,9 @@
// MD5(Method + ":" + uri)
String a2 = method + ":" + uri;
- byte[] buffer;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(a2.getBytes());
- }
- String md5a2 = md5Encoder.encode(buffer);
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(
+ a2.getBytes(EncodingToCharset.ISO_8859_1));
+ String md5a2 = MD5Encoder.encode(buffer);
return realm.authenticate(userName, response, nonce, nc, cnonce,
qop, realmName, md5a2);
@@ -701,21 +669,33 @@
}
private static class NonceInfo {
- private volatile long count;
private volatile long timestamp;
-
- public void setCount(long l) {
- count = l;
+ private volatile boolean seen[];
+ private volatile int offset;
+ private volatile int count = 0;
+
+ public NonceInfo(long currentTime, int seenWindowSize) {
+ this.timestamp = currentTime;
+ seen = new boolean[seenWindowSize];
+ offset = seenWindowSize / 2;
}
-
- public long getCount() {
- return count;
+
+ public synchronized boolean nonceCountValid(long nonceCount) {
+ if ((count - offset) >= nonceCount ||
+ (nonceCount > count - offset + seen.length)) {
+ return false;
+ }
+ int checkIndex = (int) ((nonceCount + offset) % seen.length);
+ if (seen[checkIndex]) {
+ return false;
+ } else {
+ seen[checkIndex] = true;
+ seen[count % seen.length] = false;
+ count++;
+ return true;
+ }
}
-
- public void setTimestamp(long l) {
- timestamp = l;
- }
-
+
public long getTimestamp() {
return timestamp;
}
Modified: branches/7.0.x/java/org/apache/catalina/connector/Response.java
===================================================================
--- branches/7.0.x/java/org/apache/catalina/connector/Response.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/catalina/connector/Response.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -37,6 +37,7 @@
import java.util.Vector;
import javax.servlet.ServletOutputStream;
+import javax.servlet.SessionTrackingMode;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
@@ -1595,6 +1596,12 @@
// Are we in a valid session that is not using cookies?
final Request hreq = request;
+
+ // Is URL encoding permitted
+ if (!hreq.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) {
+ return false;
+ }
+
final Session session = hreq.getSessionInternal(false);
if (session == null)
return (false);
Modified: branches/7.0.x/java/org/apache/catalina/filters/CsrfPreventionFilter.java
===================================================================
--- branches/7.0.x/java/org/apache/catalina/filters/CsrfPreventionFilter.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/catalina/filters/CsrfPreventionFilter.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -33,6 +33,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpSession;
/**
* Provides basic CSRF protection for a web application. The filter assumes
@@ -142,16 +143,19 @@
}
}
+ HttpSession session = req.getSession(false);
+
@SuppressWarnings("unchecked")
- LruCache<String> nonceCache =
- (LruCache<String>) req.getSession(true).getAttribute(
+ LruCache<String> nonceCache = (session == null) ? null :
+ (LruCache<String>) session.getAttribute(
Constants.CSRF_NONCE_SESSION_ATTR_NAME);
if (!skipNonceCheck) {
String previousNonce =
req.getParameter(Constants.CSRF_NONCE_REQUEST_PARAM);
- if (nonceCache != null && !nonceCache.contains(previousNonce)) {
+ if (nonceCache == null || previousNonce == null
+ || !nonceCache.contains(previousNonce)) {
res.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
@@ -159,7 +163,10 @@
if (nonceCache == null) {
nonceCache = new LruCache<String>(nonceCacheSize);
- req.getSession().setAttribute(
+ if (session == null) {
+ session = req.getSession(true);
+ }
+ session.setAttribute(
Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache);
}
Modified: branches/7.0.x/java/org/apache/catalina/realm/RealmBase.java
===================================================================
--- branches/7.0.x/java/org/apache/catalina/realm/RealmBase.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/catalina/realm/RealmBase.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -741,31 +741,6 @@
if (constraints == null || constraints.length == 0)
return (true);
- // Specifically allow access to the form login and form error pages
- // and the "j_security_check" action
- LoginConfig config = context.getLoginConfig();
- if ((config != null) &&
- (Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
- String requestURI = request.getRequestPathMB().toString();
- String loginPage = config.getLoginPage();
- if (loginPage.equals(requestURI)) {
- if (log.isDebugEnabled())
- log.debug(" Allow access to login page " + loginPage);
- return (true);
- }
- String errorPage = config.getErrorPage();
- if (errorPage.equals(requestURI)) {
- if (log.isDebugEnabled())
- log.debug(" Allow access to error page " + errorPage);
- return (true);
- }
- if (requestURI.endsWith(Constants.FORM_ACTION)) {
- if (log.isDebugEnabled())
- log.debug(" Allow access to username/password submission");
- return (true);
- }
- }
-
// Which user principal have we already authenticated?
Principal principal = request.getPrincipal();
boolean status = false;
Added: branches/7.0.x/java/org/apache/catalina/util/ConcurrentMessageDigest.java
===================================================================
--- branches/7.0.x/java/org/apache/catalina/util/ConcurrentMessageDigest.java (rev 0)
+++ branches/7.0.x/java/org/apache/catalina/util/ConcurrentMessageDigest.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -0,0 +1,104 @@
+/*
+ * 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.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * A thread safe wrapper around {@link MessageDigest} that does not make use
+ * of ThreadLocal and - broadly - only creates enough MessageDigest objects
+ * to satisfy the concurrency requirements.
+ */
+public class ConcurrentMessageDigest {
+
+ private static final String MD5 = "MD5";
+
+ private static final Map<String,Queue<MessageDigest>> queues =
+ new HashMap<String,Queue<MessageDigest>>();
+
+
+ private ConcurrentMessageDigest() {
+ // Hide default constructor for this utility class
+ }
+
+ static {
+ try {
+ // Init commonly used algorithms
+ init(MD5);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static byte[] digestMD5(byte[] input) {
+ return digest(MD5, input);
+ }
+
+ public static byte[] digest(String algorithm, byte[] input) {
+
+ Queue<MessageDigest> queue = queues.get(algorithm);
+ if (queue == null) {
+ throw new IllegalStateException("Must call init() first");
+ }
+
+ MessageDigest md = queue.poll();
+ if (md == null) {
+ try {
+ md = MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ // Ignore. Impossible if init() has been successfully called
+ // first.
+ throw new IllegalStateException("Must call init() first");
+ }
+ }
+
+ byte[] result = md.digest(input);
+
+ queue.add(md);
+
+ return result;
+ }
+
+
+ /**
+ * Ensures that {@link #digest(String, byte[])} and
+ * {@link #digestAsHex(String, byte[])} will support the specified
+ * algorithm. This method <b>must</b> be called and return successfully
+ * before using {@link #digest(String, byte[])} or
+ * {@link #digestAsHex(String, byte[])}.
+ *
+ * @param algorithm The message digest algorithm to be supported
+ *
+ * @throws NoSuchAlgorithmException If the algorithm is not supported by the
+ * JVM
+ */
+ public static void init(String algorithm) throws NoSuchAlgorithmException {
+ synchronized (queues) {
+ if (!queues.containsKey(algorithm)) {
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ Queue<MessageDigest> queue = new ConcurrentLinkedQueue<MessageDigest>();
+ queue.add(md);
+ queues.put(algorithm, queue);
+ }
+ }
+ }
+}
Modified: branches/7.0.x/java/org/apache/catalina/util/MD5Encoder.java
===================================================================
--- branches/7.0.x/java/org/apache/catalina/util/MD5Encoder.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/catalina/util/MD5Encoder.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -50,7 +50,7 @@
* @param binaryData Array containing the digest
* @return Encoded MD5, or null if encoding failed
*/
- public String encode( byte[] binaryData ) {
+ public static String encode( byte[] binaryData ) {
if (binaryData.length != 16)
return null;
Modified: branches/7.0.x/java/org/apache/coyote/ajp/AjpAprProcessor.java
===================================================================
--- branches/7.0.x/java/org/apache/coyote/ajp/AjpAprProcessor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/coyote/ajp/AjpAprProcessor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -980,7 +980,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/7.0.x/java/org/apache/coyote/ajp/AjpMessage.java
===================================================================
--- branches/7.0.x/java/org/apache/coyote/ajp/AjpMessage.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/coyote/ajp/AjpMessage.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -251,10 +251,8 @@
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
- if ((c <= 31) && (c != 9)) {
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
c = ' ';
- } else if (c == 127) {
- c = ' ';
}
appendByte(c);
}
Modified: branches/7.0.x/java/org/apache/coyote/ajp/AjpProcessor.java
===================================================================
--- branches/7.0.x/java/org/apache/coyote/ajp/AjpProcessor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/coyote/ajp/AjpProcessor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -994,7 +994,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/7.0.x/java/org/apache/coyote/http11/Http11AprProcessor.java
===================================================================
--- branches/7.0.x/java/org/apache/coyote/http11/Http11AprProcessor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/coyote/http11/Http11AprProcessor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -1559,7 +1559,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/7.0.x/java/org/apache/coyote/http11/Http11Processor.java
===================================================================
--- branches/7.0.x/java/org/apache/coyote/http11/Http11Processor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/coyote/http11/Http11Processor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -1447,7 +1447,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/7.0.x/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
===================================================================
--- branches/7.0.x/java/org/apache/coyote/http11/InternalAprOutputBuffer.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/coyote/http11/InternalAprOutputBuffer.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -684,10 +684,8 @@
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
- if ((c <= 31) && (c != 9)) {
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
c = ' ';
- } else if (c == 127) {
- c = ' ';
}
buf[pos++] = (byte) c;
}
Modified: branches/7.0.x/java/org/apache/coyote/http11/InternalOutputBuffer.java
===================================================================
--- branches/7.0.x/java/org/apache/coyote/http11/InternalOutputBuffer.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/coyote/http11/InternalOutputBuffer.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -690,10 +690,8 @@
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
- if ((c <= 31) && (c != 9)) {
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
c = ' ';
- } else if (c == 127) {
- c = ' ';
}
buf[pos++] = (byte) c;
}
Modified: branches/7.0.x/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
===================================================================
--- branches/7.0.x/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/7.0.x/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -266,6 +266,7 @@
int result = 0;
boolean eol = false;
+ boolean crfound = false;
boolean readDigit = false;
boolean trailer = false;
@@ -282,7 +283,10 @@
}
if (buf[pos] == Constants.CR) {
+ if (crfound) throw new IOException("Invalid CRLF, two CR characters encountered.");
+ crfound = true;
} else if (buf[pos] == Constants.LF) {
+ if (!crfound) throw new IOException("Invalid CRLF, no CR character encountered.");
eol = true;
} else if (buf[pos] == Constants.SEMI_COLON) {
trailer = true;
@@ -290,7 +294,7 @@
throw new IOException("Invalid chunk header");
} else if (!trailer) {
//don't read data after the trailer
- if (HexUtils.DEC[buf[pos]] != -1) {
+ if (HexUtils.DEC[buf[pos] & 0xff] != -1) {
readDigit = true;
result *= 16;
result += HexUtils.DEC[buf[pos]];
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/authenticator/DigestAuthenticator.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/authenticator/DigestAuthenticator.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -5,30 +5,27 @@
* 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.authenticator;
-
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
import java.security.SecureRandom;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
-import java.security.Principal;
-import java.util.StringTokenizer;
+import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Container;
@@ -38,19 +35,19 @@
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.util.ConcurrentMessageDigest;
import org.apache.catalina.util.MD5Encoder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
/**
* An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP DIGEST
* Authentication (see RFC 2069).
*
* @author Craig R. McClanahan
* @author Remy Maucherat
- * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
+ * @version $Id: DigestAuthenticator.java 1377853 2012-08-27 20:53:56Z markt $
*/
public class DigestAuthenticator
@@ -61,19 +58,6 @@
// -------------------------------------------------------------- Constants
/**
- * The MD5 helper object for this class.
- */
- protected static final MD5Encoder md5Encoder = new MD5Encoder();
-
-
- /**
- * Descriptive information about this implementation.
- */
- protected static final String info =
- "org.apache.catalina.authenticator.DigestAuthenticator/1.0";
-
-
- /**
* Tomcat's DIGEST implementation only supports auth quality of protection.
*/
protected static final String QOP = "auth";
@@ -83,12 +67,13 @@
public DigestAuthenticator() {
super();
+ setCache(false);
try {
- if (md5Helper == null)
+ if (md5Helper == null) {
md5Helper = MessageDigest.getInstance("MD5");
+ }
} catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- throw new IllegalStateException();
+ throw new IllegalStateException(e);
}
}
@@ -98,21 +83,22 @@
/**
* MD5 message digest provider.
+ * @deprecated Unused - will be removed in Tomcat 8.0.x onwards
*/
protected static MessageDigest md5Helper;
/**
- * List of client nonce values currently being tracked
+ * List of server nonce values currently being tracked
*/
- protected Map<String,NonceInfo> cnonces;
+ protected Map<String,NonceInfo> nonces;
/**
- * Maximum number of client nonces to keep in the cache. If not specified,
+ * Maximum number of server nonces to keep in the cache. If not specified,
* the default value of 1000 is used.
*/
- protected int cnonceCacheSize = 1000;
+ protected int nonceCacheSize = 1000;
/**
@@ -142,27 +128,16 @@
// ------------------------------------------------------------- Properties
- /**
- * Return descriptive information about this Valve implementation.
- */
- @Override
- public String getInfo() {
-
- return (info);
-
+ public int getNonceCacheSize() {
+ return nonceCacheSize;
}
- public int getCnonceCacheSize() {
- return cnonceCacheSize;
+ public void setNonceCacheSize(int nonceCacheSize) {
+ this.nonceCacheSize = nonceCacheSize;
}
- public void setCnonceCacheSize(int cnonceCacheSize) {
- this.cnonceCacheSize = cnonceCacheSize;
- }
-
-
public String getKey() {
return key;
}
@@ -213,8 +188,6 @@
*
* @param request Request we are processing
* @param response Response we are creating
- * @param config Login configuration describing how authentication
- * should be performed
*
* @exception IOException if an input/output error occurs
*/
@@ -222,19 +195,18 @@
public boolean authenticate(Request request,
Response response,
LoginConfig config)
- throws IOException {
+ throws IOException {
// Have we already authenticated someone?
Principal principal = request.getUserPrincipal();
//String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
if (principal != null) {
- if (log.isDebugEnabled())
- log.debug("Already authenticated '" + principal.getName() + "'");
// Associate the session with any existing SSO session in order
// to get coordinated session invalidation at logout
String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
- if (ssoId != null)
+ if (ssoId != null) {
associate(ssoId, request.getSessionInternal(true));
+ }
return (true);
}
@@ -266,19 +238,20 @@
// Validate any credentials already included with this request
String authorization = request.getHeader("authorization");
DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
- getKey(), cnonces, isValidateUri());
+ getKey(), nonces, isValidateUri());
if (authorization != null) {
- if (digestInfo.validate(request, authorization, config)) {
- principal = digestInfo.authenticate(context.getRealm());
+ if (digestInfo.parse(request, authorization)) {
+ if (digestInfo.validate(request, config)) {
+ principal = digestInfo.authenticate(context.getRealm());
+ }
+
+ if (principal != null && !digestInfo.isNonceStale()) {
+ register(request, response, principal,
+ HttpServletRequest.DIGEST_AUTH,
+ digestInfo.getUsername(), null);
+ return true;
+ }
}
-
- if (principal != null) {
- String username = parseUsername(authorization);
- register(request, response, principal,
- Constants.DIGEST_METHOD,
- username, null);
- return (true);
- }
}
// Send an "unauthorized" response and an appropriate challenge
@@ -288,11 +261,9 @@
String nonce = generateNonce(request);
setAuthenticateHeader(request, response, config, nonce,
- digestInfo.isNonceStale());
+ principal != null && digestInfo.isNonceStale());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
- // hres.flushBuffer();
- return (false);
-
+ return false;
}
@@ -300,42 +271,6 @@
/**
- * Parse the username from the specified authorization string. If none
- * can be identified, return <code>null</code>
- *
- * @param authorization Authorization string to be parsed
- */
- protected String parseUsername(String authorization) {
-
- // Validate the authorization credentials format
- if (authorization == null)
- return (null);
- if (!authorization.startsWith("Digest "))
- return (null);
- authorization = authorization.substring(7).trim();
-
- StringTokenizer commaTokenizer =
- new StringTokenizer(authorization, ",");
-
- while (commaTokenizer.hasMoreTokens()) {
- String currentToken = commaTokenizer.nextToken();
- int equalSign = currentToken.indexOf('=');
- if (equalSign < 0)
- return null;
- String currentTokenName =
- currentToken.substring(0, equalSign).trim();
- String currentTokenValue =
- currentToken.substring(equalSign + 1).trim();
- if ("username".equals(currentTokenName))
- return (removeQuotes(currentTokenValue));
- }
-
- return (null);
-
- }
-
-
- /**
* Removes the quotes on a string. RFC2617 states quotes are optional for
* all parameters except realm.
*/
@@ -348,7 +283,7 @@
} else if (quotedString.length() > 2) {
return quotedString.substring(1, quotedString.length() - 1);
} else {
- return new String();
+ return "";
}
}
@@ -370,16 +305,19 @@
long currentTime = System.currentTimeMillis();
-
+
String ipTimeKey =
request.getRemoteAddr() + ":" + currentTime + ":" + getKey();
- byte[] buffer;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(ipTimeKey.getBytes());
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(ipTimeKey.getBytes());
+ String nonce = currentTime + ":" + MD5Encoder.encode(buffer);
+
+ NonceInfo info = new NonceInfo(currentTime, 100);
+ synchronized (nonces) {
+ nonces.put(nonce, info);
}
- return currentTime + ":" + md5Encoder.encode(buffer);
+ return nonce;
}
@@ -406,12 +344,10 @@
*
* @param request HTTP Servlet request
* @param response HTTP Servlet response
- * @param config Login configuration describing how authentication
- * should be performed
* @param nonce nonce token
*/
- protected void setAuthenticateHeader(Request request,
- Response response,
+ protected void setAuthenticateHeader(HttpServletRequest request,
+ HttpServletResponse response,
LoginConfig config,
String nonce,
boolean isNonceStale) {
@@ -430,15 +366,15 @@
authenticateHeader = "Digest realm=\"" + realmName + "\", " +
"qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
getOpaque() + "\"";
- }
+ }
- response.setHeader("WWW-Authenticate", authenticateHeader);
+ response.setHeader(AUTH_HEADER_NAME, authenticateHeader);
}
// ------------------------------------------------------- Lifecycle Methods
-
+
@Override
public void start() throws LifecycleException {
super.start();
@@ -447,14 +383,14 @@
if (getKey() == null) {
setKey(generateSessionId());
}
-
+
// Generate the opaque string the same way
if (getOpaque() == null) {
setOpaque(generateSessionId());
}
-
- cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+ nonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+
private static final long serialVersionUID = 1L;
private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
@@ -465,7 +401,7 @@
Map.Entry<String,NonceInfo> eldest) {
// This is called from a sync so keep it simple
long currentTime = System.currentTimeMillis();
- if (size() > getCnonceCacheSize()) {
+ if (size() > getNonceCacheSize()) {
if (lastLog < currentTime &&
currentTime - eldest.getValue().getTimestamp() <
getNonceValidity()) {
@@ -480,13 +416,13 @@
}
};
}
-
+
private static class DigestInfo {
- private String opaque;
- private long nonceValidity;
- private String key;
- private Map<String,NonceInfo> cnonces;
+ private final String opaque;
+ private final long nonceValidity;
+ private final String key;
+ private final Map<String,NonceInfo> nonces;
private boolean validateUri = true;
private String userName = null;
@@ -498,21 +434,27 @@
private String cnonce = null;
private String realmName = null;
private String qop = null;
+ private String opaqueReceived = null;
private boolean nonceStale = false;
public DigestInfo(String opaque, long nonceValidity, String key,
- Map<String,NonceInfo> cnonces, boolean validateUri) {
+ Map<String,NonceInfo> nonces, boolean validateUri) {
this.opaque = opaque;
this.nonceValidity = nonceValidity;
this.key = key;
- this.cnonces = cnonces;
+ this.nonces = nonces;
this.validateUri = validateUri;
}
- public boolean validate(Request request, String authorization,
- LoginConfig config) {
+
+ public String getUsername() {
+ return userName;
+ }
+
+
+ public boolean parse(Request request, String authorization) {
// Validate the authorization credentials format
if (authorization == null) {
return false;
@@ -526,12 +468,12 @@
String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
method = request.getMethod();
- String opaque = null;
for (int i = 0; i < tokens.length; i++) {
String currentToken = tokens[i];
- if (currentToken.length() == 0)
+ if (currentToken.length() == 0) {
continue;
+ }
int equalSign = currentToken.indexOf('=');
if (equalSign < 0) {
@@ -541,26 +483,39 @@
currentToken.substring(0, equalSign).trim();
String currentTokenValue =
currentToken.substring(equalSign + 1).trim();
- if ("username".equals(currentTokenName))
+ if ("username".equals(currentTokenName)) {
userName = removeQuotes(currentTokenValue);
- if ("realm".equals(currentTokenName))
+ }
+ if ("realm".equals(currentTokenName)) {
realmName = removeQuotes(currentTokenValue, true);
- if ("nonce".equals(currentTokenName))
+ }
+ if ("nonce".equals(currentTokenName)) {
nonce = removeQuotes(currentTokenValue);
- if ("nc".equals(currentTokenName))
+ }
+ if ("nc".equals(currentTokenName)) {
nc = removeQuotes(currentTokenValue);
- if ("cnonce".equals(currentTokenName))
+ }
+ if ("cnonce".equals(currentTokenName)) {
cnonce = removeQuotes(currentTokenValue);
- if ("qop".equals(currentTokenName))
+ }
+ if ("qop".equals(currentTokenName)) {
qop = removeQuotes(currentTokenValue);
- if ("uri".equals(currentTokenName))
+ }
+ if ("uri".equals(currentTokenName)) {
uri = removeQuotes(currentTokenValue);
- if ("response".equals(currentTokenName))
+ }
+ if ("response".equals(currentTokenName)) {
response = removeQuotes(currentTokenValue);
- if ("opaque".equals(currentTokenName))
- opaque = removeQuotes(currentTokenValue);
+ }
+ if ("opaque".equals(currentTokenName)) {
+ opaqueReceived = removeQuotes(currentTokenValue);
+ }
}
+ return true;
+ }
+
+ public boolean validate(Request request, LoginConfig config) {
if ( (userName == null) || (realmName == null) || (nonce == null)
|| (uri == null) || (response == null) ) {
return false;
@@ -576,7 +531,23 @@
uriQuery = request.getRequestURI() + "?" + query;
}
if (!uri.equals(uriQuery)) {
- return false;
+ // Some clients (older Android) use an absolute URI for
+ // DIGEST but a relative URI in the request line.
+ // request. 2.3.5 < fixed Android version <= 4.0.3
+ String host = request.getHeader("host");
+ String scheme = request.getScheme();
+ if (host != null && !uriQuery.startsWith(scheme)) {
+ StringBuilder absolute = new StringBuilder();
+ absolute.append(scheme);
+ absolute.append("://");
+ absolute.append(host);
+ absolute.append(uriQuery);
+ if (!uri.equals(absolute.toString())) {
+ return false;
+ }
+ } else {
+ return false;
+ }
}
}
@@ -588,9 +559,9 @@
if (!lcRealm.equals(realmName)) {
return false;
}
-
+
// Validate the opaque string
- if (!this.opaque.equals(opaque)) {
+ if (!opaque.equals(opaqueReceived)) {
return false;
}
@@ -609,15 +580,15 @@
long currentTime = System.currentTimeMillis();
if ((currentTime - nonceTime) > nonceValidity) {
nonceStale = true;
- return false;
+ synchronized (nonces) {
+ nonces.remove(nonce);
+ }
}
String serverIpTimeKey =
request.getRemoteAddr() + ":" + nonceTime + ":" + key;
- byte[] buffer = null;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(serverIpTimeKey.getBytes());
- }
- String md5ServerIpTimeKey = md5Encoder.encode(buffer);
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(
+ serverIpTimeKey.getBytes());
+ String md5ServerIpTimeKey = MD5Encoder.encode(buffer);
if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
return false;
}
@@ -628,7 +599,7 @@
}
// Validate cnonce and nc
- // Check if presence of nc and nonce is consistent with presence of qop
+ // Check if presence of nc and Cnonce is consistent with presence of qop
if (qop == null) {
if (cnonce != null || nc != null) {
return false;
@@ -637,7 +608,9 @@
if (cnonce == null || nc == null) {
return false;
}
- if (nc.length() != 8) {
+ // RFC 2617 says nc must be 8 digits long. Older Android clients
+ // use 6. 2.3.5 < fixed Android version <= 4.0.3
+ if (nc.length() < 6 || nc.length() > 8) {
return false;
}
long count;
@@ -647,21 +620,18 @@
return false;
}
NonceInfo info;
- synchronized (cnonces) {
- info = cnonces.get(cnonce);
+ synchronized (nonces) {
+ info = nonces.get(nonce);
}
if (info == null) {
- info = new NonceInfo();
+ // Nonce is valid but not in cache. It must have dropped out
+ // of the cache - force a re-authentication
+ nonceStale = true;
} else {
- if (count <= info.getCount()) {
+ if (!info.nonceCountValid(count)) {
return false;
}
}
- info.setCount(count);
- info.setTimestamp(currentTime);
- synchronized (cnonces) {
- cnonces.put(cnonce, info);
- }
}
return true;
}
@@ -675,11 +645,9 @@
// MD5(Method + ":" + uri)
String a2 = method + ":" + uri;
- byte[] buffer;
- synchronized (md5Helper) {
- buffer = md5Helper.digest(a2.getBytes());
- }
- String md5a2 = md5Encoder.encode(buffer);
+ byte[] buffer = ConcurrentMessageDigest.digestMD5(
+ a2.getBytes());
+ String md5a2 = MD5Encoder.encode(buffer);
return realm.authenticate(userName, response, nonce, nc, cnonce,
qop, realmName, md5a2);
@@ -688,21 +656,33 @@
}
private static class NonceInfo {
- private volatile long count;
private volatile long timestamp;
-
- public void setCount(long l) {
- count = l;
+ private volatile boolean seen[];
+ private volatile int offset;
+ private volatile int count = 0;
+
+ public NonceInfo(long currentTime, int seenWindowSize) {
+ this.timestamp = currentTime;
+ seen = new boolean[seenWindowSize];
+ offset = seenWindowSize / 2;
}
-
- public long getCount() {
- return count;
+
+ public synchronized boolean nonceCountValid(long nonceCount) {
+ if ((count - offset) >= nonceCount ||
+ (nonceCount > count - offset + seen.length)) {
+ return false;
+ }
+ int checkIndex = (int) ((nonceCount + offset) % seen.length);
+ if (seen[checkIndex]) {
+ return false;
+ } else {
+ seen[checkIndex] = true;
+ seen[count % seen.length] = false;
+ count++;
+ return true;
+ }
}
-
- public void setTimestamp(long l) {
- timestamp = l;
- }
-
+
public long getTimestamp() {
return timestamp;
}
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/realm/RealmBase.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/realm/RealmBase.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/realm/RealmBase.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -728,31 +728,6 @@
if (constraints == null || constraints.length == 0)
return (true);
- // Specifically allow access to the form login and form error pages
- // and the "j_security_check" action
- LoginConfig config = context.getLoginConfig();
- if ((config != null) &&
- (Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
- String requestURI = request.getRequestPathMB().toString();
- String loginPage = config.getLoginPage();
- if (loginPage.equals(requestURI)) {
- if (log.isDebugEnabled())
- log.debug(" Allow access to login page " + loginPage);
- return (true);
- }
- String errorPage = config.getErrorPage();
- if (errorPage.equals(requestURI)) {
- if (log.isDebugEnabled())
- log.debug(" Allow access to error page " + errorPage);
- return (true);
- }
- if (requestURI.endsWith(Constants.FORM_ACTION)) {
- if (log.isDebugEnabled())
- log.debug(" Allow access to username/password submission");
- return (true);
- }
- }
-
// Which user principal have we already authenticated?
Principal principal = request.getPrincipal();
boolean status = false;
Added: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/util/ConcurrentMessageDigest.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/util/ConcurrentMessageDigest.java (rev 0)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/util/ConcurrentMessageDigest.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -0,0 +1,104 @@
+/*
+ * 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.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * A thread safe wrapper around {@link MessageDigest} that does not make use
+ * of ThreadLocal and - broadly - only creates enough MessageDigest objects
+ * to satisfy the concurrency requirements.
+ */
+public class ConcurrentMessageDigest {
+
+ private static final String MD5 = "MD5";
+
+ private static final Map<String,Queue<MessageDigest>> queues =
+ new HashMap<String,Queue<MessageDigest>>();
+
+
+ private ConcurrentMessageDigest() {
+ // Hide default constructor for this utility class
+ }
+
+ static {
+ try {
+ // Init commonly used algorithms
+ init(MD5);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static byte[] digestMD5(byte[] input) {
+ return digest(MD5, input);
+ }
+
+ public static byte[] digest(String algorithm, byte[] input) {
+
+ Queue<MessageDigest> queue = queues.get(algorithm);
+ if (queue == null) {
+ throw new IllegalStateException("Must call init() first");
+ }
+
+ MessageDigest md = queue.poll();
+ if (md == null) {
+ try {
+ md = MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ // Ignore. Impossible if init() has been successfully called
+ // first.
+ throw new IllegalStateException("Must call init() first");
+ }
+ }
+
+ byte[] result = md.digest(input);
+
+ queue.add(md);
+
+ return result;
+ }
+
+
+ /**
+ * Ensures that {@link #digest(String, byte[])} and
+ * {@link #digestAsHex(String, byte[])} will support the specified
+ * algorithm. This method <b>must</b> be called and return successfully
+ * before using {@link #digest(String, byte[])} or
+ * {@link #digestAsHex(String, byte[])}.
+ *
+ * @param algorithm The message digest algorithm to be supported
+ *
+ * @throws NoSuchAlgorithmException If the algorithm is not supported by the
+ * JVM
+ */
+ public static void init(String algorithm) throws NoSuchAlgorithmException {
+ synchronized (queues) {
+ if (!queues.containsKey(algorithm)) {
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ Queue<MessageDigest> queue = new ConcurrentLinkedQueue<MessageDigest>();
+ queue.add(md);
+ queues.put(algorithm, queue);
+ }
+ }
+ }
+}
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/util/MD5Encoder.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/util/MD5Encoder.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/util/MD5Encoder.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -50,7 +50,7 @@
* @param binaryData Array containing the digest
* @return Encoded MD5, or null if encoding failed
*/
- public String encode( byte[] binaryData ) {
+ public static String encode( byte[] binaryData ) {
if (binaryData.length != 16)
return null;
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpAprProcessor.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpAprProcessor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpAprProcessor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -884,7 +884,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpMessage.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpMessage.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpMessage.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -251,10 +251,8 @@
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
- if ((c <= 31) && (c != 9)) {
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
c = ' ';
- } else if (c == 127) {
- c = ' ';
}
appendByte(c);
}
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProcessor.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProcessor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProcessor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -890,7 +890,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11AprProcessor.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11AprProcessor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11AprProcessor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -1461,7 +1461,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11NioProcessor.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11NioProcessor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11NioProcessor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -1495,7 +1495,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11Processor.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11Processor.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/Http11Processor.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -1384,7 +1384,7 @@
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
- int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
+ int charValue = HexUtils.DEC[valueB[i + valueS] & 0xff];
if (charValue == -1) {
// Invalid character
error = true;
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalAprOutputBuffer.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalAprOutputBuffer.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalAprOutputBuffer.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -658,10 +658,8 @@
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
- if ((c <= 31) && (c != 9)) {
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
c = ' ';
- } else if (c == 127) {
- c = ' ';
}
buf[pos++] = (byte) c;
}
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalNioOutputBuffer.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalNioOutputBuffer.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalNioOutputBuffer.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -719,10 +719,8 @@
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
- if ((c <= 31) && (c != 9)) {
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
c = ' ';
- } else if (c == 127) {
- c = ' ';
}
buf[pos++] = (byte) c;
}
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalOutputBuffer.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalOutputBuffer.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/InternalOutputBuffer.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -704,10 +704,8 @@
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
- if ((c <= 31) && (c != 9)) {
+ if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
c = ' ';
- } else if (c == 127) {
- c = ' ';
}
buf[pos++] = (byte) c;
}
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/filters/ChunkedInputFilter.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/filters/ChunkedInputFilter.java 2012-10-10 17:48:22 UTC (rev 2106)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/http11/filters/ChunkedInputFilter.java 2012-10-10 17:50:36 UTC (rev 2107)
@@ -19,13 +19,12 @@
import java.io.IOException;
-import org.apache.tomcat.util.buf.ByteChunk;
-import org.apache.tomcat.util.buf.HexUtils;
-
import org.apache.coyote.InputBuffer;
import org.apache.coyote.Request;
import org.apache.coyote.http11.Constants;
import org.apache.coyote.http11.InputFilter;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.HexUtils;
/**
* Chunked input filter. Parses chunked data according to
@@ -123,7 +122,7 @@
if (endChunk)
return -1;
- if(needCRLFParse) {
+ if (needCRLFParse) {
needCRLFParse = false;
parseCRLF();
}
@@ -154,7 +153,7 @@
chunk.setBytes(buf, pos, remaining);
pos = pos + remaining;
remaining = 0;
- needCRLFParse = true;
+ needCRLFParse = true;
}
return result;
@@ -249,6 +248,7 @@
int result = 0;
boolean eol = false;
+ boolean crfound = false;
boolean readDigit = false;
boolean trailer = false;
@@ -260,13 +260,16 @@
}
if (buf[pos] == Constants.CR) {
+ if (crfound) throw new IOException("Invalid CRLF, two CR characters encountered.");
+ crfound = true;
} else if (buf[pos] == Constants.LF) {
+ if (!crfound) throw new IOException("Invalid CRLF, no CR character encountered.");
eol = true;
} else if (buf[pos] == Constants.SEMI_COLON) {
trailer = true;
} else if (!trailer) {
//don't read data after the trailer
- if (HexUtils.DEC[buf[pos]] != -1) {
+ if (HexUtils.DEC[buf[pos] & 0xff] != -1) {
readDigit = true;
result *= 16;
result += HexUtils.DEC[buf[pos]];
@@ -303,6 +306,7 @@
throws IOException {
boolean eol = false;
+ boolean crfound = false;
while (!eol) {
@@ -312,7 +316,10 @@
}
if (buf[pos] == Constants.CR) {
+ if (crfound) throw new IOException("Invalid CRLF, two CR characters encountered.");
+ crfound = true;
} else if (buf[pos] == Constants.LF) {
+ if (!crfound) throw new IOException("Invalid CRLF, no CR character encountered.");
eol = true;
} else {
throw new IOException("Invalid CRLF");
12 years, 2 months
JBossWeb SVN: r2106 - branches/7.2.x/src/main/java/org/apache/catalina/connector.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2012-10-10 13:48:22 -0400 (Wed, 10 Oct 2012)
New Revision: 2106
Modified:
branches/7.2.x/src/main/java/org/apache/catalina/connector/Response.java
Log:
JBWEB-249: Avoid doing any encoding if not using URL tracking.
Modified: branches/7.2.x/src/main/java/org/apache/catalina/connector/Response.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/catalina/connector/Response.java 2012-10-08 13:48:50 UTC (rev 2105)
+++ branches/7.2.x/src/main/java/org/apache/catalina/connector/Response.java 2012-10-10 17:48:22 UTC (rev 2106)
@@ -39,6 +39,7 @@
import java.util.Vector;
import javax.servlet.ServletOutputStream;
+import javax.servlet.SessionTrackingMode;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
@@ -1576,6 +1577,12 @@
// Are we in a valid session that is not using cookies?
final Request hreq = request;
+
+ // Is URL encoding permitted
+ if (!hreq.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) {
+ return false;
+ }
+
final Session session = hreq.getSessionInternal(false);
if (session == null)
return (false);
12 years, 2 months