From 913d153d61b5174a56954ff697b7d4881e9bfec2 Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Wed, 28 Aug 2024 22:39:27 +0800 Subject: [PATCH] Compatible with PHP 8.4 --- .github/workflows/framework.yml | 4 +- .github/workflows/unit.yml | 2 +- .github/workflows/xdebug.yml | 2 +- config.m4 | 27 +- ext-src/php_swoole_coroutine.h | 1 + ext-src/php_swoole_curl.h | 6 + ext-src/php_swoole_private.h | 4 + ext-src/swoole_coroutine.cc | 22 +- ext-src/swoole_mysql_coro.cc | 6 + ext-src/swoole_runtime.cc | 59 + tests/include/functions.php | 6 +- thirdparty/php/curl/curl_arginfo.h | 8 +- thirdparty/php/curl/curl_interface.h | 2 +- thirdparty/php/curl/curl_private.h | 2 +- thirdparty/php/curl/interface.cc | 2 +- thirdparty/php/curl/multi.cc | 2 +- thirdparty/php/curl/php_curl.h | 2 +- thirdparty/php/streams/plain_wrapper.c | 11 + thirdparty/php83/pdo_oci/oci_driver.c | 2 +- thirdparty/php83/pdo_oci/oci_statement.c | 2 +- thirdparty/php83/pdo_odbc/odbc_driver.c | 2 +- thirdparty/php83/pdo_odbc/odbc_stmt.c | 2 +- thirdparty/php83/pdo_pgsql/pgsql_driver.c | 2 +- thirdparty/php83/pdo_pgsql/pgsql_statement.c | 2 +- thirdparty/php83/pdo_sqlite/sqlite_driver.c | 2 +- .../php83/pdo_sqlite/sqlite_statement.c | 2 +- thirdparty/php84/curl/curl_arginfo.h | 162 + thirdparty/php84/curl/curl_interface.h | 60 + thirdparty/php84/curl/curl_private.h | 167 + thirdparty/php84/curl/interface.cc | 2805 +++++++++++++++++ thirdparty/php84/curl/multi.cc | 638 ++++ thirdparty/php84/curl/php_curl.h | 49 + thirdparty/php84/hash/hash_sha.c | 792 +++++ thirdparty/php84/hash/php_hash.h | 172 + thirdparty/php84/hash/php_hash_sha.h | 95 + thirdparty/php84/pdo_oci/LICENSE | 68 + thirdparty/php84/pdo_oci/oci_driver.c | 883 ++++++ thirdparty/php84/pdo_oci/oci_statement.c | 1044 ++++++ thirdparty/php84/pdo_oci/php_pdo_oci_int.h | 116 + thirdparty/php84/pdo_odbc/odbc_driver.c | 641 ++++ thirdparty/php84/pdo_odbc/odbc_stmt.c | 867 +++++ thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h | 179 ++ thirdparty/php84/pdo_pgsql/pgsql_driver.c | 1459 +++++++++ .../php84/pdo_pgsql/pgsql_driver_arginfo.h | 70 + thirdparty/php84/pdo_pgsql/pgsql_statement.c | 741 +++++ .../php84/pdo_pgsql/php_pdo_pgsql_int.h | 129 + .../php84/pdo_sqlite/php_pdo_sqlite_int.h | 84 + thirdparty/php84/pdo_sqlite/sqlite_driver.c | 825 +++++ .../php84/pdo_sqlite/sqlite_driver.stub.php | 18 + .../php84/pdo_sqlite/sqlite_driver_arginfo.h | 32 + .../php84/pdo_sqlite/sqlite_statement.c | 381 +++ tools/bootstrap.php | 4 +- 52 files changed, 12626 insertions(+), 39 deletions(-) create mode 100644 thirdparty/php84/curl/curl_arginfo.h create mode 100644 thirdparty/php84/curl/curl_interface.h create mode 100644 thirdparty/php84/curl/curl_private.h create mode 100644 thirdparty/php84/curl/interface.cc create mode 100644 thirdparty/php84/curl/multi.cc create mode 100644 thirdparty/php84/curl/php_curl.h create mode 100644 thirdparty/php84/hash/hash_sha.c create mode 100644 thirdparty/php84/hash/php_hash.h create mode 100644 thirdparty/php84/hash/php_hash_sha.h create mode 100644 thirdparty/php84/pdo_oci/LICENSE create mode 100644 thirdparty/php84/pdo_oci/oci_driver.c create mode 100644 thirdparty/php84/pdo_oci/oci_statement.c create mode 100644 thirdparty/php84/pdo_oci/php_pdo_oci_int.h create mode 100644 thirdparty/php84/pdo_odbc/odbc_driver.c create mode 100644 thirdparty/php84/pdo_odbc/odbc_stmt.c create mode 100644 thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h create mode 100644 thirdparty/php84/pdo_pgsql/pgsql_driver.c create mode 100644 thirdparty/php84/pdo_pgsql/pgsql_driver_arginfo.h create mode 100644 thirdparty/php84/pdo_pgsql/pgsql_statement.c create mode 100644 thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h create mode 100644 thirdparty/php84/pdo_sqlite/php_pdo_sqlite_int.h create mode 100644 thirdparty/php84/pdo_sqlite/sqlite_driver.c create mode 100644 thirdparty/php84/pdo_sqlite/sqlite_driver.stub.php create mode 100644 thirdparty/php84/pdo_sqlite/sqlite_driver_arginfo.h create mode 100644 thirdparty/php84/pdo_sqlite/sqlite_statement.c diff --git a/.github/workflows/framework.yml b/.github/workflows/framework.yml index 58e33911c3e..4fa4d7b3eea 100644 --- a/.github/workflows/framework.yml +++ b/.github/workflows/framework.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '8.0', '8.1', '8.2', '8.3' ] + php-version: [ '8.0', '8.1', '8.2', '8.3', '8.4' ] framework: [ 'Laravel Octane', 'Hyperf', 'Simps', 'imi' ] name: ${{ matrix.framework }} - PHP ${{ matrix.php-version }} steps: @@ -105,7 +105,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '8.0', '8.1', '8.2', '8.3' ] + php-version: [ '8.0', '8.1', '8.2', '8.3', '8.4' ] framework: [ 'Simps' ] name: ${{ matrix.framework }} - PHP ${{ matrix.php-version }} - macOS steps: diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index eccdbae5bbb..55f4b64c389 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['8.0', '8.1', '8.2', '8.3'] + php: ['8.0', '8.1', '8.2', '8.3', '8.4-dev'] steps: - uses: actions/checkout@v4 - name: Setup PHP diff --git a/.github/workflows/xdebug.yml b/.github/workflows/xdebug.yml index cc9dc635ae6..02dcb58cbe6 100644 --- a/.github/workflows/xdebug.yml +++ b/.github/workflows/xdebug.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['8.0', '8.1', '8.2', '8.3'] + php: ['8.0', '8.1', '8.2', '8.3', '8.4-dev'] name: PHP ${{ matrix.php }} - Swoole diff --git a/config.m4 b/config.m4 index 1c59606ec42..1a2c2b8a350 100644 --- a/config.m4 +++ b/config.m4 @@ -1014,12 +1014,15 @@ EOF swoole_source_file="$swoole_source_file \ thirdparty/php/curl/interface.cc \ thirdparty/php/curl/multi.cc \ + thirdparty/php84/curl/interface.cc \ + thirdparty/php84/curl/multi.cc \ thirdparty/php/sockets/multicast.cc \ thirdparty/php/sockets/sendrecvmsg.cc \ thirdparty/php/sockets/conversions.cc \ thirdparty/php/sockets/sockaddr_conv.cc \ thirdparty/php/standard/var_decoder.cc \ - thirdparty/php/standard/proc_open.cc" + thirdparty/php/standard/proc_open.cc \ + thirdparty/php84/hash/hash_sha.c " swoole_source_file="$swoole_source_file \ thirdparty/swoole_http_parser.c \ @@ -1051,7 +1054,9 @@ EOF thirdparty/php81/pdo_pgsql/pgsql_driver.c \ thirdparty/php81/pdo_pgsql/pgsql_statement.c \ thirdparty/php83/pdo_pgsql/pgsql_driver.c \ - thirdparty/php83/pdo_pgsql/pgsql_statement.c" + thirdparty/php83/pdo_pgsql/pgsql_statement.c \ + thirdparty/php84/pdo_pgsql/pgsql_driver.c \ + thirdparty/php84/pdo_pgsql/pgsql_statement.c" fi if test "$PHP_SWOOLE_ORACLE" != "no"; then @@ -1061,7 +1066,9 @@ EOF thirdparty/php81/pdo_oci/oci_driver.c \ thirdparty/php81/pdo_oci/oci_statement.c \ thirdparty/php83/pdo_oci/oci_driver.c \ - thirdparty/php83/pdo_oci/oci_statement.c" + thirdparty/php83/pdo_oci/oci_statement.c \ + thirdparty/php84/pdo_oci/oci_driver.c \ + thirdparty/php84/pdo_oci/oci_statement.c" fi if test "$PHP_SWOOLE_ODBC" != "no"; then @@ -1071,7 +1078,9 @@ EOF thirdparty/php81/pdo_odbc/odbc_driver.c \ thirdparty/php81/pdo_odbc/odbc_stmt.c \ thirdparty/php83/pdo_odbc/odbc_driver.c \ - thirdparty/php83/pdo_odbc/odbc_stmt.c" + thirdparty/php83/pdo_odbc/odbc_stmt.c \ + thirdparty/php84/pdo_odbc/odbc_driver.c \ + thirdparty/php84/pdo_odbc/odbc_stmt.c" fi if test "$PHP_SWOOLE_SQLITE" != "no"; then @@ -1081,7 +1090,9 @@ EOF thirdparty/php81/pdo_sqlite/sqlite_driver.c \ thirdparty/php81/pdo_sqlite/sqlite_statement.c \ thirdparty/php83/pdo_sqlite/sqlite_driver.c \ - thirdparty/php83/pdo_sqlite/sqlite_statement.c" + thirdparty/php83/pdo_sqlite/sqlite_statement.c \ + thirdparty/php84/pdo_sqlite/sqlite_driver.c \ + thirdparty/php84/pdo_sqlite/sqlite_statement.c" fi SW_ASM_DIR="thirdparty/boost/asm/" @@ -1232,6 +1243,8 @@ EOF PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/sockets) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/standard) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/curl) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/curl) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/hash) if test "$PHP_NGHTTP2_DIR" = "no"; then PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/nghttp2) fi @@ -1239,20 +1252,24 @@ EOF PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php80/pdo_pgsql) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php81/pdo_pgsql) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php83/pdo_pgsql) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/pdo_pgsql) fi if test "$PHP_SWOOLE_ODBC" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php80/pdo_odbc) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php81/pdo_odbc) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php83/pdo_odbc) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/pdo_odbc) fi if test "$PHP_SWOOLE_ORACLE" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php80/pdo_oci) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php81/pdo_oci) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php83/pdo_oci) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/pdo_oci) fi if test "$PHP_SWOOLE_SQLITE" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php80/pdo_sqlite) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php81/pdo_sqlite) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php83/pdo_sqlite) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/pdo_sqlite) fi fi diff --git a/ext-src/php_swoole_coroutine.h b/ext-src/php_swoole_coroutine.h index aebb5c94429..c3a6dff40a3 100644 --- a/ext-src/php_swoole_coroutine.h +++ b/ext-src/php_swoole_coroutine.h @@ -36,6 +36,7 @@ #define SW_DEFAULT_MAX_CORO_NUM 100000 #define SW_DEFAULT_PHP_STACK_PAGE_SIZE 8192 +enum sw_exit_flags { SW_EXIT_IN_COROUTINE = 1 << 1, SW_EXIT_IN_SERVER = 1 << 2 }; #define SWOG ((zend_output_globals *) &OG(handlers)) diff --git a/ext-src/php_swoole_curl.h b/ext-src/php_swoole_curl.h index 4977d95b1ae..2dd3b666c28 100644 --- a/ext-src/php_swoole_curl.h +++ b/ext-src/php_swoole_curl.h @@ -24,7 +24,13 @@ SW_EXTERN_C_BEGIN #include #include +#ifdef SW_USE_CURL +#if PHP_VERSION_ID >= 80400 +#include "thirdparty/php84/curl/curl_private.h" +#else #include "thirdparty/php/curl/curl_private.h" +#endif +#endif SW_EXTERN_C_END #if LIBCURL_VERSION_NUM < 0x073800 diff --git a/ext-src/php_swoole_private.h b/ext-src/php_swoole_private.h index ed861839396..5b765fb5894 100644 --- a/ext-src/php_swoole_private.h +++ b/ext-src/php_swoole_private.h @@ -182,6 +182,10 @@ extern zend_object_handlers swoole_exception_handlers; extern zend_class_entry *swoole_error_ce; extern zend_class_entry *swoole_resolve_context_ce; extern zend_object_handlers swoole_resolve_context_handlers; +#if PHP_VERSION_ID >= 80400 +extern zend_class_entry *swoole_exit_exception_ce; +extern zend_object_handlers swoole_exit_exception_handlers; +#endif PHP_FUNCTION(swoole_clear_dns_cache); PHP_FUNCTION(swoole_last_error); diff --git a/ext-src/swoole_coroutine.cc b/ext-src/swoole_coroutine.cc index e0e9d27799b..8a308e7d8cf 100644 --- a/ext-src/swoole_coroutine.cc +++ b/ext-src/swoole_coroutine.cc @@ -20,7 +20,9 @@ #include "php_swoole_cxx.h" #include "php_swoole_coroutine_system.h" +#if PHP_VERSION_ID < 80400 #include "swoole_server.h" +#endif #include "swoole_signal.h" #include "zend_builtin_functions.h" @@ -55,8 +57,6 @@ static zend_always_inline zend_vm_stack zend_vm_stack_new_page(size_t size, zend } #endif -enum sw_exit_flags { SW_EXIT_IN_COROUTINE = 1 << 1, SW_EXIT_IN_SERVER = 1 << 2 }; - bool PHPCoroutine::activated = false; zend_array *PHPCoroutine::options = nullptr; @@ -73,8 +73,11 @@ bool PHPCoroutine::interrupt_thread_running = false; extern void php_swoole_load_library(); -static zend_atomic_bool *zend_vm_interrupt = nullptr; +#if PHP_VERSION_ID < 80400 static user_opcode_handler_t ori_exit_handler = nullptr; +#endif + +static zend_atomic_bool *zend_vm_interrupt = nullptr; static user_opcode_handler_t ori_begin_silence_handler = nullptr; static user_opcode_handler_t ori_end_silence_handler = nullptr; static unordered_map user_yield_coros; @@ -82,10 +85,15 @@ static unordered_map user_yield_coros; static void (*orig_interrupt_function)(zend_execute_data *execute_data) = nullptr; static zend_class_entry *swoole_coroutine_util_ce; -static zend_class_entry *swoole_exit_exception_ce; -static zend_object_handlers swoole_exit_exception_handlers; static zend_class_entry *swoole_coroutine_iterator_ce; static zend_class_entry *swoole_coroutine_context_ce; +#if PHP_VERSION_ID >= 80400 +zend_class_entry *swoole_exit_exception_ce; +zend_object_handlers swoole_exit_exception_handlers; +#else +static zend_class_entry *swoole_exit_exception_ce; +static zend_object_handlers swoole_exit_exception_handlers; +#endif SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_coroutine, exists); @@ -180,6 +188,7 @@ static const zend_function_entry swoole_exit_exception_methods[] = { }; // clang-format on +#if PHP_VERSION_ID < 80400 static int coro_exit_handler(zend_execute_data *execute_data) { zval ex; zend_object *obj; @@ -224,6 +233,7 @@ static int coro_exit_handler(zend_execute_data *execute_data) { return ZEND_USER_OPCODE_DISPATCH; } +#endif static int coro_begin_silence_handler(zend_execute_data *execute_data) { PHPContext *task = PHPCoroutine::get_context(); @@ -989,8 +999,10 @@ void php_swoole_coroutine_minit(int module_number) { void php_swoole_coroutine_rinit() { if (SWOOLE_G(cli)) { +#if PHP_VERSION_ID < 80400 ori_exit_handler = zend_get_user_opcode_handler(ZEND_EXIT); zend_set_user_opcode_handler(ZEND_EXIT, coro_exit_handler); +#endif ori_begin_silence_handler = zend_get_user_opcode_handler(ZEND_BEGIN_SILENCE); zend_set_user_opcode_handler(ZEND_BEGIN_SILENCE, coro_begin_silence_handler); diff --git a/ext-src/swoole_mysql_coro.cc b/ext-src/swoole_mysql_coro.cc index c388de802b1..8b5683050f8 100644 --- a/ext-src/swoole_mysql_coro.cc +++ b/ext-src/swoole_mysql_coro.cc @@ -26,8 +26,14 @@ #undef L64 SW_EXTERN_C_BEGIN + +#if PHP_VERSION_ID >= 80400 +#include "thirdparty/php84/hash/php_hash.h" +#include "thirdparty/php84/hash/php_hash_sha.h" +#else #include "ext/hash/php_hash.h" #include "ext/hash/php_hash_sha.h" +#endif #include "ext/standard/php_math.h" #ifdef SW_USE_MYSQLND #include "ext/mysqlnd/mysqlnd.h" diff --git a/ext-src/swoole_runtime.cc b/ext-src/swoole_runtime.cc index a7856f401b4..9596a8c0727 100644 --- a/ext-src/swoole_runtime.cc +++ b/ext-src/swoole_runtime.cc @@ -18,10 +18,18 @@ #include "swoole_socket.h" #include "swoole_util.h" +#if PHP_VERSION_ID >= 80400 +#include "swoole_server.h" +#endif + #include "thirdparty/php/standard/proc_open.h" #ifdef SW_USE_CURL +#if PHP_VERSION_ID >= 80400 +#include "thirdparty/php84/curl/curl_interface.h" +#else #include "thirdparty/php/curl/curl_interface.h" #endif +#endif #include @@ -76,6 +84,9 @@ static PHP_FUNCTION(swoole_time_sleep_until); static PHP_FUNCTION(swoole_stream_select); static PHP_FUNCTION(swoole_stream_socket_pair); static PHP_FUNCTION(swoole_user_func_handler); +#if PHP_VERSION_ID >= 80400 +static PHP_FUNCTION(swoole_exit); +#endif SW_EXTERN_C_END static void inherit_class(const char *child_name, size_t child_length, const char *parent_name, size_t parent_length); @@ -233,6 +244,9 @@ struct real_func { void php_swoole_runtime_rinit() { tmp_function_table = (zend_array *) emalloc(sizeof(zend_array)); zend_hash_init(tmp_function_table, 8, nullptr, nullptr, 0); +#if PHP_VERSION_ID >= 80400 + SW_HOOK_FUNC(exit); +#endif } void php_swoole_runtime_rshutdown() { @@ -2120,3 +2134,48 @@ static void clear_class_entries() { } child_class_entries.clear(); } + +#if PHP_VERSION_ID >= 80400 +static PHP_FUNCTION(swoole_exit) { + zend_string *message = NULL; + zend_long status = 0; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STR_OR_LONG(message, status) + ZEND_PARSE_PARAMETERS_END(); + + zend_long flags = 0; + if (Coroutine::get_current()) { + flags |= SW_EXIT_IN_COROUTINE; + } + + if (sw_server() && sw_server()->is_started()) { + flags |= SW_EXIT_IN_SERVER; + } + + if (flags) { + zval ex = {}; + zend_object *obj = zend_throw_exception(swoole_exit_exception_ce, (message ? ZSTR_VAL(message) : "swoole exit"), 0); + ZVAL_OBJ(&ex, obj); + zend_update_property_long(swoole_exit_exception_ce, SW_Z8_OBJ_P(&ex), ZEND_STRL("flags"), flags); + zend_update_property_long(swoole_exit_exception_ce, SW_Z8_OBJ_P(&ex), ZEND_STRL("status"), status); + } else { + if (message) { + size_t len = ZSTR_LEN(message); + if (len != 0) { + /* An exception might be emitted by an output handler */ + zend_write(ZSTR_VAL(message), len); + if (EG(exception)) { + RETURN_THROWS(); + } + } + } else { + EG(exit_status) = status; + } + + ZEND_ASSERT(!EG(exception)); + zend_throw_unwind_exit(); + } +} +#endif diff --git a/tests/include/functions.php b/tests/include/functions.php index 4dd94504cad..d22c9ae39c4 100644 --- a/tests/include/functions.php +++ b/tests/include/functions.php @@ -380,7 +380,7 @@ function get_big_random(int $length = 1024 * 1024) return str_repeat(get_safe_random(1024), $length / 1024); } -function makeCoTcpClient($host, $port, callable $onConnect = null, callable $onReceive = null) +function makeCoTcpClient($host, $port, ?callable $onConnect = null, ?callable $onReceive = null) { go(function () use ($host, $port, $onConnect, $onReceive) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); @@ -457,7 +457,7 @@ function killself_in_syncmode($lifetime = 1000, $sig = SIGKILL) * @param callable $cb * @return mixed */ -function suicide($lifetime, $sig = SIGKILL, callable $cb = null) +function suicide($lifetime, $sig = SIGKILL, ?callable $cb = null) { return Swoole\Timer::after($lifetime, function () use ($lifetime, $sig, $cb) { if ($cb) { @@ -676,7 +676,7 @@ function php_fork_exec(callable $fn, $f_stdout = "/dev/null", $f_stderr = null) * @param array|null $env env * @return array [out, err] */ -function spawn_exec($cmd, $input = null, $tv_sec = null, $tv_usec = null, $cwd = null, array $env = null) +function spawn_exec($cmd, $input = null, $tv_sec = null, $tv_usec = null, $cwd = null, ?array $env = null) { $out = $err = null; $winOpt = ['suppress_errors' => true, 'binary_pipes' => true]; diff --git a/thirdparty/php/curl/curl_arginfo.h b/thirdparty/php/curl/curl_arginfo.h index 383716f0816..8b259fc9fe5 100644 --- a/thirdparty/php/curl/curl_arginfo.h +++ b/thirdparty/php/curl/curl_arginfo.h @@ -3,7 +3,7 @@ #include "curl_interface.h" -#ifdef SW_USE_CURL +#if defined(SW_USE_CURL) && PHP_VERSION_ID < 80400 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_close, 0, 1, IS_VOID, 0) ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) @@ -32,12 +32,6 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_swoole_native_curl_exec, 0, 1, M ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_swoole_native_curl_file_create, 0, 1, CURLFile, 0) - ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mime_type, IS_STRING, 1, "null") - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, posted_filename, IS_STRING, 1, "null") -ZEND_END_ARG_INFO() - ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_getinfo, 0, 1, IS_MIXED, 0) ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, option, IS_LONG, 1, "null") diff --git a/thirdparty/php/curl/curl_interface.h b/thirdparty/php/curl/curl_interface.h index cdfcf44aebd..f65e86f9962 100644 --- a/thirdparty/php/curl/curl_interface.h +++ b/thirdparty/php/curl/curl_interface.h @@ -2,7 +2,7 @@ #include "php_swoole_cxx.h" -#ifdef SW_USE_CURL +#if defined(SW_USE_CURL) && PHP_VERSION_ID < 80400 SW_EXTERN_C_BEGIN #include diff --git a/thirdparty/php/curl/curl_private.h b/thirdparty/php/curl/curl_private.h index b8be8932b59..c2a393961aa 100644 --- a/thirdparty/php/curl/curl_private.h +++ b/thirdparty/php/curl/curl_private.h @@ -17,7 +17,7 @@ /* Copied from PHP-4f68662f5b61aecf90f6d8005976f5f91d4ce8d3 */ -#ifdef SW_USE_CURL +#if defined(SW_USE_CURL) && PHP_VERSION_ID < 80400 #ifndef _PHP_CURL_PRIVATE_H #define _PHP_CURL_PRIVATE_H diff --git a/thirdparty/php/curl/interface.cc b/thirdparty/php/curl/interface.cc index a70a63ee3ac..bffc1859465 100644 --- a/thirdparty/php/curl/interface.cc +++ b/thirdparty/php/curl/interface.cc @@ -19,7 +19,7 @@ #include "php_swoole_cxx.h" -#ifdef SW_USE_CURL +#if defined(SW_USE_CURL) && PHP_VERSION_ID < 80400 #include "php_swoole_curl.h" using namespace swoole; diff --git a/thirdparty/php/curl/multi.cc b/thirdparty/php/curl/multi.cc index 52f6ccb6931..abf997c0b9b 100644 --- a/thirdparty/php/curl/multi.cc +++ b/thirdparty/php/curl/multi.cc @@ -17,7 +17,7 @@ #include "php_swoole_cxx.h" #include "zend_object_handlers.h" -#ifdef SW_USE_CURL +#if defined(SW_USE_CURL) && PHP_VERSION_ID < 80400 #include "php_swoole_curl.h" using swoole::curl::Multi; diff --git a/thirdparty/php/curl/php_curl.h b/thirdparty/php/curl/php_curl.h index 046f6b79223..2075384155b 100644 --- a/thirdparty/php/curl/php_curl.h +++ b/thirdparty/php/curl/php_curl.h @@ -19,7 +19,7 @@ /* Copied from PHP-7.4.11 */ -#ifdef SW_USE_CURL +#if defined(SW_USE_CURL) && PHP_VERSION_ID < 80400 #ifndef _PHP_CURL_H #define _PHP_CURL_H diff --git a/thirdparty/php/streams/plain_wrapper.c b/thirdparty/php/streams/plain_wrapper.c index c283f85b930..28c61ef9022 100644 --- a/thirdparty/php/streams/plain_wrapper.c +++ b/thirdparty/php/streams/plain_wrapper.c @@ -21,12 +21,23 @@ #include "ext/standard/php_filestat.h" #include + +#if PHP_VERSION_ID >= 80400 +#ifdef HAVE_SYS_WAIT_H +#include +#endif +#ifdef HAVE_SYS_FILE_H +#include +#endif +#else #if HAVE_SYS_WAIT_H #include #endif #if HAVE_SYS_FILE_H #include #endif +#endif + #ifdef HAVE_SYS_MMAN_H #include #endif diff --git a/thirdparty/php83/pdo_oci/oci_driver.c b/thirdparty/php83/pdo_oci/oci_driver.c index d1ca6d69c75..fb6aa6864fe 100644 --- a/thirdparty/php83/pdo_oci/oci_driver.c +++ b/thirdparty/php83/pdo_oci/oci_driver.c @@ -17,7 +17,7 @@ #define SW_USE_ORACLE_HOOK #include "php_swoole_oracle.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php83/pdo_oci/oci_statement.c b/thirdparty/php83/pdo_oci/oci_statement.c index d048a91217c..11e8e0c5630 100644 --- a/thirdparty/php83/pdo_oci/oci_statement.c +++ b/thirdparty/php83/pdo_oci/oci_statement.c @@ -17,7 +17,7 @@ #define SW_USE_ORACLE_HOOK #include "php_swoole_oracle.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php83/pdo_odbc/odbc_driver.c b/thirdparty/php83/pdo_odbc/odbc_driver.c index a5e3a8b4f0c..5c7cbe76890 100644 --- a/thirdparty/php83/pdo_odbc/odbc_driver.c +++ b/thirdparty/php83/pdo_odbc/odbc_driver.c @@ -17,7 +17,7 @@ #define SW_USE_ODBC_HOOK #include "php_swoole_odbc.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php83/pdo_odbc/odbc_stmt.c b/thirdparty/php83/pdo_odbc/odbc_stmt.c index c8eef615784..7df3c70ca0a 100644 --- a/thirdparty/php83/pdo_odbc/odbc_stmt.c +++ b/thirdparty/php83/pdo_odbc/odbc_stmt.c @@ -17,7 +17,7 @@ #define SW_USE_ODBC_HOOK #include "php_swoole_odbc.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php83/pdo_pgsql/pgsql_driver.c b/thirdparty/php83/pdo_pgsql/pgsql_driver.c index 73e610674a4..86b05d37fc6 100644 --- a/thirdparty/php83/pdo_pgsql/pgsql_driver.c +++ b/thirdparty/php83/pdo_pgsql/pgsql_driver.c @@ -19,7 +19,7 @@ #define SW_USE_PGSQL_HOOK #include "php_swoole_pgsql.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php83/pdo_pgsql/pgsql_statement.c b/thirdparty/php83/pdo_pgsql/pgsql_statement.c index 13532fe3659..ae4e7e16fea 100644 --- a/thirdparty/php83/pdo_pgsql/pgsql_statement.c +++ b/thirdparty/php83/pdo_pgsql/pgsql_statement.c @@ -19,7 +19,7 @@ #define SW_USE_PGSQL_HOOK #include "php_swoole_pgsql.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php83/pdo_sqlite/sqlite_driver.c b/thirdparty/php83/pdo_sqlite/sqlite_driver.c index e130d779f9c..10f48849e53 100644 --- a/thirdparty/php83/pdo_sqlite/sqlite_driver.c +++ b/thirdparty/php83/pdo_sqlite/sqlite_driver.c @@ -17,7 +17,7 @@ #include "php_swoole_sqlite.h" #include "php_swoole_call_stack.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" diff --git a/thirdparty/php83/pdo_sqlite/sqlite_statement.c b/thirdparty/php83/pdo_sqlite/sqlite_statement.c index 52fa33b2abb..bf8978f1134 100644 --- a/thirdparty/php83/pdo_sqlite/sqlite_statement.c +++ b/thirdparty/php83/pdo_sqlite/sqlite_statement.c @@ -17,7 +17,7 @@ #define SW_USE_SQLITE_HOOK #include "php_swoole_sqlite.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" diff --git a/thirdparty/php84/curl/curl_arginfo.h b/thirdparty/php84/curl/curl_arginfo.h new file mode 100644 index 00000000000..956c405c654 --- /dev/null +++ b/thirdparty/php84/curl/curl_arginfo.h @@ -0,0 +1,162 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: ddfcdd8a0bf0ee6c338ec1689c6de5d7fd87303d */ +#include "curl_interface.h" + +#if defined(SW_USE_CURL) && PHP_VERSION_ID >= 80400 +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_close, 0, 1, IS_VOID, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_swoole_native_curl_copy_handle, 0, 1, CurlHandle, MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_errno, 0, 1, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_error, 0, 1, IS_STRING, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_swoole_native_curl_escape, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) + ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_swoole_native_curl_unescape arginfo_swoole_native_curl_escape + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_setopt, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) + ZEND_ARG_TYPE_INFO(0, option, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_swoole_native_curl_exec, 0, 1, MAY_BE_STRING|MAY_BE_BOOL) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_getinfo, 0, 1, IS_MIXED, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, option, IS_LONG, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_swoole_native_curl_init, 0, 0, CurlHandle, MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, url, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_upkeep, 0, 1, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_add_handle, 0, 2, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_close, 0, 1, IS_VOID, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_errno, 0, 1, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_exec, 0, 2, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) + ZEND_ARG_INFO(1, still_running) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_getcontent, 0, 1, IS_STRING, 1) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_swoole_native_curl_multi_info_read, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) + ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, queued_messages, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_swoole_native_curl_multi_init, 0, 0, CurlMultiHandle, 0) +ZEND_END_ARG_INFO() + +#define arginfo_swoole_native_curl_multi_remove_handle arginfo_swoole_native_curl_multi_add_handle + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_select, 0, 1, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_DOUBLE, 0, "1.0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_strerror, 0, 1, IS_STRING, 1) + ZEND_ARG_TYPE_INFO(0, error_code, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_pause, 0, 2, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) + ZEND_ARG_TYPE_INFO(0, flags, IS_LONG, 0) +ZEND_END_ARG_INFO() + +#define arginfo_swoole_native_curl_reset arginfo_swoole_native_curl_close + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_setopt_array, 0, 2, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) + ZEND_ARG_TYPE_INFO(0, options, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_setopt, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) + ZEND_ARG_TYPE_INFO(0, option, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +#define arginfo_swoole_native_curl_strerror arginfo_swoole_native_curl_multi_strerror + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_swoole_native_curl_version, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + +static const zend_function_entry swoole_native_curl_functions[] = { + ZEND_FE(swoole_native_curl_close, arginfo_swoole_native_curl_close) + ZEND_FE(swoole_native_curl_copy_handle, arginfo_swoole_native_curl_copy_handle) + ZEND_FE(swoole_native_curl_errno, arginfo_swoole_native_curl_errno) + ZEND_FE(swoole_native_curl_error, arginfo_swoole_native_curl_error) + ZEND_FE(swoole_native_curl_escape, arginfo_swoole_native_curl_escape) + ZEND_FE(swoole_native_curl_unescape, arginfo_swoole_native_curl_unescape) + ZEND_FE(swoole_native_curl_multi_setopt, arginfo_swoole_native_curl_multi_setopt) + ZEND_FE(swoole_native_curl_exec, arginfo_swoole_native_curl_exec) + ZEND_FE(swoole_native_curl_getinfo, arginfo_swoole_native_curl_getinfo) + ZEND_FE(swoole_native_curl_init, arginfo_swoole_native_curl_init) +#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ + ZEND_FE(swoole_native_curl_upkeep, arginfo_swoole_native_curl_upkeep) +#endif + ZEND_FE(swoole_native_curl_multi_add_handle, arginfo_swoole_native_curl_multi_add_handle) + ZEND_FE(swoole_native_curl_multi_close, arginfo_swoole_native_curl_multi_close) + ZEND_FE(swoole_native_curl_multi_errno, arginfo_swoole_native_curl_multi_errno) + ZEND_FE(swoole_native_curl_multi_exec, arginfo_swoole_native_curl_multi_exec) + ZEND_FE(swoole_native_curl_multi_getcontent, arginfo_swoole_native_curl_multi_getcontent) + ZEND_FE(swoole_native_curl_multi_info_read, arginfo_swoole_native_curl_multi_info_read) + ZEND_FE(swoole_native_curl_multi_init, arginfo_swoole_native_curl_multi_init) + ZEND_FE(swoole_native_curl_multi_remove_handle, arginfo_swoole_native_curl_multi_remove_handle) + ZEND_FE(swoole_native_curl_multi_select, arginfo_swoole_native_curl_multi_select) + ZEND_FE(swoole_native_curl_multi_strerror, arginfo_swoole_native_curl_multi_strerror) + ZEND_FE(swoole_native_curl_pause, arginfo_swoole_native_curl_pause) + ZEND_FE(swoole_native_curl_reset, arginfo_swoole_native_curl_reset) + ZEND_FE(swoole_native_curl_setopt_array, arginfo_swoole_native_curl_setopt_array) + ZEND_FE(swoole_native_curl_setopt, arginfo_swoole_native_curl_setopt) + ZEND_FE(swoole_native_curl_strerror, arginfo_swoole_native_curl_strerror) + ZEND_FE(swoole_native_curl_version, arginfo_swoole_native_curl_version) + ZEND_FE_END +}; + +static const zend_function_entry class_CurlHandle_methods[] = { + ZEND_FE_END +}; + +static const zend_function_entry class_CurlMultiHandle_methods[] = { + ZEND_FE_END +}; + +static const zend_function_entry class_CurlShareHandle_methods[] = { + ZEND_FE_END +}; +#endif diff --git a/thirdparty/php84/curl/curl_interface.h b/thirdparty/php84/curl/curl_interface.h new file mode 100644 index 00000000000..11d9f1ef175 --- /dev/null +++ b/thirdparty/php84/curl/curl_interface.h @@ -0,0 +1,60 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2018 The Swoole Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.0 of the Apache license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.apache.org/licenses/LICENSE-2.0.html | + | If you did not receive a copy of the Apache2.0 license and are unable| + | to obtain it through the world-wide-web, please send a note to | + | license@swoole.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: NathanFreeman | + +----------------------------------------------------------------------+ +*/ +#pragma once + +#include "php_swoole_cxx.h" +#if defined(SW_USE_CURL) && PHP_VERSION_ID >= 80400 +SW_EXTERN_C_BEGIN + +#include +#include + +void swoole_native_curl_minit(int module_number); +void swoole_native_curl_mshutdown(); + +ZEND_FUNCTION(swoole_native_curl_close); +ZEND_FUNCTION(swoole_native_curl_copy_handle); +ZEND_FUNCTION(swoole_native_curl_errno); +ZEND_FUNCTION(swoole_native_curl_error); +ZEND_FUNCTION(swoole_native_curl_escape); +ZEND_FUNCTION(swoole_native_curl_unescape); +ZEND_FUNCTION(swoole_native_curl_multi_setopt); +ZEND_FUNCTION(swoole_native_curl_exec); +ZEND_FUNCTION(swoole_native_curl_getinfo); +ZEND_FUNCTION(swoole_native_curl_init); +#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +ZEND_FUNCTION(swoole_native_curl_upkeep); +#endif +ZEND_FUNCTION(swoole_native_curl_multi_add_handle); +ZEND_FUNCTION(swoole_native_curl_multi_close); +ZEND_FUNCTION(swoole_native_curl_multi_errno); +ZEND_FUNCTION(swoole_native_curl_multi_exec); +ZEND_FUNCTION(swoole_native_curl_multi_getcontent); +ZEND_FUNCTION(swoole_native_curl_multi_info_read); +ZEND_FUNCTION(swoole_native_curl_multi_init); +ZEND_FUNCTION(swoole_native_curl_multi_remove_handle); +ZEND_FUNCTION(swoole_native_curl_multi_select); +ZEND_FUNCTION(swoole_native_curl_multi_strerror); +ZEND_FUNCTION(swoole_native_curl_pause); +ZEND_FUNCTION(swoole_native_curl_reset); +ZEND_FUNCTION(swoole_native_curl_setopt_array); +ZEND_FUNCTION(swoole_native_curl_setopt); +ZEND_FUNCTION(swoole_native_curl_strerror); +ZEND_FUNCTION(swoole_native_curl_version); +SW_EXTERN_C_END +#endif diff --git a/thirdparty/php84/curl/curl_private.h b/thirdparty/php84/curl/curl_private.h new file mode 100644 index 00000000000..4890ed1df29 --- /dev/null +++ b/thirdparty/php84/curl/curl_private.h @@ -0,0 +1,167 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sterling Hughes | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#if defined(SW_USE_CURL) && PHP_VERSION_ID >= 80400 + +#ifndef _PHP_CURL_PRIVATE_H +#define _PHP_CURL_PRIVATE_H + +#include "php_curl.h" + +#define PHP_CURL_DEBUG 0 + +#include "php_version.h" +#define PHP_CURL_VERSION PHP_VERSION + +#include +#include + +#define CURLOPT_RETURNTRANSFER 19913 +#define CURLOPT_BINARYTRANSFER 19914 /* For Backward compatibility */ +#define PHP_CURL_STDOUT 0 +#define PHP_CURL_FILE 1 +#define PHP_CURL_USER 2 +#define PHP_CURL_DIRECT 3 +#define PHP_CURL_RETURN 4 +#define PHP_CURL_IGNORE 7 + +#define SAVE_CURL_ERROR(__handle, __err) \ + do { \ + (__handle)->err.no = (int) __err; \ + } while (0) + +PHP_MINIT_FUNCTION(curl); +PHP_MSHUTDOWN_FUNCTION(curl); +PHP_MINFO_FUNCTION(curl); + +typedef struct { + zend_fcall_info_cache fcc; + FILE *fp; + smart_str buf; + int method; + zval stream; +} php_curl_write; + +typedef struct { + zend_fcall_info_cache fcc; + FILE *fp; + zend_resource *res; + int method; + zval stream; +} php_curl_read; + +typedef struct { + php_curl_write *write; + php_curl_write *write_header; + php_curl_read *read; + zval std_err; + zend_fcall_info_cache progress; + zend_fcall_info_cache xferinfo; + zend_fcall_info_cache fnmatch; +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + zend_fcall_info_cache sshhostkey; +#endif +} php_curl_handlers; + +struct _php_curl_error { + char str[CURL_ERROR_SIZE + 1]; + int no; +}; + +struct _php_curl_send_headers { + zend_string *str; +}; + +struct _php_curl_free { + zend_llist post; + zend_llist stream; + HashTable *slist; +}; + +typedef struct { + CURL *cp; + php_curl_handlers handlers; + struct _php_curl_free *to_free; + struct _php_curl_send_headers header; + struct _php_curl_error err; + bool in_callback; + uint32_t *clone; + zval postfields; + /* For CURLOPT_PRIVATE */ + zval private_data; + /* CurlShareHandle object set using CURLOPT_SHARE. */ + struct _php_curlsh *share; + zend_object std; +} php_curl; + +#define CURLOPT_SAFE_UPLOAD -1 + +typedef struct { + zend_fcall_info_cache server_push; +} php_curlm_handlers; + +namespace swoole { +namespace curl { +class Multi; +} +} // namespace swoole + +using swoole::curl::Multi; + +typedef struct { + Multi *multi; + zend_llist easyh; + php_curlm_handlers handlers; + struct { + int no; + } err; + zend_object std; +} php_curlm; + +typedef struct _php_curlsh { + CURLSH *share; + struct { + int no; + } err; + zend_object std; +} php_curlsh; + +php_curl *swoole_curl_init_handle_into_zval(zval *curl); +void swoole_curl_init_handle(php_curl *ch); +void swoole_curl_cleanup_handle(php_curl *); +void swoole_curl_multi_cleanup_list(void *data); +void swoole_curl_verify_handlers(php_curl *ch, bool reporterror); +void swoole_setup_easy_copy_handlers(php_curl *ch, php_curl *source); + +static inline php_curl *curl_from_obj(zend_object *obj) { + return (php_curl *) ((char *) (obj) -XtOffsetOf(php_curl, std)); +} + +#define Z_CURL_P(zv) curl_from_obj(Z_OBJ_P(zv)) + +static inline php_curlsh *curl_share_from_obj(zend_object *obj) { + return (php_curlsh *) ((char *) (obj) -XtOffsetOf(php_curlsh, std)); +} + +#define Z_CURL_SHARE_P(zv) curl_share_from_obj(Z_OBJ_P(zv)) +void curl_multi_register_class(const zend_function_entry *method_entries); +zend_result swoole_curl_cast_object(zend_object *obj, zval *result, int type); + +php_curl *swoole_curl_get_handle(zval *zid, bool exclusive = true, bool required = true); + +#endif /* _PHP_CURL_PRIVATE_H */ +#endif diff --git a/thirdparty/php84/curl/interface.cc b/thirdparty/php84/curl/interface.cc new file mode 100644 index 00000000000..19afb0285dc --- /dev/null +++ b/thirdparty/php84/curl/interface.cc @@ -0,0 +1,2805 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sterling Hughes | + +----------------------------------------------------------------------+ +*/ + +#include "php_swoole_cxx.h" + +#if defined(SW_USE_CURL) && PHP_VERSION_ID >= 80400 +#include "php_swoole_curl.h" +using namespace swoole; + +SW_EXTERN_C_BEGIN +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS +#include "curl_interface.h" +#include "curl_arginfo.h" + +#include +#include + +#ifdef PHP_WIN32 +#include +#include +#endif + +#include +#include + +/* As of curl 7.11.1 this is no longer defined inside curl.h */ +#ifndef HttpPost +#define HttpPost curl_httppost +#endif + +/* {{{ cruft for thread safe SSL crypto locks */ +#if defined(ZTS) && defined(HAVE_CURL_OLD_OPENSSL) +#if defined(HAVE_OPENSSL_CRYPTO_H) +#define PHP_CURL_NEED_OPENSSL_TSL +#include +#else +#warning "libcurl was compiled with OpenSSL support, but configure could not find " \ + "openssl/crypto.h; thus no SSL crypto locking callbacks will be set, which may " \ + "cause random crashes on SSL requests" +#endif +#endif /* ZTS && HAVE_CURL_OLD_OPENSSL */ +/* }}} */ + +#include "zend_smart_str.h" +#include "ext/standard/info.h" +#include "ext/standard/file.h" +#include "ext/standard/url.h" +#include "curl_private.h" + +#ifdef __GNUC__ +/* don't complain about deprecated CURLOPT_* we're exposing to PHP; we + need to keep using those to avoid breaking PHP API compatibiltiy */ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef PHP_CURL_NEED_OPENSSL_TSL /* {{{ */ +static MUTEX_T *php_curl_openssl_tsl = NULL; + +/* Locking callbacks are no longer used since OpenSSL 1.1. Mark the functions as unused to + * avoid warnings due to this. */ +static ZEND_ATTRIBUTE_UNUSED void php_curl_ssl_lock(int mode, int n, const char *file, int line) { + if (mode & CRYPTO_LOCK) { + tsrm_mutex_lock(php_curl_openssl_tsl[n]); + } else { + tsrm_mutex_unlock(php_curl_openssl_tsl[n]); + } +} + +static ZEND_ATTRIBUTE_UNUSED unsigned long php_curl_ssl_id(void) { + return (unsigned long) tsrm_thread_id(); +} +#endif +/* }}} */ + +static zend_class_entry *swoole_native_curl_exception_ce; +static zend_object_handlers swoole_native_curl_exception_handlers; + +#define CAAL(s, v) add_assoc_long_ex(return_value, s, sizeof(s) - 1, (zend_long) v); +#define CAAD(s, v) add_assoc_double_ex(return_value, s, sizeof(s) - 1, (double) v); +#define CAAS(s, v) add_assoc_string_ex(return_value, s, sizeof(s) - 1, (char *) (v ? v : "")); +#define CAASTR(s, v) add_assoc_str_ex(return_value, s, sizeof(s) - 1, v ? zend_string_copy(v) : ZSTR_EMPTY_ALLOC()); +#define CAAZ(s, v) add_assoc_zval_ex(return_value, s, sizeof(s) - 1, (zval *) v); + +#if defined(PHP_WIN32) || defined(__GNUC__) +#define php_curl_ret(__ret) \ + RETVAL_FALSE; \ + return __ret; +#else +#define php_curl_ret(__ret) \ + RETVAL_FALSE; \ + return; +#endif + +php_curl *swoole_curl_get_handle(zval *zid, bool exclusive, bool required) { + php_curl *ch = Z_CURL_P(zid); + if (SWOOLE_G(req_status) == PHP_SWOOLE_RSHUTDOWN_END) { + exclusive = false; + } + if (exclusive && swoole_coroutine_is_in()) { + auto handle = swoole::curl::get_handle(ch->cp); + if (handle && handle->multi && handle->multi->check_bound_co() == nullptr) { + return nullptr; + } + } + return ch; +} + +static zend_result php_curl_option_str(php_curl *ch, zend_long option, const char *str, const size_t len) { + if (zend_char_has_nul_byte(str, len)) { + zend_value_error("%s(): cURL option must not contain any null bytes", get_active_function_name()); + return FAILURE; + } + + CURLcode error = curl_easy_setopt(ch->cp, (CURLoption) option, str); + SAVE_CURL_ERROR(ch, error); + + return error == CURLE_OK ? SUCCESS : FAILURE; +} + +static zend_result php_curl_option_url(php_curl *ch, const zend_string *url) /* {{{ */ +{ + /* Disable file:// if open_basedir are used */ + if (PG(open_basedir) && *PG(open_basedir)) { + curl_easy_setopt(ch->cp, CURLOPT_PROTOCOLS, CURLPROTO_ALL & ~CURLPROTO_FILE); + } + +#ifdef PHP_WIN32 + if (zend_string_starts_with_literal_ci(url, "file://") && '/' != ZSTR_VAL(url)[sizeof("file://") - 1] && + ZSTR_LEN(url) < MAXPATHLEN - 2) { + char _tmp[MAXPATHLEN] = {0}; + + memmove(_tmp, "file:///", sizeof("file:///") - 1); + memmove(_tmp + sizeof("file:///") - 1, + ZSTR_VAL(url) + sizeof("file://") - 1, + ZSTR_LEN(url) - sizeof("file://") + 1); + + return php_curl_option_str(ch, CURLOPT_URL, _tmp, ZSTR_LEN(url) + 1); + } +#endif + + return php_curl_option_str(ch, CURLOPT_URL, ZSTR_VAL(url), ZSTR_LEN(url)); +} +/* }}} */ + +void swoole_curl_verify_handlers(php_curl *ch, bool reporterror) /* {{{ */ +{ + php_stream *stream; + + ZEND_ASSERT(ch); + + if (!Z_ISUNDEF(ch->handlers.std_err)) { + stream = (php_stream *) zend_fetch_resource2_ex( + &ch->handlers.std_err, NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + if (reporterror) { + php_error_docref(NULL, E_WARNING, "CURLOPT_STDERR resource has gone away, resetting to stderr"); + } + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_UNDEF(&ch->handlers.std_err); + + curl_easy_setopt(ch->cp, CURLOPT_STDERR, stderr); + } + } + if (ch->handlers.read && !Z_ISUNDEF(ch->handlers.read->stream)) { + stream = (php_stream *) zend_fetch_resource2_ex( + &ch->handlers.read->stream, NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + if (reporterror) { + php_error_docref(NULL, E_WARNING, "CURLOPT_INFILE resource has gone away, resetting to default"); + } + zval_ptr_dtor(&ch->handlers.read->stream); + ZVAL_UNDEF(&ch->handlers.read->stream); + ch->handlers.read->res = NULL; + ch->handlers.read->fp = 0; + + curl_easy_setopt(ch->cp, CURLOPT_INFILE, (void *) ch); + } + } + if (ch->handlers.write_header && !Z_ISUNDEF(ch->handlers.write_header->stream)) { + stream = (php_stream *) zend_fetch_resource2_ex( + &ch->handlers.write_header->stream, NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + if (reporterror) { + php_error_docref(NULL, E_WARNING, "CURLOPT_WRITEHEADER resource has gone away, resetting to default"); + } + zval_ptr_dtor(&ch->handlers.write_header->stream); + ZVAL_UNDEF(&ch->handlers.write_header->stream); + ch->handlers.write_header->fp = 0; + + ch->handlers.write_header->method = PHP_CURL_IGNORE; + curl_easy_setopt(ch->cp, CURLOPT_WRITEHEADER, (void *) ch); + } + } + if (ch->handlers.write && !Z_ISUNDEF(ch->handlers.write->stream)) { + stream = (php_stream *) zend_fetch_resource2_ex( + &ch->handlers.write->stream, NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + if (reporterror) { + php_error_docref(NULL, E_WARNING, "CURLOPT_FILE resource has gone away, resetting to default"); + } + zval_ptr_dtor(&ch->handlers.write->stream); + ZVAL_UNDEF(&ch->handlers.write->stream); + ch->handlers.write->fp = 0; + + ch->handlers.write->method = PHP_CURL_STDOUT; + curl_easy_setopt(ch->cp, CURLOPT_FILE, (void *) ch); + } + } + return; +} +/* }}} */ + +/* CurlHandle class */ +static const zend_function_entry swoole_coroutine_curl_handle_methods[] = {ZEND_FE_END}; + +zend_class_entry *swoole_coroutine_curl_handle_ce; +static zend_object_handlers swoole_coroutine_curl_handle_handlers; + +static zend_object *swoole_curl_create_object(zend_class_entry *class_type); +static void swoole_curl_free_obj(zend_object *object); +static HashTable *swoole_curl_get_gc(zend_object *object, zval **table, int *n); +static zend_function *swoole_curl_get_constructor(zend_object *object); +static zend_object *swoole_curl_clone_obj(zend_object *object); +static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields); +SW_EXTERN_C_END + +void swoole_native_curl_minit(int module_number) { + if (!SWOOLE_G(cli)) { + return; + } + +#ifdef PHP_CURL_NEED_OPENSSL_TSL + if (!CRYPTO_get_id_callback()) { + int i, c = CRYPTO_num_locks(); + + php_curl_openssl_tsl = malloc(c * sizeof(MUTEX_T)); + if (!php_curl_openssl_tsl) { + return; + } + + for (i = 0; i < c; ++i) { + php_curl_openssl_tsl[i] = tsrm_mutex_alloc(); + } + + CRYPTO_set_id_callback(php_curl_ssl_id); + CRYPTO_set_locking_callback(php_curl_ssl_lock); + } +#endif + + swoole_coroutine_curl_handle_ce = curl_ce; + swoole_coroutine_curl_handle_ce->create_object = swoole_curl_create_object; + memcpy(&swoole_coroutine_curl_handle_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + swoole_coroutine_curl_handle_handlers.offset = XtOffsetOf(php_curl, std); + swoole_coroutine_curl_handle_handlers.free_obj = swoole_curl_free_obj; + swoole_coroutine_curl_handle_handlers.get_gc = swoole_curl_get_gc; + swoole_coroutine_curl_handle_handlers.get_constructor = swoole_curl_get_constructor; + swoole_coroutine_curl_handle_handlers.clone_obj = swoole_curl_clone_obj; + swoole_coroutine_curl_handle_handlers.cast_object = swoole_curl_cast_object; + swoole_coroutine_curl_handle_handlers.compare = [](zval *o1, zval *o2) { return ZEND_UNCOMPARABLE; }; + + swoole_coroutine_curl_handle_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NO_DYNAMIC_PROPERTIES; + zend_declare_property_null(swoole_coroutine_curl_handle_ce, ZEND_STRL("private_data"), ZEND_ACC_PUBLIC); + + curl_multi_register_class(nullptr); + + zend_unregister_functions(swoole_native_curl_functions, -1, CG(function_table)); + zend_register_functions(NULL, swoole_native_curl_functions, NULL, MODULE_PERSISTENT); + + SW_INIT_CLASS_ENTRY_EX(swoole_native_curl_exception, + "Swoole\\Coroutine\\Curl\\Exception", + "Co\\Coroutine\\Curl\\Exception", + nullptr, + swoole_exception); +} +/* }}} */ + +/* CurlHandle class */ + +static zend_object *swoole_curl_create_object(zend_class_entry *class_type) { + php_curl *intern = (php_curl *) zend_object_alloc(sizeof(php_curl), class_type); + + zend_object_std_init(&intern->std, class_type); + object_properties_init(&intern->std, class_type); + intern->std.handlers = &swoole_coroutine_curl_handle_handlers; + + return &intern->std; +} + +static zend_function *swoole_curl_get_constructor(zend_object *object) { + zend_throw_error(NULL, "Cannot directly construct CurlHandle, use curl_init() instead"); + return NULL; +} + +static zend_object *swoole_curl_clone_obj(zend_object *object) { + php_curl *ch; + CURL *cp; + zval *postfields; + zend_object *clone_object; + php_curl *clone_ch; + + clone_object = swoole_curl_create_object(curl_ce); + clone_ch = curl_from_obj(clone_object); + swoole_curl_init_handle(clone_ch); + + ch = curl_from_obj(object); + cp = curl_easy_duphandle(ch->cp); + if (!cp) { + zend_throw_exception(NULL, "Failed to clone CurlHandle", 0); + return &clone_ch->std; + } + + clone_ch->cp = cp; + swoole_setup_easy_copy_handlers(clone_ch, ch); + swoole::curl::create_handle(clone_ch->cp); + + postfields = &clone_ch->postfields; + if (Z_TYPE_P(postfields) != IS_UNDEF) { + if (build_mime_structure_from_hash(clone_ch, postfields) == FAILURE) { + zend_throw_exception(NULL, "Failed to clone CurlHandle", 0); + return &clone_ch->std; + } + } + + return &clone_ch->std; +} + +static HashTable *swoole_curl_get_gc(zend_object *object, zval **table, int *n) { + php_curl *curl = curl_from_obj(object); + + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + + zend_get_gc_buffer_add_zval(gc_buffer, &curl->postfields); + if (curl->handlers.read) { + if (ZEND_FCC_INITIALIZED(curl->handlers.read->fcc)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.read->fcc); + } + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.read->stream); + } + + if (curl->handlers.write) { + if (ZEND_FCC_INITIALIZED(curl->handlers.write->fcc)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.write->fcc); + } + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.write->stream); + } + + if (curl->handlers.write_header) { + if (ZEND_FCC_INITIALIZED(curl->handlers.write_header->fcc)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.write_header->fcc); + } + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.write_header->stream); + } + + if (ZEND_FCC_INITIALIZED(curl->handlers.progress)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.progress); + } + + if (ZEND_FCC_INITIALIZED(curl->handlers.xferinfo)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.xferinfo); + } + + if (ZEND_FCC_INITIALIZED(curl->handlers.fnmatch)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.fnmatch); + } + +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + if (ZEND_FCC_INITIALIZED(curl->handlers.sshhostkey)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.sshhostkey); + } +#endif + + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.std_err); + zend_get_gc_buffer_add_zval(gc_buffer, &curl->private_data); + + zend_get_gc_buffer_use(gc_buffer, table, n); + + return zend_std_get_properties(object); +} + +zend_result swoole_curl_cast_object(zend_object *obj, zval *result, int type) { + if (type == IS_LONG) { + /* For better backward compatibility, make (int) $curl_handle return the object ID, + * similar to how it previously returned the resource ID. */ + ZVAL_LONG(result, obj->handle); + return SUCCESS; + } + + return zend_std_cast_object_tostring(obj, result, type); +} + +void swoole_native_curl_mshutdown() {} + +/* {{{ curl_write */ +static size_t fn_write(char *data, size_t size, size_t nmemb, void *ctx) { + php_curl *ch = (php_curl *) ctx; + php_curl_write *write_handler = ch->handlers.write; + size_t length = size * nmemb; + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_write() called\n"); + fprintf(stderr, "data = %s, size = %d, nmemb = %d, ctx = %x\n", data, size, nmemb, ctx); +#endif + + switch (write_handler->method) { + case PHP_CURL_STDOUT: + PHPWRITE(data, length); + break; + case PHP_CURL_FILE: + return fwrite(data, size, nmemb, write_handler->fp); + case PHP_CURL_RETURN: + if (length > 0) { + smart_str_appendl(&write_handler->buf, data, (int) length); + } + break; + case PHP_CURL_USER: { + zval argv[2]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_STRINGL(&argv[1], data, length); + + ch->in_callback = true; + zend_call_known_fcc(&write_handler->fcc, &retval, /* param_count */ 2, argv, /* named_params */ NULL); + ch->in_callback = false; + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + /* TODO Check callback returns an int or something castable to int */ + length = zval_get_long(&retval); + } + + zval_ptr_dtor(&argv[0]); + zval_ptr_dtor(&argv[1]); + break; + } + } + + return length; +} +/* }}} */ + +/* {{{ curl_fnmatch */ +static int fn_fnmatch(void *ctx, const char *pattern, const char *string) { + php_curl *ch = (php_curl *) ctx; + int rval = CURL_FNMATCHFUNC_FAIL; + zval argv[3]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_STRING(&argv[1], pattern); + ZVAL_STRING(&argv[2], string); + + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.fnmatch, &retval, /* param_count */ 3, argv, /* named_params */ NULL); + ch->in_callback = false; + + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + /* TODO Check callback returns an int or something castable to int */ + rval = zval_get_long(&retval); + } + zval_ptr_dtor(&argv[0]); + zval_ptr_dtor(&argv[1]); + zval_ptr_dtor(&argv[2]); + return rval; +} +/* }}} */ + +/* {{{ curl_progress */ +static size_t fn_progress(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) { + php_curl *ch = (php_curl *) clientp; + size_t rval = 0; + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_progress() called\n"); + fprintf(stderr, + "clientp = %x, dltotal = %f, dlnow = %f, ultotal = %f, ulnow = %f\n", + clientp, + dltotal, + dlnow, + ultotal, + ulnow); +#endif + + zval args[5]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&args[0], &ch->std); + ZVAL_LONG(&args[1], (zend_long) dltotal); + ZVAL_LONG(&args[2], (zend_long) dlnow); + ZVAL_LONG(&args[3], (zend_long) ultotal); + ZVAL_LONG(&args[4], (zend_long) ulnow); + + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.progress, &retval, /* param_count */ 5, args, /* named_params */ NULL); + ch->in_callback = false; + + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + /* TODO Check callback returns an int or something castable to int */ + if (0 != zval_get_long(&retval)) { + rval = 1; + } + } + + zval_ptr_dtor(&args[0]); + return rval; +} +/* }}} */ + +/* {{{ curl_xferinfo */ +static size_t fn_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + php_curl *ch = (php_curl *) clientp; + size_t rval = 0; + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_xferinfo() called\n"); + fprintf(stderr, + "clientp = %x, dltotal = %ld, dlnow = %ld, ultotal = %ld, ulnow = %ld\n", + clientp, + dltotal, + dlnow, + ultotal, + ulnow); +#endif + + zval argv[5]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_LONG(&argv[1], dltotal); + ZVAL_LONG(&argv[2], dlnow); + ZVAL_LONG(&argv[3], ultotal); + ZVAL_LONG(&argv[4], ulnow); + + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.xferinfo, &retval, /* param_count */ 5, argv, /* named_params */ NULL); + ch->in_callback = false; + + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + /* TODO Check callback returns an int or something castable to int */ + if (0 != zval_get_long(&retval)) { + rval = 1; + } + } + + zval_ptr_dtor(&argv[0]); + return rval; +} +/* }}} */ + +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ +static int fn_ssh_hostkeyfunction(void *clientp, int keytype, const char *key, size_t keylen) { + php_curl *ch = (php_curl *) clientp; + int rval = CURLKHMATCH_MISMATCH; /* cancel connection in case of an exception */ + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_ssh_hostkeyfunction() called\n"); + fprintf(stderr, "clientp = %x, keytype = %d, key = %s, keylen = %zu\n", clientp, keytype, key, keylen); +#endif + + zval args[4]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&args[0], &ch->std); + ZVAL_LONG(&args[1], keytype); + ZVAL_STRINGL(&args[2], key, keylen); + ZVAL_LONG(&args[3], keylen); + + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.sshhostkey, &retval, /* param_count */ 4, args, /* named_params */ NULL); + ch->in_callback = false; + + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + if (Z_TYPE(retval) == IS_LONG) { + zend_long retval_long = Z_LVAL(retval); + if (retval_long == CURLKHMATCH_OK || retval_long == CURLKHMATCH_MISMATCH) { + rval = retval_long; + } else { + zend_throw_error(NULL, + "The CURLOPT_SSH_HOSTKEYFUNCTION callback must return either CURLKHMATCH_OK or " + "CURLKHMATCH_MISMATCH"); + } + } else { + zend_throw_error( + NULL, + "The CURLOPT_SSH_HOSTKEYFUNCTION callback must return either CURLKHMATCH_OK or CURLKHMATCH_MISMATCH"); + } + } + + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&args[2]); + return rval; +} +#endif + +/* {{{ curl_read */ +static size_t fn_read(char *data, size_t size, size_t nmemb, void *ctx) { + php_curl *ch = (php_curl *) ctx; + php_curl_read *read_handler = ch->handlers.read; + int length = 0; + + switch (read_handler->method) { + case PHP_CURL_DIRECT: + if (read_handler->fp) { + length = fread(data, size, nmemb, read_handler->fp); + } + break; + case PHP_CURL_USER: { + zval argv[3]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + if (read_handler->res) { + GC_ADDREF(read_handler->res); + ZVAL_RES(&argv[1], read_handler->res); + } else { + ZVAL_NULL(&argv[1]); + } + ZVAL_LONG(&argv[2], (int) size * nmemb); + + ch->in_callback = true; + zend_call_known_fcc(&read_handler->fcc, &retval, /* param_count */ 3, argv, /* named_params */ NULL); + ch->in_callback = false; + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + if (Z_TYPE(retval) == IS_STRING) { + length = MIN((size * nmemb), Z_STRLEN(retval)); + memcpy(data, Z_STRVAL(retval), length); + } else if (Z_TYPE(retval) == IS_LONG) { + length = Z_LVAL_P(&retval); + } + // TODO Do type error if invalid type? + zval_ptr_dtor(&retval); + } + + zval_ptr_dtor(&argv[0]); + zval_ptr_dtor(&argv[1]); + break; + } + } + + return length; +} +/* }}} */ + +/* {{{ curl_write_header */ +static size_t fn_write_header(char *data, size_t size, size_t nmemb, void *ctx) { + php_curl *ch = (php_curl *) ctx; + php_curl_write *write_handler = ch->handlers.write_header; + size_t length = size * nmemb; + + switch (write_handler->method) { + case PHP_CURL_STDOUT: + /* Handle special case write when we're returning the entire transfer + */ + if (ch->handlers.write->method == PHP_CURL_RETURN && length > 0) { + smart_str_appendl(&ch->handlers.write->buf, data, (int) length); + } else { + PHPWRITE(data, length); + } + break; + case PHP_CURL_FILE: + return fwrite(data, size, nmemb, write_handler->fp); + case PHP_CURL_USER: { + zval argv[2]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_STRINGL(&argv[1], data, length); + + ch->in_callback = true; + zend_call_known_fcc(&write_handler->fcc, &retval, /* param_count */ 2, argv, /* named_params */ NULL); + ch->in_callback = false; + if (!Z_ISUNDEF(retval)) { + // TODO: Check for valid int type for return value + swoole_curl_verify_handlers(ch, /* reporterror */ true); + length = zval_get_long(&retval); + } + zval_ptr_dtor(&argv[0]); + zval_ptr_dtor(&argv[1]); + break; + } + + case PHP_CURL_IGNORE: + return length; + + default: + return -1; + } + + return length; +} +/* }}} */ + +static int curl_debug(CURL *cp, curl_infotype type, char *buf, size_t buf_len, void *ctx) /* {{{ */ +{ + php_curl *ch = (php_curl *) ctx; + + if (type == CURLINFO_HEADER_OUT) { + if (ch->header.str) { + zend_string_release_ex(ch->header.str, 0); + } + ch->header.str = zend_string_init(buf, buf_len, 0); + } + + return 0; +} +/* }}} */ + +/* {{{ curl_free_post */ +static void curl_free_post(void **post) { + curl_mime_free((curl_mime *) *post); +} +/* }}} */ + +struct mime_data_cb_arg { + zend_string *filename; + php_stream *stream; +}; + +/* {{{ curl_free_cb_arg */ +static void curl_free_cb_arg(void **cb_arg_p) { + struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) *cb_arg_p; + + ZEND_ASSERT(cb_arg->stream == NULL); + zend_string_release(cb_arg->filename); + efree(cb_arg); +} +/* }}} */ + +/* {{{ curl_free_slist */ +static void curl_free_slist(zval *el) { + curl_slist_free_all(((struct curl_slist *) Z_PTR_P(el))); +} +/* }}} */ + +/* {{{ Return cURL version information. */ +PHP_FUNCTION(swoole_native_curl_version) { + curl_version_info_data *d; + + ZEND_PARSE_PARAMETERS_NONE(); + + d = curl_version_info(CURLVERSION_NOW); + if (d == NULL) { + RETURN_FALSE; + } + + array_init(return_value); + + CAAL("version_number", d->version_num); + CAAL("age", d->age); + CAAL("features", d->features); + /* Add an array of features */ + { + struct feat { + const char *name; + int bitmask; + }; + + unsigned int i; + zval feature_list; + array_init(&feature_list); + + /* Sync this list with PHP_MINFO_FUNCTION(curl) as well */ + static const struct feat feats[] = { + {"AsynchDNS", CURL_VERSION_ASYNCHDNS}, + {"CharConv", CURL_VERSION_CONV}, + {"Debug", CURL_VERSION_DEBUG}, + {"GSS-Negotiate", CURL_VERSION_GSSNEGOTIATE}, + {"IDN", CURL_VERSION_IDN}, + {"IPv6", CURL_VERSION_IPV6}, + {"krb4", CURL_VERSION_KERBEROS4}, + {"Largefile", CURL_VERSION_LARGEFILE}, + {"libz", CURL_VERSION_LIBZ}, + {"NTLM", CURL_VERSION_NTLM}, + {"NTLMWB", CURL_VERSION_NTLM_WB}, + {"SPNEGO", CURL_VERSION_SPNEGO}, + {"SSL", CURL_VERSION_SSL}, + {"SSPI", CURL_VERSION_SSPI}, + {"TLS-SRP", CURL_VERSION_TLSAUTH_SRP}, + {"HTTP2", CURL_VERSION_HTTP2}, + {"GSSAPI", CURL_VERSION_GSSAPI}, + {"KERBEROS5", CURL_VERSION_KERBEROS5}, + {"UNIX_SOCKETS", CURL_VERSION_UNIX_SOCKETS}, + {"PSL", CURL_VERSION_PSL}, + {"HTTPS_PROXY", CURL_VERSION_HTTPS_PROXY}, + {"MULTI_SSL", CURL_VERSION_MULTI_SSL}, + {"BROTLI", CURL_VERSION_BROTLI}, +#if LIBCURL_VERSION_NUM >= 0x074001 /* Available since 7.64.1 */ + {"ALTSVC", CURL_VERSION_ALTSVC}, +#endif +#if LIBCURL_VERSION_NUM >= 0x074200 /* Available since 7.66.0 */ + {"HTTP3", CURL_VERSION_HTTP3}, +#endif +#if LIBCURL_VERSION_NUM >= 0x074800 /* Available since 7.72.0 */ + {"UNICODE", CURL_VERSION_UNICODE}, + {"ZSTD", CURL_VERSION_ZSTD}, +#endif +#if LIBCURL_VERSION_NUM >= 0x074a00 /* Available since 7.74.0 */ + {"HSTS", CURL_VERSION_HSTS}, +#endif +#if LIBCURL_VERSION_NUM >= 0x074c00 /* Available since 7.76.0 */ + {"GSASL", CURL_VERSION_GSASL}, +#endif + }; + + for (i = 0; i < sizeof(feats) / sizeof(feats[0]); i++) { + if (feats[i].name) { + add_assoc_bool(&feature_list, feats[i].name, d->features & feats[i].bitmask ? true : false); + } + } + + CAAZ("feature_list", &feature_list); + } + CAAL("ssl_version_number", d->ssl_version_num); + CAAS("version", d->version); + CAAS("host", d->host); + CAAS("ssl_version", d->ssl_version); + CAAS("libz_version", d->libz_version); + /* Add an array of protocols */ + { + char **p = (char **) d->protocols; + zval protocol_list; + + array_init(&protocol_list); + + while (*p != NULL) { + add_next_index_string(&protocol_list, *p); + p++; + } + CAAZ("protocols", &protocol_list); + } + if (d->age >= 1) { + CAAS("ares", d->ares); + CAAL("ares_num", d->ares_num); + } + if (d->age >= 2) { + CAAS("libidn", d->libidn); + } + if (d->age >= 3) { + CAAL("iconv_ver_num", d->iconv_ver_num); + CAAS("libssh_version", d->libssh_version); + } + if (d->age >= 4) { + CAAL("brotli_ver_num", d->brotli_ver_num); + CAAS("brotli_version", d->brotli_version); + } +} +/* }}} */ + +php_curl *swoole_curl_init_handle_into_zval(zval *curl) { + php_curl *ch; + + object_init_ex(curl, swoole_coroutine_curl_handle_ce); + ch = Z_CURL_P(curl); + + swoole_curl_init_handle(ch); + + return ch; +} + +void swoole_curl_init_handle(php_curl *ch) { + ch->to_free = (struct _php_curl_free *) ecalloc(1, sizeof(struct _php_curl_free)); + ch->handlers.write = (php_curl_write *) ecalloc(1, sizeof(php_curl_write)); + ch->handlers.write_header = (php_curl_write *) ecalloc(1, sizeof(php_curl_write)); + ch->handlers.read = (php_curl_read *) ecalloc(1, sizeof(php_curl_read)); + ch->handlers.progress = empty_fcall_info_cache; + ch->handlers.xferinfo = empty_fcall_info_cache; + ch->handlers.fnmatch = empty_fcall_info_cache; +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + ch->handlers.sshhostkey = empty_fcall_info_cache; +#endif + ch->clone = (uint32_t *) emalloc(sizeof(uint32_t)); + *ch->clone = 1; + + memset(&ch->err, 0, sizeof(struct _php_curl_error)); + + zend_llist_init(&ch->to_free->post, sizeof(struct HttpPost *), (llist_dtor_func_t) curl_free_post, 0); + zend_llist_init(&ch->to_free->stream, sizeof(struct mime_data_cb_arg *), (llist_dtor_func_t) curl_free_cb_arg, 0); + + ch->to_free->slist = (HashTable *) emalloc(sizeof(HashTable)); + zend_hash_init(ch->to_free->slist, 4, NULL, curl_free_slist, 0); + ZVAL_UNDEF(&ch->postfields); +} + +/* }}} */ + +/* {{{ create_certinfo */ +static void create_certinfo(struct curl_certinfo *ci, zval *listcode) { + int i; + + if (ci) { + zval certhash; + + for (i = 0; i < ci->num_of_certs; i++) { + struct curl_slist *slist; + + array_init(&certhash); + for (slist = ci->certinfo[i]; slist; slist = slist->next) { + int len; + char s[64]; + char *tmp; + strncpy(s, slist->data, sizeof(s)); + s[sizeof(s) - 1] = '\0'; + tmp = (char *) memchr(s, ':', sizeof(s)); + if (tmp) { + *tmp = '\0'; + len = strlen(s); + add_assoc_string(&certhash, s, &slist->data[len + 1]); + } else { + php_error_docref(NULL, E_WARNING, "Could not extract hash key from certificate info"); + } + } + add_next_index_zval(listcode, &certhash); + } + } +} +/* }}} */ + +/* {{{ _php_curl_set_default_options() + Set default options for a handle */ +static void _php_curl_set_default_options(php_curl *ch) { + char *cainfo; + + curl_easy_setopt(ch->cp, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 0); + curl_easy_setopt(ch->cp, CURLOPT_ERRORBUFFER, ch->err.str); + curl_easy_setopt(ch->cp, CURLOPT_WRITEFUNCTION, fn_write); + curl_easy_setopt(ch->cp, CURLOPT_FILE, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_READFUNCTION, fn_read); + curl_easy_setopt(ch->cp, CURLOPT_INFILE, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_HEADERFUNCTION, fn_write_header); + curl_easy_setopt(ch->cp, CURLOPT_WRITEHEADER, (void *) ch); +#ifndef ZTS + curl_easy_setopt(ch->cp, CURLOPT_DNS_USE_GLOBAL_CACHE, 1); +#endif + curl_easy_setopt(ch->cp, CURLOPT_DNS_CACHE_TIMEOUT, 120); + curl_easy_setopt(ch->cp, CURLOPT_MAXREDIRS, 20); /* prevent infinite redirects */ + + cainfo = INI_STR("openssl.cafile"); + if (!(cainfo && cainfo[0] != '\0')) { + cainfo = INI_STR("curl.cainfo"); + } + if (cainfo && cainfo[0] != '\0') { + curl_easy_setopt(ch->cp, CURLOPT_CAINFO, cainfo); + } + +#ifdef ZTS + curl_easy_setopt(ch->cp, CURLOPT_NOSIGNAL, 1); +#endif +} +/* }}} */ + +/* {{{ Initialize a cURL session */ +PHP_FUNCTION(swoole_native_curl_init) { + php_curl *ch; + CURL *cp; + zend_string *url = NULL; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STR_OR_NULL(url) + ZEND_PARSE_PARAMETERS_END(); + + cp = curl_easy_init(); + if (!cp) { + php_error_docref(NULL, E_WARNING, "Could not initialize a new cURL handle"); + RETURN_FALSE; + } + + ch = swoole_curl_init_handle_into_zval(return_value); + + ch->cp = cp; + + ch->handlers.write->method = PHP_CURL_STDOUT; + ch->handlers.read->method = PHP_CURL_DIRECT; + ch->handlers.write_header->method = PHP_CURL_IGNORE; + + _php_curl_set_default_options(ch); + swoole::curl::create_handle(cp); + + if (url) { + if (php_curl_option_url(ch, url) == FAILURE) { + zval_ptr_dtor(return_value); + RETURN_FALSE; + } + } +} +/* }}} */ + +static void php_curl_copy_fcc_with_option(php_curl *ch, + CURLoption option, + zend_fcall_info_cache *target_fcc, + zend_fcall_info_cache *source_fcc) { + if (ZEND_FCC_INITIALIZED(*source_fcc)) { + zend_fcc_dup(target_fcc, source_fcc); + curl_easy_setopt(ch->cp, (CURLoption) option, (void *) ch); + } +} + +void swoole_setup_easy_copy_handlers(php_curl *ch, php_curl *source) { + if (!Z_ISUNDEF(source->handlers.write->stream)) { + Z_ADDREF(source->handlers.write->stream); + } + ch->handlers.write->stream = source->handlers.write->stream; + ch->handlers.write->method = source->handlers.write->method; + if (!Z_ISUNDEF(source->handlers.read->stream)) { + Z_ADDREF(source->handlers.read->stream); + } + ch->handlers.read->stream = source->handlers.read->stream; + ch->handlers.read->method = source->handlers.read->method; + ch->handlers.write_header->method = source->handlers.write_header->method; + if (!Z_ISUNDEF(source->handlers.write_header->stream)) { + Z_ADDREF(source->handlers.write_header->stream); + } + ch->handlers.write_header->stream = source->handlers.write_header->stream; + + ch->handlers.write->fp = source->handlers.write->fp; + ch->handlers.write_header->fp = source->handlers.write_header->fp; + ch->handlers.read->fp = source->handlers.read->fp; + ch->handlers.read->res = source->handlers.read->res; + + if (ZEND_FCC_INITIALIZED(source->handlers.read->fcc)) { + zend_fcc_dup(&source->handlers.read->fcc, &source->handlers.read->fcc); + } + if (ZEND_FCC_INITIALIZED(source->handlers.write->fcc)) { + zend_fcc_dup(&source->handlers.write->fcc, &source->handlers.write->fcc); + } + if (ZEND_FCC_INITIALIZED(source->handlers.write_header->fcc)) { + zend_fcc_dup(&source->handlers.write_header->fcc, &source->handlers.write_header->fcc); + } + + curl_easy_setopt(ch->cp, CURLOPT_ERRORBUFFER, ch->err.str); + curl_easy_setopt(ch->cp, CURLOPT_FILE, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_INFILE, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_WRITEHEADER, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_DEBUGDATA, (void *) ch); + + php_curl_copy_fcc_with_option(ch, CURLOPT_PROGRESSDATA, &ch->handlers.progress, &source->handlers.progress); + php_curl_copy_fcc_with_option(ch, CURLOPT_XFERINFODATA, &ch->handlers.xferinfo, &source->handlers.xferinfo); + php_curl_copy_fcc_with_option(ch, CURLOPT_FNMATCH_DATA, &ch->handlers.fnmatch, &source->handlers.fnmatch); +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + php_curl_copy_fcc_with_option(ch, CURLOPT_SSH_HOSTKEYDATA, &ch->handlers.sshhostkey, &source->handlers.sshhostkey); +#endif + + ZVAL_COPY(&ch->private_data, &source->private_data); + + efree(ch->to_free->slist); + efree(ch->to_free); + ch->to_free = source->to_free; + efree(ch->clone); + ch->clone = source->clone; + + /* Keep track of cloned copies to avoid invoking curl destructors for every clone */ + (*source->clone)++; +} + +static size_t read_cb(char *buffer, size_t size, size_t nitems, void *arg) /* {{{ */ +{ + struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; + ssize_t numread; + + if (cb_arg->stream == NULL) { + if (!(cb_arg->stream = php_stream_open_wrapper(ZSTR_VAL(cb_arg->filename), "rb", IGNORE_PATH, NULL))) { + return CURL_READFUNC_ABORT; + } + } + numread = php_stream_read(cb_arg->stream, buffer, nitems * size); + if (numread < 0) { + php_stream_close(cb_arg->stream); + cb_arg->stream = NULL; + return CURL_READFUNC_ABORT; + } + return numread; +} +/* }}} */ + +static int seek_cb(void *arg, curl_off_t offset, int origin) /* {{{ */ +{ + struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; + int res; + + if (cb_arg->stream == NULL) { + return CURL_SEEKFUNC_CANTSEEK; + } + res = php_stream_seek(cb_arg->stream, offset, origin); + return res == SUCCESS ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK; +} +/* }}} */ + +static void free_cb(void *arg) /* {{{ */ +{ + struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; + + if (cb_arg->stream != NULL) { + php_stream_close(cb_arg->stream); + cb_arg->stream = NULL; + } +} +/* }}} */ + +static inline CURLcode add_simple_field(curl_mime *mime, zend_string *string_key, zval *current) { + CURLcode error = CURLE_OK; + curl_mimepart *part; + CURLcode form_error; + zend_string *postval, *tmp_postval; + + postval = zval_get_tmp_string(current, &tmp_postval); + + part = curl_mime_addpart(mime); + if (part == NULL) { + zend_tmp_string_release(tmp_postval); + zend_string_release_ex(string_key, 0); + return CURLE_OUT_OF_MEMORY; + } + if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || + (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK) { + error = form_error; + } + + zend_tmp_string_release(tmp_postval); + + return error; +} + +static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields) /* {{{ */ +{ + HashTable *postfields = Z_ARRVAL_P(zpostfields); + CURLcode error = CURLE_OK; + zval *current; + zend_string *string_key; + zend_ulong num_key; + curl_mime *mime = NULL; + curl_mimepart *part; + CURLcode form_error; + + if (zend_hash_num_elements(postfields) > 0) { + mime = curl_mime_init(ch->cp); + if (mime == NULL) { + return FAILURE; + } + } + + ZEND_HASH_FOREACH_KEY_VAL(postfields, num_key, string_key, current) { + zend_string *postval; + /* Pretend we have a string_key here */ + if (!string_key) { + string_key = zend_long_to_str(num_key); + } else { + zend_string_addref(string_key); + } + + ZVAL_DEREF(current); + if (Z_TYPE_P(current) == IS_OBJECT && instanceof_function(Z_OBJCE_P(current), curl_CURLFile_class)) { + /* new-style file upload */ + zval *prop, rv; + char *type = NULL, *filename = NULL; + struct mime_data_cb_arg *cb_arg; + php_stream *stream; + php_stream_statbuf ssb; + size_t filesize = -1; + curl_seek_callback seekfunc = seek_cb; + + prop = zend_read_property_ex( + curl_CURLFile_class, Z_OBJ_P(current), ZSTR_KNOWN(ZEND_STR_NAME), /* silent */ false, &rv); + ZVAL_DEREF(prop); + if (Z_TYPE_P(prop) != IS_STRING) { + php_error_docref(NULL, E_WARNING, "Invalid filename for key %s", ZSTR_VAL(string_key)); + } else { + postval = Z_STR_P(prop); + + if (php_check_open_basedir(ZSTR_VAL(postval))) { + return FAILURE; + } + + prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "mime", sizeof("mime") - 1, 0, &rv); + ZVAL_DEREF(prop); + if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) { + type = Z_STRVAL_P(prop); + } + prop = zend_read_property( + curl_CURLFile_class, Z_OBJ_P(current), "postname", sizeof("postname") - 1, 0, &rv); + ZVAL_DEREF(prop); + if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) { + filename = Z_STRVAL_P(prop); + } + + zval_ptr_dtor(&ch->postfields); + ZVAL_COPY(&ch->postfields, zpostfields); + + if ((stream = php_stream_open_wrapper(ZSTR_VAL(postval), "rb", STREAM_MUST_SEEK, NULL))) { + if (!stream->readfilters.head && !php_stream_stat(stream, &ssb)) { + filesize = ssb.sb.st_size; + } + } else { + seekfunc = NULL; + } + + cb_arg = (struct mime_data_cb_arg *) emalloc(sizeof *cb_arg); + cb_arg->filename = zend_string_copy(postval); + cb_arg->stream = stream; + + part = curl_mime_addpart(mime); + if (part == NULL) { + zend_string_release_ex(string_key, 0); + return FAILURE; + } + if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || + (form_error = curl_mime_data_cb(part, filesize, read_cb, seekfunc, free_cb, cb_arg)) != CURLE_OK || + (form_error = curl_mime_filename(part, filename ? filename : ZSTR_VAL(postval))) != CURLE_OK || + (form_error = curl_mime_type(part, type ? type : "application/octet-stream")) != CURLE_OK) { + error = form_error; + } + zend_llist_add_element(&ch->to_free->stream, &cb_arg); + } + + zend_string_release_ex(string_key, 0); + continue; + } + + if (Z_TYPE_P(current) == IS_OBJECT && instanceof_function(Z_OBJCE_P(current), curl_CURLStringFile_class)) { + /* new-style file upload from string */ + zval *prop, rv; + char *type = NULL, *filename = NULL; + + prop = zend_read_property( + curl_CURLStringFile_class, SW_Z8_OBJ_P(current), "postname", sizeof("postname") - 1, 0, &rv); + if (EG(exception)) { + zend_string_release_ex(string_key, 0); + return FAILURE; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + filename = Z_STRVAL_P(prop); + + prop = + zend_read_property(curl_CURLStringFile_class, SW_Z8_OBJ_P(current), "mime", sizeof("mime") - 1, 0, &rv); + if (EG(exception)) { + zend_string_release_ex(string_key, 0); + return FAILURE; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + type = Z_STRVAL_P(prop); + + prop = + zend_read_property(curl_CURLStringFile_class, SW_Z8_OBJ_P(current), "data", sizeof("data") - 1, 0, &rv); + if (EG(exception)) { + zend_string_release_ex(string_key, 0); + return FAILURE; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + postval = Z_STR_P(prop); + + zval_ptr_dtor(&ch->postfields); + ZVAL_COPY(&ch->postfields, zpostfields); + + part = curl_mime_addpart(mime); + if (part == NULL) { + zend_string_release_ex(string_key, 0); + return FAILURE; + } + if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || + (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK || + (form_error = curl_mime_filename(part, filename)) != CURLE_OK || + (form_error = curl_mime_type(part, type)) != CURLE_OK) { + error = form_error; + } + + zend_string_release_ex(string_key, 0); + continue; + } + + if (Z_TYPE_P(current) == IS_ARRAY) { + zval *current_element; + + ZEND_HASH_FOREACH_VAL(HASH_OF(current), current_element) { + add_simple_field(mime, string_key, current_element); + } + ZEND_HASH_FOREACH_END(); + + zend_string_release_ex(string_key, 0); + continue; + } + + add_simple_field(mime, string_key, current); + + zend_string_release_ex(string_key, 0); + } + ZEND_HASH_FOREACH_END(); + + SAVE_CURL_ERROR(ch, error); + if (error != CURLE_OK) { + return FAILURE; + } + + if ((*ch->clone) == 1) { + zend_llist_clean(&ch->to_free->post); + } + zend_llist_add_element(&ch->to_free->post, &mime); + error = curl_easy_setopt(ch->cp, CURLOPT_MIMEPOST, mime); + + SAVE_CURL_ERROR(ch, error); + return error == CURLE_OK ? SUCCESS : FAILURE; +} +/* }}} */ + +/* {{{ Copy a cURL handle along with all of it's preferences */ +PHP_FUNCTION(swoole_native_curl_copy_handle) { + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + zend_object *clone_obj = swoole_curl_clone_obj(Z_OBJ_P(zid)); + if (!clone_obj) { + php_error_docref(NULL, E_WARNING, "Cannot duplicate cURL handle"); + RETURN_FALSE; + } + RETURN_OBJ(clone_obj); +} +/* }}} */ + +static bool php_curl_set_callable_handler(zend_fcall_info_cache *const handler_fcc, + zval *callable, + bool is_array_config, + const char *option_name) { + if (ZEND_FCC_INITIALIZED(*handler_fcc)) { + zend_fcc_dtor(handler_fcc); + } + + if (Z_TYPE_P(callable) == IS_NULL) { + return true; + } + + char *error = NULL; + if (UNEXPECTED(!zend_is_callable_ex(callable, + /* object */ NULL, + /* check_flags */ 0, + /* callable_name */ NULL, + handler_fcc, + /* error */ &error))) { + if (!EG(exception)) { + zend_argument_type_error( + 2 + !is_array_config, "must be a valid callback for option %s, %s", option_name, error); + } + efree(error); + return false; + } + zend_fcc_addref(handler_fcc); + return true; +} + +#define HANDLE_CURL_OPTION_CALLABLE_PHP_CURL_USER(curl_ptr, constant_no_function, handler_type) \ + case constant_no_function##FUNCTION: { \ + bool result = php_curl_set_callable_handler( \ + &curl_ptr->handlers.handler_type->fcc, zvalue, is_array_config, #constant_no_function "FUNCTION"); \ + if (!result) { \ + return FAILURE; \ + } \ + curl_ptr->handlers.handler_type->method = PHP_CURL_USER; \ + break; \ + } + +#define HANDLE_CURL_OPTION_CALLABLE(curl_ptr, constant_no_function, handler_fcc, c_callback) \ + case constant_no_function##FUNCTION: { \ + bool result = php_curl_set_callable_handler( \ + &curl_ptr->handler_fcc, zvalue, is_array_config, #constant_no_function "FUNCTION"); \ + if (!result) { \ + return FAILURE; \ + } \ + curl_easy_setopt(curl_ptr->cp, constant_no_function##FUNCTION, (c_callback)); \ + curl_easy_setopt(curl_ptr->cp, constant_no_function##DATA, curl_ptr); \ + break; \ + } + +static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool is_array_config) /* {{{ */ +{ + CURLcode error = CURLE_OK; + zend_long lval; + + switch (option) { + /* Callable options */ + HANDLE_CURL_OPTION_CALLABLE_PHP_CURL_USER(ch, CURLOPT_WRITE, write); + HANDLE_CURL_OPTION_CALLABLE_PHP_CURL_USER(ch, CURLOPT_HEADER, write_header); + HANDLE_CURL_OPTION_CALLABLE_PHP_CURL_USER(ch, CURLOPT_READ, read); + + HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PROGRESS, handlers.progress, fn_progress); + HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_XFERINFO, handlers.xferinfo, fn_xferinfo); + HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_FNMATCH_, handlers.fnmatch, fn_fnmatch); +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_SSH_HOSTKEY, handlers.sshhostkey, curl_ssh_hostkeyfunction); +#endif + + /* Long options */ + case CURLOPT_SSL_VERIFYHOST: + lval = zval_get_long(zvalue); + if (lval == 1) { + php_error_docref( + NULL, E_NOTICE, "CURLOPT_SSL_VERIFYHOST no longer accepts the value 1, value 2 will be used instead"); + error = curl_easy_setopt(ch->cp, (CURLoption) option, 2); + break; + } + ZEND_FALLTHROUGH; + case CURLOPT_AUTOREFERER: + case CURLOPT_BUFFERSIZE: + case CURLOPT_CONNECTTIMEOUT: + case CURLOPT_COOKIESESSION: + case CURLOPT_CRLF: + case CURLOPT_DNS_CACHE_TIMEOUT: + case CURLOPT_DNS_USE_GLOBAL_CACHE: + case CURLOPT_FAILONERROR: + case CURLOPT_FILETIME: + case CURLOPT_FORBID_REUSE: + case CURLOPT_FRESH_CONNECT: + case CURLOPT_FTP_USE_EPRT: + case CURLOPT_FTP_USE_EPSV: + case CURLOPT_HEADER: + case CURLOPT_HTTPGET: + case CURLOPT_HTTPPROXYTUNNEL: + case CURLOPT_HTTP_VERSION: + case CURLOPT_INFILESIZE: + case CURLOPT_LOW_SPEED_LIMIT: + case CURLOPT_LOW_SPEED_TIME: + case CURLOPT_MAXCONNECTS: + case CURLOPT_MAXREDIRS: + case CURLOPT_NETRC: + case CURLOPT_NOBODY: + case CURLOPT_NOPROGRESS: + case CURLOPT_NOSIGNAL: + case CURLOPT_PORT: + case CURLOPT_POST: + case CURLOPT_PROXYPORT: + case CURLOPT_PROXYTYPE: + case CURLOPT_PUT: + case CURLOPT_RESUME_FROM: + case CURLOPT_SSLVERSION: + case CURLOPT_SSL_VERIFYPEER: + case CURLOPT_TIMECONDITION: + case CURLOPT_TIMEOUT: + case CURLOPT_TIMEVALUE: + case CURLOPT_TRANSFERTEXT: + case CURLOPT_UNRESTRICTED_AUTH: + case CURLOPT_UPLOAD: + case CURLOPT_VERBOSE: + case CURLOPT_HTTPAUTH: + case CURLOPT_FTP_CREATE_MISSING_DIRS: + case CURLOPT_PROXYAUTH: + case CURLOPT_FTP_RESPONSE_TIMEOUT: + case CURLOPT_IPRESOLVE: + case CURLOPT_MAXFILESIZE: + case CURLOPT_TCP_NODELAY: + case CURLOPT_FTPSSLAUTH: + case CURLOPT_IGNORE_CONTENT_LENGTH: + case CURLOPT_FTP_SKIP_PASV_IP: + case CURLOPT_FTP_FILEMETHOD: + case CURLOPT_CONNECT_ONLY: + case CURLOPT_LOCALPORT: + case CURLOPT_LOCALPORTRANGE: + case CURLOPT_SSL_SESSIONID_CACHE: + case CURLOPT_FTP_SSL_CCC: + case CURLOPT_SSH_AUTH_TYPES: + case CURLOPT_CONNECTTIMEOUT_MS: + case CURLOPT_HTTP_CONTENT_DECODING: + case CURLOPT_HTTP_TRANSFER_DECODING: + case CURLOPT_TIMEOUT_MS: + case CURLOPT_NEW_DIRECTORY_PERMS: + case CURLOPT_NEW_FILE_PERMS: + case CURLOPT_USE_SSL: + case CURLOPT_APPEND: + case CURLOPT_DIRLISTONLY: + case CURLOPT_PROXY_TRANSFER_MODE: + case CURLOPT_ADDRESS_SCOPE: + case CURLOPT_CERTINFO: + case CURLOPT_PROTOCOLS: + case CURLOPT_REDIR_PROTOCOLS: + case CURLOPT_SOCKS5_GSSAPI_NEC: + case CURLOPT_TFTP_BLKSIZE: + case CURLOPT_FTP_USE_PRET: + case CURLOPT_RTSP_CLIENT_CSEQ: + case CURLOPT_RTSP_REQUEST: + case CURLOPT_RTSP_SERVER_CSEQ: + case CURLOPT_WILDCARDMATCH: + case CURLOPT_GSSAPI_DELEGATION: + case CURLOPT_ACCEPTTIMEOUT_MS: + case CURLOPT_SSL_OPTIONS: + case CURLOPT_TCP_KEEPALIVE: + case CURLOPT_TCP_KEEPIDLE: + case CURLOPT_TCP_KEEPINTVL: + case CURLOPT_SASL_IR: + case CURLOPT_EXPECT_100_TIMEOUT_MS: + case CURLOPT_SSL_ENABLE_ALPN: + case CURLOPT_SSL_ENABLE_NPN: + case CURLOPT_HEADEROPT: + case CURLOPT_SSL_VERIFYSTATUS: + case CURLOPT_PATH_AS_IS: + case CURLOPT_SSL_FALSESTART: + case CURLOPT_PIPEWAIT: + case CURLOPT_STREAM_WEIGHT: + case CURLOPT_TFTP_NO_OPTIONS: + case CURLOPT_TCP_FASTOPEN: + case CURLOPT_KEEP_SENDING_ON_ERROR: + case CURLOPT_PROXY_SSL_OPTIONS: + case CURLOPT_PROXY_SSL_VERIFYHOST: + case CURLOPT_PROXY_SSL_VERIFYPEER: + case CURLOPT_PROXY_SSLVERSION: + case CURLOPT_SUPPRESS_CONNECT_HEADERS: + case CURLOPT_SOCKS5_AUTH: + case CURLOPT_SSH_COMPRESSION: + case CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS: + case CURLOPT_DNS_SHUFFLE_ADDRESSES: + case CURLOPT_HAPROXYPROTOCOL: + case CURLOPT_DISALLOW_USERNAME_IN_URL: +#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ + case CURLOPT_UPKEEP_INTERVAL_MS: + case CURLOPT_UPLOAD_BUFFERSIZE: +#endif +#if LIBCURL_VERSION_NUM >= 0x074000 /* Available since 7.64.0 */ + case CURLOPT_HTTP09_ALLOWED: +#endif +#if LIBCURL_VERSION_NUM >= 0x074001 /* Available since 7.64.1 */ + case CURLOPT_ALTSVC_CTRL: +#endif +#if LIBCURL_VERSION_NUM >= 0x074100 /* Available since 7.65.0 */ + case CURLOPT_MAXAGE_CONN: +#endif +#if LIBCURL_VERSION_NUM >= 0x074500 /* Available since 7.69.0 */ + case CURLOPT_MAIL_RCPT_ALLLOWFAILS: +#endif +#if LIBCURL_VERSION_NUM >= 0x074a00 /* Available since 7.74.0 */ + case CURLOPT_HSTS_CTRL: +#endif +#if LIBCURL_VERSION_NUM >= 0x074c00 /* Available since 7.76.0 */ + case CURLOPT_DOH_SSL_VERIFYHOST: + case CURLOPT_DOH_SSL_VERIFYPEER: + case CURLOPT_DOH_SSL_VERIFYSTATUS: +#endif +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + case CURLOPT_MAXLIFETIME_CONN: +#endif +#if LIBCURL_VERSION_NUM >= 0x075100 /* Available since 7.81.0 */ + case CURLOPT_MIME_OPTIONS: +#endif +#if LIBCURL_VERSION_NUM >= 0x075600 /* Available since 7.86.0 */ + case CURLOPT_WS_OPTIONS: +#endif +#if LIBCURL_VERSION_NUM >= 0x075700 /* Available since 7.87.0 */ + case CURLOPT_CA_CACHE_TIMEOUT: + case CURLOPT_QUICK_EXIT: +#endif +#if LIBCURL_VERSION_NUM >= 0x080900 /* Available since 8.9.0 */ + case CURLOPT_TCP_KEEPCNT: +#endif + lval = zval_get_long(zvalue); + if ((option == CURLOPT_PROTOCOLS || option == CURLOPT_REDIR_PROTOCOLS) && + (PG(open_basedir) && *PG(open_basedir)) && (lval & CURLPROTO_FILE)) { + php_error_docref(NULL, E_WARNING, "CURLPROTO_FILE cannot be activated when an open_basedir is set"); + return FAILURE; + } +#if defined(ZTS) + if (option == CURLOPT_DNS_USE_GLOBAL_CACHE && lval) { + php_error_docref( + NULL, E_WARNING, "CURLOPT_DNS_USE_GLOBAL_CACHE cannot be activated when thread safety is enabled"); + return FAILURE; + } +#endif + error = curl_easy_setopt(ch->cp, (CURLoption) option, lval); + break; + case CURLOPT_SAFE_UPLOAD: + if (!zend_is_true(zvalue)) { + zend_value_error("%s(): Disabling safe uploads is no longer supported", get_active_function_name()); + return FAILURE; + } + break; + + /* String options */ + case CURLOPT_CAINFO: + case CURLOPT_CAPATH: + case CURLOPT_COOKIE: + case CURLOPT_EGDSOCKET: + case CURLOPT_INTERFACE: + case CURLOPT_PROXY: + case CURLOPT_PROXYUSERPWD: + case CURLOPT_REFERER: + case CURLOPT_SSLCERTTYPE: + case CURLOPT_SSLENGINE: + case CURLOPT_SSLENGINE_DEFAULT: + case CURLOPT_SSLKEY: + case CURLOPT_SSLKEYPASSWD: + case CURLOPT_SSLKEYTYPE: + case CURLOPT_SSL_CIPHER_LIST: + case CURLOPT_USERAGENT: + case CURLOPT_USERPWD: + case CURLOPT_COOKIELIST: + case CURLOPT_FTP_ALTERNATIVE_TO_USER: + case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: + case CURLOPT_PASSWORD: + case CURLOPT_PROXYPASSWORD: + case CURLOPT_PROXYUSERNAME: + case CURLOPT_USERNAME: + case CURLOPT_NOPROXY: + case CURLOPT_SOCKS5_GSSAPI_SERVICE: + case CURLOPT_MAIL_FROM: + case CURLOPT_RTSP_STREAM_URI: + case CURLOPT_RTSP_TRANSPORT: + case CURLOPT_TLSAUTH_TYPE: + case CURLOPT_TLSAUTH_PASSWORD: + case CURLOPT_TLSAUTH_USERNAME: + case CURLOPT_TRANSFER_ENCODING: + case CURLOPT_DNS_SERVERS: + case CURLOPT_MAIL_AUTH: + case CURLOPT_LOGIN_OPTIONS: + case CURLOPT_PINNEDPUBLICKEY: + case CURLOPT_PROXY_SERVICE_NAME: + case CURLOPT_SERVICE_NAME: + case CURLOPT_DEFAULT_PROTOCOL: + case CURLOPT_PRE_PROXY: + case CURLOPT_PROXY_CAINFO: + case CURLOPT_PROXY_CAPATH: + case CURLOPT_PROXY_CRLFILE: + case CURLOPT_PROXY_KEYPASSWD: + case CURLOPT_PROXY_PINNEDPUBLICKEY: + case CURLOPT_PROXY_SSL_CIPHER_LIST: + case CURLOPT_PROXY_SSLCERT: + case CURLOPT_PROXY_SSLCERTTYPE: + case CURLOPT_PROXY_SSLKEY: + case CURLOPT_PROXY_SSLKEYTYPE: + case CURLOPT_PROXY_TLSAUTH_PASSWORD: + case CURLOPT_PROXY_TLSAUTH_TYPE: + case CURLOPT_PROXY_TLSAUTH_USERNAME: + case CURLOPT_ABSTRACT_UNIX_SOCKET: + case CURLOPT_REQUEST_TARGET: + case CURLOPT_PROXY_TLS13_CIPHERS: + case CURLOPT_TLS13_CIPHERS: +#if LIBCURL_VERSION_NUM >= 0x074001 /* Available since 7.64.1 */ + case CURLOPT_ALTSVC: +#endif +#if LIBCURL_VERSION_NUM >= 0x074200 /* Available since 7.66.0 */ + case CURLOPT_SASL_AUTHZID: +#endif +#if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */ + case CURLOPT_PROXY_ISSUERCERT: +#endif +#if LIBCURL_VERSION_NUM >= 0x074900 /* Available since 7.73.0 */ + case CURLOPT_SSL_EC_CURVES: +#endif +#if LIBCURL_VERSION_NUM >= 0x074b00 /* Available since 7.75.0 */ + case CURLOPT_AWS_SIGV4: +#endif +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: +#endif +#if LIBCURL_VERSION_NUM >= 0x075500 /* Available since 7.85.0 */ + case CURLOPT_PROTOCOLS_STR: + case CURLOPT_REDIR_PROTOCOLS_STR: +#endif + { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); +#if LIBCURL_VERSION_NUM >= 0x075500 /* Available since 7.85.0 */ + if ((option == CURLOPT_PROTOCOLS_STR || option == CURLOPT_REDIR_PROTOCOLS_STR) && + (PG(open_basedir) && *PG(open_basedir)) && + php_memnistr(ZSTR_VAL(str), "file", sizeof("file") - 1, ZSTR_VAL(str) + ZSTR_LEN(str)) != NULL) { + php_error_docref(NULL, E_WARNING, "The FILE protocol cannot be activated when an open_basedir is set"); + return FAILURE; + } +#endif + zend_result ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str)); + zend_tmp_string_release(tmp_str); + return ret; + } + + /* Curl nullable string options */ + case CURLOPT_CUSTOMREQUEST: + case CURLOPT_FTPPORT: + case CURLOPT_RANGE: + case CURLOPT_FTP_ACCOUNT: + case CURLOPT_RTSP_SESSION_ID: + case CURLOPT_ACCEPT_ENCODING: + case CURLOPT_DNS_INTERFACE: + case CURLOPT_DNS_LOCAL_IP4: + case CURLOPT_DNS_LOCAL_IP6: + case CURLOPT_XOAUTH2_BEARER: + case CURLOPT_UNIX_SOCKET_PATH: +#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ + case CURLOPT_DOH_URL: +#endif +#if LIBCURL_VERSION_NUM >= 0x074a00 /* Available since 7.74.0 */ + case CURLOPT_HSTS: +#endif + case CURLOPT_KRBLEVEL: { + if (Z_ISNULL_P(zvalue)) { + error = curl_easy_setopt(ch->cp, (CURLoption) option, NULL); + } else { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + zend_result ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str)); + zend_tmp_string_release(tmp_str); + return ret; + } + break; + } + + /* Curl private option */ + case CURLOPT_PRIVATE: { + zval_ptr_dtor(&ch->private_data); + ZVAL_COPY(&ch->private_data, zvalue); + return SUCCESS; + } + + /* Curl url option */ + case CURLOPT_URL: { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + zend_result ret = php_curl_option_url(ch, str); + zend_tmp_string_release(tmp_str); + return ret; + } + + /* Curl file handle options */ + case CURLOPT_FILE: + case CURLOPT_INFILE: + case CURLOPT_STDERR: + case CURLOPT_WRITEHEADER: { + FILE *fp = NULL; + php_stream *what = NULL; + + if (Z_TYPE_P(zvalue) != IS_NULL) { + what = (php_stream *) zend_fetch_resource2_ex( + zvalue, "File-Handle", php_file_le_stream(), php_file_le_pstream()); + if (!what) { + return FAILURE; + } + + if (FAILURE == php_stream_cast(what, PHP_STREAM_AS_STDIO, (void **) &fp, REPORT_ERRORS)) { + return FAILURE; + } + + if (!fp) { + return FAILURE; + } + } + + error = CURLE_OK; + switch (option) { + case CURLOPT_FILE: + if (!what) { + if (!Z_ISUNDEF(ch->handlers.write->stream)) { + zval_ptr_dtor(&ch->handlers.write->stream); + ZVAL_UNDEF(&ch->handlers.write->stream); + } + ch->handlers.write->fp = NULL; + ch->handlers.write->method = PHP_CURL_STDOUT; + } else if (what->mode[0] != 'r' || what->mode[1] == '+') { + zval_ptr_dtor(&ch->handlers.write->stream); + ch->handlers.write->fp = fp; + ch->handlers.write->method = PHP_CURL_FILE; + ZVAL_COPY(&ch->handlers.write->stream, zvalue); + } else { + zend_value_error("%s(): The provided file handle must be writable", get_active_function_name()); + return FAILURE; + } + break; + case CURLOPT_WRITEHEADER: + if (!what) { + if (!Z_ISUNDEF(ch->handlers.write_header->stream)) { + zval_ptr_dtor(&ch->handlers.write_header->stream); + ZVAL_UNDEF(&ch->handlers.write_header->stream); + } + ch->handlers.write_header->fp = NULL; + ch->handlers.write_header->method = PHP_CURL_IGNORE; + } else if (what->mode[0] != 'r' || what->mode[1] == '+') { + zval_ptr_dtor(&ch->handlers.write_header->stream); + ch->handlers.write_header->fp = fp; + ch->handlers.write_header->method = PHP_CURL_FILE; + ZVAL_COPY(&ch->handlers.write_header->stream, zvalue); + } else { + zend_value_error("%s(): The provided file handle must be writable", get_active_function_name()); + return FAILURE; + } + break; + case CURLOPT_INFILE: + if (!what) { + if (!Z_ISUNDEF(ch->handlers.read->stream)) { + zval_ptr_dtor(&ch->handlers.read->stream); + ZVAL_UNDEF(&ch->handlers.read->stream); + } + ch->handlers.read->fp = NULL; + ch->handlers.read->res = NULL; + } else { + zval_ptr_dtor(&ch->handlers.read->stream); + ch->handlers.read->fp = fp; + ch->handlers.read->res = Z_RES_P(zvalue); + ZVAL_COPY(&ch->handlers.read->stream, zvalue); + } + break; + case CURLOPT_STDERR: + if (!what) { + if (!Z_ISUNDEF(ch->handlers.std_err)) { + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_UNDEF(&ch->handlers.std_err); + } + } else if (what->mode[0] != 'r' || what->mode[1] == '+') { + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_COPY(&ch->handlers.std_err, zvalue); + } else { + zend_value_error("%s(): The provided file handle must be writable", get_active_function_name()); + return FAILURE; + } + ZEND_FALLTHROUGH; + default: + error = curl_easy_setopt(ch->cp, (CURLoption) option, fp); + break; + } + break; + } + + /* Curl linked list options */ + case CURLOPT_HTTP200ALIASES: + case CURLOPT_HTTPHEADER: + case CURLOPT_POSTQUOTE: + case CURLOPT_PREQUOTE: + case CURLOPT_QUOTE: + case CURLOPT_TELNETOPTIONS: + case CURLOPT_MAIL_RCPT: + case CURLOPT_RESOLVE: + case CURLOPT_PROXYHEADER: + case CURLOPT_CONNECT_TO: { + zval *current; + HashTable *ph; + zend_string *val, *tmp_val; + struct curl_slist *slist = NULL; + + if (Z_TYPE_P(zvalue) != IS_ARRAY) { + const char *name = NULL; + switch (option) { + case CURLOPT_HTTPHEADER: + name = "CURLOPT_HTTPHEADER"; + break; + case CURLOPT_QUOTE: + name = "CURLOPT_QUOTE"; + break; + case CURLOPT_HTTP200ALIASES: + name = "CURLOPT_HTTP200ALIASES"; + break; + case CURLOPT_POSTQUOTE: + name = "CURLOPT_POSTQUOTE"; + break; + case CURLOPT_PREQUOTE: + name = "CURLOPT_PREQUOTE"; + break; + case CURLOPT_TELNETOPTIONS: + name = "CURLOPT_TELNETOPTIONS"; + break; + case CURLOPT_MAIL_RCPT: + name = "CURLOPT_MAIL_RCPT"; + break; + case CURLOPT_RESOLVE: + name = "CURLOPT_RESOLVE"; + break; + case CURLOPT_PROXYHEADER: + name = "CURLOPT_PROXYHEADER"; + break; + case CURLOPT_CONNECT_TO: + name = "CURLOPT_CONNECT_TO"; + break; + } + + zend_type_error("%s(): The %s option must have an array value", get_active_function_name(), name); + return FAILURE; + } + + ph = Z_ARRVAL_P(zvalue); + ZEND_HASH_FOREACH_VAL(ph, current) { + ZVAL_DEREF(current); + val = zval_get_tmp_string(current, &tmp_val); + slist = curl_slist_append(slist, ZSTR_VAL(val)); + zend_tmp_string_release(tmp_val); + if (!slist) { + php_error_docref(NULL, E_WARNING, "Could not build curl_slist"); + return FAILURE; + } + } + ZEND_HASH_FOREACH_END(); + + if (slist) { + if ((*ch->clone) == 1) { + zend_hash_index_update_ptr(ch->to_free->slist, option, slist); + } else { + zend_hash_next_index_insert_ptr(ch->to_free->slist, slist); + } + } + + error = curl_easy_setopt(ch->cp, (CURLoption) option, slist); + + break; + } + + case CURLOPT_BINARYTRANSFER: + /* Do nothing, just backward compatibility */ + break; + + case CURLOPT_FOLLOWLOCATION: + lval = zend_is_true(zvalue); + error = curl_easy_setopt(ch->cp, (CURLoption) option, lval); + break; + + case CURLOPT_POSTFIELDS: + if (Z_TYPE_P(zvalue) == IS_ARRAY) { + if (zend_hash_num_elements(HASH_OF(zvalue)) == 0) { + /* no need to build the mime structure for empty hashtables; + also works around https://github.com/curl/curl/issues/6455 */ + curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDS, ""); + error = curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDSIZE, 0); + } else { + return build_mime_structure_from_hash(ch, zvalue); + } + } else { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + /* with curl 7.17.0 and later, we can use COPYPOSTFIELDS, but we have to provide size before */ + error = curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDSIZE, ZSTR_LEN(str)); + error = curl_easy_setopt(ch->cp, CURLOPT_COPYPOSTFIELDS, ZSTR_VAL(str)); + zend_tmp_string_release(tmp_str); + } + break; + + case CURLOPT_RETURNTRANSFER: + if (zend_is_true(zvalue)) { + ch->handlers.write->method = PHP_CURL_RETURN; + } else { + ch->handlers.write->method = PHP_CURL_STDOUT; + } + break; + + /* Curl off_t options */ + case CURLOPT_MAX_RECV_SPEED_LARGE: + case CURLOPT_MAX_SEND_SPEED_LARGE: + case CURLOPT_MAXFILESIZE_LARGE: + case CURLOPT_TIMEVALUE_LARGE: + lval = zval_get_long(zvalue); + error = curl_easy_setopt(ch->cp, (CURLoption) option, (curl_off_t) lval); + break; + + case CURLOPT_POSTREDIR: + lval = zval_get_long(zvalue); + error = curl_easy_setopt(ch->cp, CURLOPT_POSTREDIR, lval & CURL_REDIR_POST_ALL); + break; + + /* the following options deal with files, therefore the open_basedir check + * is required. + */ + case CURLOPT_COOKIEFILE: + case CURLOPT_COOKIEJAR: + case CURLOPT_RANDOM_FILE: + case CURLOPT_SSLCERT: + case CURLOPT_NETRC_FILE: + case CURLOPT_SSH_PRIVATE_KEYFILE: + case CURLOPT_SSH_PUBLIC_KEYFILE: + case CURLOPT_CRLFILE: + case CURLOPT_ISSUERCERT: + case CURLOPT_SSH_KNOWNHOSTS: { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + zend_result ret; + + if (ZSTR_LEN(str) && php_check_open_basedir(ZSTR_VAL(str))) { + zend_tmp_string_release(tmp_str); + return FAILURE; + } + + ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str)); + zend_tmp_string_release(tmp_str); + return ret; + } + + case CURLINFO_HEADER_OUT: + if (zend_is_true(zvalue)) { + curl_easy_setopt(ch->cp, CURLOPT_DEBUGFUNCTION, curl_debug); + curl_easy_setopt(ch->cp, CURLOPT_DEBUGDATA, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 1); + } else { + curl_easy_setopt(ch->cp, CURLOPT_DEBUGFUNCTION, NULL); + curl_easy_setopt(ch->cp, CURLOPT_DEBUGDATA, NULL); + curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 0); + } + break; + + case CURLOPT_SHARE: { + if (Z_TYPE_P(zvalue) == IS_OBJECT && Z_OBJCE_P(zvalue) == curl_share_ce) { + php_curlsh *sh = Z_CURL_SHARE_P(zvalue); + curl_easy_setopt(ch->cp, CURLOPT_SHARE, sh->share); + + if (ch->share) { + OBJ_RELEASE(&ch->share->std); + } + GC_ADDREF(&sh->std); + ch->share = sh; + } + } break; + + /* Curl blob options */ +#if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */ + case CURLOPT_ISSUERCERT_BLOB: + case CURLOPT_PROXY_ISSUERCERT_BLOB: + case CURLOPT_PROXY_SSLCERT_BLOB: + case CURLOPT_PROXY_SSLKEY_BLOB: + case CURLOPT_SSLCERT_BLOB: + case CURLOPT_SSLKEY_BLOB: +#if LIBCURL_VERSION_NUM >= 0x074d00 /* Available since 7.77.0 */ + case CURLOPT_CAINFO_BLOB: + case CURLOPT_PROXY_CAINFO_BLOB: +#endif + { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + + struct curl_blob stblob; + stblob.data = ZSTR_VAL(str); + stblob.len = ZSTR_LEN(str); + stblob.flags = CURL_BLOB_COPY; + error = curl_easy_setopt(ch->cp, (CURLoption) option, &stblob); + + zend_tmp_string_release(tmp_str); + } break; +#endif + + default: + if (is_array_config) { + zend_argument_value_error(2, "must contain only valid cURL options"); + } else { + zend_argument_value_error(2, "is not a valid cURL option"); + } + error = CURLE_UNKNOWN_OPTION; + break; + } + + SAVE_CURL_ERROR(ch, error); + if (error != CURLE_OK) { + return FAILURE; + } else { + return SUCCESS; + } +} +/* }}} */ + +/* {{{ Set an option for a cURL transfer */ +PHP_FUNCTION(swoole_native_curl_setopt) { + zval *zid, *zvalue; + zend_long options; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + Z_PARAM_LONG(options) + Z_PARAM_ZVAL(zvalue) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (_php_curl_setopt(ch, options, zvalue, 0) == SUCCESS) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Set an array of option for a cURL transfer */ +PHP_FUNCTION(swoole_native_curl_setopt_array) { + zval *zid, *arr, *entry; + php_curl *ch; + zend_ulong option; + zend_string *string_key; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + Z_PARAM_ARRAY(arr) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(arr), option, string_key, entry) { + if (string_key) { + zend_argument_value_error(2, "contains an invalid cURL option"); + RETURN_THROWS(); + } + + ZVAL_DEREF(entry); + if (_php_curl_setopt(ch, (zend_long) option, entry, 1) == FAILURE) { + RETURN_FALSE; + } + } + ZEND_HASH_FOREACH_END(); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ _php_curl_cleanup_handle(ch) + Cleanup an execution phase */ +void swoole_curl_cleanup_handle(php_curl *ch) { + smart_str_free(&ch->handlers.write->buf); + if (ch->header.str) { + zend_string_release_ex(ch->header.str, 0); + ch->header.str = NULL; + } + + memset(ch->err.str, 0, CURL_ERROR_SIZE + 1); + ch->err.no = 0; +} +/* }}} */ + +/* {{{ Perform a cURL session */ +PHP_FUNCTION(swoole_native_curl_exec) { + CURLcode error; + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + swoole_curl_verify_handlers(ch, 1); + + swoole_curl_cleanup_handle(ch); + + Multi *multi = new Multi(); + error = multi->exec(swoole::curl::get_handle(ch->cp)); + delete multi; + SAVE_CURL_ERROR(ch, error); + + if (error != CURLE_OK) { + smart_str_free(&ch->handlers.write->buf); + RETURN_FALSE; + } + + if (!Z_ISUNDEF(ch->handlers.std_err)) { + php_stream *stream; + stream = (php_stream *) zend_fetch_resource2_ex( + &ch->handlers.std_err, NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream) { + php_stream_flush(stream); + } + } + + if (ch->handlers.write->method == PHP_CURL_RETURN && ch->handlers.write->buf.s) { + smart_str_0(&ch->handlers.write->buf); + RETURN_STR_COPY(ch->handlers.write->buf.s); + } + + /* flush the file handle, so any remaining data is synched to disk */ + if (ch->handlers.write->method == PHP_CURL_FILE && ch->handlers.write->fp) { + fflush(ch->handlers.write->fp); + } + if (ch->handlers.write_header->method == PHP_CURL_FILE && ch->handlers.write_header->fp) { + fflush(ch->handlers.write_header->fp); + } + + if (ch->handlers.write->method == PHP_CURL_RETURN) { + RETURN_EMPTY_STRING(); + } else { + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ Get information regarding a specific transfer */ +PHP_FUNCTION(swoole_native_curl_getinfo) { + zval *zid; + php_curl *ch; + zend_long option; + bool option_is_null = 1; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(option, option_is_null) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (option_is_null) { + char *s_code; + /* libcurl expects long datatype. So far no cases are known where + it would be an issue. Using zend_long would truncate a 64-bit + var on Win64, so the exact long datatype fits everywhere, as + long as there's no 32-bit int overflow. */ + long l_code; + double d_code; + struct curl_certinfo *ci = NULL; + zval listcode; + curl_off_t co; + + array_init(return_value); + + if (curl_easy_getinfo(ch->cp, CURLINFO_EFFECTIVE_URL, &s_code) == CURLE_OK) { + CAAS("url", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CONTENT_TYPE, &s_code) == CURLE_OK) { + if (s_code != NULL) { + CAAS("content_type", s_code); + } else { + zval retnull; + ZVAL_NULL(&retnull); + CAAZ("content_type", &retnull); + } + } + if (curl_easy_getinfo(ch->cp, CURLINFO_HTTP_CODE, &l_code) == CURLE_OK) { + CAAL("http_code", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_HEADER_SIZE, &l_code) == CURLE_OK) { + CAAL("header_size", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_REQUEST_SIZE, &l_code) == CURLE_OK) { + CAAL("request_size", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_FILETIME, &l_code) == CURLE_OK) { + CAAL("filetime", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SSL_VERIFYRESULT, &l_code) == CURLE_OK) { + CAAL("ssl_verify_result", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_REDIRECT_COUNT, &l_code) == CURLE_OK) { + CAAL("redirect_count", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_TOTAL_TIME, &d_code) == CURLE_OK) { + CAAD("total_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_NAMELOOKUP_TIME, &d_code) == CURLE_OK) { + CAAD("namelookup_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CONNECT_TIME, &d_code) == CURLE_OK) { + CAAD("connect_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PRETRANSFER_TIME, &d_code) == CURLE_OK) { + CAAD("pretransfer_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SIZE_UPLOAD, &d_code) == CURLE_OK) { + CAAD("size_upload", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SIZE_DOWNLOAD, &d_code) == CURLE_OK) { + CAAD("size_download", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SPEED_DOWNLOAD, &d_code) == CURLE_OK) { + CAAD("speed_download", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SPEED_UPLOAD, &d_code) == CURLE_OK) { + CAAD("speed_upload", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d_code) == CURLE_OK) { + CAAD("download_content_length", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CONTENT_LENGTH_UPLOAD, &d_code) == CURLE_OK) { + CAAD("upload_content_length", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_STARTTRANSFER_TIME, &d_code) == CURLE_OK) { + CAAD("starttransfer_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_REDIRECT_TIME, &d_code) == CURLE_OK) { + CAAD("redirect_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_REDIRECT_URL, &s_code) == CURLE_OK) { + CAAS("redirect_url", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PRIMARY_IP, &s_code) == CURLE_OK) { + CAAS("primary_ip", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CERTINFO, &ci) == CURLE_OK) { + array_init(&listcode); + create_certinfo(ci, &listcode); + CAAZ("certinfo", &listcode); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PRIMARY_PORT, &l_code) == CURLE_OK) { + CAAL("primary_port", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_LOCAL_IP, &s_code) == CURLE_OK) { + CAAS("local_ip", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_LOCAL_PORT, &l_code) == CURLE_OK) { + CAAL("local_port", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_HTTP_VERSION, &l_code) == CURLE_OK) { + CAAL("http_version", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PROTOCOL, &l_code) == CURLE_OK) { + CAAL("protocol", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PROXY_SSL_VERIFYRESULT, &l_code) == CURLE_OK) { + CAAL("ssl_verifyresult", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SCHEME, &s_code) == CURLE_OK) { + CAAS("scheme", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_APPCONNECT_TIME_T, &co) == CURLE_OK) { + CAAL("appconnect_time_us", co); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CONNECT_TIME_T, &co) == CURLE_OK) { + CAAL("connect_time_us", co); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_NAMELOOKUP_TIME_T, &co) == CURLE_OK) { + CAAL("namelookup_time_us", co); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PRETRANSFER_TIME_T, &co) == CURLE_OK) { + CAAL("pretransfer_time_us", co); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_REDIRECT_TIME_T, &co) == CURLE_OK) { + CAAL("redirect_time_us", co); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_STARTTRANSFER_TIME_T, &co) == CURLE_OK) { + CAAL("starttransfer_time_us", co); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_TOTAL_TIME_T, &co) == CURLE_OK) { + CAAL("total_time_us", co); + } + if (ch->header.str) { + CAASTR("request_header", ch->header.str); + } +#if LIBCURL_VERSION_NUM >= 0x074800 /* Available since 7.72.0 */ + if (curl_easy_getinfo(ch->cp, CURLINFO_EFFECTIVE_METHOD, &s_code) == CURLE_OK) { + CAAS("effective_method", s_code); + } +#endif +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + if (curl_easy_getinfo(ch->cp, CURLINFO_CAPATH, &s_code) == CURLE_OK) { + CAAS("capath", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CAINFO, &s_code) == CURLE_OK) { + CAAS("cainfo", s_code); + } +#endif + } else { + switch (option) { + case CURLINFO_HEADER_OUT: + if (ch->header.str) { + RETURN_STR_COPY(ch->header.str); + } else { + RETURN_FALSE; + } + case CURLINFO_CERTINFO: { + struct curl_certinfo *ci = NULL; + + array_init(return_value); + + if (curl_easy_getinfo(ch->cp, CURLINFO_CERTINFO, &ci) == CURLE_OK) { + create_certinfo(ci, return_value); + } else { + RETURN_FALSE; + } + break; + } + case CURLINFO_PRIVATE: + if (!Z_ISUNDEF(ch->private_data)) { + RETURN_COPY(&ch->private_data); + } else { + RETURN_FALSE; + } + break; + default: { + int type = CURLINFO_TYPEMASK & option; + switch (type) { + case CURLINFO_STRING: { + char *s_code = NULL; + + if (curl_easy_getinfo(ch->cp, (CURLINFO) option, &s_code) == CURLE_OK && s_code) { + RETURN_STRING(s_code); + } else { + RETURN_FALSE; + } + break; + } + case CURLINFO_LONG: { + zend_long code = 0; + + if (curl_easy_getinfo(ch->cp, (CURLINFO) option, &code) == CURLE_OK) { + RETURN_LONG(code); + } else { + RETURN_FALSE; + } + break; + } + case CURLINFO_DOUBLE: { + double code = 0.0; + + if (curl_easy_getinfo(ch->cp, (CURLINFO) option, &code) == CURLE_OK) { + RETURN_DOUBLE(code); + } else { + RETURN_FALSE; + } + break; + } + case CURLINFO_SLIST: { + struct curl_slist *slist; + if (curl_easy_getinfo(ch->cp, (CURLINFO) option, &slist) == CURLE_OK) { + struct curl_slist *current = slist; + array_init(return_value); + while (current) { + add_next_index_string(return_value, current->data); + current = current->next; + } + curl_slist_free_all(slist); + } else { + RETURN_FALSE; + } + break; + } + case CURLINFO_OFF_T: { + curl_off_t c_off; + if (curl_easy_getinfo(ch->cp, (CURLINFO) option, &c_off) == CURLE_OK) { + RETURN_LONG((long) c_off); + } else { + RETURN_FALSE; + } + break; + } + default: + RETURN_FALSE; + } + } + } + } +} +/* }}} */ + +/* {{{ Return a string contain the last error for the current session */ +PHP_FUNCTION(swoole_native_curl_error) { + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (ch->err.no) { + ch->err.str[CURL_ERROR_SIZE] = 0; + if (strlen(ch->err.str) > 0) { + RETURN_STRING(ch->err.str); + } else { + RETURN_STRING(curl_easy_strerror((CURLcode) ch->err.no)); + } + } else { + RETURN_EMPTY_STRING(); + } +} +/* }}} */ + +/* {{{ Return an integer containing the last error number */ +PHP_FUNCTION(swoole_native_curl_errno) { + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(ch->err.no); +} +/* }}} */ + +/* {{{ Close a cURL session */ +PHP_FUNCTION(swoole_native_curl_close) { + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (ch->in_callback) { + zend_throw_error(NULL, "%s(): Attempt to close cURL handle from a callback", get_active_function_name()); + RETURN_THROWS(); + } +} +/* }}} */ + +static void swoole_curl_free_obj(zend_object *object) { + php_curl *ch = curl_from_obj(object); + +#if PHP_CURL_DEBUG + fprintf(stderr, "DTOR CALLED, ch = %x\n", ch); +#endif + + if (!ch->cp) { + /* Can happen if constructor throws. */ + zend_object_std_dtor(&ch->std); + return; + } + + swoole_curl_verify_handlers(ch, /* reporterror */ false); + + swoole::curl::Handle *handle = swoole::curl::get_handle(ch->cp); + if (handle && handle->multi) { + handle->multi->remove_handle(handle); + } + + curl_easy_cleanup(ch->cp); + + /* cURL destructors should be invoked only by last curl handle */ + if (--(*ch->clone) == 0) { + zend_llist_clean(&ch->to_free->post); + zend_llist_clean(&ch->to_free->stream); + + zend_hash_destroy(ch->to_free->slist); + efree(ch->to_free->slist); + efree(ch->to_free); + efree(ch->clone); + swoole::curl::destroy_handle(ch->cp); + } + + smart_str_free(&ch->handlers.write->buf); + if (ZEND_FCC_INITIALIZED(ch->handlers.write->fcc)) { + zend_fcc_dtor(&ch->handlers.write->fcc); + } + if (ZEND_FCC_INITIALIZED(ch->handlers.write_header->fcc)) { + zend_fcc_dtor(&ch->handlers.write_header->fcc); + } + if (ZEND_FCC_INITIALIZED(ch->handlers.read->fcc)) { + zend_fcc_dtor(&ch->handlers.read->fcc); + } + zval_ptr_dtor(&ch->handlers.std_err); + if (ch->header.str) { + zend_string_release_ex(ch->header.str, 0); + } + + zval_ptr_dtor(&ch->handlers.write_header->stream); + zval_ptr_dtor(&ch->handlers.write->stream); + zval_ptr_dtor(&ch->handlers.read->stream); + + efree(ch->handlers.write); + efree(ch->handlers.write_header); + efree(ch->handlers.read); + + if (ZEND_FCC_INITIALIZED(ch->handlers.progress)) { + zend_fcc_dtor(&ch->handlers.progress); + } + if (ZEND_FCC_INITIALIZED(ch->handlers.xferinfo)) { + zend_fcc_dtor(&ch->handlers.xferinfo); + } + if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) { + zend_fcc_dtor(&ch->handlers.fnmatch); + } +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) { + zend_fcc_dtor(&ch->handlers.sshhostkey); + } +#endif + + zval_ptr_dtor(&ch->postfields); + zval_ptr_dtor(&ch->private_data); + + if (ch->share) { + OBJ_RELEASE(&ch->share->std); + } + + zend_object_std_dtor(&ch->std); +} +/* }}} */ + +/* {{{ return string describing error code */ +PHP_FUNCTION(swoole_native_curl_strerror) { + zend_long code; + const char *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(code) + ZEND_PARSE_PARAMETERS_END(); + + str = curl_easy_strerror((CURLcode) code); + if (str) { + RETURN_STRING(str); + } else { + RETURN_NULL(); + } +} +/* }}} */ + +/* {{{ _php_curl_reset_handlers() + Reset all handlers of a given php_curl */ +static void _php_curl_reset_handlers(php_curl *ch) { + if (!Z_ISUNDEF(ch->handlers.write->stream)) { + zval_ptr_dtor(&ch->handlers.write->stream); + ZVAL_UNDEF(&ch->handlers.write->stream); + } + ch->handlers.write->fp = NULL; + ch->handlers.write->method = PHP_CURL_STDOUT; + + if (!Z_ISUNDEF(ch->handlers.write_header->stream)) { + zval_ptr_dtor(&ch->handlers.write_header->stream); + ZVAL_UNDEF(&ch->handlers.write_header->stream); + } + ch->handlers.write_header->fp = NULL; + ch->handlers.write_header->method = PHP_CURL_IGNORE; + + if (!Z_ISUNDEF(ch->handlers.read->stream)) { + zval_ptr_dtor(&ch->handlers.read->stream); + ZVAL_UNDEF(&ch->handlers.read->stream); + } + ch->handlers.read->fp = NULL; + ch->handlers.read->res = NULL; + ch->handlers.read->method = PHP_CURL_DIRECT; + + if (!Z_ISUNDEF(ch->handlers.std_err)) { + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_UNDEF(&ch->handlers.std_err); + } + + if (ZEND_FCC_INITIALIZED(ch->handlers.progress)) { + zend_fcc_dtor(&ch->handlers.progress); + } + + if (ZEND_FCC_INITIALIZED(ch->handlers.xferinfo)) { + zend_fcc_dtor(&ch->handlers.xferinfo); + } + + if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) { + zend_fcc_dtor(&ch->handlers.fnmatch); + } + +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) { + zend_fcc_dtor(&ch->handlers.sshhostkey); + } +#endif +} +/* }}} */ + +/* {{{ Reset all options of a libcurl session handle */ +PHP_FUNCTION(swoole_native_curl_reset) { + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (ch->in_callback) { + zend_throw_error(NULL, "%s(): Attempt to reset cURL handle from a callback", get_active_function_name()); + RETURN_THROWS(); + } + + curl_easy_reset(ch->cp); + _php_curl_reset_handlers(ch); + _php_curl_set_default_options(ch); +} +/* }}} */ + +/* {{{ URL encodes the given string */ +PHP_FUNCTION(swoole_native_curl_escape) { + zend_string *str; + char *res; + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(str))) { + RETURN_FALSE; + } + + if ((res = curl_easy_escape(ch->cp, ZSTR_VAL(str), ZSTR_LEN(str)))) { + RETVAL_STRING(res); + curl_free(res); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ URL decodes the given string */ +PHP_FUNCTION(swoole_native_curl_unescape) { + char *out = NULL; + int out_len; + zval *zid; + zend_string *str; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(str))) { + RETURN_FALSE; + } + + if ((out = curl_easy_unescape(ch->cp, ZSTR_VAL(str), ZSTR_LEN(str), &out_len))) { + RETVAL_STRINGL(out, out_len); + curl_free(out); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ pause and unpause a connection */ +PHP_FUNCTION(swoole_native_curl_pause) { + zend_long bitmask; + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + Z_PARAM_LONG(bitmask) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(curl_easy_pause(ch->cp, bitmask)); +} +/* }}} */ + +#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +/* {{{ perform connection upkeep checks */ +PHP_FUNCTION(swoole_native_curl_upkeep) { + CURLcode error; + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + error = curl_easy_upkeep(ch->cp); + SAVE_CURL_ERROR(ch, error); + + RETURN_BOOL(error == CURLE_OK); +} +/*}}} */ +#endif +#endif diff --git a/thirdparty/php84/curl/multi.cc b/thirdparty/php84/curl/multi.cc new file mode 100644 index 00000000000..c428e0dc22f --- /dev/null +++ b/thirdparty/php84/curl/multi.cc @@ -0,0 +1,638 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sterling Hughes | + +----------------------------------------------------------------------+ +*/ + +#include "php_swoole_cxx.h" +#include "zend_object_handlers.h" + +#if defined(SW_USE_CURL) && PHP_VERSION_ID >= 80400 + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS +#include "php_swoole_curl.h" + +using swoole::curl::Multi; +using swoole::curl::Selector; + +SW_EXTERN_C_BEGIN +#include "curl_interface.h" +#include "curl_arginfo.h" + +#include +#include + +#include +#include + +#define SAVE_CURLM_ERROR(__handle, __err) (__handle)->err.no = (int) __err; + +void swoole_curl_multi_set_in_coroutine(php_curlm *mh, bool value) { + zend_update_property_bool(nullptr, &mh->std, ZEND_STRL("in_coroutine"), value); +} + +bool swoole_curl_multi_is_in_coroutine(php_curlm *mh) { + zval rv; + zval *zv = zend_read_property_ex(nullptr, &mh->std, SW_ZSTR_KNOWN(SW_ZEND_STR_IN_COROUTINE), 1, &rv); + return zval_is_true(zv); +} + +/* CurlMultiHandle class */ +zend_class_entry *swoole_coroutine_curl_multi_handle_ce; + +static inline php_curlm *curl_multi_from_obj(zend_object *obj) { + return (php_curlm *) ((char *) (obj) -XtOffsetOf(php_curlm, std)); +} + +#define Z_CURL_MULTI_P(zv) curl_multi_from_obj(Z_OBJ_P(zv)) + +static void _php_curl_multi_free(php_curlm *mh); + +SW_EXTERN_C_END + +/* {{{ Returns a new cURL multi handle */ +PHP_FUNCTION(swoole_native_curl_multi_init) { + php_curlm *mh; + + ZEND_PARSE_PARAMETERS_NONE(); + + object_init_ex(return_value, swoole_coroutine_curl_multi_handle_ce); + mh = Z_CURL_MULTI_P(return_value); + mh->multi = new Multi(); + mh->multi->set_selector(new Selector()); + + swoole_curl_multi_set_in_coroutine(mh, true); + zend_llist_init(&mh->easyh, sizeof(zval), swoole_curl_multi_cleanup_list, 0); +} +/* }}} */ + +/* {{{ Add a normal cURL handle to a cURL multi handle */ +PHP_FUNCTION(swoole_native_curl_multi_add_handle) { + zval *z_mh; + zval *z_ch; + php_curlm *mh; + php_curl *ch; + CURLMcode error = CURLM_OK; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_OBJECT_OF_CLASS(z_ch, swoole_coroutine_curl_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + ch = Z_CURL_P(z_ch); + + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + + swoole_curl_verify_handlers(ch, /* reporterror */ true); + swoole_curl_cleanup_handle(ch); + + Z_ADDREF_P(z_ch); + zend_llist_add_element(&mh->easyh, z_ch); + + auto handle = swoole::curl::get_handle(ch->cp); + error = mh->multi->add_handle(handle); + + swoole_trace_log(SW_TRACE_CO_CURL, "multi=%p, cp=%p, handle=%p, error=%d", mh->multi, ch->cp, handle, error); + SAVE_CURLM_ERROR(mh, error); + RETURN_LONG((zend_long) error); +} +/* }}} */ + +void swoole_curl_multi_cleanup_list(void *data) /* {{{ */ +{ + zval *z_ch = (zval *) data; + + zval_ptr_dtor(z_ch); +} +/* }}} */ + +/* Used internally as comparison routine passed to zend_list_del_element */ +static int curl_compare_objects(zval *z1, zval *z2) /* {{{ */ +{ + return (Z_TYPE_P(z1) == Z_TYPE_P(z2) && Z_TYPE_P(z1) == IS_OBJECT && Z_OBJ_P(z1) == Z_OBJ_P(z2)); +} +/* }}} */ + +/* Used to find the php_curl resource for a given curl easy handle */ +static zval *_php_curl_multi_find_easy_handle(php_curlm *mh, CURL *easy) /* {{{ */ +{ + php_curl *tmp_ch; + zend_llist_position pos; + zval *pz_ch_temp; + + for (pz_ch_temp = (zval *) zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch_temp; + pz_ch_temp = (zval *) zend_llist_get_next_ex(&mh->easyh, &pos)) { + tmp_ch = swoole_curl_get_handle(pz_ch_temp, false, false); + + if (tmp_ch && tmp_ch->cp == easy) { + return pz_ch_temp; + } + } + + return NULL; +} +/* }}} */ + +/* {{{ Remove a multi handle from a set of cURL handles */ +PHP_FUNCTION(swoole_native_curl_multi_remove_handle) { + zval *z_mh; + zval *z_ch; + php_curlm *mh; + php_curl *ch; + CURLMcode error = CURLM_OK; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_OBJECT_OF_CLASS(z_ch, swoole_coroutine_curl_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + + ch = Z_CURL_P(z_ch); + auto handle = swoole::curl::get_handle(ch->cp); + if (handle && handle->multi) { + error = mh->multi->remove_handle(handle); + } else { + error = curl_multi_remove_handle(mh->multi, ch->cp); + } + + swoole_trace_log(SW_TRACE_CO_CURL, "multi=%p, cp=%p, handle=%p, error=%d", mh->multi, ch->cp, handle, error); + SAVE_CURLM_ERROR(mh, error); + RETVAL_LONG((zend_long) error); + zend_llist_del_element(&mh->easyh, z_ch, (int (*)(void *, void *)) curl_compare_objects); +} +/* }}} */ + +/* {{{ Get all the sockets associated with the cURL extension, which can then be "selected" */ +PHP_FUNCTION(swoole_native_curl_multi_select) { + zval *z_mh; + php_curlm *mh; + double timeout = 1.0; + int numfds = 0; + CURLMcode error = CURLM_OK; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_OPTIONAL + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + + RETURN_LONG(mh->multi->select(mh, timeout)); +} +/* }}} */ + +/* {{{ Run the sub-connections of the current cURL handle */ +PHP_FUNCTION(swoole_native_curl_multi_exec) { + zval *z_mh; + zval *z_still_running; + php_curlm *mh; + int still_running; + CURLMcode error = CURLM_OK; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_ZVAL(z_still_running) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + + { + zend_llist_position pos; + php_curl *ch; + zval *pz_ch; + + for (pz_ch = (zval *) zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch; + pz_ch = (zval *) zend_llist_get_next_ex(&mh->easyh, &pos)) { + ch = Z_CURL_P(pz_ch); + swoole_curl_verify_handlers(ch, /* reporterror */ true); + } + } + + error = mh->multi->perform(); + still_running = mh->multi->get_running_handles(); + ZEND_TRY_ASSIGN_REF_LONG(z_still_running, still_running); + + SAVE_CURLM_ERROR(mh, error); + RETURN_LONG((zend_long) error); +} +/* }}} */ + +/* {{{ Return the content of a cURL handle if CURLOPT_RETURNTRANSFER is set */ +PHP_FUNCTION(swoole_native_curl_multi_getcontent) { + zval *z_ch; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(z_ch, swoole_coroutine_curl_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + ch = Z_CURL_P(z_ch); + + if (ch->handlers.write->method == PHP_CURL_RETURN) { + if (!ch->handlers.write->buf.s) { + RETURN_EMPTY_STRING(); + } + smart_str_0(&ch->handlers.write->buf); + RETURN_STR_COPY(ch->handlers.write->buf.s); + } + + RETURN_NULL(); +} +/* }}} */ + +/* {{{ Get information about the current transfers */ +PHP_FUNCTION(swoole_native_curl_multi_info_read) { + zval *z_mh; + php_curlm *mh; + CURLMsg *tmp_msg; + int queued_msgs; + zval *zmsgs_in_queue = NULL; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(zmsgs_in_queue) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + + tmp_msg = curl_multi_info_read(mh->multi->get_multi_handle(), &queued_msgs); + if (tmp_msg == NULL) { + RETURN_FALSE; + } + + if (zmsgs_in_queue) { + ZEND_TRY_ASSIGN_REF_LONG(zmsgs_in_queue, queued_msgs); + } + + array_init(return_value); + add_assoc_long(return_value, "msg", tmp_msg->msg); + add_assoc_long(return_value, "result", tmp_msg->data.result); + + /* find the original easy curl handle */ + { + zval *pz_ch = _php_curl_multi_find_easy_handle(mh, tmp_msg->easy_handle); + if (pz_ch != NULL) { + /* we must save result to be able to read error message */ + ch = swoole_curl_get_handle(pz_ch, false, false); + SAVE_CURL_ERROR(ch, tmp_msg->data.result); + + Z_ADDREF_P(pz_ch); + add_assoc_zval(return_value, "handle", pz_ch); + } + } +} +/* }}} */ + +/* {{{ Close a set of cURL handles */ +PHP_FUNCTION(swoole_native_curl_multi_close) { + php_curlm *mh; + zval *z_mh; + + zend_llist_position pos; + zval *pz_ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + + for (pz_ch = (zval *) zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch; + pz_ch = (zval *) zend_llist_get_next_ex(&mh->easyh, &pos)) { + php_curl *ch = Z_CURL_P(pz_ch); + if (!ch) { + continue; + } + swoole_curl_verify_handlers(ch, 0); + auto handle = swoole::curl::get_handle(ch->cp); + if (handle) { + mh->multi->remove_handle(handle); + } else { + curl_multi_remove_handle(mh->multi, ch->cp); + } + } + zend_llist_clean(&mh->easyh); +} +/* }}} */ + +/* {{{ Return an integer containing the last multi curl error number */ +PHP_FUNCTION(swoole_native_curl_multi_errno) { + zval *z_mh; + php_curlm *mh; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + + RETURN_LONG(mh->err.no); +} +/* }}} */ + +/* {{{ return string describing error code */ +PHP_FUNCTION(swoole_native_curl_multi_strerror) { + zend_long code; + const char *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(code) + ZEND_PARSE_PARAMETERS_END(); + + str = curl_multi_strerror((CURLMcode) code); + if (str) { + RETURN_STRING(str); + } else { + RETURN_NULL(); + } +} +/* }}} */ + +static int _php_server_push_callback( + CURL *parent_ch, CURL *easy, size_t num_headers, struct curl_pushheaders *push_headers, void *userp) /* {{{ */ +{ + php_curl *ch; + php_curl *parent; + php_curlm *mh = (php_curlm *) userp; + size_t rval = CURL_PUSH_DENY; + zval *pz_parent_ch = NULL; + zval pz_ch; + zval headers; + zval retval; + + pz_parent_ch = _php_curl_multi_find_easy_handle(mh, parent_ch); + if (pz_parent_ch == NULL) { + return rval; + } + + parent = Z_CURL_P(pz_parent_ch); + + ch = swoole_curl_init_handle_into_zval(&pz_ch); + ch->cp = easy; + swoole_setup_easy_copy_handlers(ch, parent); + + auto parent_handle = swoole::curl::get_handle(parent->cp); + if (parent_handle) { + auto handle = swoole::curl::create_handle(easy); + handle->multi = parent_handle->multi; + } + + array_init(&headers); + for (size_t i = 0; i < num_headers; i++) { + char *header = curl_pushheader_bynum(push_headers, i); + add_next_index_string(&headers, header); + } + + ZEND_ASSERT(pz_parent_ch); + zval call_args[3] = {*pz_parent_ch, pz_ch, headers}; + + zend_call_known_fcc(&mh->handlers.server_push, &retval, /* param_count */ 3, call_args, /* named_params */ NULL); + zval_ptr_dtor_nogc(&headers); + + if (!Z_ISUNDEF(retval)) { + if (CURL_PUSH_DENY != zval_get_long(&retval)) { + rval = CURL_PUSH_OK; + zend_llist_add_element(&mh->easyh, &pz_ch); + } else { + /* libcurl will free this easy handle, avoid double free */ + ch->cp = NULL; + } + } + + return rval; +} +/* }}} */ + +static bool _php_curl_multi_setopt(php_curlm *mh, zend_long option, zval *zvalue, zval *return_value) /* {{{ */ +{ + CURLMcode error = CURLM_OK; + + switch (option) { + case CURLMOPT_PIPELINING: + case CURLMOPT_MAXCONNECTS: + case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE: + case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE: + case CURLMOPT_MAX_HOST_CONNECTIONS: + case CURLMOPT_MAX_PIPELINE_LENGTH: + case CURLMOPT_MAX_TOTAL_CONNECTIONS: +#if LIBCURL_VERSION_NUM >= 0x074300 /* Available since 7.67.0 */ + case CURLMOPT_MAX_CONCURRENT_STREAMS: +#endif + { + zend_long lval = zval_get_long(zvalue); + + if (option == CURLMOPT_PIPELINING && (lval & 1)) { +#if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ + php_error_docref(NULL, E_WARNING, "CURLPIPE_HTTP1 is no longer supported"); +#else + php_error_docref(NULL, E_DEPRECATED, "CURLPIPE_HTTP1 is deprecated"); +#endif + } + error = curl_multi_setopt(mh->multi, (CURLMoption) option, lval); + break; + } + case CURLMOPT_PUSHFUNCTION: { + /* See php_curl_set_callable_handler */ + if (ZEND_FCC_INITIALIZED(mh->handlers.server_push)) { + zend_fcc_dtor(&mh->handlers.server_push); + } + + char *error_str = NULL; + if (UNEXPECTED(!zend_is_callable_ex(zvalue, + /* object */ NULL, + /* check_flags */ 0, + /* callable_name */ NULL, + &mh->handlers.server_push, + /* error */ &error_str))) { + if (!EG(exception)) { + zend_argument_type_error(2, "must be a valid callback for option CURLMOPT_PUSHFUNCTION, %s", error_str); + } + efree(error_str); + return false; + } + zend_fcc_addref(&mh->handlers.server_push); + + error = curl_multi_setopt(mh->multi, CURLMOPT_PUSHFUNCTION, _php_server_push_callback); + if (error != CURLM_OK) { + return false; + } + error = curl_multi_setopt(mh->multi, CURLMOPT_PUSHDATA, mh); + break; + } + default: + zend_argument_value_error(2, "is not a valid cURL multi option"); + error = CURLM_UNKNOWN_OPTION; + break; + } + + SAVE_CURLM_ERROR(mh, error); + + return error == CURLM_OK; +} +/* }}} */ + +/* {{{ Set an option for the curl multi handle */ +PHP_FUNCTION(swoole_native_curl_multi_setopt) { + zval *z_mh, *zvalue; + zend_long options; + php_curlm *mh; + + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_LONG(options) + Z_PARAM_ZVAL(zvalue) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + if (_php_curl_multi_setopt(mh, (CURLMoption) options, zvalue, return_value)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* CurlMultiHandle class */ + +static zend_object_handlers swoole_coroutine_curl_multi_handle_handlers; + +static zend_object *swoole_curl_multi_create_object(zend_class_entry *class_type) { + php_curlm *intern = (php_curlm *) zend_object_alloc(sizeof(php_curlm), class_type); + + zend_object_std_init(&intern->std, class_type); + object_properties_init(&intern->std, class_type); + intern->std.handlers = &swoole_coroutine_curl_multi_handle_handlers; + + return &intern->std; +} + +static zend_function *swoole_curl_multi_get_constructor(zend_object *object) { + zend_throw_error(NULL, "Cannot directly construct CurlMultiHandle, use curl_multi_init() instead"); + return NULL; +} + +static void swoole_curl_multi_free_obj(zend_object *object) { + php_curlm *mh = curl_multi_from_obj(object); + + if (!mh->multi) { + /* Can happen if constructor throws. */ + zend_object_std_dtor(&mh->std); + return; + } + + _php_curl_multi_free(mh); + zend_object_std_dtor(&mh->std); +} + +static HashTable *swoole_curl_multi_get_gc(zend_object *object, zval **table, int *n) { + php_curlm *curl_multi = curl_multi_from_obj(object); + + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + + if (ZEND_FCC_INITIALIZED(curl_multi->handlers.server_push)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl_multi->handlers.server_push); + } + + zend_llist_position pos; + for (zval *pz_ch = (zval *) zend_llist_get_first_ex(&curl_multi->easyh, &pos); pz_ch; + pz_ch = (zval *) zend_llist_get_next_ex(&curl_multi->easyh, &pos)) { + zend_get_gc_buffer_add_zval(gc_buffer, pz_ch); + } + + zend_get_gc_buffer_use(gc_buffer, table, n); + + return zend_std_get_properties(object); +} + +void curl_multi_register_class(const zend_function_entry *method_entries) { + swoole_coroutine_curl_multi_handle_ce = curl_multi_ce; + swoole_coroutine_curl_multi_handle_ce->create_object = swoole_curl_multi_create_object; + + memcpy(&swoole_coroutine_curl_multi_handle_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + swoole_coroutine_curl_multi_handle_handlers.offset = XtOffsetOf(php_curlm, std); + swoole_coroutine_curl_multi_handle_handlers.free_obj = swoole_curl_multi_free_obj; + swoole_coroutine_curl_multi_handle_handlers.get_gc = swoole_curl_multi_get_gc; + swoole_coroutine_curl_multi_handle_handlers.get_constructor = swoole_curl_multi_get_constructor; + swoole_coroutine_curl_multi_handle_handlers.clone_obj = NULL; + swoole_coroutine_curl_multi_handle_handlers.cast_object = swoole_curl_cast_object; + swoole_coroutine_curl_multi_handle_handlers.compare = [](zval *o1, zval *o2) { return ZEND_UNCOMPARABLE; }; + + zend_declare_property_bool(swoole_coroutine_curl_multi_handle_ce, ZEND_STRL("in_coroutine"), 0, ZEND_ACC_PUBLIC); +} + +static void _php_curl_multi_free(php_curlm *mh) { + bool is_in_coroutine = swoole_curl_multi_is_in_coroutine(mh); + for (zend_llist_element *element = mh->easyh.head; element; element = element->next) { + zval *z_ch = (zval *) element->data; + php_curl *ch; + if (OBJ_FLAGS(Z_OBJ_P(z_ch)) & IS_OBJ_FREE_CALLED) { + continue; + } + if ((ch = swoole_curl_get_handle(z_ch, true, false))) { + swoole_curl_verify_handlers(ch, 0); + auto handle = swoole::curl::get_handle(ch->cp); + if (is_in_coroutine && handle) { + mh->multi->remove_handle(handle); + } else { + curl_multi_remove_handle(mh->multi, ch->cp); + } + } + } + if (mh->multi) { + if (is_in_coroutine) { + delete mh->multi; + } else { + curl_multi_cleanup(mh->multi); + } + mh->multi = nullptr; + } + zend_llist_clean(&mh->easyh); + if (ZEND_FCC_INITIALIZED(mh->handlers.server_push)) { + zend_fcc_dtor(&mh->handlers.server_push); + } +} +#endif diff --git a/thirdparty/php84/curl/php_curl.h b/thirdparty/php84/curl/php_curl.h new file mode 100644 index 00000000000..e2b551bad98 --- /dev/null +++ b/thirdparty/php84/curl/php_curl.h @@ -0,0 +1,49 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sterling Hughes | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#if defined(SW_USE_CURL) && PHP_VERSION_ID >= 80400 + +#ifndef _PHP_CURL_H +#define _PHP_CURL_H + +#include "php.h" +#include "zend_smart_str.h" + +#define PHP_CURL_DEBUG 0 + +#ifdef PHP_WIN32 +# ifdef PHP_CURL_EXPORTS +# define PHP_CURL_API __declspec(dllexport) +# else +# define PHP_CURL_API __declspec(dllimport) +# endif +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_CURL_API __attribute__ ((visibility("default"))) +#else +# define PHP_CURL_API +#endif + +PHP_CURL_API extern zend_class_entry *curl_ce; +PHP_CURL_API extern zend_class_entry *curl_share_ce; +PHP_CURL_API extern zend_class_entry *curl_multi_ce; +PHP_CURL_API extern zend_class_entry *swoole_coroutine_curl_handle_ce; +PHP_CURL_API extern zend_class_entry *swoole_coroutine_curl_multi_handle_ce; +PHP_CURL_API extern zend_class_entry *curl_CURLFile_class; +PHP_CURL_API extern zend_class_entry *curl_CURLStringFile_class; + +#endif /* _PHP_CURL_H */ +#endif diff --git a/thirdparty/php84/hash/hash_sha.c b/thirdparty/php84/hash/hash_sha.c new file mode 100644 index 00000000000..08521ce14f1 --- /dev/null +++ b/thirdparty/php84/hash/hash_sha.c @@ -0,0 +1,792 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Steffan Esser | + | Sara Golemon | + +----------------------------------------------------------------------+ +*/ + +#include "php_swoole.h" + +#if PHP_VERSION_ID >= 80400 +#include "php_hash.h" +#include "php_hash_sha.h" +#include "Zend/zend_cpuinfo.h" + +static const unsigned char PADDING[128] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +/* {{{ SHAEncode32 + Encodes input (uint32_t) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void SHAEncode32(unsigned char *output, uint32_t *input, unsigned int len) { + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char) ((input[i] >> 24) & 0xff); + output[j + 1] = (unsigned char) ((input[i] >> 16) & 0xff); + output[j + 2] = (unsigned char) ((input[i] >> 8) & 0xff); + output[j + 3] = (unsigned char) (input[i] & 0xff); + } +} +/* }}} */ + +/* {{{ SHADecode32 + Decodes input (unsigned char) into output (uint32_t). Assumes len is + a multiple of 4. + */ +static void SHADecode32(uint32_t *output, const unsigned char *input, unsigned int len) { + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((uint32_t) input[j + 3]) | (((uint32_t) input[j + 2]) << 8) | (((uint32_t) input[j + 1]) << 16) | + (((uint32_t) input[j]) << 24); +} +/* }}} */ + +const php_hash_ops php_hash_sha1_ops = {"sha1", + (php_hash_init_func_t) PHP_SHA1InitArgs, + (php_hash_update_func_t) PHP_SHA1Update, + (php_hash_final_func_t) PHP_SHA1Final, + php_hash_copy, + php_hash_serialize, + php_hash_unserialize, + PHP_SHA1_SPEC, + 20, + 64, + sizeof(PHP_SHA1_CTX), + 1}; + +/* sha224/sha256 */ + +const php_hash_ops php_hash_sha256_ops = {"sha256", + (php_hash_init_func_t) PHP_SHA256InitArgs, + (php_hash_update_func_t) PHP_SHA256Update, + (php_hash_final_func_t) PHP_SHA256Final, + php_hash_copy, + php_hash_serialize, + php_hash_unserialize, + PHP_SHA256_SPEC, + 32, + 64, + sizeof(PHP_SHA256_CTX), + 1}; + +const php_hash_ops php_hash_sha224_ops = {"sha224", + (php_hash_init_func_t) PHP_SHA224InitArgs, + (php_hash_update_func_t) PHP_SHA224Update, + (php_hash_final_func_t) PHP_SHA224Final, + php_hash_copy, + php_hash_serialize, + php_hash_unserialize, + PHP_SHA224_SPEC, + 28, + 64, + sizeof(PHP_SHA224_CTX), + 1}; + +#define ROTR32(b, x) ((x >> b) | (x << (32 - b))) +#define ROTR64(b, x) ((x >> b) | (x << (64 - b))) +#define SHR(b, x) (x >> b) + +/* Ch */ +#define SHA256_F0(x, y, z) (((x) & (y)) ^ ((~(x)) & (z))) +/* Maj */ +#define SHA256_F1(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +/* SUM0 */ +#define SHA256_F2(x) (ROTR32(2, (x)) ^ ROTR32(13, (x)) ^ ROTR32(22, (x))) +/* SUM1 */ +#define SHA256_F3(x) (ROTR32(6, (x)) ^ ROTR32(11, (x)) ^ ROTR32(25, (x))) +/* OM0 */ +#define SHA256_F4(x) (ROTR32(7, (x)) ^ ROTR32(18, (x)) ^ SHR(3, (x))) +/* OM1 */ +#define SHA256_F5(x) (ROTR32(17, (x)) ^ ROTR32(19, (x)) ^ SHR(10, (x))) + +static const uint32_t SHA256_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + +/* {{{ PHP_SHA256InitArgs + * SHA256 initialization. Begins an SHA256 operation, writing a new context. + */ +PHP_HASH_API void PHP_SHA256InitArgs(PHP_SHA256_CTX *context, ZEND_ATTRIBUTE_UNUSED HashTable *args) { + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. + */ + context->state[0] = 0x6a09e667; + context->state[1] = 0xbb67ae85; + context->state[2] = 0x3c6ef372; + context->state[3] = 0xa54ff53a; + context->state[4] = 0x510e527f; + context->state[5] = 0x9b05688c; + context->state[6] = 0x1f83d9ab; + context->state[7] = 0x5be0cd19; +} +/* }}} */ + +/* {{{ SHA256Transform + * SHA256 basic transformation. Transforms state based on block. + */ +static void SHA256Transform(uint32_t state[8], const unsigned char block[64]) { + uint32_t a = state[0], b = state[1], c = state[2], d = state[3]; + uint32_t e = state[4], f = state[5], g = state[6], h = state[7]; + uint32_t x[16], T1, T2, W[64]; + int i; + + SHADecode32(x, block, 64); + + /* Schedule */ + for (i = 0; i < 16; i++) { + W[i] = x[i]; + } + for (i = 16; i < 64; i++) { + W[i] = SHA256_F5(W[i - 2]) + W[i - 7] + SHA256_F4(W[i - 15]) + W[i - 16]; + } + + for (i = 0; i < 64; i++) { + T1 = h + SHA256_F3(e) + SHA256_F0(e, f, g) + SHA256_K[i] + W[i]; + T2 = SHA256_F2(a) + SHA256_F1(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + state[5] += f; + state[6] += g; + state[7] += h; + + /* Zeroize sensitive information. */ + ZEND_SECURE_ZERO((unsigned char *) x, sizeof(x)); +} +/* }}} */ + +/* {{{ PHP_SHA224InitArgs + * SHA224 initialization. Begins an SHA224 operation, writing a new context. + */ +PHP_HASH_API void PHP_SHA224InitArgs(PHP_SHA224_CTX *context, ZEND_ATTRIBUTE_UNUSED HashTable *args) { + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. + */ + context->state[0] = 0xc1059ed8; + context->state[1] = 0x367cd507; + context->state[2] = 0x3070dd17; + context->state[3] = 0xf70e5939; + context->state[4] = 0xffc00b31; + context->state[5] = 0x68581511; + context->state[6] = 0x64f98fa7; + context->state[7] = 0xbefa4fa4; +} +/* }}} */ + +/* {{{ PHP_SHA224Update + SHA224 block update operation. Continues an SHA224 message-digest + operation, processing another message block, and updating the + context. + */ +PHP_HASH_API void PHP_SHA224Update(PHP_SHA224_CTX *context, const unsigned char *input, size_t inputLen) { + unsigned int index, partLen; + size_t i; + + /* Compute number of bytes mod 64 */ + index = (unsigned int) ((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((uint32_t) inputLen << 3)) < ((uint32_t) inputLen << 3)) { + context->count[1]++; + } + context->count[1] += (uint32_t) (inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. + */ + if (inputLen >= partLen) { + memcpy((unsigned char *) &context->buffer[index], (unsigned char *) input, partLen); + SHA256Transform(context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) { + SHA256Transform(context->state, &input[i]); + } + + index = 0; + } else { + i = 0; + } + + /* Buffer remaining input */ + memcpy((unsigned char *) &context->buffer[index], (unsigned char *) &input[i], inputLen - i); +} +/* }}} */ + +/* {{{ PHP_SHA224Final + SHA224 finalization. Ends an SHA224 message-digest operation, writing the + the message digest and zeroizing the context. + */ +PHP_HASH_API void PHP_SHA224Final(unsigned char digest[28], PHP_SHA224_CTX *context) { + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + bits[7] = (unsigned char) (context->count[0] & 0xFF); + bits[6] = (unsigned char) ((context->count[0] >> 8) & 0xFF); + bits[5] = (unsigned char) ((context->count[0] >> 16) & 0xFF); + bits[4] = (unsigned char) ((context->count[0] >> 24) & 0xFF); + bits[3] = (unsigned char) (context->count[1] & 0xFF); + bits[2] = (unsigned char) ((context->count[1] >> 8) & 0xFF); + bits[1] = (unsigned char) ((context->count[1] >> 16) & 0xFF); + bits[0] = (unsigned char) ((context->count[1] >> 24) & 0xFF); + + /* Pad out to 56 mod 64. + */ + index = (unsigned int) ((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + PHP_SHA224Update(context, PADDING, padLen); + + /* Append length (before padding) */ + PHP_SHA224Update(context, bits, 8); + + /* Store state in digest */ + SHAEncode32(digest, context->state, 28); + + /* Zeroize sensitive information. + */ + ZEND_SECURE_ZERO((unsigned char *) context, sizeof(*context)); +} +/* }}} */ + +/* {{{ PHP_SHA256Update + SHA256 block update operation. Continues an SHA256 message-digest + operation, processing another message block, and updating the + context. + */ +PHP_HASH_API void PHP_SHA256Update(PHP_SHA256_CTX *context, const unsigned char *input, size_t inputLen) { + unsigned int index, partLen; + size_t i; + + /* Compute number of bytes mod 64 */ + index = (unsigned int) ((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((uint32_t) inputLen << 3)) < ((uint32_t) inputLen << 3)) { + context->count[1]++; + } + context->count[1] += (uint32_t) (inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. + */ + if (inputLen >= partLen) { + memcpy((unsigned char *) &context->buffer[index], (unsigned char *) input, partLen); + SHA256Transform(context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) { + SHA256Transform(context->state, &input[i]); + } + + index = 0; + } else { + i = 0; + } + + /* Buffer remaining input */ + memcpy((unsigned char *) &context->buffer[index], (unsigned char *) &input[i], inputLen - i); +} +/* }}} */ + +/* {{{ PHP_SHA256Final + SHA256 finalization. Ends an SHA256 message-digest operation, writing the + the message digest and zeroizing the context. + */ +PHP_HASH_API void PHP_SHA256Final(unsigned char digest[32], PHP_SHA256_CTX *context) { + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + bits[7] = (unsigned char) (context->count[0] & 0xFF); + bits[6] = (unsigned char) ((context->count[0] >> 8) & 0xFF); + bits[5] = (unsigned char) ((context->count[0] >> 16) & 0xFF); + bits[4] = (unsigned char) ((context->count[0] >> 24) & 0xFF); + bits[3] = (unsigned char) (context->count[1] & 0xFF); + bits[2] = (unsigned char) ((context->count[1] >> 8) & 0xFF); + bits[1] = (unsigned char) ((context->count[1] >> 16) & 0xFF); + bits[0] = (unsigned char) ((context->count[1] >> 24) & 0xFF); + + /* Pad out to 56 mod 64. + */ + index = (unsigned int) ((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + PHP_SHA256Update(context, PADDING, padLen); + + /* Append length (before padding) */ + PHP_SHA256Update(context, bits, 8); + + /* Store state in digest */ + SHAEncode32(digest, context->state, 32); + + /* Zeroize sensitive information. + */ + ZEND_SECURE_ZERO((unsigned char *) context, sizeof(*context)); +} +/* }}} */ + +/* sha384/sha512 */ + +/* Ch */ +#define SHA512_F0(x, y, z) (((x) & (y)) ^ ((~(x)) & (z))) +/* Maj */ +#define SHA512_F1(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +/* SUM0 */ +#define SHA512_F2(x) (ROTR64(28, x) ^ ROTR64(34, x) ^ ROTR64(39, x)) +/* SUM1 */ +#define SHA512_F3(x) (ROTR64(14, x) ^ ROTR64(18, x) ^ ROTR64(41, x)) +/* OM0 */ +#define SHA512_F4(x) (ROTR64(1, x) ^ ROTR64(8, x) ^ SHR(7, x)) +/* OM1 */ +#define SHA512_F5(x) (ROTR64(19, x) ^ ROTR64(61, x) ^ SHR(6, x)) + +static const uint64_t SHA512_K[128] = { + L64(0x428a2f98d728ae22), L64(0x7137449123ef65cd), L64(0xb5c0fbcfec4d3b2f), L64(0xe9b5dba58189dbbc), + L64(0x3956c25bf348b538), L64(0x59f111f1b605d019), L64(0x923f82a4af194f9b), L64(0xab1c5ed5da6d8118), + L64(0xd807aa98a3030242), L64(0x12835b0145706fbe), L64(0x243185be4ee4b28c), L64(0x550c7dc3d5ffb4e2), + L64(0x72be5d74f27b896f), L64(0x80deb1fe3b1696b1), L64(0x9bdc06a725c71235), L64(0xc19bf174cf692694), + L64(0xe49b69c19ef14ad2), L64(0xefbe4786384f25e3), L64(0x0fc19dc68b8cd5b5), L64(0x240ca1cc77ac9c65), + L64(0x2de92c6f592b0275), L64(0x4a7484aa6ea6e483), L64(0x5cb0a9dcbd41fbd4), L64(0x76f988da831153b5), + L64(0x983e5152ee66dfab), L64(0xa831c66d2db43210), L64(0xb00327c898fb213f), L64(0xbf597fc7beef0ee4), + L64(0xc6e00bf33da88fc2), L64(0xd5a79147930aa725), L64(0x06ca6351e003826f), L64(0x142929670a0e6e70), + L64(0x27b70a8546d22ffc), L64(0x2e1b21385c26c926), L64(0x4d2c6dfc5ac42aed), L64(0x53380d139d95b3df), + L64(0x650a73548baf63de), L64(0x766a0abb3c77b2a8), L64(0x81c2c92e47edaee6), L64(0x92722c851482353b), + L64(0xa2bfe8a14cf10364), L64(0xa81a664bbc423001), L64(0xc24b8b70d0f89791), L64(0xc76c51a30654be30), + L64(0xd192e819d6ef5218), L64(0xd69906245565a910), L64(0xf40e35855771202a), L64(0x106aa07032bbd1b8), + L64(0x19a4c116b8d2d0c8), L64(0x1e376c085141ab53), L64(0x2748774cdf8eeb99), L64(0x34b0bcb5e19b48a8), + L64(0x391c0cb3c5c95a63), L64(0x4ed8aa4ae3418acb), L64(0x5b9cca4f7763e373), L64(0x682e6ff3d6b2b8a3), + L64(0x748f82ee5defb2fc), L64(0x78a5636f43172f60), L64(0x84c87814a1f0ab72), L64(0x8cc702081a6439ec), + L64(0x90befffa23631e28), L64(0xa4506cebde82bde9), L64(0xbef9a3f7b2c67915), L64(0xc67178f2e372532b), + L64(0xca273eceea26619c), L64(0xd186b8c721c0c207), L64(0xeada7dd6cde0eb1e), L64(0xf57d4f7fee6ed178), + L64(0x06f067aa72176fba), L64(0x0a637dc5a2c898a6), L64(0x113f9804bef90dae), L64(0x1b710b35131c471b), + L64(0x28db77f523047d84), L64(0x32caab7b40c72493), L64(0x3c9ebe0a15c9bebc), L64(0x431d67c49c100d4c), + L64(0x4cc5d4becb3e42b6), L64(0x597f299cfc657e2a), L64(0x5fcb6fab3ad6faec), L64(0x6c44198c4a475817)}; + +/* {{{ SHAEncode64 + Encodes input (uint64_t) into output (unsigned char). Assumes len is + a multiple of 8. + */ +static void SHAEncode64(unsigned char *output, uint64_t *input, unsigned int len) { + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 8) { + output[j] = (unsigned char) ((input[i] >> 56) & 0xff); + output[j + 1] = (unsigned char) ((input[i] >> 48) & 0xff); + output[j + 2] = (unsigned char) ((input[i] >> 40) & 0xff); + output[j + 3] = (unsigned char) ((input[i] >> 32) & 0xff); + output[j + 4] = (unsigned char) ((input[i] >> 24) & 0xff); + output[j + 5] = (unsigned char) ((input[i] >> 16) & 0xff); + output[j + 6] = (unsigned char) ((input[i] >> 8) & 0xff); + output[j + 7] = (unsigned char) (input[i] & 0xff); + } +} +/* }}} */ + +/* {{{ SHADecode64 + Decodes input (unsigned char) into output (uint64_t). Assumes len is + a multiple of 8. + */ +static void SHADecode64(uint64_t *output, const unsigned char *input, unsigned int len) { + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 8) + output[i] = ((uint64_t) input[j + 7]) | (((uint64_t) input[j + 6]) << 8) | (((uint64_t) input[j + 5]) << 16) | + (((uint64_t) input[j + 4]) << 24) | (((uint64_t) input[j + 3]) << 32) | + (((uint64_t) input[j + 2]) << 40) | (((uint64_t) input[j + 1]) << 48) | + (((uint64_t) input[j]) << 56); +} +/* }}} */ + +/* {{{ PHP_SHA384InitArgs + * SHA384 initialization. Begins an SHA384 operation, writing a new context. + */ +PHP_HASH_API void PHP_SHA384InitArgs(PHP_SHA384_CTX *context, ZEND_ATTRIBUTE_UNUSED HashTable *args) { + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. + */ + context->state[0] = L64(0xcbbb9d5dc1059ed8); + context->state[1] = L64(0x629a292a367cd507); + context->state[2] = L64(0x9159015a3070dd17); + context->state[3] = L64(0x152fecd8f70e5939); + context->state[4] = L64(0x67332667ffc00b31); + context->state[5] = L64(0x8eb44a8768581511); + context->state[6] = L64(0xdb0c2e0d64f98fa7); + context->state[7] = L64(0x47b5481dbefa4fa4); +} +/* }}} */ + +/* {{{ SHA512Transform + * SHA512 basic transformation. Transforms state based on block. + * SHA384 uses the exact same algorithm + */ +static void SHA512Transform(uint64_t state[8], const unsigned char block[128]) { + uint64_t a = state[0], b = state[1], c = state[2], d = state[3]; + uint64_t e = state[4], f = state[5], g = state[6], h = state[7]; + uint64_t x[16], T1, T2, W[80]; + int i; + + SHADecode64(x, block, 128); + + /* Schedule */ + for (i = 0; i < 16; i++) { + W[i] = x[i]; + } + for (i = 16; i < 80; i++) { + W[i] = SHA512_F5(W[i - 2]) + W[i - 7] + SHA512_F4(W[i - 15]) + W[i - 16]; + } + + for (i = 0; i < 80; i++) { + T1 = h + SHA512_F3(e) + SHA512_F0(e, f, g) + SHA512_K[i] + W[i]; + T2 = SHA512_F2(a) + SHA512_F1(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + state[5] += f; + state[6] += g; + state[7] += h; + + /* Zeroize sensitive information. */ + ZEND_SECURE_ZERO((unsigned char *) x, sizeof(x)); +} +/* }}} */ + +/* {{{ PHP_SHA384Update + SHA384 block update operation. Continues an SHA384 message-digest + operation, processing another message block, and updating the + context. + */ +PHP_HASH_API void PHP_SHA384Update(PHP_SHA384_CTX *context, const unsigned char *input, size_t inputLen) { + unsigned int index, partLen; + size_t i = 0; + + /* Compute number of bytes mod 128 */ + index = (unsigned int) ((context->count[0] >> 3) & 0x7F); + + /* Update number of bits */ + if ((context->count[0] += ((uint64_t) inputLen << 3)) < ((uint64_t) inputLen << 3)) { + context->count[1]++; + } + /* The cast may seem unnecessary, but on 32-bit this makes sure the result is 0 without invoking undefined + * behaviour. */ + context->count[1] += (uint64_t) inputLen >> 61; + + partLen = 128 - index; + + /* Transform as many times as possible. + */ + if (inputLen >= partLen) { + memcpy((unsigned char *) &context->buffer[index], (unsigned char *) input, partLen); + SHA512Transform(context->state, context->buffer); + + for (i = partLen; i + 127 < inputLen; i += 128) { + SHA512Transform(context->state, &input[i]); + } + + index = 0; + } + + /* Buffer remaining input */ + memcpy((unsigned char *) &context->buffer[index], (unsigned char *) &input[i], inputLen - i); +} +/* }}} */ + +/* {{{ PHP_SHA384Final + SHA384 finalization. Ends an SHA384 message-digest operation, writing the + the message digest and zeroizing the context. + */ +PHP_HASH_API void PHP_SHA384Final(unsigned char digest[48], PHP_SHA384_CTX *context) { + unsigned char bits[16]; + unsigned int index, padLen; + + /* Save number of bits */ + bits[15] = (unsigned char) (context->count[0] & 0xFF); + bits[14] = (unsigned char) ((context->count[0] >> 8) & 0xFF); + bits[13] = (unsigned char) ((context->count[0] >> 16) & 0xFF); + bits[12] = (unsigned char) ((context->count[0] >> 24) & 0xFF); + bits[11] = (unsigned char) ((context->count[0] >> 32) & 0xFF); + bits[10] = (unsigned char) ((context->count[0] >> 40) & 0xFF); + bits[9] = (unsigned char) ((context->count[0] >> 48) & 0xFF); + bits[8] = (unsigned char) ((context->count[0] >> 56) & 0xFF); + bits[7] = (unsigned char) (context->count[1] & 0xFF); + bits[6] = (unsigned char) ((context->count[1] >> 8) & 0xFF); + bits[5] = (unsigned char) ((context->count[1] >> 16) & 0xFF); + bits[4] = (unsigned char) ((context->count[1] >> 24) & 0xFF); + bits[3] = (unsigned char) ((context->count[1] >> 32) & 0xFF); + bits[2] = (unsigned char) ((context->count[1] >> 40) & 0xFF); + bits[1] = (unsigned char) ((context->count[1] >> 48) & 0xFF); + bits[0] = (unsigned char) ((context->count[1] >> 56) & 0xFF); + + /* Pad out to 112 mod 128. + */ + index = (unsigned int) ((context->count[0] >> 3) & 0x7f); + padLen = (index < 112) ? (112 - index) : (240 - index); + PHP_SHA384Update(context, PADDING, padLen); + + /* Append length (before padding) */ + PHP_SHA384Update(context, bits, 16); + + /* Store state in digest */ + SHAEncode64(digest, context->state, 48); + + /* Zeroize sensitive information. + */ + ZEND_SECURE_ZERO((unsigned char *) context, sizeof(*context)); +} +/* }}} */ + +const php_hash_ops php_hash_sha384_ops = {"sha384", + (php_hash_init_func_t) PHP_SHA384InitArgs, + (php_hash_update_func_t) PHP_SHA384Update, + (php_hash_final_func_t) PHP_SHA384Final, + php_hash_copy, + php_hash_serialize, + php_hash_unserialize, + PHP_SHA384_SPEC, + 48, + 128, + sizeof(PHP_SHA384_CTX), + 1}; + +/* {{{ PHP_SHA512InitArgs + * SHA512 initialization. Begins an SHA512 operation, writing a new context. + */ +PHP_HASH_API void PHP_SHA512InitArgs(PHP_SHA512_CTX *context, ZEND_ATTRIBUTE_UNUSED HashTable *args) { + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. + */ + context->state[0] = L64(0x6a09e667f3bcc908); + context->state[1] = L64(0xbb67ae8584caa73b); + context->state[2] = L64(0x3c6ef372fe94f82b); + context->state[3] = L64(0xa54ff53a5f1d36f1); + context->state[4] = L64(0x510e527fade682d1); + context->state[5] = L64(0x9b05688c2b3e6c1f); + context->state[6] = L64(0x1f83d9abfb41bd6b); + context->state[7] = L64(0x5be0cd19137e2179); +} +/* }}} */ + +/* {{{ PHP_SHA512_256InitArgs + * SHA512/245 initialization. Identical algorithm to SHA512, using alternate initval and truncation + */ +PHP_HASH_API void PHP_SHA512_256InitArgs(PHP_SHA512_CTX *context, ZEND_ATTRIBUTE_UNUSED HashTable *args) { + context->count[0] = context->count[1] = 0; + + context->state[0] = L64(0x22312194FC2BF72C); + context->state[1] = L64(0x9F555FA3C84C64C2); + context->state[2] = L64(0x2393B86B6F53B151); + context->state[3] = L64(0x963877195940EABD); + context->state[4] = L64(0x96283EE2A88EFFE3); + context->state[5] = L64(0xBE5E1E2553863992); + context->state[6] = L64(0x2B0199FC2C85B8AA); + context->state[7] = L64(0x0EB72DDC81C52CA2); +} +/* }}} */ + +/* {{{ PHP_SHA512_224InitArgs + * SHA512/224 initialization. Identical algorithm to SHA512, using alternate initval and truncation + */ +PHP_HASH_API void PHP_SHA512_224InitArgs(PHP_SHA512_CTX *context, ZEND_ATTRIBUTE_UNUSED HashTable *args) { + context->count[0] = context->count[1] = 0; + + context->state[0] = L64(0x8C3D37C819544DA2); + context->state[1] = L64(0x73E1996689DCD4D6); + context->state[2] = L64(0x1DFAB7AE32FF9C82); + context->state[3] = L64(0x679DD514582F9FCF); + context->state[4] = L64(0x0F6D2B697BD44DA8); + context->state[5] = L64(0x77E36F7304C48942); + context->state[6] = L64(0x3F9D85A86A1D36C8); + context->state[7] = L64(0x1112E6AD91D692A1); +} +/* }}} */ + +/* {{{ PHP_SHA512Update + SHA512 block update operation. Continues an SHA512 message-digest + operation, processing another message block, and updating the + context. + */ +PHP_HASH_API void PHP_SHA512Update(PHP_SHA512_CTX *context, const unsigned char *input, size_t inputLen) { + unsigned int index, partLen; + size_t i; + + /* Compute number of bytes mod 128 */ + index = (unsigned int) ((context->count[0] >> 3) & 0x7F); + + /* Update number of bits */ + if ((context->count[0] += ((uint64_t) inputLen << 3)) < ((uint64_t) inputLen << 3)) { + context->count[1]++; + } + /* The cast may seem unnecessary, but on 32-bit this makes sure the result is 0 without invoking undefined + * behaviour. */ + context->count[1] += (uint64_t) inputLen >> 61; + + partLen = 128 - index; + + /* Transform as many times as possible. + */ + if (inputLen >= partLen) { + memcpy((unsigned char *) &context->buffer[index], (unsigned char *) input, partLen); + SHA512Transform(context->state, context->buffer); + + for (i = partLen; i + 127 < inputLen; i += 128) { + SHA512Transform(context->state, &input[i]); + } + + index = 0; + } else { + i = 0; + } + + /* Buffer remaining input */ + memcpy((unsigned char *) &context->buffer[index], (unsigned char *) &input[i], inputLen - i); +} +/* }}} */ + +/* {{{ PHP_SHA512Final + SHA512 finalization. Ends an SHA512 message-digest operation, writing the + the message digest and zeroizing the context. + */ +PHP_HASH_API void PHP_SHA512Final(unsigned char digest[64], PHP_SHA512_CTX *context) { + unsigned char bits[16]; + unsigned int index, padLen; + + /* Save number of bits */ + bits[15] = (unsigned char) (context->count[0] & 0xFF); + bits[14] = (unsigned char) ((context->count[0] >> 8) & 0xFF); + bits[13] = (unsigned char) ((context->count[0] >> 16) & 0xFF); + bits[12] = (unsigned char) ((context->count[0] >> 24) & 0xFF); + bits[11] = (unsigned char) ((context->count[0] >> 32) & 0xFF); + bits[10] = (unsigned char) ((context->count[0] >> 40) & 0xFF); + bits[9] = (unsigned char) ((context->count[0] >> 48) & 0xFF); + bits[8] = (unsigned char) ((context->count[0] >> 56) & 0xFF); + bits[7] = (unsigned char) (context->count[1] & 0xFF); + bits[6] = (unsigned char) ((context->count[1] >> 8) & 0xFF); + bits[5] = (unsigned char) ((context->count[1] >> 16) & 0xFF); + bits[4] = (unsigned char) ((context->count[1] >> 24) & 0xFF); + bits[3] = (unsigned char) ((context->count[1] >> 32) & 0xFF); + bits[2] = (unsigned char) ((context->count[1] >> 40) & 0xFF); + bits[1] = (unsigned char) ((context->count[1] >> 48) & 0xFF); + bits[0] = (unsigned char) ((context->count[1] >> 56) & 0xFF); + + /* Pad out to 112 mod 128. + */ + index = (unsigned int) ((context->count[0] >> 3) & 0x7f); + padLen = (index < 112) ? (112 - index) : (240 - index); + PHP_SHA512Update(context, PADDING, padLen); + + /* Append length (before padding) */ + PHP_SHA512Update(context, bits, 16); + + /* Store state in digest */ + SHAEncode64(digest, context->state, 64); + + /* Zeroize sensitive information. + */ + ZEND_SECURE_ZERO((unsigned char *) context, sizeof(*context)); +} +/* }}} */ + +/* {{{ PHP_SHA512_256Final + SHA512/256 finalization. Identical to SHA512Final, but with truncation + */ +PHP_HASH_API void PHP_SHA512_256Final(unsigned char digest[32], PHP_SHA512_CTX *context) { + unsigned char full_digest[64]; + PHP_SHA512Final(full_digest, context); + memcpy(digest, full_digest, 32); +} +/* }}} */ + +/* {{{ PHP_SHA512_224Final + SHA512/224 finalization. Identical to SHA512Final, but with truncation + */ +PHP_HASH_API void PHP_SHA512_224Final(unsigned char digest[28], PHP_SHA512_CTX *context) { + unsigned char full_digest[64]; + PHP_SHA512Final(full_digest, context); + memcpy(digest, full_digest, 28); +} +/* }}} */ + +const php_hash_ops php_hash_sha512_ops = {"sha512", + (php_hash_init_func_t) PHP_SHA512InitArgs, + (php_hash_update_func_t) PHP_SHA512Update, + (php_hash_final_func_t) PHP_SHA512Final, + php_hash_copy, + php_hash_serialize, + php_hash_unserialize, + PHP_SHA512_SPEC, + 64, + 128, + sizeof(PHP_SHA512_CTX), + 1}; + +const php_hash_ops php_hash_sha512_256_ops = {"sha512/256", + (php_hash_init_func_t) PHP_SHA512_256InitArgs, + (php_hash_update_func_t) PHP_SHA512_256Update, + (php_hash_final_func_t) PHP_SHA512_256Final, + php_hash_copy, + php_hash_serialize, + php_hash_unserialize, + PHP_SHA512_SPEC, + 32, + 128, + sizeof(PHP_SHA512_CTX), + 1}; + +const php_hash_ops php_hash_sha512_224_ops = {"sha512/224", + (php_hash_init_func_t) PHP_SHA512_224InitArgs, + (php_hash_update_func_t) PHP_SHA512_224Update, + (php_hash_final_func_t) PHP_SHA512_224Final, + php_hash_copy, + php_hash_serialize, + php_hash_unserialize, + PHP_SHA512_SPEC, + 28, + 128, + sizeof(PHP_SHA512_CTX), + 1}; +#endif diff --git a/thirdparty/php84/hash/php_hash.h b/thirdparty/php84/hash/php_hash.h new file mode 100644 index 00000000000..5511040b3c7 --- /dev/null +++ b/thirdparty/php84/hash/php_hash.h @@ -0,0 +1,172 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sara Golemon | + +----------------------------------------------------------------------+ +*/ +#include "php_swoole.h" + +#ifndef PHP_HASH_H +#define PHP_HASH_H + +#include "php.h" + +#define PHP_HASH_EXTNAME "hash" +#define PHP_HASH_VERSION PHP_VERSION +#define PHP_MHASH_VERSION PHP_VERSION + +#define PHP_HASH_HMAC 0x0001 + +#define PHP_HASH_SERIALIZE_MAGIC_SPEC 2 + +#define L64 INT64_C + +typedef struct _php_hashcontext_object php_hashcontext_object; + +typedef void (*php_hash_init_func_t)(void *context, HashTable *args); +typedef void (*php_hash_update_func_t)(void *context, const unsigned char *buf, size_t count); +typedef void (*php_hash_final_func_t)(unsigned char *digest, void *context); +typedef int (*php_hash_copy_func_t)(const void *ops, void *orig_context, void *dest_context); +typedef int (*php_hash_serialize_func_t)(const php_hashcontext_object *hash, zend_long *magic, zval *zv); +typedef int (*php_hash_unserialize_func_t)(php_hashcontext_object *hash, zend_long magic, const zval *zv); + +typedef struct _php_hash_ops { + const char *algo; + php_hash_init_func_t hash_init; + php_hash_update_func_t hash_update; + php_hash_final_func_t hash_final; + php_hash_copy_func_t hash_copy; + php_hash_serialize_func_t hash_serialize; + php_hash_unserialize_func_t hash_unserialize; + const char *serialize_spec; + + size_t digest_size; + size_t block_size; + size_t context_size; + unsigned is_crypto : 1; +} php_hash_ops; + +struct _php_hashcontext_object { + const php_hash_ops *ops; + void *context; + + zend_long options; + unsigned char *key; + + zend_object std; +}; + +static inline php_hashcontext_object *php_hashcontext_from_object(zend_object *obj) { + return ((php_hashcontext_object *) (obj + 1)) - 1; +} + +extern const php_hash_ops php_hash_md2_ops; +extern const php_hash_ops php_hash_md4_ops; +extern const php_hash_ops php_hash_md5_ops; +extern const php_hash_ops php_hash_sha1_ops; +extern const php_hash_ops php_hash_sha224_ops; +extern const php_hash_ops php_hash_sha256_ops; +extern const php_hash_ops php_hash_sha384_ops; +extern const php_hash_ops php_hash_sha512_ops; +extern const php_hash_ops php_hash_sha512_256_ops; +extern const php_hash_ops php_hash_sha512_224_ops; +extern const php_hash_ops php_hash_sha3_224_ops; +extern const php_hash_ops php_hash_sha3_256_ops; +extern const php_hash_ops php_hash_sha3_384_ops; +extern const php_hash_ops php_hash_sha3_512_ops; +extern const php_hash_ops php_hash_ripemd128_ops; +extern const php_hash_ops php_hash_ripemd160_ops; +extern const php_hash_ops php_hash_ripemd256_ops; +extern const php_hash_ops php_hash_ripemd320_ops; +extern const php_hash_ops php_hash_whirlpool_ops; +extern const php_hash_ops php_hash_3tiger128_ops; +extern const php_hash_ops php_hash_3tiger160_ops; +extern const php_hash_ops php_hash_3tiger192_ops; +extern const php_hash_ops php_hash_4tiger128_ops; +extern const php_hash_ops php_hash_4tiger160_ops; +extern const php_hash_ops php_hash_4tiger192_ops; +extern const php_hash_ops php_hash_snefru_ops; +extern const php_hash_ops php_hash_gost_ops; +extern const php_hash_ops php_hash_gost_crypto_ops; +extern const php_hash_ops php_hash_adler32_ops; +extern const php_hash_ops php_hash_crc32_ops; +extern const php_hash_ops php_hash_crc32b_ops; +extern const php_hash_ops php_hash_crc32c_ops; +extern const php_hash_ops php_hash_fnv132_ops; +extern const php_hash_ops php_hash_fnv1a32_ops; +extern const php_hash_ops php_hash_fnv164_ops; +extern const php_hash_ops php_hash_fnv1a64_ops; +extern const php_hash_ops php_hash_joaat_ops; +extern const php_hash_ops php_hash_murmur3a_ops; +extern const php_hash_ops php_hash_murmur3c_ops; +extern const php_hash_ops php_hash_murmur3f_ops; +extern const php_hash_ops php_hash_xxh32_ops; +extern const php_hash_ops php_hash_xxh64_ops; +extern const php_hash_ops php_hash_xxh3_64_ops; +extern const php_hash_ops php_hash_xxh3_128_ops; + +#define PHP_HASH_HAVAL_OPS(p, b) extern const php_hash_ops php_hash_##p##haval##b##_ops; + +PHP_HASH_HAVAL_OPS(3, 128) +PHP_HASH_HAVAL_OPS(3, 160) +PHP_HASH_HAVAL_OPS(3, 192) +PHP_HASH_HAVAL_OPS(3, 224) +PHP_HASH_HAVAL_OPS(3, 256) + +PHP_HASH_HAVAL_OPS(4, 128) +PHP_HASH_HAVAL_OPS(4, 160) +PHP_HASH_HAVAL_OPS(4, 192) +PHP_HASH_HAVAL_OPS(4, 224) +PHP_HASH_HAVAL_OPS(4, 256) + +PHP_HASH_HAVAL_OPS(5, 128) +PHP_HASH_HAVAL_OPS(5, 160) +PHP_HASH_HAVAL_OPS(5, 192) +PHP_HASH_HAVAL_OPS(5, 224) +PHP_HASH_HAVAL_OPS(5, 256) + +extern zend_module_entry hash_module_entry; +#define phpext_hash_ptr &hash_module_entry + +#ifdef PHP_WIN32 +#define PHP_HASH_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +#define PHP_HASH_API __attribute__((visibility("default"))) +#else +#define PHP_HASH_API +#endif + +extern PHP_HASH_API zend_class_entry *php_hashcontext_ce; +PHP_HASH_API const php_hash_ops *php_hash_fetch_ops(zend_string *algo); +PHP_HASH_API void php_hash_register_algo(const char *algo, const php_hash_ops *ops); +PHP_HASH_API int php_hash_copy(const void *ops, void *orig_context, void *dest_context); +PHP_HASH_API int php_hash_serialize(const php_hashcontext_object *context, zend_long *magic, zval *zv); +PHP_HASH_API int php_hash_unserialize(php_hashcontext_object *context, zend_long magic, const zval *zv); +PHP_HASH_API int php_hash_serialize_spec(const php_hashcontext_object *context, zval *zv, const char *spec); +PHP_HASH_API int php_hash_unserialize_spec(php_hashcontext_object *hash, const zval *zv, const char *spec); + +static inline void *php_hash_alloc_context(const php_hash_ops *ops) { + /* Zero out context memory so serialization doesn't expose internals */ + return ecalloc(1, ops->context_size); +} + +static inline void php_hash_bin2hex(char *out, const unsigned char *in, size_t in_len) { + static const char hexits[17] = "0123456789abcdef"; + size_t i; + + for (i = 0; i < in_len; i++) { + out[i * 2] = hexits[in[i] >> 4]; + out[(i * 2) + 1] = hexits[in[i] & 0x0F]; + } +} + +#endif /* PHP_HASH_H */ diff --git a/thirdparty/php84/hash/php_hash_sha.h b/thirdparty/php84/hash/php_hash_sha.h new file mode 100644 index 00000000000..440536fc5be --- /dev/null +++ b/thirdparty/php84/hash/php_hash_sha.h @@ -0,0 +1,95 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | SHA1 Author: Stefan Esser | + | SHA256 Author: Sara Golemon | + +----------------------------------------------------------------------+ +*/ +#include "php_swoole.h" + +#ifndef PHP_HASH_SHA_H +#define PHP_HASH_SHA_H + +#include "ext/standard/sha1.h" + +/* SHA224 context. */ +typedef struct { + uint32_t state[8]; /* state */ + uint32_t count[2]; /* number of bits, modulo 2^64 */ + unsigned char buffer[64]; /* input buffer */ +} PHP_SHA224_CTX; +#define PHP_SHA224_SPEC "l8l2b64." + +#define PHP_SHA224Init(ctx) PHP_SHA224InitArgs(ctx, NULL) +PHP_HASH_API void PHP_SHA224InitArgs(PHP_SHA224_CTX *, ZEND_ATTRIBUTE_UNUSED HashTable *); +PHP_HASH_API void PHP_SHA224Update(PHP_SHA224_CTX *, const unsigned char *, size_t); +PHP_HASH_API void PHP_SHA224Final(unsigned char[28], PHP_SHA224_CTX *); + +/* SHA256 context. */ +typedef struct { + uint32_t state[8]; /* state */ + uint32_t count[2]; /* number of bits, modulo 2^64 */ + unsigned char buffer[64]; /* input buffer */ +} PHP_SHA256_CTX; +#define PHP_SHA256_SPEC "l8l2b64." + +#define PHP_SHA256Init(ctx) PHP_SHA256InitArgs(ctx, NULL) +PHP_HASH_API void PHP_SHA256InitArgs(PHP_SHA256_CTX *, ZEND_ATTRIBUTE_UNUSED HashTable *); +PHP_HASH_API void PHP_SHA256Update(PHP_SHA256_CTX *, const unsigned char *, size_t); + +#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IMMINTRIN_H) +#if defined(__SSSE3__) && defined(__SHA__) +#define PHP_HASH_INTRIN_SHA_NATIVE 1 +#elif defined(HAVE_FUNC_ATTRIBUTE_TARGET) +#define PHP_HASH_INTRIN_SHA_RESOLVER 1 +#endif +#endif + +PHP_HASH_API void PHP_SHA256Final(unsigned char[32], PHP_SHA256_CTX *); + +/* SHA384 context */ +typedef struct { + uint64_t state[8]; /* state */ + uint64_t count[2]; /* number of bits, modulo 2^128 */ + unsigned char buffer[128]; /* input buffer */ +} PHP_SHA384_CTX; +#define PHP_SHA384_SPEC "q8q2b128." + +#define PHP_SHA384Init(ctx) PHP_SHA384InitArgs(ctx, NULL) +PHP_HASH_API void PHP_SHA384InitArgs(PHP_SHA384_CTX *, ZEND_ATTRIBUTE_UNUSED HashTable *); +PHP_HASH_API void PHP_SHA384Update(PHP_SHA384_CTX *, const unsigned char *, size_t); +PHP_HASH_API void PHP_SHA384Final(unsigned char[48], PHP_SHA384_CTX *); + +/* SHA512 context */ +typedef struct { + uint64_t state[8]; /* state */ + uint64_t count[2]; /* number of bits, modulo 2^128 */ + unsigned char buffer[128]; /* input buffer */ +} PHP_SHA512_CTX; +#define PHP_SHA512_SPEC "q8q2b128." + +#define PHP_SHA512Init(ctx) PHP_SHA512InitArgs(ctx, NULL) +PHP_HASH_API void PHP_SHA512InitArgs(PHP_SHA512_CTX *, ZEND_ATTRIBUTE_UNUSED HashTable *); +PHP_HASH_API void PHP_SHA512Update(PHP_SHA512_CTX *, const unsigned char *, size_t); +PHP_HASH_API void PHP_SHA512Final(unsigned char[64], PHP_SHA512_CTX *); + +#define PHP_SHA512_256Init(ctx) PHP_SHA512_256InitArgs(ctx, NULL) +PHP_HASH_API void PHP_SHA512_256InitArgs(PHP_SHA512_CTX *, ZEND_ATTRIBUTE_UNUSED HashTable *); +#define PHP_SHA512_256Update PHP_SHA512Update +PHP_HASH_API void PHP_SHA512_256Final(unsigned char[32], PHP_SHA512_CTX *); + +#define PHP_SHA512_224Init(ctx) PHP_SHA512_224InitArgs(ctx, NULL) +PHP_HASH_API void PHP_SHA512_224InitArgs(PHP_SHA512_CTX *, ZEND_ATTRIBUTE_UNUSED HashTable *); +#define PHP_SHA512_224Update PHP_SHA512Update +PHP_HASH_API void PHP_SHA512_224Final(unsigned char[28], PHP_SHA512_CTX *); + +#endif /* PHP_HASH_SHA_H */ diff --git a/thirdparty/php84/pdo_oci/LICENSE b/thirdparty/php84/pdo_oci/LICENSE new file mode 100644 index 00000000000..0815d7eb791 --- /dev/null +++ b/thirdparty/php84/pdo_oci/LICENSE @@ -0,0 +1,68 @@ +-------------------------------------------------------------------- + The PHP License, version 3.01 +Copyright (c) 1999 - 2024 The PHP Group. All rights reserved. +-------------------------------------------------------------------- + +Redistribution and use in source and binary forms, with or without +modification, is permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + 3. The name "PHP" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact group@php.net. + + 4. Products derived from this software may not be called "PHP", nor + may "PHP" appear in their name, without prior written permission + from group@php.net. You may indicate that your software works in + conjunction with PHP by saying "Foo for PHP" instead of calling + it "PHP Foo" or "phpfoo" + + 5. The PHP Group may publish revised and/or new versions of the + license from time to time. Each version will be given a + distinguishing version number. + Once covered code has been published under a particular version + of the license, you may always continue to use it under the terms + of that version. You may also choose to use such covered code + under the terms of any subsequent version of the license + published by the PHP Group. No one other than the PHP Group has + the right to modify the terms applicable to covered code created + under this License. + + 6. Redistributions of any form whatsoever must retain the following + acknowledgment: + "This product includes PHP software, freely available from + ". + +THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND +ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP +DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------- + +This software consists of voluntary contributions made by many +individuals on behalf of the PHP Group. + +The PHP Group can be contacted via Email at group@php.net. + +For more information on the PHP Group and the PHP project, +please see . + +PHP includes the Zend Engine, freely available at +. diff --git a/thirdparty/php84/pdo_oci/oci_driver.c b/thirdparty/php84/pdo_oci/oci_driver.c new file mode 100644 index 00000000000..80dc5f04718 --- /dev/null +++ b/thirdparty/php84/pdo_oci/oci_driver.c @@ -0,0 +1,883 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ORACLE_HOOK +#include "php_swoole_oracle.h" + +#if PHP_VERSION_ID >= 80400 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_oci.h" +#include "php_pdo_oci_int.h" +#include "Zend/zend_exceptions.h" + +static inline ub4 pdo_oci_sanitize_prefetch(long prefetch); + +static void pdo_oci_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_error_info *einfo; + + einfo = &H->einfo; + + if (stmt) { + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + + if (S->einfo.errmsg) { + einfo = &S->einfo; + } + } + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + add_next_index_string(info, einfo->errmsg); + } +} +/* }}} */ + +ub4 _oci_error(OCIError *err, + pdo_dbh_t *dbh, + pdo_stmt_t *stmt, + char *what, + sword status, + int isinit, + const char *file, + int line) /* {{{ */ +{ + text errbuf[1024] = "<>"; + char tmp_buf[2048]; + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_error_info *einfo; + pdo_oci_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_oci_stmt *) stmt->driver_data; + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } else { + einfo = &H->einfo; + } + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + } + + einfo->errmsg = NULL; + einfo->errcode = 0; + einfo->file = file; + einfo->line = line; + + if (isinit) { /* Initialization error */ + strcpy(*pdo_err, "HY000"); + slprintf(tmp_buf, sizeof(tmp_buf), "%s (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + } else { + switch (status) { + case OCI_SUCCESS: + strcpy(*pdo_err, "00000"); + break; + case OCI_ERROR: + OCIErrorGet(err, (ub4) 1, NULL, &einfo->errcode, errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR); + slprintf(tmp_buf, sizeof(tmp_buf), "%s: %s (%s:%d)", what, errbuf, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_SUCCESS_WITH_INFO: + OCIErrorGet(err, (ub4) 1, NULL, &einfo->errcode, errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR); + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_SUCCESS_WITH_INFO: %s (%s:%d)", what, errbuf, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_NEED_DATA: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_NEED_DATA (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_NO_DATA: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_NO_DATA (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_INVALID_HANDLE: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_INVALID_HANDLE (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_STILL_EXECUTING: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_STILL_EXECUTING (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_CONTINUE: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_CONTINUE (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + } + + if (einfo->errcode) { + switch (einfo->errcode) { + case 1013: /* user requested cancel of current operation */ + zend_bailout(); + break; + + case 12154: /* ORA-12154: TNS:could not resolve service name */ + strcpy(*pdo_err, "42S02"); + break; + + case 22: /* ORA-00022: invalid session id */ + case 378: + case 602: + case 603: + case 604: + case 609: + case 1012: /* ORA-01012: */ + case 1033: + case 1041: + case 1043: + case 1089: + case 1090: + case 1092: + case 3113: /* ORA-03133: end of file on communication channel */ + case 3114: + case 3122: + case 3135: + case 12153: + case 27146: + case 28511: + /* consider the connection closed */ + dbh->is_closed = 1; + H->attached = 0; + strcpy(*pdo_err, "01002"); /* FIXME */ + break; + + default: + strcpy(*pdo_err, "HY000"); + } + } + + if (stmt) { + /* always propagate the error code back up to the dbh, + * so that we can catch the error information when execute + * is called via query. See Bug #33707 */ + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + } + H->einfo = *einfo; + H->einfo.errmsg = einfo->errmsg ? pestrdup(einfo->errmsg, dbh->is_persistent) : NULL; + strcpy(dbh->error_code, stmt->error_code); + } + } + + /* little mini hack so that we can use this code from the dbh ctor */ + if (!dbh->methods && status != OCI_SUCCESS_WITH_INFO) { + zend_throw_exception_ex(php_pdo_get_exception(), einfo->errcode, "SQLSTATE[%s]: %s", *pdo_err, einfo->errmsg); + } + + return einfo->errcode; +} +/* }}} */ + +static void oci_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + if (H->svc) { + /* rollback any outstanding work */ + OCITransRollback(H->svc, H->err, 0); + } + + if (H->session) { + OCIHandleFree(H->session, OCI_HTYPE_SESSION); + H->session = NULL; + } + + if (H->svc) { + OCIHandleFree(H->svc, OCI_HTYPE_SVCCTX); + H->svc = NULL; + } + + if (H->server && H->attached) { + H->last_err = OCIServerDetach(H->server, H->err, OCI_DEFAULT); + if (H->last_err) { + oci_drv_error("OCIServerDetach"); + } + H->attached = 0; + } + + if (H->server) { + OCIHandleFree(H->server, OCI_HTYPE_SERVER); + H->server = NULL; + } + + if (H->err) { + OCIHandleFree(H->err, OCI_HTYPE_ERROR); + H->err = NULL; + } + + if (H->charset && H->env) { + OCIHandleFree(H->env, OCI_HTYPE_ENV); + H->env = NULL; + } + + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + + pefree(H, dbh->is_persistent); +} +/* }}} */ + +static bool oci_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_stmt *S = ecalloc(1, sizeof(*S)); + ub4 prefetch; + zend_string *nsql = NULL; + int ret; + + S->exec_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL + ? OCI_STMT_SCROLLABLE_READONLY + : OCI_DEFAULT; + + S->H = H; + stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return false; + } + + /* create an OCI statement handle */ + OCIHandleAlloc(H->env, (dvoid *) &S->stmt, OCI_HTYPE_STMT, 0, NULL); + + /* and our own private error handle */ + OCIHandleAlloc(H->env, (dvoid *) &S->err, OCI_HTYPE_ERROR, 0, NULL); + + if (ZSTR_LEN(sql) != 0) { + H->last_err = + OCIStmtPrepare(S->stmt, H->err, (text *) ZSTR_VAL(sql), (ub4) ZSTR_LEN(sql), OCI_NTV_SYNTAX, OCI_DEFAULT); + if (nsql) { + zend_string_release(nsql); + nsql = NULL; + } + if (H->last_err) { + H->last_err = oci_drv_error("OCIStmtPrepare"); + OCIHandleFree(S->stmt, OCI_HTYPE_STMT); + OCIHandleFree(S->err, OCI_HTYPE_ERROR); + efree(S); + return false; + } + } + + prefetch = H->prefetch; /* Note 0 is allowed so in future REF CURSORs can be used & then passed with no row loss*/ + H->last_err = OCIAttrSet(S->stmt, OCI_HTYPE_STMT, &prefetch, 0, OCI_ATTR_PREFETCH_ROWS, H->err); + if (!H->last_err) { + prefetch *= PDO_OCI_PREFETCH_ROWSIZE; + H->last_err = OCIAttrSet(S->stmt, OCI_HTYPE_STMT, &prefetch, 0, OCI_ATTR_PREFETCH_MEMORY, H->err); + } + + stmt->driver_data = S; + stmt->methods = &swoole_oci_stmt_methods; + if (nsql) { + zend_string_release(nsql); + nsql = NULL; + } + + return true; +} +/* }}} */ + +static zend_long oci_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + OCIStmt *stmt; + ub2 stmt_type; + ub4 rowcount; + int ret = -1; + + OCIHandleAlloc(H->env, (dvoid *) &stmt, OCI_HTYPE_STMT, 0, NULL); + + H->last_err = + OCIStmtPrepare(stmt, H->err, (text *) ZSTR_VAL(sql), (ub4) ZSTR_LEN(sql), OCI_NTV_SYNTAX, OCI_DEFAULT); + if (H->last_err) { + H->last_err = oci_drv_error("OCIStmtPrepare"); + OCIHandleFree(stmt, OCI_HTYPE_STMT); + return -1; + } + + H->last_err = OCIAttrGet(stmt, OCI_HTYPE_STMT, &stmt_type, 0, OCI_ATTR_STMT_TYPE, H->err); + + if (stmt_type == OCI_STMT_SELECT) { + /* invalid usage; cancel it */ + OCIHandleFree(stmt, OCI_HTYPE_STMT); + php_error_docref(NULL, E_WARNING, "issuing a SELECT query here is invalid"); + return -1; + } + + /* now we are good to go */ + H->last_err = OCIStmtExecute(H->svc, + stmt, + H->err, + 1, + 0, + NULL, + NULL, + (dbh->auto_commit && !dbh->in_txn) ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT); + + sword last_err = H->last_err; + + if (last_err) { + H->last_err = oci_drv_error("OCIStmtExecute"); + } + + if (!last_err || last_err == OCI_SUCCESS_WITH_INFO) { + /* return the number of affected rows */ + H->last_err = OCIAttrGet(stmt, OCI_HTYPE_STMT, &rowcount, 0, OCI_ATTR_ROW_COUNT, H->err); + ret = rowcount; + } + + OCIHandleFree(stmt, OCI_HTYPE_STMT); + + return ret; +} +/* }}} */ + +static zend_string *oci_handle_quoter(pdo_dbh_t *dbh, + const zend_string *unquoted, + enum pdo_param_type paramtype) /* {{{ */ +{ + int qcount = 0; + char const *cu, *l, *r; + char *c, *quoted; + size_t quotedlen; + zend_string *quoted_str; + + if (ZSTR_LEN(unquoted) == 0) { + return ZSTR_INIT_LITERAL("''", 0); + } + + /* count single quotes */ + for (cu = ZSTR_VAL(unquoted); (cu = strchr(cu, '\'')); qcount++, cu++) + ; /* empty loop */ + + quotedlen = ZSTR_LEN(unquoted) + qcount + 2; + quoted = c = emalloc(quotedlen + 1); + *c++ = '\''; + + /* foreach (chunk that ends in a quote) */ + for (l = ZSTR_VAL(unquoted); (r = strchr(l, '\'')); l = r + 1) { + strncpy(c, l, r - l + 1); + c += (r - l + 1); + *c++ = '\''; /* add second quote */ + } + + /* Copy remainder and add enclosing quote */ + strncpy(c, l, quotedlen - (c - quoted) - 1); + quoted[quotedlen - 1] = '\''; + quoted[quotedlen] = '\0'; + + quoted_str = zend_string_init(quoted, quotedlen, 0); + efree(quoted); + return quoted_str; +} +/* }}} */ + +static bool oci_handle_begin(pdo_dbh_t *dbh) /* {{{ */ +{ + /* with Oracle, there is nothing special to be done */ + return true; +} +/* }}} */ + +static bool oci_handle_commit(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + H->last_err = OCITransCommit(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransCommit"); + return false; + } + return true; +} +/* }}} */ + +static bool oci_handle_rollback(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + H->last_err = OCITransRollback(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransRollback"); + return false; + } + return true; +} +/* }}} */ + +static bool oci_handle_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */ +{ + zend_long lval; + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_AUTOCOMMIT: { + bool bval; + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + + if (dbh->in_txn) { + /* Assume they want to commit whatever is outstanding */ + H->last_err = OCITransCommit(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransCommit"); + return false; + } + dbh->in_txn = false; + } + + dbh->auto_commit = (unsigned int) bval; + return true; + } + case PDO_ATTR_PREFETCH: { + if (!pdo_get_long_param(&lval, val)) { + return false; + } + + H->prefetch = pdo_oci_sanitize_prefetch(lval); + return true; + } + case PDO_OCI_ATTR_ACTION: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *action = zval_try_get_string(val); + if (UNEXPECTED(!action)) { + return false; + } + + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, (dvoid *) ZSTR_VAL(action), (ub4) ZSTR_LEN(action), OCI_ATTR_ACTION, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_ACTION"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_CLIENT_INFO: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *client_info = zval_try_get_string(val); + if (UNEXPECTED(!client_info)) { + return false; + } + + H->last_err = OCIAttrSet(H->session, + OCI_HTYPE_SESSION, + (dvoid *) ZSTR_VAL(client_info), + (ub4) ZSTR_LEN(client_info), + OCI_ATTR_CLIENT_INFO, + H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CLIENT_INFO"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_CLIENT_IDENTIFIER: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *identifier = zval_try_get_string(val); + if (UNEXPECTED(!identifier)) { + return false; + } + + H->last_err = OCIAttrSet(H->session, + OCI_HTYPE_SESSION, + (dvoid *) ZSTR_VAL(identifier), + (ub4) ZSTR_LEN(identifier), + OCI_ATTR_CLIENT_IDENTIFIER, + H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CLIENT_IDENTIFIER"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_MODULE: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *module = zval_try_get_string(val); + if (UNEXPECTED(!module)) { + return false; + } + + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, (dvoid *) ZSTR_VAL(module), (ub4) ZSTR_LEN(module), OCI_ATTR_MODULE, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_MODULE"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_CALL_TIMEOUT: { +#if (OCI_MAJOR_VERSION >= 18) + if (!pdo_get_long_param(&lval, val)) { + return false; + } + ub4 timeout = (ub4) lval; + + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, (dvoid *) &timeout, (ub4) 0, OCI_ATTR_CALL_TIMEOUT, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CALL_TIMEOUT"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + default: + return false; + } +} +/* }}} */ + +static int oci_handle_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_SERVER_INFO: { + text infostr[512]; + char verstr[15]; + ub4 vernum; + + if (OCIServerRelease(H->svc, H->err, infostr, (ub4) sizeof(infostr), (ub1) OCI_HTYPE_SVCCTX, &vernum)) { + ZVAL_STRING(return_value, "<>"); + } else { + if (attr == PDO_ATTR_SERVER_INFO) { + ZVAL_STRING(return_value, (char *) infostr); + } else { + slprintf(verstr, + sizeof(verstr), + "%d.%d.%d.%d.%d", + (int) ((vernum >> 24) & 0xFF), /* version number */ + (int) ((vernum >> 20) & 0x0F), /* release number*/ + (int) ((vernum >> 12) & 0xFF), /* update number */ + (int) ((vernum >> 8) & 0x0F), /* port release number */ + (int) ((vernum >> 0) & 0xFF)); /* port update number */ + + ZVAL_STRING(return_value, verstr); + } + } + return TRUE; + } + + case PDO_ATTR_CLIENT_VERSION: { +#if OCI_MAJOR_VERSION > 10 || (OCI_MAJOR_VERSION == 10 && OCI_MINOR_VERSION >= 2) + /* Run time client version */ + sword major, minor, update, patch, port_update; + char verstr[15]; + + OCIClientVersion(&major, &minor, &update, &patch, &port_update); + slprintf(verstr, sizeof(verstr), "%d.%d.%d.%d.%d", major, minor, update, patch, port_update); + ZVAL_STRING(return_value, verstr); +#elif defined(SWOOLE_PDO_OCI_CLIENT_VERSION) + /* Compile time client version */ + ZVAL_STRING(return_value, SWOOLE_PDO_OCI_CLIENT_VERSION); +#else + return FALSE; + +#endif /* Check for OCIClientVersion() support */ + + return TRUE; + } + + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(return_value, dbh->auto_commit); + return TRUE; + + case PDO_ATTR_PREFETCH: + ZVAL_LONG(return_value, H->prefetch); + return TRUE; + case PDO_OCI_ATTR_CALL_TIMEOUT: { +#if (OCI_MAJOR_VERSION >= 18) + ub4 timeout; + + H->last_err = OCIAttrGet(H->svc, OCI_HTYPE_SVCCTX, (dvoid *) &timeout, NULL, OCI_ATTR_CALL_TIMEOUT, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrGet: OCI_ATTR_CALL_TIMEOUT"); + return FALSE; + } + + ZVAL_LONG(return_value, (zend_long) timeout); + return TRUE; +#else + oci_drv_error("Unsupported attribute type"); + return FALSE; +#endif + } + default: + return FALSE; + } + return FALSE; +} +/* }}} */ + +static zend_result pdo_oci_check_liveness(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + sb4 error_code = 0; +#if (!((OCI_MAJOR_VERSION > 10) || ((OCI_MAJOR_VERSION == 10) && (OCI_MINOR_VERSION >= 2)))) + char version[256]; +#endif + + /* TODO move attached check to PDO level */ + if (H->attached == 0) { + return FAILURE; + } + /* TODO add persistent_timeout check at PDO level */ + + /* Use OCIPing instead of OCIServerVersion. If OCIPing returns ORA-1010 (invalid OCI operation) + * such as from Pre-10.1 servers, the error is still from the server and we would have + * successfully performed a roundtrip and validated the connection. Use OCIServerVersion for + * Pre-10.2 clients + */ +#if ((OCI_MAJOR_VERSION > 10) || \ + ((OCI_MAJOR_VERSION == 10) && (OCI_MINOR_VERSION >= 2))) /* OCIPing available 10.2 onwards */ + H->last_err = OCIPing(H->svc, H->err, OCI_DEFAULT); +#else + /* use good old OCIServerVersion() */ + H->last_err = OCIServerVersion(H->svc, H->err, (text *) version, sizeof(version), OCI_HTYPE_SVCCTX); +#endif + if (H->last_err == OCI_SUCCESS) { + return SUCCESS; + } + + OCIErrorGet(H->err, (ub4) 1, NULL, &error_code, NULL, 0, OCI_HTYPE_ERROR); + + if (error_code == 1010) { + return SUCCESS; + } + return FAILURE; +} +/* }}} */ + +static const struct pdo_dbh_methods oci_methods = { + oci_handle_closer, + oci_handle_preparer, + oci_handle_doer, + oci_handle_quoter, + oci_handle_begin, + oci_handle_commit, + oci_handle_rollback, + oci_handle_set_attribute, + NULL, /* last_id not supported */ + pdo_oci_fetch_error_func, + oci_handle_get_attribute, + pdo_oci_check_liveness, /* check_liveness */ + NULL, /* get_driver_methods */ + NULL, /* request_shutdown */ + NULL, /* in transaction, use PDO's internal tracking mechanism */ + NULL /* get_gc */ +}; + +static int pdo_oci_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_oci_db_handle *H; + int i, ret = 0; + struct pdo_data_src_parser vars[] = { + {"charset", NULL, 0}, {"dbname", "", 0}, {"user", NULL, 0}, {"password", NULL, 0}}; + + php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 4); + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + dbh->driver_data = H; + + dbh->skip_param_evt = 1 << PDO_PARAM_EVT_FETCH_PRE | 1 << PDO_PARAM_EVT_FETCH_POST | 1 << PDO_PARAM_EVT_NORMALIZE; + + H->prefetch = PDO_OCI_PREFETCH_DEFAULT; + + /* allocate an environment */ + if (vars[0].optval) { + H->charset = OCINlsCharSetNameToId(swoole_pdo_oci_Env, (const oratext *) vars[0].optval); + if (!H->charset) { + oci_init_error("OCINlsCharSetNameToId: unknown character set name"); + goto cleanup; + } else { + if (OCIEnvNlsCreate( + &H->env, SWOOLE_PDO_OCI_INIT_MODE, 0, NULL, NULL, NULL, 0, NULL, H->charset, H->charset) != + OCI_SUCCESS) { + oci_init_error("OCIEnvNlsCreate: Check the character set is valid and that PHP has access to Oracle " + "libraries and NLS data"); + goto cleanup; + } + } + } + + if (H->env == NULL) { + /* use the global environment */ + H->env = swoole_pdo_oci_Env; + } + + /* something to hold errors */ + OCIHandleAlloc(H->env, (dvoid **) &H->err, OCI_HTYPE_ERROR, 0, NULL); + + /* handle for the server */ + OCIHandleAlloc(H->env, (dvoid **) &H->server, OCI_HTYPE_SERVER, 0, NULL); + + H->last_err = + OCIServerAttach(H->server, H->err, (text *) vars[1].optval, (sb4) strlen(vars[1].optval), OCI_DEFAULT); + + if (H->last_err) { + oci_drv_error("pdo_oci_handle_factory"); + goto cleanup; + } + + H->attached = 1; + + /* create a service context */ + H->last_err = OCIHandleAlloc(H->env, (dvoid **) &H->svc, OCI_HTYPE_SVCCTX, 0, NULL); + if (H->last_err) { + oci_drv_error("OCIHandleAlloc: OCI_HTYPE_SVCCTX"); + goto cleanup; + } + + H->last_err = OCIHandleAlloc(H->env, (dvoid **) &H->session, OCI_HTYPE_SESSION, 0, NULL); + if (H->last_err) { + oci_drv_error("OCIHandleAlloc: OCI_HTYPE_SESSION"); + goto cleanup; + } + + /* set server handle into service handle */ + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, H->server, 0, OCI_ATTR_SERVER, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_SERVER"); + goto cleanup; + } + + /* username */ + if (!dbh->username && vars[2].optval) { + dbh->username = pestrdup(vars[2].optval, dbh->is_persistent); + } + + if (dbh->username) { + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, dbh->username, (ub4) strlen(dbh->username), OCI_ATTR_USERNAME, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_USERNAME"); + goto cleanup; + } + } + + /* password */ + if (!dbh->password && vars[3].optval) { + dbh->password = pestrdup(vars[3].optval, dbh->is_persistent); + } + + if (dbh->password) { + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, dbh->password, (ub4) strlen(dbh->password), OCI_ATTR_PASSWORD, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_PASSWORD"); + goto cleanup; + } + } + + /* Now fire up the session */ + H->last_err = OCISessionBegin(H->svc, H->err, H->session, OCI_CRED_RDBMS, OCI_DEFAULT); + if (H->last_err) { + oci_drv_error("OCISessionBegin"); + /* OCISessionBegin returns OCI_SUCCESS_WITH_INFO when + * user's password has expired, but is still usable. + */ + if (H->last_err != OCI_SUCCESS_WITH_INFO) { + goto cleanup; + } + } + + /* set the server handle into service handle */ + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, H->session, 0, OCI_ATTR_SESSION, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_SESSION"); + goto cleanup; + } + + /* Get max character width */ + H->last_err = OCINlsNumericInfoGet(H->env, H->err, &H->max_char_width, OCI_NLS_CHARSET_MAXBYTESZ); + if (H->last_err) { + oci_drv_error("OCINlsNumericInfoGet: OCI_NLS_CHARSET_MAXBYTESZ"); + goto cleanup; + } + + dbh->methods = &oci_methods; + dbh->alloc_own_columns = 1; + dbh->native_case = PDO_CASE_UPPER; + + ret = 1; + +cleanup: + for (i = 0; i < sizeof(vars) / sizeof(vars[0]); i++) { + if (vars[i].freeme) { + efree(vars[i].optval); + } + } + + if (!ret) { + oci_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_oci_driver = {PDO_DRIVER_HEADER(oci), pdo_oci_handle_factory}; + +static inline ub4 pdo_oci_sanitize_prefetch(long prefetch) /* {{{ */ +{ + if (prefetch < 0) { + prefetch = 0; + } else if (prefetch > UB4MAXVAL / PDO_OCI_PREFETCH_ROWSIZE) { + prefetch = PDO_OCI_PREFETCH_DEFAULT; + } + return ((ub4) prefetch); +} +/* }}} */ +#endif diff --git a/thirdparty/php84/pdo_oci/oci_statement.c b/thirdparty/php84/pdo_oci/oci_statement.c new file mode 100644 index 00000000000..c26f64a919a --- /dev/null +++ b/thirdparty/php84/pdo_oci/oci_statement.c @@ -0,0 +1,1044 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ORACLE_HOOK +#include "php_swoole_oracle.h" + +#if PHP_VERSION_ID >= 80400 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_oci.h" +#include "php_pdo_oci_int.h" +#include "Zend/zend_extensions.h" + +#define PDO_OCI_LOBMAXSIZE (4294967295UL) /* OCI_LOBMAXSIZE */ + +#define STMT_CALL(name, params) \ + do { \ + S->last_err = name params; \ + S->last_err = _oci_error(S->err, stmt->dbh, stmt, #name, S->last_err, FALSE, __FILE__, __LINE__); \ + if (S->last_err) { \ + return 0; \ + } \ + } while (0) + +#define STMT_CALL_MSG(name, msg, params) \ + do { \ + S->last_err = name params; \ + S->last_err = _oci_error(S->err, stmt->dbh, stmt, #name ": " #msg, S->last_err, FALSE, __FILE__, __LINE__); \ + if (S->last_err) { \ + return 0; \ + } \ + } while (0) + +static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLocator *lob); + +#define OCI_TEMPLOB_CLOSE(envhp, svchp, errhp, lob) \ + do { \ + boolean isTempLOB; \ + OCILobIsTemporary(envhp, errhp, lob, &isTempLOB); \ + if (isTempLOB) OCILobFreeTemporary(svchp, errhp, lob); \ + } while (0) + +static int oci_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + HashTable *BC = stmt->bound_columns; + HashTable *BP = stmt->bound_params; + + int i; + + if (S->stmt) { + /* cancel server side resources for the statement if we didn't + * fetch it all */ + OCIStmtFetch(S->stmt, S->err, 0, OCI_FETCH_NEXT, OCI_DEFAULT); + + /* free the handle */ + OCIHandleFree(S->stmt, OCI_HTYPE_STMT); + S->stmt = NULL; + } + if (S->err) { + OCIHandleFree(S->err, OCI_HTYPE_ERROR); + S->err = NULL; + } + + /* need to ensure these go away now */ + if (BC) { + zend_hash_destroy(BC); + FREE_HASHTABLE(stmt->bound_columns); + stmt->bound_columns = NULL; + } + + if (BP) { + zend_hash_destroy(BP); + FREE_HASHTABLE(stmt->bound_params); + stmt->bound_params = NULL; + } + + if (S->einfo.errmsg) { + pefree(S->einfo.errmsg, stmt->dbh->is_persistent); + S->einfo.errmsg = NULL; + } + + if (S->cols) { + for (i = 0; i < stmt->column_count; i++) { + if (S->cols[i].data) { + switch (S->cols[i].dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, (OCILobLocator *) S->cols[i].data); + OCIDescriptorFree(S->cols[i].data, OCI_DTYPE_LOB); + break; + default: + efree(S->cols[i].data); + } + } + } + efree(S->cols); + S->cols = NULL; + } + efree(S); + + stmt->driver_data = NULL; + + return 1; +} /* }}} */ + +static int oci_stmt_execute(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + ub4 rowcount; + b4 mode; + + if (!S->stmt_type) { + STMT_CALL_MSG( + OCIAttrGet, "OCI_ATTR_STMT_TYPE", (S->stmt, OCI_HTYPE_STMT, &S->stmt_type, 0, OCI_ATTR_STMT_TYPE, S->err)); + } + + if (stmt->executed) { + /* ensure that we cancel the cursor from a previous fetch */ + OCIStmtFetch(S->stmt, S->err, 0, OCI_FETCH_NEXT, OCI_DEFAULT); + } + +#ifdef OCI_STMT_SCROLLABLE_READONLY /* needed for oci8 ? */ + if (S->exec_type == OCI_STMT_SCROLLABLE_READONLY) { + mode = OCI_STMT_SCROLLABLE_READONLY; + } else +#endif + if (stmt->dbh->auto_commit && !stmt->dbh->in_txn) { + mode = OCI_COMMIT_ON_SUCCESS; + } else { + mode = OCI_DEFAULT; + } + + STMT_CALL( + OCIStmtExecute, + (S->H->svc, S->stmt, S->err, (S->stmt_type == OCI_STMT_SELECT && !S->have_blobs) ? 0 : 1, 0, NULL, NULL, mode)); + + if (!stmt->executed) { + ub4 colcount; + /* do first-time-only definition of bind/mapping stuff */ + + /* how many columns do we have ? */ + STMT_CALL_MSG( + OCIAttrGet, "ATTR_PARAM_COUNT", (S->stmt, OCI_HTYPE_STMT, &colcount, 0, OCI_ATTR_PARAM_COUNT, S->err)); + + stmt->column_count = (int) colcount; + + if (S->cols) { + int i; + for (i = 0; i < stmt->column_count; i++) { + if (S->cols[i].data) { + switch (S->cols[i].dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + /* do nothing */ + break; + default: + efree(S->cols[i].data); + } + } + } + efree(S->cols); + } + + S->cols = ecalloc(colcount, sizeof(pdo_oci_column)); + } + + STMT_CALL_MSG(OCIAttrGet, "ATTR_ROW_COUNT", (S->stmt, OCI_HTYPE_STMT, &rowcount, 0, OCI_ATTR_ROW_COUNT, S->err)); + stmt->row_count = (long) rowcount; + + return 1; +} /* }}} */ + +static sb4 oci_bind_input_cb( + dvoid *ctx, OCIBind *bindp, ub4 iter, ub4 index, dvoid **bufpp, ub4 *alenp, ub1 *piecep, dvoid **indpp) /* {{{ */ +{ + struct pdo_bound_param_data *param = (struct pdo_bound_param_data *) ctx; + pdo_oci_bound_param *P = (pdo_oci_bound_param *) param->driver_data; + zval *parameter; + + ZEND_ASSERT(param); + + *indpp = &P->indicator; + + if (Z_ISREF(param->parameter)) + parameter = Z_REFVAL(param->parameter); + else + parameter = ¶m->parameter; + + if (P->thing) { + *bufpp = P->thing; + *alenp = sizeof(void *); + } else if (ZVAL_IS_NULL(parameter)) { + /* insert a NULL value into the column */ + P->indicator = -1; /* NULL */ + *bufpp = 0; + *alenp = -1; + } else if (!P->thing) { + /* regular string bind */ + if (!try_convert_to_string(parameter)) { + return OCI_ERROR; + } + *bufpp = Z_STRVAL_P(parameter); + *alenp = (ub4) Z_STRLEN_P(parameter); + } + + *piecep = OCI_ONE_PIECE; + return OCI_CONTINUE; +} /* }}} */ + +static sb4 oci_bind_output_cb(dvoid *ctx, + OCIBind *bindp, + ub4 iter, + ub4 index, + dvoid **bufpp, + ub4 **alenpp, + ub1 *piecep, + dvoid **indpp, + ub2 **rcodepp) /* {{{ */ +{ + struct pdo_bound_param_data *param = (struct pdo_bound_param_data *) ctx; + pdo_oci_bound_param *P = (pdo_oci_bound_param *) param->driver_data; + zval *parameter; + + ZEND_ASSERT(param); + + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + P->actual_len = sizeof(OCILobLocator *); + *bufpp = P->thing; + *alenpp = &P->actual_len; + *piecep = OCI_ONE_PIECE; + *rcodepp = &P->retcode; + *indpp = &P->indicator; + return OCI_CONTINUE; + } + + if (Z_TYPE_P(parameter) == IS_OBJECT || Z_TYPE_P(parameter) == IS_RESOURCE) { + return OCI_CONTINUE; + } + + zval_ptr_dtor(parameter); + + Z_STR_P(parameter) = zend_string_alloc(param->max_value_len, 1); + P->used_for_output = 1; + + P->actual_len = (ub4) Z_STRLEN_P(parameter); + *alenpp = &P->actual_len; + *bufpp = (Z_STR_P(parameter))->val; + *piecep = OCI_ONE_PIECE; + *rcodepp = &P->retcode; + *indpp = &P->indicator; + + return OCI_CONTINUE; +} /* }}} */ + +static int oci_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, + enum pdo_param_event event_type) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + pdo_oci_bound_param *P; + sb4 value_sz = -1; + zval *parameter; + + if (Z_ISREF(param->parameter)) + parameter = Z_REFVAL(param->parameter); + else + parameter = ¶m->parameter; + + P = (pdo_oci_bound_param *) param->driver_data; + + switch (event_type) { + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + case PDO_PARAM_EVT_NORMALIZE: + /* Do nothing */ + break; + + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P && P->thing) { + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing); + OCIDescriptorFree(P->thing, OCI_DTYPE_LOB); + P->thing = NULL; + efree(P); + } else if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: + P = (pdo_oci_bound_param *) ecalloc(1, sizeof(pdo_oci_bound_param)); + param->driver_data = P; + + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_STMT: + return 0; + + case PDO_PARAM_LOB: + /* P->thing is now an OCILobLocator * */ + P->oci_type = SQLT_BLOB; + value_sz = (sb4) sizeof(OCILobLocator *); + break; + + case PDO_PARAM_STR: + default: + P->oci_type = SQLT_CHR; + value_sz = (sb4) param->max_value_len; + if (param->max_value_len == 0) { + value_sz = (sb4) 1332; /* maximum size before value is interpreted as a LONG value */ + } + } + + if (param->name) { + STMT_CALL(OCIBindByName, + (S->stmt, + &P->bind, + S->err, + (text *) param->name->val, + (sb4) param->name->len, + 0, + value_sz, + P->oci_type, + &P->indicator, + 0, + &P->retcode, + 0, + 0, + OCI_DATA_AT_EXEC)); + } else { + STMT_CALL(OCIBindByPos, + (S->stmt, + &P->bind, + S->err, + ((ub4) param->paramno) + 1, + 0, + value_sz, + P->oci_type, + &P->indicator, + 0, + &P->retcode, + 0, + 0, + OCI_DATA_AT_EXEC)); + } + + STMT_CALL(OCIBindDynamic, (P->bind, S->err, param, oci_bind_input_cb, param, oci_bind_output_cb)); + + return 1; + + case PDO_PARAM_EVT_EXEC_PRE: + P->indicator = 0; + P->used_for_output = 0; + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + ub4 empty = 0; + STMT_CALL(OCIDescriptorAlloc, (S->H->env, &P->thing, OCI_DTYPE_LOB, 0, NULL)); + STMT_CALL(OCIAttrSet, (P->thing, OCI_DTYPE_LOB, &empty, 0, OCI_ATTR_LOBEMPTY, S->err)); + S->have_blobs = 1; + } + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + /* fixup stuff set in motion in oci_bind_output_cb */ + if (P->used_for_output) { + if (P->indicator == -1) { + /* set up a NULL value */ + if (Z_TYPE_P(parameter) == IS_STRING) { + /* OCI likes to stick non-terminated strings in things */ + *Z_STRVAL_P(parameter) = '\0'; + } + zval_ptr_dtor_str(parameter); + ZVAL_UNDEF(parameter); + } else if (Z_TYPE_P(parameter) == IS_STRING) { + Z_STR_P(parameter) = zend_string_init(Z_STRVAL_P(parameter), P->actual_len, 1); + } + } else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->thing) { + php_stream *stm; + + if (Z_TYPE_P(parameter) == IS_NULL) { + /* if the param is NULL, then we assume that they + * wanted to bind a lob locator into it from the query + * */ + + stm = oci_create_lob_stream(&stmt->database_object_handle, stmt, (OCILobLocator *) P->thing); + if (stm) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + php_stream_to_zval(stm, parameter); + } + } else { + /* we're a LOB being used for insert; transfer the data now */ + size_t n; + ub4 amt, offset = 1; + char *consume; + + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + do { + char buf[8192]; + n = php_stream_read(stm, buf, sizeof(buf)); + if ((int) n <= 0) { + break; + } + consume = buf; + do { + amt = (ub4) n; + OCILobWrite(S->H->svc, + S->err, + (OCILobLocator *) P->thing, + &amt, + offset, + consume, + (ub4) n, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + offset += amt; + n -= amt; + consume += amt; + } while (n); + } while (1); + OCILobClose(S->H->svc, S->err, (OCILobLocator *) P->thing); + OCILobFlushBuffer(S->H->svc, S->err, (OCILobLocator *) P->thing, 0); + } else if (Z_TYPE_P(parameter) == IS_STRING) { + /* stick the string into the LOB */ + consume = Z_STRVAL_P(parameter); + n = Z_STRLEN_P(parameter); + if (n) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + while (n) { + amt = (ub4) n; + OCILobWrite(S->H->svc, + S->err, + (OCILobLocator *) P->thing, + &amt, + offset, + consume, + (ub4) n, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + consume += amt; + n -= amt; + } + OCILobClose(S->H->svc, S->err, (OCILobLocator *) P->thing); + } + } + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing); + OCIDescriptorFree(P->thing, OCI_DTYPE_LOB); + P->thing = NULL; + } + } + + return 1; + } + } + + return 1; +} /* }}} */ + +static int oci_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) /* {{{ */ +{ + ub4 ociori = OCI_FETCH_NEXT; + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: + ociori = OCI_FETCH_NEXT; + break; + case PDO_FETCH_ORI_PRIOR: + ociori = OCI_FETCH_PRIOR; + break; + case PDO_FETCH_ORI_FIRST: + ociori = OCI_FETCH_FIRST; + break; + case PDO_FETCH_ORI_LAST: + ociori = OCI_FETCH_LAST; + break; + case PDO_FETCH_ORI_ABS: + ociori = OCI_FETCH_ABSOLUTE; + break; + case PDO_FETCH_ORI_REL: + ociori = OCI_FETCH_RELATIVE; + break; + } + S->last_err = OCIStmtFetch2(S->stmt, S->err, 1, ociori, (sb4) offset, OCI_DEFAULT); + + if (S->last_err == OCI_NO_DATA) { + /* no (more) data */ + return 0; + } + + if (S->last_err == OCI_NEED_DATA) { + oci_stmt_error("OCI_NEED_DATA"); + return 0; + } + + if (S->last_err == OCI_SUCCESS_WITH_INFO || S->last_err == OCI_SUCCESS) { + return 1; + } + + oci_stmt_error("OCIStmtFetch"); + + return 0; +} /* }}} */ + +static sb4 oci_define_callback( + dvoid *octxp, OCIDefine *define, ub4 iter, dvoid **bufpp, ub4 **alenpp, ub1 *piecep, dvoid **indpp, ub2 **rcodepp) { + pdo_oci_column *col = (pdo_oci_column *) octxp; + + switch (col->dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + *piecep = OCI_ONE_PIECE; + *bufpp = col->data; + *alenpp = &col->datalen; + *indpp = (dvoid *) &col->indicator; + break; + EMPTY_SWITCH_DEFAULT_CASE(); + } + + return OCI_CONTINUE; +} + +static int oci_stmt_describe(pdo_stmt_t *stmt, int colno) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + OCIParam *param = NULL; + text *colname; + ub2 dtype, data_size, precis; + ub4 namelen; + struct pdo_column_data *col = &stmt->columns[colno]; + bool dyn = FALSE; + + /* describe the column */ + STMT_CALL(OCIParamGet, (S->stmt, OCI_HTYPE_STMT, S->err, (dvoid *) ¶m, colno + 1)); + + /* what type ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_TYPE", (param, OCI_DTYPE_PARAM, &dtype, 0, OCI_ATTR_DATA_TYPE, S->err)); + + /* how big ? */ + STMT_CALL_MSG( + OCIAttrGet, "OCI_ATTR_DATA_SIZE", (param, OCI_DTYPE_PARAM, &data_size, 0, OCI_ATTR_DATA_SIZE, S->err)); + + /* precision ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_PRECISION", (param, OCI_DTYPE_PARAM, &precis, 0, OCI_ATTR_PRECISION, S->err)); + + /* name ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_NAME", (param, OCI_DTYPE_PARAM, &colname, &namelen, OCI_ATTR_NAME, S->err)); + + col->precision = precis; + col->maxlen = data_size; + col->name = zend_string_init((char *) colname, namelen, 0); + + S->cols[colno].dtype = dtype; + + /* how much room do we need to store the field */ + switch (dtype) { + case SQLT_LBI: + case SQLT_LNG: + if (dtype == SQLT_LBI) { + dtype = SQLT_BIN; + } else { + dtype = SQLT_CHR; + } + S->cols[colno].datalen = 512; /* XXX should be INT_MAX and fetched by pieces */ + S->cols[colno].data = emalloc(S->cols[colno].datalen + 1); + break; + + case SQLT_BLOB: + case SQLT_CLOB: + STMT_CALL(OCIDescriptorAlloc, (S->H->env, (dvoid **) &S->cols[colno].data, OCI_DTYPE_LOB, 0, NULL)); + S->cols[colno].datalen = sizeof(OCILobLocator *); + dyn = TRUE; + break; + + case SQLT_BIN: + default: + if (dtype == SQLT_DAT || dtype == SQLT_NUM || dtype == SQLT_RDD +#ifdef SQLT_TIMESTAMP + || dtype == SQLT_TIMESTAMP +#endif +#ifdef SQLT_TIMESTAMP_TZ + || dtype == SQLT_TIMESTAMP_TZ +#endif + ) { + /* should be big enough for most date formats and numbers */ + S->cols[colno].datalen = 512; +#if defined(SQLT_IBFLOAT) && defined(SQLT_IBDOUBLE) + } else if (dtype == SQLT_IBFLOAT || dtype == SQLT_IBDOUBLE) { + S->cols[colno].datalen = 1024; +#endif + } else if (dtype == SQLT_BIN) { + S->cols[colno].datalen = (ub4) col->maxlen * 2; /* raw characters to hex digits */ + } else { + S->cols[colno].datalen = (ub4) (col->maxlen * S->H->max_char_width); + } + + S->cols[colno].data = emalloc(S->cols[colno].datalen + 1); + dtype = SQLT_CHR; + } + + STMT_CALL(OCIDefineByPos, + (S->stmt, + &S->cols[colno].def, + S->err, + colno + 1, + S->cols[colno].data, + S->cols[colno].datalen, + dtype, + &S->cols[colno].indicator, + &S->cols[colno].fetched_len, + &S->cols[colno].retcode, + dyn ? OCI_DYNAMIC_FETCH : OCI_DEFAULT)); + + if (dyn) { + STMT_CALL(OCIDefineDynamic, (S->cols[colno].def, S->err, &S->cols[colno], oci_define_callback)); + } + + return 1; +} /* }}} */ + +struct _oci_lob_env { + OCISvcCtx *svc; + OCIError *err; +}; +typedef struct _oci_lob_env oci_lob_env; + +struct oci_lob_self { + zval dbh; + pdo_stmt_t *stmt; + pdo_oci_stmt *S; + OCILobLocator *lob; + oci_lob_env *E; + ub4 offset; + ub1 csfrm; +}; + +static ssize_t oci_blob_write(php_stream *stream, const char *buf, size_t count) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + ub4 amt; + sword r; + + amt = (ub4) count; + r = OCILobWrite(self->E->svc, + self->E->err, + self->lob, + &amt, + self->offset, + (char *) buf, + (ub4) count, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + + if (r != OCI_SUCCESS) { + return (ssize_t) -1; + } + + self->offset += amt; + return amt; +} + +static ssize_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, + 0, + self->csfrm); + + if (r != OCI_SUCCESS && r != OCI_NEED_DATA) { + return (ssize_t) -1; + } + + self->offset += self->csfrm == 0 ? byte_amt : char_amt; + if (byte_amt < count) { + stream->eof = 1; + } + return byte_amt; +} + +static int oci_blob_close(php_stream *stream, int close_handle) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + pdo_stmt_t *stmt = self->stmt; + + if (close_handle) { + zend_object *obj = &stmt->std; + + OCILobClose(self->E->svc, self->E->err, self->lob); + zval_ptr_dtor(&self->dbh); + GC_DELREF(obj); + efree(self->E); + efree(self); + } + + /* php_pdo_free_statement(stmt); */ + return 0; +} + +static int oci_blob_flush(php_stream *stream) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + OCILobFlushBuffer(self->E->svc, self->E->err, self->lob, 0); + return 0; +} + +static int oci_blob_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + + if (offset >= PDO_OCI_LOBMAXSIZE) { + return -1; + } else { + self->offset = (ub4) offset + 1; /* Oracle LOBS are 1-based, but PHP is 0-based */ + return 0; + } +} + +static const php_stream_ops oci_blob_stream_ops = {oci_blob_write, + oci_blob_read, + oci_blob_close, + oci_blob_flush, + "pdo_oci blob stream", + oci_blob_seek, + NULL, + NULL, + NULL}; + +static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLocator *lob) { + php_stream *stm; + struct oci_lob_self *self = ecalloc(1, sizeof(*self)); + + ZVAL_COPY_VALUE(&self->dbh, dbh); + self->lob = lob; + self->offset = 1; /* 1-based */ + self->stmt = stmt; + self->S = (pdo_oci_stmt *) stmt->driver_data; + self->E = ecalloc(1, sizeof(oci_lob_env)); + self->E->svc = self->S->H->svc; + self->E->err = self->S->err; + + 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) { + zend_object *obj; + obj = &stmt->std; + Z_ADDREF(self->dbh); + GC_ADDREF(obj); + return stm; + } + + efree(self); + return NULL; +} + +static int oci_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + pdo_oci_column *C = &S->cols[colno]; + + /* check the indicator to ensure that the data is intact */ + if (C->indicator == -1) { + /* A NULL value */ + ZVAL_NULL(result); + return 1; + } else if (C->indicator == 0) { + /* it was stored perfectly */ + + if (C->dtype == SQLT_BLOB || C->dtype == SQLT_CLOB) { + if (C->data) { + php_stream *stream = + oci_create_lob_stream(&stmt->database_object_handle, stmt, (OCILobLocator *) C->data); + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) C->data, OCI_LOB_READONLY); + php_stream_to_zval(stream, result); + return 1; + } + return 0; + } + + ZVAL_STRINGL_FAST(result, C->data, C->fetched_len); + return 1; + } else { + /* it was truncated */ + php_error_docref(NULL, E_WARNING, "Column %d data was too large for buffer and was truncated to fit it", colno); + ZVAL_STRINGL(result, C->data, C->fetched_len); + return 1; + } +} /* }}} */ + +static int oci_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + OCIParam *param = NULL; + ub2 dtype, precis; + sb1 scale; + zval flags; + ub1 isnull, charset_form; + if (!S->stmt) { + return FAILURE; + } + if (colno >= stmt->column_count) { + /* error invalid column */ + return FAILURE; + } + + array_init(return_value); + array_init(&flags); + + /* describe the column */ + STMT_CALL(OCIParamGet, (S->stmt, OCI_HTYPE_STMT, S->err, (dvoid *) ¶m, colno + 1)); + + /* column data type */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_TYPE", (param, OCI_DTYPE_PARAM, &dtype, 0, OCI_ATTR_DATA_TYPE, S->err)); + + /* column precision */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_PRECISION", (param, OCI_DTYPE_PARAM, &precis, 0, OCI_ATTR_PRECISION, S->err)); + + /* column scale */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_SCALE", (param, OCI_DTYPE_PARAM, &scale, 0, OCI_ATTR_SCALE, S->err)); + + /* string column charset form */ + if (dtype == SQLT_CHR || dtype == SQLT_VCS || dtype == SQLT_AFC || dtype == SQLT_CLOB) { + STMT_CALL_MSG(OCIAttrGet, + "OCI_ATTR_CHARSET_FORM", + (param, OCI_DTYPE_PARAM, &charset_form, 0, OCI_ATTR_CHARSET_FORM, S->err)); + } + + if (dtype) { + /* if there is a declared type */ + switch (dtype) { +#ifdef SQLT_TIMESTAMP + case SQLT_TIMESTAMP: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP"); + add_assoc_string(return_value, "native_type", "TIMESTAMP"); + break; +#endif +#ifdef SQLT_TIMESTAMP_TZ + case SQLT_TIMESTAMP_TZ: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP WITH TIMEZONE"); + add_assoc_string(return_value, "native_type", "TIMESTAMP WITH TIMEZONE"); + break; +#endif +#ifdef SQLT_TIMESTAMP_LTZ + case SQLT_TIMESTAMP_LTZ: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP WITH LOCAL TIMEZONE"); + add_assoc_string(return_value, "native_type", "TIMESTAMP WITH LOCAL TIMEZONE"); + break; +#endif +#ifdef SQLT_INTERVAL_YM + case SQLT_INTERVAL_YM: + add_assoc_string(return_value, "oci:decl_type", "INTERVAL YEAR TO MONTH"); + add_assoc_string(return_value, "native_type", "INTERVAL YEAR TO MONTH"); + break; +#endif +#ifdef SQLT_INTERVAL_DS + case SQLT_INTERVAL_DS: + add_assoc_string(return_value, "oci:decl_type", "INTERVAL DAY TO SECOND"); + add_assoc_string(return_value, "native_type", "INTERVAL DAY TO SECOND"); + break; +#endif + case SQLT_DAT: + add_assoc_string(return_value, "oci:decl_type", "DATE"); + add_assoc_string(return_value, "native_type", "DATE"); + break; + case SQLT_FLT: + case SQLT_NUM: + /* if the precision is nonzero and scale is -127 then it is a FLOAT */ + if (scale == -127 && precis != 0) { + add_assoc_string(return_value, "oci:decl_type", "FLOAT"); + add_assoc_string(return_value, "native_type", "FLOAT"); + } else { + add_assoc_string(return_value, "oci:decl_type", "NUMBER"); + add_assoc_string(return_value, "native_type", "NUMBER"); + } + break; + case SQLT_LNG: + add_assoc_string(return_value, "oci:decl_type", "LONG"); + add_assoc_string(return_value, "native_type", "LONG"); + break; + case SQLT_BIN: + add_assoc_string(return_value, "oci:decl_type", "RAW"); + add_assoc_string(return_value, "native_type", "RAW"); + break; + case SQLT_LBI: + add_assoc_string(return_value, "oci:decl_type", "LONG RAW"); + add_assoc_string(return_value, "native_type", "LONG RAW"); + break; + case SQLT_CHR: + case SQLT_VCS: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NVARCHAR2"); + add_assoc_string(return_value, "native_type", "NVARCHAR2"); + } else { + add_assoc_string(return_value, "oci:decl_type", "VARCHAR2"); + add_assoc_string(return_value, "native_type", "VARCHAR2"); + } + break; + case SQLT_AFC: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NCHAR"); + add_assoc_string(return_value, "native_type", "NCHAR"); + } else { + add_assoc_string(return_value, "oci:decl_type", "CHAR"); + add_assoc_string(return_value, "native_type", "CHAR"); + } + break; + case SQLT_BLOB: + add_assoc_string(return_value, "oci:decl_type", "BLOB"); + add_next_index_string(&flags, "blob"); + add_assoc_string(return_value, "native_type", "BLOB"); + break; + case SQLT_CLOB: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NCLOB"); + add_assoc_string(return_value, "native_type", "NCLOB"); + } else { + add_assoc_string(return_value, "oci:decl_type", "CLOB"); + add_assoc_string(return_value, "native_type", "CLOB"); + } + add_next_index_string(&flags, "blob"); + break; + case SQLT_BFILE: + add_assoc_string(return_value, "oci:decl_type", "BFILE"); + add_next_index_string(&flags, "blob"); + add_assoc_string(return_value, "native_type", "BFILE"); + break; + case SQLT_RDD: + add_assoc_string(return_value, "oci:decl_type", "ROWID"); + add_assoc_string(return_value, "native_type", "ROWID"); + break; + case SQLT_BFLOAT: + case SQLT_IBFLOAT: + add_assoc_string(return_value, "oci:decl_type", "BINARY_FLOAT"); + add_assoc_string(return_value, "native_type", "BINARY_FLOAT"); + break; + case SQLT_BDOUBLE: + case SQLT_IBDOUBLE: + add_assoc_string(return_value, "oci:decl_type", "BINARY_DOUBLE"); + add_assoc_string(return_value, "native_type", "BINARY_DOUBLE"); + break; + default: + add_assoc_long(return_value, "oci:decl_type", dtype); + add_assoc_string(return_value, "native_type", "UNKNOWN"); + } + } else { + /* if the column is NULL */ + add_assoc_long(return_value, "oci:decl_type", 0); + add_assoc_string(return_value, "native_type", "NULL"); + } + + switch (dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_LOB); + break; + default: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + break; + } + + /* column can be null */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_IS_NULL", (param, OCI_DTYPE_PARAM, &isnull, 0, OCI_ATTR_IS_NULL, S->err)); + + if (isnull) { + add_next_index_string(&flags, "nullable"); + } else { + add_next_index_string(&flags, "not_null"); + } + + /* PDO type */ + switch (dtype) { + case SQLT_BFILE: + case SQLT_BLOB: + case SQLT_CLOB: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_LOB); + break; + default: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + } + + add_assoc_long(return_value, "scale", scale); + add_assoc_zval(return_value, "flags", &flags); + + OCIDescriptorFree(param, OCI_DTYPE_PARAM); + return SUCCESS; +} /* }}} */ + +const struct pdo_stmt_methods swoole_oci_stmt_methods = {oci_stmt_dtor, + oci_stmt_execute, + oci_stmt_fetch, + oci_stmt_describe, + oci_stmt_get_col, + oci_stmt_param_hook, + NULL, /* set_attr */ + NULL, /* get_attr */ + oci_stmt_col_meta, + NULL, + NULL}; +#endif diff --git a/thirdparty/php84/pdo_oci/php_pdo_oci_int.h b/thirdparty/php84/pdo_oci/php_pdo_oci_int.h new file mode 100644 index 00000000000..116fdf0bbf8 --- /dev/null +++ b/thirdparty/php84/pdo_oci/php_pdo_oci_int.h @@ -0,0 +1,116 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_OCI_INT_H +#define PHP_PDO_OCI_INT_H + +#include "zend_portability.h" + +ZEND_DIAGNOSTIC_IGNORED_START("-Wstrict-prototypes") +#include +ZEND_DIAGNOSTIC_IGNORED_END + +typedef struct { + const char *file; + int line; + sb4 errcode; + char *errmsg; +} pdo_oci_error_info; + +/* stuff we use in an OCI database handle */ +typedef struct { + OCIServer *server; + OCISession *session; + OCIEnv *env; + OCIError *err; + OCISvcCtx *svc; + /* OCI9; 0 == use NLS_LANG */ + ub4 prefetch; + ub2 charset; + sword last_err; + sb4 max_char_width; + + unsigned attached : 1; + unsigned _reserved : 31; + + pdo_oci_error_info einfo; +} pdo_oci_db_handle; + +typedef struct { + OCIDefine *def; + ub2 fetched_len; + ub2 retcode; + sb2 indicator; + + char *data; + ub4 datalen; + + ub2 dtype; + +} pdo_oci_column; + +typedef struct { + pdo_oci_db_handle *H; + OCIStmt *stmt; + OCIError *err; + sword last_err; + ub2 stmt_type; + ub4 exec_type; + pdo_oci_column *cols; + pdo_oci_error_info einfo; + unsigned int have_blobs : 1; +} pdo_oci_stmt; + +typedef struct { + OCIBind *bind; /* allocated by OCI */ + sb2 oci_type; + sb2 indicator; + ub2 retcode; + + ub4 actual_len; + + dvoid *thing; /* for LOBS, REFCURSORS etc. */ + + unsigned used_for_output; +} pdo_oci_bound_param; + +extern const ub4 PDO_OCI_INIT_MODE; +extern const pdo_driver_t pdo_oci_driver; +extern OCIEnv *pdo_oci_Env; + +ub4 _oci_error( + OCIError *err, pdo_dbh_t *dbh, pdo_stmt_t *stmt, char *what, sword status, int isinit, const char *file, int line); +#define oci_init_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, TRUE, __FILE__, __LINE__) +#define oci_drv_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, FALSE, __FILE__, __LINE__) +#define oci_stmt_error(w) _oci_error(S->err, stmt->dbh, stmt, w, S->last_err, FALSE, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_oci_stmt_methods; + +/* Default prefetch size in number of rows */ +#define PDO_OCI_PREFETCH_DEFAULT 100 + +/* Arbitrary assumed row length for prefetch memory limit calcuation */ +#define PDO_OCI_PREFETCH_ROWSIZE 1024 + +enum { + PDO_OCI_ATTR_ACTION = PDO_ATTR_DRIVER_SPECIFIC, + PDO_OCI_ATTR_CLIENT_INFO, + PDO_OCI_ATTR_CLIENT_IDENTIFIER, + PDO_OCI_ATTR_MODULE, + PDO_OCI_ATTR_CALL_TIMEOUT +}; + +#endif /* PHP_PDO_OCI_INT_H */ diff --git a/thirdparty/php84/pdo_odbc/odbc_driver.c b/thirdparty/php84/pdo_odbc/odbc_driver.c new file mode 100644 index 00000000000..28a5199901f --- /dev/null +++ b/thirdparty/php84/pdo_odbc/odbc_driver.c @@ -0,0 +1,641 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80400 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +/* this file actually lives in main/ */ +#include "php_odbc_utils.h" +#include "php_pdo_odbc.h" +#include "php_pdo_odbc_int.h" +#include "zend_exceptions.h" + +static void pdo_odbc_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + zend_string *message = NULL; + + if (stmt) { + S = (pdo_odbc_stmt *) stmt->driver_data; + einfo = &S->einfo; + } + + message = strpprintf(0, + "%s (%s[%ld] at %s:%d)", + einfo->last_err_msg, + einfo->what, + (long) einfo->last_error, + einfo->file, + einfo->line); + + add_next_index_long(info, einfo->last_error); + add_next_index_str(info, message); + add_next_index_string(info, einfo->last_state); +} + +void pdo_odbc_error( + pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line) /* {{{ */ +{ + SQLRETURN rc; + SQLSMALLINT errmsgsize = 0; + SQLHANDLE eh; + SQLSMALLINT htype, recno = 1; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_odbc_stmt *) stmt->driver_data; + + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } + + if (statement == SQL_NULL_HSTMT && S) { + statement = S->stmt; + } + + if (statement) { + htype = SQL_HANDLE_STMT; + eh = statement; + } else if (H->dbc) { + htype = SQL_HANDLE_DBC; + eh = H->dbc; + } else { + htype = SQL_HANDLE_ENV; + eh = H->env; + } + + rc = SQLGetDiagRec(htype, + eh, + recno++, + (SQLCHAR *) einfo->last_state, + &einfo->last_error, + (SQLCHAR *) einfo->last_err_msg, + sizeof(einfo->last_err_msg) - 1, + &errmsgsize); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + errmsgsize = 0; + } + + einfo->last_err_msg[errmsgsize] = '\0'; + einfo->file = file; + einfo->line = line; + einfo->what = what; + + strcpy(*pdo_err, einfo->last_state); + /* printf("@@ SQLSTATE[%s] %s\n", *pdo_err, einfo->last_err_msg); */ + if (!dbh->methods) { + zend_throw_exception_ex(php_pdo_get_exception(), + einfo->last_error, + "SQLSTATE[%s] %s: %d %s", + *pdo_err, + what, + einfo->last_error, + einfo->last_err_msg); + } + + /* just like a cursor, once you start pulling, you need to keep + * going until the end; SQL Server (at least) will mess with the + * actual cursor state if you don't finish retrieving all the + * diagnostic records (which can be generated by PRINT statements + * in the query, for instance). */ + while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + SQLCHAR discard_state[6]; + SQLCHAR discard_buf[1024]; + SQLINTEGER code; + rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code, discard_buf, sizeof(discard_buf) - 1, &errmsgsize); + } +} +/* }}} */ + +static void odbc_handle_closer(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + if (H->dbc != SQL_NULL_HANDLE) { + SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + SQLDisconnect(H->dbc); + SQLFreeHandle(SQL_HANDLE_DBC, H->dbc); + H->dbc = NULL; + } + SQLFreeHandle(SQL_HANDLE_ENV, H->env); + H->env = NULL; + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; +} + +static bool odbc_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) { + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_stmt *S = ecalloc(1, sizeof(*S)); + enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY; + int ret; + zend_string *nsql = NULL; + + S->H = H; + S->assume_utf8 = H->assume_utf8; + + /* before we prepare, we need to peek at the query; if it uses named parameters, + * we want PDO to rewrite them for us */ + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return false; + } + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt); + + if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) { + efree(S); + if (nsql) { + zend_string_release(nsql); + } + pdo_odbc_drv_error("SQLAllocStmt"); + return false; + } + + stmt->driver_data = S; + + cursor_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY); + if (cursor_type != PDO_CURSOR_FWDONLY) { + rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_CURSOR_SCROLLABLE, (void *) SQL_SCROLLABLE, 0); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_CURSOR_SCROLLABLE"); + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + if (nsql) { + zend_string_release(nsql); + } + return false; + } + } + + rc = SQLPrepare(S->stmt, (SQLCHAR *) ZSTR_VAL(sql), SQL_NTS); + if (nsql) { + zend_string_release(nsql); + } + + stmt->methods = &odbc_stmt_methods; + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLPrepare"); + if (rc != SQL_SUCCESS_WITH_INFO) { + /* clone error information into the db handle */ + strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg); + H->einfo.file = S->einfo.file; + H->einfo.line = S->einfo.line; + H->einfo.what = S->einfo.what; + strcpy(dbh->error_code, stmt->error_code); + } + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + return true; +} + +static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + SQLLEN row_count = -1; + PDO_ODBC_HSTMT stmt; + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: STMT"); + return -1; + } + + rc = SQLExecDirect(stmt, (SQLCHAR *) ZSTR_VAL(sql), ZSTR_LEN(sql)); + + if (rc == SQL_NO_DATA) { + /* If SQLExecDirect executes a searched update or delete statement that + * does not affect any rows at the data source, the call to + * SQLExecDirect returns SQL_NO_DATA. */ + row_count = 0; + goto out; + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLExecDirect"); + goto out; + } + + rc = SQLRowCount(stmt, &row_count); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLRowCount"); + goto out; + } + if (row_count == -1) { + row_count = 0; + } +out: + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return row_count; +} + +/* TODO: Do ODBC quoter +static int odbc_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t +*quotedlen, enum pdo_param_type param_type ) +{ + // pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + // TODO: figure it out + return 0; +} +*/ + +static bool odbc_handle_begin(pdo_dbh_t *dbh) { + if (dbh->auto_commit) { + /* we need to disable auto-commit now, to be able to initiate a transaction */ + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = OFF"); + return false; + } + } + return true; +} + +static bool odbc_handle_commit(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_COMMIT); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Commit"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + + if (dbh->auto_commit) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + return true; +} + +static bool odbc_handle_rollback(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Rollback"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + if (dbh->auto_commit && H->dbc) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + + return true; +} + +static bool odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + bool bval; + + switch (attr) { + case PDO_ODBC_ATTR_ASSUME_UTF8: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->assume_utf8 = bval; + return true; + case PDO_ATTR_AUTOCOMMIT: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + if (dbh->in_txn) { + pdo_raise_impl_error( + dbh, NULL, "HY000", "Cannot change autocommit mode while a transaction is already open"); + return false; + } + if (dbh->auto_commit ^ bval) { + dbh->auto_commit = bval; + RETCODE rc = + SQLSetConnectAttr(H->dbc, + SQL_ATTR_AUTOCOMMIT, + dbh->auto_commit ? (SQLPOINTER) SQL_AUTOCOMMIT_ON : (SQLPOINTER) SQL_AUTOCOMMIT_OFF, + SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error(dbh->auto_commit ? "SQLSetConnectAttr AUTOCOMMIT = ON" + : "SQLSetConnectAttr AUTOCOMMIT = OFF"); + return false; + } + } + return true; + default: + strcpy(H->einfo.last_err_msg, "Unknown Attribute"); + H->einfo.what = "setAttribute"; + strcpy(H->einfo.last_state, "IM001"); + return false; + } +} + +static int pdo_odbc_get_info_string(pdo_dbh_t *dbh, SQLUSMALLINT type, zval *val) { + RETCODE rc; + SQLSMALLINT out_len; + char buf[256]; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + rc = SQLGetInfo(H->dbc, type, (SQLPOINTER) buf, sizeof(buf), &out_len); + /* returning -1 is treated as an error, not as unsupported */ + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return -1; + } + ZVAL_STRINGL(val, buf, out_len); + return 1; +} + +static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE); + return 1; + + case PDO_ATTR_SERVER_VERSION: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_VER, val); + case PDO_ATTR_SERVER_INFO: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_NAME, val); + case PDO_ATTR_PREFETCH: + case PDO_ATTR_TIMEOUT: + case PDO_ATTR_CONNECTION_STATUS: + break; + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, H->assume_utf8); + return 1; + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(val, dbh->auto_commit); + return 1; + } + return 0; +} + +static zend_result odbc_handle_check_liveness(pdo_dbh_t *dbh) { + RETCODE ret; + UCHAR d_name[32]; + SQLSMALLINT len; + SQLUINTEGER dead = SQL_CD_FALSE; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + ret = SQLGetConnectAttr(H->dbc, SQL_ATTR_CONNECTION_DEAD, &dead, 0, NULL); + if (ret == SQL_SUCCESS && dead == SQL_CD_TRUE) { + /* Bail early here, since we know it's gone */ + return FAILURE; + } + /* + * If the driver doesn't support SQL_ATTR_CONNECTION_DEAD, or if + * it returns false (which could be a false positive), fall back + * to using SQL_DATA_SOURCE_READ_ONLY, which isn't semantically + * correct, but works with many drivers. + */ + ret = SQLGetInfo(H->dbc, SQL_DATA_SOURCE_READ_ONLY, d_name, sizeof(d_name), &len); + + if (ret != SQL_SUCCESS || len == 0) { + return FAILURE; + } + return SUCCESS; +} + +static const struct pdo_dbh_methods odbc_methods = { + odbc_handle_closer, + odbc_handle_preparer, + odbc_handle_doer, + NULL, /* quoter */ + odbc_handle_begin, + odbc_handle_commit, + odbc_handle_rollback, + odbc_handle_set_attr, + NULL, /* last id */ + pdo_odbc_fetch_error_func, + odbc_handle_get_attr, /* get attr */ + odbc_handle_check_liveness, /* check_liveness */ + NULL, /* get_driver_methods */ + NULL, /* request_shutdown */ + NULL, /* in transaction, use PDO's internal tracking mechanism */ + NULL, /* get_gc */ + NULL /* scanner */ +}; + +static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_odbc_db_handle *H; + RETCODE rc; + int use_direct = 0; + zend_ulong cursor_lib; + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + + dbh->driver_data = H; + + rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H->env); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: ENV"); + goto fail; + } + + rc = SQLSetEnvAttr(H->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLSetEnvAttr: ODBC3"); + goto fail; + } + +#ifdef SQL_ATTR_CONNECTION_POOLING + if (pdo_odbc_pool_on != SQL_CP_OFF) { + rc = SQLSetEnvAttr(H->env, SQL_ATTR_CP_MATCH, (void *) pdo_odbc_pool_mode, 0); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetEnvAttr: SQL_ATTR_CP_MATCH"); + goto fail; + } + } +#endif + + rc = SQLAllocHandle(SQL_HANDLE_DBC, H->env, &H->dbc); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: DBC"); + goto fail; + } + + rc = SQLSetConnectAttr(H->dbc, + SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER) (intptr_t) (dbh->auto_commit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), + SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT"); + goto fail; + } + + /* set up the cursor library, if needed, or if configured explicitly */ + cursor_lib = pdo_attr_lval(driver_options, PDO_ODBC_ATTR_USE_CURSOR_LIBRARY, SQL_CUR_USE_IF_NEEDED); + rc = SQLSetConnectAttr(H->dbc, SQL_ODBC_CURSORS, (void *) cursor_lib, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS && cursor_lib != SQL_CUR_USE_IF_NEEDED) { + pdo_odbc_drv_error("SQLSetConnectAttr SQL_ODBC_CURSORS"); + goto fail; + } + + /* a connection string may have = but not ; - i.e. "DSN=PHP" */ + if (strchr(dbh->data_source, '=')) { + SQLCHAR dsnbuf[1024]; + SQLSMALLINT dsnbuflen; + + use_direct = 1; + + size_t db_len = strlen(dbh->data_source); + bool use_uid_arg = + dbh->username != NULL && !php_memnistr(dbh->data_source, "uid=", strlen("uid="), dbh->data_source + db_len); + bool use_pwd_arg = + dbh->password != NULL && !php_memnistr(dbh->data_source, "pwd=", strlen("pwd="), dbh->data_source + db_len); + + if (use_uid_arg || use_pwd_arg) { + char *db = (char *) emalloc(db_len + 1); + strcpy(db, dbh->data_source); + char *db_end = db + db_len; + db_end--; + if ((unsigned char) *(db_end) == ';') { + *db_end = '\0'; + } + + char *uid = NULL, *pwd = NULL, *dsn = NULL; + bool should_quote_uid, should_quote_pwd; + size_t new_dsn_size; + + if (use_uid_arg) { + should_quote_uid = + !php_odbc_connstr_is_quoted(dbh->username) && php_odbc_connstr_should_quote(dbh->username); + if (should_quote_uid) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->username); + uid = emalloc(estimated_length); + php_odbc_connstr_quote(uid, dbh->username, estimated_length); + } else { + uid = dbh->username; + } + + if (!use_pwd_arg) { + new_dsn_size = strlen(db) + strlen(uid) + strlen(";UID=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;UID=%s;", db, uid); + } + } + + if (use_pwd_arg) { + should_quote_pwd = + !php_odbc_connstr_is_quoted(dbh->password) && php_odbc_connstr_should_quote(dbh->password); + if (should_quote_pwd) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->password); + pwd = emalloc(estimated_length); + php_odbc_connstr_quote(pwd, dbh->password, estimated_length); + } else { + pwd = dbh->password; + } + + if (!use_uid_arg) { + new_dsn_size = strlen(db) + strlen(pwd) + strlen(";PWD=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;PWD=%s;", db, pwd); + } + } + + if (use_uid_arg && use_pwd_arg) { + new_dsn_size = strlen(db) + strlen(uid) + strlen(pwd) + strlen(";UID=;PWD=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;UID=%s;PWD=%s;", db, uid, pwd); + } + + pefree((char *) dbh->data_source, dbh->is_persistent); + dbh->data_source = dsn; + if (uid && should_quote_uid) { + efree(uid); + } + if (pwd && should_quote_pwd) { + efree(pwd); + } + efree(db); + } + + rc = SQLDriverConnect(H->dbc, + NULL, + (SQLCHAR *) dbh->data_source, + strlen(dbh->data_source), + dsnbuf, + sizeof(dsnbuf) - 1, + &dsnbuflen, + SQL_DRIVER_NOPROMPT); + } + if (!use_direct) { + rc = SQLConnect(H->dbc, + (SQLCHAR *) dbh->data_source, + SQL_NTS, + (SQLCHAR *) dbh->username, + SQL_NTS, + (SQLCHAR *) dbh->password, + SQL_NTS); + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error(use_direct ? "SQLDriverConnect" : "SQLConnect"); + goto fail; + } + + /* TODO: if we want to play nicely, we should check to see if the driver really supports ODBC v3 or not */ + + dbh->methods = &odbc_methods; + dbh->alloc_own_columns = 1; + + return 1; + +fail: + dbh->methods = &odbc_methods; + return 0; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_odbc_driver = {PDO_DRIVER_HEADER(odbc), pdo_odbc_handle_factory}; +#endif diff --git a/thirdparty/php84/pdo_odbc/odbc_stmt.c b/thirdparty/php84/pdo_odbc/odbc_stmt.c new file mode 100644 index 00000000000..3407ef26918 --- /dev/null +++ b/thirdparty/php84/pdo_odbc/odbc_stmt.c @@ -0,0 +1,867 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80400 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_odbc.h" +#include "php_pdo_odbc_int.h" + +enum pdo_odbc_conv_result { PDO_ODBC_CONV_NOT_REQUIRED, PDO_ODBC_CONV_OK, PDO_ODBC_CONV_FAIL }; + +static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SQLSMALLINT sqltype) { + if (!S->assume_utf8) return 0; + switch (sqltype) { +#ifdef SQL_WCHAR + case SQL_WCHAR: + return 1; +#endif +#ifdef SQL_WLONGVARCHAR + case SQL_WLONGVARCHAR: + return 1; +#endif +#ifdef SQL_WVARCHAR + case SQL_WVARCHAR: + return 1; +#endif + default: + return 0; + } +} + +static int pdo_odbc_utf82ucs2( + pdo_stmt_t *stmt, int is_unicode, const char *buf, zend_ulong buflen, zend_ulong *outlen) { +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + DWORD ret; + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, NULL, 0); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + + if (S->convbufsize <= ret) { + S->convbufsize = ret + sizeof(WCHAR); + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, (LPWSTR) S->convbuf, S->convbufsize / sizeof(WCHAR)); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + *outlen = ret; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, zval *result) { +#ifdef PHP_WIN32 + ZEND_ASSERT(Z_TYPE_P(result) == IS_STRING); + if (is_unicode && Z_STRLEN_P(result) != 0) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + DWORD ret; + + ret = WideCharToMultiByte( + CP_UTF8, 0, (LPCWSTR) Z_STRVAL_P(result), Z_STRLEN_P(result) / sizeof(WCHAR), NULL, 0, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + zend_string *str = zend_string_alloc(ret, 0); + ret = WideCharToMultiByte(CP_UTF8, + 0, + (LPCWSTR) Z_STRVAL_P(result), + Z_STRLEN_P(result) / sizeof(WCHAR), + ZSTR_VAL(str), + ZSTR_LEN(str), + NULL, + NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + ZSTR_VAL(str)[ret] = '\0'; + zval_ptr_dtor_str(result); + ZVAL_STR(result, str); + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S) { + if (S->cols) { + int i; + + for (i = 0; i < S->col_count; i++) { + if (S->cols[i].data) { + efree(S->cols[i].data); + } + } + efree(S->cols); + S->cols = NULL; + S->col_count = 0; + } +} + +static int odbc_stmt_dtor(pdo_stmt_t *stmt) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + if (S->stmt != SQL_NULL_HANDLE) { + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + S->stmt = SQL_NULL_HANDLE; + } + + free_cols(stmt, S); + if (S->convbuf) { + efree(S->convbuf); + } + efree(S); + + return 1; +} + +static int odbc_stmt_execute(pdo_stmt_t *stmt) { + RETCODE rc, rc1; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + char *buf = NULL; + SQLLEN row_count = -1; + + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + + rc = SQLExecute(S->stmt); + + while (rc == SQL_NEED_DATA) { + struct pdo_bound_param_data *param; + + rc = SQLParamData(S->stmt, (SQLPOINTER *) ¶m); + if (rc == SQL_NEED_DATA) { + php_stream *stm; + int len; + pdo_odbc_param *P; + zval *parameter; + + P = (pdo_odbc_param *) param->driver_data; + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) != IS_RESOURCE) { + /* they passed in a string */ + zend_ulong ulen; + convert_to_string(parameter); + + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &ulen)) { + case PDO_ODBC_CONV_NOT_REQUIRED: + rc1 = SQLPutData(S->stmt, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter)); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_OK: + rc1 = SQLPutData(S->stmt, S->convbuf, ulen); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_FAIL: + pdo_odbc_stmt_error("error converting input string"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + continue; + } + + /* we assume that LOBs are binary and don't need charset + * conversion */ + + php_stream_from_zval_no_verify(stm, parameter); + if (!stm) { + /* shouldn't happen either */ + pdo_odbc_stmt_error("input LOB is no longer a stream"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + + /* now suck data from the stream and stick it into the database */ + if (buf == NULL) { + buf = emalloc(8192); + } + + do { + len = php_stream_read(stm, buf, 8192); + if (len == 0) { + break; + } + rc1 = SQLPutData(S->stmt, buf, len); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + } while (1); + } + } + + if (buf) { + efree(buf); + } + + switch (rc) { + case SQL_SUCCESS: + break; + case SQL_NO_DATA_FOUND: + case SQL_SUCCESS_WITH_INFO: + pdo_odbc_stmt_error("SQLExecute"); + break; + + default: + pdo_odbc_stmt_error("SQLExecute"); + return 0; + } + + SQLRowCount(S->stmt, &row_count); + stmt->row_count = row_count; + + if (S->cols == NULL) { + /* do first-time-only definition of bind/mapping stuff */ + SQLSMALLINT colcount; + + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + + stmt->column_count = S->col_count = (int) colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + } + + return 1; +} + +static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, enum pdo_param_event event_type) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + RETCODE rc; + SQLSMALLINT sqltype = 0, ctype = 0, scale = 0, nullable = 0; + SQLULEN precision = 0; + pdo_odbc_param *P; + zval *parameter; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + switch (event_type) { + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + case PDO_PARAM_EVT_NORMALIZE: + /* Do nothing */ + break; + + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: { + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_LOB: + break; + + case PDO_PARAM_STMT: + return 0; + + default: + break; + } + + rc = SQLDescribeParam(S->stmt, (SQLUSMALLINT) param->paramno + 1, &sqltype, &precision, &scale, &nullable); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + /* MS Access, for instance, doesn't support SQLDescribeParam, + * so we need to guess */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_INT: + sqltype = SQL_INTEGER; + break; + case PDO_PARAM_LOB: + sqltype = SQL_LONGVARBINARY; + break; + default: + sqltype = SQL_LONGVARCHAR; + } + precision = 4000; + scale = 5; + nullable = 1; + + if (param->max_value_len > 0) { + precision = param->max_value_len; + } + } + if (sqltype == SQL_BINARY || sqltype == SQL_VARBINARY || sqltype == SQL_LONGVARBINARY) { + ctype = SQL_C_BINARY; + } else { + ctype = SQL_C_CHAR; + } + + P = emalloc(sizeof(*P)); + param->driver_data = P; + + P->len = 0; /* is re-populated each EXEC_PRE */ + P->outbuf = NULL; + + P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype); + if (P->is_unicode) { + /* avoid driver auto-translation: we'll do it ourselves */ + ctype = SQL_C_BINARY; + } + + if ((param->param_type & PDO_PARAM_INPUT_OUTPUT) == PDO_PARAM_INPUT_OUTPUT) { + P->paramtype = SQL_PARAM_INPUT_OUTPUT; + } else if (param->max_value_len <= 0) { + P->paramtype = SQL_PARAM_INPUT; + } else { + P->paramtype = SQL_PARAM_OUTPUT; + } + + if (P->paramtype != SQL_PARAM_INPUT) { + if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_NULL) { + /* need an explicit buffer to hold result */ + P->len = param->max_value_len > 0 ? param->max_value_len : precision; + if (P->is_unicode) { + P->len *= 2; + } + P->outbuf = emalloc(P->len + (P->is_unicode ? 2 : 1)); + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->paramtype != SQL_PARAM_INPUT) { + pdo_odbc_stmt_error("Can't bind a lob for output"); + return 0; + } + + rc = SQLBindParameter(S->stmt, + (SQLUSMALLINT) param->paramno + 1, + P->paramtype, + ctype, + sqltype, + precision, + scale, + P->paramtype == SQL_PARAM_INPUT ? (SQLPOINTER) param : P->outbuf, + P->len, + &P->len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLBindParameter"); + return 0; + } + + case PDO_PARAM_EVT_EXEC_PRE: + P = param->driver_data; + if (!Z_ISREF(param->parameter)) { + parameter = ¶m->parameter; + } else { + parameter = Z_REFVAL(param->parameter); + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_statbuf sb; + + php_stream_from_zval_no_verify(stm, parameter); + + if (!stm) { + return 0; + } + + if (0 == php_stream_stat(stm, &sb)) { + if (P->outbuf) { + int len, amount; + char *ptr = P->outbuf; + char *end = P->outbuf + P->len; + + P->len = 0; + do { + amount = end - ptr; + if (amount == 0) { + break; + } + if (amount > 8192) amount = 8192; + len = php_stream_read(stm, ptr, amount); + if (len == 0) { + break; + } + ptr += len; + P->len += len; + } while (1); + + } else { + P->len = SQL_LEN_DATA_AT_EXEC(sb.sb.st_size); + } + } else { + if (P->outbuf) { + P->len = 0; + } else { + P->len = SQL_LEN_DATA_AT_EXEC(0); + } + } + } else { + convert_to_string(parameter); + if (P->outbuf) { + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + } else if (Z_TYPE_P(parameter) == IS_NULL || PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL) { + P->len = SQL_NULL_DATA; + } else { + convert_to_string(parameter); + if (P->outbuf) { + zend_ulong ulen; + switch ( + pdo_odbc_utf82ucs2(stmt, P->is_unicode, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &ulen)) { + case PDO_ODBC_CONV_FAIL: + case PDO_ODBC_CONV_NOT_REQUIRED: + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + break; + case PDO_ODBC_CONV_OK: + P->len = ulen; + memcpy(P->outbuf, S->convbuf, P->len); + break; + } + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + P = param->driver_data; + + if (P->outbuf) { + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + zval_ptr_dtor(parameter); + + if (P->len >= 0) { + ZVAL_STRINGL(parameter, P->outbuf, P->len); + switch (pdo_odbc_ucs22utf8(stmt, P->is_unicode, parameter)) { + case PDO_ODBC_CONV_FAIL: + /* something fishy, but allow it to come back as binary */ + case PDO_ODBC_CONV_NOT_REQUIRED: + break; + case PDO_ODBC_CONV_OK: + break; + } + } else { + ZVAL_NULL(parameter); + } + } + return 1; + } + } + return 1; +} + +static int odbc_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) { + RETCODE rc; + SQLSMALLINT odbcori; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: + odbcori = SQL_FETCH_NEXT; + break; + case PDO_FETCH_ORI_PRIOR: + odbcori = SQL_FETCH_PRIOR; + break; + case PDO_FETCH_ORI_FIRST: + odbcori = SQL_FETCH_FIRST; + break; + case PDO_FETCH_ORI_LAST: + odbcori = SQL_FETCH_LAST; + break; + case PDO_FETCH_ORI_ABS: + odbcori = SQL_FETCH_ABSOLUTE; + break; + case PDO_FETCH_ORI_REL: + odbcori = SQL_FETCH_RELATIVE; + break; + default: + strcpy(stmt->error_code, "HY106"); + return 0; + } + rc = SQLFetchScroll(S->stmt, odbcori, offset); + + if (rc == SQL_SUCCESS) { + return 1; + } + if (rc == SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLFetchScroll"); + return 1; + } + + if (rc == SQL_NO_DATA) { + /* pdo_odbc_stmt_error("SQLFetchScroll"); */ + return 0; + } + + pdo_odbc_stmt_error("SQLFetchScroll"); + + return 0; +} + +static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + RETCODE rc; + SQLSMALLINT colnamelen; + SQLULEN colsize; + SQLLEN displaysize = 0; + + rc = SQLDescribeCol(S->stmt, + colno + 1, + (SQLCHAR *) S->cols[colno].colname, + sizeof(S->cols[colno].colname) - 1, + &colnamelen, + &S->cols[colno].coltype, + &colsize, + NULL, + NULL); + + /* This fixes a known issue with SQL Server and (max) lengths, + may affect others as well. If we are SQL_VARCHAR, + SQL_VARBINARY, or SQL_WVARCHAR (or any of the long variations) + and zero is returned from colsize then consider it long */ + if (0 == colsize && (S->cols[colno].coltype == SQL_VARCHAR || S->cols[colno].coltype == SQL_LONGVARCHAR || +#ifdef SQL_WVARCHAR + S->cols[colno].coltype == SQL_WVARCHAR || +#endif +#ifdef SQL_WLONGVARCHAR + S->cols[colno].coltype == SQL_WLONGVARCHAR || +#endif + S->cols[colno].coltype == SQL_VARBINARY || S->cols[colno].coltype == SQL_LONGVARBINARY)) { + S->going_long = 1; + } + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLDescribeCol"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + + rc = SQLColAttribute(S->stmt, colno + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &displaysize); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLColAttribute"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + colsize = displaysize; + + col->maxlen = S->cols[colno].datalen = colsize; + col->name = zend_string_init(S->cols[colno].colname, colnamelen, 0); + S->cols[colno].is_unicode = pdo_odbc_sqltype_is_unicode(S, S->cols[colno].coltype); + + /* tell ODBC to put it straight into our buffer, but only if it + * isn't "long" data, and only if we haven't already bound a long + * column. */ + if (colsize < 256 && !S->going_long) { + S->cols[colno].data = emalloc(colsize + 1); + S->cols[colno].is_long = 0; + + rc = SQLBindCol(S->stmt, + colno + 1, + S->cols[colno].is_unicode ? SQL_C_BINARY : SQL_C_CHAR, + S->cols[colno].data, + S->cols[colno].datalen + 1, + &S->cols[colno].fetched_len); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLBindCol"); + return 0; + } + } else { + /* allocate a smaller buffer to keep around for smaller + * "long" columns */ + S->cols[colno].data = emalloc(256); + S->going_long = 1; + S->cols[colno].is_long = 1; + } + + return 1; +} + +static int odbc_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) { + array_init(return_value); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + return 1; +} + +static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + pdo_odbc_column *C = &S->cols[colno]; + + /* if it is a column containing "long" data, perform late binding now */ + if (C->is_long) { + SQLLEN orig_fetched_len = SQL_NULL_DATA; + RETCODE rc; + + /* fetch it into C->data, which is allocated with a length + * 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, 256, &C->fetched_len); + orig_fetched_len = C->fetched_len; + + if (rc == SQL_SUCCESS && C->fetched_len < 256) { + /* all the data fit into our little buffer; + * jump down to the generic bound data case */ + goto in_data; + } + + if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) { + /* this is a 'long column' + + read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks + in order into the output buffer; 255 bytes are an optimistic assumption, since the driver may assert + more or less NUL bytes at the end; we cater to that later, if actual length information is available + + this loop has to work whether or not SQLGetData() provides the total column length. + calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read + for that size would be slower except maybe for extremely long columns.*/ + char *buf2 = emalloc(256); + zend_string *str = zend_string_init(C->data, 256, 0); + size_t used = 255; /* not 256; the driver NUL terminated the buffer */ + + 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); + + /* adjust `used` in case we have length info from the driver */ + if (orig_fetched_len >= 0 && C->fetched_len >= 0) { + SQLLEN fixed_used = orig_fetched_len - C->fetched_len; + ZEND_ASSERT(fixed_used <= used + 1); + used = fixed_used; + } + + /* resize output buffer and reassemble block */ + if (rc == SQL_SUCCESS_WITH_INFO || (rc == SQL_SUCCESS && C->fetched_len > 255)) { + /* point 5, in section "Retrieving Data with SQLGetData" in + http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx states that if + SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size) (if a driver fails to + follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */ + str = zend_string_realloc(str, used + 256, 0); + memcpy(ZSTR_VAL(str) + used, buf2, 256); + used = used + 255; + } else if (rc == SQL_SUCCESS) { + str = zend_string_realloc(str, used + C->fetched_len, 0); + memcpy(ZSTR_VAL(str) + used, buf2, C->fetched_len); + used = used + C->fetched_len; + } else { + /* includes SQL_NO_DATA */ + break; + } + + } while (1); + + efree(buf2); + + /* NULL terminate the buffer once, when finished, for use with the rest of PHP */ + ZSTR_VAL(str)[used] = '\0'; + ZVAL_STR(result, str); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } + + /* something went caca */ + return 1; + } + +in_data: + /* check the indicator to ensure that the data is intact */ + if (C->fetched_len == SQL_NULL_DATA) { + /* A NULL value */ + ZVAL_NULL(result); + return 1; + } else if (C->fetched_len >= 0) { + /* it was stored perfectly */ + ZVAL_STRINGL_FAST(result, C->data, C->fetched_len); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } else { + /* no data? */ + ZVAL_NULL(result); + return 1; + } + +unicode_conv: + switch (pdo_odbc_ucs22utf8(stmt, C->is_unicode, result)) { + case PDO_ODBC_CONV_FAIL: + /* oh well. They can have the binary version of it */ + case PDO_ODBC_CONV_NOT_REQUIRED: + /* shouldn't happen... */ + return 1; + case PDO_ODBC_CONV_OK: + return 1; + } + return 1; +} + +static int odbc_stmt_set_param(pdo_stmt_t *stmt, zend_long attr, zval *val) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: + convert_to_string(val); + rc = SQLSetCursorName(S->stmt, (SQLCHAR *) Z_STRVAL_P(val), Z_STRLEN_P(val)); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLSetCursorName"); + return 0; + + case PDO_ODBC_ATTR_ASSUME_UTF8: + S->assume_utf8 = zval_is_true(val); + return 0; + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "setAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: { + char buf[256]; + SQLSMALLINT len = 0; + rc = SQLGetCursorName(S->stmt, (SQLCHAR *) buf, sizeof(buf), &len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + ZVAL_STRINGL(val, buf, len); + return 1; + } + pdo_odbc_stmt_error("SQLGetCursorName"); + return 0; + } + + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, S->assume_utf8 ? 1 : 0); + return 0; + + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "getAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_next_rowset(pdo_stmt_t *stmt) { + SQLRETURN rc; + SQLSMALLINT colcount; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + /* NOTE: can't guarantee that output or input/output parameters + * are set until this fella returns SQL_NO_DATA, according to + * MSDN ODBC docs */ + rc = SQLMoreResults(S->stmt); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + + free_cols(stmt, S); + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + stmt->column_count = S->col_count = (int) colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + + return 1; +} + +static int odbc_stmt_close_cursor(pdo_stmt_t *stmt) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + rc = SQLCloseCursor(S->stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + return 1; +} + +const struct pdo_stmt_methods odbc_stmt_methods = {odbc_stmt_dtor, + odbc_stmt_execute, + odbc_stmt_fetch, + odbc_stmt_describe, + odbc_stmt_get_col, + odbc_stmt_param_hook, + odbc_stmt_set_param, + odbc_stmt_get_attr, + odbc_stmt_get_column_meta, + odbc_stmt_next_rowset, + odbc_stmt_close_cursor}; +#endif diff --git a/thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h b/thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h new file mode 100644 index 00000000000..0893f4b824b --- /dev/null +++ b/thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h @@ -0,0 +1,179 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifdef PHP_WIN32 +#define PDO_ODBC_TYPE "Win32" +#endif + +#ifndef PDO_ODBC_TYPE +#warning Please fix configure to give your ODBC libraries a name +#define PDO_ODBC_TYPE "Unknown" +#endif + +/* {{{ Roll a dice, pick a header at random... */ +#ifdef HAVE_SQLCLI1_H +#include +#if defined(DB268K) && HAVE_LIBRARYMANAGER_H +#include +#endif +#endif + +#ifdef HAVE_ODBC_H +#include +#endif + +#ifdef HAVE_IODBC_H +#include +#endif + +#if defined(HAVE_SQLUNIX_H) && !defined(PHP_WIN32) +#include +#endif + +#ifdef HAVE_SQLTYPES_H +#include +#endif + +#ifdef HAVE_SQLUCODE_H +#include +#endif + +#ifdef HAVE_SQL_H +#include +#endif + +#ifdef HAVE_ISQL_H +#include +#endif + +#ifdef HAVE_SQLEXT_H +#include +#endif + +#ifdef HAVE_ISQLEXT_H +#include +#endif + +#ifdef HAVE_UDBCEXT_H +#include +#endif + +#ifdef HAVE_CLI0CORE_H +#include +#endif + +#ifdef HAVE_CLI0EXT1_H +#include +#endif + +#ifdef HAVE_CLI0CLI_H +#include +#endif + +#ifdef HAVE_CLI0DEFS_H +#include +#endif + +#ifdef HAVE_CLI0ENV_H +#include +#endif + +/* }}} */ + +/* {{{ Figure out the type for handles */ +#if !defined(HENV) && !defined(SQLHENV) && defined(SQLHANDLE) +#define PDO_ODBC_HENV SQLHANDLE +#define PDO_ODBC_HDBC SQLHANDLE +#define PDO_ODBC_HSTMT SQLHANDLE +#elif !defined(HENV) && (defined(SQLHENV) || defined(DB2CLI_VER)) +#define PDO_ODBC_HENV SQLHENV +#define PDO_ODBC_HDBC SQLHDBC +#define PDO_ODBC_HSTMT SQLHSTMT +#else +#define PDO_ODBC_HENV HENV +#define PDO_ODBC_HDBC HDBC +#define PDO_ODBC_HSTMT HSTMT +#endif +/* }}} */ + +typedef struct { + char last_state[6]; + char last_err_msg[SQL_MAX_MESSAGE_LENGTH]; + SQLINTEGER last_error; + const char *file, *what; + int line; +} pdo_odbc_errinfo; + +typedef struct { + PDO_ODBC_HENV env; + PDO_ODBC_HDBC dbc; + pdo_odbc_errinfo einfo; + unsigned assume_utf8 : 1; + unsigned _spare : 31; +} pdo_odbc_db_handle; + +typedef struct { + char *data; + zend_ulong datalen; + SQLLEN fetched_len; + SQLSMALLINT coltype; + char colname[128]; + unsigned is_long; + unsigned is_unicode : 1; + unsigned _spare : 31; +} pdo_odbc_column; + +typedef struct { + PDO_ODBC_HSTMT stmt; + pdo_odbc_column *cols; + pdo_odbc_db_handle *H; + pdo_odbc_errinfo einfo; + char *convbuf; + zend_ulong convbufsize; + unsigned going_long : 1; + unsigned assume_utf8 : 1; + signed col_count : 16; + unsigned _spare : 14; +} pdo_odbc_stmt; + +typedef struct { + SQLLEN len; + SQLSMALLINT paramtype; + char *outbuf; + unsigned is_unicode : 1; + unsigned _spare : 31; +} pdo_odbc_param; + +extern const pdo_driver_t pdo_odbc_driver; +extern const struct pdo_stmt_methods odbc_stmt_methods; + +void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line); +#define pdo_odbc_drv_error(what) pdo_odbc_error(dbh, NULL, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_stmt_error(what) pdo_odbc_error(stmt->dbh, stmt, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_doer_error(what) pdo_odbc_error(dbh, NULL, stmt, what, __FILE__, __LINE__) + +void pdo_odbc_init_error_table(void); +void pdo_odbc_fini_error_table(void); + +#ifdef SQL_ATTR_CONNECTION_POOLING +extern zend_ulong pdo_odbc_pool_on; +extern zend_ulong pdo_odbc_pool_mode; +#endif + +enum { + PDO_ODBC_ATTR_USE_CURSOR_LIBRARY = PDO_ATTR_DRIVER_SPECIFIC, + PDO_ODBC_ATTR_ASSUME_UTF8 /* assume that input strings are UTF-8 when feeding data to unicode columns */ +}; diff --git a/thirdparty/php84/pdo_pgsql/pgsql_driver.c b/thirdparty/php84/pdo_pgsql/pgsql_driver.c new file mode 100644 index 00000000000..056bc330e73 --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/pgsql_driver.c @@ -0,0 +1,1459 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80400 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/php_string.h" /* For php_addcslashes_str() in _pdo_pgsql_escape_credentials() */ +#include "main/php_network.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_error.h" +#include "ext/standard/file.h" +#include "php_pdo_pgsql.h" +#include "php_pdo_pgsql_int.h" +#include "zend_exceptions.h" +#include "zend_smart_str.h" +#include "pgsql_driver_arginfo.h" + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh); + +static char *_pdo_pgsql_trim_message(const char *message, int persistent) { + size_t i = strlen(message) - 1; + char *tmp; + + if (i > 1 && (message[i - 1] == '\r' || message[i - 1] == '\n') && message[i] == '.') { + --i; + } + while (i > 0 && (message[i] == '\r' || message[i] == '\n')) { + --i; + } + ++i; + tmp = pemalloc(i + 1, persistent); + memcpy(tmp, message, i); + tmp[i] = '\0'; + + return tmp; +} + +static zend_string *_pdo_pgsql_escape_credentials(char *str) { + if (str) { + return php_addcslashes_str(str, strlen(str), "\\'", sizeof("\\'")); + } + + return NULL; +} + +int _pdo_pgsql_error(pdo_dbh_t *dbh, + pdo_stmt_t *stmt, + int errcode, + const char *sqlstate, + const char *msg, + const char *file, + int line) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code; + pdo_pgsql_error_info *einfo = &H->einfo; + char *errmsg = PQerrorMessage(H->server); + + einfo->errcode = errcode; + einfo->file = file; + einfo->line = line; + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + } + + if (sqlstate == NULL || strlen(sqlstate) >= sizeof(pdo_error_type)) { + strcpy(*pdo_err, "HY000"); + } else { + strcpy(*pdo_err, sqlstate); + } + + if (msg) { + einfo->errmsg = pestrdup(msg, dbh->is_persistent); + } else if (errmsg) { + einfo->errmsg = _pdo_pgsql_trim_message(errmsg, dbh->is_persistent); + } + + if (!dbh->methods) { + pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err); + } + + return errcode; +} +/* }}} */ + +static void _pdo_pgsql_notice(void *context, const char *message) /* {{{ */ +{ + pdo_dbh_t *dbh = (pdo_dbh_t *) context; + zend_fcall_info_cache *fc = ((pdo_pgsql_db_handle *) dbh->driver_data)->notice_callback; + if (fc) { + zval zarg; + ZVAL_STRING(&zarg, message); + zend_call_known_fcc(fc, NULL, 1, &zarg, NULL); + zval_ptr_dtor_str(&zarg); + } +} +/* }}} */ + +static void pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + pdo_pgsql_error_info *einfo = &H->einfo; + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + } else { + /* Add null to respect expected info array structure */ + add_next_index_null(info); + } + if (einfo->errmsg) { + add_next_index_string(info, einfo->errmsg); + } +} +/* }}} */ + +void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H) /* {{{ */ +{ + if (H->notice_callback) { + zend_fcc_dtor(H->notice_callback); + efree(H->notice_callback); + H->notice_callback = NULL; + } +} +/* }}} */ + +/* {{{ pdo_pgsql_create_lob_stream */ +static ssize_t pgsql_lob_write(php_stream *stream, const char *buf, size_t count) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stream->abstract; + return lo_write(self->conn, self->lfd, (char *) buf, count); +} + +static ssize_t pgsql_lob_read(php_stream *stream, char *buf, size_t count) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stream->abstract; + return lo_read(self->conn, self->lfd, buf, count); +} + +static int pgsql_lob_close(php_stream *stream, int close_handle) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stream->abstract; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) (Z_PDO_DBH_P(&self->dbh))->driver_data; + + if (close_handle) { + lo_close(self->conn, self->lfd); + } + zend_hash_index_del(H->lob_streams, php_stream_get_resource_id(stream)); + zval_ptr_dtor(&self->dbh); + efree(self); + return 0; +} + +static int pgsql_lob_flush(php_stream *stream) { + return 0; +} + +static int pgsql_lob_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stream->abstract; +#ifdef ZEND_ENABLE_ZVAL_LONG64 + zend_off_t pos = lo_lseek64(self->conn, self->lfd, offset, whence); +#else + zend_off_t pos = lo_lseek(self->conn, self->lfd, offset, whence); +#endif + *newoffset = pos; + return pos >= 0 ? 0 : -1; +} + +const php_stream_ops pdo_pgsql_lob_stream_ops = {pgsql_lob_write, + pgsql_lob_read, + pgsql_lob_close, + pgsql_lob_flush, + "pdo_pgsql lob stream", + pgsql_lob_seek, + NULL, + NULL, + NULL}; + +php_stream *pdo_pgsql_create_lob_stream(zval *dbh, int lfd, Oid oid) { + php_stream *stm; + struct pdo_pgsql_lob_self *self = ecalloc(1, sizeof(*self)); + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) (Z_PDO_DBH_P(dbh))->driver_data; + + ZVAL_COPY_VALUE(&self->dbh, dbh); + self->lfd = lfd; + self->oid = oid; + self->conn = H->server; + + stm = php_stream_alloc(&pdo_pgsql_lob_stream_ops, self, 0, "r+b"); + + if (stm) { + Z_ADDREF_P(dbh); + zend_hash_index_add_ptr(H->lob_streams, php_stream_get_resource_id(stm), stm->res); + return stm; + } + + efree(self); + return NULL; +} +/* }}} */ + +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh) { + zend_resource *res; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + if (H->lob_streams) { + ZEND_HASH_REVERSE_FOREACH_PTR(H->lob_streams, res) { + if (res->type >= 0) { + zend_list_close(res); + } + } + ZEND_HASH_FOREACH_END(); + } +} + +static void pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + if (H) { + if (H->lob_streams) { + pdo_pgsql_close_lob_streams(dbh); + zend_hash_destroy(H->lob_streams); + pefree(H->lob_streams, dbh->is_persistent); + H->lob_streams = NULL; + } + pdo_pgsql_cleanup_notice_callback(H); + if (H->server) { + PQfinish(H->server); + H->server = NULL; + } + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + } +} +/* }}} */ + +static bool pgsql_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + pdo_pgsql_stmt *S = ecalloc(1, sizeof(pdo_pgsql_stmt)); + int scrollable; + int ret; + zend_string *nsql = NULL; + int emulate = 0; + int execute_only = 0; + + S->H = H; + stmt->driver_data = S; + stmt->methods = &swoole_pgsql_stmt_methods; + + scrollable = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL; + + if (scrollable) { + if (S->cursor_name) { + efree(S->cursor_name); + } + spprintf(&S->cursor_name, 0, "pdo_crsr_%08x", ++H->stmt_counter); + emulate = 1; + } else if (driver_options) { + if (pdo_attr_lval(driver_options, PDO_ATTR_EMULATE_PREPARES, H->emulate_prepares) == 1) { + emulate = 1; + } + if (pdo_attr_lval(driver_options, PDO_PGSQL_ATTR_DISABLE_PREPARES, H->disable_prepares) == 1) { + execute_only = 1; + } + } else { + emulate = H->disable_native_prepares || H->emulate_prepares; + execute_only = H->disable_prepares; + } + + if (emulate) { + stmt->supports_placeholders = PDO_PLACEHOLDER_NONE; + } else { + stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; + stmt->named_rewrite_template = "$%d"; + } + + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + return false; + } else if (ret == 1) { + /* query was re-written */ + S->query = nsql; + } else { + S->query = zend_string_copy(sql); + } + + if (!emulate && !execute_only) { + /* prepared query: set the query name and defer the + actual prepare until the first execute call */ + spprintf(&S->stmt_name, 0, "pdo_stmt_%08x", ++H->stmt_counter); + } + + return true; +} + +static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + PGresult *res; + zend_long ret = 1; + ExecStatusType qs; + + bool in_trans = pgsql_handle_in_transaction(dbh); + + if (!(res = PQexec(H->server, ZSTR_VAL(sql)))) { + /* fatal error */ + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + return -1; + } + qs = PQresultStatus(res); + if (qs != PGRES_COMMAND_OK && qs != PGRES_TUPLES_OK) { + pdo_pgsql_error(dbh, qs, pdo_pgsql_sqlstate(res)); + PQclear(res); + return -1; + } + H->pgoid = PQoidValue(res); + if (qs == PGRES_COMMAND_OK) { + ret = ZEND_ATOL(PQcmdTuples(res)); + } else { + ret = Z_L(0); + } + PQclear(res); + if (in_trans && !pgsql_handle_in_transaction(dbh)) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +static zend_string *pgsql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) { + unsigned char *escaped; + char *quoted; + size_t quotedlen; + zend_string *quoted_str; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + size_t tmp_len; + + switch (paramtype) { + case PDO_PARAM_LOB: + /* escapedlen returned by PQescapeBytea() accounts for trailing 0 */ + escaped = PQescapeByteaConn(H->server, (unsigned char *) ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &tmp_len); + quotedlen = tmp_len + 1; + quoted = emalloc(quotedlen + 1); + memcpy(quoted + 1, escaped, quotedlen - 2); + quoted[0] = '\''; + quoted[quotedlen - 1] = '\''; + quoted[quotedlen] = '\0'; + PQfreemem(escaped); + break; + default: + quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); + quoted[0] = '\''; + quotedlen = PQescapeStringConn(H->server, quoted + 1, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), NULL); + quoted[quotedlen + 1] = '\''; + quoted[quotedlen + 2] = '\0'; + quotedlen += 2; + } + + quoted_str = zend_string_init(quoted, quotedlen, 0); + efree(quoted); + return quoted_str; +} + +static zend_string *pdo_pgsql_last_insert_id(pdo_dbh_t *dbh, const zend_string *name) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + zend_string *id = NULL; + PGresult *res; + ExecStatusType status; + + if (name == NULL) { + res = PQexec(H->server, "SELECT LASTVAL()"); + } else { + const char *q[1]; + q[0] = ZSTR_VAL(name); + + res = PQexecParams(H->server, "SELECT CURRVAL($1)", 1, NULL, q, NULL, NULL, 0); + } + status = PQresultStatus(res); + + if (res && (status == PGRES_TUPLES_OK)) { + id = zend_string_init((char *) PQgetvalue(res, 0, 0), PQgetlength(res, 0, 0), 0); + } else { + pdo_pgsql_error(dbh, status, pdo_pgsql_sqlstate(res)); + } + + if (res) { + PQclear(res); + } + + return id; +} + +void pdo_libpq_version(char *buf, size_t len) { + int version = PQlibVersion(); + int major = version / 10000; + if (major >= 10) { + int minor = version % 10000; + snprintf(buf, len, "%d.%d", major, minor); + } else { + int minor = version / 100 % 100; + int revision = version % 100; + snprintf(buf, len, "%d.%d.%d", major, minor, revision); + } +} + +static int pdo_pgsql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + ZVAL_BOOL(return_value, H->emulate_prepares); + break; + + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + ZVAL_BOOL(return_value, H->disable_prepares); + break; + + case PDO_ATTR_CLIENT_VERSION: { + char buf[16]; + pdo_libpq_version(buf, sizeof(buf)); + ZVAL_STRING(return_value, buf); + break; + } + + case PDO_ATTR_SERVER_VERSION: + ZVAL_STRING(return_value, (char *) PQparameterStatus(H->server, "server_version")); + break; + + case PDO_ATTR_CONNECTION_STATUS: + switch (PQstatus(H->server)) { + case CONNECTION_STARTED: + ZVAL_STRINGL( + return_value, "Waiting for connection to be made.", strlen("Waiting for connection to be made.")); + break; + + case CONNECTION_MADE: + case CONNECTION_OK: + ZVAL_STRINGL(return_value, "Connection OK; waiting to send.", strlen("Connection OK; waiting to send.")); + break; + + case CONNECTION_AWAITING_RESPONSE: + ZVAL_STRINGL(return_value, + "Waiting for a response from the server.", + strlen("Waiting for a response from the server.")); + break; + + case CONNECTION_AUTH_OK: + ZVAL_STRINGL(return_value, + "Received authentication; waiting for backend start-up to finish.", + strlen("Received authentication; waiting for backend start-up to finish.")); + break; +#ifdef CONNECTION_SSL_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating SSL encryption.", strlen("Negotiating SSL encryption.")); + break; +#endif + case CONNECTION_SETENV: + ZVAL_STRINGL(return_value, + "Negotiating environment-driven parameter settings.", + strlen("Negotiating environment-driven parameter settings.")); + break; + +#ifdef CONNECTION_CONSUME + case CONNECTION_CONSUME: + ZVAL_STRINGL(return_value, + "Flushing send queue/consuming extra data.", + strlen("Flushing send queue/consuming extra data.")); + break; +#endif +#ifdef CONNECTION_GSS_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating GSSAPI.", strlen("Negotiating GSSAPI.")); + break; +#endif +#ifdef CONNECTION_CHECK_TARGET + case CONNECTION_CHECK_TARGET: + ZVAL_STRINGL(return_value, + "Connection OK; checking target server properties.", + strlen("Connection OK; checking target server properties.")); + break; +#endif +#ifdef CONNECTION_CHECK_STANDBY + case CONNECTION_CHECK_STANDBY: + ZVAL_STRINGL(return_value, + "Connection OK; checking if server in standby.", + strlen("Connection OK; checking if server in standby.")); + break; +#endif + case CONNECTION_BAD: + default: + ZVAL_STRINGL(return_value, "Bad connection.", strlen("Bad connection.")); + break; + } + break; + + case PDO_ATTR_SERVER_INFO: { + int spid = PQbackendPID(H->server); + + zend_string *str_info = + strpprintf(0, + "PID: %d; Client Encoding: %s; Is Superuser: %s; Session Authorization: %s; Date Style: %s", + spid, + (char *) PQparameterStatus(H->server, "client_encoding"), + (char *) PQparameterStatus(H->server, "is_superuser"), + (char *) PQparameterStatus(H->server, "session_authorization"), + (char *) PQparameterStatus(H->server, "DateStyle")); + + ZVAL_STR(return_value, str_info); + break; + } + + default: + return 0; + } + + return 1; +} + +/* {{{ */ +static zend_result pdo_pgsql_check_liveness(pdo_dbh_t *dbh) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + if (!PQconsumeInput(H->server) || PQstatus(H->server) == CONNECTION_BAD) { + PQreset(H->server); + } + return (PQstatus(H->server) == CONNECTION_OK) ? SUCCESS : FAILURE; +} +/* }}} */ + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + return PQtransactionStatus(H->server) > PQTRANS_IDLE; +} + +static bool pdo_pgsql_transaction_cmd(const char *cmd, pdo_dbh_t *dbh) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + PGresult *res; + bool ret = true; + + res = PQexec(H->server, cmd); + + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + pdo_pgsql_error(dbh, PQresultStatus(res), pdo_pgsql_sqlstate(res)); + ret = false; + } + + PQclear(res); + return ret; +} + +static bool pgsql_handle_begin(pdo_dbh_t *dbh) { + return pdo_pgsql_transaction_cmd("BEGIN", dbh); +} + +static bool pgsql_handle_commit(pdo_dbh_t *dbh) { + bool ret = pdo_pgsql_transaction_cmd("COMMIT", dbh); + + /* When deferred constraints are used the commit could + fail, and a ROLLBACK implicitly ran. See bug #67462 */ + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } else { + dbh->in_txn = pgsql_handle_in_transaction(dbh); + } + + return ret; +} + +static bool pgsql_handle_rollback(pdo_dbh_t *dbh) { + int ret = pdo_pgsql_transaction_cmd("ROLLBACK", dbh); + + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + zval *pg_rows; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "sa|sss!", + &table_name, + &table_name_len, + &pg_rows, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + if (!zend_hash_num_elements(Z_ARRVAL_P(pg_rows))) { + zend_argument_value_error(2, "cannot be empty"); + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + /* Obtain db Handle */ + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + query = NULL; + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + int command_failed = 0; + size_t buffer_len = 0; + zval *tmp; + + PQclear(pgsql_result); + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), tmp) { + size_t query_len; + if (!try_convert_to_string(tmp)) { + efree(query); + RETURN_THROWS(); + } + + if (buffer_len < Z_STRLEN_P(tmp)) { + buffer_len = Z_STRLEN_P(tmp); + query = erealloc(query, buffer_len + 2); /* room for \n\0 */ + } + query_len = Z_STRLEN_P(tmp); + memcpy(query, Z_STRVAL_P(tmp), query_len); + if (query[query_len - 1] != '\n') { + query[query_len++] = '\n'; + } + query[query_len] = '\0'; + if (PQputCopyData(H->server, query, query_len) != 1) { + efree(query); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + ZEND_HASH_FOREACH_END(); + if (query) { + efree(query); + } + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray) { + pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *filename, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, filename_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + PGresult *pgsql_result; + ExecStatusType status; + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "sp|sss!", + &table_name, + &table_name_len, + &filename, + &filename_len, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + /* Obtain db Handler */ + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + stream = php_stream_open_wrapper_ex(filename, "rb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + char *buf; + int command_failed = 0; + size_t line_len = 0; + + PQclear(pgsql_result); + while ((buf = php_stream_get_line(stream, NULL, 0, &line_len)) != NULL) { + if (PQputCopyData(H->server, buf, line_len) != 1) { + efree(buf); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + efree(buf); + } + php_stream_close(stream); + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile) { + pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL, *filename = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len, filename_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "sp|sss!", + &table_name, + &table_name_len, + &filename, + &filename_len, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + stream = php_stream_open_wrapper_ex(filename, "wb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file for writing"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + + if (ret == -1) { + break; /* done */ + } else if (ret > 0) { + if (php_stream_write(stream, csv, ret) != (size_t) ret) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to write to file"); + PQfreemem(csv); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } else { + PQfreemem(csv); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + php_stream_close(stream); + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + RETURN_TRUE; + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile) { + pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "s|sss!", + &table_name, + &table_name_len, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + array_init(return_value); + + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + if (ret == -1) { + break; /* copy done */ + } else if (ret > 0) { + add_next_index_stringl(return_value, csv, ret); + PQfreemem(csv); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray) { + pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid lfd; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + lfd = lo_creat(H->server, INV_READ | INV_WRITE); + + if (lfd != InvalidOid) { + zend_string *buf = strpprintf(0, ZEND_ULONG_FMT, (zend_long) lfd); + + RETURN_STR(buf); + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Creates a new large object, returning its identifier. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate) { + pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + int lfd; + char *oidstr; + size_t oidstrlen; + char *modestr = "rb"; + size_t modestrlen; + int mode = INV_READ; + char *end_ptr; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &oidstr, &oidstrlen, &modestr, &modestrlen)) { + RETURN_THROWS(); + } + + oid = (Oid) strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + if (strpbrk(modestr, "+w")) { + mode = INV_READ | INV_WRITE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + lfd = lo_open(H->server, oid, mode); + + if (lfd >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(ZEND_THIS, lfd, oid); + if (stream) { + php_stream_to_zval(stream, return_value); + return; + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Opens an existing large object stream. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen) { + pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + char *oidstr, *end_ptr; + size_t oidlen; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s", &oidstr, &oidlen)) { + RETURN_THROWS(); + } + + oid = (Oid) strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + if (1 == lo_unlink(H->server, oid)) { + RETURN_TRUE; + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Deletes the large object identified by oid. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink) { + pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + zend_long result_type = PDO_FETCH_USE_DEFAULT; + zend_long ms_timeout = 0; + PGnotify *pgsql_notify; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|ll", &result_type, &ms_timeout)) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + if (result_type == PDO_FETCH_USE_DEFAULT) { + result_type = dbh->default_fetch_type; + } + + if (result_type != PDO_FETCH_BOTH && result_type != PDO_FETCH_ASSOC && result_type != PDO_FETCH_NUM) { + zend_argument_value_error(1, "must be one of PDO::FETCH_BOTH, PDO::FETCH_ASSOC, or PDO::FETCH_NUM"); + RETURN_THROWS(); + } + + if (ms_timeout < 0) { + zend_argument_value_error(2, "must be greater than or equal to 0"); + RETURN_THROWS(); +#ifdef ZEND_ENABLE_ZVAL_LONG64 + } else if (ms_timeout > INT_MAX) { + php_error_docref(NULL, E_WARNING, "Timeout was shrunk to %d", INT_MAX); + ms_timeout = INT_MAX; +#endif + } + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + + if (ms_timeout && !pgsql_notify) { + php_pollfd_for_ms(PQsocket(H->server), PHP_POLLREADABLE, (int) ms_timeout); + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + } + + if (!pgsql_notify) { + RETURN_FALSE; + } + + array_init(return_value); + if (result_type == PDO_FETCH_NUM || result_type == PDO_FETCH_BOTH) { + add_index_string(return_value, 0, pgsql_notify->relname); + add_index_long(return_value, 1, pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_index_string(return_value, 2, pgsql_notify->extra); + } + } + if (result_type == PDO_FETCH_ASSOC || result_type == PDO_FETCH_BOTH) { + add_assoc_string(return_value, "message", pgsql_notify->relname); + add_assoc_long(return_value, "pid", pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_assoc_string(return_value, "payload", pgsql_notify->extra); + } + } + + PQfreemem(pgsql_notify); +} + +/* {{{ Get asynchronous notification */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetNotify) { + pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + RETURN_LONG(PQbackendPID(H->server)); +} + +/* {{{ Get backend(server) pid */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid) { + pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ Sets a callback to receive DB notices (after client_min_messages has been set) */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlSetNoticeCallback) { + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "F!", &fci, &fcc)) { + RETURN_THROWS(); + } + + pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK_WITH_CLEANUP(cleanup); + + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + pdo_pgsql_cleanup_notice_callback(H); + + if (ZEND_FCC_INITIALIZED(fcc)) { + H->notice_callback = emalloc(sizeof(zend_fcall_info_cache)); + zend_fcc_dup(H->notice_callback, &fcc); + } + + return; + +cleanup: + if (ZEND_FCC_INITIALIZED(fcc)) { + zend_fcc_dtor(&fcc); + } + RETURN_THROWS(); +} +/* }}} */ + +static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind) { + switch (kind) { + case PDO_DBH_DRIVER_METHOD_KIND_DBH: + return class_PDO_PGSql_Ext_methods; + default: + return NULL; + } +} + +static bool pdo_pgsql_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + bool bval; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->emulate_prepares = bval; + return true; + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->disable_prepares = bval; + return true; + default: + return false; + } +} + +static const struct pdo_dbh_methods pgsql_methods = {pgsql_handle_closer, + pgsql_handle_preparer, + pgsql_handle_doer, + pgsql_handle_quoter, + pgsql_handle_begin, + pgsql_handle_commit, + pgsql_handle_rollback, + pdo_pgsql_set_attr, + pdo_pgsql_last_insert_id, + pdo_pgsql_fetch_error_func, + pdo_pgsql_get_attribute, + pdo_pgsql_check_liveness, /* check_liveness */ + pdo_pgsql_get_driver_methods, /* get_driver_methods */ + NULL, + pgsql_handle_in_transaction, + NULL, /* get_gc */ + pdo_pgsql_scanner}; + +static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_pgsql_db_handle *H; + int ret = 0; + char *p, *e; + zend_string *tmp_user, *tmp_pass; + smart_str conn_str = {0}; + zend_long connect_timeout = 30; + + H = pecalloc(1, sizeof(pdo_pgsql_db_handle), dbh->is_persistent); + dbh->driver_data = H; + + dbh->skip_param_evt = 1 << PDO_PARAM_EVT_EXEC_POST | 1 << PDO_PARAM_EVT_FETCH_PRE | 1 << PDO_PARAM_EVT_FETCH_POST; + + H->einfo.errcode = 0; + H->einfo.errmsg = NULL; + + /* PostgreSQL wants params in the connect string to be separated by spaces, + * if the PDO standard semicolons are used, we convert them to spaces + */ + e = (char *) dbh->data_source + strlen(dbh->data_source); + p = (char *) dbh->data_source; + while ((p = memchr(p, ';', (e - p)))) { + *p = ' '; + } + + if (driver_options) { + connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30); + } + + /* escape username and password, if provided */ + tmp_user = !strstr((char *) dbh->data_source, "user=") ? _pdo_pgsql_escape_credentials(dbh->username) : NULL; + tmp_pass = !strstr((char *) dbh->data_source, "password=") ? _pdo_pgsql_escape_credentials(dbh->password) : NULL; + + smart_str_appends(&conn_str, dbh->data_source); + smart_str_append_printf(&conn_str, " connect_timeout=" ZEND_LONG_FMT, connect_timeout); + + /* support both full connection string & connection string + login and/or password */ + if (tmp_user) { + smart_str_append_printf(&conn_str, " user='%s'", ZSTR_VAL(tmp_user)); + } + + if (tmp_pass) { + smart_str_append_printf(&conn_str, " password='%s'", ZSTR_VAL(tmp_pass)); + } + smart_str_0(&conn_str); + + H->server = PQconnectdb(ZSTR_VAL(conn_str.s)); + H->lob_streams = (HashTable *) pemalloc(sizeof(HashTable), dbh->is_persistent); + zend_hash_init(H->lob_streams, 0, NULL, NULL, 1); + + if (tmp_user) { + zend_string_release_ex(tmp_user, 0); + } + if (tmp_pass) { + zend_string_release_ex(tmp_pass, 0); + } + + smart_str_free(&conn_str); + + if (PQstatus(H->server) != CONNECTION_OK) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE); + goto cleanup; + } + + PQsetNoticeProcessor(H->server, _pdo_pgsql_notice, (void *) dbh); + + H->attached = 1; + H->pgoid = -1; + + dbh->methods = &pgsql_methods; + dbh->alloc_own_columns = 1; + dbh->max_escaped_char_length = 2; + + ret = 1; + +cleanup: + dbh->methods = &pgsql_methods; + if (!ret) { + pgsql_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_pgsql_driver = {PDO_DRIVER_HEADER(pgsql), pdo_pgsql_handle_factory}; +#endif diff --git a/thirdparty/php84/pdo_pgsql/pgsql_driver_arginfo.h b/thirdparty/php84/pdo_pgsql/pgsql_driver_arginfo.h new file mode 100644 index 00000000000..5bdea01edc2 --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/pgsql_driver_arginfo.h @@ -0,0 +1,70 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: dd20abc5d8580d72b25bfb3c598b1ca54a501fcc */ + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, rows, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 0, "\"rb\"") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fetchMode, IS_LONG, 0, "PDO::FETCH_DEFAULT") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeoutMilliseconds, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetNotify); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetPid); + +static const zend_function_entry class_PDO_PGSql_Ext_methods[] = { + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBCreate, arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBOpen, arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBUnlink, arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetNotify, arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetPid, arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; diff --git a/thirdparty/php84/pdo_pgsql/pgsql_statement.c b/thirdparty/php84/pdo_pgsql/pgsql_statement.c new file mode 100644 index 00000000000..dc9cc165326 --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/pgsql_statement.c @@ -0,0 +1,741 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80400 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_pgsql.h" +#include "php_pdo_pgsql_int.h" +#ifdef HAVE_NETINET_IN_H +#include +#endif + +/* from postgresql/src/include/catalog/pg_type.h */ +#define BOOLLABEL "bool" +#define BOOLOID 16 +#define BYTEALABEL "bytea" +#define BYTEAOID 17 +#define DATELABEL "date" +#define DATEOID 1082 +#define INT2LABEL "int2" +#define INT2OID 21 +#define INT4LABEL "int4" +#define INT4OID 23 +#define INT8LABEL "int8" +#define INT8OID 20 +#define OIDOID 26 +#define TEXTLABEL "text" +#define TEXTOID 25 +#define TIMESTAMPLABEL "timestamp" +#define TIMESTAMPOID 1114 +#define VARCHARLABEL "varchar" +#define VARCHAROID 1043 +#define FLOAT4LABEL "float4" +#define FLOAT4OID 700 +#define FLOAT8LABEL "float8" +#define FLOAT8OID 701 + +static int pgsql_stmt_dtor(pdo_stmt_t *stmt) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + bool server_obj_usable = + !Z_ISUNDEF(stmt->database_object_handle) && + IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)]) && + !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED); + + if (S->result) { + /* free the resource */ + PQclear(S->result); + S->result = NULL; + } + + if (S->stmt_name) { + if (S->is_prepared && server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + spprintf(&q, 0, "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, q); + efree(q); + if (res) { + PQclear(res); + } + } + efree(S->stmt_name); + S->stmt_name = NULL; + } + if (S->param_lengths) { + efree(S->param_lengths); + S->param_lengths = NULL; + } + if (S->param_values) { + efree(S->param_values); + S->param_values = NULL; + } + if (S->param_formats) { + efree(S->param_formats); + S->param_formats = NULL; + } + if (S->param_types) { + efree(S->param_types); + S->param_types = NULL; + } + if (S->query) { + zend_string_release(S->query); + S->query = NULL; + } + + if (S->cursor_name) { + if (server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + res = PQexec(H->server, q); + efree(q); + if (res) PQclear(res); + } + efree(S->cursor_name); + S->cursor_name = NULL; + } + + if (S->cols) { + efree(S->cols); + S->cols = NULL; + } + efree(S); + stmt->driver_data = NULL; + return 1; +} + +static int pgsql_stmt_execute(pdo_stmt_t *stmt) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + pdo_pgsql_db_handle *H = S->H; + ExecStatusType status; + + bool in_trans = stmt->dbh->methods->in_transaction(stmt->dbh); + + /* ensure that we free any previous unfetched results */ + if (S->result) { + PQclear(S->result); + S->result = NULL; + } + + S->current_row = 0; + + if (S->cursor_name) { + char *q = NULL; + + if (S->is_prepared) { + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + PQclear(PQexec(H->server, q)); + efree(q); + } + + spprintf( + &q, 0, "DECLARE %s SCROLL CURSOR WITH HOLD FOR %s", S->cursor_name, ZSTR_VAL(stmt->active_query_string)); + S->result = PQexec(H->server, q); + efree(q); + + /* check if declare failed */ + status = PQresultStatus(S->result); + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + PQclear(S->result); + + /* the cursor was declared correctly */ + S->is_prepared = 1; + + /* fetch to be able to get the number of tuples later, but don't advance the cursor pointer */ + spprintf(&q, 0, "FETCH FORWARD 0 FROM %s", S->cursor_name); + S->result = PQexec(H->server, q); + efree(q); + } else if (S->stmt_name) { + /* using a prepared statement */ + + if (!S->is_prepared) { + stmt_retry: + /* we deferred the prepare until now, because we didn't + * know anything about the parameter types; now we do */ + S->result = PQprepare(H->server, + S->stmt_name, + ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types); + status = PQresultStatus(S->result); + switch (status) { + case PGRES_COMMAND_OK: + case PGRES_TUPLES_OK: + /* it worked */ + S->is_prepared = 1; + PQclear(S->result); + break; + default: { + char *sqlstate = pdo_pgsql_sqlstate(S->result); + /* 42P05 means that the prepared statement already existed. this can happen if you use + * a connection pooling software line pgpool which doesn't close the db-connection once + * php disconnects. if php dies (no chance to run RSHUTDOWN) during execution it has no + * chance to DEALLOCATE the prepared statements it has created. so, if we hit a 42P05 we + * deallocate it and retry ONCE (thies 2005.12.15) + */ + if (sqlstate && !strcmp(sqlstate, "42P05")) { + char buf[100]; /* stmt_name == "pdo_crsr_%08x" */ + PGresult *res; + snprintf(buf, sizeof(buf), "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, buf); + if (res) { + PQclear(res); + } + goto stmt_retry; + } else { + pdo_pgsql_error_stmt(stmt, status, sqlstate); + return 0; + } + } + } + } + S->result = PQexecPrepared(H->server, + S->stmt_name, + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + (const char **) S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED) { + /* execute query with parameters */ + S->result = PQexecParams(H->server, + ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types, + (const char **) S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else { + /* execute plain query (with embedded parameters) */ + S->result = PQexec(H->server, ZSTR_VAL(stmt->active_query_string)); + } + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + stmt->column_count = (int) PQnfields(S->result); + if (S->cols == NULL) { + S->cols = ecalloc(stmt->column_count, sizeof(pdo_pgsql_column)); + } + + if (status == PGRES_COMMAND_OK) { + stmt->row_count = ZEND_ATOL(PQcmdTuples(S->result)); + H->pgoid = PQoidValue(S->result); + } else { + stmt->row_count = (zend_long) PQntuples(S->result); + } + + if (in_trans && !stmt->dbh->methods->in_transaction(stmt->dbh)) { + pdo_pgsql_close_lob_streams(stmt->dbh); + } + + return 1; +} + +static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, + enum pdo_param_event event_type) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + + if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED && param->is_param) { + switch (event_type) { + case PDO_PARAM_EVT_FREE: + if (param->driver_data) { + efree(param->driver_data); + } + break; + + case PDO_PARAM_EVT_NORMALIZE: + /* decode name from $1, $2 into 0, 1 etc. */ + if (param->name) { + if (ZSTR_VAL(param->name)[0] == '$') { + param->paramno = ZEND_ATOL(ZSTR_VAL(param->name) + 1); + } else { + /* resolve parameter name to rewritten name */ + zend_string *namevar; + + if (stmt->bound_param_map && + (namevar = zend_hash_find_ptr(stmt->bound_param_map, param->name)) != NULL) { + param->paramno = ZEND_ATOL(ZSTR_VAL(namevar) + 1); + param->paramno--; + } else { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", ZSTR_VAL(param->name)); + return 0; + } + } + } + break; + + case PDO_PARAM_EVT_ALLOC: + if (!stmt->bound_param_map) { + return 1; + } + if (!zend_hash_index_exists(stmt->bound_param_map, param->paramno)) { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", "parameter was not defined"); + return 0; + } + ZEND_FALLTHROUGH; + case PDO_PARAM_EVT_EXEC_POST: + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + /* work is handled by EVT_NORMALIZE */ + return 1; + + case PDO_PARAM_EVT_EXEC_PRE: + if (!stmt->bound_param_map) { + return 1; + } + if (!S->param_values) { + S->param_values = ecalloc(zend_hash_num_elements(stmt->bound_param_map), sizeof(char *)); + S->param_lengths = ecalloc(zend_hash_num_elements(stmt->bound_param_map), sizeof(int)); + S->param_formats = ecalloc(zend_hash_num_elements(stmt->bound_param_map), sizeof(int)); + S->param_types = ecalloc(zend_hash_num_elements(stmt->bound_param_map), sizeof(Oid)); + } + if (param->paramno >= 0) { + zval *parameter; + + /* + if (param->paramno >= zend_hash_num_elements(stmt->bound_params)) { + pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined"); + return 0; + } + */ + + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + if (php_stream_is(stm, &pdo_pgsql_lob_stream_ops)) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stm->abstract; + pdo_pgsql_bound_param *P = param->driver_data; + + if (P == NULL) { + P = ecalloc(1, sizeof(*P)); + param->driver_data = P; + } + P->oid = htonl(self->oid); + S->param_values[param->paramno] = (char *) &P->oid; + S->param_lengths[param->paramno] = sizeof(P->oid); + S->param_formats[param->paramno] = 1; + S->param_types[param->paramno] = OIDOID; + return 1; + } else { + zend_string *str = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + if (str != NULL) { + ZVAL_STR(parameter, str); + } else { + ZVAL_EMPTY_STRING(parameter); + } + } + } else { + /* expected a stream resource */ + pdo_pgsql_error_stmt(stmt, PGRES_FATAL_ERROR, "HY105"); + return 0; + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL || Z_TYPE_P(parameter) == IS_NULL) { + S->param_values[param->paramno] = NULL; + S->param_lengths[param->paramno] = 0; + } else if (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE) { + S->param_values[param->paramno] = Z_TYPE_P(parameter) == IS_TRUE ? "t" : "f"; + S->param_lengths[param->paramno] = 1; + S->param_formats[param->paramno] = 0; + } else { + convert_to_string(parameter); + S->param_values[param->paramno] = Z_STRVAL_P(parameter); + S->param_lengths[param->paramno] = Z_STRLEN_P(parameter); + S->param_formats[param->paramno] = 0; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + S->param_types[param->paramno] = 0; + S->param_formats[param->paramno] = 1; + } else { + S->param_types[param->paramno] = 0; + } + } + break; + } + } else if (param->is_param && event_type == PDO_PARAM_EVT_NORMALIZE) { + /* We need to manually convert to a pg native boolean value */ + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL && + ((param->param_type & PDO_PARAM_INPUT_OUTPUT) != PDO_PARAM_INPUT_OUTPUT)) { + const char *s = zend_is_true(¶m->parameter) ? "t" : "f"; + param->param_type = PDO_PARAM_STR; + zval_ptr_dtor(¶m->parameter); + ZVAL_STRINGL(¶m->parameter, s, 1); + } + } + return 1; +} + +static int pgsql_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + + if (S->cursor_name) { + char *ori_str = NULL; + char *q = NULL; + ExecStatusType status; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: + spprintf(&ori_str, 0, "NEXT"); + break; + case PDO_FETCH_ORI_PRIOR: + spprintf(&ori_str, 0, "BACKWARD"); + break; + case PDO_FETCH_ORI_FIRST: + spprintf(&ori_str, 0, "FIRST"); + break; + case PDO_FETCH_ORI_LAST: + spprintf(&ori_str, 0, "LAST"); + break; + case PDO_FETCH_ORI_ABS: + spprintf(&ori_str, 0, "ABSOLUTE " ZEND_LONG_FMT, offset); + break; + case PDO_FETCH_ORI_REL: + spprintf(&ori_str, 0, "RELATIVE " ZEND_LONG_FMT, offset); + break; + default: + return 0; + } + + if (S->result) { + PQclear(S->result); + S->result = NULL; + } + + spprintf(&q, 0, "FETCH %s FROM %s", ori_str, S->cursor_name); + efree(ori_str); + S->result = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + if (PQntuples(S->result)) { + S->current_row = 1; + return 1; + } else { + return 0; + } + } else { + if (S->current_row < stmt->row_count) { + S->current_row++; + return 1; + } else { + return 0; + } + } +} + +static int pgsql_stmt_describe(pdo_stmt_t *stmt, int colno) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + struct pdo_column_data *cols = stmt->columns; + char *str; + + if (!S->result) { + return 0; + } + + str = PQfname(S->result, colno); + cols[colno].name = zend_string_init(str, strlen(str), 0); + cols[colno].maxlen = PQfsize(S->result, colno); + cols[colno].precision = PQfmod(S->result, colno); + S->cols[colno].pgsql_type = PQftype(S->result, colno); + + return 1; +} + +static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + if (!S->result) { + return 0; + } + + /* We have already increased count by 1 in pgsql_stmt_fetch() */ + if (PQgetisnull(S->result, S->current_row - 1, colno)) { /* Check if we got NULL */ + ZVAL_NULL(result); + } else { + char *ptr = PQgetvalue(S->result, S->current_row - 1, colno); + size_t len = PQgetlength(S->result, S->current_row - 1, colno); + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + ZVAL_BOOL(result, *ptr == 't'); + break; + + case INT2OID: + case INT4OID: +#if SIZEOF_ZEND_LONG >= 8 + case INT8OID: +#endif + ZVAL_LONG(result, ZEND_ATOL(ptr)); + break; + case FLOAT4OID: + case FLOAT8OID: + if (strncmp(ptr, "Infinity", len) == 0) { + ZVAL_DOUBLE(result, ZEND_INFINITY); + } else if (strncmp(ptr, "-Infinity", len) == 0) { + ZVAL_DOUBLE(result, -ZEND_INFINITY); + } else if (strncmp(ptr, "NaN", len) == 0) { + ZVAL_DOUBLE(result, ZEND_NAN); + } else { + ZVAL_DOUBLE(result, zend_strtod(ptr, NULL)); + } + break; + + case OIDOID: { + char *end_ptr; + Oid oid = (Oid) strtoul(ptr, &end_ptr, 10); + if (type && *type == PDO_PARAM_LOB) { + /* If column was bound as LOB, return a stream. */ + int loid = lo_open(S->H->server, oid, INV_READ); + if (loid >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(&stmt->database_object_handle, loid, oid); + if (stream) { + php_stream_to_zval(stream, result); + return 1; + } + } + return 0; + } else { + /* Otherwise return OID as integer. */ + ZVAL_LONG(result, oid); + } + break; + } + + case BYTEAOID: { + size_t tmp_len; + char *tmp_ptr = (char *) PQunescapeBytea((unsigned char *) ptr, &tmp_len); + if (!tmp_ptr) { + /* PQunescapeBytea returned an error */ + return 0; + } + + zend_string *str = zend_string_init(tmp_ptr, tmp_len, 0); + php_stream *stream = php_stream_memory_open(TEMP_STREAM_READONLY, str); + php_stream_to_zval(stream, result); + zend_string_release(str); + PQfreemem(tmp_ptr); + break; + } + + default: + ZVAL_STRINGL_FAST(result, ptr, len); + break; + } + } + + return 1; +} + +static zend_always_inline char *pdo_pgsql_translate_oid_to_table(Oid oid, PGconn *conn) { + char *table_name = NULL; + PGresult *tmp_res; + char *querystr = NULL; + + spprintf(&querystr, 0, "SELECT RELNAME FROM PG_CLASS WHERE OID=%d", oid); + + if ((tmp_res = PQexec(conn, querystr)) == NULL || PQresultStatus(tmp_res) != PGRES_TUPLES_OK) { + if (tmp_res) { + PQclear(tmp_res); + } + efree(querystr); + return 0; + } + efree(querystr); + + if (1 == PQgetisnull(tmp_res, 0, 0) || (table_name = PQgetvalue(tmp_res, 0, 0)) == NULL) { + PQclear(tmp_res); + return 0; + } + + table_name = estrdup(table_name); + + PQclear(tmp_res); + return table_name; +} + +static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + PGresult *res; + char *q = NULL; + ExecStatusType status; + Oid table_oid; + char *table_name = NULL; + + if (!S->result) { + return FAILURE; + } + + if (colno >= stmt->column_count) { + return FAILURE; + } + + array_init(return_value); + add_assoc_long(return_value, "pgsql:oid", S->cols[colno].pgsql_type); + + table_oid = PQftable(S->result, colno); + add_assoc_long(return_value, "pgsql:table_oid", table_oid); + table_name = pdo_pgsql_translate_oid_to_table(table_oid, S->H->server); + if (table_name) { + add_assoc_string(return_value, "table", table_name); + efree(table_name); + } + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + add_assoc_string(return_value, "native_type", BOOLLABEL); + break; + case BYTEAOID: + add_assoc_string(return_value, "native_type", BYTEALABEL); + break; + case INT8OID: + add_assoc_string(return_value, "native_type", INT8LABEL); + break; + case INT2OID: + add_assoc_string(return_value, "native_type", INT2LABEL); + break; + case INT4OID: + add_assoc_string(return_value, "native_type", INT4LABEL); + break; + case FLOAT4OID: + add_assoc_string(return_value, "native_type", FLOAT4LABEL); + break; + case FLOAT8OID: + add_assoc_string(return_value, "native_type", FLOAT8LABEL); + break; + case TEXTOID: + add_assoc_string(return_value, "native_type", TEXTLABEL); + break; + case VARCHAROID: + add_assoc_string(return_value, "native_type", VARCHARLABEL); + break; + case DATEOID: + add_assoc_string(return_value, "native_type", DATELABEL); + break; + case TIMESTAMPOID: + add_assoc_string(return_value, "native_type", TIMESTAMPLABEL); + break; + default: + /* Fetch metadata from Postgres system catalogue */ + spprintf(&q, 0, "SELECT TYPNAME FROM PG_TYPE WHERE OID=%u", S->cols[colno].pgsql_type); + res = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(res); + if (status == PGRES_TUPLES_OK && 1 == PQntuples(res)) { + add_assoc_string(return_value, "native_type", PQgetvalue(res, 0, 0)); + } + PQclear(res); + } + + enum pdo_param_type param_type; + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + param_type = PDO_PARAM_BOOL; + break; + case INT2OID: + case INT4OID: + case INT8OID: + param_type = PDO_PARAM_INT; + break; + case OIDOID: + case BYTEAOID: + param_type = PDO_PARAM_LOB; + break; + default: + param_type = PDO_PARAM_STR; + } + add_assoc_long(return_value, "pdo_type", param_type); + + return 1; +} + +static int pdo_pgsql_stmt_cursor_closer(pdo_stmt_t *stmt) { + return 1; +} + +static int pgsql_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + + switch (attr) { +#ifdef HAVE_PG_RESULT_MEMORY_SIZE + case PDO_PGSQL_ATTR_RESULT_MEMORY_SIZE: + if (stmt->executed) { + ZVAL_LONG(val, PQresultMemorySize(S->result)); + } else { + char *tmp; + spprintf(&tmp, 0, "statement '%s' has not been executed yet", S->stmt_name); + + pdo_pgsql_error_stmt_msg(stmt, 0, "HY000", tmp); + efree(tmp); + + ZVAL_NULL(val); + } + return 1; +#endif + + default: + (void) S; + return 0; + } +} + +const struct pdo_stmt_methods swoole_pgsql_stmt_methods = {pgsql_stmt_dtor, + pgsql_stmt_execute, + pgsql_stmt_fetch, + pgsql_stmt_describe, + pgsql_stmt_get_col, + pgsql_stmt_param_hook, + NULL, /* set_attr */ + pgsql_stmt_get_attr, + pgsql_stmt_get_column_meta, + NULL, /* next_rowset */ + pdo_pgsql_stmt_cursor_closer}; +#endif diff --git a/thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h b/thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h new file mode 100644 index 00000000000..b5c07349622 --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h @@ -0,0 +1,129 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_PGSQL_INT_H +#define PHP_PDO_PGSQL_INT_H + +#include +#include +#include + +#define PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE "08006" + +typedef struct { + const char *file; + int line; + unsigned int errcode; + char *errmsg; +} pdo_pgsql_error_info; + +/* stuff we use in a pgsql database handle */ +typedef struct { + PGconn *server; + unsigned attached : 1; + unsigned _reserved : 31; + pdo_pgsql_error_info einfo; + Oid pgoid; + unsigned int stmt_counter; + /* The following two variables have the same purpose. Unfortunately we need + to keep track of two different attributes having the same effect. */ + bool emulate_prepares; + bool disable_native_prepares; /* deprecated since 5.6 */ + bool disable_prepares; + HashTable *lob_streams; + zend_fcall_info_cache *notice_callback; +} pdo_pgsql_db_handle; + +typedef struct { + Oid pgsql_type; +} pdo_pgsql_column; + +typedef struct { + pdo_pgsql_db_handle *H; + PGresult *result; + pdo_pgsql_column *cols; + char *cursor_name; + char *stmt_name; + zend_string *query; + char **param_values; + int *param_lengths; + int *param_formats; + Oid *param_types; + int current_row; + bool is_prepared; +} pdo_pgsql_stmt; + +typedef struct { + Oid oid; +} pdo_pgsql_bound_param; + +extern const pdo_driver_t pdo_pgsql_driver; + +extern int pdo_pgsql_scanner(pdo_scanner_t *s); + +extern int _pdo_pgsql_error( + pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line); +#define pdo_pgsql_error(d, e, z) _pdo_pgsql_error(d, NULL, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_msg(d, e, m) _pdo_pgsql_error(d, NULL, e, NULL, m, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt(s, e, z) _pdo_pgsql_error(s->dbh, s, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt_msg(stmt, e, sqlstate, msg) \ + _pdo_pgsql_error(stmt->dbh, stmt, e, sqlstate, msg, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods pgsql_stmt_methods; + +#define pdo_pgsql_sqlstate(r) PQresultErrorField(r, PG_DIAG_SQLSTATE) + +enum { + PDO_PGSQL_ATTR_DISABLE_PREPARES = PDO_ATTR_DRIVER_SPECIFIC, + PDO_PGSQL_ATTR_RESULT_MEMORY_SIZE, +}; + +struct pdo_pgsql_lob_self { + zval dbh; + PGconn *conn; + int lfd; + Oid oid; +}; + +enum pdo_pgsql_specific_constants { + PGSQL_TRANSACTION_IDLE = PQTRANS_IDLE, + PGSQL_TRANSACTION_ACTIVE = PQTRANS_ACTIVE, + PGSQL_TRANSACTION_INTRANS = PQTRANS_INTRANS, + PGSQL_TRANSACTION_INERROR = PQTRANS_INERROR, + PGSQL_TRANSACTION_UNKNOWN = PQTRANS_UNKNOWN +}; + +php_stream *pdo_pgsql_create_lob_stream(zval *pdh, int lfd, Oid oid); +extern const php_stream_ops pdo_pgsql_lob_stream_ops; + +void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H); + +void pdo_libpq_version(char *buf, size_t len); +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh); + +void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAMETERS); + +#endif /* PHP_PDO_PGSQL_INT_H */ diff --git a/thirdparty/php84/pdo_sqlite/php_pdo_sqlite_int.h b/thirdparty/php84/pdo_sqlite/php_pdo_sqlite_int.h new file mode 100644 index 00000000000..b04f31ec7f0 --- /dev/null +++ b/thirdparty/php84/pdo_sqlite/php_pdo_sqlite_int.h @@ -0,0 +1,84 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_SQLITE_INT_H +#define PHP_PDO_SQLITE_INT_H + +#include + +typedef struct { + const char *file; + int line; + unsigned int errcode; + char *errmsg; +} pdo_sqlite_error_info; + +struct pdo_sqlite_func { + struct pdo_sqlite_func *next; + + int argc; + const char *funcname; + + /* accelerated callback references */ + zend_fcall_info_cache func; + zend_fcall_info_cache step; + zend_fcall_info_cache fini; +}; + +struct pdo_sqlite_collation { + struct pdo_sqlite_collation *next; + + const char *name; + zend_fcall_info_cache callback; +}; + +typedef struct { + sqlite3 *db; + pdo_sqlite_error_info einfo; + struct pdo_sqlite_func *funcs; + struct pdo_sqlite_collation *collations; +} pdo_sqlite_db_handle; + +typedef struct { + pdo_sqlite_db_handle *H; + sqlite3_stmt *stmt; + unsigned pre_fetched : 1; + unsigned done : 1; +} pdo_sqlite_stmt; + +extern const pdo_driver_t pdo_sqlite_driver; + +extern int pdo_sqlite_scanner(pdo_scanner_t *s); + +extern int _pdo_sqlite_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line); +#define pdo_sqlite_error(s) _pdo_sqlite_error(s, NULL, __FILE__, __LINE__) +#define pdo_sqlite_error_stmt(s) _pdo_sqlite_error(stmt->dbh, stmt, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_sqlite_stmt_methods; + +enum { + PDO_SQLITE_ATTR_OPEN_FLAGS = PDO_ATTR_DRIVER_SPECIFIC, + PDO_SQLITE_ATTR_READONLY_STATEMENT, + PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES +}; + +typedef int pdo_sqlite_create_collation_callback(void *, int, const void *, int, const void *); + +void pdo_sqlite_create_function_internal(INTERNAL_FUNCTION_PARAMETERS); +void pdo_sqlite_create_aggregate_internal(INTERNAL_FUNCTION_PARAMETERS); +void pdo_sqlite_create_collation_internal(INTERNAL_FUNCTION_PARAMETERS, pdo_sqlite_create_collation_callback callback); + +#endif diff --git a/thirdparty/php84/pdo_sqlite/sqlite_driver.c b/thirdparty/php84/pdo_sqlite/sqlite_driver.c new file mode 100644 index 00000000000..27d53bbe991 --- /dev/null +++ b/thirdparty/php84/pdo_sqlite/sqlite_driver.c @@ -0,0 +1,825 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_SQLITE_HOOK +#include "php_swoole_sqlite.h" +#include "php_swoole_call_stack.h" + +#if PHP_VERSION_ID >= 80400 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "zend_exceptions.h" +#include "sqlite_driver_arginfo.h" + +int _pdo_sqlite_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line) /* {{{ */ +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code; + pdo_sqlite_error_info *einfo = &H->einfo; + + einfo->errcode = sqlite3_errcode(H->db); + einfo->file = file; + einfo->line = line; + + if (einfo->errcode != SQLITE_OK) { + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + } + einfo->errmsg = pestrdup((char *) sqlite3_errmsg(H->db), dbh->is_persistent); + } else { /* no error */ + strncpy(*pdo_err, PDO_ERR_NONE, sizeof(*pdo_err)); + return 0; + } + switch (einfo->errcode) { + case SQLITE_NOTFOUND: + strncpy(*pdo_err, "42S02", sizeof(*pdo_err)); + break; + + case SQLITE_INTERRUPT: + strncpy(*pdo_err, "01002", sizeof(*pdo_err)); + break; + + case SQLITE_NOLFS: + strncpy(*pdo_err, "HYC00", sizeof(*pdo_err)); + break; + + case SQLITE_TOOBIG: + strncpy(*pdo_err, "22001", sizeof(*pdo_err)); + break; + + case SQLITE_CONSTRAINT: + strncpy(*pdo_err, "23000", sizeof(*pdo_err)); + break; + + case SQLITE_ERROR: + default: + strncpy(*pdo_err, "HY000", sizeof(*pdo_err)); + break; + } + + if (!dbh->methods) { + pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err); + } + + return einfo->errcode; +} +/* }}} */ + +static void pdo_sqlite_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + pdo_sqlite_error_info *einfo = &H->einfo; + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + add_next_index_string(info, einfo->errmsg); + } +} + +static void pdo_sqlite_cleanup_callbacks(pdo_sqlite_db_handle *H) { + struct pdo_sqlite_func *func; + + while (H->funcs) { + func = H->funcs; + H->funcs = func->next; + + if (H->db) { + /* delete the function from the handle */ + sqlite3_create_function(H->db, func->funcname, func->argc, SQLITE_UTF8, func, NULL, NULL, NULL); + } + + efree((char *) func->funcname); + if (ZEND_FCC_INITIALIZED(func->func)) { + zend_fcc_dtor(&func->func); + } + if (ZEND_FCC_INITIALIZED(func->step)) { + zend_fcc_dtor(&func->step); + } + if (ZEND_FCC_INITIALIZED(func->fini)) { + zend_fcc_dtor(&func->fini); + } + efree(func); + } + + while (H->collations) { + struct pdo_sqlite_collation *collation; + collation = H->collations; + H->collations = collation->next; + + if (H->db) { + /* delete the collation from the handle */ + sqlite3_create_collation(H->db, collation->name, SQLITE_UTF8, collation, NULL); + } + + efree((char *) collation->name); + if (ZEND_FCC_INITIALIZED(collation->callback)) { + zend_fcc_dtor(&collation->callback); + } + efree(collation); + } +} + +static void sqlite_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + if (H) { + pdo_sqlite_error_info *einfo = &H->einfo; + + pdo_sqlite_cleanup_callbacks(H); + if (H->db) { +#ifdef HAVE_SQLITE3_CLOSE_V2 + sqlite3_close_v2(H->db); +#else + sqlite3_close(H->db); +#endif + H->db = NULL; + } + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + } + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + } +} +/* }}} */ + +static bool sqlite_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + pdo_sqlite_stmt *S = ecalloc(1, sizeof(pdo_sqlite_stmt)); + int i; + const char *tail; + + S->H = H; + stmt->driver_data = S; + stmt->methods = &swoole_sqlite_stmt_methods; + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL | PDO_PLACEHOLDER_NAMED; + + if (PDO_CURSOR_FWDONLY != pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY)) { + H->einfo.errcode = SQLITE_ERROR; + pdo_sqlite_error(dbh); + return false; + } + + i = sqlite3_prepare_v2(H->db, ZSTR_VAL(sql), ZSTR_LEN(sql), &S->stmt, &tail); + if (i == SQLITE_OK) { + return true; + } + + pdo_sqlite_error(dbh); + + return false; +} + +static zend_long sqlite_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + if (sqlite3_exec(H->db, ZSTR_VAL(sql), NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return -1; + } else { + return sqlite3_changes(H->db); + } +} + +static zend_string *pdo_sqlite_last_insert_id(pdo_dbh_t *dbh, const zend_string *name) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + return zend_i64_to_str(sqlite3_last_insert_rowid(H->db)); +} + +/* NB: doesn't handle binary strings... use prepared stmts for that */ +static zend_string *sqlite_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) { + char *quoted; + if (ZSTR_LEN(unquoted) > (INT_MAX - 3) / 2) { + return NULL; + } + quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); + /* TODO use %Q format? */ + sqlite3_snprintf(2 * ZSTR_LEN(unquoted) + 3, quoted, "'%q'", ZSTR_VAL(unquoted)); + zend_string *quoted_str = zend_string_init(quoted, strlen(quoted), 0); + efree(quoted); + return quoted_str; +} + +static bool sqlite_handle_begin(pdo_dbh_t *dbh) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + if (sqlite3_exec(H->db, "BEGIN", NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return false; + } + return true; +} + +static bool sqlite_handle_commit(pdo_dbh_t *dbh) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + if (sqlite3_exec(H->db, "COMMIT", NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return false; + } + return true; +} + +static bool sqlite_handle_rollback(pdo_dbh_t *dbh) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + if (sqlite3_exec(H->db, "ROLLBACK", NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return false; + } + return true; +} + +static int pdo_sqlite_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) { + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + case PDO_ATTR_SERVER_VERSION: + ZVAL_STRING(return_value, (char *) sqlite3_libversion()); + break; + + default: + return 0; + } + + return 1; +} + +static bool pdo_sqlite_in_transaction(pdo_dbh_t *dbh) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + /* It's not possible in sqlite3 to explicitly turn autocommit off other + * than manually starting a transaction. Manual transactions always are + * the mode of operation when autocommit is off. */ + return H->db && sqlite3_get_autocommit(H->db) == 0; +} + +static bool pdo_sqlite_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + zend_long lval; + + switch (attr) { + case PDO_ATTR_TIMEOUT: + if (!pdo_get_long_param(&lval, val)) { + return false; + } + sqlite3_busy_timeout(H->db, lval * 1000); + return true; + case PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES: + if (!pdo_get_long_param(&lval, val)) { + return false; + } + sqlite3_extended_result_codes(H->db, lval); + return true; + } + return false; +} + +typedef struct { + zval val; + zend_long row; +} aggregate_context; + +static int do_callback( + zend_fcall_info_cache *fcc, int argc, sqlite3_value **argv, sqlite3_context *context, int is_agg) { + zval *zargs = NULL; + zval retval; + int i; + int ret = SUCCESS; + int fake_argc; + aggregate_context *agg_context = NULL; + + if (is_agg) { + is_agg = 2; + } + + fake_argc = argc + is_agg; + + /* build up the params */ + if (fake_argc) { + zargs = safe_emalloc(fake_argc, sizeof(zval), 0); + } + + if (is_agg) { + agg_context = sqlite3_aggregate_context(context, sizeof(aggregate_context)); + if (!agg_context) { + efree(zargs); + return FAILURE; + } + if (Z_ISUNDEF(agg_context->val)) { + ZVAL_NEW_REF(&agg_context->val, &EG(uninitialized_zval)); + } + ZVAL_COPY_VALUE(&zargs[0], &agg_context->val); + ZVAL_LONG(&zargs[1], ++agg_context->row); + } + + for (i = 0; i < argc; i++) { + /* get the value */ + switch (sqlite3_value_type(argv[i])) { + case SQLITE_INTEGER: + ZVAL_LONG(&zargs[i + is_agg], sqlite3_value_int(argv[i])); + break; + + case SQLITE_FLOAT: + ZVAL_DOUBLE(&zargs[i + is_agg], sqlite3_value_double(argv[i])); + break; + + case SQLITE_NULL: + ZVAL_NULL(&zargs[i + is_agg]); + break; + + case SQLITE_BLOB: + case SQLITE3_TEXT: + default: + ZVAL_STRINGL(&zargs[i + is_agg], (char *) sqlite3_value_text(argv[i]), sqlite3_value_bytes(argv[i])); + break; + } + } + + HOOK_PHP_CALL_STACK(zend_call_known_fcc(fcc, &retval, fake_argc, zargs, /* named_params */ NULL);); + + /* clean up the params */ + if (zargs) { + for (i = is_agg; i < fake_argc; i++) { + zval_ptr_dtor(&zargs[i]); + } + if (is_agg) { + zval_ptr_dtor(&zargs[1]); + } + efree(zargs); + } + + if (!is_agg || !argv) { + /* only set the sqlite return value if we are a scalar function, + * or if we are finalizing an aggregate */ + if (!Z_ISUNDEF(retval)) { + switch (Z_TYPE(retval)) { + case IS_LONG: + sqlite3_result_int(context, Z_LVAL(retval)); + break; + + case IS_NULL: + sqlite3_result_null(context); + break; + + case IS_DOUBLE: + sqlite3_result_double(context, Z_DVAL(retval)); + break; + + default: + if (!try_convert_to_string(&retval)) { + ret = FAILURE; + break; + } + sqlite3_result_text(context, Z_STRVAL(retval), Z_STRLEN(retval), SQLITE_TRANSIENT); + break; + } + } else { + sqlite3_result_error(context, "failed to invoke callback", 0); + } + + if (agg_context) { + zval_ptr_dtor(&agg_context->val); + } + } else { + /* we're stepping in an aggregate; the return value goes into + * the context */ + if (agg_context) { + if (Z_ISUNDEF(retval)) { + return FAILURE; + } + zval_ptr_dtor(Z_REFVAL(agg_context->val)); + ZVAL_COPY_VALUE(Z_REFVAL(agg_context->val), &retval); + ZVAL_UNDEF(&retval); + } + } + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + + return ret; +} + +static void php_sqlite3_func_step_callback(sqlite3_context *context, int argc, sqlite3_value **argv) { + struct pdo_sqlite_func *func = (struct pdo_sqlite_func *) sqlite3_user_data(context); + + do_callback(&func->step, argc, argv, context, 1); +} + +static void php_sqlite3_func_final_callback(sqlite3_context *context) { + struct pdo_sqlite_func *func = (struct pdo_sqlite_func *) sqlite3_user_data(context); + + do_callback(&func->fini, 0, NULL, context, 1); +} + +static int php_sqlite3_collation_callback( + void *context, int string1_len, const void *string1, int string2_len, const void *string2) { + int ret = 0; + zval zargs[2]; + zval retval; + struct pdo_sqlite_collation *collation = (struct pdo_sqlite_collation *) context; + + /* Prepare the arguments. */ + ZVAL_STRINGL(&zargs[0], (char *) string1, string1_len); + ZVAL_STRINGL(&zargs[1], (char *) string2, string2_len); + + HOOK_PHP_CALL_STACK( + zend_call_known_fcc(&collation->callback, &retval, /* argc */ 2, zargs, /* named_params */ NULL);); + + if (!Z_ISUNDEF(retval)) { + if (Z_TYPE(retval) != IS_LONG) { + convert_to_long(&retval); + } + if (Z_LVAL(retval) > 0) { + ret = 1; + } else if (Z_LVAL(retval) < 0) { + ret = -1; + } + zval_ptr_dtor(&retval); + } + + zval_ptr_dtor(&zargs[0]); + zval_ptr_dtor(&zargs[1]); + + return ret; +} + +static void php_sqlite3_func_callback(sqlite3_context *context, int argc, sqlite3_value **argv) { + struct pdo_sqlite_func *func = (struct pdo_sqlite_func *) sqlite3_user_data(context); + + do_callback(&func->func, argc, argv, context, 0); +} + +void pdo_sqlite_create_function_internal(INTERNAL_FUNCTION_PARAMETERS) { + struct pdo_sqlite_func *func; + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + char *func_name; + size_t func_name_len; + zend_long argc = -1; + zend_long flags = 0; + pdo_dbh_t *dbh; + pdo_sqlite_db_handle *H; + int ret; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(func_name, func_name_len) + Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(argc) + Z_PARAM_LONG(flags) + ZEND_PARSE_PARAMETERS_END_EX(goto error;); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_sqlite_db_handle *) dbh->driver_data; + + func = (struct pdo_sqlite_func *) ecalloc(1, sizeof(*func)); + + ret = sqlite3_create_function( + H->db, func_name, argc, flags | SQLITE_UTF8, func, php_sqlite3_func_callback, NULL, NULL); + if (ret == SQLITE_OK) { + func->funcname = estrdup(func_name); + + zend_fcc_dup(&func->func, &fcc); + + func->argc = argc; + + func->next = H->funcs; + H->funcs = func; + + RETURN_TRUE; + } + + efree(func); + +error: + zend_release_fcall_info_cache(&fcc); + RETURN_FALSE; +} + +/* {{{ bool SQLite::sqliteCreateFunction(string name, callable callback [, int argcount, int flags]) + Registers a UDF with the sqlite db handle */ +PHP_METHOD(PDO_SQLite_Ext, sqliteCreateFunction) { + pdo_sqlite_create_function_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pdo_sqlite_create_aggregate_internal(INTERNAL_FUNCTION_PARAMETERS) { + struct pdo_sqlite_func *func; + zend_fcall_info step_fci = empty_fcall_info; + zend_fcall_info fini_fci = empty_fcall_info; + zend_fcall_info_cache step_fcc = empty_fcall_info_cache; + zend_fcall_info_cache fini_fcc = empty_fcall_info_cache; + char *func_name; + size_t func_name_len; + zend_long argc = -1; + pdo_dbh_t *dbh; + pdo_sqlite_db_handle *H; + int ret; + + ZEND_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_STRING(func_name, func_name_len) + Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(step_fci, step_fcc) + Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fini_fci, fini_fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(argc) + ZEND_PARSE_PARAMETERS_END_EX(goto error;); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_sqlite_db_handle *) dbh->driver_data; + + func = (struct pdo_sqlite_func *) ecalloc(1, sizeof(*func)); + + ret = sqlite3_create_function(H->db, + func_name, + argc, + SQLITE_UTF8, + func, + NULL, + php_sqlite3_func_step_callback, + php_sqlite3_func_final_callback); + if (ret == SQLITE_OK) { + func->funcname = estrdup(func_name); + + zend_fcc_dup(&func->step, &step_fcc); + zend_fcc_dup(&func->fini, &fini_fcc); + + func->argc = argc; + + func->next = H->funcs; + H->funcs = func; + + RETURN_TRUE; + } + + efree(func); + +error: + zend_release_fcall_info_cache(&step_fcc); + zend_release_fcall_info_cache(&fini_fcc); + RETURN_FALSE; +} + +/* {{{ bool SQLite::sqliteCreateAggregate(string name, callable step, callable fini [, int argcount]) + Registers a UDF with the sqlite db handle */ + +/* The step function should have the prototype: + mixed step(mixed $context, int $rownumber, $value [, $value2 [, ...]]) + + $context will be null for the first row; on subsequent rows it will have + the value that was previously returned from the step function; you should + use this to maintain state for the aggregate. + + The fini function should have the prototype: + mixed fini(mixed $context, int $rownumber) + + $context will hold the return value from the very last call to the step function. + rownumber will hold the number of rows over which the aggregate was performed. + The return value of this function will be used as the return value for this + aggregate UDF. +*/ + +PHP_METHOD(PDO_SQLite_Ext, sqliteCreateAggregate) { + pdo_sqlite_create_aggregate_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pdo_sqlite_create_collation_internal(INTERNAL_FUNCTION_PARAMETERS, pdo_sqlite_create_collation_callback callback) { + struct pdo_sqlite_collation *collation; + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + char *collation_name; + size_t collation_name_len; + pdo_dbh_t *dbh; + pdo_sqlite_db_handle *H; + int ret; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STRING(collation_name, collation_name_len) + Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc) + ZEND_PARSE_PARAMETERS_END(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_sqlite_db_handle *) dbh->driver_data; + + collation = (struct pdo_sqlite_collation *) ecalloc(1, sizeof(*collation)); + + ret = sqlite3_create_collation(H->db, collation_name, SQLITE_UTF8, collation, callback); + if (ret == SQLITE_OK) { + collation->name = estrdup(collation_name); + + zend_fcc_dup(&collation->callback, &fcc); + + collation->next = H->collations; + H->collations = collation; + + RETURN_TRUE; + } + + zend_release_fcall_info_cache(&fcc); + + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + + efree(collation); + RETURN_FALSE; +} + +/* {{{ bool SQLite::sqliteCreateCollation(string name, callable callback) + Registers a collation with the sqlite db handle */ +PHP_METHOD(PDO_SQLite_Ext, sqliteCreateCollation) { + pdo_sqlite_create_collation_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_sqlite3_collation_callback); +} +/* }}} */ + +static const zend_function_entry *get_driver_methods(pdo_dbh_t *dbh, int kind) { + switch (kind) { + case PDO_DBH_DRIVER_METHOD_KIND_DBH: + return class_PDO_SQLite_Ext_methods; + + default: + return NULL; + } +} + +static void pdo_sqlite_request_shutdown(pdo_dbh_t *dbh) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + /* unregister functions, so that they don't linger for the next + * request */ + if (H) { + pdo_sqlite_cleanup_callbacks(H); + } +} + +static void pdo_sqlite_get_gc(pdo_dbh_t *dbh, zend_get_gc_buffer *gc_buffer) { + pdo_sqlite_db_handle *H = dbh->driver_data; + + struct pdo_sqlite_func *func = H->funcs; + while (func) { + if (ZEND_FCC_INITIALIZED(func->func)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &func->func); + } + if (ZEND_FCC_INITIALIZED(func->step)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &func->step); + } + if (ZEND_FCC_INITIALIZED(func->fini)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &func->fini); + } + func = func->next; + } + + struct pdo_sqlite_collation *collation = H->collations; + while (collation) { + if (ZEND_FCC_INITIALIZED(collation->callback)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &collation->callback); + } + collation = collation->next; + } +} + +static const struct pdo_dbh_methods sqlite_methods = {sqlite_handle_closer, + sqlite_handle_preparer, + sqlite_handle_doer, + sqlite_handle_quoter, + sqlite_handle_begin, + sqlite_handle_commit, + sqlite_handle_rollback, + pdo_sqlite_set_attr, + pdo_sqlite_last_insert_id, + pdo_sqlite_fetch_error_func, + pdo_sqlite_get_attribute, + NULL, /* check_liveness: not needed */ + get_driver_methods, + pdo_sqlite_request_shutdown, + pdo_sqlite_in_transaction, + pdo_sqlite_get_gc, + pdo_sqlite_scanner}; + +static char *make_filename_safe(const char *filename) { + if (!filename) { + return NULL; + } + if (*filename && strncasecmp(filename, "file:", 5) == 0) { + if (PG(open_basedir) && *PG(open_basedir)) { + return NULL; + } + return estrdup(filename); + } + if (*filename && strcmp(filename, ":memory:")) { + char *fullpath = expand_filepath(filename, NULL); + + if (!fullpath) { + return NULL; + } + + if (php_check_open_basedir(fullpath)) { + efree(fullpath); + return NULL; + } + return fullpath; + } + return estrdup(filename); +} + +static int authorizer( + void *autharg, int access_type, const char *arg3, const char *arg4, const char *arg5, const char *arg6) { + char *filename; + switch (access_type) { + case SQLITE_ATTACH: { + filename = make_filename_safe(arg3); + if (!filename) { + return SQLITE_DENY; + } + efree(filename); + return SQLITE_OK; + } + + default: + /* access allowed */ + return SQLITE_OK; + } +} + +static int pdo_sqlite_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_sqlite_db_handle *H; + int i, ret = 0; + zend_long timeout = 60, flags; + char *filename; + + H = pecalloc(1, sizeof(pdo_sqlite_db_handle), dbh->is_persistent); + + H->einfo.errcode = 0; + H->einfo.errmsg = NULL; + dbh->driver_data = H; + + /* skip all but this one param event */ + dbh->skip_param_evt = 0x7F ^ (1 << PDO_PARAM_EVT_EXEC_PRE); + + filename = make_filename_safe(dbh->data_source); + + if (!filename) { + zend_throw_exception_ex(php_pdo_get_exception(), 0, "open_basedir prohibits opening %s", dbh->data_source); + goto cleanup; + } + + flags = pdo_attr_lval(driver_options, PDO_SQLITE_ATTR_OPEN_FLAGS, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + + if (!(PG(open_basedir) && *PG(open_basedir))) { + flags |= SQLITE_OPEN_URI; + } + i = sqlite3_open_v2(filename, &H->db, flags, NULL); + + efree(filename); + + if (i != SQLITE_OK) { + pdo_sqlite_error(dbh); + goto cleanup; + } + + if (PG(open_basedir) && *PG(open_basedir)) { + sqlite3_set_authorizer(H->db, authorizer, NULL); + } + + if (driver_options) { + timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, timeout); + } + sqlite3_busy_timeout(H->db, timeout * 1000); + + dbh->alloc_own_columns = 1; + dbh->max_escaped_char_length = 2; + + ret = 1; + +cleanup: + dbh->methods = &sqlite_methods; + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_sqlite_driver = {PDO_DRIVER_HEADER(sqlite), pdo_sqlite_handle_factory}; +#endif diff --git a/thirdparty/php84/pdo_sqlite/sqlite_driver.stub.php b/thirdparty/php84/pdo_sqlite/sqlite_driver.stub.php new file mode 100644 index 00000000000..add395f2b99 --- /dev/null +++ b/thirdparty/php84/pdo_sqlite/sqlite_driver.stub.php @@ -0,0 +1,18 @@ + | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_SQLITE_HOOK +#include "php_swoole_sqlite.h" + +#if PHP_VERSION_ID >= 80400 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" + +static int pdo_sqlite_stmt_dtor(pdo_stmt_t *stmt) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + + if (S->stmt) { + sqlite3_finalize(S->stmt); + S->stmt = NULL; + } + efree(S); + return 1; +} + +static int pdo_sqlite_stmt_execute(pdo_stmt_t *stmt) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + + if (stmt->executed && !S->done) { + sqlite3_reset(S->stmt); + } + + S->done = 0; + switch (sqlite3_step(S->stmt)) { + case SQLITE_ROW: + S->pre_fetched = 1; + php_pdo_stmt_set_column_count(stmt, sqlite3_data_count(S->stmt)); + return 1; + + case SQLITE_DONE: + php_pdo_stmt_set_column_count(stmt, sqlite3_column_count(S->stmt)); + stmt->row_count = sqlite3_changes(S->H->db); + sqlite3_reset(S->stmt); + S->done = 1; + return 1; + + case SQLITE_ERROR: + sqlite3_reset(S->stmt); + ZEND_FALLTHROUGH; + case SQLITE_MISUSE: + case SQLITE_BUSY: + default: + pdo_sqlite_error_stmt(stmt); + return 0; + } +} + +static int pdo_sqlite_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, + enum pdo_param_event event_type) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + zval *parameter; + + switch (event_type) { + case PDO_PARAM_EVT_EXEC_PRE: + if (stmt->executed && !S->done) { + sqlite3_reset(S->stmt); + S->done = 1; + } + + if (param->is_param) { + if (param->paramno == -1) { + param->paramno = sqlite3_bind_parameter_index(S->stmt, ZSTR_VAL(param->name)) - 1; + } + + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_STMT: + return 0; + + case PDO_PARAM_NULL: + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + pdo_sqlite_error_stmt(stmt); + return 0; + + case PDO_PARAM_INT: + case PDO_PARAM_BOOL: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + } else { + convert_to_long(parameter); +#if ZEND_LONG_MAX > 2147483647 + if (SQLITE_OK == sqlite3_bind_int64(S->stmt, param->paramno + 1, Z_LVAL_P(parameter))) { + return 1; + } +#else + if (SQLITE_OK == sqlite3_bind_int(S->stmt, param->paramno + 1, Z_LVAL_P(parameter))) { + return 1; + } +#endif + } + pdo_sqlite_error_stmt(stmt); + return 0; + + case PDO_PARAM_LOB: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm = NULL; + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + zend_string *mem = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + zval_ptr_dtor(parameter); + ZVAL_STR(parameter, mem ? mem : ZSTR_EMPTY_ALLOC()); + } else { + pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource"); + return 0; + } + } else if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + pdo_sqlite_error_stmt(stmt); + return 0; + } else { + if (!try_convert_to_string(parameter)) { + return 0; + } + } + + if (SQLITE_OK == + sqlite3_bind_blob( + S->stmt, param->paramno + 1, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), SQLITE_STATIC)) { + return 1; + } + return 0; + + case PDO_PARAM_STR: + default: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + } else { + if (!try_convert_to_string(parameter)) { + return 0; + } + if (SQLITE_OK == + sqlite3_bind_text( + S->stmt, param->paramno + 1, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), SQLITE_STATIC)) { + return 1; + } + } + pdo_sqlite_error_stmt(stmt); + return 0; + } + } + break; + + default:; + } + return 1; +} + +static int pdo_sqlite_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + int i; + if (!S->stmt) { + return 0; + } + if (S->pre_fetched) { + S->pre_fetched = 0; + return 1; + } + if (S->done) { + return 0; + } + i = sqlite3_step(S->stmt); + switch (i) { + case SQLITE_ROW: + return 1; + + case SQLITE_DONE: + S->done = 1; + sqlite3_reset(S->stmt); + return 0; + + case SQLITE_ERROR: + sqlite3_reset(S->stmt); + ZEND_FALLTHROUGH; + default: + pdo_sqlite_error_stmt(stmt); + return 0; + } +} + +static int pdo_sqlite_stmt_describe(pdo_stmt_t *stmt, int colno) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + const char *str; + + if (colno >= sqlite3_column_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return 0; + } + + str = sqlite3_column_name(S->stmt, colno); + stmt->columns[colno].name = zend_string_init(str, strlen(str), 0); + stmt->columns[colno].maxlen = SIZE_MAX; + stmt->columns[colno].precision = 0; + + return 1; +} + +static int pdo_sqlite_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + if (!S->stmt) { + return 0; + } + if (colno >= sqlite3_data_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return 0; + } + switch (sqlite3_column_type(S->stmt, colno)) { + case SQLITE_NULL: + ZVAL_NULL(result); + return 1; + + case SQLITE_INTEGER: { + int64_t i = sqlite3_column_int64(S->stmt, colno); +#if SIZEOF_ZEND_LONG < 8 + if (i > ZEND_LONG_MAX || i < ZEND_LONG_MIN) { + ZVAL_STRINGL(result, (char *) sqlite3_column_text(S->stmt, colno), sqlite3_column_bytes(S->stmt, colno)); + return 1; + } +#endif + ZVAL_LONG(result, i); + return 1; + } + + case SQLITE_FLOAT: + ZVAL_DOUBLE(result, sqlite3_column_double(S->stmt, colno)); + return 1; + + case SQLITE_BLOB: + ZVAL_STRINGL_FAST(result, sqlite3_column_blob(S->stmt, colno), sqlite3_column_bytes(S->stmt, colno)); + return 1; + + default: + ZVAL_STRINGL_FAST(result, (char *) sqlite3_column_text(S->stmt, colno), sqlite3_column_bytes(S->stmt, colno)); + return 1; + } +} + +static int pdo_sqlite_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + const char *str; + zval flags; + + if (!S->stmt) { + return FAILURE; + } + if (colno >= sqlite3_column_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return FAILURE; + } + + array_init(return_value); + array_init(&flags); + + switch (sqlite3_column_type(S->stmt, colno)) { + case SQLITE_NULL: + add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE)); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_NULL); + break; + + case SQLITE_FLOAT: + add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_DOUBLE)); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + break; + + case SQLITE_BLOB: + add_next_index_string(&flags, "blob"); + /* TODO Check this is correct */ + ZEND_FALLTHROUGH; + 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); + break; + + case SQLITE_INTEGER: + add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_INTEGER)); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_INT); + break; + } + + str = sqlite3_column_decltype(S->stmt, colno); + if (str) { + add_assoc_string(return_value, "sqlite:decl_type", (char *) str); + } + +#ifdef HAVE_SQLITE3_COLUMN_TABLE_NAME + str = sqlite3_column_table_name(S->stmt, colno); + if (str) { + add_assoc_string(return_value, "table", (char *) str); + } +#endif + + add_assoc_zval(return_value, "flags", &flags); + + return SUCCESS; +} + +static int pdo_sqlite_stmt_cursor_closer(pdo_stmt_t *stmt) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + sqlite3_reset(S->stmt); + return 1; +} + +static int pdo_sqlite_stmt_get_attribute(pdo_stmt_t *stmt, zend_long attr, zval *val) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_SQLITE_ATTR_READONLY_STATEMENT: + ZVAL_FALSE(val); + +#if SQLITE_VERSION_NUMBER >= 3007004 + if (sqlite3_stmt_readonly(S->stmt)) { + ZVAL_TRUE(val); + } +#endif + break; + + default: + return 0; + } + + return 1; +} + +const struct pdo_stmt_methods swoole_sqlite_stmt_methods = {pdo_sqlite_stmt_dtor, + pdo_sqlite_stmt_execute, + pdo_sqlite_stmt_fetch, + pdo_sqlite_stmt_describe, + pdo_sqlite_stmt_get_col, + pdo_sqlite_stmt_param_hook, + NULL, /* set_attr */ + pdo_sqlite_stmt_get_attribute, /* get_attr */ + pdo_sqlite_stmt_col_meta, + NULL, /* next_rowset */ + pdo_sqlite_stmt_cursor_closer}; +#endif diff --git a/tools/bootstrap.php b/tools/bootstrap.php index 5486505073f..3a917bb29a2 100755 --- a/tools/bootstrap.php +++ b/tools/bootstrap.php @@ -125,7 +125,7 @@ function swoole_execute_and_check(array $commands): void echo "=========== Finish Done ============" . PHP_EOL . PHP_EOL; } -function scan_dir(string $dir, callable $filter = null): array +function scan_dir(string $dir, ?callable $filter = null): array { $files = array_filter(scandir($dir), function (string $file) { return $file[0] !== '.'; @@ -136,7 +136,7 @@ function scan_dir(string $dir, callable $filter = null): array return array_values($filter ? array_filter($files, $filter) : $files); } -function scan_dir_recursive(string $dir, callable $filter = null): array +function scan_dir_recursive(string $dir, ?callable $filter = null): array { $result = []; $files = scan_dir($dir, $filter);