Skip to content

WIP: Implement PDO::PARAM_BINARY #11674

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

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions ext/pdo/pdo_dbh.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class PDO
public const int PARAM_LOB = 3;
/** @cvalue LONG_CONST(PDO_PARAM_STMT) */
public const int PARAM_STMT = 4;
/** @cvalue LONG_CONST(PDO_PARAM_BINARY) */
public const int PARAM_BINARY = 6;
/** @cvalue LONG_CONST(PDO_PARAM_INPUT_OUTPUT) */
public const int PARAM_INPUT_OUTPUT = UNKNOWN;
/** @cvalue LONG_CONST(PDO_PARAM_STR_NATL) */
Expand Down
9 changes: 8 additions & 1 deletion ext/pdo/pdo_dbh_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion ext/pdo/pdo_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ static bool really_register_bound_param(struct pdo_bound_param_data *param, pdo_
parameter = Z_REFVAL(param->parameter);
}

if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_STR && param->max_value_len <= 0 && !Z_ISNULL_P(parameter)) {
if ((PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_STR || PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BINARY) && param->max_value_len <= 0 && !Z_ISNULL_P(parameter)) {
if (!try_convert_to_string(parameter)) {
return 0;
}
Expand Down Expand Up @@ -517,6 +517,7 @@ static inline void fetch_value(pdo_stmt_t *stmt, zval *dest, int colno, enum pdo
convert_to_boolean(dest);
break;
case PDO_PARAM_STR:
case PDO_PARAM_BINARY:
if (Z_TYPE_P(dest) == IS_FALSE) {
/* Return "0" rather than "", because this is what database drivers that
* don't have a dedicated boolean type would return. */
Expand Down
2 changes: 2 additions & 0 deletions ext/pdo/php_pdo_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ enum pdo_param_type {
/* get_col: Not supported (yet?) */
PDO_PARAM_STMT = 4, /* hierarchical result set */

PDO_PARAM_BINARY = 6,

/* magic flag to denote a parameter as being input/output */
PDO_PARAM_INPUT_OUTPUT = 0x80000000,

Expand Down
16 changes: 16 additions & 0 deletions ext/pdo_dblib/dblib_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,22 @@ static zend_string* dblib_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo
if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
use_national_character_set = 0;
}
/*
* A user could be passing a binary (i.e. an image file) in a query.
* It's fragile trying to escape it as a string, so encode it as a
* binary literal instead.
*/
if (paramtype == PDO_PARAM_BINARY) {
/* 1 char = 2 chars in hex, plus 0x */
quoted_str = zend_string_safe_alloc(ZSTR_LEN(unquoted), 2, 2, false);
q = ZSTR_VAL(quoted_str);
*q++ = '0';
*q++ = 'x';
for (i = 0; i < ZSTR_LEN(unquoted); i++) {
q += sprintf(q, "%02X", (unsigned char)ZSTR_VAL(unquoted)[i]);
}
return quoted_str;
}

/* Detect quoted length, adding extra char for doubled single quotes */
for (i = 0; i < ZSTR_LEN(unquoted); i++) {
Expand Down
46 changes: 46 additions & 0 deletions ext/pdo_dblib/tests/pdo_dblib_binary.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--TEST--
PDO_DBLIB: Ensure binary bound parameter is a binary literal
--EXTENSIONS--
pdo_dblib
--SKIPIF--
<?php
require __DIR__ . '/config.inc';
?>
--FILE--
<?php
require __DIR__ . '/config.inc';

// This is a one pixel PNG of rgba(0, 0, 0, 255)
$binary = base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2OUAAAAABJRU5ErkJggg==");
$query =
"SELECT ?";
// "create table #php_pdo(value varbinary(max));" .
// "insert into #php_pdo values ?;" .
// "drop table #php_pdo;";

$db = getDbConnection();

$stmt = $db->prepare($query);
// PARAM_LOB gets converted to a binary literal instead of a string literal
$stmt->bindParam(1, $binary, PDO::PARAM_BINARY);
$result = $stmt->execute();

// Verify we sent the binary literal over the wire
var_dump($stmt->debugDumpParams());

// Verify that we get the same PNG back over the wire
$rows = $stmt->fetchAll();
var_dump(base64_encode($rows[0][0]));

?>
--EXPECT--
SQL: [8] SELECT ?
Sent SQL: [149] SELECT 0x89504E470D0A1A0A0000000D49484452000000010000000108060000001F15C4890000000D49444154085B63606060F80F0001040100C12D8E500000000049454E44AE426082
Params: 1
Key: Position #0:
paramno=0
name=[0] ""
is_param=1
param_type=6
NULL
string(96) "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2OUAAAAABJRU5ErkJggg=="
30 changes: 27 additions & 3 deletions ext/pdo_odbc/odbc_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ enum pdo_odbc_conv_result {
PDO_ODBC_CONV_FAIL
};

/*
* Used for determing if we should use SQL_C_BINARY on binary columns
* XXX: Callers use what ODBC returns, rather that what user told PDO,
* need to propagate
*/
static bool php_odbc_sqltype_is_binary(SWORD sqltype) {
if (sqltype == SQL_BINARY || sqltype == SQL_VARBINARY || sqltype == SQL_LONGVARBINARY) {
return true;
}
return false;
}

static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SQLSMALLINT sqltype)
{
if (!S->assume_utf8) return 0;
Expand Down Expand Up @@ -332,6 +344,7 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
sqltype = SQL_INTEGER;
break;
case PDO_PARAM_LOB:
case PDO_PARAM_BINARY:
sqltype = SQL_LONGVARBINARY;
break;
default:
Expand Down Expand Up @@ -641,8 +654,15 @@ static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno)

static int odbc_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value)
{
pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
pdo_odbc_column *C = &S->cols[colno];

array_init(return_value);
add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR);
if (php_odbc_sqltype_is_binary(C->coltype)) {
add_assoc_long(return_value, "pdo_type", PDO_PARAM_BINARY);
} else {
add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR);
}
return 1;
}

Expand All @@ -651,6 +671,10 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo
pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
pdo_odbc_column *C = &S->cols[colno];

SQLSMALLINT c_type = C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR;
if (type && (*type == PDO_PARAM_BINARY || *type == PDO_PARAM_LOB)) {
c_type = SQL_C_BINARY;
}
/* if it is a column containing "long" data, perform late binding now */
if (C->is_long) {
SQLLEN orig_fetched_len = SQL_NULL_DATA;
Expand All @@ -660,7 +684,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo
* of 256 bytes; if there is more to be had, we then allocate
* bigger buffer for the caller to free */

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

Expand All @@ -687,7 +711,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo
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, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, 256, &C->fetched_len);
rc = SQLGetData(S->stmt, colno+1, c_type, buf2, 256, &C->fetched_len);

/* adjust `used` in case we have proper length info from the driver */
if (orig_fetched_len >= 0 && C->fetched_len >= 0) {
Expand Down
2 changes: 1 addition & 1 deletion ext/pdo_odbc/tests/bug80783.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ $stmt->bindColumn(1, $data, PDO::PARAM_LOB);
$stmt->execute();
$stmt->fetch(PDO::FETCH_BOUND);

var_dump($data === bin2hex($string));
var_dump($data === $string);
?>
--CLEAN--
<?php
Expand Down
40 changes: 40 additions & 0 deletions ext/pdo_odbc/tests/pdo_odbc_binary.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
PDO_odbc: Test PARAM_BINARY
--EXTENSIONS--
pdo_odbc
--FILE--
<?php

// This is a one pixel PNG of rgba(0, 0, 0, 255)
$binary = base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2OUAAAAABJRU5ErkJggg==");

require 'ext/pdo/tests/pdo_test.inc';
$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
// XXX: Is this the right type to use?
$db->exec("CREATE TABLE test_binary(field VARBINARY(256))");

$stmt = $db->prepare("INSERT INTO test_binary(field) VALUES (?)");
$stmt->bindParam(1, $binary, PDO::PARAM_BINARY);
$result = $stmt->execute();
var_dump($result);

$stmt = $db->prepare("SELECT field FROM test_binary");
$result = $stmt->execute();
$binary = "";
$stmt->bindColumn(1, $binary, PDO::PARAM_BINARY);
$result = $stmt->execute();
$result = $stmt->fetch();

var_dump(base64_encode($binary));
var_dump($stmt->getColumnMeta(0)["pdo_type"]);
?>
--CLEAN--
<?php
require 'ext/pdo/tests/pdo_test.inc';
$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
$db->exec("DROP TABLE test_binary");
?>
--EXPECT--
bool(true)
string(96) "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2OUAAAAABJRU5ErkJggg=="
int(6)
10 changes: 7 additions & 3 deletions ext/pdo_sqlite/sqlite_statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ static int pdo_sqlite_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_d
return 0;

case PDO_PARAM_LOB:
// For SQLite BLOB columns, these are identical
case PDO_PARAM_BINARY:
if (Z_ISREF(param->parameter)) {
parameter = Z_REFVAL(param->parameter);
} else {
Expand Down Expand Up @@ -329,9 +331,11 @@ static int pdo_sqlite_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *ret
break;

case SQLITE_BLOB:
add_next_index_string(&flags, "blob");
/* TODO Check this is correct */
ZEND_FALLTHROUGH;
// add_next_index_string(&flags, "blob");
add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_STRING));
add_assoc_long(return_value, "pdo_type", PDO_PARAM_BINARY);
break;

case SQLITE_TEXT:
add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_STRING));
add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR);
Expand Down
37 changes: 37 additions & 0 deletions ext/pdo_sqlite/tests/pdo_sqlite_binary.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
PDO_sqlite: Test PARAM_BINARY with a BLOB
--EXTENSIONS--
pdo_sqlite
--FILE--
<?php

// This is a one pixel PNG of rgba(0, 0, 0, 255)
$binary = base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2OUAAAAABJRU5ErkJggg==");

$db = new PDO("sqlite::memory:");
$db->exec("CREATE TABLE test_binary(field BLOB)");

$stmt = $db->prepare("INSERT INTO test_binary(field) VALUES (?)");
$stmt->bindParam(1, $binary, PDO::PARAM_BINARY);
$result = $stmt->execute();
var_dump($result);

// We have to bind the result as a PDO binary/SQLite BLOB,
// because just getting the results otherwise will be
// considered a "null" type to SQLite.
$stmt = $db->prepare("SELECT field FROM test_binary");
$result = $stmt->execute();
$binary = "";
$stmt->bindColumn(1, $binary, PDO::PARAM_BINARY);
$result = $stmt->execute();
$result = $stmt->fetch();

var_dump(base64_encode($binary));
var_dump($stmt->getColumnMeta(0)["pdo_type"]);
var_dump($stmt->getColumnMeta(0)["sqlite:decl_type"]);
?>
--EXPECT--
bool(true)
string(96) "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2OUAAAAABJRU5ErkJggg=="
int(6)
string(4) "BLOB"
Loading