This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/10.1.x by this push:
     new b0637270e2 Fix BZ 69814 Ensure HttpSession.isNew() returns false once 
client joins
b0637270e2 is described below

commit b0637270e2b5d3b61b593de4577aaf32eb77b83b
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Sep 19 14:53:55 2025 +0100

    Fix BZ 69814 Ensure HttpSession.isNew() returns false once client joins
---
 java/org/apache/catalina/connector/Request.java    |   2 +
 .../http2/TestStandardSessionIntegrationHttp2.java | 175 +++++++++++++++++++++
 webapps/docs/changelog.xml                         |   4 +
 3 files changed, 181 insertions(+)

diff --git a/java/org/apache/catalina/connector/Request.java 
b/java/org/apache/catalina/connector/Request.java
index 118984c5fa..c7c2832f93 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -2790,6 +2790,8 @@ public class Request implements HttpServletRequest {
             }
             if (session != null) {
                 session.access();
+                // The client has chosen to join the session
+                session.setNew(false);
                 return session;
             }
         }
diff --git 
a/test/org/apache/coyote/http2/TestStandardSessionIntegrationHttp2.java 
b/test/org/apache/coyote/http2/TestStandardSessionIntegrationHttp2.java
new file mode 100644
index 0000000000..7661310f3e
--- /dev/null
+++ b/test/org/apache/coyote/http2/TestStandardSessionIntegrationHttp2.java
@@ -0,0 +1,175 @@
+/*
+ * 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.coyote.http2;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.tomcat.util.http.Method;
+import org.apache.tomcat.util.http.MimeHeaders;
+
+public class TestStandardSessionIntegrationHttp2 extends Http2TestBase {
+
+    @Test
+    public void testSessionIsNew() throws Exception {
+
+        enableHttp2();
+
+        Tomcat tomcat = getTomcatInstance();
+
+        Context ctxt = getProgrammaticRootContext();
+        // Need simple servlet for the HTTP upgrade
+        Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
+        ctxt.addServletMappingDecoded("/simple", "simple");
+        // Servlet for this test
+        Tomcat.addServlet(ctxt, "session", new SessionServlet());
+        ctxt.addServletMappingDecoded("/session", "session");
+        tomcat.start();
+
+        openClientConnection();
+        doHttpUpgrade();
+        sendClientPreface();
+        validateHttp2InitialResponse();
+
+        output.setTraceBody(true);
+
+        // Make first request
+        // Generate headers
+        byte[] headersFrameHeader = new byte[9];
+        ByteBuffer headersPayload = ByteBuffer.allocate(128);
+
+        MimeHeaders headers = new MimeHeaders();
+        headers.addValue(":method").setString(Method.GET);
+        headers.addValue(":scheme").setString("http");
+        headers.addValue(":path").setString("/session");
+        headers.addValue(":authority").setString("localhost:" + getPort());
+
+        hpackEncoder.encode(headers, headersPayload);
+        headersPayload.flip();
+
+        ByteUtil.setThreeBytes(headersFrameHeader, 0, headersPayload.limit());
+        headersFrameHeader[3] = FrameType.HEADERS.getIdByte();
+        // Flags. end of headers (0x04). end of stream (0x01)
+        headersFrameHeader[4] = 0x05;
+        // Stream id
+        ByteUtil.set31Bits(headersFrameHeader, 5, 3);
+
+        writeFrame(headersFrameHeader, headersPayload);
+
+        // Read headers from first request
+        parser.readFrame();
+        // extract the session ID
+        String trace = output.getTrace();
+        int index = trace.indexOf("JSESSIONID=");
+        String sessionID = trace.substring(index + 11, index + 11 + 32);
+        output.clearTrace();
+
+        // Make second request - can just 'update' first request
+        headersPayload.clear();
+        headers.addValue("cookie").setString("JSESSIONID=" + sessionID);
+        hpackEncoder.encode(headers, headersPayload);
+        headersPayload.flip();
+
+        ByteUtil.setThreeBytes(headersFrameHeader, 0, headersPayload.limit());
+        // Stream id
+        ByteUtil.set31Bits(headersFrameHeader, 5, 5);
+
+        writeFrame(headersFrameHeader, headersPayload);
+
+        // Request 2 headers
+        parser.readFrame();
+        // body (request 1 or 2)
+        parser.readFrame();
+        // body (request 1 or 2)
+        parser.readFrame();
+
+        trace = output.getTrace();
+        Assert.assertTrue(trace, trace.contains("3-Body-OK"));
+        Assert.assertTrue(trace, trace.contains("5-Body-OK"));
+    }
+
+
+    private static class SessionServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
+
+            resp.setContentType("text/plain");
+            resp.setCharacterEncoding(StandardCharsets.UTF_8);
+            PrintWriter pw = resp.getWriter();
+
+            boolean pass = true;
+
+            HttpSession s = req.getSession(false);
+            CountDownLatch latch;
+            if (s == null) {
+                s = req.getSession(true);
+                if (!s.isNew()) {
+                    // This is first request so session must be new.
+                    pass = false;
+                }
+                latch = new CountDownLatch(1);
+                s.setAttribute("latch", latch);
+                // Return the session ID to the client
+                resp.flushBuffer();
+                // Wait for the second request to this session
+                while (latch.getCount() > 0) {
+                    try {
+                        latch.await();
+                    } catch (InterruptedException e) {
+                        // Ignore. Only proceed one the latch has counted down.
+                    }
+                }
+                // Second request has accessed session. Client has joined the 
session.
+                if (s.isNew()) {
+                    // Session should not be new once client has joined it.
+                    pass = false;
+                }
+            } else {
+                if (s.isNew()) {
+                    // This is second (or later) request. Client has joined 
the session. Sessions should not be new.
+                    pass = false;
+                }
+                // Release the first request if it is still waiting.
+                latch = (CountDownLatch) s.getAttribute("latch");
+                latch.countDown();
+            }
+
+            if (pass) {
+                pw.print("OK");
+            } else {
+                pw.print("FAIL");
+            }
+        }
+    }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 73fa6cdc7f..bdc709b84e 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -123,6 +123,10 @@
         HTTP methods are case-sensitive so always use case sensitive 
comparisons
         when comparing HTTP methods. (markt)
       </fix>
+      <fix>
+        <bug>69814</bug>: Ensure that <code>HttpSession.isNew()</code> returns
+        <code>false</code> once the client has joined the session. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to