diff options
author | Christoph M. Becker <cmbecker69@gmx.de> | 2021-03-18 15:31:23 +0100 |
---|---|---|
committer | Christoph M. Becker <cmbecker69@gmx.de> | 2021-03-18 15:31:23 +0100 |
commit | 02fdf9fd3bb739e223771fc9f8b4d9860b58133e (patch) | |
tree | f2564a4dfdd0f52a01eac5fafc81b0ec924f5b98 /ext/pdo_odbc | |
parent | 8690efd1f8593edec3d1e9c4ed9e89b3018bfd58 (diff) | |
parent | 97cfdcd73b4d4816610c61011f02cfa788fee046 (diff) | |
download | php-git-02fdf9fd3bb739e223771fc9f8b4d9860b58133e.tar.gz |
Merge branch 'PHP-8.0'
* PHP-8.0:
Fix #80783: PDO ODBC truncates BLOB records at every 256th byte
Diffstat (limited to 'ext/pdo_odbc')
-rw-r--r-- | ext/pdo_odbc/odbc_stmt.c | 20 | ||||
-rw-r--r-- | ext/pdo_odbc/tests/bug80783.phpt | 32 | ||||
-rw-r--r-- | ext/pdo_odbc/tests/bug80783a.phpt | 33 |
3 files changed, 80 insertions, 5 deletions
diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c index a80aab9a88..c1f4b5f497 100644 --- a/ext/pdo_odbc/odbc_stmt.c +++ b/ext/pdo_odbc/odbc_stmt.c @@ -639,6 +639,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo /* if it is a column containing "long" data, perform late binding now */ if (C->is_long) { + SQLLEN orig_fetched_len = SQL_NULL_DATA; RETCODE rc; /* 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 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; @@ -658,27 +660,35 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo /* 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 for that size would be slower except maybe for extremely long columns.*/ char *buf2 = emalloc(256); - zend_string *str = zend_string_init(C->data, 255, 0); + zend_string *str = zend_string_init(C->data, 256, 0); size_t used = 255; /* not 256; the driver NUL terminated the buffer */ 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) { /* 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) */ - str = zend_string_realloc(str, used + 255, 0); - memcpy(ZSTR_VAL(str) + used, buf2, 255); + str = zend_string_realloc(str, used + 256, 0); + memcpy(ZSTR_VAL(str) + used, buf2, 256); used = used + 255; } else if (rc==SQL_SUCCESS) { str = zend_string_realloc(str, used + C->fetched_len, 0); diff --git a/ext/pdo_odbc/tests/bug80783.phpt b/ext/pdo_odbc/tests/bug80783.phpt new file mode 100644 index 0000000000..9794c25a30 --- /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 0000000000..f9e123ae54 --- /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) |