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