postgresql-jdbc.git / summary / log / commit / refs
commit ed03772b3ab980cee24470b39cf6f4d428f034ed
Author: Kris Jurka <books@ejurka.com>
Date: Mon Oct 18 03:45:26 2004 +0000
When streaming data from setBinaryStream to the backend, do not
allow Exceptions or bad length parameters to cause the connection
state to get confused destroying the connection.
org/postgresql/core/PGBindException.java | 16 ++++++
org/postgresql/core/PGStream.java | 20 +++++--
org/postgresql/core/v3/QueryExecutorImpl.java | 66 +++++++++++++++++++---
org/postgresql/jdbc2/AbstractJdbc2Statement.java | 9 +++
.../test/jdbc2/PreparedStatementTest.java | 56 ++++++++++++++++++
org/postgresql/test/util/BrokenInputStream.java | 25 ++++++++
6 files changed, 179 insertions(+), 13 deletions(-)
diff --git a/org/postgresql/core/PGBindException.java b/org/postgresql/core/PGBindException.java
new file mode 100644
index 00000000..8f7e8120
--- /dev/null
+++ b/org/postgresql/core/PGBindException.java
@@ -0,0 +1,16 @@
+package org.postgresql.core;
+
+import java.io.IOException;
+
+public class PGBindException extends IOException {
+
+ private IOException _ioe;
+
+ public PGBindException(IOException ioe) {
+ _ioe = ioe;
+ }
+
+ public IOException getIOException() {
+ return _ioe;
+ }
+}
diff --git a/org/postgresql/core/PGStream.java b/org/postgresql/core/PGStream.java
index 068aaaac..3c942707 100644
--- a/org/postgresql/core/PGStream.java
+++ b/org/postgresql/core/PGStream.java
@@ -24,6 +24,8 @@ import java.io.Writer;
import java.net.Socket;
import java.sql.*;
+import org.postgresql.util.GT;
+
/**
* Wrapper around the raw connection to the server that implements some basic
* primitives (reading/writing formatted data, doing string encoding, etc).
@@ -437,6 +439,7 @@ public class PGStream
* @param remaining the number of bytes to copy
*/
public void SendStream(InputStream inStream, int remaining) throws IOException {
+ int expectedLength = remaining;
if (streamBuffer == null)
streamBuffer = new byte[8192];
@@ -444,10 +447,19 @@ public class PGStream
int count = (remaining > streamBuffer.length ? streamBuffer.length : remaining);
int readCount;
- readCount = inStream.read(streamBuffer, 0, count);
- if (readCount < 0)
- throw new EOFException("Premature end of input stream");
-
+ try {
+ readCount = inStream.read(streamBuffer, 0, count);
+ if (readCount < 0)
+ throw new EOFException(GT.tr("Premature end of input stream, expected {0} bytes, but only read {1}.", new Object[]{new Integer(expectedLength), new Integer(expectedLength - remaining)}));
+ } catch(IOException ioe) {
+ while (remaining > 0) {
+ Send(streamBuffer, count);
+ remaining -= count;
+ count = (remaining > streamBuffer.length ? streamBuffer.length : remaining);
+ }
+ throw new PGBindException(ioe);
+ }
+
Send(streamBuffer, readCount);
remaining -= readCount;
}
diff --git a/org/postgresql/core/v3/QueryExecutorImpl.java b/org/postgresql/core/v3/QueryExecutorImpl.java
index 8cd4e41e..a1ba095f 100644
--- a/org/postgresql/core/v3/QueryExecutorImpl.java
+++ b/org/postgresql/core/v3/QueryExecutorImpl.java
@@ -131,10 +131,30 @@ public class QueryExecutorImpl implements QueryExecutor {
((V3ParameterList)parameters).checkAllParametersSet();
try {
- handler = sendQueryPreamble(handler, flags);
- sendQuery((V3Query)query, (V3ParameterList)parameters, maxRows, fetchSize, flags);
- sendSync();
- processResults(handler, flags);
+ try {
+ handler = sendQueryPreamble(handler, flags);
+ sendQuery((V3Query)query, (V3ParameterList)parameters, maxRows, fetchSize, flags);
+ sendSync();
+ processResults(handler, flags);
+ } catch (PGBindException se) {
+ // There are three causes of this error, an
+ // invalid total Bind message length, a
+ // BinaryStream that cannot provide the amount
+ // of data claimed by the length arugment, and
+ // a BinaryStream that throws an Exception
+ // when reading.
+ //
+ // We simply do not send the Execute message
+ // so we can just continue on as if nothing
+ // has happened. Perhaps we need to
+ // introduce an error here to force the
+ // caller to rollback if there is a
+ // transaction in progress?
+ //
+ sendSync();
+ processResults(handler, flags);
+ handler.handleError(new PSQLException(GT.tr("Unable to bind parameter values for statement."), PSQLState.INVALID_PARAMETER_VALUE, se.getIOException()));
+ }
} catch (IOException e) {
protoConnection.close();
handler.handleError(new PSQLException(GT.tr("An I/O error occured while sending to the backend."), PSQLState.CONNECTION_FAILURE, e));
@@ -576,14 +596,14 @@ public class QueryExecutorImpl implements QueryExecutor {
// + 2 (param format code count) + N * 2 (format codes)
// + 2 (param value count) + N (encoded param value size)
// + 2 (result format code count, 0)
- int encodedSize = 0;
+ long encodedSize = 0;
for (int i = 1; i <= params.getParameterCount(); ++i) {
if (params.isNull(i))
encodedSize += 4;
else
- encodedSize += 4 + params.getV3Length(i);
+ encodedSize += (long)4 + params.getV3Length(i);
}
-
+
encodedSize = 4
+ (encodedPortalName == null ? 0 : encodedPortalName.length) + 1
+ (encodedStatementName == null ? 0 : encodedStatementName.length) + 1
@@ -591,8 +611,18 @@ public class QueryExecutorImpl implements QueryExecutor {
+ 2 + encodedSize
+ 2;
+ // backend's MaxAllocSize is the largest message that can
+ // be received from a client. If we have a bigger value
+ // from either very large parameters or incorrent length
+ // descriptions of setXXXStream we do not send the bind
+ // messsage.
+ //
+ if (encodedSize > 0x3fffffff) {
+ throw new PGBindException(new IOException(GT.tr("Bind message length {0} too long. This can be caused by very large or incorrect length specifications on InputStream parameters.", new Long(encodedSize))));
+ }
+
pgStream.SendChar('B'); // Bind
- pgStream.SendInteger4(encodedSize); // Message size
+ pgStream.SendInteger4((int)encodedSize); // Message size
if (encodedPortalName != null)
pgStream.Send(encodedPortalName); // Destination portal name.
pgStream.SendChar(0); // End of portal name.
@@ -605,12 +635,26 @@ public class QueryExecutorImpl implements QueryExecutor {
pgStream.SendInteger2(params.isBinary(i) ? 1 : 0); // Parameter format code
pgStream.SendInteger2(params.getParameterCount()); // # of parameter values
+
+ // If an error occurs when reading a stream we have to
+ // continue pumping out data to match the length we
+ // said we would. Once we've done that we throw
+ // this exception. Multiple exceptions can occur and
+ // it really doesn't matter which one is reported back
+ // to the caller.
+ //
+ PGBindException bindException = null;
+
for (int i = 1; i <= params.getParameterCount(); ++i) {
if (params.isNull(i))
pgStream.SendInteger4(-1); // Magic size of -1 means NULL
else {
pgStream.SendInteger4(params.getV3Length(i)); // Parameter size
- params.writeV3Value(i, pgStream); // Parameter value
+ try {
+ params.writeV3Value(i, pgStream); // Parameter value
+ } catch(PGBindException be) {
+ bindException = be;
+ }
}
}
@@ -618,6 +662,10 @@ public class QueryExecutorImpl implements QueryExecutor {
pgStream.SendChar(0); // (...)
pendingBindQueue.add(portal);
+
+ if (bindException != null) {
+ throw bindException;
+ }
}
private void sendDescribe(Portal portal) throws IOException {
diff --git a/org/postgresql/jdbc2/AbstractJdbc2Statement.java b/org/postgresql/jdbc2/AbstractJdbc2Statement.java
index 33dade8a..c1e359c0 100644
--- a/org/postgresql/jdbc2/AbstractJdbc2Statement.java
+++ b/org/postgresql/jdbc2/AbstractJdbc2Statement.java
@@ -1184,6 +1184,9 @@ public abstract class AbstractJdbc2Statement implements BaseStatement
setNull(parameterIndex, Types.VARCHAR);
return;
}
+ if (length < 0)
+ throw new PSQLException(GT.tr("Invalid stream length {0}.", new Integer(length)));
+
//Version 7.2 supports AsciiStream for all PG text types (char, varchar, text)
//As the spec/javadoc for this method indicate this is to be used for
@@ -1304,6 +1307,9 @@ public abstract class AbstractJdbc2Statement implements BaseStatement
return;
}
+ if (length < 0)
+ throw new PSQLException(GT.tr("Invalid stream length {0}.", new Integer(length)));
+
if (connection.haveMinimumCompatibleVersion("7.2"))
{
//Version 7.2 supports BinaryStream for for the PG bytea type
@@ -2514,6 +2520,9 @@ public abstract class AbstractJdbc2Statement implements BaseStatement
public void setCharacterStream(int i, java.io.Reader x, int length) throws SQLException
{
checkClosed();
+ if (length < 0)
+ throw new PSQLException(GT.tr("Invalid stream length {0}.", new Integer(length)));
+
if (connection.haveMinimumCompatibleVersion("7.2"))
{
//Version 7.2 supports CharacterStream for for the PG text types
diff --git a/org/postgresql/test/jdbc2/PreparedStatementTest.java b/org/postgresql/test/jdbc2/PreparedStatementTest.java
index 61d46ac2..2d3c4d62 100644
--- a/org/postgresql/test/jdbc2/PreparedStatementTest.java
+++ b/org/postgresql/test/jdbc2/PreparedStatementTest.java
@@ -1,6 +1,7 @@
package org.postgresql.test.jdbc2;
import org.postgresql.test.TestUtil;
+import org.postgresql.test.util.BrokenInputStream;
import junit.framework.TestCase;
import java.io.*;
import java.sql.*;
@@ -87,6 +88,61 @@ public class PreparedStatementTest extends TestCase
} catch (SQLException e) {}
}
+ public void testBinaryStreamErrorsRestartable() throws SQLException {
+ // The V2 protocol does not have the ability to recover when
+ // streaming data to the server. We could potentially try
+ // introducing a syntax error to force the query to fail, but
+ // that seems dangerous.
+ //
+ if(!TestUtil.haveMinimumServerVersion(conn, "7.4")) {
+ return;
+ }
+
+ byte buf[] = new byte[10];
+ for (int i=0; i<buf.length; i++) {
+ buf[i] = (byte)i;
+ }
+
+ // InputStream is shorter than the length argument implies.
+ InputStream is = new ByteArrayInputStream(buf);
+ runBrokenStream(is, buf.length+1);
+
+ // InputStream throws an Exception during read.
+ is = new BrokenInputStream(new ByteArrayInputStream(buf), buf.length/2);
+ runBrokenStream(is, buf.length);
+
+ // Invalid length < 0.
+ is = new ByteArrayInputStream(buf);
+ runBrokenStream(is, -1);
+
+ // Total Bind message length too long.
+ is = new ByteArrayInputStream(buf);
+ runBrokenStream(is, Integer.MAX_VALUE);
+ }
+
+ private void runBrokenStream(InputStream is, int length) throws SQLException
+ {
+ PreparedStatement pstmt = null;
+ try {
+ pstmt = conn.prepareStatement("INSERT INTO streamtable (bin,str) VALUES (?,?)");
+ pstmt.setBinaryStream(1, is, length);
+ pstmt.setString(2, "Other");
+ pstmt.executeUpdate();
+ fail("This isn't supposed to work.");
+ } catch (SQLException sqle) {
+ // don't need to rollback because we're in autocommit mode
+ pstmt.close();
+
+ // verify the connection is still valid and the row didn't go in.
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM streamtable");
+ assertTrue(rs.next());
+ assertEquals(0, rs.getInt(1));
+ rs.close();
+ stmt.close();
+ }
+ }
+
private void doSetBinaryStream(ByteArrayInputStream bais, int length) throws SQLException
{
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO streamtable (bin,str) VALUES (?,?)");
diff --git a/org/postgresql/test/util/BrokenInputStream.java b/org/postgresql/test/util/BrokenInputStream.java
new file mode 100644
index 00000000..b806dea2
--- /dev/null
+++ b/org/postgresql/test/util/BrokenInputStream.java
@@ -0,0 +1,25 @@
+package org.postgresql.test.util;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+public class BrokenInputStream extends InputStream {
+
+ private InputStream _is;
+ private long _numRead;
+ private long _breakOn;
+
+ public BrokenInputStream(InputStream is, long breakOn) {
+ _is = is;
+ _breakOn = breakOn;
+ _numRead = 0;
+ }
+
+ public int read() throws IOException {
+ if (_breakOn > _numRead++) {
+ throw new IOException("I was told to break on " + _breakOn);
+ }
+
+ return _is.read();
+ }
+}
[parent: dfa3fa2f26be]