From 39877d39b0a0eb597c66fe9ace1141a683508a71 Mon Sep 17 00:00:00 2001 From: Randy Stark Date: Wed, 4 Mar 2020 01:27:37 +0000 Subject: [PATCH 1/3] Fix PDO OCI Bug #60994 (Reading a multibyte CLOB caps at 8192 characters) --- NEWS | 3 +++ ext/pdo_oci/config.m4 | 8 ++++++++ ext/pdo_oci/oci_statement.c | 31 +++++++++++++++++++++++++++++++ ext/pdo_oci/tests/bug60994.phpt | 6 ++---- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 1cdfac06bb9d8..b021cd5fc39b9 100644 --- a/NEWS +++ b/NEWS @@ -33,6 +33,9 @@ PHP NEWS . Fixed bug #79188 (Memory corruption in preg_replace/preg_replace_callback and unicode). (Nikita) +- PDO_OCI: + . Fixed bug #60994 (Reading a multibyte CLOB caps at 8192 characters). (Randy Stark) + - PDO_ODBC: . Fixed bug #79038 (PDOStatement::nextRowset() leaks column values). (cmb) diff --git a/ext/pdo_oci/config.m4 b/ext/pdo_oci/config.m4 index ea85fc15566c8..a0dbe6990dcc9 100755 --- a/ext/pdo_oci/config.m4 +++ b/ext/pdo_oci/config.m4 @@ -204,6 +204,14 @@ if test "$PHP_PDO_OCI" != "no"; then -L$PDO_OCI_LIB_DIR $PDO_OCI_SHARED_LIBADD ]) + dnl Can handle bytes vs. characters? + PHP_CHECK_LIBRARY(clntsh, OCILobRead2, + [ + AC_DEFINE(HAVE_OCILOBREAD2,1,[ ]) +], [], [ + -L$PDO_OCI_LIB_DIR $PDO_OCI_SHARED_LIBADD + ]) + ifdef([PHP_CHECK_PDO_INCLUDES], [ PHP_CHECK_PDO_INCLUDES diff --git a/ext/pdo_oci/oci_statement.c b/ext/pdo_oci/oci_statement.c index f2c43b9a6975a..0564d79e9fecc 100644 --- a/ext/pdo_oci/oci_statement.c +++ b/ext/pdo_oci/oci_statement.c @@ -635,6 +635,8 @@ struct oci_lob_self { OCILobLocator *lob; oci_lob_env *E; ub4 offset; + ub2 csid; + ub1 csfrm; }; static size_t oci_blob_write(php_stream *stream, const char *buf, size_t count) @@ -657,6 +659,29 @@ static size_t oci_blob_write(php_stream *stream, const char *buf, size_t count) return amt; } +#if HAVE_OCILOBREAD2 +static size_t oci_blob_read(php_stream *stream, char *buf, size_t count) +{ + struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract; + oraub8 byte_amt = (oraub8) count; + oraub8 char_amt = 0; + + sword r = OCILobRead2(self->E->svc, self->E->err, self->lob, + &byte_amt, &char_amt, (oraub8) self->offset, buf, + (oraub8) count, OCI_ONE_PIECE, + NULL, NULL, self->csid, self->csfrm); + + if (r != OCI_SUCCESS && r != OCI_NEED_DATA) { + return (size_t)-1; + } + + self->offset += self->csid == 0 ? byte_amt : char_amt; + if (byte_amt < count) { + stream->eof = 1; + } + return byte_amt; +} +#else static size_t oci_blob_read(php_stream *stream, char *buf, size_t count) { struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract; @@ -678,6 +703,7 @@ static size_t oci_blob_read(php_stream *stream, char *buf, size_t count) } return amt; } +#endif static int oci_blob_close(php_stream *stream, int close_handle) { @@ -743,6 +769,11 @@ static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLoca self->E->svc = self->S->H->svc; self->E->err = self->S->err; + OCILobCharSetId(self->S->H->env, self->S->err, self->lob, &self->csid); + if (self->csid != (ub2) 0) { + OCILobCharSetForm(self->S->H->env, self->S->err, self->lob, &self->csfrm); + } + stm = php_stream_alloc(&oci_blob_stream_ops, self, 0, "r+b"); if (stm) { diff --git a/ext/pdo_oci/tests/bug60994.phpt b/ext/pdo_oci/tests/bug60994.phpt index 890ecb47c7617..7b93537a2f3c4 100644 --- a/ext/pdo_oci/tests/bug60994.phpt +++ b/ext/pdo_oci/tests/bug60994.phpt @@ -92,12 +92,10 @@ echo 'size of string4 is ', strlen($string4), ' bytes, ', mb_strlen($string4), ' echo 'size of stream4 is ', strlen($stream4), ' bytes, ', mb_strlen($stream4), ' chars.', PHP_EOL; echo 'beg of stream4 is ', $start4, PHP_EOL; echo 'end of stream4 is ', $ending4, PHP_EOL; ---XFAIL-- -Fails due to Bug 60994 --EXPECT-- Test 1: j -size of string1 is 1000006 bytes, 1000006 chars. -size of stream1 is 1000006 bytes, 1000006 chars. +size of string1 is 8193 bytes, 8193 chars. +size of stream1 is 8193 bytes, 8193 chars. beg of stream1 is abcjjjjjjj end of stream1 is jjjjjjjxyz From b49ad0c880c9e75706ae157067042c0ade690c44 Mon Sep 17 00:00:00 2001 From: Randy Stark Date: Sat, 21 Mar 2020 17:15:11 +0000 Subject: [PATCH 2/3] Fix PDO OCI Bug #60994 (Reading a multibyte CLOB caps at 8192 characters) - Fix CharSetForm logic --- ext/pdo_oci/oci_statement.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ext/pdo_oci/oci_statement.c b/ext/pdo_oci/oci_statement.c index 0564d79e9fecc..b536eaaf60207 100644 --- a/ext/pdo_oci/oci_statement.c +++ b/ext/pdo_oci/oci_statement.c @@ -636,7 +636,6 @@ struct oci_lob_self { oci_lob_env *E; ub4 offset; ub2 csid; - ub1 csfrm; }; static size_t oci_blob_write(php_stream *stream, const char *buf, size_t count) @@ -669,7 +668,7 @@ static size_t oci_blob_read(php_stream *stream, char *buf, size_t count) sword r = OCILobRead2(self->E->svc, self->E->err, self->lob, &byte_amt, &char_amt, (oraub8) self->offset, buf, (oraub8) count, OCI_ONE_PIECE, - NULL, NULL, self->csid, self->csfrm); + NULL, NULL, 0, SQLCS_IMPLICIT); if (r != OCI_SUCCESS && r != OCI_NEED_DATA) { return (size_t)-1; @@ -770,9 +769,6 @@ static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLoca self->E->err = self->S->err; OCILobCharSetId(self->S->H->env, self->S->err, self->lob, &self->csid); - if (self->csid != (ub2) 0) { - OCILobCharSetForm(self->S->H->env, self->S->err, self->lob, &self->csfrm); - } stm = php_stream_alloc(&oci_blob_stream_ops, self, 0, "r+b"); From 94427b0e2cec463c6a74b8b35789e11424832f4a Mon Sep 17 00:00:00 2001 From: Randy Stark Date: Tue, 24 Mar 2020 12:47:45 +0000 Subject: [PATCH 3/3] Fix PDO OCI Bug #60994 (Reading a multibyte CLOB caps at 8192 characters) - Handle NCLOBs --- ext/pdo_oci/oci_statement.c | 8 ++++---- ext/pdo_oci/tests/bug60994.phpt | 25 +++++++++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/ext/pdo_oci/oci_statement.c b/ext/pdo_oci/oci_statement.c index b536eaaf60207..21566975a0f91 100644 --- a/ext/pdo_oci/oci_statement.c +++ b/ext/pdo_oci/oci_statement.c @@ -635,7 +635,7 @@ struct oci_lob_self { OCILobLocator *lob; oci_lob_env *E; ub4 offset; - ub2 csid; + ub1 csfrm; }; static size_t oci_blob_write(php_stream *stream, const char *buf, size_t count) @@ -668,13 +668,13 @@ static size_t oci_blob_read(php_stream *stream, char *buf, size_t count) sword r = OCILobRead2(self->E->svc, self->E->err, self->lob, &byte_amt, &char_amt, (oraub8) self->offset, buf, (oraub8) count, OCI_ONE_PIECE, - NULL, NULL, 0, SQLCS_IMPLICIT); + NULL, NULL, 0, self->csfrm); if (r != OCI_SUCCESS && r != OCI_NEED_DATA) { return (size_t)-1; } - self->offset += self->csid == 0 ? byte_amt : char_amt; + self->offset += self->csfrm == 0 ? byte_amt : char_amt; if (byte_amt < count) { stream->eof = 1; } @@ -768,7 +768,7 @@ static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLoca self->E->svc = self->S->H->svc; self->E->err = self->S->err; - OCILobCharSetId(self->S->H->env, self->S->err, self->lob, &self->csid); + OCILobCharSetForm(self->S->H->env, self->S->err, self->lob, &self->csfrm); stm = php_stream_alloc(&oci_blob_stream_ops, self, 0, "r+b"); diff --git a/ext/pdo_oci/tests/bug60994.phpt b/ext/pdo_oci/tests/bug60994.phpt index 7b93537a2f3c4..298895276fb1c 100644 --- a/ext/pdo_oci/tests/bug60994.phpt +++ b/ext/pdo_oci/tests/bug60994.phpt @@ -18,18 +18,19 @@ $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); $dbh->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); @$dbh->exec('DROP TABLE pdo_oci_bug60994'); -$dbh->exec('CREATE TABLE pdo_oci_bug60994 (id NUMBER, data CLOB)'); +$dbh->exec('CREATE TABLE pdo_oci_bug60994 (id NUMBER, data CLOB, data2 NCLOB)'); $id = null; -$insert = $dbh->prepare('INSERT INTO pdo_oci_bug60994 (id, data) VALUES (:id, :data)'); +$insert = $dbh->prepare('INSERT INTO pdo_oci_bug60994 (id, data, data2) VALUES (:id, :data, :data2)'); $insert->bindParam(':id', $id, \PDO::PARAM_STR); -$select = $dbh->prepare("SELECT data FROM pdo_oci_bug60994 WHERE id = :id"); +$select = $dbh->prepare("SELECT data, data2 FROM pdo_oci_bug60994 WHERE id = :id"); echo PHP_EOL, 'Test 1: j', PHP_EOL; $string1 = 'abc' . str_repeat('j', 8187) . 'xyz'; // 8193 chars total works fine here (even 1 million works fine, subject to memory_limit) $id = 1; $insert->bindParam(':data', $string1, \PDO::PARAM_STR, strlen($string1)); // length in bytes +$insert->bindParam(':data2', $string1, \PDO::PARAM_STR, strlen($string1)); $insert->execute(); $select->bindParam(':id', $id, \PDO::PARAM_STR); $select->execute(); @@ -41,12 +42,15 @@ echo 'size of string1 is ', strlen($string1), ' bytes, ', mb_strlen($string1), ' echo 'size of stream1 is ', strlen($stream1), ' bytes, ', mb_strlen($stream1), ' chars.', PHP_EOL; echo 'beg of stream1 is ', $start1, PHP_EOL; echo 'end of stream1 is ', $ending1, PHP_EOL; - +if ($stream1 != stream_get_contents($row['DATA2'])) { + echo 'Expected nclob value to match clob value for stream1', PHP_EOL; +} echo PHP_EOL, 'Test 2: £', PHP_EOL; $string2 = 'abc' . str_repeat('£', 8187) . 'xyz'; // 8193 chars total is when it breaks $id = 2; $insert->bindParam(':data', $string2, \PDO::PARAM_STR, strlen($string2)); // length in bytes +$insert->bindParam(':data2', $string2, \PDO::PARAM_STR, strlen($string2)); $insert->execute(); $select->bindParam(':id', $id, \PDO::PARAM_STR); $select->execute(); @@ -58,12 +62,15 @@ echo 'size of string2 is ', strlen($string2), ' bytes, ', mb_strlen($string2), ' echo 'size of stream2 is ', strlen($stream2), ' bytes, ', mb_strlen($stream2), ' chars.', PHP_EOL; echo 'beg of stream2 is ', $start2, PHP_EOL; echo 'end of stream2 is ', $ending2, PHP_EOL; - +if ($stream2 != stream_get_contents($row['DATA2'])) { + echo 'Expected nclob value to match clob value for stream2', PHP_EOL; +} echo PHP_EOL, 'Test 3: Җ', PHP_EOL; $string3 = 'abc' . str_repeat('Җ', 8187) . 'xyz'; // 8193 chars total is when it breaks $id = 3; $insert->bindParam(':data', $string3, \PDO::PARAM_STR, strlen($string3)); // length in bytes +$insert->bindParam(':data2', $string3, \PDO::PARAM_STR, strlen($string3)); $insert->execute(); $select->bindParam(':id', $id, \PDO::PARAM_STR); $select->execute(); @@ -75,12 +82,15 @@ echo 'size of string3 is ', strlen($string3), ' bytes, ', mb_strlen($string3), ' echo 'size of stream3 is ', strlen($stream3), ' bytes, ', mb_strlen($stream3), ' chars.', PHP_EOL; echo 'beg of stream3 is ', $start3, PHP_EOL; echo 'end of stream3 is ', $ending3, PHP_EOL; - +if ($stream3 != stream_get_contents($row['DATA2'])) { + echo 'Expected nclob value to match clob value for stream3', PHP_EOL; +} echo PHP_EOL, 'Test 4: の', PHP_EOL; $string4 = 'abc' . str_repeat('の', 8187) . 'xyz'; // 8193 chars total is when it breaks $id = 4; $insert->bindParam(':data', $string4, \PDO::PARAM_STR, strlen($string4)); // length in bytes +$insert->bindParam(':data2', $string4, \PDO::PARAM_STR, strlen($string4)); $insert->execute(); $select->bindParam(':id', $id, \PDO::PARAM_STR); $select->execute(); @@ -92,6 +102,9 @@ echo 'size of string4 is ', strlen($string4), ' bytes, ', mb_strlen($string4), ' echo 'size of stream4 is ', strlen($stream4), ' bytes, ', mb_strlen($stream4), ' chars.', PHP_EOL; echo 'beg of stream4 is ', $start4, PHP_EOL; echo 'end of stream4 is ', $ending4, PHP_EOL; +if ($stream4 != stream_get_contents($row['DATA2'])) { + echo 'Expected nclob value to match clob value for stream4', PHP_EOL; +} --EXPECT-- Test 1: j size of string1 is 8193 bytes, 8193 chars.