/* * Copyright 2014-2017 MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif /* External libs */ #include "bson.h" #include "mongoc.h" /* PHP Core stuff */ #include #include #include #include #include #include #include #include #include #include #if PHP_VERSION_ID >= 70000 # include #else # include #endif /* getpid() */ #if HAVE_UNISTD_H # include #endif #ifdef PHP_WIN32 # include #endif /* Stream wrapper */ #include
#include
/* Debug log writing */ #include
/* For formating timestamp in the log */ #include /* String manipulation */ #include /* PHP array helpers */ #include "php_array_api.h" /* Our Compatability header */ #include "phongo_compat.h" /* Our stuffz */ #include "php_phongo.h" #include "php_bson.h" #include "src/BSON/functions.h" #include "src/MongoDB/Monitoring/functions.h" #undef MONGOC_LOG_DOMAIN #define MONGOC_LOG_DOMAIN "PHONGO" #define PHONGO_DEBUG_INI "mongodb.debug" #define PHONGO_DEBUG_INI_DEFAULT "" ZEND_DECLARE_MODULE_GLOBALS(mongodb) #if PHP_VERSION_ID >= 70000 #if defined(ZTS) && defined(COMPILE_DL_MONGODB) ZEND_TSRMLS_CACHE_DEFINE(); #endif #endif /* Declare zend_class_entry dependencies, which are initialized in MINIT */ zend_class_entry *php_phongo_date_immutable_ce; zend_class_entry *php_phongo_json_serializable_ce; php_phongo_server_description_type_map_t php_phongo_server_description_type_map[PHONGO_SERVER_DESCRIPTION_TYPES] = { { PHONGO_SERVER_UNKNOWN, "Unknown" }, { PHONGO_SERVER_STANDALONE, "Standalone" }, { PHONGO_SERVER_MONGOS, "Mongos" }, { PHONGO_SERVER_POSSIBLE_PRIMARY, "PossiblePrimary" }, { PHONGO_SERVER_RS_PRIMARY, "RSPrimary" }, { PHONGO_SERVER_RS_SECONDARY, "RSSecondary" }, { PHONGO_SERVER_RS_ARBITER, "RSArbiter" }, { PHONGO_SERVER_RS_OTHER, "RSOther" }, { PHONGO_SERVER_RS_GHOST, "RSGhost" }, }; /* {{{ phongo_std_object_handlers */ zend_object_handlers phongo_std_object_handlers; zend_object_handlers *phongo_get_std_object_handlers(void) { return &phongo_std_object_handlers; } /* }}} */ /* Forward declarations */ static bool phongo_split_namespace(const char *namespace, char **dbname, char **cname); /* {{{ Error reporting and logging */ zend_class_entry* phongo_exception_from_phongo_domain(php_phongo_error_domain_t domain) { switch (domain) { case PHONGO_ERROR_INVALID_ARGUMENT: return php_phongo_invalidargumentexception_ce; case PHONGO_ERROR_LOGIC: return php_phongo_logicexception_ce; case PHONGO_ERROR_RUNTIME: return php_phongo_runtimeexception_ce; case PHONGO_ERROR_UNEXPECTED_VALUE: return php_phongo_unexpectedvalueexception_ce; case PHONGO_ERROR_MONGOC_FAILED: return php_phongo_runtimeexception_ce; case PHONGO_ERROR_WRITE_FAILED: return php_phongo_bulkwriteexception_ce; case PHONGO_ERROR_CONNECTION_FAILED: return php_phongo_connectionexception_ce; } MONGOC_ERROR("Resolving unknown phongo error domain: %d", domain); return php_phongo_runtimeexception_ce; } zend_class_entry* phongo_exception_from_mongoc_domain(uint32_t /* mongoc_error_domain_t */ domain, uint32_t /* mongoc_error_code_t */ code) { switch(code) { case 50: /* ExceededTimeLimit */ return php_phongo_executiontimeoutexception_ce; case MONGOC_ERROR_STREAM_SOCKET: case MONGOC_ERROR_SERVER_SELECTION_FAILURE: return php_phongo_connectiontimeoutexception_ce; case MONGOC_ERROR_CLIENT_AUTHENTICATE: return php_phongo_authenticationexception_ce; case MONGOC_ERROR_COMMAND_INVALID_ARG: return php_phongo_invalidargumentexception_ce; case MONGOC_ERROR_STREAM_INVALID_TYPE: case MONGOC_ERROR_STREAM_INVALID_STATE: case MONGOC_ERROR_STREAM_NAME_RESOLUTION: case MONGOC_ERROR_STREAM_CONNECT: case MONGOC_ERROR_STREAM_NOT_ESTABLISHED: return php_phongo_connectionexception_ce; case MONGOC_ERROR_CLIENT_NOT_READY: case MONGOC_ERROR_CLIENT_TOO_BIG: case MONGOC_ERROR_CLIENT_TOO_SMALL: case MONGOC_ERROR_CLIENT_GETNONCE: case MONGOC_ERROR_CLIENT_NO_ACCEPTABLE_PEER: case MONGOC_ERROR_CLIENT_IN_EXHAUST: case MONGOC_ERROR_PROTOCOL_INVALID_REPLY: case MONGOC_ERROR_PROTOCOL_BAD_WIRE_VERSION: case MONGOC_ERROR_CURSOR_INVALID_CURSOR: case MONGOC_ERROR_QUERY_FAILURE: /*case MONGOC_ERROR_PROTOCOL_ERROR:*/ case MONGOC_ERROR_BSON_INVALID: case MONGOC_ERROR_MATCHER_INVALID: case MONGOC_ERROR_NAMESPACE_INVALID: case MONGOC_ERROR_COLLECTION_INSERT_FAILED: case MONGOC_ERROR_GRIDFS_INVALID_FILENAME: case MONGOC_ERROR_QUERY_COMMAND_NOT_FOUND: case MONGOC_ERROR_QUERY_NOT_TAILABLE: return php_phongo_runtimeexception_ce; } switch (domain) { case MONGOC_ERROR_CLIENT: case MONGOC_ERROR_STREAM: case MONGOC_ERROR_PROTOCOL: case MONGOC_ERROR_CURSOR: case MONGOC_ERROR_QUERY: case MONGOC_ERROR_INSERT: case MONGOC_ERROR_SASL: case MONGOC_ERROR_BSON: case MONGOC_ERROR_MATCHER: case MONGOC_ERROR_NAMESPACE: case MONGOC_ERROR_COMMAND: case MONGOC_ERROR_COLLECTION: case MONGOC_ERROR_GRIDFS: /* FIXME: We don't have the Exceptions mocked yet.. */ #if 0 return phongo_ce_mongo_connection_exception; #endif default: return php_phongo_runtimeexception_ce; } } void phongo_throw_exception(php_phongo_error_domain_t domain TSRMLS_DC, const char *format, ...) { va_list args; char *message; int message_len; va_start(args, format); message_len = vspprintf(&message, 0, format, args); zend_throw_exception(phongo_exception_from_phongo_domain(domain), message, 0 TSRMLS_CC); efree(message); va_end(args); } void phongo_throw_exception_from_bson_error_t(bson_error_t *error TSRMLS_DC) { zend_throw_exception(phongo_exception_from_mongoc_domain(error->domain, error->code), error->message, error->code TSRMLS_CC); } static void php_phongo_log(mongoc_log_level_t log_level, const char *log_domain, const char *message, void *user_data) { struct timeval tv; time_t t; phongo_long tu; phongo_char *dt; PHONGO_TSRMLS_FETCH_FROM_CTX(user_data); (void)user_data; gettimeofday(&tv, NULL); t = tv.tv_sec; tu = tv.tv_usec; dt = php_format_date((char *) ZEND_STRL("Y-m-d\\TH:i:s"), t, 0 TSRMLS_CC); fprintf(MONGODB_G(debug_fd), "[%s.%06" PHONGO_LONG_FORMAT "+00:00] %10s: %-8s> %s\n", ZSTR_VAL(dt), tu, log_domain, mongoc_log_level_str(log_level), message); fflush(MONGODB_G(debug_fd)); efree(dt); } /* }}} */ /* {{{ Init objects */ static void phongo_cursor_init(zval *return_value, mongoc_client_t *client, mongoc_cursor_t *cursor, zval *readPreference, zval *session TSRMLS_DC) /* {{{ */ { php_phongo_cursor_t *intern; object_init_ex(return_value, php_phongo_cursor_ce); intern = Z_CURSOR_OBJ_P(return_value); intern->cursor = cursor; intern->server_id = mongoc_cursor_get_hint(cursor); intern->client = client; intern->advanced = false; if (readPreference) { #if PHP_VERSION_ID >= 70000 ZVAL_ZVAL(&intern->read_preference, readPreference, 1, 0); #else Z_ADDREF_P(readPreference); intern->read_preference = readPreference; #endif } if (session) { #if PHP_VERSION_ID >= 70000 ZVAL_ZVAL(&intern->session, session, 1, 0); #else Z_ADDREF_P(session); intern->session = session; #endif } } /* }}} */ static void phongo_cursor_init_for_command(zval *return_value, mongoc_client_t *client, mongoc_cursor_t *cursor, const char *db, zval *command, zval *readPreference, zval *session TSRMLS_DC) /* {{{ */ { php_phongo_cursor_t *intern; phongo_cursor_init(return_value, client, cursor, readPreference, session TSRMLS_CC); intern = Z_CURSOR_OBJ_P(return_value); intern->database = estrdup(db); #if PHP_VERSION_ID >= 70000 ZVAL_ZVAL(&intern->command, command, 1, 0); #else Z_ADDREF_P(command); intern->command = command; #endif } /* }}} */ static void phongo_cursor_init_for_query(zval *return_value, mongoc_client_t *client, mongoc_cursor_t *cursor, const char *namespace, zval *query, zval *readPreference, zval *session TSRMLS_DC) /* {{{ */ { php_phongo_cursor_t *intern; phongo_cursor_init(return_value, client, cursor, readPreference, session TSRMLS_CC); intern = Z_CURSOR_OBJ_P(return_value); /* namespace has already been validated by phongo_execute_query() */ phongo_split_namespace(namespace, &intern->database, &intern->collection); /* cursor has already been advanced by phongo_execute_query() calling * phongo_cursor_advance_and_check_for_error() */ intern->advanced = true; #if PHP_VERSION_ID >= 70000 ZVAL_ZVAL(&intern->query, query, 1, 0); #else Z_ADDREF_P(query); intern->query = query; #endif } /* }}} */ void phongo_server_init(zval *return_value, mongoc_client_t *client, uint32_t server_id TSRMLS_DC) /* {{{ */ { php_phongo_server_t *server; object_init_ex(return_value, php_phongo_server_ce); server = Z_SERVER_OBJ_P(return_value); server->server_id = server_id; server->client = client; } /* }}} */ void phongo_session_init(zval *return_value, mongoc_client_session_t *client_session TSRMLS_DC) /* {{{ */ { php_phongo_session_t *session; object_init_ex(return_value, php_phongo_session_ce); session = Z_SESSION_OBJ_P(return_value); session->client_session = client_session; } /* }}} */ void phongo_readconcern_init(zval *return_value, const mongoc_read_concern_t *read_concern TSRMLS_DC) /* {{{ */ { php_phongo_readconcern_t *intern; object_init_ex(return_value, php_phongo_readconcern_ce); intern = Z_READCONCERN_OBJ_P(return_value); intern->read_concern = mongoc_read_concern_copy(read_concern); } /* }}} */ void phongo_readpreference_init(zval *return_value, const mongoc_read_prefs_t *read_prefs TSRMLS_DC) /* {{{ */ { php_phongo_readpreference_t *intern; object_init_ex(return_value, php_phongo_readpreference_ce); intern = Z_READPREFERENCE_OBJ_P(return_value); intern->read_preference = mongoc_read_prefs_copy(read_prefs); } /* }}} */ void phongo_writeconcern_init(zval *return_value, const mongoc_write_concern_t *write_concern TSRMLS_DC) /* {{{ */ { php_phongo_writeconcern_t *intern; object_init_ex(return_value, php_phongo_writeconcern_ce); intern = Z_WRITECONCERN_OBJ_P(return_value); intern->write_concern = mongoc_write_concern_copy(write_concern); } /* }}} */ zend_bool phongo_writeconcernerror_init(zval *return_value, bson_t *bson TSRMLS_DC) /* {{{ */ { bson_iter_t iter; php_phongo_writeconcernerror_t *intern; object_init_ex(return_value, php_phongo_writeconcernerror_ce); intern = Z_WRITECONCERNERROR_OBJ_P(return_value); if (bson_iter_init_find(&iter, bson, "code") && BSON_ITER_HOLDS_INT32(&iter)) { intern->code = bson_iter_int32(&iter); } if (bson_iter_init_find(&iter, bson, "errmsg") && BSON_ITER_HOLDS_UTF8(&iter)) { uint32_t errmsg_len; const char *err_msg = bson_iter_utf8(&iter, &errmsg_len); intern->message = estrndup(err_msg, errmsg_len); } if (bson_iter_init_find(&iter, bson, "errInfo") && BSON_ITER_HOLDS_DOCUMENT(&iter)) { uint32_t len; const uint8_t *data = NULL; bson_iter_document(&iter, &len, &data); if (!php_phongo_bson_to_zval(data, len, &intern->info)) { zval_ptr_dtor(&intern->info); ZVAL_UNDEF(&intern->info); return false; } } return true; } /* }}} */ zend_bool phongo_writeerror_init(zval *return_value, bson_t *bson TSRMLS_DC) /* {{{ */ { bson_iter_t iter; php_phongo_writeerror_t *intern; object_init_ex(return_value, php_phongo_writeerror_ce); intern = Z_WRITEERROR_OBJ_P(return_value); if (bson_iter_init_find(&iter, bson, "code") && BSON_ITER_HOLDS_INT32(&iter)) { intern->code = bson_iter_int32(&iter); } if (bson_iter_init_find(&iter, bson, "errmsg") && BSON_ITER_HOLDS_UTF8(&iter)) { uint32_t errmsg_len; const char *err_msg = bson_iter_utf8(&iter, &errmsg_len); intern->message = estrndup(err_msg, errmsg_len); } if (bson_iter_init_find(&iter, bson, "errInfo") && BSON_ITER_HOLDS_DOCUMENT(&iter)) { uint32_t len; const uint8_t *data = NULL; bson_iter_document(&iter, &len, &data); if (!php_phongo_bson_to_zval(data, len, &intern->info)) { zval_ptr_dtor(&intern->info); ZVAL_UNDEF(&intern->info); return false; } } if (bson_iter_init_find(&iter, bson, "index") && BSON_ITER_HOLDS_INT32(&iter)) { intern->index = bson_iter_int32(&iter); } return true; } /* }}} */ static php_phongo_writeresult_t *phongo_writeresult_init(zval *return_value, bson_t *reply, mongoc_client_t *client, uint32_t server_id TSRMLS_DC) /* {{{ */ { php_phongo_writeresult_t *writeresult; object_init_ex(return_value, php_phongo_writeresult_ce); writeresult = Z_WRITERESULT_OBJ_P(return_value); writeresult->reply = bson_copy(reply); writeresult->server_id = server_id; writeresult->client = client; return writeresult; } /* }}} */ /* }}} */ /* {{{ CRUD */ /* Splits a namespace name into the database and collection names, allocated with estrdup. */ static bool phongo_split_namespace(const char *namespace, char **dbname, char **cname) /* {{{ */ { char *dot = strchr(namespace, '.'); if (!dot) { return false; } if (cname) { *cname = estrdup(namespace + (dot - namespace) + 1); } if (dbname) { *dbname = estrndup(namespace, dot - namespace); } return true; } /* }}} */ /* Parses the "readConcern" option for an execute method. If mongoc_opts is not * NULL, the option will be appended. On error, false is returned and an * exception is thrown. */ static bool phongo_parse_read_concern(zval *options, bson_t *mongoc_opts TSRMLS_DC) /* {{{ */ { zval *option = NULL; mongoc_read_concern_t *read_concern; if (!options) { return true; } if (Z_TYPE_P(options) != IS_ARRAY) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected options to be array, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(options)); return false; } option = php_array_fetchc(options, "readConcern"); if (!option) { return true; } if (Z_TYPE_P(option) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(option), php_phongo_readconcern_ce TSRMLS_CC)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected \"readConcern\" option to be %s, %s given", ZSTR_VAL(php_phongo_readconcern_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(option)); return false; } read_concern = Z_READCONCERN_OBJ_P(option)->read_concern; if (mongoc_opts && !mongoc_read_concern_append(read_concern, mongoc_opts)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error appending \"readConcern\" option"); return false; } return true; } /* }}} */ /* Parses the "readPreference" option for an execute method. If zreadPreference * is not NULL, it will be assigned to the option. On error, false is returned * and an exception is thrown. */ bool phongo_parse_read_preference(zval *options, zval **zreadPreference TSRMLS_DC) /* {{{ */ { zval *option = NULL; if (!options) { return true; } if (Z_TYPE_P(options) != IS_ARRAY) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected options to be array, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(options)); return false; } option = php_array_fetchc(options, "readPreference"); if (!option) { return true; } if (Z_TYPE_P(option) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(option), php_phongo_readpreference_ce TSRMLS_CC)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected \"readPreference\" option to be %s, %s given", ZSTR_VAL(php_phongo_readpreference_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(option)); return false; } if (zreadPreference) { *zreadPreference = option; } return true; } /* }}} */ /* Parses the "session" option for an execute method. The client object should * correspond to the Manager executing the operation and will be used to ensure * that the session is correctly associated with that client. If mongoc_opts is * not NULL, the option will be appended. If zsession is not NULL, it will be * assigned to the option. On error, false is returned and an exception is * thrown. */ static bool phongo_parse_session(zval *options, mongoc_client_t *client, bson_t *mongoc_opts, zval **zsession TSRMLS_DC) /* {{{ */ { zval *option = NULL; const mongoc_client_session_t *client_session; if (!options) { return true; } if (Z_TYPE_P(options) != IS_ARRAY) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected options to be array, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(options)); return false; } option = php_array_fetchc(options, "session"); if (!option) { return true; } if (Z_TYPE_P(option) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(option), php_phongo_session_ce TSRMLS_CC)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected \"session\" option to be %s, %s given", ZSTR_VAL(php_phongo_session_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(option)); return false; } client_session = Z_SESSION_OBJ_P(option)->client_session; if (client != mongoc_client_session_get_client(client_session)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Cannot use Session started from a different Manager"); return false; } if (mongoc_opts && !mongoc_client_session_append(client_session, mongoc_opts, NULL)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error appending \"session\" option"); return false; } if (zsession) { *zsession = option; } return true; } /* }}} */ /* Parses the "writeConcern" option for an execute method. If mongoc_opts is not * NULL, the option will be appended. If zwriteConcern is not NULL, it will be * assigned to the option. On error, false is returned and an exception is * thrown. */ static bool phongo_parse_write_concern(zval *options, bson_t *mongoc_opts, zval **zwriteConcern TSRMLS_DC) /* {{{ */ { zval *option = NULL; mongoc_write_concern_t *write_concern; if (!options) { return true; } if (Z_TYPE_P(options) != IS_ARRAY) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected options to be array, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(options)); return false; } option = php_array_fetchc(options, "writeConcern"); if (!option) { return true; } if (Z_TYPE_P(option) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(option), php_phongo_writeconcern_ce TSRMLS_CC)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected \"writeConcern\" option to be %s, %s given", ZSTR_VAL(php_phongo_writeconcern_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(option)); return false; } write_concern = Z_WRITECONCERN_OBJ_P(option)->write_concern; if (mongoc_opts && !mongoc_write_concern_append(write_concern, mongoc_opts)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error appending \"writeConcern\" option"); return false; } if (zwriteConcern) { *zwriteConcern = option; } return true; } bool phongo_execute_bulk_write(mongoc_client_t *client, const char *namespace, php_phongo_bulkwrite_t *bulk_write, zval *options, uint32_t server_id, zval *return_value, int return_value_used TSRMLS_DC) /* {{{ */ { bson_error_t error; int success; bson_t reply = BSON_INITIALIZER; mongoc_bulk_operation_t *bulk = bulk_write->bulk; php_phongo_writeresult_t *writeresult; zval *zwriteConcern = NULL; zval *zsession = NULL; const mongoc_write_concern_t *write_concern = NULL; if (bulk_write->executed) { phongo_throw_exception(PHONGO_ERROR_WRITE_FAILED TSRMLS_CC, "BulkWrite objects may only be executed once and this instance has already been executed"); return false; } if (!phongo_split_namespace(namespace, &bulk_write->database, &bulk_write->collection)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "%s: %s", "Invalid namespace provided", namespace); return false; } if (!phongo_parse_session(options, client, NULL, &zsession TSRMLS_CC)) { /* Exception should already have been thrown */ return false; } if (!phongo_parse_write_concern(options, NULL, &zwriteConcern TSRMLS_CC)) { /* Exception should already have been thrown */ return false; } mongoc_bulk_operation_set_database(bulk, bulk_write->database); mongoc_bulk_operation_set_collection(bulk, bulk_write->collection); mongoc_bulk_operation_set_client(bulk, client); mongoc_bulk_operation_set_hint(bulk, server_id); if (zsession) { mongoc_bulk_operation_set_client_session(bulk, Z_SESSION_OBJ_P(zsession)->client_session); } /* If a write concern was not specified, libmongoc will use the client's * write concern; however, we should still fetch it for the write result. */ write_concern = phongo_write_concern_from_zval(zwriteConcern TSRMLS_CC); if (write_concern) { mongoc_bulk_operation_set_write_concern(bulk, write_concern); } else { write_concern = mongoc_client_get_write_concern(client); } success = mongoc_bulk_operation_execute(bulk, &reply, &error); bulk_write->executed = true; /* Write succeeded and the user doesn't care for the results */ if (success && !return_value_used) { bson_destroy(&reply); return true; } /* Check for connection related exceptions */ if (EG(exception)) { bson_destroy(&reply); return false; } writeresult = phongo_writeresult_init(return_value, &reply, client, mongoc_bulk_operation_get_hint(bulk) TSRMLS_CC); writeresult->write_concern = mongoc_write_concern_copy(write_concern); /* The Write failed */ if (!success) { if ((error.domain == MONGOC_ERROR_COMMAND && error.code != MONGOC_ERROR_COMMAND_INVALID_ARG) || error.domain == MONGOC_ERROR_WRITE_CONCERN) { phongo_throw_exception(PHONGO_ERROR_WRITE_FAILED TSRMLS_CC, "%s", error.message); phongo_add_exception_prop(ZEND_STRL("writeResult"), return_value TSRMLS_CC); } else { phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC); } } bson_destroy(&reply); return success; } /* }}} */ /* Advance the cursor and return whether there is an error. On error, false is * returned and an exception is thrown. */ bool phongo_cursor_advance_and_check_for_error(mongoc_cursor_t *cursor TSRMLS_DC) /* {{{ */ { const bson_t *doc; if (!mongoc_cursor_next(cursor, &doc)) { bson_error_t error; /* Check for connection related exceptions */ if (EG(exception)) { return false; } /* Could simply be no docs, which is not an error */ if (mongoc_cursor_error(cursor, &error)) { phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC); return false; } } return true; } /* }}} */ bool phongo_execute_query(mongoc_client_t *client, const char *namespace, zval *zquery, zval *options, uint32_t server_id, zval *return_value, int return_value_used TSRMLS_DC) /* {{{ */ { const php_phongo_query_t *query; mongoc_cursor_t *cursor; char *dbname; char *collname; mongoc_collection_t *collection; zval *zreadPreference = NULL; zval *zsession = NULL; if (!phongo_split_namespace(namespace, &dbname, &collname)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "%s: %s", "Invalid namespace provided", namespace); return false; } collection = mongoc_client_get_collection(client, dbname, collname); efree(dbname); efree(collname); query = Z_QUERY_OBJ_P(zquery); if (query->read_concern) { mongoc_collection_set_read_concern(collection, query->read_concern); } if (!phongo_parse_read_preference(options, &zreadPreference TSRMLS_CC)) { /* Exception should already have been thrown */ mongoc_collection_destroy(collection); return false; } if (!phongo_parse_session(options, client, query->opts, &zsession TSRMLS_CC)) { /* Exception should already have been thrown */ mongoc_collection_destroy(collection); return false; } if (!BSON_APPEND_INT32(query->opts, "serverId", server_id)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error appending \"serverId\" option"); mongoc_collection_destroy(collection); return false; } cursor = mongoc_collection_find_with_opts(collection, query->filter, query->opts, phongo_read_preference_from_zval(zreadPreference TSRMLS_CC)); mongoc_collection_destroy(collection); /* maxAwaitTimeMS must be set before the cursor is sent */ if (query->max_await_time_ms) { mongoc_cursor_set_max_await_time_ms(cursor, query->max_await_time_ms); } if (!phongo_cursor_advance_and_check_for_error(cursor TSRMLS_CC)) { mongoc_cursor_destroy(cursor); return false; } if (!return_value_used) { mongoc_cursor_destroy(cursor); return true; } phongo_cursor_init_for_query(return_value, client, cursor, namespace, zquery, zreadPreference, zsession TSRMLS_CC); return true; } /* }}} */ static bson_t *create_wrapped_command_envelope(const char *db, bson_t *reply) { bson_t *tmp; size_t max_ns_len = strlen(db) + 5 + 1; /* db + ".$cmd" + '\0' */ char *ns = emalloc(max_ns_len); snprintf(ns, max_ns_len, "%s.$cmd", db); tmp = BCON_NEW("cursor", "{", "id", BCON_INT64(0), "ns", BCON_UTF8(ns), "firstBatch", "[", BCON_DOCUMENT(reply), "]", "}"); efree(ns); return tmp; } static zval *phongo_create_implicit_session(mongoc_client_t *client TSRMLS_DC) /* {{{ */ { mongoc_client_session_t *cs; zval *zsession; cs = mongoc_client_start_session(client, NULL, NULL); if (!cs) { return NULL; } #if PHP_VERSION_ID >= 70000 zsession = ecalloc(sizeof(zval), 1); #else ALLOC_INIT_ZVAL(zsession); #endif phongo_session_init(zsession, cs TSRMLS_CC); return zsession; } /* }}} */ bool phongo_execute_command(mongoc_client_t *client, php_phongo_command_type_t type, const char *db, zval *zcommand, zval *options, uint32_t server_id, zval *return_value, int return_value_used TSRMLS_DC) /* {{{ */ { const php_phongo_command_t *command; bson_iter_t iter; bson_t reply; bson_error_t error; bson_t opts = BSON_INITIALIZER; mongoc_cursor_t *cmd_cursor; zval *zreadPreference = NULL; zval *zsession = NULL; bool result = false; bool free_reply = false; bool free_zsession = false; command = Z_COMMAND_OBJ_P(zcommand); if ((type & PHONGO_OPTION_READ_CONCERN) && !phongo_parse_read_concern(options, &opts TSRMLS_CC)) { /* Exception should already have been thrown */ goto cleanup; } if ((type & PHONGO_OPTION_READ_PREFERENCE) && !phongo_parse_read_preference(options, &zreadPreference TSRMLS_CC)) { /* Exception should already have been thrown */ goto cleanup; } if (!phongo_parse_session(options, client, &opts, &zsession TSRMLS_CC)) { /* Exception should already have been thrown */ goto cleanup; } /* If an explicit session was not provided, attempt to create an implicit * client session (ignoring any errors). */ if (!zsession) { zsession = phongo_create_implicit_session(client TSRMLS_CC); if (zsession) { free_zsession = true; if (!mongoc_client_session_append(Z_SESSION_OBJ_P(zsession)->client_session, &opts, NULL)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error appending implicit \"sessionId\" option"); goto cleanup; } } } if ((type & PHONGO_OPTION_WRITE_CONCERN) && !phongo_parse_write_concern(options, &opts, NULL TSRMLS_CC)) { /* Exception should already have been thrown */ goto cleanup; } if (!BSON_APPEND_INT32(&opts, "serverId", server_id)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error appending \"serverId\" option"); goto cleanup; } /* Although "opts" already always includes the serverId option, the read * preference is added to the command parts, which is relevant for mongos * command construction. */ switch (type) { case PHONGO_COMMAND_RAW: result = mongoc_client_command_with_opts(client, db, command->bson, phongo_read_preference_from_zval(zreadPreference TSRMLS_CC), &opts, &reply, &error); break; case PHONGO_COMMAND_READ: result = mongoc_client_read_command_with_opts(client, db, command->bson, phongo_read_preference_from_zval(zreadPreference TSRMLS_CC), &opts, &reply, &error); break; case PHONGO_COMMAND_WRITE: result = mongoc_client_write_command_with_opts(client, db, command->bson, &opts, &reply, &error); break; case PHONGO_COMMAND_READ_WRITE: /* We can pass NULL as readPreference, as this argument was added historically, but has no function */ result = mongoc_client_read_write_command_with_opts(client, db, command->bson, NULL, &opts, &reply, &error); break; default: /* Should never happen, but if it does: exception */ phongo_throw_exception(PHONGO_ERROR_LOGIC TSRMLS_CC, "Type '%d' should never have been passed to phongo_execute_command, please file a bug report", type); goto cleanup; } free_reply = true; if (!result) { phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC); goto cleanup; } if (!return_value_used) { goto cleanup; } /* According to mongoc_cursor_new_from_command_reply(), the reply bson_t * is ultimately destroyed on both success and failure. */ if (bson_iter_init_find(&iter, &reply, "cursor") && BSON_ITER_HOLDS_DOCUMENT(&iter)) { bson_t initial_reply = BSON_INITIALIZER; bson_error_t error = {0}; bson_copy_to(&reply, &initial_reply); if (command->max_await_time_ms) { bson_append_bool(&initial_reply, "awaitData", -1, 1); bson_append_int64(&initial_reply, "maxAwaitTimeMS", -1, command->max_await_time_ms); bson_append_bool(&initial_reply, "tailable", -1, 1); } if (command->batch_size) { bson_append_int64(&initial_reply, "batchSize", -1, command->batch_size); } if (zsession && !mongoc_client_session_append(Z_SESSION_OBJ_P(zsession)->client_session, &initial_reply, &error)) { phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC); bson_destroy(&initial_reply); result = false; goto cleanup; } cmd_cursor = mongoc_cursor_new_from_command_reply(client, &initial_reply, server_id); } else { bson_t *wrapped_reply = create_wrapped_command_envelope(db, &reply); cmd_cursor = mongoc_cursor_new_from_command_reply(client, wrapped_reply, server_id); } phongo_cursor_init_for_command(return_value, client, cmd_cursor, db, zcommand, zreadPreference, zsession TSRMLS_CC); cleanup: bson_destroy(&opts); if (free_reply) { bson_destroy(&reply); } if (free_zsession) { #if PHP_VERSION_ID >= 70000 zval_ptr_dtor(zsession); efree(zsession); #else zval_ptr_dtor(&zsession); #endif } return result; } /* }}} */ /* }}} */ /* {{{ mongoc types from from_zval */ const mongoc_write_concern_t* phongo_write_concern_from_zval(zval *zwrite_concern TSRMLS_DC) /* {{{ */ { if (zwrite_concern) { php_phongo_writeconcern_t *intern = Z_WRITECONCERN_OBJ_P(zwrite_concern); if (intern) { return intern->write_concern; } } return NULL; } /* }}} */ const mongoc_read_concern_t* phongo_read_concern_from_zval(zval *zread_concern TSRMLS_DC) /* {{{ */ { if (zread_concern) { php_phongo_readconcern_t *intern = Z_READCONCERN_OBJ_P(zread_concern); if (intern) { return intern->read_concern; } } return NULL; } /* }}} */ const mongoc_read_prefs_t* phongo_read_preference_from_zval(zval *zread_preference TSRMLS_DC) /* {{{ */ { if (zread_preference) { php_phongo_readpreference_t *intern = Z_READPREFERENCE_OBJ_P(zread_preference); if (intern) { return intern->read_preference; } } return NULL; } /* }}} */ /* }}} */ /* {{{ phongo zval from mongoc types */ void php_phongo_cursor_id_new_from_id(zval *object, int64_t cursorid TSRMLS_DC) /* {{{ */ { php_phongo_cursorid_t *intern; object_init_ex(object, php_phongo_cursorid_ce); intern = Z_CURSORID_OBJ_P(object); intern->id = cursorid; } /* }}} */ void php_phongo_objectid_new_from_oid(zval *object, const bson_oid_t *oid TSRMLS_DC) /* {{{ */ { php_phongo_objectid_t *intern; object_init_ex(object, php_phongo_objectid_ce); intern = Z_OBJECTID_OBJ_P(object); bson_oid_to_string(oid, intern->oid); intern->initialized = true; } /* }}} */ php_phongo_server_description_type_t php_phongo_server_description_type(mongoc_server_description_t *sd) { const char* name = mongoc_server_description_type(sd); int i; for (i = 0; i < PHONGO_SERVER_DESCRIPTION_TYPES; i++) { if (!strcmp(name, php_phongo_server_description_type_map[i].name)) { return php_phongo_server_description_type_map[i].type; } } return PHONGO_SERVER_UNKNOWN; } void php_phongo_server_to_zval(zval *retval, mongoc_server_description_t *sd) /* {{{ */ { mongoc_host_list_t *host = mongoc_server_description_host(sd); const bson_t *is_master = mongoc_server_description_ismaster(sd); bson_iter_t iter; array_init(retval); ADD_ASSOC_STRING(retval, "host", host->host); ADD_ASSOC_LONG_EX(retval, "port", host->port); ADD_ASSOC_LONG_EX(retval, "type", php_phongo_server_description_type(sd)); ADD_ASSOC_BOOL_EX(retval, "is_primary", !strcmp(mongoc_server_description_type(sd), php_phongo_server_description_type_map[PHONGO_SERVER_RS_PRIMARY].name)); ADD_ASSOC_BOOL_EX(retval, "is_secondary", !strcmp(mongoc_server_description_type(sd), php_phongo_server_description_type_map[PHONGO_SERVER_RS_SECONDARY].name)); ADD_ASSOC_BOOL_EX(retval, "is_arbiter", !strcmp(mongoc_server_description_type(sd), php_phongo_server_description_type_map[PHONGO_SERVER_RS_ARBITER].name)); ADD_ASSOC_BOOL_EX(retval, "is_hidden", bson_iter_init_find_case(&iter, is_master, "hidden") && bson_iter_as_bool(&iter)); ADD_ASSOC_BOOL_EX(retval, "is_passive", bson_iter_init_find_case(&iter, is_master, "passive") && bson_iter_as_bool(&iter)); if (bson_iter_init_find(&iter, is_master, "tags") && BSON_ITER_HOLDS_DOCUMENT(&iter)) { const uint8_t *bytes; uint32_t len; php_phongo_bson_state state = PHONGO_BSON_STATE_INITIALIZER; /* Use native arrays for debugging output */ state.map.root_type = PHONGO_TYPEMAP_NATIVE_ARRAY; state.map.document_type = PHONGO_TYPEMAP_NATIVE_ARRAY; bson_iter_document(&iter, &len, &bytes); php_phongo_bson_to_zval_ex(bytes, len, &state); #if PHP_VERSION_ID >= 70000 ADD_ASSOC_ZVAL_EX(retval, "tags", &state.zchild); #else ADD_ASSOC_ZVAL_EX(retval, "tags", state.zchild); #endif } { php_phongo_bson_state state = PHONGO_BSON_STATE_INITIALIZER; /* Use native arrays for debugging output */ state.map.root_type = PHONGO_TYPEMAP_NATIVE_ARRAY; state.map.document_type = PHONGO_TYPEMAP_NATIVE_ARRAY; php_phongo_bson_to_zval_ex(bson_get_data(is_master), is_master->len, &state); #if PHP_VERSION_ID >= 70000 ADD_ASSOC_ZVAL_EX(retval, "last_is_master", &state.zchild); #else ADD_ASSOC_ZVAL_EX(retval, "last_is_master", state.zchild); #endif } ADD_ASSOC_LONG_EX(retval, "round_trip_time", (phongo_long) mongoc_server_description_round_trip_time(sd)); } /* }}} */ void php_phongo_read_concern_to_zval(zval *retval, const mongoc_read_concern_t *read_concern) /* {{{ */ { const char *level = mongoc_read_concern_get_level(read_concern); array_init_size(retval, 1); if (level) { ADD_ASSOC_STRING(retval, "level", level); } } /* }}} */ /* If options is not an array, insert it as a field in a newly allocated array. * This may be used to convert legacy options (e.g. ReadPreference option for * an executeQuery method) into an options array. * * A pointer to the array zval will always be returned. If allocated is set to * true, php_phongo_prep_legacy_option_free() should be used to free the array * zval later. */ zval *php_phongo_prep_legacy_option(zval *options, const char *key, bool *allocated TSRMLS_DC) /* {{{ */ { *allocated = false; if (options && Z_TYPE_P(options) != IS_ARRAY) { #if PHP_VERSION_ID >= 70000 zval *new_options = ecalloc(sizeof(zval), 1); #else zval *new_options = NULL; ALLOC_INIT_ZVAL(new_options); #endif array_init_size(new_options, 1); add_assoc_zval(new_options, key, options); Z_ADDREF_P(options); *allocated = true; return new_options; } return options; } /* }}} */ void php_phongo_prep_legacy_option_free(zval *options TSRMLS_DC) /* {{{ */ { #if PHP_VERSION_ID >= 70000 zval_ptr_dtor(options); efree(options); #else zval_ptr_dtor(&options); #endif } /* }}} */ /* Prepare tagSets for BSON encoding by converting each array in the set to an * object. This ensures that empty arrays will serialize as empty documents. * * php_phongo_read_preference_tags_are_valid() handles actual validation of the * tag set structure. */ void php_phongo_read_preference_prep_tagsets(zval *tagSets TSRMLS_DC) /* {{{ */ { HashTable *ht_data; if (Z_TYPE_P(tagSets) != IS_ARRAY) { return; } ht_data = HASH_OF(tagSets); #if PHP_VERSION_ID >= 70000 { zval *tagSet; ZEND_HASH_FOREACH_VAL(ht_data, tagSet) { ZVAL_DEREF(tagSet); if (Z_TYPE_P(tagSet) == IS_ARRAY) { SEPARATE_ZVAL_NOREF(tagSet); convert_to_object(tagSet); } } ZEND_HASH_FOREACH_END(); } #else { HashPosition pos; zval **tagSet; for (zend_hash_internal_pointer_reset_ex(ht_data, &pos); zend_hash_get_current_data_ex(ht_data, (void **) &tagSet, &pos) == SUCCESS; zend_hash_move_forward_ex(ht_data, &pos)) { if (Z_TYPE_PP(tagSet) == IS_ARRAY) { SEPARATE_ZVAL_IF_NOT_REF(tagSet); convert_to_object(*tagSet); } } } #endif return; } /* }}} */ /* Checks if tags is valid to set on a mongoc_read_prefs_t. It may be null or an * array of one or more documents. */ bool php_phongo_read_preference_tags_are_valid(const bson_t *tags) /* {{{ */ { bson_iter_t iter; if (bson_empty0(tags)) { return true; } if (!bson_iter_init(&iter, tags)) { return false; } while (bson_iter_next(&iter)) { if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) { return false; } } return true; } /* }}} */ void php_phongo_read_preference_to_zval(zval *retval, const mongoc_read_prefs_t *read_prefs) /* {{{ */ { const bson_t *tags = mongoc_read_prefs_get_tags(read_prefs); mongoc_read_mode_t mode = mongoc_read_prefs_get_mode(read_prefs); array_init_size(retval, 3); switch (mode) { case MONGOC_READ_PRIMARY: ADD_ASSOC_STRING(retval, "mode", "primary"); break; case MONGOC_READ_PRIMARY_PREFERRED: ADD_ASSOC_STRING(retval, "mode", "primaryPreferred"); break; case MONGOC_READ_SECONDARY: ADD_ASSOC_STRING(retval, "mode", "secondary"); break; case MONGOC_READ_SECONDARY_PREFERRED: ADD_ASSOC_STRING(retval, "mode", "secondaryPreferred"); break; case MONGOC_READ_NEAREST: ADD_ASSOC_STRING(retval, "mode", "nearest"); break; default: /* Do nothing */ break; } if (!bson_empty0(tags)) { /* Use PHONGO_TYPEMAP_NATIVE_ARRAY for the root type since tags is an * array; however, inner documents and arrays can use the default. */ php_phongo_bson_state state = PHONGO_BSON_STATE_INITIALIZER; state.map.root_type = PHONGO_TYPEMAP_NATIVE_ARRAY; php_phongo_bson_to_zval_ex(bson_get_data(tags), tags->len, &state); #if PHP_VERSION_ID >= 70000 ADD_ASSOC_ZVAL_EX(retval, "tags", &state.zchild); #else ADD_ASSOC_ZVAL_EX(retval, "tags", state.zchild); #endif } if (mongoc_read_prefs_get_max_staleness_seconds(read_prefs) != MONGOC_NO_MAX_STALENESS) { ADD_ASSOC_LONG_EX(retval, "maxStalenessSeconds", mongoc_read_prefs_get_max_staleness_seconds(read_prefs)); } } /* }}} */ void php_phongo_write_concern_to_zval(zval *retval, const mongoc_write_concern_t *write_concern) /* {{{ */ { const char *wtag = mongoc_write_concern_get_wtag(write_concern); const int32_t w = mongoc_write_concern_get_w(write_concern); const int32_t wtimeout = mongoc_write_concern_get_wtimeout(write_concern); array_init_size(retval, 4); if (wtag) { ADD_ASSOC_STRING(retval, "w", wtag); } else if (mongoc_write_concern_get_wmajority(write_concern)) { ADD_ASSOC_STRING(retval, "w", PHONGO_WRITE_CONCERN_W_MAJORITY); } else if (w != MONGOC_WRITE_CONCERN_W_DEFAULT) { ADD_ASSOC_LONG_EX(retval, "w", w); } if (mongoc_write_concern_journal_is_set(write_concern)) { ADD_ASSOC_BOOL_EX(retval, "j", mongoc_write_concern_get_journal(write_concern)); } if (wtimeout != 0) { ADD_ASSOC_LONG_EX(retval, "wtimeout", wtimeout); } } /* }}} */ /* }}} */ static mongoc_uri_t *php_phongo_make_uri(const char *uri_string, bson_t *options TSRMLS_DC) /* {{{ */ { mongoc_uri_t *uri; bson_error_t error; uri = mongoc_uri_new_with_error(uri_string, &error); MONGOC_DEBUG("Connection string: '%s'", uri_string); if (!uri) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Failed to parse MongoDB URI: '%s'. %s.", uri_string, error.message); return NULL; } return uri; } /* }}} */ static const char *php_phongo_bson_type_to_string(bson_type_t type) /* {{{ */ { switch (type) { case BSON_TYPE_EOD: return "EOD"; case BSON_TYPE_DOUBLE: return "double"; case BSON_TYPE_UTF8: return "string"; case BSON_TYPE_DOCUMENT: return "document"; case BSON_TYPE_ARRAY: return "array"; case BSON_TYPE_BINARY: return "Binary"; case BSON_TYPE_UNDEFINED: return "undefined"; case BSON_TYPE_OID: return "ObjectId"; case BSON_TYPE_BOOL: return "boolean"; case BSON_TYPE_DATE_TIME: return "UTCDateTime"; case BSON_TYPE_NULL: return "null"; case BSON_TYPE_REGEX: return "Regex"; case BSON_TYPE_DBPOINTER: return "DBPointer"; case BSON_TYPE_CODE: return "Javascript"; case BSON_TYPE_SYMBOL: return "symbol"; case BSON_TYPE_CODEWSCOPE: return "Javascript with scope"; case BSON_TYPE_INT32: return "32-bit integer"; case BSON_TYPE_TIMESTAMP: return "Timestamp"; case BSON_TYPE_INT64: return "64-bit integer"; case BSON_TYPE_DECIMAL128: return "Decimal128"; case BSON_TYPE_MAXKEY: return "MaxKey"; case BSON_TYPE_MINKEY: return "MinKey"; default: return "unknown"; } } /* }}} */ #define PHONGO_URI_INVALID_TYPE(iter, expected) \ phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, \ "Expected %s for \"%s\" URI option, %s given", \ (expected), \ bson_iter_key(&(iter)), \ php_phongo_bson_type_to_string(bson_iter_type(&(iter)))) static bool php_phongo_apply_options_to_uri(mongoc_uri_t *uri, bson_t *options TSRMLS_DC) /* {{{ */ { bson_iter_t iter; /* Return early if there are no options to apply */ if (bson_empty0(options) || !bson_iter_init(&iter, options)) { return true; } while (bson_iter_next(&iter)) { const char *key = bson_iter_key(&iter); /* Skip read preference, read concern, and write concern options, as * those will be processed by other functions. */ if (!strcasecmp(key, MONGOC_URI_JOURNAL) || !strcasecmp(key, MONGOC_URI_MAXSTALENESSSECONDS) || !strcasecmp(key, MONGOC_URI_READCONCERNLEVEL) || !strcasecmp(key, MONGOC_URI_READPREFERENCE) || !strcasecmp(key, MONGOC_URI_READPREFERENCETAGS) || !strcasecmp(key, MONGOC_URI_SAFE) || !strcasecmp(key, MONGOC_URI_SLAVEOK) || !strcasecmp(key, MONGOC_URI_W) || !strcasecmp(key, MONGOC_URI_WTIMEOUTMS)) { continue; } if (mongoc_uri_option_is_bool(key)) { /* The option's type is not validated because bson_iter_as_bool() is * used to cast the value to a boolean. Validation may be introduced * in PHPC-990. */ if (!mongoc_uri_set_option_as_bool(uri, key, bson_iter_as_bool(&iter))) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Failed to parse \"%s\" URI option", key); return false; } continue; } if (mongoc_uri_option_is_int32(key)) { if (!BSON_ITER_HOLDS_INT32(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "32-bit integer"); return false; } if (!mongoc_uri_set_option_as_int32(uri, key, bson_iter_int32(&iter))) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Failed to parse \"%s\" URI option", key); return false; } continue; } if (mongoc_uri_option_is_utf8(key)) { if (!BSON_ITER_HOLDS_UTF8(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "string"); return false; } if (!mongoc_uri_set_option_as_utf8(uri, key, bson_iter_utf8(&iter, NULL))) { /* Assignment uses mongoc_uri_set_appname() for the "appname" * option, which validates length in addition to UTF-8 encoding. * For BC, we report the invalid string to the user. */ if (!strcasecmp(key, MONGOC_URI_APPNAME)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Invalid appname value: '%s'", bson_iter_utf8(&iter, NULL)); } else { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Failed to parse \"%s\" URI option", key); } return false; } continue; } if (!strcasecmp(key, "username")) { if (!BSON_ITER_HOLDS_UTF8(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "string"); return false; } if (!mongoc_uri_set_username(uri, bson_iter_utf8(&iter, NULL))) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Failed to parse \"%s\" URI option", key); return false; } continue; } if (!strcasecmp(key, "password")) { if (!BSON_ITER_HOLDS_UTF8(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "string"); return false; } if (!mongoc_uri_set_password(uri, bson_iter_utf8(&iter, NULL))) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Failed to parse \"%s\" URI option", key); return false; } continue; } if (!strcasecmp(key, MONGOC_URI_AUTHMECHANISM)) { if (!BSON_ITER_HOLDS_UTF8(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "string"); return false; } if (!mongoc_uri_set_auth_mechanism(uri, bson_iter_utf8(&iter, NULL))) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Failed to parse \"%s\" URI option", key); return false; } continue; } if (!strcasecmp(key, MONGOC_URI_AUTHSOURCE)) { if (!BSON_ITER_HOLDS_UTF8(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "string"); return false; } if (!mongoc_uri_set_auth_source(uri, bson_iter_utf8(&iter, NULL))) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Failed to parse \"%s\" URI option", key); return false; } continue; } if (!strcasecmp(key, MONGOC_URI_AUTHMECHANISMPROPERTIES)) { bson_t properties; uint32_t len; const uint8_t *data; if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "array or object"); return false; } bson_iter_document(&iter, &len, &data); if (!bson_init_static(&properties, data, len)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Could not initialize BSON structure for auth mechanism properties"); return false; } if (!mongoc_uri_set_mechanism_properties(uri, &properties)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Failed to parse \"%s\" URI option", key); return false; } continue; } if (!strcasecmp(key, MONGOC_URI_GSSAPISERVICENAME)) { bson_t unused, properties = BSON_INITIALIZER; if (mongoc_uri_get_mechanism_properties(uri, &unused)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "authMechanismProperties SERVICE_NAME already set, ignoring \"%s\"", key); return false; } if (!BSON_ITER_HOLDS_UTF8(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "string"); return false; } bson_append_utf8(&properties, "SERVICE_NAME", -1, bson_iter_utf8(&iter, NULL), -1); if (!mongoc_uri_set_mechanism_properties(uri, &properties)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Failed to parse \"%s\" URI option", key); bson_destroy(&properties); return false; } bson_destroy(&properties); continue; } if (!strcasecmp(key, MONGOC_URI_COMPRESSORS)) { if (!BSON_ITER_HOLDS_UTF8(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "string"); return false; } if (!mongoc_uri_set_compressors(uri, bson_iter_utf8(&iter, NULL))) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Failed to parse \"%s\" URI option", key); return false; } continue; } } return true; } /* }}} */ static bool php_phongo_apply_rc_options_to_uri(mongoc_uri_t *uri, bson_t *options TSRMLS_DC) /* {{{ */ { bson_iter_t iter; mongoc_read_concern_t *new_rc; const mongoc_read_concern_t *old_rc; if (!(old_rc = mongoc_uri_get_read_concern(uri))) { phongo_throw_exception(PHONGO_ERROR_MONGOC_FAILED TSRMLS_CC, "mongoc_uri_t does not have a read concern"); return false; } /* Return early if there are no options to apply */ if (bson_empty0(options)) { return true; } if (!bson_iter_init_find_case(&iter, options, MONGOC_URI_READCONCERNLEVEL)) { return true; } new_rc = mongoc_read_concern_copy(old_rc); if (bson_iter_init_find_case(&iter, options, MONGOC_URI_READCONCERNLEVEL)) { if (!BSON_ITER_HOLDS_UTF8(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "string"); mongoc_read_concern_destroy(new_rc); return false; } mongoc_read_concern_set_level(new_rc, bson_iter_utf8(&iter, NULL)); } mongoc_uri_set_read_concern(uri, new_rc); mongoc_read_concern_destroy(new_rc); return true; } /* }}} */ static bool php_phongo_apply_rp_options_to_uri(mongoc_uri_t *uri, bson_t *options TSRMLS_DC) /* {{{ */ { bson_iter_t iter; mongoc_read_prefs_t *new_rp; const mongoc_read_prefs_t *old_rp; if (!(old_rp = mongoc_uri_get_read_prefs_t(uri))) { phongo_throw_exception(PHONGO_ERROR_MONGOC_FAILED TSRMLS_CC, "mongoc_uri_t does not have a read preference"); return false; } /* Return early if there are no options to apply */ if (bson_empty0(options)) { return true; } if (!bson_iter_init_find_case(&iter, options, MONGOC_URI_SLAVEOK) && !bson_iter_init_find_case(&iter, options, MONGOC_URI_READPREFERENCE) && !bson_iter_init_find_case(&iter, options, MONGOC_URI_READPREFERENCETAGS) && !bson_iter_init_find_case(&iter, options, MONGOC_URI_MAXSTALENESSSECONDS) ) { return true; } new_rp = mongoc_read_prefs_copy(old_rp); if (bson_iter_init_find_case(&iter, options, MONGOC_URI_SLAVEOK)) { if (!BSON_ITER_HOLDS_BOOL(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "boolean"); mongoc_read_prefs_destroy(new_rp); return false; } if (bson_iter_bool(&iter)) { mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_SECONDARY_PREFERRED); } } if (bson_iter_init_find_case(&iter, options, MONGOC_URI_READPREFERENCE)) { const char *str; if (!BSON_ITER_HOLDS_UTF8(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "string"); mongoc_read_prefs_destroy(new_rp); return false; } str = bson_iter_utf8(&iter, NULL); if (0 == strcasecmp("primary", str)) { mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_PRIMARY); } else if (0 == strcasecmp("primarypreferred", str)) { mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_PRIMARY_PREFERRED); } else if (0 == strcasecmp("secondary", str)) { mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_SECONDARY); } else if (0 == strcasecmp("secondarypreferred", str)) { mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_SECONDARY_PREFERRED); } else if (0 == strcasecmp("nearest", str)) { mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_NEAREST); } else { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Unsupported %s value: '%s'", bson_iter_key(&iter), str); mongoc_read_prefs_destroy(new_rp); return false; } } if (bson_iter_init_find_case(&iter, options, MONGOC_URI_READPREFERENCETAGS)) { bson_t tags; uint32_t len; const uint8_t *data; if (!BSON_ITER_HOLDS_ARRAY(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "array"); mongoc_read_prefs_destroy(new_rp); return false; } bson_iter_array(&iter, &len, &data); if (!bson_init_static(&tags, data, len)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Could not initialize BSON structure for read preference tags"); mongoc_read_prefs_destroy(new_rp); return false; } if (!php_phongo_read_preference_tags_are_valid(&tags)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Read preference tags must be an array of zero or more documents"); mongoc_read_prefs_destroy(new_rp); return false; } mongoc_read_prefs_set_tags(new_rp, &tags); } if (mongoc_read_prefs_get_mode(new_rp) == MONGOC_READ_PRIMARY && !bson_empty(mongoc_read_prefs_get_tags(new_rp))) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Primary read preference mode conflicts with tags"); mongoc_read_prefs_destroy(new_rp); return false; } /* Handle maxStalenessSeconds, and make sure it is not combined with primary * readPreference */ if (bson_iter_init_find_case(&iter, options, MONGOC_URI_MAXSTALENESSSECONDS)) { int64_t max_staleness_seconds; if (!BSON_ITER_HOLDS_INT(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "integer"); mongoc_read_prefs_destroy(new_rp); return false; } max_staleness_seconds = bson_iter_as_int64(&iter); if (max_staleness_seconds != MONGOC_NO_MAX_STALENESS) { if (max_staleness_seconds < MONGOC_SMALLEST_MAX_STALENESS_SECONDS) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected maxStalenessSeconds to be >= %d, %" PRId64 " given", MONGOC_SMALLEST_MAX_STALENESS_SECONDS, max_staleness_seconds); mongoc_read_prefs_destroy(new_rp); return false; } if (max_staleness_seconds > INT32_MAX) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected maxStalenessSeconds to be <= %d, %" PRId64 " given", INT32_MAX, max_staleness_seconds); mongoc_read_prefs_destroy(new_rp); return false; } if (mongoc_read_prefs_get_mode(new_rp) == MONGOC_READ_PRIMARY) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Primary read preference mode conflicts with maxStalenessSeconds"); mongoc_read_prefs_destroy(new_rp); return false; } } mongoc_read_prefs_set_max_staleness_seconds(new_rp, max_staleness_seconds); } /* This may be redundant in light of the last check (primary with tags), but * we'll check anyway in case additional validation is implemented. */ if (!mongoc_read_prefs_is_valid(new_rp)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Read preference is not valid"); mongoc_read_prefs_destroy(new_rp); return false; } mongoc_uri_set_read_prefs_t(uri, new_rp); mongoc_read_prefs_destroy(new_rp); return true; } /* }}} */ static bool php_phongo_apply_wc_options_to_uri(mongoc_uri_t *uri, bson_t *options TSRMLS_DC) /* {{{ */ { bson_iter_t iter; int32_t wtimeoutms; mongoc_write_concern_t *new_wc; const mongoc_write_concern_t *old_wc; if (!(old_wc = mongoc_uri_get_write_concern(uri))) { phongo_throw_exception(PHONGO_ERROR_MONGOC_FAILED TSRMLS_CC, "mongoc_uri_t does not have a write concern"); return false; } /* Return early if there are no options to apply */ if (bson_empty0(options)) { return true; } if (!bson_iter_init_find_case(&iter, options, MONGOC_URI_JOURNAL) && !bson_iter_init_find_case(&iter, options, MONGOC_URI_SAFE) && !bson_iter_init_find_case(&iter, options, MONGOC_URI_W) && !bson_iter_init_find_case(&iter, options, MONGOC_URI_WTIMEOUTMS)) { return true; } wtimeoutms = mongoc_write_concern_get_wtimeout(old_wc); new_wc = mongoc_write_concern_copy(old_wc); if (bson_iter_init_find_case(&iter, options, MONGOC_URI_SAFE)) { if (!BSON_ITER_HOLDS_BOOL(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "boolean"); mongoc_write_concern_destroy(new_wc); return false; } mongoc_write_concern_set_w(new_wc, bson_iter_bool(&iter) ? 1 : MONGOC_WRITE_CONCERN_W_UNACKNOWLEDGED); } if (bson_iter_init_find_case(&iter, options, MONGOC_URI_WTIMEOUTMS)) { if (!BSON_ITER_HOLDS_INT32(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "32-bit integer"); mongoc_write_concern_destroy(new_wc); return false; } wtimeoutms = bson_iter_int32(&iter); } if (bson_iter_init_find_case(&iter, options, MONGOC_URI_JOURNAL)) { if (!BSON_ITER_HOLDS_BOOL(&iter)) { PHONGO_URI_INVALID_TYPE(iter, "boolean"); mongoc_write_concern_destroy(new_wc); return false; } mongoc_write_concern_set_journal(new_wc, bson_iter_bool(&iter)); } if (bson_iter_init_find_case(&iter, options, MONGOC_URI_W)) { if (BSON_ITER_HOLDS_INT32(&iter)) { int32_t value = bson_iter_int32(&iter); switch (value) { case MONGOC_WRITE_CONCERN_W_ERRORS_IGNORED: case MONGOC_WRITE_CONCERN_W_UNACKNOWLEDGED: mongoc_write_concern_set_w(new_wc, value); break; default: if (value > 0) { mongoc_write_concern_set_w(new_wc, value); break; } phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Unsupported w value: %d", value); mongoc_write_concern_destroy(new_wc); return false; } } else if (BSON_ITER_HOLDS_UTF8(&iter)) { const char *str = bson_iter_utf8(&iter, NULL); if (0 == strcasecmp(PHONGO_WRITE_CONCERN_W_MAJORITY, str)) { mongoc_write_concern_set_wmajority(new_wc, wtimeoutms); } else { mongoc_write_concern_set_wtag(new_wc, str); } } else { PHONGO_URI_INVALID_TYPE(iter, "32-bit integer or string"); mongoc_write_concern_destroy(new_wc); return false; } } /* Only set wtimeout if it's still applicable; otherwise, clear it. */ if (mongoc_write_concern_get_w(new_wc) > 1 || mongoc_write_concern_get_wmajority(new_wc) || mongoc_write_concern_get_wtag(new_wc)) { mongoc_write_concern_set_wtimeout(new_wc, wtimeoutms); } else { mongoc_write_concern_set_wtimeout(new_wc, 0); } if (mongoc_write_concern_get_journal(new_wc)) { int32_t w = mongoc_write_concern_get_w(new_wc); if (w == MONGOC_WRITE_CONCERN_W_UNACKNOWLEDGED || w == MONGOC_WRITE_CONCERN_W_ERRORS_IGNORED) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Journal conflicts with w value: %d", w); mongoc_write_concern_destroy(new_wc); return false; } } /* This may be redundant in light of the last check (unacknowledged w with journal), but we'll check anyway in case additional validation is implemented. */ if (!mongoc_write_concern_is_valid(new_wc)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Write concern is not valid"); mongoc_write_concern_destroy(new_wc); return false; } mongoc_uri_set_write_concern(uri, new_wc); mongoc_write_concern_destroy(new_wc); return true; } /* }}} */ #ifdef MONGOC_ENABLE_SSL static inline char *php_phongo_fetch_ssl_opt_string(zval *zoptions, const char *key, int key_len) { int plen; zend_bool pfree; char *pval, *value; pval = php_array_fetchl_string(zoptions, key, key_len, &plen, &pfree); value = pfree ? pval : estrndup(pval, plen); return value; } static mongoc_ssl_opt_t *php_phongo_make_ssl_opt(zval *zoptions TSRMLS_DC) { mongoc_ssl_opt_t *ssl_opt; if (!zoptions) { return NULL; } #if defined(MONGOC_ENABLE_SSL_SECURE_CHANNEL) || defined(MONGOC_ENABLE_SSL_SECURE_TRANSPORT) if (php_array_existsc(zoptions, "ca_dir")) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "\"ca_dir\" option is not supported by Secure Channel and Secure Transport"); return NULL; } if (php_array_existsc(zoptions, "capath")) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "\"capath\" option is not supported by Secure Channel and Secure Transport"); return NULL; } #endif #if defined(MONGOC_ENABLE_SSL_LIBRESSL) || defined(MONGOC_ENABLE_SSL_SECURE_TRANSPORT) if (php_array_existsc(zoptions, "crl_file")) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "\"crl_file\" option is not supported by LibreSSL and Secure Transport"); return NULL; } #endif ssl_opt = ecalloc(1, sizeof(mongoc_ssl_opt_t)); /* Check canonical option names first and fall back to SSL context options * for backwards compatibility. */ if (php_array_existsc(zoptions, "allow_invalid_hostname")) { ssl_opt->allow_invalid_hostname = php_array_fetchc_bool(zoptions, "allow_invalid_hostname"); } if (php_array_existsc(zoptions, "weak_cert_validation")) { ssl_opt->weak_cert_validation = php_array_fetchc_bool(zoptions, "weak_cert_validation"); } else if (php_array_existsc(zoptions, "allow_self_signed")) { ssl_opt->weak_cert_validation = php_array_fetchc_bool(zoptions, "allow_self_signed"); } if (php_array_existsc(zoptions, "pem_file")) { ssl_opt->pem_file = php_phongo_fetch_ssl_opt_string(zoptions, ZEND_STRL("pem_file")); } else if (php_array_existsc(zoptions, "local_cert")) { ssl_opt->pem_file = php_phongo_fetch_ssl_opt_string(zoptions, ZEND_STRL("local_cert")); } if (php_array_existsc(zoptions, "pem_pwd")) { ssl_opt->pem_pwd = php_phongo_fetch_ssl_opt_string(zoptions, ZEND_STRL("pem_pwd")); } else if (php_array_existsc(zoptions, "passphrase")) { ssl_opt->pem_pwd = php_phongo_fetch_ssl_opt_string(zoptions, ZEND_STRL("passphrase")); } if (php_array_existsc(zoptions, "ca_file")) { ssl_opt->ca_file = php_phongo_fetch_ssl_opt_string(zoptions, ZEND_STRL("ca_file")); } else if (php_array_existsc(zoptions, "cafile")) { ssl_opt->ca_file = php_phongo_fetch_ssl_opt_string(zoptions, ZEND_STRL("cafile")); } if (php_array_existsc(zoptions, "ca_dir")) { ssl_opt->ca_dir = php_phongo_fetch_ssl_opt_string(zoptions, ZEND_STRL("ca_dir")); } else if (php_array_existsc(zoptions, "capath")) { ssl_opt->ca_dir = php_phongo_fetch_ssl_opt_string(zoptions, ZEND_STRL("capath")); } if (php_array_existsc(zoptions, "crl_file")) { ssl_opt->crl_file = php_phongo_fetch_ssl_opt_string(zoptions, ZEND_STRL("crl_file")); } return ssl_opt; } static void php_phongo_free_ssl_opt(mongoc_ssl_opt_t *ssl_opt) { if (ssl_opt->pem_file) { str_efree(ssl_opt->pem_file); } if (ssl_opt->pem_pwd) { str_efree(ssl_opt->pem_pwd); } if (ssl_opt->ca_file) { str_efree(ssl_opt->ca_file); } if (ssl_opt->ca_dir) { str_efree(ssl_opt->ca_dir); } if (ssl_opt->crl_file) { str_efree(ssl_opt->crl_file); } efree(ssl_opt); } #endif /* APM callbacks */ static void php_phongo_dispatch_handlers(const char *name, zval *z_event) { #if PHP_VERSION_ID >= 70000 zval *value; ZEND_HASH_FOREACH_VAL(MONGODB_G(subscribers), value) { /* We can't use the zend_call_method_with_1_params macro here, as it * does a sizeof() on the name argument, which does only work with * constant names, but not with parameterized ones as it does * "sizeof(char*)" in that case. */ zend_call_method(value, NULL, NULL, name, strlen(name), NULL, 1, z_event, NULL TSRMLS_CC); } ZEND_HASH_FOREACH_END(); #else HashPosition pos; TSRMLS_FETCH(); zend_hash_internal_pointer_reset_ex(MONGODB_G(subscribers), &pos); for (;; zend_hash_move_forward_ex(MONGODB_G(subscribers), &pos)) { zval **value; if (zend_hash_get_current_data_ex(MONGODB_G(subscribers), (void **) &value, &pos) == FAILURE) { break; } /* We can't use the zend_call_method_with_1_params macro here, as it * does a sizeof() on the name argument, which does only work with * constant names, but not with parameterized ones as it does * "sizeof(char*)" in that case. */ zend_call_method(value, NULL, NULL, name, strlen(name), NULL, 1, z_event, NULL TSRMLS_CC); } #endif } static void php_phongo_command_started(const mongoc_apm_command_started_t *event) { php_phongo_commandstartedevent_t *p_event; #if PHP_VERSION_ID >= 70000 zval z_event; #else zval *z_event = NULL; #endif TSRMLS_FETCH(); /* Return early if there are no APM subscribers to notify */ if (!MONGODB_G(subscribers) || zend_hash_num_elements(MONGODB_G(subscribers)) == 0) { return; } #if PHP_VERSION_ID >= 70000 object_init_ex(&z_event, php_phongo_commandstartedevent_ce); p_event = Z_COMMANDSTARTEDEVENT_OBJ_P(&z_event); #else MAKE_STD_ZVAL(z_event); object_init_ex(z_event, php_phongo_commandstartedevent_ce); p_event = Z_COMMANDSTARTEDEVENT_OBJ_P(z_event); #endif p_event->client = mongoc_apm_command_started_get_context(event); p_event->command_name = estrdup(mongoc_apm_command_started_get_command_name(event)); p_event->server_id = mongoc_apm_command_started_get_server_id(event); p_event->operation_id = mongoc_apm_command_started_get_operation_id(event); p_event->request_id = mongoc_apm_command_started_get_request_id(event); p_event->command = bson_copy(mongoc_apm_command_started_get_command(event)); p_event->database_name = estrdup(mongoc_apm_command_started_get_database_name(event)); #if PHP_VERSION_ID >= 70000 php_phongo_dispatch_handlers("commandStarted", &z_event); #else php_phongo_dispatch_handlers("commandStarted", z_event); #endif zval_ptr_dtor(&z_event); } static void php_phongo_command_succeeded(const mongoc_apm_command_succeeded_t *event) { php_phongo_commandsucceededevent_t *p_event; #if PHP_VERSION_ID >= 70000 zval z_event; #else zval *z_event = NULL; #endif TSRMLS_FETCH(); /* Return early if there are no APM subscribers to notify */ if (!MONGODB_G(subscribers) || zend_hash_num_elements(MONGODB_G(subscribers)) == 0) { return; } #if PHP_VERSION_ID >= 70000 object_init_ex(&z_event, php_phongo_commandsucceededevent_ce); p_event = Z_COMMANDSUCCEEDEDEVENT_OBJ_P(&z_event); #else MAKE_STD_ZVAL(z_event); object_init_ex(z_event, php_phongo_commandsucceededevent_ce); p_event = Z_COMMANDSUCCEEDEDEVENT_OBJ_P(z_event); #endif p_event->client = mongoc_apm_command_succeeded_get_context(event); p_event->command_name = estrdup(mongoc_apm_command_succeeded_get_command_name(event)); p_event->server_id = mongoc_apm_command_succeeded_get_server_id(event); p_event->operation_id = mongoc_apm_command_succeeded_get_operation_id(event); p_event->request_id = mongoc_apm_command_succeeded_get_request_id(event); p_event->duration_micros = mongoc_apm_command_succeeded_get_duration(event); p_event->reply = bson_copy(mongoc_apm_command_succeeded_get_reply(event)); #if PHP_VERSION_ID >= 70000 php_phongo_dispatch_handlers("commandSucceeded", &z_event); #else php_phongo_dispatch_handlers("commandSucceeded", z_event); #endif zval_ptr_dtor(&z_event); } static void php_phongo_command_failed(const mongoc_apm_command_failed_t *event) { php_phongo_commandfailedevent_t *p_event; #if PHP_VERSION_ID >= 70000 zval z_event; #else zval *z_event = NULL; #endif bson_error_t tmp_error; zend_class_entry *default_exception_ce; TSRMLS_FETCH(); default_exception_ce = zend_exception_get_default(TSRMLS_C); /* Return early if there are no APM subscribers to notify */ if (!MONGODB_G(subscribers) || zend_hash_num_elements(MONGODB_G(subscribers)) == 0) { return; } #if PHP_VERSION_ID >= 70000 object_init_ex(&z_event, php_phongo_commandfailedevent_ce); p_event = Z_COMMANDFAILEDEVENT_OBJ_P(&z_event); #else MAKE_STD_ZVAL(z_event); object_init_ex(z_event, php_phongo_commandfailedevent_ce); p_event = Z_COMMANDFAILEDEVENT_OBJ_P(z_event); #endif p_event->client = mongoc_apm_command_failed_get_context(event); p_event->command_name = estrdup(mongoc_apm_command_failed_get_command_name(event)); p_event->server_id = mongoc_apm_command_failed_get_server_id(event); p_event->operation_id = mongoc_apm_command_failed_get_operation_id(event); p_event->request_id = mongoc_apm_command_failed_get_request_id(event); p_event->duration_micros = mongoc_apm_command_failed_get_duration(event); /* We need to process and convert the error right here, otherwise * debug_info will turn into a recursive loop, and with the wrong trace * locations */ mongoc_apm_command_failed_get_error(event, &tmp_error); { #if PHP_VERSION_ID < 70000 MAKE_STD_ZVAL(p_event->z_error); object_init_ex(p_event->z_error, phongo_exception_from_mongoc_domain(tmp_error.domain, tmp_error.code)); zend_update_property_string(default_exception_ce, p_event->z_error, ZEND_STRL("message"), tmp_error.message TSRMLS_CC); zend_update_property_long(default_exception_ce, p_event->z_error, ZEND_STRL("code"), tmp_error.code TSRMLS_CC); #else object_init_ex(&p_event->z_error, phongo_exception_from_mongoc_domain(tmp_error.domain, tmp_error.code)); zend_update_property_string(default_exception_ce, &p_event->z_error, ZEND_STRL("message"), tmp_error.message TSRMLS_CC); zend_update_property_long(default_exception_ce, &p_event->z_error, ZEND_STRL("code"), tmp_error.code TSRMLS_CC); #endif } #if PHP_VERSION_ID >= 70000 php_phongo_dispatch_handlers("commandFailed", &z_event); #else php_phongo_dispatch_handlers("commandFailed", z_event); #endif zval_ptr_dtor(&z_event); } /* Sets the callbacks for APM */ int php_phongo_set_monitoring_callbacks(mongoc_client_t *client) { int retval; mongoc_apm_callbacks_t *callbacks = mongoc_apm_callbacks_new(); mongoc_apm_set_command_started_cb(callbacks, php_phongo_command_started); mongoc_apm_set_command_succeeded_cb(callbacks, php_phongo_command_succeeded); mongoc_apm_set_command_failed_cb(callbacks, php_phongo_command_failed); retval = mongoc_client_set_apm_callbacks(client, callbacks, client); mongoc_apm_callbacks_destroy(callbacks); return retval; } /* Creates a hash for a client by concatenating the URI string with serialized * options arrays. On success, a persistent string is returned (i.e. pefree() * should be used to free it) and hash_len will be set to the string's length. * On error, an exception will have been thrown and NULL will be returned. */ static char *php_phongo_manager_make_client_hash(const char *uri_string, zval *options, zval *driverOptions, size_t *hash_len TSRMLS_DC) { char *hash = NULL; smart_str var_buf = {0}; php_serialize_data_t var_hash; #if PHP_VERSION_ID >= 70000 zval args; array_init_size(&args, 4); ADD_ASSOC_LONG_EX(&args, "pid", getpid()); ADD_ASSOC_STRING(&args, "uri", uri_string); if (options) { ADD_ASSOC_ZVAL_EX(&args, "options", options); Z_ADDREF_P(options); } else { ADD_ASSOC_NULL_EX(&args, "options"); } if (driverOptions) { ADD_ASSOC_ZVAL_EX(&args, "driverOptions", driverOptions); Z_ADDREF_P(driverOptions); } else { ADD_ASSOC_NULL_EX(&args, "driverOptions"); } PHP_VAR_SERIALIZE_INIT(var_hash); php_var_serialize(&var_buf, &args, &var_hash); PHP_VAR_SERIALIZE_DESTROY(var_hash); if (!EG(exception)) { *hash_len = ZSTR_LEN(var_buf.s); hash = pestrndup(ZSTR_VAL(var_buf.s), *hash_len, 1); } zval_ptr_dtor(&args); #else zval *args; MAKE_STD_ZVAL(args); array_init_size(args, 4); ADD_ASSOC_LONG_EX(args, "pid", getpid()); ADD_ASSOC_STRING(args, "uri", uri_string); if (options) { ADD_ASSOC_ZVAL_EX(args, "options", options); Z_ADDREF_P(options); } else { ADD_ASSOC_NULL_EX(args, "options"); } if (driverOptions) { ADD_ASSOC_ZVAL_EX(args, "driverOptions", driverOptions); Z_ADDREF_P(driverOptions); } else { ADD_ASSOC_NULL_EX(args, "driverOptions"); } PHP_VAR_SERIALIZE_INIT(var_hash); php_var_serialize(&var_buf, &args, &var_hash TSRMLS_CC); PHP_VAR_SERIALIZE_DESTROY(var_hash); if (!EG(exception)) { *hash_len = var_buf.len; hash = pestrndup(var_buf.c, *hash_len, 1); } zval_ptr_dtor(&args); #endif smart_str_free(&var_buf); return hash; } static mongoc_client_t *php_phongo_make_mongo_client(const mongoc_uri_t *uri TSRMLS_DC) /* {{{ */ { const char *mongoc_version, *bson_version; #ifdef HAVE_SYSTEM_LIBMONGOC mongoc_version = mongoc_get_version(); #else mongoc_version = "bundled"; #endif #ifdef HAVE_SYSTEM_LIBBSON bson_version = bson_get_version(); #else bson_version = "bundled"; #endif MONGOC_DEBUG("Creating Manager, phongo-%s[%s] - mongoc-%s(%s), libbson-%s(%s), php-%s", PHP_MONGODB_VERSION, PHP_MONGODB_STABILITY, MONGOC_VERSION_S, mongoc_version, BSON_VERSION_S, bson_version, PHP_VERSION ); return mongoc_client_new_from_uri(uri); } /* }}} */ static void php_phongo_persist_client(const char *hash, size_t hash_len, mongoc_client_t *client TSRMLS_DC) { php_phongo_pclient_t *pclient = (php_phongo_pclient_t *) pecalloc(1, sizeof(php_phongo_pclient_t), 1); pclient->pid = (int) getpid(); pclient->client = client; #if PHP_VERSION_ID >= 70000 zend_hash_str_update_ptr(&MONGODB_G(pclients), hash, hash_len, pclient); #else zend_hash_update(&MONGODB_G(pclients), hash, hash_len + 1, &pclient, sizeof(php_phongo_pclient_t *), NULL); #endif } static mongoc_client_t *php_phongo_find_client(const char *hash, size_t hash_len TSRMLS_DC) { #if PHP_VERSION_ID >= 70000 php_phongo_pclient_t *pclient; if ((pclient = zend_hash_str_find_ptr(&MONGODB_G(pclients), hash, hash_len)) != NULL) { return pclient->client; } #else php_phongo_pclient_t **pclient; if (zend_hash_find(&MONGODB_G(pclients), hash, hash_len + 1, (void**) &pclient) == SUCCESS) { return (*pclient)->client; } #endif return NULL; } void phongo_manager_init(php_phongo_manager_t *manager, const char *uri_string, zval *options, zval *driverOptions TSRMLS_DC) /* {{{ */ { char *hash = NULL; size_t hash_len = 0; bson_t bson_options = BSON_INITIALIZER; mongoc_uri_t *uri = NULL; #ifdef MONGOC_ENABLE_SSL mongoc_ssl_opt_t *ssl_opt = NULL; #endif if (!(hash = php_phongo_manager_make_client_hash(uri_string, options, driverOptions, &hash_len TSRMLS_CC))) { /* Exception should already have been thrown and there is nothing to free */ return; } if ((manager->client = php_phongo_find_client(hash, hash_len TSRMLS_CC))) { MONGOC_DEBUG("Found client for hash: %s\n", hash); goto cleanup; } if (options) { php_phongo_zval_to_bson(options, PHONGO_BSON_NONE, &bson_options, NULL TSRMLS_CC); } /* An exception may be thrown during BSON conversion */ if (EG(exception)) { goto cleanup; } if (!(uri = php_phongo_make_uri(uri_string, &bson_options TSRMLS_CC))) { /* Exception should already have been thrown */ goto cleanup; } if (!php_phongo_apply_options_to_uri(uri, &bson_options TSRMLS_CC) || !php_phongo_apply_rc_options_to_uri(uri, &bson_options TSRMLS_CC) || !php_phongo_apply_rp_options_to_uri(uri, &bson_options TSRMLS_CC) || !php_phongo_apply_wc_options_to_uri(uri, &bson_options TSRMLS_CC)) { /* Exception should already have been thrown */ goto cleanup; } #ifdef MONGOC_ENABLE_SSL /* Construct SSL options even if SSL is not enabled so that exceptions can * be thrown for unsupported driver options. */ ssl_opt = php_phongo_make_ssl_opt(driverOptions TSRMLS_CC); /* An exception may be thrown during SSL option creation */ if (EG(exception)) { goto cleanup; } #else if (mongoc_uri_get_ssl(uri)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Cannot create SSL client. SSL is not enabled in this build."); goto cleanup; } #endif manager->client = php_phongo_make_mongo_client(uri TSRMLS_CC); if (!manager->client) { phongo_throw_exception(PHONGO_ERROR_RUNTIME TSRMLS_CC, "Failed to create Manager from URI: '%s'", uri_string); goto cleanup; } #ifdef MONGOC_ENABLE_SSL if (ssl_opt && mongoc_uri_get_ssl(uri)) { mongoc_client_set_ssl_opts(manager->client, ssl_opt); } #endif MONGOC_DEBUG("Created client hash: %s\n", hash); php_phongo_persist_client(hash, hash_len, manager->client TSRMLS_CC); cleanup: if (hash) { pefree(hash, 1); } bson_destroy(&bson_options); if (uri) { mongoc_uri_destroy(uri); } #ifdef MONGOC_ENABLE_SSL if (ssl_opt) { php_phongo_free_ssl_opt(ssl_opt); } #endif } /* }}} */ void php_phongo_new_utcdatetime_from_epoch(zval *object, int64_t msec_since_epoch TSRMLS_DC) /* {{{ */ { php_phongo_utcdatetime_t *intern; object_init_ex(object, php_phongo_utcdatetime_ce); intern = Z_UTCDATETIME_OBJ_P(object); intern->milliseconds = msec_since_epoch; intern->initialized = true; } /* }}} */ void php_phongo_new_timestamp_from_increment_and_timestamp(zval *object, uint32_t increment, uint32_t timestamp TSRMLS_DC) /* {{{ */ { php_phongo_timestamp_t *intern; object_init_ex(object, php_phongo_timestamp_ce); intern = Z_TIMESTAMP_OBJ_P(object); intern->increment = increment; intern->timestamp = timestamp; intern->initialized = true; } /* }}} */ void php_phongo_new_javascript_from_javascript(int init, zval *object, const char *code, size_t code_len TSRMLS_DC) /* {{{ */ { php_phongo_new_javascript_from_javascript_and_scope(init, object, code, code_len, NULL TSRMLS_CC); } /* }}} */ void php_phongo_new_javascript_from_javascript_and_scope(int init, zval *object, const char *code, size_t code_len, const bson_t *scope TSRMLS_DC) /* {{{ */ { php_phongo_javascript_t *intern; if (init) { object_init_ex(object, php_phongo_javascript_ce); } intern = Z_JAVASCRIPT_OBJ_P(object); intern->code = estrndup(code, code_len); intern->code_len = code_len; intern->scope = scope ? bson_copy(scope) : NULL; } /* }}} */ void php_phongo_new_binary_from_binary_and_type(zval *object, const char *data, size_t data_len, bson_subtype_t type TSRMLS_DC) /* {{{ */ { php_phongo_binary_t *intern; object_init_ex(object, php_phongo_binary_ce); intern = Z_BINARY_OBJ_P(object); intern->data = estrndup(data, data_len); intern->data_len = data_len; intern->type = (uint8_t) type; } /* }}} */ void php_phongo_new_decimal128(zval *object, const bson_decimal128_t *decimal TSRMLS_DC) /* {{{ */ { php_phongo_decimal128_t *intern; object_init_ex(object, php_phongo_decimal128_ce); intern = Z_DECIMAL128_OBJ_P(object); memcpy(&intern->decimal, decimal, sizeof(bson_decimal128_t)); intern->initialized = true; } /* }}} */ /* qsort() compare callback for alphabetizing regex flags upon initialization */ static int php_phongo_regex_compare_flags(const void *f1, const void *f2) { if (* (const char *) f1 == * (const char *) f2) { return 0; } return (* (const char *) f1 > * (const char *) f2) ? 1 : -1; } void php_phongo_new_regex_from_regex_and_options(zval *object, const char *pattern, const char *flags TSRMLS_DC) /* {{{ */ { php_phongo_regex_t *intern; object_init_ex(object, php_phongo_regex_ce); intern = Z_REGEX_OBJ_P(object); intern->pattern_len = strlen(pattern); intern->pattern = estrndup(pattern, intern->pattern_len); intern->flags_len = strlen(flags); intern->flags = estrndup(flags, intern->flags_len); /* Ensure flags are alphabetized upon initialization. This may be removed * once CDRIVER-1883 is implemented. */ qsort((void *) intern->flags, intern->flags_len, 1, php_phongo_regex_compare_flags); } /* }}} */ void php_phongo_new_symbol(zval *object, const char *symbol, size_t symbol_len TSRMLS_DC) /* {{{ */ { php_phongo_symbol_t *intern; object_init_ex(object, php_phongo_symbol_ce); intern = Z_SYMBOL_OBJ_P(object); intern->symbol = estrndup(symbol, symbol_len); intern->symbol_len = symbol_len; } /* }}} */ void php_phongo_new_dbpointer(zval *object, const char *ref, size_t ref_len, const bson_oid_t *oid TSRMLS_DC) /* {{{ */ { php_phongo_dbpointer_t *intern; object_init_ex(object, php_phongo_dbpointer_ce); intern = Z_DBPOINTER_OBJ_P(object); intern->ref = estrndup(ref, ref_len); intern->ref_len = ref_len; bson_oid_to_string(oid, intern->id); } /* }}} */ /* {{{ Memory allocation wrappers */ static void* php_phongo_malloc(size_t num_bytes) /* {{{ */ { return pemalloc(num_bytes, 1); } /* }}} */ static void* php_phongo_calloc(size_t num_members, size_t num_bytes) /* {{{ */ { return pecalloc(num_members, num_bytes, 1); } /* }}} */ static void* php_phongo_realloc(void *mem, size_t num_bytes) { /* {{{ */ return perealloc(mem, num_bytes, 1); } /* }}} */ static void php_phongo_free(void *mem) /* {{{ */ { if (mem) { pefree(mem, 1); } } /* }}} */ /* }}} */ /* {{{ M[INIT|SHUTDOWN] R[INIT|SHUTDOWN] G[INIT|SHUTDOWN] MINFO INI */ ZEND_INI_MH(OnUpdateDebug) { void ***ctx = NULL; char *tmp_dir = NULL; TSRMLS_SET_CTX(ctx); /* Close any previously open log files */ if (MONGODB_G(debug_fd)) { if (MONGODB_G(debug_fd) != stderr && MONGODB_G(debug_fd) != stdout) { fclose(MONGODB_G(debug_fd)); } MONGODB_G(debug_fd) = NULL; } if (!new_value || (new_value && !ZSTR_VAL(new_value)[0]) || strcasecmp("0", ZSTR_VAL(new_value)) == 0 || strcasecmp("off", ZSTR_VAL(new_value)) == 0 || strcasecmp("no", ZSTR_VAL(new_value)) == 0 || strcasecmp("false", ZSTR_VAL(new_value)) == 0 ) { mongoc_log_trace_disable(); mongoc_log_set_handler(NULL, NULL); #if PHP_VERSION_ID >= 70000 return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC); #else return OnUpdateString(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC); #endif } if (strcasecmp(ZSTR_VAL(new_value), "stderr") == 0) { MONGODB_G(debug_fd) = stderr; } else if (strcasecmp(ZSTR_VAL(new_value), "stdout") == 0) { MONGODB_G(debug_fd) = stdout; } else if ( strcasecmp("1", ZSTR_VAL(new_value)) == 0 || strcasecmp("on", ZSTR_VAL(new_value)) == 0 || strcasecmp("yes", ZSTR_VAL(new_value)) == 0 || strcasecmp("true", ZSTR_VAL(new_value)) == 0 ) { tmp_dir = NULL; } else { tmp_dir = ZSTR_VAL(new_value); } if (!MONGODB_G(debug_fd)) { time_t t; int fd = -1; char *prefix; int len; phongo_char *filename; time(&t); len = spprintf(&prefix, 0, "PHONGO-%ld", t); fd = php_open_temporary_fd(tmp_dir, prefix, &filename TSRMLS_CC); if (fd != -1) { const char *path = ZSTR_VAL(filename); MONGODB_G(debug_fd) = VCWD_FOPEN(path, "a"); } efree(filename); efree(prefix); close(fd); } mongoc_log_trace_enable(); mongoc_log_set_handler(php_phongo_log, ctx); #if PHP_VERSION_ID >= 70000 return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC); #else return OnUpdateString(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC); #endif } /* {{{ INI entries */ PHP_INI_BEGIN() #if PHP_VERSION_ID >= 70000 STD_PHP_INI_ENTRY(PHONGO_DEBUG_INI, PHONGO_DEBUG_INI_DEFAULT, PHP_INI_ALL, OnUpdateDebug, debug, zend_mongodb_globals, mongodb_globals) #else { 0, PHP_INI_ALL, (char *)PHONGO_DEBUG_INI, sizeof(PHONGO_DEBUG_INI), OnUpdateDebug, (void *) XtOffsetOf(zend_mongodb_globals, debug), (void *) &mglo, NULL, (char *)PHONGO_DEBUG_INI_DEFAULT, sizeof(PHONGO_DEBUG_INI_DEFAULT)-1, NULL, 0, 0, 0, NULL }, #endif PHP_INI_END() /* }}} */ static inline void php_phongo_pclient_destroy(php_phongo_pclient_t *pclient) { /* Do not destroy mongoc_client_t objects created by other processes. This * ensures that we do not shutdown sockets that may still be in use by our * parent process (see: CDRIVER-2049). While this is a leak, we are already * in MSHUTDOWN at this point. */ if (pclient->pid == getpid()) { mongoc_client_destroy(pclient->client); } pefree(pclient, 1); } #if PHP_VERSION_ID >= 70000 static void php_phongo_pclient_dtor(zval *zv) { php_phongo_pclient_destroy((php_phongo_pclient_t *) Z_PTR_P(zv)); } #else static void php_phongo_pclient_dtor(void *pp) { php_phongo_pclient_destroy(*((php_phongo_pclient_t **) pp)); } #endif /* {{{ PHP_RINIT_FUNCTION */ PHP_RINIT_FUNCTION(mongodb) { /* Initialize HashTable for APM subscribers, which is initialized to NULL in * GINIT and destroyed and reset to NULL in RSHUTDOWN. */ if (MONGODB_G(subscribers) == NULL) { ALLOC_HASHTABLE(MONGODB_G(subscribers)); zend_hash_init(MONGODB_G(subscribers), 0, NULL, ZVAL_PTR_DTOR, 0); } return SUCCESS; } /* }}} */ /* {{{ PHP_GINIT_FUNCTION */ PHP_GINIT_FUNCTION(mongodb) { bson_mem_vtable_t bsonMemVTable = { php_phongo_malloc, php_phongo_calloc, php_phongo_realloc, php_phongo_free, }; #if PHP_VERSION_ID >= 70000 #if defined(COMPILE_DL_MONGODB) && defined(ZTS) ZEND_TSRMLS_CACHE_UPDATE(); #endif #endif memset(mongodb_globals, 0, sizeof(zend_mongodb_globals)); mongodb_globals->bsonMemVTable = bsonMemVTable; /* Initialize HashTable for persistent clients */ zend_hash_init_ex(&mongodb_globals->pclients, 0, NULL, php_phongo_pclient_dtor, 1, 0); } /* }}} */ static zend_class_entry *php_phongo_fetch_internal_class(const char *class_name, size_t class_name_len TSRMLS_DC) { #if PHP_VERSION_ID >= 70000 zend_class_entry *pce; if ((pce = zend_hash_str_find_ptr(CG(class_table), class_name, class_name_len))) { return pce; } #else zend_class_entry **pce; if (zend_hash_find(CG(class_table), class_name, class_name_len + 1, (void **) &pce) == SUCCESS) { return *pce; } #endif return NULL; } /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(mongodb) { char *php_version_string; (void)type; /* We don't care if we are loaded via dl() or extension= */ REGISTER_INI_ENTRIES(); /* Initialize libmongoc */ mongoc_init(); /* Set handshake options */ php_version_string = malloc(4 + sizeof(PHP_VERSION) + 1); snprintf(php_version_string, 4 + sizeof(PHP_VERSION) + 1, "PHP %s", PHP_VERSION); mongoc_handshake_data_append("ext-mongodb:PHP", PHP_MONGODB_VERSION, php_version_string); free(php_version_string); /* Initialize libbson */ bson_mem_set_vtable(&MONGODB_G(bsonMemVTable)); /* Prep default object handlers to be used when we register the classes */ memcpy(&phongo_std_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); phongo_std_object_handlers.clone_obj = NULL; /* phongo_std_object_handlers.get_debug_info = NULL; phongo_std_object_handlers.compare_objects = NULL; phongo_std_object_handlers.cast_object = NULL; phongo_std_object_handlers.count_elements = NULL; phongo_std_object_handlers.get_closure = NULL; */ /* Initialize zend_class_entry dependencies. * * Although DateTimeImmutable was introduced in PHP 5.5.0, * php_date_get_immutable_ce() is not available in PHP versions before * 5.5.24 and 5.6.8. * * Although JsonSerializable was introduced in PHP 5.4.0, * php_json_serializable_ce is not exported in PHP versions before 5.4.26 * and 5.5.10. For later PHP versions, looking up the class manually also * helps with distros that disable LTDL_LAZY for dlopen() (e.g. Fedora). */ php_phongo_date_immutable_ce = php_phongo_fetch_internal_class(ZEND_STRL("datetimeimmutable") TSRMLS_CC); php_phongo_json_serializable_ce = php_phongo_fetch_internal_class(ZEND_STRL("jsonserializable") TSRMLS_CC); if (php_phongo_json_serializable_ce == NULL) { zend_error(E_ERROR, "JsonSerializable class is not defined. Please ensure that the 'json' module is loaded before the 'mongodb' module."); return FAILURE; } /* Register base BSON classes first */ php_phongo_type_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_serializable_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_unserializable_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_binary_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_decimal128_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_javascript_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_maxkey_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_minkey_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_objectid_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_regex_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_timestamp_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_utcdatetime_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_binary_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_dbpointer_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_decimal128_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_javascript_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_maxkey_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_minkey_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_objectid_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_persistable_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_regex_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_symbol_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_timestamp_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_undefined_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_utcdatetime_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_bulkwrite_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_command_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_cursor_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_cursorid_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_manager_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_query_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_readconcern_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_readpreference_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_server_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_session_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_writeconcern_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_writeconcernerror_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_writeerror_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_writeresult_init_ce(INIT_FUNC_ARGS_PASSTHRU); /* Register base exception classes first */ php_phongo_exception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_runtimeexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_connectionexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_writeexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_authenticationexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_bulkwriteexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_connectiontimeoutexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_executiontimeoutexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_invalidargumentexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_logicexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_sslconnectionexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_unexpectedvalueexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); /* Register base APM classes first */ php_phongo_subscriber_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_commandsubscriber_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_commandfailedevent_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_commandstartedevent_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_commandsucceededevent_init_ce(INIT_FUNC_ARGS_PASSTHRU); REGISTER_STRING_CONSTANT("MONGODB_VERSION", (char *)PHP_MONGODB_VERSION, CONST_CS | CONST_PERSISTENT); REGISTER_STRING_CONSTANT("MONGODB_STABILITY", (char *)PHP_MONGODB_STABILITY, CONST_CS | CONST_PERSISTENT); return SUCCESS; } /* }}} */ /* {{{ PHP_MSHUTDOWN_FUNCTION */ PHP_MSHUTDOWN_FUNCTION(mongodb) { (void)type; /* We don't care if we are loaded via dl() or extension= */ /* Destroy HashTable for persistent clients. The HashTable destructor will * destroy any mongoc_client_t objects that were created by this process. */ zend_hash_destroy(&MONGODB_G(pclients)); bson_mem_restore_vtable(); /* Cleanup after libmongoc */ mongoc_cleanup(); UNREGISTER_INI_ENTRIES(); return SUCCESS; } /* }}} */ /* {{{ PHP_RSHUTDOWN_FUNCTION */ PHP_RSHUTDOWN_FUNCTION(mongodb) { /* Destroy HashTable for APM subscribers, which was initialized in RINIT */ if (MONGODB_G(subscribers)) { zend_hash_destroy(MONGODB_G(subscribers)); FREE_HASHTABLE(MONGODB_G(subscribers)); MONGODB_G(subscribers) = NULL; } return SUCCESS; } /* }}} */ /* {{{ PHP_GSHUTDOWN_FUNCTION */ PHP_GSHUTDOWN_FUNCTION(mongodb) { mongodb_globals->debug = NULL; if (mongodb_globals->debug_fd) { fclose(mongodb_globals->debug_fd); mongodb_globals->debug_fd = NULL; } } /* }}} */ /* {{{ PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(mongodb) { php_info_print_table_start(); php_info_print_table_header(2, "MongoDB support", "enabled"); php_info_print_table_row(2, "MongoDB extension version", PHP_MONGODB_VERSION); php_info_print_table_row(2, "MongoDB extension stability", PHP_MONGODB_STABILITY); #ifdef HAVE_SYSTEM_LIBBSON php_info_print_table_row(2, "libbson headers version", BSON_VERSION_S); php_info_print_table_row(2, "libbson library version", bson_get_version()); #else php_info_print_table_row(2, "libbson bundled version", BSON_VERSION_S); #endif #ifdef HAVE_SYSTEM_LIBMONGOC php_info_print_table_row(2, "libmongoc headers version", MONGOC_VERSION_S); php_info_print_table_row(2, "libmongoc library version", mongoc_get_version()); #else /* Bundled libraries, buildtime = runtime */ php_info_print_table_row(2, "libmongoc bundled version", MONGOC_VERSION_S); #endif #ifdef MONGOC_ENABLE_SSL php_info_print_table_row(2, "libmongoc SSL", "enabled"); # if defined(MONGOC_ENABLE_SSL_OPENSSL) php_info_print_table_row(2, "libmongoc SSL library", "OpenSSL"); # elif defined(MONGOC_ENABLE_SSL_LIBRESSL) php_info_print_table_row(2, "libmongoc SSL library", "LibreSSL"); # elif defined(MONGOC_ENABLE_SSL_SECURE_TRANSPORT) php_info_print_table_row(2, "libmongoc SSL library", "Secure Transport"); # elif defined(MONGOC_ENABLE_SSL_SECURE_CHANNEL) php_info_print_table_row(2, "libmongoc SSL library", "Secure Channel"); # else php_info_print_table_row(2, "libmongoc SSL library", "unknown"); # endif #else php_info_print_table_row(2, "libmongoc SSL", "disabled"); #endif #ifdef MONGOC_ENABLE_CRYPTO php_info_print_table_row(2, "libmongoc crypto", "enabled"); # if defined(MONGOC_ENABLE_CRYPTO_LIBCRYPTO) php_info_print_table_row(2, "libmongoc crypto library", "libcrypto"); # elif defined(MONGOC_ENABLE_CRYPTO_COMMON_CRYPTO) php_info_print_table_row(2, "libmongoc crypto library", "Common Crypto"); # elif defined(MONGOC_ENABLE_CRYPTO_CNG) php_info_print_table_row(2, "libmongoc crypto library", "CNG"); # else php_info_print_table_row(2, "libmongoc crypto library", "unknown"); # endif # ifdef MONGOC_ENABLE_CRYPTO_SYSTEM_PROFILE php_info_print_table_row(2, "libmongoc crypto system profile", "enabled"); # else php_info_print_table_row(2, "libmongoc crypto system profile", "disabled"); # endif #else php_info_print_table_row(2, "libmongoc crypto", "disabled"); #endif #ifdef MONGOC_ENABLE_SASL php_info_print_table_row(2, "libmongoc SASL", "enabled"); #else php_info_print_table_row(2, "libmongoc SASL", "disabled"); #endif #ifdef MONGOC_ENABLE_COMPRESSION php_info_print_table_row(2, "libmongoc compression", "enabled"); # ifdef MONGOC_ENABLE_COMPRESSION_SNAPPY php_info_print_table_row(2, "libmongoc compression snappy", "enabled"); # else php_info_print_table_row(2, "libmongoc compression snappy", "disabled"); # endif # ifdef MONGOC_ENABLE_COMPRESSION_ZLIB php_info_print_table_row(2, "libmongoc compression zlib", "enabled"); # else php_info_print_table_row(2, "libmongoc compression zlib", "disabled"); # endif #else php_info_print_table_row(2, "libmongoc compression", "disabled"); #endif php_info_print_table_end(); DISPLAY_INI_ENTRIES(); } /* }}} */ /* }}} */ /* {{{ Shared function entries for disabling constructors and unserialize() */ PHP_FUNCTION(MongoDB_disabled___construct) /* {{{ */ { phongo_throw_exception(PHONGO_ERROR_RUNTIME TSRMLS_CC, "Accessing private constructor"); } /* }}} */ PHP_FUNCTION(MongoDB_disabled___wakeup) /* {{{ */ { if (zend_parse_parameters_none() == FAILURE) { return; } phongo_throw_exception(PHONGO_ERROR_RUNTIME TSRMLS_CC, "%s", "MongoDB\\Driver objects cannot be serialized"); } /* }}} */ /* }}} */ /* {{{ mongodb_functions[] */ ZEND_BEGIN_ARG_INFO_EX(ai_bson_fromPHP, 0, 0, 1) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO(); ZEND_BEGIN_ARG_INFO_EX(ai_bson_toPHP, 0, 0, 1) ZEND_ARG_INFO(0, bson) ZEND_ARG_ARRAY_INFO(0, typemap, 0) ZEND_END_ARG_INFO(); ZEND_BEGIN_ARG_INFO_EX(ai_bson_toJSON, 0, 0, 1) ZEND_ARG_INFO(0, bson) ZEND_END_ARG_INFO(); ZEND_BEGIN_ARG_INFO_EX(ai_bson_fromJSON, 0, 0, 1) ZEND_ARG_INFO(0, json) ZEND_END_ARG_INFO(); ZEND_BEGIN_ARG_INFO_EX(ai_mongodb_driver_monitoring_subscriber, 0, 0, 1) ZEND_ARG_OBJ_INFO(0, subscriber, MongoDB\\Driver\\Monitoring\\Subscriber, 0) ZEND_END_ARG_INFO(); static const zend_function_entry mongodb_functions[] = { ZEND_NS_NAMED_FE("MongoDB\\BSON", fromPHP, PHP_FN(MongoDB_BSON_fromPHP), ai_bson_fromPHP) ZEND_NS_NAMED_FE("MongoDB\\BSON", toPHP, PHP_FN(MongoDB_BSON_toPHP), ai_bson_toPHP) ZEND_NS_NAMED_FE("MongoDB\\BSON", toJSON, PHP_FN(MongoDB_BSON_toJSON), ai_bson_toJSON) ZEND_NS_NAMED_FE("MongoDB\\BSON", toCanonicalExtendedJSON, PHP_FN(MongoDB_BSON_toCanonicalExtendedJSON), ai_bson_toJSON) ZEND_NS_NAMED_FE("MongoDB\\BSON", toRelaxedExtendedJSON, PHP_FN(MongoDB_BSON_toRelaxedExtendedJSON), ai_bson_toJSON) ZEND_NS_NAMED_FE("MongoDB\\BSON", fromJSON, PHP_FN(MongoDB_BSON_fromJSON), ai_bson_fromJSON) ZEND_NS_NAMED_FE("MongoDB\\Driver\\Monitoring", addSubscriber, PHP_FN(MongoDB_Driver_Monitoring_addSubscriber), ai_mongodb_driver_monitoring_subscriber) ZEND_NS_NAMED_FE("MongoDB\\Driver\\Monitoring", removeSubscriber, PHP_FN(MongoDB_Driver_Monitoring_removeSubscriber), ai_mongodb_driver_monitoring_subscriber) PHP_FE_END }; /* }}} */ static const zend_module_dep mongodb_deps[] = { ZEND_MOD_REQUIRED("date") ZEND_MOD_REQUIRED("json") ZEND_MOD_REQUIRED("spl") ZEND_MOD_REQUIRED("standard") ZEND_MOD_END }; /* {{{ mongodb_module_entry */ zend_module_entry mongodb_module_entry = { STANDARD_MODULE_HEADER_EX, NULL, mongodb_deps, "mongodb", mongodb_functions, PHP_MINIT(mongodb), PHP_MSHUTDOWN(mongodb), PHP_RINIT(mongodb), PHP_RSHUTDOWN(mongodb), PHP_MINFO(mongodb), PHP_MONGODB_VERSION, PHP_MODULE_GLOBALS(mongodb), PHP_GINIT(mongodb), PHP_GSHUTDOWN(mongodb), NULL, STANDARD_MODULE_PROPERTIES_EX }; /* }}} */ #ifdef COMPILE_DL_MONGODB ZEND_GET_MODULE(mongodb) #endif /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */