Skip to content

Commit 02fdf9f

Browse files
committed
Merge branch 'PHP-8.0'
* PHP-8.0: Fix #80783: PDO ODBC truncates BLOB records at every 256th byte
2 parents 8690efd + 97cfdcd commit 02fdf9f

File tree

3 files changed

+80
-5
lines changed

3 files changed

+80
-5
lines changed

ext/pdo_odbc/odbc_stmt.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo
639639

640640
/* if it is a column containing "long" data, perform late binding now */
641641
if (C->is_long) {
642+
SQLLEN orig_fetched_len = SQL_NULL_DATA;
642643
RETCODE rc;
643644

644645
/* fetch it into C->data, which is allocated with a length
@@ -647,6 +648,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo
647648

648649
rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data,
649650
256, &C->fetched_len);
651+
orig_fetched_len = C->fetched_len;
650652

651653
if (rc == SQL_SUCCESS) {
652654
/* all the data fit into our little buffer;
@@ -658,27 +660,35 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo
658660
/* this is a 'long column'
659661
660662
read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks
661-
in order into the output buffer
663+
in order into the output buffer; 255 bytes are an optimistic assumption, since the driver may assert
664+
more or less NUL bytes at the end; we cater to that later, if actual length information is available
662665
663666
this loop has to work whether or not SQLGetData() provides the total column length.
664667
calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read
665668
for that size would be slower except maybe for extremely long columns.*/
666669
char *buf2 = emalloc(256);
667-
zend_string *str = zend_string_init(C->data, 255, 0);
670+
zend_string *str = zend_string_init(C->data, 256, 0);
668671
size_t used = 255; /* not 256; the driver NUL terminated the buffer */
669672

670673
do {
671674
C->fetched_len = 0;
672675
/* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */
673-
rc = SQLGetData(S->stmt, colno+1, SQL_C_CHAR, buf2, 256, &C->fetched_len);
676+
rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, 256, &C->fetched_len);
677+
678+
/* adjust `used` in case we have length info from the driver */
679+
if (orig_fetched_len >= 0 && C->fetched_len >= 0) {
680+
SQLLEN fixed_used = orig_fetched_len - C->fetched_len;
681+
ZEND_ASSERT(fixed_used <= used + 1);
682+
used = fixed_used;
683+
}
674684

675685
/* resize output buffer and reassemble block */
676686
if (rc==SQL_SUCCESS_WITH_INFO) {
677687
/* point 5, in section "Retrieving Data with SQLGetData" in http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx
678688
states that if SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size)
679689
(if a driver fails to follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */
680-
str = zend_string_realloc(str, used + 255, 0);
681-
memcpy(ZSTR_VAL(str) + used, buf2, 255);
690+
str = zend_string_realloc(str, used + 256, 0);
691+
memcpy(ZSTR_VAL(str) + used, buf2, 256);
682692
used = used + 255;
683693
} else if (rc==SQL_SUCCESS) {
684694
str = zend_string_realloc(str, used + C->fetched_len, 0);

ext/pdo_odbc/tests/bug80783.phpt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
Bug #80783 (PDO ODBC truncates BLOB records at every 256th byte)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo_odbc')) die('skip pdo_odbc extension not available');
6+
require 'ext/pdo/tests/pdo_test.inc';
7+
PDOTest::skip();
8+
?>
9+
--FILE--
10+
<?php
11+
require 'ext/pdo/tests/pdo_test.inc';
12+
$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
13+
$db->exec("CREATE TABLE bug80783 (name IMAGE)");
14+
15+
$string = str_repeat("0123456789", 50);
16+
$db->exec("INSERT INTO bug80783 VALUES('$string')");
17+
18+
$stmt = $db->prepare("SELECT name FROM bug80783");
19+
$stmt->bindColumn(1, $data, PDO::PARAM_LOB);
20+
$stmt->execute();
21+
$stmt->fetch(PDO::FETCH_BOUND);
22+
23+
var_dump($data === bin2hex($string));
24+
?>
25+
--CLEAN--
26+
<?php
27+
require 'ext/pdo/tests/pdo_test.inc';
28+
$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
29+
$db->exec("DROP TABLE bug80783");
30+
?>
31+
--EXPECT--
32+
bool(true)

ext/pdo_odbc/tests/bug80783a.phpt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Bug #80783 (PDO ODBC truncates BLOB records at every 256th byte)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo_odbc')) die('skip pdo_odbc extension not available');
6+
require 'ext/pdo/tests/pdo_test.inc';
7+
PDOTest::skip();
8+
?>
9+
--FILE--
10+
<?php
11+
require 'ext/pdo/tests/pdo_test.inc';
12+
$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
13+
$db->exec("CREATE TABLE bug80783a (name NVARCHAR(MAX))");
14+
15+
$string = str_repeat("0123456789", 50);
16+
$db->exec("INSERT INTO bug80783a VALUES('$string')");
17+
18+
$stmt = $db->prepare("SELECT name FROM bug80783a");
19+
$stmt->setAttribute(PDO::ODBC_ATTR_ASSUME_UTF8, true);
20+
$stmt->bindColumn(1, $data, PDO::PARAM_STR);
21+
$stmt->execute();
22+
$stmt->fetch(PDO::FETCH_BOUND);
23+
24+
var_dump($data === $string);
25+
?>
26+
--CLEAN--
27+
<?php
28+
require 'ext/pdo/tests/pdo_test.inc';
29+
$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
30+
$db->exec("DROP TABLE bug80783a");
31+
?>
32+
--EXPECT--
33+
bool(true)

0 commit comments

Comments
 (0)