[Git][java-team/libpostgresql-jdbc-java][upstream] New upstream version 42.7.14
Christoph Berg (@myon)
gitlab at salsa.debian.org
Fri Oct 9 10:29:20 BST 2026
Christoph Berg pushed to branch upstream at Debian Java Maintainers / libpostgresql-jdbc-java
Commits:
a87f3166 by Christoph Berg at 2026-10-09T11:14:23+02:00
New upstream version 42.7.14
- - - - -
11 changed files:
- pom.xml
- src/main/java/org/postgresql/core/AuthMethod.java
- src/main/java/org/postgresql/util/DriverInfo.java
- src/main/java/org/postgresql/util/internal/PgBufferedOutputStream.java
- src/main/resources/META-INF/MANIFEST.MF
- + src/test/java/org/postgresql/core/AuthMethodTest.java
- + src/test/java/org/postgresql/core/RequireAuthConnectTest.java
- + src/test/java/org/postgresql/core/RequireAuthValues.java
- src/test/java/org/postgresql/test/gss/GSSStreamTest.java
- src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java
- src/test/java/org/postgresql/util/internal/PgBufferedOutputStreamTest.java
Changes:
=====================================
pom.xml
=====================================
@@ -8,7 +8,7 @@
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
- <version>42.7.13</version>
+ <version>42.7.14</version>
<packaging>jar</packaging>
<name>PostgreSQL JDBC Driver - JDBC 4.2</name>
<description>Java JDBC 4.2 (JRE 8+) driver for PostgreSQL database</description>
=====================================
src/main/java/org/postgresql/core/AuthMethod.java
=====================================
@@ -31,17 +31,40 @@ public enum AuthMethod {
}
}
+ /**
+ * Parses a {@code requireAuth} value into the authentication methods the
+ * server may request.
+ *
+ * @param requireAuth comma-separated method names, either all plain or all
+ * prefixed with {@code !}
+ * @return {@code null} when {@code requireAuth} is {@code null}, and
+ * {@link #checkAuth} allows every method for {@code null}; otherwise the
+ * allowed methods, empty when the value excludes every method
+ * @throws PSQLException with {@link PSQLState#INVALID_PARAMETER_VALUE} if
+ * the value mixes the two forms, names a method twice, names an unknown
+ * method, or is written without a method
+ */
public static /* @Nullable */ EnumSet<AuthMethod> parseRequireAuth(/* @Nullable */ String requireAuth) throws PSQLException {
if (requireAuth == null) {
return null;
}
- EnumSet<AuthMethod> allowedMethods;
EnumSet<AuthMethod> seenMethods = EnumSet.noneOf(AuthMethod.class);
String[] methods = requireAuth.split(",");
- boolean isDisallowMode = methods.length > 0 && methods[0].trim().startsWith("!");
+ // split drops empty strings at the end, so "," and ",," give an empty
+ // array. A value without a method in it names neither methods to allow
+ // nor methods to exclude. It is a mistake, not a request to refuse every
+ // method, so it fails as an invalid value.
+ if (methods.length == 0) {
+ throw new PSQLException(
+ GT.tr("Invalid authentication method: {0}", requireAuth),
+ PSQLState.INVALID_PARAMETER_VALUE);
+ }
+ boolean isDisallowMode = methods[0].trim().startsWith("!");
- allowedMethods = isDisallowMode ? EnumSet.allOf(AuthMethod.class) : EnumSet.noneOf(AuthMethod.class);
+ EnumSet<AuthMethod> allowedMethods = isDisallowMode
+ ? EnumSet.allOf(AuthMethod.class)
+ : EnumSet.noneOf(AuthMethod.class);
for (String method : methods) {
method = method.trim();
@@ -62,9 +85,20 @@ public enum AuthMethod {
allowedMethods.add(authMethod);
}
}
- return allowedMethods.isEmpty() ? null : allowedMethods;
+ // Return an empty set, not null. checkAuth allows every method for null,
+ // so null here would let the server pick any method.
+ return allowedMethods;
}
+ /**
+ * Refuses an authentication method that {@code requireAuth} does not allow.
+ *
+ * @param allowedMethods the methods to allow, or {@code null} to allow
+ * every method; an empty set refuses every method
+ * @param authMethod the method the server asked for
+ * @throws PSQLException with {@link PSQLState#CONNECTION_REJECTED} if the
+ * method is not allowed
+ */
public static void checkAuth(/* @Nullable */ EnumSet<AuthMethod> allowedMethods, AuthMethod authMethod) throws PSQLException {
if (allowedMethods == null) {
return;
=====================================
src/main/java/org/postgresql/util/DriverInfo.java
=====================================
@@ -16,13 +16,13 @@ public final class DriverInfo {
// Driver name
public static final String DRIVER_NAME = "PostgreSQL JDBC Driver";
public static final String DRIVER_SHORT_NAME = "PgJDBC";
- public static final String DRIVER_VERSION = "42.7.13";
+ public static final String DRIVER_VERSION = "42.7.14";
public static final String DRIVER_FULL_NAME = DRIVER_NAME + " " + DRIVER_VERSION;
// Driver version
public static final int MAJOR_VERSION = 42;
public static final int MINOR_VERSION = 7;
- public static final int PATCH_VERSION = 13;
+ public static final int PATCH_VERSION = 14;
// JDBC specification
public static final String JDBC_VERSION = "4.2";
=====================================
src/main/java/org/postgresql/util/internal/PgBufferedOutputStream.java
=====================================
@@ -164,31 +164,18 @@ public class PgBufferedOutputStream extends FilterOutputStream {
* @throws IOException in case writing to the underlying stream fails
*/
public void writeZeros(int len) throws IOException {
- int startPos = count;
- if (count > 0) {
- int avail = buf.length - count;
- int prefixLength = Math.min(len, avail);
- Arrays.fill(buf, count, count + prefixLength, (byte) 0);
- count += prefixLength;
- len -= prefixLength;
+ if (len < 0) {
+ throw new IllegalArgumentException("len must be non-negative, got " + len);
+ }
+ // If the buffer is full on entry, the first pass fills nothing and just flushes.
+ do {
+ int chunk = Math.min(len, buf.length - count);
+ Arrays.fill(buf, count, count + chunk, (byte) 0);
+ count += chunk;
+ len -= chunk;
if (count == buf.length) {
flushBuffer();
}
- if (len == 0) {
- return;
- }
- }
- // The buffer is empty at this point, and startPos..buf.length is filled with zeros
- // So fill the beginning with zeros as well.
- Arrays.fill(buf, 0, Math.min(startPos, len), (byte) 0);
-
- while (len >= buf.length) {
- // Pretend we have the full buffer
- count = buf.length;
- flushBuffer();
- len -= buf.length;
- }
- // Pretend we have the remaining zeros in the buffer.
- count = len;
+ } while (len > 0);
}
}
=====================================
src/main/resources/META-INF/MANIFEST.MF
=====================================
@@ -1,7 +1,7 @@
Manifest-Version: 1.0
Bundle-License: BSD-2-Clause
Implementation-Title: PostgreSQL JDBC Driver
-Implementation-Version: 42.7.13
+Implementation-Version: 42.7.14
Specification-Vendor: Oracle Corporation
Specification-Version: 4.2
Specification-Title: JDBC
=====================================
src/test/java/org/postgresql/core/AuthMethodTest.java
=====================================
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.core;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.postgresql.util.PSQLException;
+import org.postgresql.util.PSQLState;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.EnumSet;
+
+/**
+ * An unset {@code requireAuth} allows every authentication method, and a
+ * value that excludes every method refuses all of them. A value cannot mix
+ * plain and {@code !} names, name a method twice, name an unknown method, or
+ * be written without a method. Commas at the very end of a value are
+ * ignored.
+ */
+class AuthMethodTest {
+
+ @Test
+ void anUnsetRequireAuthParsesToNull() throws PSQLException {
+ assertNull(AuthMethod.parseRequireAuth(null), "parseRequireAuth(null)");
+ }
+
+ @Test
+ void aPositiveListAllowsOnlyTheMethodsItNames() throws PSQLException {
+ assertEquals(EnumSet.of(AuthMethod.SCRAM_SHA_256, AuthMethod.GSS),
+ AuthMethod.parseRequireAuth("scram-sha-256,gss"));
+ }
+
+ @Test
+ void anExclusionListAllowsEveryMethodItDoesNotName() throws PSQLException {
+ EnumSet<AuthMethod> expected = EnumSet.allOf(AuthMethod.class);
+ expected.remove(AuthMethod.PASSWORD);
+ expected.remove(AuthMethod.MD5);
+
+ assertEquals(expected, AuthMethod.parseRequireAuth("!password,!md5"));
+ }
+
+ /**
+ * Excluding every method but one leaves exactly that one. Over all runs,
+ * each method is checked both when it is the only one allowed and when it
+ * is excluded. A method added to the enum later is checked too.
+ */
+ @ParameterizedTest(name = "every method but {0} excluded")
+ @EnumSource(AuthMethod.class)
+ void anExclusionListCanLeaveASingleMethod(AuthMethod kept)
+ throws PSQLException {
+ String allButKept =
+ RequireAuthValues.excluding(EnumSet.complementOf(EnumSet.of(kept)));
+ EnumSet<AuthMethod> allowed = AuthMethod.parseRequireAuth(allButKept);
+
+ assertEquals(EnumSet.of(kept), allowed,
+ "parseRequireAuth(\"" + allButKept + "\")");
+ assertDoesNotThrow(() -> AuthMethod.checkAuth(allowed, kept));
+ for (AuthMethod other : EnumSet.complementOf(EnumSet.of(kept))) {
+ PSQLException e = assertThrows(PSQLException.class,
+ () -> AuthMethod.checkAuth(allowed, other), "checkAuth for " + other);
+ assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(),
+ "SQLState of the refusal of " + other);
+ }
+ }
+
+ @Test
+ void surroundingSpaceIsIgnoredInAPositiveList() throws PSQLException {
+ assertEquals(EnumSet.of(AuthMethod.PASSWORD, AuthMethod.MD5),
+ AuthMethod.parseRequireAuth(" password , md5 "));
+ }
+
+ @Test
+ void surroundingSpaceIsIgnoredInAnExclusionList() throws PSQLException {
+ assertEquals(
+ EnumSet.complementOf(EnumSet.of(AuthMethod.PASSWORD, AuthMethod.MD5)),
+ AuthMethod.parseRequireAuth(" !password , !md5 "));
+ }
+
+ /**
+ * Excluding every method must parse to an empty set, not to null.
+ * checkAuth allows every method for null, so with null the driver sends a
+ * cleartext password to a server that asks for one.
+ */
+ @Test
+ void excludingEveryMethodParsesToAnEmptySet() throws PSQLException {
+ EnumSet<AuthMethod> allowed =
+ AuthMethod.parseRequireAuth(RequireAuthValues.EVERY_METHOD_EXCLUDED);
+
+ assertEquals(EnumSet.noneOf(AuthMethod.class), allowed,
+ "parseRequireAuth(\"" + RequireAuthValues.EVERY_METHOD_EXCLUDED
+ + "\")");
+ }
+
+ @ParameterizedTest(name = "{0} against an empty allowed set")
+ @EnumSource(AuthMethod.class)
+ void anEmptyAllowedSetRefusesEveryMethod(AuthMethod method) {
+ PSQLException e = assertThrows(PSQLException.class,
+ () -> AuthMethod.checkAuth(EnumSet.noneOf(AuthMethod.class), method));
+ assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(),
+ "SQLState of the refusal");
+ }
+
+ @ParameterizedTest(name = "{0} against an unset requireAuth")
+ @EnumSource(AuthMethod.class)
+ void anUnsetRequireAuthAllowsEveryMethod(AuthMethod method) {
+ assertDoesNotThrow(() -> AuthMethod.checkAuth(null, method));
+ }
+
+ @ParameterizedTest(name = "requireAuth={0}")
+ @ValueSource(strings = {"password,!md5", "!password,md5"})
+ void positiveAndNegativeFormsCannotBeMixed(String requireAuth) {
+ assertParseError(requireAuth);
+ }
+
+ @ParameterizedTest(name = "requireAuth={0}")
+ @ValueSource(strings = {"password,password", "!md5,!md5"})
+ void aMethodCannotBeNamedTwice(String requireAuth) {
+ assertParseError(requireAuth);
+ }
+
+ @ParameterizedTest(name = "requireAuth={0}")
+ @ValueSource(strings = {"sha-256", "trust"})
+ void anUnknownMethodIsRefused(String requireAuth) {
+ assertParseError(requireAuth);
+ }
+
+ /**
+ * Commas at the very end of the value are ignored, and the methods before
+ * them are enforced. Users may already have such a value in their
+ * connection settings, and refusing it would break their connections.
+ */
+ @ParameterizedTest(name = "requireAuth={0}")
+ @ValueSource(strings = {"password,", "password,,"})
+ void commasAtTheEndAreIgnored(String requireAuth) throws PSQLException {
+ assertEquals(EnumSet.of(AuthMethod.PASSWORD),
+ AuthMethod.parseRequireAuth(requireAuth),
+ "parseRequireAuth(\"" + requireAuth + "\")");
+ }
+
+ /**
+ * A value without a method in it is refused as an invalid value. It names
+ * neither methods to allow nor methods to exclude, so it is a mistake, not
+ * a request to refuse every method.
+ */
+ @ParameterizedTest(name = "requireAuth={0}")
+ @ValueSource(strings = {"!", ",", ",,"})
+ void aValueWithoutAMethodIsRefused(String requireAuth) {
+ assertParseError(requireAuth);
+ }
+
+ private static void assertParseError(String requireAuth) {
+ PSQLException e = assertThrows(PSQLException.class,
+ () -> AuthMethod.parseRequireAuth(requireAuth));
+ assertEquals(PSQLState.INVALID_PARAMETER_VALUE.getState(), e.getSQLState(),
+ "SQLState of the parse error");
+ }
+}
=====================================
src/test/java/org/postgresql/core/RequireAuthConnectTest.java
=====================================
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.core;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.postgresql.PGProperty;
+import org.postgresql.util.PSQLState;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.net.SocketFactory;
+
+/**
+ * A requireAuth value that excludes every method must refuse the connection
+ * with {@link PSQLState#CONNECTION_REJECTED}, whatever the server asks for,
+ * and the driver must not send the password.
+ */
+public class RequireAuthConnectTest {
+
+ /** AuthenticationCleartextPassword: the server asks for the password. */
+ private static final byte[] CLEARTEXT_PASSWORD_REQUEST =
+ {'R', 0, 0, 0, 8, 0, 0, 0, 3};
+
+ /** AuthenticationOk sent before any request: trust authentication. */
+ private static final byte[] AUTHENTICATION_OK =
+ {'R', 0, 0, 0, 8, 0, 0, 0, 0};
+
+ private static final String PASSWORD = "secret";
+
+ /** The PasswordMessage that carries {@link #PASSWORD}. */
+ private static final byte[] PASSWORD_MESSAGE =
+ {'p', 0, 0, 0, 11, 's', 'e', 'c', 'r', 'e', 't', 0};
+
+ @Test
+ void refusesACleartextPasswordRequest() {
+ FakeServer server = new FakeServer(CLEARTEXT_PASSWORD_REQUEST);
+ SQLException e = assertThrows(SQLException.class,
+ () -> server.connect(RequireAuthValues.EVERY_METHOD_EXCLUDED));
+ // The bytes are checked before the SQLState, so if the driver sends the
+ // password, the test fails on the password and not on the error that
+ // follows it.
+ assertArrayEquals(new byte[0], server.writtenAfterStartup(),
+ "bytes the driver wrote after the startup message");
+ assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(),
+ "SQLState of the refusal");
+ }
+
+ /**
+ * A server that sends the same request gets a PasswordMessage when
+ * requireAuth allows the password. Without this test, the empty result in
+ * {@link #refusesACleartextPasswordRequest} could come from a socket that
+ * stopped recording.
+ */
+ @Test
+ void sendsThePasswordWhenRequireAuthAllowsIt() {
+ FakeServer server = new FakeServer(CLEARTEXT_PASSWORD_REQUEST);
+ // The reply ends after the request, so the driver fails at end of
+ // stream once it has sent the password. This test does not check that
+ // failure.
+ assertThrows(SQLException.class, () -> server.connect("password"));
+ assertArrayEquals(PASSWORD_MESSAGE, server.writtenAfterStartup(),
+ "bytes the driver wrote after the startup message");
+ }
+
+ /**
+ * requireAuth calls trust authentication "none", and
+ * {@link RequireAuthValues#EVERY_METHOD_EXCLUDED} excludes "none" too.
+ */
+ @Test
+ void refusesTrust() {
+ FakeServer server = new FakeServer(AUTHENTICATION_OK);
+ SQLException e = assertThrows(SQLException.class,
+ () -> server.connect(RequireAuthValues.EVERY_METHOD_EXCLUDED));
+ assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(),
+ "SQLState of the refusal");
+ }
+
+ /** Replies with a fixed message and records what the driver writes. */
+ private static final class FakeServer {
+ /**
+ * The driver creates the socket factory from its class name and passes
+ * it only string properties. So the factory finds the server by the key
+ * that {@link #connect} passes as socketFactoryArg.
+ */
+ static final Map<String, FakeServer> BY_KEY = new ConcurrentHashMap<>();
+
+ final byte[] reply;
+ final ByteArrayOutputStream written = new ByteArrayOutputStream();
+
+ FakeServer(byte[] reply) {
+ this.reply = reply;
+ }
+
+ void connect(String requireAuth) throws SQLException {
+ String key = UUID.randomUUID().toString();
+ Properties props = new Properties();
+ PGProperty.USER.set(props, "test");
+ PGProperty.PASSWORD.set(props, PASSWORD);
+ PGProperty.SSL_MODE.set(props, "disable");
+ PGProperty.GSS_ENC_MODE.set(props, "disable");
+ PGProperty.REQUIRE_AUTH.set(props, requireAuth);
+ PGProperty.SOCKET_FACTORY.set(props,
+ FakeServerSocketFactory.class.getName());
+ PGProperty.SOCKET_FACTORY_ARG.set(props, key);
+ BY_KEY.put(key, this);
+ try {
+ // The factory ignores the host. The driver records the failure for the
+ // host in the JVM-wide GlobalHostStatusTracker, which multi-host URLs
+ // read, so the host is a name that cannot exist.
+ DriverManager.getConnection(
+ "jdbc:postgresql://requireauth.invalid/test", props).close();
+ } finally {
+ BY_KEY.remove(key);
+ }
+ }
+
+ /**
+ * Returns what the driver wrote after its startup message, which starts
+ * with its length.
+ */
+ byte[] writtenAfterStartup() {
+ byte[] bytes = written.toByteArray();
+ int startupLength = ByteBuffer.wrap(bytes).getInt();
+ return Arrays.copyOfRange(bytes, startupLength, bytes.length);
+ }
+ }
+
+ /**
+ * Creates sockets that read {@link FakeServer#reply} and write to
+ * {@link FakeServer#written}. The driver loads this class through the
+ * socketFactory property and calls only {@link #createSocket()}.
+ */
+ public static class FakeServerSocketFactory extends SocketFactory {
+ private final FakeServer server;
+
+ public FakeServerSocketFactory(String key) {
+ this.server = FakeServer.BY_KEY.get(key);
+ }
+
+ @Override
+ public Socket createSocket() {
+ return new Socket() {
+ private final InputStream in = new ByteArrayInputStream(server.reply);
+
+ /** Reports connected, so the driver does not connect it. */
+ @Override
+ public boolean isConnected() {
+ return true;
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return in;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return server.written;
+ }
+ };
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost,
+ int localPort) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port,
+ InetAddress localAddress, int localPort) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
=====================================
src/test/java/org/postgresql/core/RequireAuthValues.java
=====================================
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.core;
+
+import java.util.EnumSet;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+/**
+ * Builds {@code requireAuth} values from the {@link AuthMethod} enum, so the
+ * tests pick up a new method without anyone editing a list. If a name built
+ * here does not match what {@link AuthMethod#fromString} accepts, fromString
+ * throws and the tests fail.
+ */
+final class RequireAuthValues {
+
+ /** Excludes every method, so a connection that uses it must be refused. */
+ static final String EVERY_METHOD_EXCLUDED =
+ excluding(EnumSet.allOf(AuthMethod.class));
+
+ private RequireAuthValues() {
+ }
+
+ /**
+ * The requireAuth value that excludes each of {@code methods}, as in
+ * {@code !password,!md5}.
+ */
+ static String excluding(EnumSet<AuthMethod> methods) {
+ return methods.stream()
+ .map(method ->
+ "!" + method.name().toLowerCase(Locale.ROOT).replace('_', '-'))
+ .collect(Collectors.joining(","));
+ }
+}
=====================================
src/test/java/org/postgresql/test/gss/GSSStreamTest.java
=====================================
@@ -18,6 +18,8 @@ import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
import java.util.Random;
public class GSSStreamTest {
@@ -74,4 +76,44 @@ public class GSSStreamTest {
assertArrayEquals(testMessage, unwrapResult,
"the message should be intact after wrap and unwrap");
}
+
+ /**
+ * {@link GSSOutputStream} inherits {@code writeZeros}. {@code write} sends 100 bytes or more
+ * written into an empty buffer straight from the caller's array, so the data goes in as 3 bytes
+ * and then 97: the 97-byte write fills the buffer exactly, {@code write} sends it and leaves the
+ * 0xff bytes in the array, and {@code writeZeros} starts on an empty buffer that is not zeroed.
+ * It used to send those bytes in place of the zeros.
+ */
+ @Test
+ void writeZerosAfterDataEndingAtBufferBoundarySendsZeros() throws Exception {
+ ByteArrayOutputStream wrappedContents = new ByteArrayOutputStream();
+ MockGSSContext gssContext = new MockGSSContext(0, messageProp);
+ // MockGSSContext.getWrapSizeLimit returns 100, so the GSSOutputStream buffer holds 100 bytes
+ GSSOutputStream out = new GSSOutputStream(
+ new PgBufferedOutputStream(wrappedContents, 20), gssContext, messageProp, 20);
+ byte[] data = new byte[100];
+ Arrays.fill(data, (byte) 0xff);
+ out.write(data, 0, 3);
+ out.write(data, 3, 97);
+
+ out.writeZeros(103);
+ out.flush();
+
+ byte[] expected = new byte[203];
+ Arrays.fill(expected, 0, 100, (byte) 0xff);
+ assertArrayEquals(expected, unwrap(wrappedContents.toByteArray(), gssContext),
+ "100 bytes of 0xff written as 3 + 97, then writeZeros(103)");
+ }
+
+ private byte[] unwrap(byte[] wrapped, MockGSSContext gssContext) throws IOException {
+ GSSInputStream in =
+ new GSSInputStream(new ByteArrayInputStream(wrapped), gssContext, messageProp);
+ ByteArrayOutputStream unwrapped = new ByteArrayOutputStream();
+ byte[] tmp = new byte[256];
+ int n;
+ while ((n = in.read(tmp)) != -1) {
+ unwrapped.write(tmp, 0, n);
+ }
+ return unwrapped.toByteArray();
+ }
}
=====================================
src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java
=====================================
@@ -176,6 +176,22 @@ public class ByteStreamWriterTest extends BaseTest4 {
validateContent(expectedData);
}
+ /**
+ * The content is more than twice the 8192-byte send buffer, so the driver sends it to the socket
+ * without copying it into the buffer, and the buffer array still holds the bytes of the messages
+ * sent before the value when the padding starts. Those bytes used to be sent in place of the
+ * zeros, and the server stored them in the column.
+ */
+ @Test
+ public void contentOverTwiceTheSendBufferIsPaddedWithZeros() throws Exception {
+ ByteBuffer testData = testData(20 * 1024);
+ insertStream(testData, 30 * 1024);
+ byte[] expectedData = new byte[30 * 1024];
+ testData.rewind();
+ testData.get(expectedData, 0, 20 * 1024);
+ validateContent(expectedData);
+ }
+
@Test
public void testLengthLessThanContent() throws Exception {
ByteBuffer testData = testData(8);
=====================================
src/test/java/org/postgresql/util/internal/PgBufferedOutputStreamTest.java
=====================================
@@ -6,6 +6,7 @@
package org.postgresql.util.internal;
import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.params.provider.Arguments.arguments;
@@ -290,6 +291,32 @@ public class PgBufferedOutputStreamTest {
}
);
}
+
+ /**
+ * {@code flush()} sends the buffer and resets its count, but leaves the 0xff bytes in the
+ * array, so {@code writeZeros} starts on an empty buffer that is not zeroed. It used to send
+ * those bytes in place of the zeros.
+ */
+ @ParameterizedTest
+ @ValueSource(ints = {1, ZEROS_BUFFER_SIZE - 1, ZEROS_BUFFER_SIZE, ZEROS_BUFFER_SIZE + 1,
+ ZEROS_BUFFER_SIZE * 2 + 1})
+ void writesZerosWhenTheFlushedBufferStillHoldsEarlierBytes(int numZeros) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int bufferSize = ZEROS_BUFFER_SIZE;
+ PgBufferedOutputStream out = new PgBufferedOutputStream(baos, bufferSize);
+ for (int i = 0; i < bufferSize; i++) {
+ out.write(0xff);
+ }
+ out.flush();
+
+ out.writeZeros(numZeros);
+ out.flush();
+
+ byte[] expected = new byte[bufferSize + numZeros];
+ Arrays.fill(expected, 0, bufferSize, (byte) 0xff);
+ assertArrayEquals(expected, baos.toByteArray(),
+ () -> bufferSize + " bytes of 0xff, flush(), writeZeros(" + numZeros + ")");
+ }
}
@Test
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/commit/a87f3166bb93eafd1368c34299190366996ef902
--
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/commit/a87f3166bb93eafd1368c34299190366996ef902
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20261009/7eaea77e/attachment.htm>
More information about the pkg-java-commits
mailing list