From b08e86d557538c16e482ad10a9bdbc474893d07e Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Sun, 15 Jul 2001 04:21:26 +0000
Subject: [PATCH] The attached patch fixes problems with the JDBC driver
 handling long null terminated strings.  The FE/BE protocol sends in some
 cases null terminated strings to the client.  The docs for the FE/BE protocol
 state that there is no limit on the size of a null terminated string sent to
 the client and a client should be coded using an expanding buffer to deal
 with large strings.  The old code did not do this and gave an error if a null
 terminated string was greater than either 4 or 8K.  It appears that with the
 advent of TOAST very long SQL statements are becoming more common, and
 apparently some error messages from the backend include the SQL statement
 thus easily exceeding the 8K limit in the old code.

In fixing I also cleaned up some calls in the JDBC fastpath code that
were not doing character set conversion under multibyte, and removed
some methods that were no longer needed.  I also removed a potential
threading problem with a shared variable that was being used in
Connection.java.

Thanks to Steve Wampler for discovering the problem and sending the
initial diffs that were the basis of this patch.

thanks,
--Barry
---
 .../jdbc/org/postgresql/Connection.java       | 27 +++----
 .../jdbc/org/postgresql/PG_Stream.java        | 70 ++++++-------------
 .../org/postgresql/fastpath/Fastpath.java     |  6 +-
 3 files changed, 35 insertions(+), 68 deletions(-)

diff --git a/src/interfaces/jdbc/org/postgresql/Connection.java b/src/interfaces/jdbc/org/postgresql/Connection.java
index 357a4ee9e42..ba0076bcfc1 100644
--- a/src/interfaces/jdbc/org/postgresql/Connection.java
+++ b/src/interfaces/jdbc/org/postgresql/Connection.java
@@ -10,7 +10,7 @@ import org.postgresql.largeobject.*;
 import org.postgresql.util.*;
 
 /**
- * $Id: Connection.java,v 1.17 2001/06/07 00:09:32 momjian Exp $
+ * $Id: Connection.java,v 1.18 2001/07/15 04:21:26 momjian Exp $
  *
  * This abstract class is used by org.postgresql.Driver to open either the JDBC1 or
  * JDBC2 versions of the Connection class.
@@ -82,11 +82,6 @@ public abstract class Connection
     public int pid;
     public int ckey;
 
-    // This receive_sbuf should be used by the different methods
-    // that call pg_stream.ReceiveString() in this Connection, so
-    // so we avoid uneccesary new allocations.
-    byte receive_sbuf[] = new byte[8192];
-
     /**
      * This is called by Class.forName() from within org.postgresql.Driver
      */
@@ -167,8 +162,7 @@ public abstract class Connection
 		// The most common one to be thrown here is:
 		// "User authentication failed"
 		//
-		throw new SQLException(pg_stream.ReceiveString
-                                       (receive_sbuf, 4096, getEncoding()));
+                throw new SQLException(pg_stream.ReceiveString(getEncoding()));
 
 	      case 'R':
 		// Get the type of request
@@ -238,8 +232,7 @@ public abstract class Connection
           break;
 	case 'E':
 	case 'N':
-           throw new SQLException(pg_stream.ReceiveString
-                                  (receive_sbuf, 4096, getEncoding()));
+           throw new SQLException(pg_stream.ReceiveString(getEncoding()));
         default:
           throw new PSQLException("postgresql.con.setup");
       }
@@ -251,7 +244,7 @@ public abstract class Connection
 	   break;
 	case 'E':
 	case 'N':
-           throw new SQLException(pg_stream.ReceiveString(receive_sbuf, 4096, getEncoding()));
+           throw new SQLException(pg_stream.ReceiveString(getEncoding()));
         default:
           throw new PSQLException("postgresql.con.setup");
       }
