ASF Bugzilla – Attachment 29518 Details for
Bug 54060
DigestAuthenticator doesn't parse Authorization header correctly
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Proposed patch fixes problem
Fix_issue_54060.patch (text/plain), 29.71 KB, created by
Mark Thornton
on 2012-10-29 15:21:54 UTC
(
hide
)
Description:
Proposed patch fixes problem
Filename:
MIME Type:
Creator:
Mark Thornton
Created:
2012-10-29 15:21:54 UTC
Size:
29.71 KB
patch
obsolete
>Index: java/org/apache/catalina/authenticator/DigestAuthenticator.java >IDEA additional info: >Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP ><+>ISO-8859-1 >Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP ><+>/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements. See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * \n * http://www.apache.org/licenses/LICENSE-2.0\n * \n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\npackage org.apache.catalina.authenticator;\n\n\nimport java.io.IOException;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.Principal;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.StringTokenizer;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\nimport org.apache.catalina.LifecycleException;\nimport org.apache.catalina.Realm;\nimport org.apache.catalina.connector.Request;\nimport org.apache.catalina.deploy.LoginConfig;\nimport org.apache.catalina.util.ConcurrentMessageDigest;\nimport org.apache.catalina.util.MD5Encoder;\nimport org.apache.juli.logging.Log;\nimport org.apache.juli.logging.LogFactory;\nimport org.apache.tomcat.util.buf.B2CConverter;\n\n\n\n/**\n * An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP DIGEST\n * Authentication (see RFC 2069).\n *\n * @author Craig R. McClanahan\n * @author Remy Maucherat\n * @version $Id$\n */\n\npublic class DigestAuthenticator extends AuthenticatorBase {\n\n private static final Log log = LogFactory.getLog(DigestAuthenticator.class);\n\n\n // -------------------------------------------------------------- Constants\n\n /**\n * The MD5 helper object for this class.\n *\n * @deprecated Unused - will be removed in Tomcat 8.0.x\n */\n @Deprecated\n protected static final MD5Encoder md5Encoder = new MD5Encoder();\n\n\n /**\n * Descriptive information about this implementation.\n */\n protected static final String info =\n \"org.apache.catalina.authenticator.DigestAuthenticator/1.0\";\n\n\n /**\n * Tomcat's DIGEST implementation only supports auth quality of protection.\n */\n protected static final String QOP = \"auth\";\n\n // ----------------------------------------------------------- Constructors\n\n\n public DigestAuthenticator() {\n super();\n setCache(false);\n try {\n if (md5Helper == null)\n md5Helper = MessageDigest.getInstance(\"MD5\");\n } catch (NoSuchAlgorithmException e) {\n throw new IllegalStateException(e);\n }\n }\n\n\n // ----------------------------------------------------- Instance Variables\n\n\n /**\n * MD5 message digest provider.\n * @deprecated Unused - will be removed in Tomcat 8.0.x onwards\n */\n @Deprecated\n protected static volatile MessageDigest md5Helper;\n\n\n /**\n * List of server nonce values currently being tracked\n */\n protected Map<String,NonceInfo> nonces;\n\n\n /**\n * Maximum number of server nonces to keep in the cache. If not specified,\n * the default value of 1000 is used.\n */\n protected int nonceCacheSize = 1000;\n\n\n /**\n * Private key.\n */\n protected String key = null;\n\n\n /**\n * How long server nonces are valid for in milliseconds. Defaults to 5\n * minutes.\n */\n protected long nonceValidity = 5 * 60 * 1000;\n\n\n /**\n * Opaque string.\n */\n protected String opaque;\n\n\n /**\n * Should the URI be validated as required by RFC2617? Can be disabled in\n * reverse proxies where the proxy has modified the URI.\n */\n protected boolean validateUri = true;\n\n // ------------------------------------------------------------- Properties\n\n /**\n * Return descriptive information about this Valve implementation.\n */\n @Override\n public String getInfo() {\n\n return (info);\n\n }\n\n\n public int getNonceCacheSize() {\n return nonceCacheSize;\n }\n\n\n public void setNonceCacheSize(int nonceCacheSize) {\n this.nonceCacheSize = nonceCacheSize;\n }\n\n\n public String getKey() {\n return key;\n }\n\n\n public void setKey(String key) {\n this.key = key;\n }\n\n\n public long getNonceValidity() {\n return nonceValidity;\n }\n\n\n public void setNonceValidity(long nonceValidity) {\n this.nonceValidity = nonceValidity;\n }\n\n\n public String getOpaque() {\n return opaque;\n }\n\n\n public void setOpaque(String opaque) {\n this.opaque = opaque;\n }\n\n\n public boolean isValidateUri() {\n return validateUri;\n }\n\n\n public void setValidateUri(boolean validateUri) {\n this.validateUri = validateUri;\n }\n\n\n // --------------------------------------------------------- Public Methods\n\n /**\n * Authenticate the user making this request, based on the specified\n * login configuration. Return <code>true</code> if any specified\n * constraint has been satisfied, or <code>false</code> if we have\n * created a response challenge already.\n *\n * @param request Request we are processing\n * @param response Response we are creating\n * @param config Login configuration describing how authentication\n * should be performed\n *\n * @exception IOException if an input/output error occurs\n */\n @Override\n public boolean authenticate(Request request,\n HttpServletResponse response,\n LoginConfig config)\n throws IOException {\n\n // Have we already authenticated someone?\n Principal principal = request.getUserPrincipal();\n //String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);\n if (principal != null) {\n if (log.isDebugEnabled())\n log.debug(\"Already authenticated '\" + principal.getName() + \"'\");\n // Associate the session with any existing SSO session in order\n // to get coordinated session invalidation at logout\n String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);\n if (ssoId != null)\n associate(ssoId, request.getSessionInternal(true));\n return (true);\n }\n\n // NOTE: We don't try to reauthenticate using any existing SSO session,\n // because that will only work if the original authentication was\n // BASIC or FORM, which are less secure than the DIGEST auth-type\n // specified for this webapp\n //\n // Uncomment below to allow previous FORM or BASIC authentications\n // to authenticate users for this webapp\n // TODO make this a configurable attribute (in SingleSignOn??)\n /*\n // Is there an SSO session against which we can try to reauthenticate?\n if (ssoId != null) {\n if (log.isDebugEnabled())\n log.debug(\"SSO Id \" + ssoId + \" set; attempting \" +\n \"reauthentication\");\n // Try to reauthenticate using data cached by SSO. If this fails,\n // either the original SSO logon was of DIGEST or SSL (which\n // we can't reauthenticate ourselves because there is no\n // cached username and password), or the realm denied\n // the user's reauthentication for some reason.\n // In either case we have to prompt the user for a logon\n if (reauthenticateFromSSO(ssoId, request))\n return true;\n }\n */\n\n // Validate any credentials already included with this request\n String authorization = request.getHeader(\"authorization\");\n DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),\n getKey(), nonces, isValidateUri());\n if (authorization != null) {\n if (digestInfo.parse(request, authorization)) {\n if (digestInfo.validate(request, config)) {\n principal = digestInfo.authenticate(context.getRealm());\n }\n \n if (principal != null && !digestInfo.isNonceStale()) {\n register(request, response, principal,\n HttpServletRequest.DIGEST_AUTH,\n digestInfo.getUsername(), null);\n return true;\n }\n }\n }\n\n // Send an \"unauthorized\" response and an appropriate challenge\n\n // Next, generate a nonce token (that is a token which is supposed\n // to be unique).\n String nonce = generateNonce(request);\n\n setAuthenticateHeader(request, response, config, nonce,\n principal != null && digestInfo.isNonceStale());\n response.sendError(HttpServletResponse.SC_UNAUTHORIZED);\n return false;\n }\n\n\n @Override\n protected String getAuthMethod() {\n return HttpServletRequest.DIGEST_AUTH;\n }\n\n\n // ------------------------------------------------------ Protected Methods\n\n\n /**\n * Parse the username from the specified authorization string. If none\n * can be identified, return <code>null</code>\n *\n * @param authorization Authorization string to be parsed\n *\n * @deprecated Unused. Will be removed in Tomcat 8.0.x\n */\n @Deprecated\n protected String parseUsername(String authorization) {\n\n // Validate the authorization credentials format\n if (authorization == null)\n return (null);\n if (!authorization.startsWith(\"Digest \"))\n return (null);\n authorization = authorization.substring(7).trim();\n\n StringTokenizer commaTokenizer =\n new StringTokenizer(authorization, \",\");\n\n while (commaTokenizer.hasMoreTokens()) {\n String currentToken = commaTokenizer.nextToken();\n int equalSign = currentToken.indexOf('=');\n if (equalSign < 0)\n return null;\n String currentTokenName =\n currentToken.substring(0, equalSign).trim();\n String currentTokenValue =\n currentToken.substring(equalSign + 1).trim();\n if (\"username\".equals(currentTokenName))\n return (removeQuotes(currentTokenValue));\n }\n\n return (null);\n\n }\n\n\n /**\n * Removes the quotes on a string. RFC2617 states quotes are optional for\n * all parameters except realm.\n */\n protected static String removeQuotes(String quotedString,\n boolean quotesRequired) {\n //support both quoted and non-quoted\n if (quotedString.length() > 0 && quotedString.charAt(0) != '\"' &&\n !quotesRequired) {\n return quotedString;\n } else if (quotedString.length() > 2) {\n return quotedString.substring(1, quotedString.length() - 1);\n } else {\n return \"\";\n }\n }\n\n /**\n * Removes the quotes on a string.\n */\n protected static String removeQuotes(String quotedString) {\n return removeQuotes(quotedString, false);\n }\n\n /**\n * Generate a unique token. The token is generated according to the\n * following pattern. NOnceToken = Base64 ( MD5 ( client-IP \":\"\n * time-stamp \":\" private-key ) ).\n *\n * @param request HTTP Servlet request\n */\n protected String generateNonce(Request request) {\n\n long currentTime = System.currentTimeMillis();\n\n \n String ipTimeKey =\n request.getRemoteAddr() + \":\" + currentTime + \":\" + getKey();\n\n byte[] buffer = ConcurrentMessageDigest.digestMD5(\n ipTimeKey.getBytes(B2CConverter.ISO_8859_1));\n String nonce = currentTime + \":\" + MD5Encoder.encode(buffer);\n\n NonceInfo info = new NonceInfo(currentTime, 100);\n synchronized (nonces) {\n nonces.put(nonce, info);\n }\n\n return nonce;\n }\n\n\n /**\n * Generates the WWW-Authenticate header.\n * <p>\n * The header MUST follow this template :\n * <pre>\n * WWW-Authenticate = \"WWW-Authenticate\" \":\" \"Digest\"\n * digest-challenge\n *\n * digest-challenge = 1#( realm | [ domain ] | nonce |\n * [ digest-opaque ] |[ stale ] | [ algorithm ] )\n *\n * realm = \"realm\" \"=\" realm-value\n * realm-value = quoted-string\n * domain = \"domain\" \"=\" <\"> 1#URI <\">\n * nonce = \"nonce\" \"=\" nonce-value\n * nonce-value = quoted-string\n * opaque = \"opaque\" \"=\" quoted-string\n * stale = \"stale\" \"=\" ( \"true\" | \"false\" )\n * algorithm = \"algorithm\" \"=\" ( \"MD5\" | token )\n * </pre>\n *\n * @param request HTTP Servlet request\n * @param response HTTP Servlet response\n * @param config Login configuration describing how authentication\n * should be performed\n * @param nonce nonce token\n */\n protected void setAuthenticateHeader(HttpServletRequest request,\n HttpServletResponse response,\n LoginConfig config,\n String nonce,\n boolean isNonceStale) {\n\n // Get the realm name\n String realmName = config.getRealmName();\n if (realmName == null)\n realmName = REALM_NAME;\n\n String authenticateHeader;\n if (isNonceStale) {\n authenticateHeader = \"Digest realm=\\\"\" + realmName + \"\\\", \" +\n \"qop=\\\"\" + QOP + \"\\\", nonce=\\\"\" + nonce + \"\\\", \" + \"opaque=\\\"\" +\n getOpaque() + \"\\\", stale=true\";\n } else {\n authenticateHeader = \"Digest realm=\\\"\" + realmName + \"\\\", \" +\n \"qop=\\\"\" + QOP + \"\\\", nonce=\\\"\" + nonce + \"\\\", \" + \"opaque=\\\"\" +\n getOpaque() + \"\\\"\";\n }\n\n response.setHeader(AUTH_HEADER_NAME, authenticateHeader);\n\n }\n\n\n // ------------------------------------------------------- Lifecycle Methods\n \n @Override\n protected synchronized void startInternal() throws LifecycleException {\n super.startInternal();\n \n // Generate a random secret key\n if (getKey() == null) {\n setKey(sessionIdGenerator.generateSessionId());\n }\n \n // Generate the opaque string the same way\n if (getOpaque() == null) {\n setOpaque(sessionIdGenerator.generateSessionId());\n }\n \n nonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {\n\n private static final long serialVersionUID = 1L;\n private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;\n\n private long lastLog = 0;\n\n @Override\n protected boolean removeEldestEntry(\n Map.Entry<String,NonceInfo> eldest) {\n // This is called from a sync so keep it simple\n long currentTime = System.currentTimeMillis();\n if (size() > getNonceCacheSize()) {\n if (lastLog < currentTime &&\n currentTime - eldest.getValue().getTimestamp() <\n getNonceValidity()) {\n // Replay attack is possible\n log.warn(sm.getString(\n \"digestAuthenticator.cacheRemove\"));\n lastLog = currentTime + LOG_SUPPRESS_TIME;\n }\n return true;\n }\n return false;\n }\n };\n }\n \n private static class DigestInfo {\n\n private final String opaque;\n private final long nonceValidity;\n private final String key;\n private final Map<String,NonceInfo> nonces;\n private boolean validateUri = true;\n\n private String userName = null;\n private String method = null;\n private String uri = null;\n private String response = null;\n private String nonce = null;\n private String nc = null;\n private String cnonce = null;\n private String realmName = null;\n private String qop = null;\n private String opaqueReceived = null;\n\n private boolean nonceStale = false;\n\n\n public DigestInfo(String opaque, long nonceValidity, String key,\n Map<String,NonceInfo> nonces, boolean validateUri) {\n this.opaque = opaque;\n this.nonceValidity = nonceValidity;\n this.key = key;\n this.nonces = nonces;\n this.validateUri = validateUri;\n }\n\n\n public String getUsername() {\n return userName;\n }\n\n\n public boolean parse(Request request, String authorization) {\n // Validate the authorization credentials format\n if (authorization == null) {\n return false;\n }\n if (!authorization.startsWith(\"Digest \")) {\n return false;\n }\n authorization = authorization.substring(7).trim();\n\n // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132\n String[] tokens = authorization.split(\",(?=(?:[^\\\"]*\\\"[^\\\"]*\\\")+$)\");\n\n method = request.getMethod();\n\n for (int i = 0; i < tokens.length; i++) {\n String currentToken = tokens[i];\n if (currentToken.length() == 0)\n continue;\n\n int equalSign = currentToken.indexOf('=');\n if (equalSign < 0) {\n return false;\n }\n String currentTokenName =\n currentToken.substring(0, equalSign).trim();\n String currentTokenValue =\n currentToken.substring(equalSign + 1).trim();\n if (\"username\".equals(currentTokenName))\n userName = removeQuotes(currentTokenValue);\n if (\"realm\".equals(currentTokenName))\n realmName = removeQuotes(currentTokenValue, true);\n if (\"nonce\".equals(currentTokenName))\n nonce = removeQuotes(currentTokenValue);\n if (\"nc\".equals(currentTokenName))\n nc = removeQuotes(currentTokenValue);\n if (\"cnonce\".equals(currentTokenName))\n cnonce = removeQuotes(currentTokenValue);\n if (\"qop\".equals(currentTokenName))\n qop = removeQuotes(currentTokenValue);\n if (\"uri\".equals(currentTokenName))\n uri = removeQuotes(currentTokenValue);\n if (\"response\".equals(currentTokenName))\n response = removeQuotes(currentTokenValue);\n if (\"opaque\".equals(currentTokenName))\n opaqueReceived = removeQuotes(currentTokenValue);\n }\n\n return true;\n }\n\n public boolean validate(Request request, LoginConfig config) {\n if ( (userName == null) || (realmName == null) || (nonce == null)\n || (uri == null) || (response == null) ) {\n return false;\n }\n\n // Validate the URI - should match the request line sent by client\n if (validateUri) {\n String uriQuery;\n String query = request.getQueryString();\n if (query == null) {\n uriQuery = request.getRequestURI();\n } else {\n uriQuery = request.getRequestURI() + \"?\" + query;\n }\n if (!uri.equals(uriQuery)) {\n // Some clients (older Android) use an absolute URI for\n // DIGEST but a relative URI in the request line.\n // request. 2.3.5 < fixed Android version <= 4.0.3\n String host = request.getHeader(\"host\");\n String scheme = request.getScheme();\n if (host != null && !uriQuery.startsWith(scheme)) {\n StringBuilder absolute = new StringBuilder();\n absolute.append(scheme);\n absolute.append(\"://\");\n absolute.append(host);\n absolute.append(uriQuery);\n if (!uri.equals(absolute.toString())) {\n return false;\n }\n } else {\n return false;\n }\n }\n }\n\n // Validate the Realm name\n String lcRealm = config.getRealmName();\n if (lcRealm == null) {\n lcRealm = REALM_NAME;\n }\n if (!lcRealm.equals(realmName)) {\n return false;\n }\n \n // Validate the opaque string\n if (!opaque.equals(opaqueReceived)) {\n return false;\n }\n\n // Validate nonce\n int i = nonce.indexOf(\":\");\n if (i < 0 || (i + 1) == nonce.length()) {\n return false;\n }\n long nonceTime;\n try {\n nonceTime = Long.parseLong(nonce.substring(0, i));\n } catch (NumberFormatException nfe) {\n return false;\n }\n String md5clientIpTimeKey = nonce.substring(i + 1);\n long currentTime = System.currentTimeMillis();\n if ((currentTime - nonceTime) > nonceValidity) {\n nonceStale = true;\n synchronized (nonces) {\n nonces.remove(nonce);\n }\n }\n String serverIpTimeKey =\n request.getRemoteAddr() + \":\" + nonceTime + \":\" + key;\n byte[] buffer = ConcurrentMessageDigest.digestMD5(\n serverIpTimeKey.getBytes(B2CConverter.ISO_8859_1));\n String md5ServerIpTimeKey = MD5Encoder.encode(buffer);\n if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {\n return false;\n }\n\n // Validate qop\n if (qop != null && !QOP.equals(qop)) {\n return false;\n }\n\n // Validate cnonce and nc\n // Check if presence of nc and Cnonce is consistent with presence of qop\n if (qop == null) {\n if (cnonce != null || nc != null) {\n return false;\n }\n } else {\n if (cnonce == null || nc == null) {\n return false;\n }\n // RFC 2617 says nc must be 8 digits long. Older Android clients\n // use 6. 2.3.5 < fixed Android version <= 4.0.3\n if (nc.length() < 6 || nc.length() > 8) {\n return false;\n }\n long count;\n try {\n count = Long.parseLong(nc, 16);\n } catch (NumberFormatException nfe) {\n return false;\n }\n NonceInfo info;\n synchronized (nonces) {\n info = nonces.get(nonce);\n }\n if (info == null) {\n // Nonce is valid but not in cache. It must have dropped out\n // of the cache - force a re-authentication\n nonceStale = true;\n } else {\n if (!info.nonceCountValid(count)) {\n return false;\n }\n }\n }\n return true;\n }\n\n public boolean isNonceStale() {\n return nonceStale;\n }\n\n public Principal authenticate(Realm realm) {\n // Second MD5 digest used to calculate the digest :\n // MD5(Method + \":\" + uri)\n String a2 = method + \":\" + uri;\n\n byte[] buffer = ConcurrentMessageDigest.digestMD5(\n a2.getBytes(B2CConverter.ISO_8859_1));\n String md5a2 = MD5Encoder.encode(buffer);\n\n return realm.authenticate(userName, response, nonce, nc, cnonce,\n qop, realmName, md5a2);\n }\n\n }\n\n private static class NonceInfo {\n private volatile long timestamp;\n private volatile boolean seen[];\n private volatile int offset;\n private volatile int count = 0;\n\n public NonceInfo(long currentTime, int seenWindowSize) {\n this.timestamp = currentTime;\n seen = new boolean[seenWindowSize];\n offset = seenWindowSize / 2;\n }\n \n public synchronized boolean nonceCountValid(long nonceCount) {\n if ((count - offset) >= nonceCount ||\n (nonceCount > count - offset + seen.length)) {\n return false;\n }\n int checkIndex = (int) ((nonceCount + offset) % seen.length);\n if (seen[checkIndex]) {\n return false;\n } else {\n seen[checkIndex] = true;\n seen[count % seen.length] = false;\n count++;\n return true;\n }\n }\n \n public long getTimestamp() {\n return timestamp;\n }\n }\n}\n >=================================================================== >--- java/org/apache/catalina/authenticator/DigestAuthenticator.java (date 1346535387000) >+++ java/org/apache/catalina/authenticator/DigestAuthenticator.java (revision ) >@@ -26,6 +26,8 @@ > import java.util.LinkedHashMap; > import java.util.Map; > import java.util.StringTokenizer; >+import java.util.regex.Matcher; >+import java.util.regex.Pattern; > > import javax.servlet.http.HttpServletRequest; > import javax.servlet.http.HttpServletResponse; >@@ -55,6 +57,10 @@ > > private static final Log log = LogFactory.getLog(DigestAuthenticator.class); > >+ /** Pattern for parsing tokens in Authorization header. >+ * >+ */ >+ private static final Pattern TOKEN_PATTERN = Pattern.compile("\\s*([a-zA-Z]+)\\s*=\\s*((?:[^\", ]+)|(?:\"(?:[^\"\\\\]|(?:\\\\.))*\"))\\s*,?"); > > // -------------------------------------------------------------- Constants > >@@ -357,7 +363,21 @@ > !quotesRequired) { > return quotedString; > } else if (quotedString.length() > 2) { >- return quotedString.substring(1, quotedString.length() - 1); >+ String string = quotedString.substring(1, quotedString.length() - 1); >+ if (string.indexOf('\\') != -1) { >+ // The string contains quoted characters >+ StringBuilder buffer = new StringBuilder(string); >+ int pos = 0; >+ while (pos < buffer.length()) { >+ int index = buffer.indexOf("\\", pos); >+ if (index == -1) >+ break; >+ buffer.deleteCharAt(index); >+ pos = index+1; >+ } >+ string = buffer.toString(); >+ } >+ return string; > } else { > return ""; > } >@@ -543,24 +563,18 @@ > } > authorization = authorization.substring(7).trim(); > >- // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132 >- String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)"); >- > method = request.getMethod(); > >- for (int i = 0; i < tokens.length; i++) { >- String currentToken = tokens[i]; >- if (currentToken.length() == 0) >- continue; >- >- int equalSign = currentToken.indexOf('='); >- if (equalSign < 0) { >+ Matcher matcher = TOKEN_PATTERN.matcher(authorization); >+ while (matcher.regionStart() < authorization.length()) { >+ if (!matcher.lookingAt()) { >+ // have unmatched text >+ log.warn("Unmatched text in Authorization: "+authorization.substring(matcher.regionStart())); > return false; > } >- String currentTokenName = >- currentToken.substring(0, equalSign).trim(); >- String currentTokenValue = >- currentToken.substring(equalSign + 1).trim(); >+ String currentTokenName = matcher.group(1); >+ String currentTokenValue = matcher.group(2); >+ matcher.region(matcher.end(), authorization.length()); > if ("username".equals(currentTokenName)) > userName = removeQuotes(currentTokenValue); > if ("realm".equals(currentTokenName))
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 54060
:
29515
| 29518 |
29519