diff --git a/ext/pdo/pdo_dbh.stub.php b/ext/pdo/pdo_dbh.stub.php index 7fcec0226b0ba..c8376c21836bc 100644 --- a/ext/pdo/pdo_dbh.stub.php +++ b/ext/pdo/pdo_dbh.stub.php @@ -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) */ diff --git a/ext/pdo/pdo_dbh_arginfo.h b/ext/pdo/pdo_dbh_arginfo.h index 71df4c519e1a7..4271ccc4652d7 100644 --- a/ext/pdo/pdo_dbh_arginfo.h +++ b/ext/pdo/pdo_dbh_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 006be61b2c519e7d9ca997a7f12135eb3e0f3500 */ + * Stub hash: e0faa5c9dda689ae7c22a73df8d720f75e721d50 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, dsn, IS_STRING, 0) @@ -151,6 +151,13 @@ static zend_class_entry *register_class_PDO(void) zend_string_release(const_PARAM_STMT_name); ZEND_ASSERT(LONG_CONST(PDO_PARAM_STMT) == 4); + zval const_PARAM_BINARY_value; + ZVAL_LONG(&const_PARAM_BINARY_value, LONG_CONST(PDO_PARAM_BINARY)); + zend_string *const_PARAM_BINARY_name = zend_string_init_interned("PARAM_BINARY", sizeof("PARAM_BINARY") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_PARAM_BINARY_name, &const_PARAM_BINARY_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_PARAM_BINARY_name); + ZEND_ASSERT(LONG_CONST(PDO_PARAM_BINARY) == 6); + zval const_PARAM_INPUT_OUTPUT_value; ZVAL_LONG(&const_PARAM_INPUT_OUTPUT_value, LONG_CONST(PDO_PARAM_INPUT_OUTPUT)); zend_string *const_PARAM_INPUT_OUTPUT_name = zend_string_init_interned("PARAM_INPUT_OUTPUT", sizeof("PARAM_INPUT_OUTPUT") - 1, 1); diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index a39b7a3b06804..f0c5baa69ed1e 100644 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -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; } @@ -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. */ diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h index c3930f4022464..630e03032e8f4 100644 --- a/ext/pdo/php_pdo_driver.h +++ b/ext/pdo/php_pdo_driver.h @@ -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, diff --git a/ext/pdo_dblib/dblib_driver.c b/ext/pdo_dblib/dblib_driver.c index 17036d2b002fd..70a0aaf4522a5 100644 --- a/ext/pdo_dblib/dblib_driver.c +++ b/ext/pdo_dblib/dblib_driver.c @@ -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++) { diff --git a/ext/pdo_dblib/tests/pdo_dblib_binary.phpt b/ext/pdo_dblib/tests/pdo_dblib_binary.phpt new file mode 100644 index 0000000000000..1fd3c4467db62 --- /dev/null +++ b/ext/pdo_dblib/tests/pdo_dblib_binary.phpt @@ -0,0 +1,46 @@ +--TEST-- +PDO_DBLIB: Ensure binary bound parameter is a binary literal +--EXTENSIONS-- +pdo_dblib +--SKIPIF-- + +--FILE-- +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==" diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c index ffe09a6bf16aa..9caf6342b3632 100644 --- a/ext/pdo_odbc/odbc_stmt.c +++ b/ext/pdo_odbc/odbc_stmt.c @@ -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; @@ -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: @@ -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; } @@ -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; @@ -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; @@ -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) { diff --git a/ext/pdo_odbc/tests/bug80783.phpt b/ext/pdo_odbc/tests/bug80783.phpt index 1660815f95148..5b0bf83736235 100644 --- a/ext/pdo_odbc/tests/bug80783.phpt +++ b/ext/pdo_odbc/tests/bug80783.phpt @@ -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-- 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-- +exec("DROP TABLE test_binary"); +?> +--EXPECT-- +bool(true) +string(96) "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2OUAAAAABJRU5ErkJggg==" +int(6) diff --git a/ext/pdo_sqlite/sqlite_statement.c b/ext/pdo_sqlite/sqlite_statement.c index 29309ac5f1ff0..ff1650cd30501 100644 --- a/ext/pdo_sqlite/sqlite_statement.c +++ b/ext/pdo_sqlite/sqlite_statement.c @@ -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 { @@ -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); diff --git a/ext/pdo_sqlite/tests/pdo_sqlite_binary.phpt b/ext/pdo_sqlite/tests/pdo_sqlite_binary.phpt new file mode 100644 index 0000000000000..58486e0a5b2fe --- /dev/null +++ b/ext/pdo_sqlite/tests/pdo_sqlite_binary.phpt @@ -0,0 +1,37 @@ +--TEST-- +PDO_sqlite: Test PARAM_BINARY with a BLOB +--EXTENSIONS-- +pdo_sqlite +--FILE-- +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"