Blame SOURCES/php-bug80783.patch

9740aa
From bccca0b53aa60a62e2988c750fc73c02d109e642 Mon Sep 17 00:00:00 2001
9740aa
From: "Christoph M. Becker" <cmbecker69@gmx.de>
9740aa
Date: Thu, 25 Feb 2021 14:38:42 +0100
9740aa
Subject: [PATCH] Fix #80783: PDO ODBC truncates BLOB records at every 256th
9740aa
 byte
9740aa
9740aa
It is not guaranteed, that the driver inserts only a single NUL byte at
9740aa
the end of the buffer.  Apparently, there is no way to find out the
9740aa
actual data length in the buffer after calling `SQLGetData()`, so we
9740aa
adjust after the next `SQLGetData()` call.
9740aa
9740aa
We also prevent PDO::ODBC_ATTR_ASSUME_UTF8 from fetching garbage, by
9740aa
fetching all chunks with the same C type.
9740aa
9740aa
Closes GH-6716.
9740aa
---
9740aa
 NEWS                              |  4 ++++
9740aa
 ext/pdo_odbc/odbc_stmt.c          | 14 +++++++++++--
9740aa
 ext/pdo_odbc/tests/bug80783.phpt  | 32 ++++++++++++++++++++++++++++++
9740aa
 ext/pdo_odbc/tests/bug80783a.phpt | 33 +++++++++++++++++++++++++++++++
9740aa
 4 files changed, 81 insertions(+), 2 deletions(-)
9740aa
 create mode 100644 ext/pdo_odbc/tests/bug80783.phpt
9740aa
 create mode 100644 ext/pdo_odbc/tests/bug80783a.phpt
9740aa
9740aa
diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c
9740aa
index 18abc475b9eb..7ce0bebdca0d 100644
9740aa
--- a/ext/pdo_odbc/odbc_stmt.c
9740aa
+++ b/ext/pdo_odbc/odbc_stmt.c
9740aa
@@ -652,6 +652,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong
9740aa
 
9740aa
 	/* if it is a column containing "long" data, perform late binding now */
9740aa
 	if (C->is_long) {
9740aa
+		SQLLEN orig_fetched_len = SQL_NULL_DATA;
9740aa
 		zend_ulong used = 0;
9740aa
 		char *buf;
9740aa
 		RETCODE rc;
9740aa
@@ -662,6 +663,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong
9740aa
 
9740aa
 		rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data,
9740aa
  			256, &C->fetched_len);
9740aa
+		orig_fetched_len = C->fetched_len;
9740aa
 
9740aa
 		if (rc == SQL_SUCCESS) {
9740aa
 			/* all the data fit into our little buffer;
9740aa
@@ -673,7 +675,8 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong
9740aa
 			/* this is a 'long column'
9740aa
 
9740aa
 			 read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks
9740aa
-			 in order into the output buffer
9740aa
+			 in order into the output buffer; 255 bytes are an optimistic assumption, since the driver may assert
9740aa
+			 more or less NUL bytes at the end; we cater to that later, if actual length information is available
9740aa
 
9740aa
 			 this loop has to work whether or not SQLGetData() provides the total column length.
9740aa
 			 calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read
9740aa
@@ -687,7 +690,14 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong
9740aa
 			do {
9740aa
 				C->fetched_len = 0;
9740aa
 				/* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */
9740aa
-				rc = SQLGetData(S->stmt, colno+1, SQL_C_CHAR, buf2, 256, &C->fetched_len);
9740aa
+				rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, 256, &C->fetched_len);
9740aa
+
9740aa
+				/* adjust `used` in case we have length info from the driver */
9740aa
+				if (orig_fetched_len >= 0 && C->fetched_len >= 0) {
9740aa
+					SQLLEN fixed_used = orig_fetched_len - C->fetched_len;
9740aa
+					ZEND_ASSERT(fixed_used <= used + 1);
9740aa
+					used = fixed_used;
9740aa
+				}
9740aa
 
9740aa
 				/* resize output buffer and reassemble block */
