Skip to content
Draft
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