php-src/ext/pdo_sqlite/pdo_sqlite.c
David Carlier 53231a81dd
ext/pdo_sqlite: adding Pdo_Sqlite::ATTR_BUSY_STATEMENT
allow to check if a statement is still running before reusage.

close GH-18804
2025-06-09 18:24:35 +01:00

448 lines
13 KiB
C

/*
+----------------------------------------------------------------------+
| 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 <wez@php.net> |
+----------------------------------------------------------------------+
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "SAPI.h"
#include "ext/pdo/php_pdo.h"
#include "ext/pdo/php_pdo_driver.h"
#include "php_pdo_sqlite.h"
#include "php_pdo_sqlite_int.h"
#include "zend_exceptions.h"
#include "pdo_sqlite_arginfo.h"
static zend_class_entry *pdosqlite_ce;
/* {{{ pdo_sqlite_deps */
static const zend_module_dep pdo_sqlite_deps[] = {
ZEND_MOD_REQUIRED("pdo")
ZEND_MOD_END
};
/* }}} */
/* {{{ pdo_sqlite_module_entry */
zend_module_entry pdo_sqlite_module_entry = {
STANDARD_MODULE_HEADER_EX, NULL,
pdo_sqlite_deps,
"pdo_sqlite",
NULL,
PHP_MINIT(pdo_sqlite),
PHP_MSHUTDOWN(pdo_sqlite),
NULL,
NULL,
PHP_MINFO(pdo_sqlite),
PHP_PDO_SQLITE_VERSION,
STANDARD_MODULE_PROPERTIES
};
/* }}} */
#if defined(COMPILE_DL_PDO_SQLITE) || defined(COMPILE_DL_PDO_SQLITE_EXTERNAL)
ZEND_GET_MODULE(pdo_sqlite)
#endif
/* proto bool PdoSqlite::createFunction(string $function_name, callable $callback, int $num_args = -1, int $flags = 0)
Creates a function that can be used in a query
*/
PHP_METHOD(Pdo_Sqlite, createFunction)
{
pdo_sqlite_create_function_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
#ifndef PDO_SQLITE_OMIT_LOAD_EXTENSION
/* Attempts to load an SQLite extension library. */
PHP_METHOD(Pdo_Sqlite, loadExtension)
{
char *extension, *errtext = NULL;
char fullpath[MAXPATHLEN];
size_t extension_len;
pdo_dbh_t *dbh;
pdo_sqlite_db_handle *db_handle;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &extension, &extension_len) == FAILURE) {
RETURN_THROWS();
}
if (extension_len == 0) {
zend_argument_must_not_be_empty_error(1);
RETURN_THROWS();
}
dbh = Z_PDO_DBH_P(ZEND_THIS);
PDO_CONSTRUCT_CHECK;
db_handle = (pdo_sqlite_db_handle *)dbh->driver_data;
#ifdef ZTS
if ((strncmp(sapi_module.name, "cgi", 3) != 0) &&
(strcmp(sapi_module.name, "cli") != 0) &&
(strncmp(sapi_module.name, "embed", 5) != 0)
) {
zend_throw_exception_ex(php_pdo_get_exception(), 0, "Not supported in multithreaded Web servers");
RETURN_THROWS();
}
#endif
if (!VCWD_REALPATH(extension, fullpath)) {
zend_throw_exception_ex(php_pdo_get_exception(), 0, "Unable to load extension \"%s\"", extension);
RETURN_THROWS();
}
sqlite3 *sqlite_handle;
sqlite_handle = db_handle->db;
/* This only enables extension loading for the C api, not for SQL */
sqlite3_db_config(sqlite_handle, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL);
if (sqlite3_load_extension(sqlite_handle, fullpath, 0, &errtext) != SQLITE_OK) {
zend_throw_exception_ex(php_pdo_get_exception(), 0, "Unable to load extension \"%s\"", errtext);
sqlite3_free(errtext);
sqlite3_db_config(sqlite_handle, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 0, NULL);
RETURN_THROWS();
}
/* We disable extension loading for a vague feeling of safety. This is probably not necessary
as extensions can only be loaded through C code, not through SQL, and if someone can get
some C code to run on the server, they can do anything.*/
sqlite3_db_config(sqlite_handle, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 0, NULL);
}
#endif
typedef struct {
sqlite3_blob *blob;
size_t position;
size_t size;
int flags;
} php_stream_pdosqlite3_data;
static ssize_t php_pdosqlite3_stream_write(php_stream *stream, const char *buf, size_t count)
{
php_stream_pdosqlite3_data *sqlite3_stream = (php_stream_pdosqlite3_data *) stream->abstract;
if (sqlite3_stream->flags & SQLITE_OPEN_READONLY) {
php_error_docref(NULL, E_WARNING, "Can't write to blob stream: is open as read only");
return -1;
}
if (sqlite3_stream->position + count > sqlite3_stream->size) {
php_error_docref(NULL, E_WARNING, "It is not possible to increase the size of a BLOB");
return -1;
}
if (sqlite3_blob_write(sqlite3_stream->blob, buf, count, sqlite3_stream->position) != SQLITE_OK) {
return -1;
}
if (sqlite3_stream->position + count >= sqlite3_stream->size) {
stream->eof = 1;
sqlite3_stream->position = sqlite3_stream->size;
}
else {
sqlite3_stream->position += count;
}
return count;
}
static ssize_t php_pdosqlite3_stream_read(php_stream *stream, char *buf, size_t count)
{
php_stream_pdosqlite3_data *sqlite3_stream = (php_stream_pdosqlite3_data *) stream->abstract;
if (sqlite3_stream->position + count >= sqlite3_stream->size) {
count = sqlite3_stream->size - sqlite3_stream->position;
stream->eof = 1;
}
if (count) {
if (sqlite3_blob_read(sqlite3_stream->blob, buf, count, sqlite3_stream->position) != SQLITE_OK) {
return -1;
}
sqlite3_stream->position += count;
}
return count;
}
static int php_pdosqlite3_stream_close(php_stream *stream, int close_handle)
{
php_stream_pdosqlite3_data *sqlite3_stream = (php_stream_pdosqlite3_data *) stream->abstract;
if (sqlite3_blob_close(sqlite3_stream->blob) != SQLITE_OK) {
/* Error occurred, but it still closed */
}
efree(sqlite3_stream);
return 0;
}
static int php_pdosqlite3_stream_flush(php_stream *stream)
{
/* do nothing */
return 0;
}
static int php_pdosqlite3_stream_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
{
php_stream_pdosqlite3_data *sqlite3_stream = (php_stream_pdosqlite3_data *) stream->abstract;
switch(whence) {
case SEEK_CUR:
if (offset < 0) {
if (sqlite3_stream->position < (size_t)(-offset)) {
sqlite3_stream->position = 0;
*newoffs = -1;
return -1;
} else {
sqlite3_stream->position = sqlite3_stream->position + offset;
*newoffs = sqlite3_stream->position;
stream->eof = 0;
return 0;
}
} else {
if (sqlite3_stream->position + (size_t)(offset) > sqlite3_stream->size) {
sqlite3_stream->position = sqlite3_stream->size;
*newoffs = -1;
return -1;
} else {
sqlite3_stream->position = sqlite3_stream->position + offset;
*newoffs = sqlite3_stream->position;
stream->eof = 0;
return 0;
}
}
case SEEK_SET:
if (sqlite3_stream->size < (size_t)(offset)) {
sqlite3_stream->position = sqlite3_stream->size;
*newoffs = -1;
return -1;
} else {
sqlite3_stream->position = offset;
*newoffs = sqlite3_stream->position;
stream->eof = 0;
return 0;
}
case SEEK_END:
if (offset > 0) {
sqlite3_stream->position = sqlite3_stream->size;
*newoffs = -1;
return -1;
} else if (sqlite3_stream->size < (size_t)(-offset)) {
sqlite3_stream->position = 0;
*newoffs = -1;
return -1;
} else {
sqlite3_stream->position = sqlite3_stream->size + offset;
*newoffs = sqlite3_stream->position;
stream->eof = 0;
return 0;
}
default:
*newoffs = sqlite3_stream->position;
return -1;
}
}
static int php_pdosqlite3_stream_cast(php_stream *stream, int castas, void **ret)
{
return FAILURE;
}
static int php_pdosqlite3_stream_stat(php_stream *stream, php_stream_statbuf *ssb)
{
php_stream_pdosqlite3_data *sqlite3_stream = (php_stream_pdosqlite3_data *) stream->abstract;
ssb->sb.st_size = sqlite3_stream->size;
return 0;
}
static const php_stream_ops php_stream_pdosqlite3_ops = {
php_pdosqlite3_stream_write,
php_pdosqlite3_stream_read,
php_pdosqlite3_stream_close,
php_pdosqlite3_stream_flush,
"PDOSQLite",
php_pdosqlite3_stream_seek,
php_pdosqlite3_stream_cast,
php_pdosqlite3_stream_stat,
NULL
};
/* Open a blob as a stream which we can read / write to. */
PHP_METHOD(Pdo_Sqlite, openBlob)
{
char *table, *column, *dbname = "main", *mode = "rb";
size_t table_len, column_len, dbname_len;
zend_long rowid, flags = SQLITE_OPEN_READONLY, sqlite_flags = 0;
sqlite3_blob *blob = NULL;
php_stream_pdosqlite3_data *sqlite3_stream;
php_stream *stream;
pdo_dbh_t *dbh;
pdo_sqlite_db_handle *db_handle;
dbh = Z_PDO_DBH_P(ZEND_THIS);
PDO_CONSTRUCT_CHECK;
db_handle = (pdo_sqlite_db_handle *)dbh->driver_data;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ppl|pl", &table, &table_len, &column, &column_len, &rowid, &dbname, &dbname_len, &flags) == FAILURE) {
RETURN_THROWS();
}
sqlite3 *sqlite_handle;
sqlite_handle = db_handle->db;
sqlite_flags = (flags & SQLITE_OPEN_READWRITE) ? 1 : 0;
if (sqlite3_blob_open(sqlite_handle, dbname, table, column, rowid, sqlite_flags, &blob) != SQLITE_OK) {
zend_error(E_WARNING, "Unable to open blob: %s", sqlite3_errmsg(sqlite_handle));
RETURN_FALSE;
}
sqlite3_stream = emalloc(sizeof(php_stream_pdosqlite3_data));
sqlite3_stream->blob = blob;
sqlite3_stream->flags = flags;
sqlite3_stream->position = 0;
sqlite3_stream->size = sqlite3_blob_bytes(blob);
if (sqlite_flags != 0) {
mode = "r+b";
}
stream = php_stream_alloc(&php_stream_pdosqlite3_ops, sqlite3_stream, 0, mode);
if (stream) {
php_stream_to_zval(stream, return_value);
} else {
RETURN_FALSE;
}
}
PHP_METHOD(Pdo_Sqlite, setAuthorizer)
{
zend_fcall_info fci;
zend_fcall_info_cache fcc;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_FUNC_NO_TRAMPOLINE_FREE_OR_NULL(fci, fcc)
ZEND_PARSE_PARAMETERS_END();
pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
PDO_CONSTRUCT_CHECK_WITH_CLEANUP(free_fcc);
pdo_sqlite_db_handle *db_handle = (pdo_sqlite_db_handle *) dbh->driver_data;
/* Clear previously set callback */
if (ZEND_FCC_INITIALIZED(db_handle->authorizer_fcc)) {
zend_fcc_dtor(&db_handle->authorizer_fcc);
}
/* Only enable userland authorizer if argument is not NULL */
if (ZEND_FCI_INITIALIZED(fci)) {
zend_fcc_dup(&db_handle->authorizer_fcc, &fcc);
}
return;
free_fcc:
zend_release_fcall_info_cache(&fcc);
RETURN_THROWS();
}
static int php_sqlite_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);
zend_call_known_fcc(&collation->callback, &retval, /* argc */ 2, zargs, /* named_params */ NULL);
zval_ptr_dtor(&zargs[0]);
zval_ptr_dtor(&zargs[1]);
if (!Z_ISUNDEF(retval)) {
if (Z_TYPE(retval) != IS_LONG) {
zend_string *func_name = get_active_function_or_method_name();
zend_type_error("%s(): Return value of the collation callback must be of type int, %s returned",
ZSTR_VAL(func_name), zend_zval_value_name(&retval));
zend_string_release(func_name);
zval_ptr_dtor(&retval);
return FAILURE;
}
ret = ZEND_NORMALIZE_BOOL(Z_LVAL(retval));
}
return ret;
}
PHP_METHOD(Pdo_Sqlite, createAggregate)
{
pdo_sqlite_create_aggregate_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
PHP_METHOD(Pdo_Sqlite, createCollation)
{
pdo_sqlite_create_collation_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_sqlite_collation_callback);
}
/* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(pdo_sqlite)
{
#ifdef SQLITE_DETERMINISTIC
REGISTER_PDO_CLASS_CONST_LONG("SQLITE_DETERMINISTIC", (zend_long)SQLITE_DETERMINISTIC);
#endif
REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_OPEN_FLAGS", (zend_long)PDO_SQLITE_ATTR_OPEN_FLAGS);
REGISTER_PDO_CLASS_CONST_LONG("SQLITE_OPEN_READONLY", (zend_long)SQLITE_OPEN_READONLY);
REGISTER_PDO_CLASS_CONST_LONG("SQLITE_OPEN_READWRITE", (zend_long)SQLITE_OPEN_READWRITE);
REGISTER_PDO_CLASS_CONST_LONG("SQLITE_OPEN_CREATE", (zend_long)SQLITE_OPEN_CREATE);
REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_READONLY_STATEMENT", (zend_long)PDO_SQLITE_ATTR_READONLY_STATEMENT);
REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_EXTENDED_RESULT_CODES", (zend_long)PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES);
pdosqlite_ce = register_class_Pdo_Sqlite(pdo_dbh_ce);
pdosqlite_ce->create_object = pdo_dbh_new;
if (php_pdo_register_driver(&pdo_sqlite_driver) == FAILURE) {
return FAILURE;
}
return php_pdo_register_driver_specific_ce(&pdo_sqlite_driver, pdosqlite_ce);
}
/* }}} */
/* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(pdo_sqlite)
{
php_pdo_unregister_driver(&pdo_sqlite_driver);
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(pdo_sqlite)
{
php_info_print_table_start();
php_info_print_table_row(2, "PDO Driver for SQLite 3.x", "enabled");
php_info_print_table_row(2, "SQLite Library", sqlite3_libversion());
php_info_print_table_end();
}
/* }}} */