@@ -491,7 +484,7 @@ public abstract class Connection
 			{
 			case 'A':	// Asynchronous Notify
 			    pid = pg_stream.ReceiveInteger(4);
-			    msg = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
+                            msg = pg_stream.ReceiveString(getEncoding());
 			    break;
 			case 'B':	// Binary Data Transfer
 			    if (fields == null)
@@ -502,7 +495,7 @@ public abstract class Connection
 				tuples.addElement(tup);
 			    break;
 			case 'C':	// Command Status
-			    recv_status = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
+                            recv_status = pg_stream.ReceiveString(getEncoding());
 
 				// Now handle the update count correctly.
 				if(recv_status.startsWith("INSERT") || recv_status.startsWith("UPDATE") || recv_status.startsWith("DELETE") || recv_status.startsWith("MOVE")) {
@@ -544,7 +537,7 @@ public abstract class Connection
 				tuples.addElement(tup);
 			    break;
 			case 'E':	// Error Message
-			    msg = pg_stream.ReceiveString(receive_sbuf,4096,getEncoding());
+                            msg = pg_stream.ReceiveString(getEncoding());
 			    final_error = new SQLException(msg);
 			    hfr = true;
 			    break;
@@ -559,10 +552,10 @@ public abstract class Connection
 				hfr = true;
 			    break;
 			case 'N':	// Error Notification
-			    addWarning(pg_stream.ReceiveString(receive_sbuf,4096,getEncoding()));
+                            addWarning(pg_stream.ReceiveString(getEncoding()));
 			    break;
 			case 'P':	// Portal Name
-			    String pname = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
+                            String pname = pg_stream.ReceiveString(getEncoding());
 			    break;
 			case 'T':	// MetaData Field Description
 			    if (fields != null)
@@ -595,7 +588,7 @@ public abstract class Connection
 
 	for (i = 0 ; i < nf ; ++i)
 	    {
-		String typname = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
+                String typname = pg_stream.ReceiveString(getEncoding());
 		int typid = pg_stream.ReceiveIntegerR(4);
 		int typlen = pg_stream.ReceiveIntegerR(2);
 		int typmod = pg_stream.ReceiveIntegerR(4);
diff --git a/src/interfaces/jdbc/org/postgresql/PG_Stream.java b/src/interfaces/jdbc/org/postgresql/PG_Stream.java
index 44d84a8e5a3..30eeb6cece6 100644
--- a/src/interfaces/jdbc/org/postgresql/PG_Stream.java
+++ b/src/interfaces/jdbc/org/postgresql/PG_Stream.java
@@ -23,6 +23,7 @@ public class PG_Stream
   private Socket connection;
   private InputStream pg_input;
   private BufferedOutputStream pg_output;
+  private byte[] byte_buf = new byte[8*1024];
 
     BytePoolDim1 bytePoolDim1 = new BytePoolDim1();
     BytePoolDim2 bytePoolDim2 = new BytePoolDim2();
@@ -200,72 +201,45 @@ public class PG_Stream
   }
 
   /**
-   * Receives a null-terminated string from the backend.  Maximum of
-   * maxsiz bytes - if we don't see a null, then we assume something
-   * has gone wrong.
+   * Receives a null-terminated string from the backend.  If we don't see a
+   * null, then we assume something has gone wrong.
    *
-   * @param maxsiz maximum length of string
-   * @return string from back end
-   * @exception SQLException if an I/O error occurs
-   */
-  public String ReceiveString(int maxsiz) throws SQLException
-  {
-    byte[] rst = bytePoolDim1.allocByte(maxsiz);
-    return ReceiveString(rst, maxsiz, null);
-  }
-
-  /**
-   * Receives a null-terminated string from the backend.  Maximum of
-   * maxsiz bytes - if we don't see a null, then we assume something
-   * has gone wrong.
-   *
-   * @param maxsiz maximum length of string
-   * @param encoding the charset encoding to use.
-   * @param maxsiz maximum length of string in bytes
-   * @return string from back end
-   * @exception SQLException if an I/O error occurs
-   */
-  public String ReceiveString(int maxsiz, String encoding) throws SQLException
-  {
-    byte[] rst = bytePoolDim1.allocByte(maxsiz);
-    return ReceiveString(rst, maxsiz, encoding);
-  }
-
-  /**
-   * Receives a null-terminated string from the backend.  Maximum of
-   * maxsiz bytes - if we don't see a null, then we assume something
-   * has gone wrong.
-   *
-   * @param rst byte array to read the String into. rst.length must
-   *        equal to or greater than maxsize.
-   * @param maxsiz maximum length of string in bytes
    * @param encoding the charset encoding to use.
    * @return string from back end
-   * @exception SQLException if an I/O error occurs
+   * @exception SQLException if an I/O error occurs, or end of file
    */
-  public String ReceiveString(byte rst[], int maxsiz, String encoding)
+  public String ReceiveString(String encoding)
       throws SQLException
   {
     int s = 0;
-
-    try
-      {
-	while (s < maxsiz)
-	  {
+    byte[] rst = byte_buf;
+    try {
+      int buflen = rst.length;
+      boolean done = false;
+      while (!done) {
+        while (s < buflen) {
 	    int c = pg_input.read();
 	    if (c < 0)
 	      throw new PSQLException("postgresql.stream.eof");
  	    else if (c == 0) {
  		rst[s] = 0;
+            done = true;
  		break;
- 	    } else
+          } else {
 	      rst[s++] = (byte)c;
 	  }
-	if (s >= maxsiz)
-	  throw new PSQLException("postgresql.stream.toomuch");
+          if (s >= buflen) { // Grow the buffer
+            buflen = (int)(buflen*2); // 100% bigger
+            byte[] newrst = new byte[buflen];
+            System.arraycopy(rst, 0, newrst, 0, s);
+            rst = newrst;
+          }
+        }
+      }
       } catch (IOException e) {
 	throw new PSQLException("postgresql.stream.ioerror",e);
       }
+
       String v = null;
       if (encoding == null)
           v = new String(rst, 0, s);
diff --git a/src/interfaces/jdbc/org/postgresql/fastpath/Fastpath.java b/src/interfaces/jdbc/org/postgresql/fastpath/Fastpath.java
index 4566f562d28..a99bd0c0d16 100644
--- a/src/interfaces/jdbc/org/postgresql/fastpath/Fastpath.java
+++ b/src/interfaces/jdbc/org/postgresql/fastpath/Fastpath.java
@@ -89,7 +89,7 @@ public class Fastpath
     //DriverManager.println("ReceiveChar() = "+in+" '"+((char)in)+"'");
     //if(in!='V') {
     //if(in=='E')
-    //throw new SQLException(stream.ReceiveString(4096));
+    //throw new SQLException(stream.ReceiveString(conn.getEncoding()));
     //throw new SQLException("Fastpath: expected 'V' from backend, got "+((char)in));
     //}
     
@@ -123,12 +123,12 @@ public class Fastpath
 	  //------------------------------
 	  // Error message returned
 	case 'E':
-	  throw new PSQLException("postgresql.fp.error",stream.ReceiveString(4096));
+          throw new PSQLException("postgresql.fp.error",stream.ReceiveString(conn.getEncoding()));
 	  
 	  //------------------------------
 	  // Notice from backend
 	case 'N':
-	  conn.addWarning(stream.ReceiveString(4096));
+          conn.addWarning(stream.ReceiveString(conn.getEncoding()));
 	  break;
 	  
 	  //------------------------------
-- 
GitLab