diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index a69127c1..b1b6e8ed 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -125,7 +125,7 @@ struct pdo_int_conn_str_func { static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str ) { - SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "Wrong zval type for this keyword" ) + SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "Wrong zval type for this keyword"); std::string val_str = Z_STRVAL_P( value ); @@ -136,6 +136,42 @@ struct pdo_int_conn_str_func { } }; +struct pdo_encrypt_set_func +{ + static void func(_In_ connection_option const* option, _Inout_ zval* value_z, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str) + { + SQLSRV_ASSERT(Z_TYPE_P(value_z) == IS_STRING, "Wrong zval type for this keyword"); + std::string val_str = Z_STRVAL_P(value_z); + std::string whitespaces(" \t\f\v\n\r"); + + // Trim white spaces + std::size_t found = val_str.find_last_not_of(whitespaces); + if (found != std::string::npos) + val_str.erase(found + 1); + + const char TRUE_VALUE_1[] = "true"; + const char TRUE_VALUE_2[] = "1"; + const char FALSE_VALUE_1[] = "false"; + const char FALSE_VALUE_2[] = "0"; + + // For backward compatibility, convert true/1 to yes and false/0 to no + std::string attr; + if (!val_str.compare(TRUE_VALUE_1) || !val_str.compare(TRUE_VALUE_2)) { + attr = "yes"; + } else if (!val_str.compare(FALSE_VALUE_1) || !val_str.compare(FALSE_VALUE_2)) { + attr = "no"; + } else { + // simply pass the attribute value to ODBC driver + attr = val_str; + } + + conn_str += option->odbc_name; + conn_str += "={"; + conn_str += attr; + conn_str += "};"; + } +}; + template struct pdo_int_conn_attr_func { @@ -303,8 +339,8 @@ const connection_option PDO_CONN_OPTS[] = { SQLSRV_CONN_OPTION_ENCRYPT, ODBCConnOptions::Encrypt, sizeof( ODBCConnOptions::Encrypt ), - CONN_ATTR_BOOL, - pdo_bool_conn_str_func::func + CONN_ATTR_MIXED, + pdo_encrypt_set_func::func }, { PDOConnOptionNames::Failover_Partner, diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 4fb2caff..b157a797 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -1124,34 +1124,22 @@ void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval* // Values = ("true" or "1") are treated as true values. Everything else is treated as false. // Returns 1 for true and 0 for false. -size_t core_str_zval_is_true( _Inout_ zval* value_z ) +size_t core_str_zval_is_true(_Inout_ zval* value_z) { SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." ); + std::string val_str = Z_STRVAL_P(value_z); + std::string whitespaces(" \t\f\v\n\r"); - char* value_in = Z_STRVAL_P( value_z ); - size_t val_len = Z_STRLEN_P( value_z ); + // Trim white spaces + std::size_t found = val_str.find_last_not_of(whitespaces); + if (found != std::string::npos) + val_str.erase(found + 1); - // strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8) - size_t last_char = val_len - 1; - while( isspace(( unsigned char )value_in[last_char] )) { - value_in[last_char] = '\0'; - val_len = last_char; - --last_char; + const char TRUE_VALUE_1[] = "true"; + const char TRUE_VALUE_2[] = "1"; + if (!val_str.compare(TRUE_VALUE_1) || !val_str.compare(TRUE_VALUE_2)) { + return 1; // true } - - // save adjustments to the value made by stripping whitespace at the end - Z_STRLEN_P( value_z ) = val_len; - - const char VALID_TRUE_VALUE_1[] = "true"; - const char VALID_TRUE_VALUE_2[] = "1"; - - if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) || - ( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len )) - ) { - - return 1; // true - } - return 0; // false } diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 766b3f45..3bd37ee8 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1215,6 +1215,7 @@ enum CONN_ATTR_TYPE { CONN_ATTR_INT, CONN_ATTR_BOOL, CONN_ATTR_STRING, + CONN_ATTR_MIXED, CONN_ATTR_INVALID, }; diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index d83f4458..1e07be9a 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -55,7 +55,6 @@ struct format_decimals_func struct decimal_places_func { - static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/) { // first check if the input is an integer @@ -73,6 +72,33 @@ struct decimal_places_func } }; +struct srv_encrypt_set_func { + static void func(connection_option const* option, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn, std::string& conn_str) + { + std::string attr; + + if (Z_TYPE_P(value_z) == IS_LONG) { + long val = Z_LVAL_P(value_z); + if (val == 1) { + attr = "yes"; + } else if (val == 0) { + attr = "no"; + } else { + attr = std::to_string(val); + } + } else if (Z_TYPE_P(value_z) == IS_TRUE || Z_TYPE_P(value_z) == IS_FALSE) { + attr = zend_is_true(value_z) ? "yes" : "no"; + } else { + attr = Z_STRVAL_P(value_z); + } + + char temp_str[MAX_CONN_VALSTRING_LEN]; + snprintf(temp_str, MAX_CONN_VALSTRING_LEN, "%s={%s};", option->odbc_name, attr.c_str()); + + conn_str += temp_str; + } +}; + struct conn_char_set_func { static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ ) @@ -421,8 +447,8 @@ const connection_option SS_CONN_OPTS[] = { SQLSRV_CONN_OPTION_ENCRYPT, ODBCConnOptions::Encrypt, sizeof( ODBCConnOptions::Encrypt ), - CONN_ATTR_BOOL, - bool_conn_str_func::func + CONN_ATTR_MIXED, + srv_encrypt_set_func::func }, { SSConnOptionNames::Failover_Partner, @@ -1336,6 +1362,8 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In // if we ever introduce a boolean connection option that maps to a string connection // attribute. break; + case CONN_ATTR_MIXED: + break; case CONN_ATTR_INT: { CHECK_CUSTOM_ERROR( (Z_TYPE_P( value_z ) != IS_LONG ), ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypt_attributes.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypt_attributes.phpt new file mode 100644 index 00000000..be9f472b --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypt_attributes.phpt @@ -0,0 +1,102 @@ +--TEST-- +Test various encrypt attributes +--SKIPIF-- + +--FILE-- +getMessage()); + echo PHP_EOL; +} + +unset($conn1); + +try { + $encrypt = ' 1 '; + $trust = 'true '; + $connectionInfo = "Database = $databaseName; Encrypt = $encrypt; TrustServerCertificate = $trust;"; + $conn2 = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd); + echo 'Test case 2' . PHP_EOL; +} catch (PDOException $e) { + echo 'Failed to connect (test case 2)' . PHP_EOL; + print_r($e->getMessage()); + echo PHP_EOL; +} + +unset($conn2); + +try { + $encrypt = ' yes '; + $trust = 'true'; + $connectionInfo = "Database = $databaseName; Encrypt = $encrypt; TrustServerCertificate = $trust;"; + $conn3 = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd); + echo 'Test case 3' . PHP_EOL; +} catch (PDOException $e) { + echo 'Failed to connect (test case 3)' . PHP_EOL; + print_r($e->getMessage()); + echo PHP_EOL; +} + +unset($conn3); + +try { + $encrypt = ' 0 '; + $trust = 'false '; + $connectionInfo = "Database = $databaseName; Encrypt = $encrypt; TrustServerCertificate = $trust;"; + $conn4 = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd); + echo 'Test case 4' . PHP_EOL; +} catch (PDOException $e) { + echo 'Failed to connect (test case 4)' . PHP_EOL; + print_r($e->getMessage()); + echo PHP_EOL; +} + +unset($conn4); + +try { + $encrypt = ' false '; + $trust = 'false '; + $connectionInfo = "Database = $databaseName; Encrypt = $encrypt; TrustServerCertificate = $trust;"; + $conn5 = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd); + echo 'Test case 5' . PHP_EOL; +} catch (PDOException $e) { + echo 'Failed to connect (test case 5)' . PHP_EOL; + print_r($e->getMessage()); + echo PHP_EOL; +} + +unset($conn5); + +try { + $encrypt = 'no '; + $trust = 'false '; + $connectionInfo = "Database = $databaseName; Encrypt = $encrypt; TrustServerCertificate = $trust;"; + $conn6 = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd); + echo 'Test case 6' . PHP_EOL; +} catch (PDOException $e) { + echo 'Failed to connect (test case 6)' . PHP_EOL; + print_r($e->getMessage()); + echo PHP_EOL; +} + +unset($conn6); +echo 'Done' . PHP_EOL; + +?> +--EXPECT-- +Test case 1 +Test case 2 +Test case 3 +Test case 4 +Test case 5 +Test case 6 +Done diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypt_attributes.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypt_attributes.phpt new file mode 100644 index 00000000..becf3e81 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypt_attributes.phpt @@ -0,0 +1,79 @@ +--TEST-- +Test various encrypt attributes +--SKIPIF-- + +--FILE-- + true, 'TrustServerCertificate' => true); +$conn = sqlsrv_connect($server, $connectionOptions); +if ($conn === false) { + die(print_r(sqlsrv_errors(), true)); +} + +sqlsrv_close($conn); + +echo 'Test case 2' . PHP_EOL; +$connectionOptions = array('Encrypt' => 1, 'TrustServerCertificate' => true); +$conn = sqlsrv_connect($server, $connectionOptions); +if ($conn === false) { + die(print_r(sqlsrv_errors(), true)); +} + +sqlsrv_close($conn); + +echo 'Test case 3' . PHP_EOL; +$connectionOptions = array('Encrypt' => "yes", 'TrustServerCertificate' => true); +$conn = sqlsrv_connect($server, $connectionOptions); +if ($conn === false) { + die(print_r(sqlsrv_errors(), true)); +} + +sqlsrv_close($conn); + +echo 'Test case 4' . PHP_EOL; +$connectionOptions = array('Encrypt' => "no"); +$conn = sqlsrv_connect($server, $connectionOptions); +if ($conn === false) { + die(print_r(sqlsrv_errors(), true)); +} + +sqlsrv_close($conn); + +echo 'Test case 5' . PHP_EOL; +$connectionOptions = array('Encrypt' => false); +$conn = sqlsrv_connect($server, $connectionOptions); +if ($conn === false) { + die(print_r(sqlsrv_errors(), true)); +} + +sqlsrv_close($conn); + +echo 'Test case 6' . PHP_EOL; +$connectionOptions = array('Encrypt' => 0); +$conn = sqlsrv_connect($server, $connectionOptions); +if ($conn === false) { + die(print_r(sqlsrv_errors(), true)); +} + +echo 'Test case 7' . PHP_EOL; +$connectionOptions = array('Encrypt' => 3); +$conn = sqlsrv_connect($server, $connectionOptions); +if ($conn !== false) { + echo 'Expect this to fail' . PHP_EOL; +} + +echo 'Done' . PHP_EOL; + +?> +--EXPECT-- +Test case 1 +Test case 2 +Test case 3 +Test case 4 +Test case 5 +Test case 6 +Test case 7 +Done