Skip to content

Fix #80783: PDO ODBC truncates BLOB records at every 256th byte #6716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions ext/pdo_odbc/odbc_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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) {
Expand Down
32 changes: 32 additions & 0 deletions ext/pdo_odbc/tests/bug80783.phpt
Original file line number Diff line number Diff line change
@@ -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)
33 changes: 33 additions & 0 deletions ext/pdo_odbc/tests/bug80783a.phpt
Original file line number Diff line number Diff line change
@@ -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_LOB);
$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)