This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 8.5.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/8.5.x by this push: new 7ccf37d Align with 9.0.x 7ccf37d is described below commit 7ccf37dd9e5b3e8648d928ea9c98f4bc4b856c3c Author: Mark Thomas <ma...@apache.org> AuthorDate: Thu Feb 17 14:14:22 2022 +0000 Align with 9.0.x This fixed BZ 64192 in 9.0.x onwards but that bug doesn't affect 8.5.x. Backported primarily to align 8.5.x with 9.0.x to simplify additional backports. --- .../tomcat/util/net/SocketBufferHandler.java | 67 ++++++++++ .../apache/tomcat/util/net/SocketWrapperBase.java | 3 +- .../tomcat/util/net/TestSocketBufferHandler.java | 139 +++++++++++++++++++++ 3 files changed, 207 insertions(+), 2 deletions(-) diff --git a/java/org/apache/tomcat/util/net/SocketBufferHandler.java b/java/org/apache/tomcat/util/net/SocketBufferHandler.java index 134ee1b..78adf30 100644 --- a/java/org/apache/tomcat/util/net/SocketBufferHandler.java +++ b/java/org/apache/tomcat/util/net/SocketBufferHandler.java @@ -16,12 +16,30 @@ */ package org.apache.tomcat.util.net; +import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import org.apache.tomcat.util.buf.ByteBufferUtils; public class SocketBufferHandler { + static SocketBufferHandler EMPTY = new SocketBufferHandler(0, 0, false) { + @Override + public void expand(int newSize) { + } + /* + * Http2AsyncParser$FrameCompletionHandler will return incomplete + * frame(s) to the buffer. If the previous frame (or concurrent write to + * a stream) triggered a connection close this call would fail with a + * BufferOverflowException as data can't be returned to a buffer of zero + * length. Override the method and make it a NO-OP to avoid triggering + * the exception. + */ + @Override + public void unReadReadBuffer(ByteBuffer returnedData) { + } + }; + private volatile boolean readBufferConfiguredForWrite = true; private volatile ByteBuffer readBuffer; @@ -87,6 +105,55 @@ public class SocketBufferHandler { } + public void unReadReadBuffer(ByteBuffer returnedData) { + if (isReadBufferEmpty()) { + configureReadBufferForWrite(); + readBuffer.put(returnedData); + } else { + int bytesReturned = returnedData.remaining(); + if (readBufferConfiguredForWrite) { + // Writes always start at position zero + if ((readBuffer.position() + bytesReturned) > readBuffer.capacity()) { + throw new BufferOverflowException(); + } else { + // Move the bytes up to make space for the returned data + for (int i = 0; i < readBuffer.position(); i++) { + readBuffer.put(i + bytesReturned, readBuffer.get(i)); + } + // Insert the bytes returned + for (int i = 0; i < bytesReturned; i++) { + readBuffer.put(i, returnedData.get()); + } + // Update the position + readBuffer.position(readBuffer.position() + bytesReturned); + } + } else { + // Reads will start at zero but may have progressed + int shiftRequired = bytesReturned - readBuffer.position(); + if (shiftRequired > 0) { + if ((readBuffer.capacity() - readBuffer.limit()) < shiftRequired) { + throw new BufferOverflowException(); + } + // Move the bytes up to make space for the returned data + int oldLimit = readBuffer.limit(); + readBuffer.limit(oldLimit + shiftRequired); + for (int i = readBuffer.position(); i < oldLimit; i++) { + readBuffer.put(i + shiftRequired, readBuffer.get(i)); + } + } else { + shiftRequired = 0; + } + // Insert the returned bytes + int insertOffset = readBuffer.position() + shiftRequired - bytesReturned; + for (int i = insertOffset; i < bytesReturned + insertOffset; i++) { + readBuffer.put(i, returnedData.get()); + } + readBuffer.position(insertOffset); + } + } + } + + public void configureWriteBufferForWrite() { setWriteBufferConfiguredForWrite(true); } diff --git a/java/org/apache/tomcat/util/net/SocketWrapperBase.java b/java/org/apache/tomcat/util/net/SocketWrapperBase.java index 0898b4f..d6f224c 100644 --- a/java/org/apache/tomcat/util/net/SocketWrapperBase.java +++ b/java/org/apache/tomcat/util/net/SocketWrapperBase.java @@ -380,8 +380,7 @@ public abstract class SocketWrapperBase<E> { */ public void unRead(ByteBuffer returnedInput) { if (returnedInput != null) { - socketBufferHandler.configureReadBufferForWrite(); - socketBufferHandler.getReadBuffer().put(returnedInput); + socketBufferHandler.unReadReadBuffer(returnedInput); } } diff --git a/test/org/apache/tomcat/util/net/TestSocketBufferHandler.java b/test/org/apache/tomcat/util/net/TestSocketBufferHandler.java new file mode 100644 index 0000000..048c182 --- /dev/null +++ b/test/org/apache/tomcat/util/net/TestSocketBufferHandler.java @@ -0,0 +1,139 @@ +/* + * 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.tomcat.util.net; + +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; + + +@RunWith(Parameterized.class) +public class TestSocketBufferHandler { + + @Parameterized.Parameters(name = "{index}: direct[{0}]") + public static Collection<Object[]> parameters() { + List<Object[]> parameterSets = new ArrayList<>(); + + parameterSets.add(new Object[] { Boolean.FALSE }); + parameterSets.add(new Object[] { Boolean.TRUE }); + + return parameterSets; + } + + + @Parameter(0) + public boolean direct; + + + @Test + public void testReturnWhenEmpty() { + SocketBufferHandler sbh = new SocketBufferHandler(8, 8, direct); + sbh.unReadReadBuffer(ByteBuffer.wrap(getBytes("WXYZ"))); + + validate(sbh, "WXYZ"); + } + + + @Test + public void testReturnWhenWritable() { + SocketBufferHandler sbh = new SocketBufferHandler(8, 8, direct); + + sbh.configureReadBufferForWrite(); + sbh.getReadBuffer().put(getBytes("AB")); + + sbh.unReadReadBuffer(ByteBuffer.wrap(getBytes("WXYZ"))); + + validate(sbh, "WXYZAB"); + } + + + @Test(expected = BufferOverflowException.class) + public void testReturnWhenWritableAndFull() { + SocketBufferHandler sbh = new SocketBufferHandler(8, 8, direct); + + sbh.configureReadBufferForWrite(); + sbh.getReadBuffer().put(getBytes("ABCDEFGH")); + + sbh.unReadReadBuffer(ByteBuffer.wrap(getBytes("WXYZ"))); + } + + + @Test + public void testReturnWhenReadableAndUnread() { + SocketBufferHandler sbh = new SocketBufferHandler(8, 8, direct); + + sbh.configureReadBufferForWrite(); + sbh.getReadBuffer().put(getBytes("AB")); + sbh.configureReadBufferForRead(); + + sbh.unReadReadBuffer(ByteBuffer.wrap(getBytes("WXYZ"))); + + validate(sbh, "WXYZAB"); + } + + + @Test(expected = BufferOverflowException.class) + public void testReturnWhenReadableAndUnreadAndFull() { + SocketBufferHandler sbh = new SocketBufferHandler(8, 8, direct); + + sbh.configureReadBufferForWrite(); + sbh.getReadBuffer().put(getBytes("ABCDEF")); + sbh.configureReadBufferForRead(); + + sbh.unReadReadBuffer(ByteBuffer.wrap(getBytes("WXYZ"))); + } + + + @Test + public void testReturnWhenReadableAndPartiallyead() { + SocketBufferHandler sbh = new SocketBufferHandler(8, 8, direct); + + sbh.configureReadBufferForWrite(); + sbh.getReadBuffer().put(getBytes("ABCDEFGH")); + sbh.configureReadBufferForRead(); + for (int i = 0; i < 4; i++) { + sbh.getReadBuffer().get(); + } + + sbh.unReadReadBuffer(ByteBuffer.wrap(getBytes("WXYZ"))); + + validate(sbh, "WXYZEFGH"); + } + + + private void validate(SocketBufferHandler sbh, String expected) { + sbh.configureReadBufferForRead(); + for (byte b : getBytes(expected)) { + Assert.assertEquals(b, sbh.getReadBuffer().get()); + } + Assert.assertEquals(0, sbh.getReadBuffer().remaining()); + } + + + private byte[] getBytes(String input) { + return input.getBytes(StandardCharsets.UTF_8); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org