9740aa
 				if (rc==SQL_SUCCESS_WITH_INFO) {
9740aa
diff --git a/ext/pdo_odbc/tests/bug80783.phpt b/ext/pdo_odbc/tests/bug80783.phpt
9740aa
new file mode 100644
9740aa
index 000000000000..9794c25a30ec
9740aa
--- /dev/null
9740aa
+++ b/ext/pdo_odbc/tests/bug80783.phpt
9740aa
@@ -0,0 +1,32 @@
9740aa
+--TEST--
9740aa
+Bug #80783 (PDO ODBC truncates BLOB records at every 256th byte)
9740aa
+--SKIPIF--
9740aa
+
9740aa
+if (!extension_loaded('pdo_odbc')) die('skip pdo_odbc extension not available');
9740aa
+require 'ext/pdo/tests/pdo_test.inc';
9740aa
+PDOTest::skip();
9740aa
+?>
9740aa
+--FILE--
9740aa
+
9740aa
+require 'ext/pdo/tests/pdo_test.inc';
9740aa
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
9740aa
+$db->exec("CREATE TABLE bug80783 (name IMAGE)");
9740aa
+
9740aa
+$string = str_repeat("0123456789", 50);
9740aa
+$db->exec("INSERT INTO bug80783 VALUES('$string')");
9740aa
+
9740aa
+$stmt = $db->prepare("SELECT name FROM bug80783");
9740aa
+$stmt->bindColumn(1, $data, PDO::PARAM_LOB);
9740aa
+$stmt->execute();
9740aa
+$stmt->fetch(PDO::FETCH_BOUND);
9740aa
+
9740aa
+var_dump($data === bin2hex($string));
9740aa
+?>
9740aa
+--CLEAN--
9740aa
+
9740aa
+require 'ext/pdo/tests/pdo_test.inc';
9740aa
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
9740aa
+$db->exec("DROP TABLE bug80783");
9740aa
+?>
9740aa
+--EXPECT--
9740aa
+bool(true)
9740aa
diff --git a/ext/pdo_odbc/tests/bug80783a.phpt b/ext/pdo_odbc/tests/bug80783a.phpt
9740aa
new file mode 100644
9740aa
index 000000000000..f9e123ae5426
9740aa
--- /dev/null
9740aa
+++ b/ext/pdo_odbc/tests/bug80783a.phpt
9740aa
@@ -0,0 +1,33 @@
9740aa
+--TEST--
9740aa
+Bug #80783 (PDO ODBC truncates BLOB records at every 256th byte)
9740aa
+--SKIPIF--
9740aa
+
9740aa
+if (!extension_loaded('pdo_odbc')) die('skip pdo_odbc extension not available');
9740aa
+require 'ext/pdo/tests/pdo_test.inc';
9740aa
+PDOTest::skip();
9740aa
+?>
9740aa
+--FILE--
9740aa
+
9740aa
+require 'ext/pdo/tests/pdo_test.inc';
9740aa
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
9740aa
+$db->exec("CREATE TABLE bug80783a (name NVARCHAR(MAX))");
9740aa
+
9740aa
+$string = str_repeat("0123456789", 50);
9740aa
+$db->exec("INSERT INTO bug80783a VALUES('$string')");
9740aa
+
9740aa
+$stmt = $db->prepare("SELECT name FROM bug80783a");
9740aa
+$stmt->setAttribute(PDO::ODBC_ATTR_ASSUME_UTF8, true);
9740aa
+$stmt->bindColumn(1, $data, PDO::PARAM_STR);
9740aa
+$stmt->execute();
9740aa
+$stmt->fetch(PDO::FETCH_BOUND);
9740aa
+
9740aa
+var_dump($data === $string);
9740aa
+?>
9740aa
+--CLEAN--
9740aa
+
9740aa
+require 'ext/pdo/tests/pdo_test.inc';
9740aa
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
9740aa
+$db->exec("DROP TABLE bug80783a");
9740aa
+?>
9740aa
+--EXPECT--
9740aa
+bool(true)
9740aa
From 25f5a1b2e15344e75d69a7140631d467e8b3f966 Mon Sep 17 00:00:00 2001
9740aa
From: Remi Collet <remi@remirepo.net>
9740aa
Date: Thu, 8 Apr 2021 11:04:33 +0200
9740aa
Subject: [PATCH] Improve fix for #80783
9740aa
9740aa
---
9740aa
 ext/pdo_odbc/odbc_stmt.c | 6 +++---
9740aa
 1 file changed, 3 insertions(+), 3 deletions(-)
9740aa
9740aa
diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c
9740aa
index 7ce0bebdca0d..368648c36ae2 100644
9740aa
--- a/ext/pdo_odbc/odbc_stmt.c
9740aa
+++ b/ext/pdo_odbc/odbc_stmt.c
9740aa
@@ -665,13 +665,13 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong
9740aa
  			256, &C->fetched_len);
9740aa
 		orig_fetched_len = C->fetched_len;
9740aa
 
9740aa
-		if (rc == SQL_SUCCESS) {
9740aa
+		if (rc == SQL_SUCCESS && C->fetched_len < 256) {
9740aa
 			/* all the data fit into our little buffer;
9740aa
 			 * jump down to the generic bound data case */
9740aa
 			goto in_data;
9740aa
 		}
9740aa
 
9740aa
-		if (rc == SQL_SUCCESS_WITH_INFO) {
9740aa
+		if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) {
9740aa
 			/* this is a 'long column'
9740aa
 
9740aa
 			 read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks
9740aa
@@ -700,7 +700,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong
9740aa
 				}
9740aa
 
9740aa
 				/* resize output buffer and reassemble block */
9740aa
-				if (rc==SQL_SUCCESS_WITH_INFO) {
9740aa
+				if (rc==SQL_SUCCESS_WITH_INFO || (rc==SQL_SUCCESS && C->fetched_len > 255)) {
9740aa
 					/* point 5, in section "Retrieving Data with SQLGetData" in http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx
9740aa
 					 states that if SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size)
9740aa
 					 (if a driver fails to follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */