This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new b288a7a92d Fix WebSocket + proxy + DIGEST auth on proxy
b288a7a92d is described below
commit b288a7a92d1dfe85c90bb1f485bcbbc66f4de1af
Author: Mark Thomas <[email protected]>
AuthorDate: Mon Apr 27 22:59:51 2026 +0100
Fix WebSocket + proxy + DIGEST auth on proxy
---
.../org/apache/tomcat/websocket/Authenticator.java | 30 ++++++-
.../tomcat/websocket/BasicAuthenticator.java | 4 +-
.../tomcat/websocket/DigestAuthenticator.java | 18 +++--
.../tomcat/websocket/WsWebSocketContainer.java | 93 +++++++++++++---------
.../websocket/TesterWebSocketClientProxy.java | 61 ++++++++++----
webapps/docs/changelog.xml | 4 +
6 files changed, 144 insertions(+), 66 deletions(-)
diff --git a/java/org/apache/tomcat/websocket/Authenticator.java
b/java/org/apache/tomcat/websocket/Authenticator.java
index cdf20cfd06..9745c0286e 100644
--- a/java/org/apache/tomcat/websocket/Authenticator.java
+++ b/java/org/apache/tomcat/websocket/Authenticator.java
@@ -21,6 +21,7 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.tomcat.util.http.Method;
import org.apache.tomcat.util.res.StringManager;
/**
@@ -46,9 +47,34 @@ public abstract class Authenticator {
* @return The generated authorization header value
*
* @throws AuthenticationException When an error occurs
+ *
+ * @deprecated Unused. Will be remove in Tomcat 12. Use
+ * {@link #getAuthorization(String, String, String,
String, String, String)}
+ */
+ @Deprecated
+ public String getAuthorization(String requestUri, String
authenticateHeader, String userName, String userPassword,
+ String userRealm) throws AuthenticationException {
+ return getAuthorization(Method.GET, requestUri, authenticateHeader,
userName, userPassword, userRealm);
+ }
+
+
+ /**
+ * Generate the authorization header value that will be sent to the server.
+ *
+ * @param method The request method
+ * @param requestUri The request URI
+ * @param authenticateHeader The server authentication header received
+ * @param userName The username
+ * @param userPassword The user password
+ * @param userRealm The realm for which the provided username and
password are valid. {@code null} to
+ * indicate all realms.
+ *
+ * @return The generated authorization header value
+ *
+ * @throws AuthenticationException When an error occurs
*/
- public abstract String getAuthorization(String requestUri, String
authenticateHeader, String userName,
- String userPassword, String userRealm) throws
AuthenticationException;
+ public abstract String getAuthorization(String method, String requestUri,
String authenticateHeader,
+ String userName, String userPassword, String userRealm) throws
AuthenticationException;
/**
diff --git a/java/org/apache/tomcat/websocket/BasicAuthenticator.java
b/java/org/apache/tomcat/websocket/BasicAuthenticator.java
index b669d2eb94..cf11d72031 100644
--- a/java/org/apache/tomcat/websocket/BasicAuthenticator.java
+++ b/java/org/apache/tomcat/websocket/BasicAuthenticator.java
@@ -30,8 +30,8 @@ public class BasicAuthenticator extends Authenticator {
public static final String charsetparam = "charset";
@Override
- public String getAuthorization(String requestUri, String
authenticateHeader, String userName, String userPassword,
- String userRealm) throws AuthenticationException {
+ public String getAuthorization(String method, String requestUri, String
authenticateHeader, String userName,
+ String userPassword, String userRealm) throws
AuthenticationException {
validateUsername(userName);
validatePassword(userPassword);
diff --git a/java/org/apache/tomcat/websocket/DigestAuthenticator.java
b/java/org/apache/tomcat/websocket/DigestAuthenticator.java
index 9f018a7a5e..e68b33fab6 100644
--- a/java/org/apache/tomcat/websocket/DigestAuthenticator.java
+++ b/java/org/apache/tomcat/websocket/DigestAuthenticator.java
@@ -40,8 +40,8 @@ public class DigestAuthenticator extends Authenticator {
private long cNonce;
@Override
- public String getAuthorization(String requestUri, String
authenticateHeader, String userName, String userPassword,
- String userRealm) throws AuthenticationException {
+ public String getAuthorization(String method, String requestUri, String
authenticateHeader, String userName,
+ String userPassword, String userRealm) throws
AuthenticationException {
validateUsername(userName);
validatePassword(userPassword);
@@ -79,8 +79,8 @@ public class DigestAuthenticator extends Authenticator {
try {
challenge.append("response=\"");
- challenge.append(
- calculateRequestDigest(requestUri, userName, userPassword,
realm, nonce, messageQop, algorithm));
+ challenge.append(calculateRequestDigest(method, requestUri,
userName, userPassword, realm, nonce,
+ messageQop, algorithm));
challenge.append("\",");
}
@@ -89,7 +89,9 @@ public class DigestAuthenticator extends Authenticator {
}
challenge.append("algorithm=").append(algorithm).append(",");
- challenge.append("opaque=\"").append(opaque).append("\",");
+ if (opaque != null) {
+ challenge.append("opaque=\"").append(opaque).append("\",");
+ }
if (!messageQop.isEmpty()) {
challenge.append("qop=\"").append(messageQop).append("\"");
@@ -101,8 +103,8 @@ public class DigestAuthenticator extends Authenticator {
}
- private String calculateRequestDigest(String requestUri, String userName,
String password, String realm,
- String nonce, String qop, String algorithm) throws
NoSuchAlgorithmException {
+ private String calculateRequestDigest(String method, String requestUri,
String userName, String password,
+ String realm, String nonce, String qop, String algorithm) throws
NoSuchAlgorithmException {
boolean session = false;
if (algorithm.endsWith("-sess")) {
@@ -123,7 +125,7 @@ public class DigestAuthenticator extends Authenticator {
* If the "qop" value is "auth-int", then A2 is: A2 = Method ":"
digest-uri-value ":" H(entity-body) since we do
* not have an entity-body, A2 = Method ":" digest-uri-value for auth
and auth_int
*/
- String A2 = "GET:" + requestUri;
+ String A2 = method + ":" + requestUri;
preDigest.append(encode(algorithm, A1));
preDigest.append(':');
diff --git a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
index 3827be38cc..810ffdfe6d 100644
--- a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
+++ b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
@@ -69,6 +69,7 @@ import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.InstanceManagerBindings;
import org.apache.tomcat.util.buf.StringUtils;
import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap;
+import org.apache.tomcat.util.http.Method;
import org.apache.tomcat.util.res.StringManager;
public class WsWebSocketContainer implements WebSocketContainer,
BackgroundProcess {
@@ -175,40 +176,41 @@ public class WsWebSocketContainer implements
WebSocketContainer, BackgroundProce
private Session connectToServerRecursive(ClientEndpointHolder
clientEndpointHolder,
- ClientEndpointConfig clientEndpointConfiguration, URI path,
Set<URI> redirectSet)
+ ClientEndpointConfig clientEndpointConfiguration, URI
serverEndpointUri, Set<URI> redirectSet)
throws DeploymentException {
if (log.isTraceEnabled()) {
- log.trace(sm.getString("wsWebSocketContainer.connect.entry",
clientEndpointHolder.getClassName(), path));
+ log.trace(sm.getString("wsWebSocketContainer.connect.entry",
clientEndpointHolder.getClassName(),
+ serverEndpointUri));
}
boolean secure = false;
ByteBuffer proxyConnect = null;
- URI proxyPath;
+ URI proxyUri;
// Validate scheme (and build proxyPath)
- String scheme = path.getScheme();
+ String scheme = serverEndpointUri.getScheme();
if ("ws".equalsIgnoreCase(scheme)) {
- proxyPath = URI.create("http" + path.toString().substring(2));
+ proxyUri = URI.create("http" +
serverEndpointUri.toString().substring(2));
} else if ("wss".equalsIgnoreCase(scheme)) {
- proxyPath = URI.create("https" + path.toString().substring(3));
+ proxyUri = URI.create("https" +
serverEndpointUri.toString().substring(3));
secure = true;
} else {
throw new
DeploymentException(sm.getString("wsWebSocketContainer.pathWrongScheme",
scheme));
}
- // Validate host
- String host = path.getHost();
- if (host == null) {
+ // Validate server endpoint host
+ String serverEndpointHost = serverEndpointUri.getHost();
+ if (serverEndpointHost == null) {
throw new
DeploymentException(sm.getString("wsWebSocketContainer.pathNoHost"));
}
- int port = path.getPort();
+ int serverEndpointPort = serverEndpointUri.getPort();
SocketAddress sa = null;
// Check to see if a proxy is configured. Javadoc indicates return
value
// will never be null
- List<Proxy> proxies = ProxySelector.getDefault().select(proxyPath);
+ List<Proxy> proxies = ProxySelector.getDefault().select(proxyUri);
Proxy selectedProxy = null;
for (Proxy proxy : proxies) {
if (proxy.type().equals(Proxy.Type.HTTP)) {
@@ -225,12 +227,12 @@ public class WsWebSocketContainer implements
WebSocketContainer, BackgroundProce
// If the port is not explicitly specified, compute it based on the
// scheme
- if (port == -1) {
+ if (serverEndpointPort == -1) {
if ("ws".equalsIgnoreCase(scheme)) {
- port = 80;
+ serverEndpointPort = 80;
} else {
// Must be wss due to scheme validation above
- port = 443;
+ serverEndpointPort = 443;
}
}
@@ -238,21 +240,23 @@ public class WsWebSocketContainer implements
WebSocketContainer, BackgroundProce
// If sa is null, no proxy is configured so need to create sa
if (sa == null) {
- sa = new InetSocketAddress(host, port);
+ sa = new InetSocketAddress(serverEndpointHost, serverEndpointPort);
} else {
- proxyConnect = createProxyRequest(host, port,
+ proxyConnect = createProxyRequest(serverEndpointHost,
serverEndpointPort,
(String)
userProperties.get(Constants.PROXY_AUTHORIZATION_HEADER_NAME));
}
// Create the initial HTTP request to open the WebSocket connection
- Map<String,List<String>> reqHeaders = createRequestHeaders(host, port,
secure, clientEndpointConfiguration);
-
clientEndpointConfiguration.getConfigurator().beforeRequest(reqHeaders);
- if (Constants.DEFAULT_ORIGIN_HEADER_VALUE != null &&
!reqHeaders.containsKey(Constants.ORIGIN_HEADER_NAME)) {
+ Map<String,List<String>> upgradeRequestHeaders =
+ createRequestHeaders(serverEndpointHost, serverEndpointPort,
secure, clientEndpointConfiguration);
+
clientEndpointConfiguration.getConfigurator().beforeRequest(upgradeRequestHeaders);
+ if (Constants.DEFAULT_ORIGIN_HEADER_VALUE != null &&
+
!upgradeRequestHeaders.containsKey(Constants.ORIGIN_HEADER_NAME)) {
List<String> originValues = new ArrayList<>(1);
originValues.add(Constants.DEFAULT_ORIGIN_HEADER_VALUE);
- reqHeaders.put(Constants.ORIGIN_HEADER_NAME, originValues);
+ upgradeRequestHeaders.put(Constants.ORIGIN_HEADER_NAME,
originValues);
}
- ByteBuffer request = createRequest(path, reqHeaders);
+ ByteBuffer upgradeRequest = createRequest(serverEndpointUri,
upgradeRequestHeaders);
// Get the connection timeout
long timeout = Constants.IO_TIMEOUT_MS_DEFAULT;
@@ -289,19 +293,23 @@ public class WsWebSocketContainer implements
WebSocketContainer, BackgroundProce
writeRequest(channel, proxyConnect, timeout);
HttpResponse httpResponse = processResponse(response, channel,
timeout);
if (httpResponse.status ==
Constants.PROXY_AUTHENTICATION_REQUIRED) {
- return
processAuthenticationChallenge(clientEndpointHolder,
clientEndpointConfiguration, path,
- redirectSet, userProperties, request,
httpResponse, AuthenticationType.PROXY);
+ return
processAuthenticationChallenge(clientEndpointHolder,
clientEndpointConfiguration,
+ serverEndpointUri, redirectSet, userProperties,
Method.CONNECT,
+ serverEndpointHost + ":" + serverEndpointPort,
httpResponse, AuthenticationType.PROXY);
} else if (httpResponse.status() != 200) {
throw new
DeploymentException(sm.getString("wsWebSocketContainer.proxyConnectFail",
selectedProxy,
Integer.toString(httpResponse.status())));
}
+ // Proxy authentication either successful or not required.
+
userProperties.remove(Constants.PROXY_AUTHORIZATION_HEADER_NAME);
}
if (secure) {
// Regardless of whether a non-secure wrapper was created for a
// proxy CONNECT, need to use TLS from this point on so wrap
the
// original AsynchronousSocketChannel
- SSLEngine sslEngine =
createSSLEngine(clientEndpointConfiguration, host, port);
+ SSLEngine sslEngine =
+ createSSLEngine(clientEndpointConfiguration,
serverEndpointHost, serverEndpointPort);
channel = new AsyncChannelWrapperSecure(socketChannel,
sslEngine);
} else if (channel == null) {
// Only need to wrap as this point if it wasn't wrapped to
process a
@@ -321,10 +329,10 @@ public class WsWebSocketContainer implements
WebSocketContainer, BackgroundProce
} catch (IOException ioe) {
// Ignore
}
- log.trace(sm.getString("wsWebSocketContainer.connect.write",
Integer.valueOf(request.position()),
- Integer.valueOf(request.limit()), localAddress));
+ log.trace(sm.getString("wsWebSocketContainer.connect.write",
Integer.valueOf(upgradeRequest.position()),
+ Integer.valueOf(upgradeRequest.limit()),
localAddress));
}
- writeRequest(channel, request, timeout);
+ writeRequest(channel, upgradeRequest, timeout);
HttpResponse httpResponse = processResponse(response, channel,
timeout);
@@ -337,6 +345,9 @@ public class WsWebSocketContainer implements
WebSocketContainer, BackgroundProce
if (httpResponse.status != 101) {
if (isRedirectStatus(httpResponse.status)) {
+ // HTTP redirect. Authentication either successful or not
required.
+ userProperties.remove(Constants.AUTHORIZATION_HEADER_NAME);
+
List<String> locationHeader =
httpResponse.handshakeResponse().getHeaders().get(Constants.LOCATION_HEADER_NAME);
@@ -349,7 +360,7 @@ public class WsWebSocketContainer implements
WebSocketContainer, BackgroundProce
URI redirectLocation =
URI.create(locationHeader.getFirst()).normalize();
if (!redirectLocation.isAbsolute()) {
- redirectLocation = path.resolve(redirectLocation);
+ redirectLocation =
serverEndpointUri.resolve(redirectLocation);
}
String redirectScheme =
redirectLocation.getScheme().toLowerCase(Locale.ENGLISH);
@@ -370,14 +381,20 @@ public class WsWebSocketContainer implements
WebSocketContainer, BackgroundProce
redirectSet);
} else if (httpResponse.status == Constants.UNAUTHORIZED) {
- return
processAuthenticationChallenge(clientEndpointHolder,
clientEndpointConfiguration, path,
- redirectSet, userProperties, request,
httpResponse, AuthenticationType.WWW);
+ String authenticationUri =
+ new String(upgradeRequest.array(),
StandardCharsets.ISO_8859_1).split("\\s", 3)[1];
+ return
processAuthenticationChallenge(clientEndpointHolder,
clientEndpointConfiguration,
+ serverEndpointUri, redirectSet, userProperties,
Method.GET, authenticationUri, httpResponse,
+ AuthenticationType.WWW);
} else {
throw new DeploymentException(
sm.getString("wsWebSocketContainer.invalidStatus",
Integer.toString(httpResponse.status)));
}
}
+ // HTTP upgrade successful. Authentication either successful or
not required.
+ userProperties.remove(Constants.AUTHORIZATION_HEADER_NAME);
+
handshakeResponse = httpResponse.handshakeResponse();
// Sub-protocol
@@ -419,7 +436,7 @@ public class WsWebSocketContainer implements
WebSocketContainer, BackgroundProce
success = true;
} catch (ExecutionException | InterruptedException | SSLException |
EOFException | TimeoutException |
URISyntaxException | AuthenticationException e) {
- throw new
DeploymentException(sm.getString("wsWebSocketContainer.httpRequestFailed",
path), e);
+ throw new
DeploymentException(sm.getString("wsWebSocketContainer.httpRequestFailed",
serverEndpointUri), e);
} finally {
clientEndpointConfiguration.getConfigurator().afterResponse(handshakeResponse);
if (!success) {
@@ -464,9 +481,10 @@ public class WsWebSocketContainer implements
WebSocketContainer, BackgroundProce
private Session processAuthenticationChallenge(ClientEndpointHolder
clientEndpointHolder,
- ClientEndpointConfig clientEndpointConfiguration, URI path,
Set<URI> redirectSet,
- Map<String,Object> userProperties, ByteBuffer request,
HttpResponse httpResponse,
- AuthenticationType authenticationType) throws DeploymentException,
AuthenticationException {
+ ClientEndpointConfig clientEndpointConfiguration, URI
serverEndpointUri, Set<URI> redirectSet,
+ Map<String,Object> userProperties, String authenticationMethod,
String authenticationUri,
+ HttpResponse httpResponse, AuthenticationType authenticationType)
+ throws DeploymentException, AuthenticationException {
if
(userProperties.get(authenticationType.getAuthorizationHeaderName()) != null) {
throw new
DeploymentException(sm.getString("wsWebSocketContainer.failedAuthentication",
@@ -491,15 +509,14 @@ public class WsWebSocketContainer implements
WebSocketContainer, BackgroundProce
Integer.valueOf(httpResponse.status), authScheme));
}
- String requestUri = new String(request.array(),
StandardCharsets.ISO_8859_1).split("\\s", 3)[1];
-
userProperties.put(authenticationType.getAuthorizationHeaderName(),
- auth.getAuthorization(requestUri,
authenticateHeaders.getFirst(),
+ auth.getAuthorization(authenticationMethod, authenticationUri,
authenticateHeaders.getFirst(),
(String)
userProperties.get(authenticationType.getUserNameProperty()),
(String)
userProperties.get(authenticationType.getUserPasswordProperty()),
(String)
userProperties.get(authenticationType.getUserRealmProperty())));
- return connectToServerRecursive(clientEndpointHolder,
clientEndpointConfiguration, path, redirectSet);
+ return connectToServerRecursive(clientEndpointHolder,
clientEndpointConfiguration, serverEndpointUri,
+ redirectSet);
}
diff --git a/test/org/apache/tomcat/websocket/TesterWebSocketClientProxy.java
b/test/org/apache/tomcat/websocket/TesterWebSocketClientProxy.java
index 4182b5bb2b..021d729059 100644
--- a/test/org/apache/tomcat/websocket/TesterWebSocketClientProxy.java
+++ b/test/org/apache/tomcat/websocket/TesterWebSocketClientProxy.java
@@ -58,11 +58,22 @@ import
org.apache.tomcat.websocket.TesterMessageCountClient.TesterProgrammaticEn
* ProxyVia On
* AllowCONNECT 0-65535
* <Proxy *>
- * Order deny,allow
- * Allow from all
* AuthType Basic
* AuthName "Proxy Password Required"
- * AuthUserFile password.file
+ * AuthUserFile "/etc/apache2/password.file"
+ * Require valid-user
+ * </Proxy>
+ * </VirtualHost>
+ *
+ * Listen 8890
+ * <VirtualHost *:8890>
+ * ProxyRequests On
+ * ProxyVia On
+ * AllowCONNECT 0-65535
+ * <Proxy *>
+ * AuthType Digest
+ * AuthName "Proxy Password Required"
+ * AuthUserFile "/etc/apache2/password-digest.file"
* Require valid-user
* </Proxy>
* </VirtualHost>
@@ -71,16 +82,20 @@ import
org.apache.tomcat.websocket.TesterMessageCountClient.TesterProgrammaticEn
* # htpasswd -c password.file proxy
* New Password: proxy-pass
*
+ * # htdigest -c password-digest.file proxy
+ * New Password: proxy-pass
+ *
*/
public class TesterWebSocketClientProxy extends WebSocketBaseTest {
private static final String MESSAGE_STRING = "proxy-test-message";
- private static final String PROXY_ADDRESS = "192.168.0.200";
+ private static final String PROXY_ADDRESS = "192.168.23.32";
private static final String PROXY_PORT_NO_AUTH = "8888";
- private static final String PROXY_PORT_AUTH = "8889";
+ private static final String PROXY_PORT_BASIC_AUTH = "8889";
+ private static final String PROXY_PORT_DIGEST_AUTH = "8890";
// The IP address of the test instance that is reachable from the proxy
- private static final String TOMCAT_ADDRESS = "192.168.0.100";
+ private static final String TOMCAT_ADDRESS = "192.168.23.12";
private static final String TOMCAT_USER = "tomcat";
private static final String TOMCAT_PASSWORD = "tomcat-pass";
@@ -91,35 +106,49 @@ public class TesterWebSocketClientProxy extends
WebSocketBaseTest {
@Test
public void testConnectToServerViaProxyWithNoAuthentication() throws
Exception {
- doTestConnectToServerViaProxy(false, false);
+ doTestConnectToServerViaProxy(false, null);
}
@Test
public void testConnectToServerViaProxyWithServerAuthentication() throws
Exception {
- doTestConnectToServerViaProxy(true, false);
+ doTestConnectToServerViaProxy(true, null);
+ }
+
+
+ @Test
+ public void testConnectToServerViaProxyWithProxyBasicAuthentication()
throws Exception {
+ doTestConnectToServerViaProxy(false, "basic");
+ }
+
+
+ @Test
+ public void
testConnectToServerViaProxyWithServerAndProxyBasicAuthentication() throws
Exception {
+ doTestConnectToServerViaProxy(true, "basic");
}
@Test
- public void testConnectToServerViaProxyWithProxyAuthentication() throws
Exception {
- doTestConnectToServerViaProxy(false, true);
+ public void testConnectToServerViaProxyWithProxyDigestAuthentication()
throws Exception {
+ doTestConnectToServerViaProxy(false, "digest");
}
@Test
- public void testConnectToServerViaProxyWithServerAndProxyAuthentication()
throws Exception {
- doTestConnectToServerViaProxy(true, true);
+ public void
testConnectToServerViaProxyWithServerAndProxyDigestAuthentication() throws
Exception {
+ doTestConnectToServerViaProxy(true, "digest");
}
- private void doTestConnectToServerViaProxy(boolean serverAuthentication,
boolean proxyAuthentication)
+ private void doTestConnectToServerViaProxy(boolean serverAuthentication,
String proxyAuthentication)
throws Exception {
// Configure the proxy
System.setProperty("http.proxyHost", PROXY_ADDRESS);
- if (proxyAuthentication) {
- System.setProperty("http.proxyPort", PROXY_PORT_AUTH);
+ if ("basic".equalsIgnoreCase(proxyAuthentication)) {
+ System.setProperty("http.proxyPort", PROXY_PORT_BASIC_AUTH);
+ } else if ("digest".equalsIgnoreCase(proxyAuthentication)) {
+ System.setProperty("http.proxyPort", PROXY_PORT_DIGEST_AUTH);
} else {
System.setProperty("http.proxyPort", PROXY_PORT_NO_AUTH);
}
@@ -166,7 +195,7 @@ public class TesterWebSocketClientProxy extends
WebSocketBaseTest {
clientEndpointConfig.getUserProperties().put(Constants.WS_AUTHENTICATION_USER_NAME,
TOMCAT_USER);
clientEndpointConfig.getUserProperties().put(Constants.WS_AUTHENTICATION_PASSWORD,
TOMCAT_PASSWORD);
}
- if (proxyAuthentication) {
+ if (proxyAuthentication != null) {
clientEndpointConfig.getUserProperties().put(Constants.WS_AUTHENTICATION_PROXY_USER_NAME,
PROXY_USER);
clientEndpointConfig.getUserProperties().put(Constants.WS_AUTHENTICATION_PROXY_PASSWORD,
PROXY_PASSWORD);
}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 5d12be02fe..2cd61b305f 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -426,6 +426,10 @@
<code>Writer</code> and <code>OutputStream</code>. (markt)
</fix>
<!-- Entries for backport and removal before 12.0.0-M1 below this line
-->
+ <fix>
+ Fix the initial connection to a WebSocket end point where the
connection
+ is made via a proxy that requires DIGEST authentication. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Web applications">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]