1 /*
2  * Copyright 2014-2017 MongoDB, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /* External libs */
18 #include "bson/bson.h"
19 #include "mongoc/mongoc.h"
20 
21 /* PHP Core stuff */
22 #include <php.h>
23 #include <php_ini.h>
24 #include <ext/standard/info.h>
25 #include <ext/standard/file.h>
26 #include <Zend/zend_hash.h>
27 #include <Zend/zend_interfaces.h>
28 #include <Zend/zend_exceptions.h>
29 #include <ext/spl/spl_iterators.h>
30 #include <ext/spl/spl_exceptions.h>
31 #include <ext/standard/php_var.h>
32 
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36 
37 #include <Zend/zend_smart_str.h>
38 
39 #ifdef MONGOC_ENABLE_CLIENT_SIDE_ENCRYPTION
40 #include <mongocrypt/mongocrypt.h>
41 #endif
42 
43 /* getpid() */
44 #if HAVE_UNISTD_H
45 #include <unistd.h>
46 #endif
47 #ifdef PHP_WIN32
48 #include <process.h>
49 #endif
50 
51 /* Stream wrapper */
52 #include <main/php_streams.h>
53 #include <main/php_network.h>
54 /* Debug log writing */
55 #include <main/php_open_temporary_file.h>
56 /* For formating timestamp in the log */
57 #include <ext/date/php_date.h>
58 /* String manipulation */
59 #include <Zend/zend_string.h>
60 /* PHP array helpers */
61 #include "php_array_api.h"
62 
63 /* Our Compatability header */
64 #include "phongo_compat.h"
65 
66 /* Our stuffz */
67 #include "php_phongo.h"
68 #include "php_bson.h"
69 #include "src/BSON/functions.h"
70 #include "src/MongoDB/Monitoring/functions.h"
71 
72 #undef MONGOC_LOG_DOMAIN
73 #define MONGOC_LOG_DOMAIN "PHONGO"
74 
75 #define PHONGO_DEBUG_INI "mongodb.debug"
76 #define PHONGO_DEBUG_INI_DEFAULT ""
77 #define PHONGO_METADATA_SEPARATOR " / "
78 #define PHONGO_METADATA_SEPARATOR_LEN (sizeof(PHONGO_METADATA_SEPARATOR) - 1)
79 #define PHONGO_METADATA_PHP_VERSION_PREFIX "PHP "
80 #define PHONGO_METADATA_PHP_VERSION_PREFIX_LEN (sizeof(PHONGO_METADATA_PHP_VERSION_PREFIX) - 1)
81 
82 ZEND_DECLARE_MODULE_GLOBALS(mongodb)
83 #if defined(ZTS) && defined(COMPILE_DL_MONGODB)
84 ZEND_TSRMLS_CACHE_DEFINE();
85 #endif
86 
87 /* Declare zend_class_entry dependencies, which are initialized in MINIT */
88 zend_class_entry* php_phongo_date_immutable_ce;
89 zend_class_entry* php_phongo_json_serializable_ce;
90 
91 php_phongo_server_description_type_map_t
92 	php_phongo_server_description_type_map[PHONGO_SERVER_DESCRIPTION_TYPES] = {
93 		{ PHONGO_SERVER_UNKNOWN, "Unknown" },
94 		{ PHONGO_SERVER_STANDALONE, "Standalone" },
95 		{ PHONGO_SERVER_MONGOS, "Mongos" },
96 		{ PHONGO_SERVER_POSSIBLE_PRIMARY, "PossiblePrimary" },
97 		{ PHONGO_SERVER_RS_PRIMARY, "RSPrimary" },
98 		{ PHONGO_SERVER_RS_SECONDARY, "RSSecondary" },
99 		{ PHONGO_SERVER_RS_ARBITER, "RSArbiter" },
100 		{ PHONGO_SERVER_RS_OTHER, "RSOther" },
101 		{ PHONGO_SERVER_RS_GHOST, "RSGhost" },
102 	};
103 
104 /* {{{ phongo_std_object_handlers */
105 zend_object_handlers phongo_std_object_handlers;
106 
phongo_get_std_object_handlers(void)107 zend_object_handlers* phongo_get_std_object_handlers(void)
108 {
109 	return &phongo_std_object_handlers;
110 }
111 /* }}} */
112 
113 /* Forward declarations */
114 static bool phongo_split_namespace(const char* namespace, char** dbname, char** cname);
115 
116 /* {{{ Error reporting and logging */
phongo_exception_from_phongo_domain(php_phongo_error_domain_t domain)117 zend_class_entry* phongo_exception_from_phongo_domain(php_phongo_error_domain_t domain)
118 {
119 	switch (domain) {
120 		case PHONGO_ERROR_INVALID_ARGUMENT:
121 			return php_phongo_invalidargumentexception_ce;
122 		case PHONGO_ERROR_LOGIC:
123 			return php_phongo_logicexception_ce;
124 		case PHONGO_ERROR_RUNTIME:
125 			return php_phongo_runtimeexception_ce;
126 		case PHONGO_ERROR_UNEXPECTED_VALUE:
127 			return php_phongo_unexpectedvalueexception_ce;
128 		case PHONGO_ERROR_MONGOC_FAILED:
129 			return php_phongo_runtimeexception_ce;
130 		case PHONGO_ERROR_CONNECTION_FAILED:
131 			return php_phongo_connectionexception_ce;
132 	}
133 
134 	MONGOC_ERROR("Resolving unknown phongo error domain: %d", domain);
135 	return php_phongo_runtimeexception_ce;
136 }
phongo_exception_from_mongoc_domain(mongoc_error_domain_t domain,mongoc_error_code_t code)137 zend_class_entry* phongo_exception_from_mongoc_domain(mongoc_error_domain_t domain, mongoc_error_code_t code)
138 {
139 	if (domain == MONGOC_ERROR_CLIENT) {
140 		if (code == MONGOC_ERROR_CLIENT_AUTHENTICATE) {
141 			return php_phongo_authenticationexception_ce;
142 		}
143 
144 		if (code == MONGOC_ERROR_CLIENT_INVALID_ENCRYPTION_ARG) {
145 			return php_phongo_invalidargumentexception_ce;
146 		}
147 	}
148 
149 	if (domain == MONGOC_ERROR_COMMAND && code == MONGOC_ERROR_COMMAND_INVALID_ARG) {
150 		return php_phongo_invalidargumentexception_ce;
151 	}
152 
153 	if (domain == MONGOC_ERROR_SERVER) {
154 		if (code == PHONGO_SERVER_ERROR_EXCEEDED_TIME_LIMIT) {
155 			return php_phongo_executiontimeoutexception_ce;
156 		}
157 
158 		return php_phongo_serverexception_ce;
159 	}
160 
161 	if (domain == MONGOC_ERROR_SERVER_SELECTION && code == MONGOC_ERROR_SERVER_SELECTION_FAILURE) {
162 		return php_phongo_connectiontimeoutexception_ce;
163 	}
164 
165 	if (domain == MONGOC_ERROR_STREAM) {
166 		if (code == MONGOC_ERROR_STREAM_SOCKET) {
167 			return php_phongo_connectiontimeoutexception_ce;
168 		}
169 
170 		return php_phongo_connectionexception_ce;
171 	}
172 
173 	if (domain == MONGOC_ERROR_WRITE_CONCERN) {
174 		return php_phongo_serverexception_ce;
175 	}
176 
177 	if (domain == MONGOC_ERROR_PROTOCOL && code == MONGOC_ERROR_PROTOCOL_BAD_WIRE_VERSION) {
178 		return php_phongo_connectionexception_ce;
179 	}
180 
181 	if (domain == MONGOC_ERROR_CLIENT_SIDE_ENCRYPTION) {
182 		return php_phongo_encryptionexception_ce;
183 	}
184 
185 	return php_phongo_runtimeexception_ce;
186 }
phongo_throw_exception(php_phongo_error_domain_t domain,const char * format,...)187 void phongo_throw_exception(php_phongo_error_domain_t domain, const char* format, ...)
188 {
189 	va_list args;
190 	char*   message;
191 	int     message_len;
192 
193 	va_start(args, format);
194 	message_len = vspprintf(&message, 0, format, args);
195 	zend_throw_exception(phongo_exception_from_phongo_domain(domain), message, 0);
196 	efree(message);
197 	va_end(args);
198 }
199 
phongo_exception_append_error_labels(zval * labels,const bson_iter_t * iter)200 static int phongo_exception_append_error_labels(zval* labels, const bson_iter_t* iter)
201 {
202 	bson_iter_t error_labels;
203 	uint32_t    label_count = 0;
204 
205 	if (!BSON_ITER_HOLDS_ARRAY(iter) || !bson_iter_recurse(iter, &error_labels)) {
206 		return label_count;
207 	}
208 
209 	while (bson_iter_next(&error_labels)) {
210 		if (BSON_ITER_HOLDS_UTF8(&error_labels)) {
211 			const char* error_label;
212 			uint32_t    error_label_len;
213 
214 			error_label = bson_iter_utf8(&error_labels, &error_label_len);
215 			ADD_NEXT_INDEX_STRINGL(labels, error_label, error_label_len);
216 			label_count++;
217 		}
218 	}
219 
220 	return label_count;
221 }
222 
phongo_exception_add_error_labels(const bson_t * reply)223 static void phongo_exception_add_error_labels(const bson_t* reply)
224 {
225 	bson_iter_t iter, child;
226 	zval        labels;
227 	uint32_t    label_count = 0;
228 
229 	if (!reply) {
230 		return;
231 	}
232 
233 	array_init(&labels);
234 
235 	if (bson_iter_init_find(&iter, reply, "errorLabels")) {
236 		label_count += phongo_exception_append_error_labels(&labels, &iter);
237 	}
238 
239 	if (bson_iter_init_find(&iter, reply, "writeConcernError") && BSON_ITER_HOLDS_DOCUMENT(&iter) &&
240 		bson_iter_recurse(&iter, &child) && bson_iter_find(&child, "errorLabels")) {
241 		label_count += phongo_exception_append_error_labels(&labels, &child);
242 	}
243 
244 	/* mongoc_write_result_t always reports writeConcernErrors in an array, so
245 	 * we must iterate this to collect WCE labels for BulkWrite replies. */
246 	if (bson_iter_init_find(&iter, reply, "writeConcernErrors") && BSON_ITER_HOLDS_ARRAY(&iter) && bson_iter_recurse(&iter, &child)) {
247 		bson_iter_t wce;
248 
249 		while (bson_iter_next(&child)) {
250 			if (BSON_ITER_HOLDS_DOCUMENT(&child) && bson_iter_recurse(&child, &wce) && bson_iter_find(&wce, "errorLabels")) {
251 				label_count += phongo_exception_append_error_labels(&labels, &wce);
252 			}
253 		}
254 	}
255 
256 	if (label_count > 0) {
257 		phongo_add_exception_prop(ZEND_STRL("errorLabels"), &labels);
258 	}
259 
260 	zval_ptr_dtor(&labels);
261 }
262 
phongo_throw_exception_from_bson_error_t_and_reply(bson_error_t * error,const bson_t * reply)263 void phongo_throw_exception_from_bson_error_t_and_reply(bson_error_t* error, const bson_t* reply)
264 {
265 	/* Server errors (other than ExceededTimeLimit) and write concern errors
266 	 * may use CommandException and report the result document for the
267 	 * failed command. For BC, ExceededTimeLimit errors will continue to use
268 	 * ExcecutionTimeoutException and omit the result document. */
269 	if (reply && ((error->domain == MONGOC_ERROR_SERVER && error->code != PHONGO_SERVER_ERROR_EXCEEDED_TIME_LIMIT) || error->domain == MONGOC_ERROR_WRITE_CONCERN)) {
270 		zval zv;
271 
272 		zend_throw_exception(php_phongo_commandexception_ce, error->message, error->code);
273 		if (php_phongo_bson_to_zval(bson_get_data(reply), reply->len, &zv)) {
274 			phongo_add_exception_prop(ZEND_STRL("resultDocument"), &zv);
275 		}
276 
277 		zval_ptr_dtor(&zv);
278 	} else {
279 		zend_throw_exception(phongo_exception_from_mongoc_domain(error->domain, error->code), error->message, error->code);
280 	}
281 	phongo_exception_add_error_labels(reply);
282 }
283 
phongo_throw_exception_from_bson_error_t(bson_error_t * error)284 void phongo_throw_exception_from_bson_error_t(bson_error_t* error)
285 {
286 	phongo_throw_exception_from_bson_error_t_and_reply(error, NULL);
287 }
288 
php_phongo_log(mongoc_log_level_t log_level,const char * log_domain,const char * message,void * user_data)289 static void php_phongo_log(mongoc_log_level_t log_level, const char* log_domain, const char* message, void* user_data)
290 {
291 	struct timeval tv;
292 	time_t         t;
293 	zend_long      tu;
294 	zend_string*   dt;
295 
296 	(void) user_data;
297 
298 	gettimeofday(&tv, NULL);
299 	t  = tv.tv_sec;
300 	tu = tv.tv_usec;
301 
302 	dt = php_format_date((char*) ZEND_STRL("Y-m-d\\TH:i:s"), t, 0);
303 
304 	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);
305 	fflush(MONGODB_G(debug_fd));
306 	efree(dt);
307 }
308 
309 /* }}} */
310 
311 /* {{{ Init objects */
phongo_cursor_init(zval * return_value,mongoc_client_t * client,mongoc_cursor_t * cursor,zval * readPreference,zval * session)312 static void phongo_cursor_init(zval* return_value, mongoc_client_t* client, mongoc_cursor_t* cursor, zval* readPreference, zval* session) /* {{{ */
313 {
314 	php_phongo_cursor_t* intern;
315 
316 	object_init_ex(return_value, php_phongo_cursor_ce);
317 
318 	intern            = Z_CURSOR_OBJ_P(return_value);
319 	intern->cursor    = cursor;
320 	intern->server_id = mongoc_cursor_get_hint(cursor);
321 	intern->client    = client;
322 	intern->advanced  = false;
323 	intern->current   = 0;
324 
325 	if (readPreference) {
326 		ZVAL_ZVAL(&intern->read_preference, readPreference, 1, 0);
327 	}
328 
329 	if (session) {
330 		ZVAL_ZVAL(&intern->session, session, 1, 0);
331 	}
332 } /* }}} */
333 
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)334 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) /* {{{ */
335 {
336 	php_phongo_cursor_t* intern;
337 
338 	phongo_cursor_init(return_value, client, cursor, readPreference, session);
339 	intern = Z_CURSOR_OBJ_P(return_value);
340 
341 	intern->database = estrdup(db);
342 
343 	ZVAL_ZVAL(&intern->command, command, 1, 0);
344 } /* }}} */
345 
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)346 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) /* {{{ */
347 {
348 	php_phongo_cursor_t* intern;
349 
350 	phongo_cursor_init(return_value, client, cursor, readPreference, session);
351 	intern = Z_CURSOR_OBJ_P(return_value);
352 
353 	/* namespace has already been validated by phongo_execute_query() */
354 	phongo_split_namespace(namespace, &intern->database, &intern->collection);
355 
356 	/* cursor has already been advanced by phongo_execute_query() calling
357 	 * phongo_cursor_advance_and_check_for_error() */
358 	intern->advanced = true;
359 
360 	ZVAL_ZVAL(&intern->query, query, 1, 0);
361 } /* }}} */
362 
phongo_server_init(zval * return_value,mongoc_client_t * client,uint32_t server_id)363 void phongo_server_init(zval* return_value, mongoc_client_t* client, uint32_t server_id) /* {{{ */
364 {
365 	php_phongo_server_t* server;
366 
367 	object_init_ex(return_value, php_phongo_server_ce);
368 
369 	server            = Z_SERVER_OBJ_P(return_value);
370 	server->server_id = server_id;
371 	server->client    = client;
372 }
373 /* }}} */
374 
phongo_session_init(zval * return_value,mongoc_client_session_t * client_session)375 void phongo_session_init(zval* return_value, mongoc_client_session_t* client_session) /* {{{ */
376 {
377 	php_phongo_session_t* session;
378 
379 	object_init_ex(return_value, php_phongo_session_ce);
380 
381 	session                 = Z_SESSION_OBJ_P(return_value);
382 	session->client_session = client_session;
383 	session->client         = mongoc_client_session_get_client(client_session);
384 }
385 /* }}} */
386 
phongo_readconcern_init(zval * return_value,const mongoc_read_concern_t * read_concern)387 void phongo_readconcern_init(zval* return_value, const mongoc_read_concern_t* read_concern) /* {{{ */
388 {
389 	php_phongo_readconcern_t* intern;
390 
391 	object_init_ex(return_value, php_phongo_readconcern_ce);
392 
393 	intern               = Z_READCONCERN_OBJ_P(return_value);
394 	intern->read_concern = mongoc_read_concern_copy(read_concern);
395 }
396 /* }}} */
397 
phongo_readpreference_init(zval * return_value,const mongoc_read_prefs_t * read_prefs)398 void phongo_readpreference_init(zval* return_value, const mongoc_read_prefs_t* read_prefs) /* {{{ */
399 {
400 	php_phongo_readpreference_t* intern;
401 
402 	object_init_ex(return_value, php_phongo_readpreference_ce);
403 
404 	intern                  = Z_READPREFERENCE_OBJ_P(return_value);
405 	intern->read_preference = mongoc_read_prefs_copy(read_prefs);
406 }
407 /* }}} */
408 
phongo_writeconcern_init(zval * return_value,const mongoc_write_concern_t * write_concern)409 void phongo_writeconcern_init(zval* return_value, const mongoc_write_concern_t* write_concern) /* {{{ */
410 {
411 	php_phongo_writeconcern_t* intern;
412 
413 	object_init_ex(return_value, php_phongo_writeconcern_ce);
414 
415 	intern                = Z_WRITECONCERN_OBJ_P(return_value);
416 	intern->write_concern = mongoc_write_concern_copy(write_concern);
417 }
418 /* }}} */
419 
phongo_writeconcernerror_init(zval * return_value,bson_t * bson)420 zend_bool phongo_writeconcernerror_init(zval* return_value, bson_t* bson) /* {{{ */
421 {
422 	bson_iter_t                     iter;
423 	php_phongo_writeconcernerror_t* intern;
424 
425 	object_init_ex(return_value, php_phongo_writeconcernerror_ce);
426 
427 	intern       = Z_WRITECONCERNERROR_OBJ_P(return_value);
428 	intern->code = 0;
429 
430 	if (bson_iter_init_find(&iter, bson, "code") && BSON_ITER_HOLDS_INT32(&iter)) {
431 		intern->code = bson_iter_int32(&iter);
432 	}
433 
434 	if (bson_iter_init_find(&iter, bson, "errmsg") && BSON_ITER_HOLDS_UTF8(&iter)) {
435 		uint32_t    errmsg_len;
436 		const char* err_msg = bson_iter_utf8(&iter, &errmsg_len);
437 
438 		intern->message = estrndup(err_msg, errmsg_len);
439 	}
440 
441 	if (bson_iter_init_find(&iter, bson, "errInfo") && BSON_ITER_HOLDS_DOCUMENT(&iter)) {
442 		uint32_t       len;
443 		const uint8_t* data = NULL;
444 
445 		bson_iter_document(&iter, &len, &data);
446 
447 		if (!php_phongo_bson_to_zval(data, len, &intern->info)) {
448 			zval_ptr_dtor(&intern->info);
449 			ZVAL_UNDEF(&intern->info);
450 
451 			return false;
452 		}
453 	}
454 
455 	return true;
456 } /* }}} */
457 
phongo_writeerror_init(zval * return_value,bson_t * bson)458 zend_bool phongo_writeerror_init(zval* return_value, bson_t* bson) /* {{{ */
459 {
460 	bson_iter_t              iter;
461 	php_phongo_writeerror_t* intern;
462 
463 	object_init_ex(return_value, php_phongo_writeerror_ce);
464 
465 	intern        = Z_WRITEERROR_OBJ_P(return_value);
466 	intern->code  = 0;
467 	intern->index = 0;
468 
469 	if (bson_iter_init_find(&iter, bson, "code") && BSON_ITER_HOLDS_INT32(&iter)) {
470 		intern->code = bson_iter_int32(&iter);
471 	}
472 
473 	if (bson_iter_init_find(&iter, bson, "errmsg") && BSON_ITER_HOLDS_UTF8(&iter)) {
474 		uint32_t    errmsg_len;
475 		const char* err_msg = bson_iter_utf8(&iter, &errmsg_len);
476 
477 		intern->message = estrndup(err_msg, errmsg_len);
478 	}
479 
480 	if (bson_iter_init_find(&iter, bson, "errInfo") && BSON_ITER_HOLDS_DOCUMENT(&iter)) {
481 		uint32_t       len;
482 		const uint8_t* data = NULL;
483 
484 		bson_iter_document(&iter, &len, &data);
485 
486 		if (!php_phongo_bson_to_zval(data, len, &intern->info)) {
487 			zval_ptr_dtor(&intern->info);
488 			ZVAL_UNDEF(&intern->info);
489 
490 			return false;
491 		}
492 	}
493 
494 	if (bson_iter_init_find(&iter, bson, "index") && BSON_ITER_HOLDS_INT32(&iter)) {
495 		intern->index = bson_iter_int32(&iter);
496 	}
497 
498 	return true;
499 } /* }}} */
500 
phongo_writeresult_init(zval * return_value,bson_t * reply,mongoc_client_t * client,uint32_t server_id)501 static php_phongo_writeresult_t* phongo_writeresult_init(zval* return_value, bson_t* reply, mongoc_client_t* client, uint32_t server_id) /* {{{ */
502 {
503 	php_phongo_writeresult_t* writeresult;
504 
505 	object_init_ex(return_value, php_phongo_writeresult_ce);
506 
507 	writeresult            = Z_WRITERESULT_OBJ_P(return_value);
508 	writeresult->reply     = bson_copy(reply);
509 	writeresult->server_id = server_id;
510 	writeresult->client    = client;
511 
512 	return writeresult;
513 } /* }}} */
514 /* }}} */
515 
516 /* {{{ CRUD */
517 /* Splits a namespace name into the database and collection names, allocated with estrdup. */
phongo_split_namespace(const char * namespace,char ** dbname,char ** cname)518 static bool phongo_split_namespace(const char* namespace, char** dbname, char** cname) /* {{{ */
519 {
520 	char* dot = strchr(namespace, '.');
521 
522 	if (!dot) {
523 		return false;
524 	}
525 
526 	if (cname) {
527 		*cname = estrdup(namespace + (dot - namespace) + 1);
528 	}
529 	if (dbname) {
530 		*dbname = estrndup(namespace, dot - namespace);
531 	}
532 
533 	return true;
534 } /* }}} */
535 
536 /* Parses the "readConcern" option for an execute method. If mongoc_opts is not
537  * NULL, the option will be appended. On error, false is returned and an
538  * exception is thrown. */
phongo_parse_read_concern(zval * options,bson_t * mongoc_opts)539 static bool phongo_parse_read_concern(zval* options, bson_t* mongoc_opts) /* {{{ */
540 {
541 	zval*                  option = NULL;
542 	mongoc_read_concern_t* read_concern;
543 
544 	if (!options) {
545 		return true;
546 	}
547 
548 	if (Z_TYPE_P(options) != IS_ARRAY) {
549 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected options to be array, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(options));
550 		return false;
551 	}
552 
553 	option = php_array_fetchc(options, "readConcern");
554 
555 	if (!option) {
556 		return true;
557 	}
558 
559 	if (Z_TYPE_P(option) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(option), php_phongo_readconcern_ce)) {
560 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"readConcern\" option to be %s, %s given", ZSTR_VAL(php_phongo_readconcern_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(option));
561 		return false;
562 	}
563 
564 	read_concern = Z_READCONCERN_OBJ_P(option)->read_concern;
565 
566 	if (mongoc_opts && !mongoc_read_concern_append(read_concern, mongoc_opts)) {
567 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Error appending \"readConcern\" option");
568 		return false;
569 	}
570 
571 	return true;
572 } /* }}} */
573 
574 /* Parses the "readPreference" option for an execute method. If zreadPreference
575  * is not NULL, it will be assigned to the option. On error, false is returned
576  * and an exception is thrown. */
phongo_parse_read_preference(zval * options,zval ** zreadPreference)577 bool phongo_parse_read_preference(zval* options, zval** zreadPreference) /* {{{ */
578 {
579 	zval* option = NULL;
580 
581 	if (!options) {
582 		return true;
583 	}
584 
585 	if (Z_TYPE_P(options) != IS_ARRAY) {
586 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected options to be array, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(options));
587 		return false;
588 	}
589 
590 	option = php_array_fetchc(options, "readPreference");
591 
592 	if (!option) {
593 		return true;
594 	}
595 
596 	if (Z_TYPE_P(option) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(option), php_phongo_readpreference_ce)) {
597 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"readPreference\" option to be %s, %s given", ZSTR_VAL(php_phongo_readpreference_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(option));
598 		return false;
599 	}
600 
601 	if (zreadPreference) {
602 		*zreadPreference = option;
603 	}
604 
605 	return true;
606 } /* }}} */
607 
608 /* Parses the "session" option for an execute method. The client object should
609  * correspond to the Manager executing the operation and will be used to ensure
610  * that the session is correctly associated with that client. If mongoc_opts is
611  * not NULL, the option will be appended. If zsession is not NULL, it will be
612  * assigned to the option. On error, false is returned and an exception is
613  * thrown. */
phongo_parse_session(zval * options,mongoc_client_t * client,bson_t * mongoc_opts,zval ** zsession)614 bool phongo_parse_session(zval* options, mongoc_client_t* client, bson_t* mongoc_opts, zval** zsession) /* {{{ */
615 {
616 	zval*                          option = NULL;
617 	const mongoc_client_session_t* client_session;
618 
619 	if (!options) {
620 		return true;
621 	}
622 
623 	if (Z_TYPE_P(options) != IS_ARRAY) {
624 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected options to be array, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(options));
625 		return false;
626 	}
627 
628 	option = php_array_fetchc(options, "session");
629 
630 	if (!option) {
631 		return true;
632 	}
633 
634 	if (Z_TYPE_P(option) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(option), php_phongo_session_ce)) {
635 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"session\" option to be %s, %s given", ZSTR_VAL(php_phongo_session_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(option));
636 		return false;
637 	}
638 
639 	client_session = Z_SESSION_OBJ_P(option)->client_session;
640 
641 	if (client != mongoc_client_session_get_client(client_session)) {
642 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Cannot use Session started from a different Manager");
643 		return false;
644 	}
645 
646 	if (mongoc_opts && !mongoc_client_session_append(client_session, mongoc_opts, NULL)) {
647 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Error appending \"session\" option");
648 		return false;
649 	}
650 
651 	if (zsession) {
652 		*zsession = option;
653 	}
654 
655 	return true;
656 } /* }}} */
657 
658 /* Parses the "writeConcern" option for an execute method. If mongoc_opts is not
659  * NULL, the option will be appended. If zwriteConcern is not NULL, it will be
660  * assigned to the option. On error, false is returned and an exception is
661  * thrown. */
phongo_parse_write_concern(zval * options,bson_t * mongoc_opts,zval ** zwriteConcern)662 static bool phongo_parse_write_concern(zval* options, bson_t* mongoc_opts, zval** zwriteConcern) /* {{{ */
663 {
664 	zval*                   option = NULL;
665 	mongoc_write_concern_t* write_concern;
666 
667 	if (!options) {
668 		return true;
669 	}
670 
671 	if (Z_TYPE_P(options) != IS_ARRAY) {
672 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected options to be array, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(options));
673 		return false;
674 	}
675 
676 	option = php_array_fetchc(options, "writeConcern");
677 
678 	if (!option) {
679 		return true;
680 	}
681 
682 	if (Z_TYPE_P(option) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(option), php_phongo_writeconcern_ce)) {
683 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"writeConcern\" option to be %s, %s given", ZSTR_VAL(php_phongo_writeconcern_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(option));
684 		return false;
685 	}
686 
687 	write_concern = Z_WRITECONCERN_OBJ_P(option)->write_concern;
688 
689 	if (mongoc_opts && !mongoc_write_concern_append(write_concern, mongoc_opts)) {
690 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Error appending \"writeConcern\" option");
691 		return false;
692 	}
693 
694 	if (zwriteConcern) {
695 		*zwriteConcern = option;
696 	}
697 
698 	return true;
699 }
700 
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)701 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) /* {{{ */
702 {
703 	bson_error_t                  error = { 0 };
704 	int                           success;
705 	bson_t                        reply = BSON_INITIALIZER;
706 	mongoc_bulk_operation_t*      bulk  = bulk_write->bulk;
707 	php_phongo_writeresult_t*     writeresult;
708 	zval*                         zwriteConcern = NULL;
709 	zval*                         zsession      = NULL;
710 	const mongoc_write_concern_t* write_concern = NULL;
711 
712 	if (bulk_write->executed) {
713 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "BulkWrite objects may only be executed once and this instance has already been executed");
714 		return false;
715 	}
716 
717 	if (!phongo_split_namespace(namespace, &bulk_write->database, &bulk_write->collection)) {
718 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "%s: %s", "Invalid namespace provided", namespace);
719 		return false;
720 	}
721 
722 	if (!phongo_parse_session(options, client, NULL, &zsession)) {
723 		/* Exception should already have been thrown */
724 		return false;
725 	}
726 
727 	if (!phongo_parse_write_concern(options, NULL, &zwriteConcern)) {
728 		/* Exception should already have been thrown */
729 		return false;
730 	}
731 
732 	/* If a write concern was not specified, libmongoc will use the client's
733 	 * write concern; however, we should still fetch it for the write result.
734 	 * Additionally, we need to check if an unacknowledged write concern would
735 	 * conflict with an explicit session. */
736 	write_concern = zwriteConcern ? Z_WRITECONCERN_OBJ_P(zwriteConcern)->write_concern : mongoc_client_get_write_concern(client);
737 
738 	if (zsession && !mongoc_write_concern_is_acknowledged(write_concern)) {
739 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Cannot combine \"session\" option with an unacknowledged write concern");
740 		return false;
741 	}
742 
743 	mongoc_bulk_operation_set_database(bulk, bulk_write->database);
744 	mongoc_bulk_operation_set_collection(bulk, bulk_write->collection);
745 	mongoc_bulk_operation_set_client(bulk, client);
746 	mongoc_bulk_operation_set_hint(bulk, server_id);
747 
748 	if (zsession) {
749 		ZVAL_ZVAL(&bulk_write->session, zsession, 1, 0);
750 		mongoc_bulk_operation_set_client_session(bulk, Z_SESSION_OBJ_P(zsession)->client_session);
751 	}
752 
753 	if (zwriteConcern) {
754 		mongoc_bulk_operation_set_write_concern(bulk, Z_WRITECONCERN_OBJ_P(zwriteConcern)->write_concern);
755 	}
756 
757 	success              = mongoc_bulk_operation_execute(bulk, &reply, &error);
758 	bulk_write->executed = true;
759 
760 	writeresult                = phongo_writeresult_init(return_value, &reply, client, mongoc_bulk_operation_get_hint(bulk));
761 	writeresult->write_concern = mongoc_write_concern_copy(write_concern);
762 
763 	/* A BulkWriteException is always thrown if mongoc_bulk_operation_execute()
764 	 * fails to ensure that the write result is accessible. If the error does
765 	 * not originate from the server (e.g. socket error), throw the appropriate
766 	 * exception first. It will be included in BulkWriteException's message and
767 	 * will also be accessible via Exception::getPrevious(). */
768 	if (!success) {
769 		if (error.domain != MONGOC_ERROR_SERVER && error.domain != MONGOC_ERROR_WRITE_CONCERN) {
770 			phongo_throw_exception_from_bson_error_t_and_reply(&error, &reply);
771 		}
772 
773 		/* Argument errors occur before command execution, so there is no need
774 		 * to layer this InvalidArgumentException behind a BulkWriteException.
775 		 * In practice, this will be a "Cannot do an empty bulk write" error. */
776 		if (error.domain == MONGOC_ERROR_COMMAND && error.code == MONGOC_ERROR_COMMAND_INVALID_ARG) {
777 			goto cleanup;
778 		}
779 
780 		if (EG(exception)) {
781 			char* message;
782 
783 			(void) spprintf(&message, 0, "Bulk write failed due to previous %s: %s", PHONGO_ZVAL_EXCEPTION_NAME(EG(exception)), error.message);
784 			zend_throw_exception(php_phongo_bulkwriteexception_ce, message, 0);
785 			efree(message);
786 		} else {
787 			zend_throw_exception(php_phongo_bulkwriteexception_ce, error.message, error.code);
788 		}
789 
790 		/* Ensure error labels are added to the final BulkWriteException. If a
791 		 * previous exception was also thrown, error labels will already have
792 		 * been added by phongo_throw_exception_from_bson_error_t_and_reply. */
793 		phongo_exception_add_error_labels(&reply);
794 		phongo_add_exception_prop(ZEND_STRL("writeResult"), return_value);
795 	}
796 
797 cleanup:
798 	bson_destroy(&reply);
799 
800 	return success;
801 } /* }}} */
802 
803 /* Advance the cursor and return whether there is an error. On error, false is
804  * returned and an exception is thrown. */
phongo_cursor_advance_and_check_for_error(mongoc_cursor_t * cursor)805 bool phongo_cursor_advance_and_check_for_error(mongoc_cursor_t* cursor) /* {{{ */
806 {
807 	const bson_t* doc = NULL;
808 
809 	if (!mongoc_cursor_next(cursor, &doc)) {
810 		bson_error_t error = { 0 };
811 
812 		/* Check for connection related exceptions */
813 		if (EG(exception)) {
814 			return false;
815 		}
816 
817 		/* Could simply be no docs, which is not an error */
818 		if (mongoc_cursor_error_document(cursor, &error, &doc)) {
819 			phongo_throw_exception_from_bson_error_t_and_reply(&error, doc);
820 			return false;
821 		}
822 	}
823 
824 	return true;
825 } /* }}} */
826 
phongo_execute_query(mongoc_client_t * client,const char * namespace,zval * zquery,zval * options,uint32_t server_id,zval * return_value)827 bool phongo_execute_query(mongoc_client_t* client, const char* namespace, zval* zquery, zval* options, uint32_t server_id, zval* return_value) /* {{{ */
828 {
829 	const php_phongo_query_t* query;
830 	bson_t                    opts = BSON_INITIALIZER;
831 	mongoc_cursor_t*          cursor;
832 	char*                     dbname;
833 	char*                     collname;
834 	mongoc_collection_t*      collection;
835 	zval*                     zreadPreference = NULL;
836 	zval*                     zsession        = NULL;
837 
838 	if (!phongo_split_namespace(namespace, &dbname, &collname)) {
839 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "%s: %s", "Invalid namespace provided", namespace);
840 		return false;
841 	}
842 	collection = mongoc_client_get_collection(client, dbname, collname);
843 	efree(dbname);
844 	efree(collname);
845 
846 	query = Z_QUERY_OBJ_P(zquery);
847 
848 	bson_copy_to(query->opts, &opts);
849 
850 	if (query->read_concern) {
851 		mongoc_collection_set_read_concern(collection, query->read_concern);
852 	}
853 
854 	if (!phongo_parse_read_preference(options, &zreadPreference)) {
855 		/* Exception should already have been thrown */
856 		mongoc_collection_destroy(collection);
857 		bson_destroy(&opts);
858 		return false;
859 	}
860 
861 	if (!phongo_parse_session(options, client, &opts, &zsession)) {
862 		/* Exception should already have been thrown */
863 		mongoc_collection_destroy(collection);
864 		bson_destroy(&opts);
865 		return false;
866 	}
867 
868 	if (!BSON_APPEND_INT32(&opts, "serverId", server_id)) {
869 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Error appending \"serverId\" option");
870 		mongoc_collection_destroy(collection);
871 		bson_destroy(&opts);
872 		return false;
873 	}
874 
875 	cursor = mongoc_collection_find_with_opts(collection, query->filter, &opts, phongo_read_preference_from_zval(zreadPreference));
876 	mongoc_collection_destroy(collection);
877 	bson_destroy(&opts);
878 
879 	/* maxAwaitTimeMS must be set before the cursor is sent */
880 	if (query->max_await_time_ms) {
881 		mongoc_cursor_set_max_await_time_ms(cursor, query->max_await_time_ms);
882 	}
883 
884 	if (!phongo_cursor_advance_and_check_for_error(cursor)) {
885 		mongoc_cursor_destroy(cursor);
886 		return false;
887 	}
888 
889 	phongo_cursor_init_for_query(return_value, client, cursor, namespace, zquery, zreadPreference, zsession);
890 
891 	return true;
892 } /* }}} */
893 
create_wrapped_command_envelope(const char * db,bson_t * reply)894 static bson_t* create_wrapped_command_envelope(const char* db, bson_t* reply)
895 {
896 	bson_t* tmp;
897 	size_t  max_ns_len = strlen(db) + 5 + 1; /* db + ".$cmd" + '\0' */
898 	char*   ns         = emalloc(max_ns_len);
899 
900 	snprintf(ns, max_ns_len, "%s.$cmd", db);
901 	tmp = BCON_NEW("cursor", "{", "id", BCON_INT64(0), "ns", BCON_UTF8(ns), "firstBatch", "[", BCON_DOCUMENT(reply), "]", "}");
902 	efree(ns);
903 
904 	return tmp;
905 }
906 
phongo_create_implicit_session(mongoc_client_t * client)907 static zval* phongo_create_implicit_session(mongoc_client_t* client) /* {{{ */
908 {
909 	mongoc_client_session_t* cs;
910 	zval*                    zsession;
911 
912 	cs = mongoc_client_start_session(client, NULL, NULL);
913 
914 	if (!cs) {
915 		return NULL;
916 	}
917 
918 	zsession = ecalloc(sizeof(zval), 1);
919 
920 	phongo_session_init(zsession, cs);
921 
922 	return zsession;
923 } /* }}} */
924 
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)925 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) /* {{{ */
926 {
927 	const php_phongo_command_t* command;
928 	bson_iter_t                 iter;
929 	bson_t                      reply;
930 	bson_error_t                error = { 0 };
931 	bson_t                      opts  = BSON_INITIALIZER;
932 	mongoc_cursor_t*            cmd_cursor;
933 	zval*                       zreadPreference                 = NULL;
934 	zval*                       zsession                        = NULL;
935 	bool                        result                          = false;
936 	bool                        free_reply                      = false;
937 	bool                        free_zsession                   = false;
938 	bool                        is_unacknowledged_write_concern = false;
939 
940 	command = Z_COMMAND_OBJ_P(zcommand);
941 
942 	if ((type & PHONGO_OPTION_READ_CONCERN) && !phongo_parse_read_concern(options, &opts)) {
943 		/* Exception should already have been thrown */
944 		goto cleanup;
945 	}
946 
947 	if ((type & PHONGO_OPTION_READ_PREFERENCE) && !phongo_parse_read_preference(options, &zreadPreference)) {
948 		/* Exception should already have been thrown */
949 		goto cleanup;
950 	}
951 
952 	if (!phongo_parse_session(options, client, &opts, &zsession)) {
953 		/* Exception should already have been thrown */
954 		goto cleanup;
955 	}
956 
957 	if (type & PHONGO_OPTION_WRITE_CONCERN) {
958 		zval* zwriteConcern = NULL;
959 
960 		if (!phongo_parse_write_concern(options, &opts, &zwriteConcern)) {
961 			/* Exception should already have been thrown */
962 			goto cleanup;
963 		}
964 
965 		/* Determine if the explicit or inherited write concern is
966 		 * unacknowledged so that we can ensure it does not conflict with an
967 		 * explicit or implicit session. */
968 		if (zwriteConcern) {
969 			is_unacknowledged_write_concern = !mongoc_write_concern_is_acknowledged(Z_WRITECONCERN_OBJ_P(zwriteConcern)->write_concern);
970 		} else if (type != PHONGO_COMMAND_RAW) {
971 			is_unacknowledged_write_concern = !mongoc_write_concern_is_acknowledged(mongoc_client_get_write_concern(client));
972 		}
973 	}
974 
975 	if (zsession && is_unacknowledged_write_concern) {
976 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Cannot combine \"session\" option with an unacknowledged write concern");
977 		goto cleanup;
978 	}
979 
980 	/* If an explicit session was not provided and the effective write concern
981 	 * is not unacknowledged, attempt to create an implicit client session
982 	 * (ignoring any errors). */
983 	if (!zsession && !is_unacknowledged_write_concern) {
984 		zsession = phongo_create_implicit_session(client);
985 
986 		if (zsession) {
987 			free_zsession = true;
988 
989 			if (!mongoc_client_session_append(Z_SESSION_OBJ_P(zsession)->client_session, &opts, NULL)) {
990 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Error appending implicit \"sessionId\" option");
991 				goto cleanup;
992 			}
993 		}
994 	}
995 
996 	if (!BSON_APPEND_INT32(&opts, "serverId", server_id)) {
997 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Error appending \"serverId\" option");
998 		goto cleanup;
999 	}
1000 
1001 	/* Although "opts" already always includes the serverId option, the read
1002 	 * preference is added to the command parts, which is relevant for mongos
1003 	 * command construction. */
1004 	switch (type) {
1005 		case PHONGO_COMMAND_RAW:
1006 			result = mongoc_client_command_with_opts(client, db, command->bson, phongo_read_preference_from_zval(zreadPreference), &opts, &reply, &error);
1007 			break;
1008 		case PHONGO_COMMAND_READ:
1009 			result = mongoc_client_read_command_with_opts(client, db, command->bson, phongo_read_preference_from_zval(zreadPreference), &opts, &reply, &error);
1010 			break;
1011 		case PHONGO_COMMAND_WRITE:
1012 			result = mongoc_client_write_command_with_opts(client, db, command->bson, &opts, &reply, &error);
1013 			break;
1014 		case PHONGO_COMMAND_READ_WRITE:
1015 			/* We can pass NULL as readPreference, as this argument was added historically, but has no function */
1016 			result = mongoc_client_read_write_command_with_opts(client, db, command->bson, NULL, &opts, &reply, &error);
1017 			break;
1018 		default:
1019 			/* Should never happen, but if it does: exception */
1020 			phongo_throw_exception(PHONGO_ERROR_LOGIC, "Type '%d' should never have been passed to phongo_execute_command, please file a bug report", type);
1021 			goto cleanup;
1022 	}
1023 
1024 	free_reply = true;
1025 
1026 	if (!result) {
1027 		phongo_throw_exception_from_bson_error_t_and_reply(&error, &reply);
1028 		goto cleanup;
1029 	}
1030 
1031 	/* According to mongoc_cursor_new_from_command_reply_with_opts(), the reply
1032 	 * bson_t is ultimately destroyed on both success and failure. */
1033 	if (bson_iter_init_find(&iter, &reply, "cursor") && BSON_ITER_HOLDS_DOCUMENT(&iter)) {
1034 		bson_t       initial_reply = BSON_INITIALIZER;
1035 		bson_t       cursor_opts   = BSON_INITIALIZER;
1036 		bson_error_t error         = { 0 };
1037 
1038 		bson_copy_to(&reply, &initial_reply);
1039 
1040 		bson_append_int32(&cursor_opts, "serverId", -1, server_id);
1041 
1042 		if (command->max_await_time_ms) {
1043 			bson_append_bool(&cursor_opts, "awaitData", -1, 1);
1044 			bson_append_int64(&cursor_opts, "maxAwaitTimeMS", -1, command->max_await_time_ms);
1045 			bson_append_bool(&cursor_opts, "tailable", -1, 1);
1046 		}
1047 
1048 		if (command->batch_size) {
1049 			bson_append_int64(&cursor_opts, "batchSize", -1, command->batch_size);
1050 		}
1051 
1052 		if (zsession && !mongoc_client_session_append(Z_SESSION_OBJ_P(zsession)->client_session, &cursor_opts, &error)) {
1053 			phongo_throw_exception_from_bson_error_t(&error);
1054 			bson_destroy(&initial_reply);
1055 			bson_destroy(&cursor_opts);
1056 			result = false;
1057 			goto cleanup;
1058 		}
1059 
1060 		cmd_cursor = mongoc_cursor_new_from_command_reply_with_opts(client, &initial_reply, &cursor_opts);
1061 		bson_destroy(&cursor_opts);
1062 	} else {
1063 		bson_t  cursor_opts   = BSON_INITIALIZER;
1064 		bson_t* wrapped_reply = create_wrapped_command_envelope(db, &reply);
1065 
1066 		bson_append_int32(&cursor_opts, "serverId", -1, server_id);
1067 		cmd_cursor = mongoc_cursor_new_from_command_reply_with_opts(client, wrapped_reply, &cursor_opts);
1068 		bson_destroy(&cursor_opts);
1069 	}
1070 
1071 	phongo_cursor_init_for_command(return_value, client, cmd_cursor, db, zcommand, zreadPreference, zsession);
1072 
1073 cleanup:
1074 	bson_destroy(&opts);
1075 
1076 	if (free_reply) {
1077 		bson_destroy(&reply);
1078 	}
1079 
1080 	if (free_zsession) {
1081 		zval_ptr_dtor(zsession);
1082 		efree(zsession);
1083 	}
1084 
1085 	return result;
1086 } /* }}} */
1087 /* }}} */
1088 
1089 /* {{{ mongoc types from from_zval */
phongo_write_concern_from_zval(zval * zwrite_concern)1090 const mongoc_write_concern_t* phongo_write_concern_from_zval(zval* zwrite_concern) /* {{{ */
1091 {
1092 	if (zwrite_concern) {
1093 		php_phongo_writeconcern_t* intern = Z_WRITECONCERN_OBJ_P(zwrite_concern);
1094 
1095 		if (intern) {
1096 			return intern->write_concern;
1097 		}
1098 	}
1099 
1100 	return NULL;
1101 } /* }}} */
1102 
phongo_read_concern_from_zval(zval * zread_concern)1103 const mongoc_read_concern_t* phongo_read_concern_from_zval(zval* zread_concern) /* {{{ */
1104 {
1105 	if (zread_concern) {
1106 		php_phongo_readconcern_t* intern = Z_READCONCERN_OBJ_P(zread_concern);
1107 
1108 		if (intern) {
1109 			return intern->read_concern;
1110 		}
1111 	}
1112 
1113 	return NULL;
1114 } /* }}} */
1115 
phongo_read_preference_from_zval(zval * zread_preference)1116 const mongoc_read_prefs_t* phongo_read_preference_from_zval(zval* zread_preference) /* {{{ */
1117 {
1118 	if (zread_preference) {
1119 		php_phongo_readpreference_t* intern = Z_READPREFERENCE_OBJ_P(zread_preference);
1120 
1121 		if (intern) {
1122 			return intern->read_preference;
1123 		}
1124 	}
1125 
1126 	return NULL;
1127 } /* }}} */
1128 /* }}} */
1129 
1130 /* {{{ phongo zval from mongoc types */
php_phongo_server_description_type(mongoc_server_description_t * sd)1131 php_phongo_server_description_type_t php_phongo_server_description_type(mongoc_server_description_t* sd)
1132 {
1133 	const char* name = mongoc_server_description_type(sd);
1134 	int         i;
1135 
1136 	for (i = 0; i < PHONGO_SERVER_DESCRIPTION_TYPES; i++) {
1137 		if (!strcmp(name, php_phongo_server_description_type_map[i].name)) {
1138 			return php_phongo_server_description_type_map[i].type;
1139 		}
1140 	}
1141 
1142 	return PHONGO_SERVER_UNKNOWN;
1143 }
1144 
php_phongo_server_to_zval(zval * retval,mongoc_server_description_t * sd)1145 bool php_phongo_server_to_zval(zval* retval, mongoc_server_description_t* sd) /* {{{ */
1146 {
1147 	mongoc_host_list_t* host      = mongoc_server_description_host(sd);
1148 	const bson_t*       is_master = mongoc_server_description_ismaster(sd);
1149 	bson_iter_t         iter;
1150 
1151 	array_init(retval);
1152 
1153 	ADD_ASSOC_STRING(retval, "host", host->host);
1154 	ADD_ASSOC_LONG_EX(retval, "port", host->port);
1155 	ADD_ASSOC_LONG_EX(retval, "type", php_phongo_server_description_type(sd));
1156 	ADD_ASSOC_BOOL_EX(retval, "is_primary", !strcmp(mongoc_server_description_type(sd), php_phongo_server_description_type_map[PHONGO_SERVER_RS_PRIMARY].name));
1157 	ADD_ASSOC_BOOL_EX(retval, "is_secondary", !strcmp(mongoc_server_description_type(sd), php_phongo_server_description_type_map[PHONGO_SERVER_RS_SECONDARY].name));
1158 	ADD_ASSOC_BOOL_EX(retval, "is_arbiter", !strcmp(mongoc_server_description_type(sd), php_phongo_server_description_type_map[PHONGO_SERVER_RS_ARBITER].name));
1159 	ADD_ASSOC_BOOL_EX(retval, "is_hidden", bson_iter_init_find_case(&iter, is_master, "hidden") && bson_iter_as_bool(&iter));
1160 	ADD_ASSOC_BOOL_EX(retval, "is_passive", bson_iter_init_find_case(&iter, is_master, "passive") && bson_iter_as_bool(&iter));
1161 
1162 	if (bson_iter_init_find(&iter, is_master, "tags") && BSON_ITER_HOLDS_DOCUMENT(&iter)) {
1163 		const uint8_t*        bytes;
1164 		uint32_t              len;
1165 		php_phongo_bson_state state;
1166 
1167 		PHONGO_BSON_INIT_DEBUG_STATE(state);
1168 		bson_iter_document(&iter, &len, &bytes);
1169 		if (!php_phongo_bson_to_zval_ex(bytes, len, &state)) {
1170 			zval_ptr_dtor(&state.zchild);
1171 			return false;
1172 		}
1173 
1174 		ADD_ASSOC_ZVAL_EX(retval, "tags", &state.zchild);
1175 	}
1176 
1177 	{
1178 		php_phongo_bson_state state;
1179 
1180 		PHONGO_BSON_INIT_DEBUG_STATE(state);
1181 
1182 		if (!php_phongo_bson_to_zval_ex(bson_get_data(is_master), is_master->len, &state)) {
1183 			zval_ptr_dtor(&state.zchild);
1184 			return false;
1185 		}
1186 
1187 		ADD_ASSOC_ZVAL_EX(retval, "last_is_master", &state.zchild);
1188 	}
1189 	ADD_ASSOC_LONG_EX(retval, "round_trip_time", (zend_long) mongoc_server_description_round_trip_time(sd));
1190 
1191 	return true;
1192 } /* }}} */
1193 
php_phongo_read_concern_to_zval(zval * retval,const mongoc_read_concern_t * read_concern)1194 void php_phongo_read_concern_to_zval(zval* retval, const mongoc_read_concern_t* read_concern) /* {{{ */
1195 {
1196 	const char* level = mongoc_read_concern_get_level(read_concern);
1197 
1198 	array_init_size(retval, 1);
1199 
1200 	if (level) {
1201 		ADD_ASSOC_STRING(retval, "level", level);
1202 	}
1203 } /* }}} */
1204 
1205 /* If options is not an array, insert it as a field in a newly allocated array.
1206  * This may be used to convert legacy options (e.g. ReadPreference option for
1207  * an executeQuery method) into an options array.
1208  *
1209  * A pointer to the array zval will always be returned. If allocated is set to
1210  * true, php_phongo_prep_legacy_option_free() should be used to free the array
1211  * zval later. */
php_phongo_prep_legacy_option(zval * options,const char * key,bool * allocated)1212 zval* php_phongo_prep_legacy_option(zval* options, const char* key, bool* allocated) /* {{{ */
1213 {
1214 	*allocated = false;
1215 
1216 	if (options && Z_TYPE_P(options) != IS_ARRAY) {
1217 		zval* new_options = ecalloc(sizeof(zval), 1);
1218 
1219 		array_init_size(new_options, 1);
1220 		add_assoc_zval(new_options, key, options);
1221 		Z_ADDREF_P(options);
1222 		*allocated = true;
1223 
1224 		return new_options;
1225 	}
1226 
1227 	return options;
1228 } /* }}} */
1229 
php_phongo_prep_legacy_option_free(zval * options)1230 void php_phongo_prep_legacy_option_free(zval* options) /* {{{ */
1231 {
1232 	zval_ptr_dtor(options);
1233 	efree(options);
1234 } /* }}} */
1235 
1236 /* Prepare tagSets for BSON encoding by converting each array in the set to an
1237  * object. This ensures that empty arrays will serialize as empty documents.
1238  *
1239  * php_phongo_read_preference_tags_are_valid() handles actual validation of the
1240  * tag set structure. */
php_phongo_read_preference_prep_tagsets(zval * tagSets)1241 void php_phongo_read_preference_prep_tagsets(zval* tagSets) /* {{{ */
1242 {
1243 	HashTable* ht_data;
1244 	zval*      tagSet;
1245 
1246 	if (Z_TYPE_P(tagSets) != IS_ARRAY) {
1247 		return;
1248 	}
1249 
1250 	ht_data = HASH_OF(tagSets);
1251 
1252 	ZEND_HASH_FOREACH_VAL_IND(ht_data, tagSet)
1253 	{
1254 		ZVAL_DEREF(tagSet);
1255 		if (Z_TYPE_P(tagSet) == IS_ARRAY) {
1256 			SEPARATE_ZVAL_NOREF(tagSet);
1257 			convert_to_object(tagSet);
1258 		}
1259 	}
1260 	ZEND_HASH_FOREACH_END();
1261 } /* }}} */
1262 
1263 /* Checks if tags is valid to set on a mongoc_read_prefs_t. It may be null or an
1264  * array of one or more documents. */
php_phongo_read_preference_tags_are_valid(const bson_t * tags)1265 bool php_phongo_read_preference_tags_are_valid(const bson_t* tags) /* {{{ */
1266 {
1267 	bson_iter_t iter;
1268 
1269 	if (bson_empty0(tags)) {
1270 		return true;
1271 	}
1272 
1273 	if (!bson_iter_init(&iter, tags)) {
1274 		return false;
1275 	}
1276 
1277 	while (bson_iter_next(&iter)) {
1278 		if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) {
1279 			return false;
1280 		}
1281 	}
1282 
1283 	return true;
1284 } /* }}} */
1285 
php_phongo_write_concern_to_zval(zval * retval,const mongoc_write_concern_t * write_concern)1286 void php_phongo_write_concern_to_zval(zval* retval, const mongoc_write_concern_t* write_concern) /* {{{ */
1287 {
1288 	const char*   wtag     = mongoc_write_concern_get_wtag(write_concern);
1289 	const int32_t w        = mongoc_write_concern_get_w(write_concern);
1290 	const int64_t wtimeout = mongoc_write_concern_get_wtimeout_int64(write_concern);
1291 
1292 	array_init_size(retval, 4);
1293 
1294 	if (wtag) {
1295 		ADD_ASSOC_STRING(retval, "w", wtag);
1296 	} else if (mongoc_write_concern_get_wmajority(write_concern)) {
1297 		ADD_ASSOC_STRING(retval, "w", PHONGO_WRITE_CONCERN_W_MAJORITY);
1298 	} else if (w != MONGOC_WRITE_CONCERN_W_DEFAULT) {
1299 		ADD_ASSOC_LONG_EX(retval, "w", w);
1300 	}
1301 
1302 	if (mongoc_write_concern_journal_is_set(write_concern)) {
1303 		ADD_ASSOC_BOOL_EX(retval, "j", mongoc_write_concern_get_journal(write_concern));
1304 	}
1305 
1306 	if (wtimeout != 0) {
1307 #if SIZEOF_ZEND_LONG == 4
1308 		if (wtimeout > INT32_MAX || wtimeout < INT32_MIN) {
1309 			ADD_ASSOC_INT64_AS_STRING(&retval, "wtimeout", wtimeout);
1310 		} else {
1311 			ADD_ASSOC_LONG_EX(retval, "wtimeout", wtimeout);
1312 		}
1313 #else
1314 		ADD_ASSOC_LONG_EX(retval, "wtimeout", wtimeout);
1315 #endif
1316 	}
1317 } /* }}} */
1318 /* }}} */
1319 
php_phongo_make_uri(const char * uri_string)1320 static mongoc_uri_t* php_phongo_make_uri(const char* uri_string) /* {{{ */
1321 {
1322 	mongoc_uri_t* uri;
1323 	bson_error_t  error = { 0 };
1324 
1325 	uri = mongoc_uri_new_with_error(uri_string, &error);
1326 	MONGOC_DEBUG("Connection string: '%s'", uri_string);
1327 
1328 	if (!uri) {
1329 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse MongoDB URI: '%s'. %s.", uri_string, error.message);
1330 		return NULL;
1331 	}
1332 
1333 	return uri;
1334 } /* }}} */
1335 
php_phongo_bson_type_to_string(bson_type_t type)1336 static const char* php_phongo_bson_type_to_string(bson_type_t type) /* {{{ */
1337 {
1338 	switch (type) {
1339 		case BSON_TYPE_EOD:
1340 			return "EOD";
1341 		case BSON_TYPE_DOUBLE:
1342 			return "double";
1343 		case BSON_TYPE_UTF8:
1344 			return "string";
1345 		case BSON_TYPE_DOCUMENT:
1346 			return "document";
1347 		case BSON_TYPE_ARRAY:
1348 			return "array";
1349 		case BSON_TYPE_BINARY:
1350 			return "Binary";
1351 		case BSON_TYPE_UNDEFINED:
1352 			return "undefined";
1353 		case BSON_TYPE_OID:
1354 			return "ObjectId";
1355 		case BSON_TYPE_BOOL:
1356 			return "boolean";
1357 		case BSON_TYPE_DATE_TIME:
1358 			return "UTCDateTime";
1359 		case BSON_TYPE_NULL:
1360 			return "null";
1361 		case BSON_TYPE_REGEX:
1362 			return "Regex";
1363 		case BSON_TYPE_DBPOINTER:
1364 			return "DBPointer";
1365 		case BSON_TYPE_CODE:
1366 			return "Javascript";
1367 		case BSON_TYPE_SYMBOL:
1368 			return "symbol";
1369 		case BSON_TYPE_CODEWSCOPE:
1370 			return "Javascript with scope";
1371 		case BSON_TYPE_INT32:
1372 			return "32-bit integer";
1373 		case BSON_TYPE_TIMESTAMP:
1374 			return "Timestamp";
1375 		case BSON_TYPE_INT64:
1376 			return "64-bit integer";
1377 		case BSON_TYPE_DECIMAL128:
1378 			return "Decimal128";
1379 		case BSON_TYPE_MAXKEY:
1380 			return "MaxKey";
1381 		case BSON_TYPE_MINKEY:
1382 			return "MinKey";
1383 		default:
1384 			return "unknown";
1385 	}
1386 } /* }}} */
1387 
1388 #define PHONGO_URI_INVALID_TYPE(iter, expected)        \
1389 	phongo_throw_exception(                            \
1390 		PHONGO_ERROR_INVALID_ARGUMENT,                 \
1391 		"Expected %s for \"%s\" URI option, %s given", \
1392 		(expected),                                    \
1393 		bson_iter_key(&(iter)),                        \
1394 		php_phongo_bson_type_to_string(bson_iter_type(&(iter))))
1395 
php_phongo_uri_finalize_auth(mongoc_uri_t * uri)1396 static bool php_phongo_uri_finalize_auth(mongoc_uri_t* uri) /* {{{ */
1397 {
1398 	const bson_t* credentials = mongoc_uri_get_credentials(uri);
1399 	bson_iter_t   iter;
1400 	const char*   source       = NULL;
1401 	const char*   username     = mongoc_uri_get_username(uri);
1402 	bool          require_auth = username != NULL;
1403 
1404 	if (bson_iter_init_find_case(&iter, credentials, MONGOC_URI_AUTHSOURCE)) {
1405 		source       = bson_iter_utf8(&iter, NULL);
1406 		require_auth = true;
1407 	}
1408 
1409 	/* authSource with GSSAPI or X509 should always be external */
1410 	if (mongoc_uri_get_auth_mechanism(uri)) {
1411 		if (!strcasecmp(mongoc_uri_get_auth_mechanism(uri), "GSSAPI") ||
1412 			!strcasecmp(mongoc_uri_get_auth_mechanism(uri), "MONGODB-X509")) {
1413 
1414 			if (source) {
1415 				if (strcasecmp(source, "$external")) {
1416 					phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse URI options: GSSAPI and X509 require \"$external\" authSource.");
1417 					return false;
1418 				}
1419 			} else {
1420 				mongoc_uri_set_auth_source(uri, "$external");
1421 			}
1422 		}
1423 
1424 		/* Mechanisms other than MONGODB-X509 and MONGODB-AWS require a username */
1425 		if (strcasecmp(mongoc_uri_get_auth_mechanism(uri), "MONGODB-X509") &&
1426 			strcasecmp(mongoc_uri_get_auth_mechanism(uri), "MONGODB-AWS")) {
1427 			if (!mongoc_uri_get_username(uri) ||
1428 				!strcmp(mongoc_uri_get_username(uri), "")) {
1429 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse URI options: '%s' authentication mechanism requires username.", mongoc_uri_get_auth_mechanism(uri));
1430 				return false;
1431 			}
1432 		}
1433 
1434 		/* MONGODB-X509 errors if a password is supplied. */
1435 		if (!strcasecmp(mongoc_uri_get_auth_mechanism(uri), "MONGODB-X509")) {
1436 			if (mongoc_uri_get_password(uri)) {
1437 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse URI options: X509 authentication mechanism does not accept a password.");
1438 				return false;
1439 			}
1440 		}
1441 	} else if (require_auth) {
1442 		if (source && strcmp(source, "$external") != 0 && (!username || strcmp(username, "") == 0)) {
1443 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse URI options: Default authentication mechanism requires username.");
1444 			return false;
1445 		}
1446 	}
1447 
1448 	return true;
1449 } /* }}} */
1450 
php_phongo_uri_finalize_directconnection(mongoc_uri_t * uri)1451 static bool php_phongo_uri_finalize_directconnection(mongoc_uri_t* uri) /* {{{ */
1452 {
1453 	const mongoc_host_list_t* hosts;
1454 
1455 	if (!mongoc_uri_get_option_as_bool(uri, MONGOC_URI_DIRECTCONNECTION, false)) {
1456 		return true;
1457 	}
1458 
1459 	/* Per the URI options spec, directConnection conflicts with multiple hosts
1460 	 * and SRV URIs, which may resolve to multiple hosts. */
1461 	if (!strncmp(mongoc_uri_get_string(uri), "mongodb+srv://", 14)) {
1462 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse URI options: SRV URI not allowed with directConnection option.");
1463 		return false;
1464 	}
1465 
1466 	hosts = mongoc_uri_get_hosts(uri);
1467 
1468 	if (hosts && hosts->next) {
1469 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse URI options: Multiple seeds not allowed with directConnection option.");
1470 		return false;
1471 	}
1472 
1473 	return true;
1474 } /* }}} */
1475 
php_phongo_uri_finalize_tls(mongoc_uri_t * uri)1476 static bool php_phongo_uri_finalize_tls(mongoc_uri_t* uri) /* {{{ */
1477 {
1478 	const bson_t* options;
1479 	bson_iter_t   iter;
1480 
1481 	if (!(options = mongoc_uri_get_options(uri))) {
1482 		return true;
1483 	}
1484 
1485 	if (bson_iter_init_find_case(&iter, options, MONGOC_URI_TLSINSECURE) &&
1486 		(bson_iter_init_find_case(&iter, options, MONGOC_URI_TLSALLOWINVALIDCERTIFICATES) ||
1487 		 bson_iter_init_find_case(&iter, options, MONGOC_URI_TLSALLOWINVALIDHOSTNAMES) ||
1488 		 bson_iter_init_find_case(&iter, options, MONGOC_URI_TLSDISABLEOCSPENDPOINTCHECK) ||
1489 		 bson_iter_init_find_case(&iter, options, MONGOC_URI_TLSDISABLECERTIFICATEREVOCATIONCHECK))) {
1490 		phongo_throw_exception(
1491 			PHONGO_ERROR_INVALID_ARGUMENT,
1492 			"Failed to parse URI options: %s may not be combined with %s, %s, %s, or %s.",
1493 			MONGOC_URI_TLSINSECURE,
1494 			MONGOC_URI_TLSALLOWINVALIDCERTIFICATES,
1495 			MONGOC_URI_TLSALLOWINVALIDHOSTNAMES,
1496 			MONGOC_URI_TLSDISABLEOCSPENDPOINTCHECK,
1497 			MONGOC_URI_TLSDISABLECERTIFICATEREVOCATIONCHECK);
1498 		return false;
1499 	}
1500 
1501 	if (bson_iter_init_find_case(&iter, options, MONGOC_URI_TLSALLOWINVALIDCERTIFICATES) &&
1502 		(bson_iter_init_find_case(&iter, options, MONGOC_URI_TLSDISABLEOCSPENDPOINTCHECK) ||
1503 		 bson_iter_init_find_case(&iter, options, MONGOC_URI_TLSDISABLECERTIFICATEREVOCATIONCHECK))) {
1504 		phongo_throw_exception(
1505 			PHONGO_ERROR_INVALID_ARGUMENT,
1506 			"Failed to parse URI options: %s may not be combined with %s or %s.",
1507 			MONGOC_URI_TLSALLOWINVALIDCERTIFICATES,
1508 			MONGOC_URI_TLSDISABLEOCSPENDPOINTCHECK,
1509 			MONGOC_URI_TLSDISABLECERTIFICATEREVOCATIONCHECK);
1510 		return false;
1511 	}
1512 
1513 	return true;
1514 } /* }}} */
1515 
php_phongo_apply_options_to_uri(mongoc_uri_t * uri,bson_t * options)1516 static bool php_phongo_apply_options_to_uri(mongoc_uri_t* uri, bson_t* options) /* {{{ */
1517 {
1518 	bson_iter_t iter;
1519 
1520 	/* Return early if there are no options to apply */
1521 	if (bson_empty0(options) || !bson_iter_init(&iter, options)) {
1522 		return true;
1523 	}
1524 
1525 	while (bson_iter_next(&iter)) {
1526 		const char* key = bson_iter_key(&iter);
1527 
1528 		/* Skip read preference, read concern, and write concern options, as
1529 		 * those will be processed by other functions. */
1530 		if (!strcasecmp(key, MONGOC_URI_JOURNAL) ||
1531 			!strcasecmp(key, MONGOC_URI_MAXSTALENESSSECONDS) ||
1532 			!strcasecmp(key, MONGOC_URI_READCONCERNLEVEL) ||
1533 			!strcasecmp(key, MONGOC_URI_READPREFERENCE) ||
1534 			!strcasecmp(key, MONGOC_URI_READPREFERENCETAGS) ||
1535 			!strcasecmp(key, MONGOC_URI_SAFE) ||
1536 			!strcasecmp(key, MONGOC_URI_SLAVEOK) ||
1537 			!strcasecmp(key, MONGOC_URI_W) ||
1538 			!strcasecmp(key, MONGOC_URI_WTIMEOUTMS)) {
1539 
1540 			continue;
1541 		}
1542 
1543 		if (mongoc_uri_option_is_bool(key)) {
1544 			/* The option's type is not validated because bson_iter_as_bool() is
1545 			 * used to cast the value to a boolean. Validation may be introduced
1546 			 * in PHPC-990. */
1547 			if (!mongoc_uri_set_option_as_bool(uri, key, bson_iter_as_bool(&iter))) {
1548 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" URI option", key);
1549 				return false;
1550 			}
1551 
1552 			continue;
1553 		}
1554 
1555 		if (mongoc_uri_option_is_int32(key)) {
1556 			if (!BSON_ITER_HOLDS_INT32(&iter)) {
1557 				PHONGO_URI_INVALID_TYPE(iter, "32-bit integer");
1558 				return false;
1559 			}
1560 
1561 			if (!mongoc_uri_set_option_as_int32(uri, key, bson_iter_int32(&iter))) {
1562 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" URI option", key);
1563 				return false;
1564 			}
1565 
1566 			continue;
1567 		}
1568 
1569 		if (mongoc_uri_option_is_utf8(key)) {
1570 			if (!BSON_ITER_HOLDS_UTF8(&iter)) {
1571 				PHONGO_URI_INVALID_TYPE(iter, "string");
1572 				return false;
1573 			}
1574 
1575 			if (!strcasecmp(key, MONGOC_URI_REPLICASET) && !strcmp("", bson_iter_utf8(&iter, NULL))) {
1576 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Value for URI option \"%s\" cannot be empty string.", key);
1577 				return false;
1578 			}
1579 
1580 			if (!mongoc_uri_set_option_as_utf8(uri, key, bson_iter_utf8(&iter, NULL))) {
1581 				/* Assignment uses mongoc_uri_set_appname() for the "appname"
1582 				 * option, which validates length in addition to UTF-8 encoding.
1583 				 * For BC, we report the invalid string to the user. */
1584 				if (!strcasecmp(key, MONGOC_URI_APPNAME)) {
1585 					phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Invalid appname value: '%s'", bson_iter_utf8(&iter, NULL));
1586 				} else {
1587 					phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" URI option", key);
1588 				}
1589 				return false;
1590 			}
1591 
1592 			continue;
1593 		}
1594 
1595 		if (!strcasecmp(key, "username")) {
1596 			if (!BSON_ITER_HOLDS_UTF8(&iter)) {
1597 				PHONGO_URI_INVALID_TYPE(iter, "string");
1598 				return false;
1599 			}
1600 
1601 			if (!mongoc_uri_set_username(uri, bson_iter_utf8(&iter, NULL))) {
1602 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" URI option", key);
1603 				return false;
1604 			}
1605 
1606 			continue;
1607 		}
1608 
1609 		if (!strcasecmp(key, "password")) {
1610 			if (!BSON_ITER_HOLDS_UTF8(&iter)) {
1611 				PHONGO_URI_INVALID_TYPE(iter, "string");
1612 				return false;
1613 			}
1614 
1615 			if (!mongoc_uri_set_password(uri, bson_iter_utf8(&iter, NULL))) {
1616 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" URI option", key);
1617 				return false;
1618 			}
1619 
1620 			continue;
1621 		}
1622 
1623 		if (!strcasecmp(key, MONGOC_URI_AUTHMECHANISM)) {
1624 			if (!BSON_ITER_HOLDS_UTF8(&iter)) {
1625 				PHONGO_URI_INVALID_TYPE(iter, "string");
1626 				return false;
1627 			}
1628 
1629 			if (!mongoc_uri_set_auth_mechanism(uri, bson_iter_utf8(&iter, NULL))) {
1630 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" URI option", key);
1631 				return false;
1632 			}
1633 
1634 			continue;
1635 		}
1636 
1637 		if (!strcasecmp(key, MONGOC_URI_AUTHSOURCE)) {
1638 			if (!BSON_ITER_HOLDS_UTF8(&iter)) {
1639 				PHONGO_URI_INVALID_TYPE(iter, "string");
1640 				return false;
1641 			}
1642 
1643 			if (!mongoc_uri_set_auth_source(uri, bson_iter_utf8(&iter, NULL))) {
1644 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" URI option", key);
1645 				return false;
1646 			}
1647 
1648 			continue;
1649 		}
1650 
1651 		if (!strcasecmp(key, MONGOC_URI_AUTHMECHANISMPROPERTIES)) {
1652 			bson_t         properties;
1653 			uint32_t       len;
1654 			const uint8_t* data;
1655 
1656 			if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) {
1657 				PHONGO_URI_INVALID_TYPE(iter, "array or object");
1658 				return false;
1659 			}
1660 
1661 			bson_iter_document(&iter, &len, &data);
1662 
1663 			if (!bson_init_static(&properties, data, len)) {
1664 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Could not initialize BSON structure for auth mechanism properties");
1665 				return false;
1666 			}
1667 
1668 			if (!mongoc_uri_set_mechanism_properties(uri, &properties)) {
1669 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" URI option", key);
1670 				return false;
1671 			}
1672 
1673 			continue;
1674 		}
1675 
1676 		if (!strcasecmp(key, MONGOC_URI_GSSAPISERVICENAME)) {
1677 			bson_t unused, properties = BSON_INITIALIZER;
1678 
1679 			if (mongoc_uri_get_mechanism_properties(uri, &unused)) {
1680 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "authMechanismProperties SERVICE_NAME already set, ignoring \"%s\"", key);
1681 				return false;
1682 			}
1683 
1684 			if (!BSON_ITER_HOLDS_UTF8(&iter)) {
1685 				PHONGO_URI_INVALID_TYPE(iter, "string");
1686 				return false;
1687 			}
1688 
1689 			bson_append_utf8(&properties, "SERVICE_NAME", -1, bson_iter_utf8(&iter, NULL), -1);
1690 
1691 			if (!mongoc_uri_set_mechanism_properties(uri, &properties)) {
1692 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" URI option", key);
1693 				bson_destroy(&properties);
1694 				return false;
1695 			}
1696 
1697 			bson_destroy(&properties);
1698 
1699 			continue;
1700 		}
1701 
1702 		if (!strcasecmp(key, MONGOC_URI_COMPRESSORS)) {
1703 			if (!BSON_ITER_HOLDS_UTF8(&iter)) {
1704 				PHONGO_URI_INVALID_TYPE(iter, "string");
1705 				return false;
1706 			}
1707 
1708 			if (!mongoc_uri_set_compressors(uri, bson_iter_utf8(&iter, NULL))) {
1709 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" URI option", key);
1710 				return false;
1711 			}
1712 
1713 			continue;
1714 		}
1715 	}
1716 
1717 	/* Validate any interactions between URI options */
1718 	if (!php_phongo_uri_finalize_auth(uri)) {
1719 		/* Exception should already have been thrown */
1720 		return false;
1721 	}
1722 
1723 	if (!php_phongo_uri_finalize_directconnection(uri)) {
1724 		/* Exception should already have been thrown */
1725 		return false;
1726 	}
1727 
1728 	return true;
1729 } /* }}} */
1730 
php_phongo_apply_rc_options_to_uri(mongoc_uri_t * uri,bson_t * options)1731 static bool php_phongo_apply_rc_options_to_uri(mongoc_uri_t* uri, bson_t* options) /* {{{ */
1732 {
1733 	bson_iter_t                  iter;
1734 	mongoc_read_concern_t*       new_rc;
1735 	const mongoc_read_concern_t* old_rc;
1736 
1737 	if (!(old_rc = mongoc_uri_get_read_concern(uri))) {
1738 		phongo_throw_exception(PHONGO_ERROR_MONGOC_FAILED, "mongoc_uri_t does not have a read concern");
1739 
1740 		return false;
1741 	}
1742 
1743 	/* Return early if there are no options to apply */
1744 	if (bson_empty0(options) || !bson_iter_init(&iter, options)) {
1745 		return true;
1746 	}
1747 
1748 	new_rc = mongoc_read_concern_copy(old_rc);
1749 
1750 	while (bson_iter_next(&iter)) {
1751 		const char* key = bson_iter_key(&iter);
1752 
1753 		if (!strcasecmp(key, MONGOC_URI_READCONCERNLEVEL)) {
1754 			if (!BSON_ITER_HOLDS_UTF8(&iter)) {
1755 				PHONGO_URI_INVALID_TYPE(iter, "string");
1756 				mongoc_read_concern_destroy(new_rc);
1757 
1758 				return false;
1759 			}
1760 
1761 			mongoc_read_concern_set_level(new_rc, bson_iter_utf8(&iter, NULL));
1762 		}
1763 	}
1764 
1765 	mongoc_uri_set_read_concern(uri, new_rc);
1766 	mongoc_read_concern_destroy(new_rc);
1767 
1768 	return true;
1769 } /* }}} */
1770 
php_phongo_apply_rp_options_to_uri(mongoc_uri_t * uri,bson_t * options)1771 static bool php_phongo_apply_rp_options_to_uri(mongoc_uri_t* uri, bson_t* options) /* {{{ */
1772 {
1773 	bson_iter_t                iter;
1774 	mongoc_read_prefs_t*       new_rp;
1775 	const mongoc_read_prefs_t* old_rp;
1776 	bool                       ignore_slaveok = false;
1777 
1778 	if (!(old_rp = mongoc_uri_get_read_prefs_t(uri))) {
1779 		phongo_throw_exception(PHONGO_ERROR_MONGOC_FAILED, "mongoc_uri_t does not have a read preference");
1780 
1781 		return false;
1782 	}
1783 
1784 	/* Return early if there are no options to apply */
1785 	if (bson_empty0(options) || !bson_iter_init(&iter, options)) {
1786 		return true;
1787 	}
1788 
1789 	new_rp = mongoc_read_prefs_copy(old_rp);
1790 
1791 	while (bson_iter_next(&iter)) {
1792 		const char* key = bson_iter_key(&iter);
1793 
1794 		if (!ignore_slaveok && !strcasecmp(key, MONGOC_URI_SLAVEOK)) {
1795 			if (!BSON_ITER_HOLDS_BOOL(&iter)) {
1796 				PHONGO_URI_INVALID_TYPE(iter, "boolean");
1797 				mongoc_read_prefs_destroy(new_rp);
1798 
1799 				return false;
1800 			}
1801 
1802 			if (bson_iter_bool(&iter)) {
1803 				mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_SECONDARY_PREFERRED);
1804 			}
1805 		}
1806 
1807 		if (!strcasecmp(key, MONGOC_URI_READPREFERENCE)) {
1808 			const char* str;
1809 
1810 			if (!BSON_ITER_HOLDS_UTF8(&iter)) {
1811 				PHONGO_URI_INVALID_TYPE(iter, "string");
1812 				mongoc_read_prefs_destroy(new_rp);
1813 
1814 				return false;
1815 			}
1816 
1817 			str = bson_iter_utf8(&iter, NULL);
1818 
1819 			if (0 == strcasecmp("primary", str)) {
1820 				mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_PRIMARY);
1821 			} else if (0 == strcasecmp("primarypreferred", str)) {
1822 				mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_PRIMARY_PREFERRED);
1823 			} else if (0 == strcasecmp("secondary", str)) {
1824 				mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_SECONDARY);
1825 			} else if (0 == strcasecmp("secondarypreferred", str)) {
1826 				mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_SECONDARY_PREFERRED);
1827 			} else if (0 == strcasecmp("nearest", str)) {
1828 				mongoc_read_prefs_set_mode(new_rp, MONGOC_READ_NEAREST);
1829 			} else {
1830 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Unsupported %s value: '%s'", bson_iter_key(&iter), str);
1831 				mongoc_read_prefs_destroy(new_rp);
1832 
1833 				return false;
1834 			}
1835 
1836 			ignore_slaveok = true;
1837 		}
1838 
1839 		if (!strcasecmp(key, MONGOC_URI_READPREFERENCETAGS)) {
1840 			bson_t         tags;
1841 			uint32_t       len;
1842 			const uint8_t* data;
1843 
1844 			if (!BSON_ITER_HOLDS_ARRAY(&iter)) {
1845 				PHONGO_URI_INVALID_TYPE(iter, "array");
1846 				mongoc_read_prefs_destroy(new_rp);
1847 
1848 				return false;
1849 			}
1850 
1851 			bson_iter_array(&iter, &len, &data);
1852 
1853 			if (!bson_init_static(&tags, data, len)) {
1854 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Could not initialize BSON structure for read preference tags");
1855 				mongoc_read_prefs_destroy(new_rp);
1856 
1857 				return false;
1858 			}
1859 
1860 			if (!php_phongo_read_preference_tags_are_valid(&tags)) {
1861 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Read preference tags must be an array of zero or more documents");
1862 				mongoc_read_prefs_destroy(new_rp);
1863 
1864 				return false;
1865 			}
1866 
1867 			mongoc_read_prefs_set_tags(new_rp, &tags);
1868 		}
1869 
1870 		if (!strcasecmp(key, MONGOC_URI_MAXSTALENESSSECONDS)) {
1871 			int64_t max_staleness_seconds;
1872 
1873 			if (!BSON_ITER_HOLDS_INT(&iter)) {
1874 				PHONGO_URI_INVALID_TYPE(iter, "integer");
1875 				mongoc_read_prefs_destroy(new_rp);
1876 
1877 				return false;
1878 			}
1879 
1880 			max_staleness_seconds = bson_iter_as_int64(&iter);
1881 
1882 			if (max_staleness_seconds != MONGOC_NO_MAX_STALENESS) {
1883 
1884 				if (max_staleness_seconds < MONGOC_SMALLEST_MAX_STALENESS_SECONDS) {
1885 					phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected maxStalenessSeconds to be >= %d, %" PRId64 " given", MONGOC_SMALLEST_MAX_STALENESS_SECONDS, max_staleness_seconds);
1886 					mongoc_read_prefs_destroy(new_rp);
1887 
1888 					return false;
1889 				}
1890 
1891 				if (max_staleness_seconds > INT32_MAX) {
1892 					phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected maxStalenessSeconds to be <= %d, %" PRId64 " given", INT32_MAX, max_staleness_seconds);
1893 					mongoc_read_prefs_destroy(new_rp);
1894 
1895 					return false;
1896 				}
1897 
1898 				if (mongoc_read_prefs_get_mode(new_rp) == MONGOC_READ_PRIMARY) {
1899 					phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Primary read preference mode conflicts with maxStalenessSeconds");
1900 					mongoc_read_prefs_destroy(new_rp);
1901 
1902 					return false;
1903 				}
1904 			}
1905 
1906 			mongoc_read_prefs_set_max_staleness_seconds(new_rp, max_staleness_seconds);
1907 		}
1908 	}
1909 
1910 	if (mongoc_read_prefs_get_mode(new_rp) == MONGOC_READ_PRIMARY &&
1911 		!bson_empty(mongoc_read_prefs_get_tags(new_rp))) {
1912 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Primary read preference mode conflicts with tags");
1913 		mongoc_read_prefs_destroy(new_rp);
1914 
1915 		return false;
1916 	}
1917 
1918 	/* Make sure maxStalenessSeconds is not combined with primary readPreference */
1919 	if (mongoc_read_prefs_get_mode(new_rp) == MONGOC_READ_PRIMARY &&
1920 		mongoc_read_prefs_get_max_staleness_seconds(new_rp) != MONGOC_NO_MAX_STALENESS) {
1921 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Primary read preference mode conflicts with maxStalenessSeconds");
1922 		mongoc_read_prefs_destroy(new_rp);
1923 
1924 		return false;
1925 	}
1926 
1927 	/* This may be redundant in light of the previous checks (primary with tags
1928 	 * or maxStalenessSeconds), but we'll check anyway in case additional
1929 	 * validation is implemented. */
1930 	if (!mongoc_read_prefs_is_valid(new_rp)) {
1931 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Read preference is not valid");
1932 		mongoc_read_prefs_destroy(new_rp);
1933 
1934 		return false;
1935 	}
1936 
1937 	mongoc_uri_set_read_prefs_t(uri, new_rp);
1938 	mongoc_read_prefs_destroy(new_rp);
1939 
1940 	return true;
1941 } /* }}} */
1942 
php_phongo_apply_wc_options_to_uri(mongoc_uri_t * uri,bson_t * options)1943 static bool php_phongo_apply_wc_options_to_uri(mongoc_uri_t* uri, bson_t* options) /* {{{ */
1944 {
1945 	bson_iter_t                   iter;
1946 	mongoc_write_concern_t*       new_wc;
1947 	const mongoc_write_concern_t* old_wc;
1948 	bool                          ignore_safe = false;
1949 
1950 	if (!(old_wc = mongoc_uri_get_write_concern(uri))) {
1951 		phongo_throw_exception(PHONGO_ERROR_MONGOC_FAILED, "mongoc_uri_t does not have a write concern");
1952 
1953 		return false;
1954 	}
1955 
1956 	/* Return early if there are no options to apply */
1957 	if (bson_empty0(options) || !bson_iter_init(&iter, options)) {
1958 		return true;
1959 	}
1960 
1961 	new_wc = mongoc_write_concern_copy(old_wc);
1962 
1963 	while (bson_iter_next(&iter)) {
1964 		const char* key = bson_iter_key(&iter);
1965 
1966 		if (!ignore_safe && !strcasecmp(key, MONGOC_URI_SAFE)) {
1967 			if (!BSON_ITER_HOLDS_BOOL(&iter)) {
1968 				PHONGO_URI_INVALID_TYPE(iter, "boolean");
1969 				mongoc_write_concern_destroy(new_wc);
1970 
1971 				return false;
1972 			}
1973 
1974 			mongoc_write_concern_set_w(new_wc, bson_iter_bool(&iter) ? 1 : MONGOC_WRITE_CONCERN_W_UNACKNOWLEDGED);
1975 		}
1976 
1977 		if (!strcasecmp(key, MONGOC_URI_WTIMEOUTMS)) {
1978 			int64_t wtimeout;
1979 
1980 			if (!BSON_ITER_HOLDS_INT(&iter)) {
1981 				PHONGO_URI_INVALID_TYPE(iter, "integer");
1982 				mongoc_write_concern_destroy(new_wc);
1983 
1984 				return false;
1985 			}
1986 
1987 			wtimeout = bson_iter_as_int64(&iter);
1988 
1989 			if (wtimeout < 0) {
1990 				phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected wtimeoutMS to be >= 0, %" PRId64 " given", wtimeout);
1991 				mongoc_write_concern_destroy(new_wc);
1992 
1993 				return false;
1994 			}
1995 
1996 			mongoc_write_concern_set_wtimeout_int64(new_wc, wtimeout);
1997 		}
1998 
1999 		if (!strcasecmp(key, MONGOC_URI_JOURNAL)) {
2000 			if (!BSON_ITER_HOLDS_BOOL(&iter)) {
2001 				PHONGO_URI_INVALID_TYPE(iter, "boolean");
2002 				mongoc_write_concern_destroy(new_wc);
2003 
2004 				return false;
2005 			}
2006 
2007 			mongoc_write_concern_set_journal(new_wc, bson_iter_bool(&iter));
2008 		}
2009 
2010 		if (!strcasecmp(key, MONGOC_URI_W)) {
2011 			if (BSON_ITER_HOLDS_INT32(&iter)) {
2012 				int32_t value = bson_iter_int32(&iter);
2013 
2014 				switch (value) {
2015 					case MONGOC_WRITE_CONCERN_W_ERRORS_IGNORED:
2016 					case MONGOC_WRITE_CONCERN_W_UNACKNOWLEDGED:
2017 						mongoc_write_concern_set_w(new_wc, value);
2018 						break;
2019 
2020 					default:
2021 						if (value > 0) {
2022 							mongoc_write_concern_set_w(new_wc, value);
2023 							break;
2024 						}
2025 						phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Unsupported w value: %d", value);
2026 						mongoc_write_concern_destroy(new_wc);
2027 
2028 						return false;
2029 				}
2030 			} else if (BSON_ITER_HOLDS_UTF8(&iter)) {
2031 				const char* str = bson_iter_utf8(&iter, NULL);
2032 
2033 				if (0 == strcasecmp(PHONGO_WRITE_CONCERN_W_MAJORITY, str)) {
2034 					mongoc_write_concern_set_w(new_wc, MONGOC_WRITE_CONCERN_W_MAJORITY);
2035 				} else {
2036 					mongoc_write_concern_set_wtag(new_wc, str);
2037 				}
2038 			} else {
2039 				PHONGO_URI_INVALID_TYPE(iter, "32-bit integer or string");
2040 				mongoc_write_concern_destroy(new_wc);
2041 
2042 				return false;
2043 			}
2044 
2045 			ignore_safe = true;
2046 		}
2047 	}
2048 
2049 	if (mongoc_write_concern_get_journal(new_wc)) {
2050 		int32_t w = mongoc_write_concern_get_w(new_wc);
2051 
2052 		if (w == MONGOC_WRITE_CONCERN_W_UNACKNOWLEDGED || w == MONGOC_WRITE_CONCERN_W_ERRORS_IGNORED) {
2053 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Journal conflicts with w value: %d", w);
2054 			mongoc_write_concern_destroy(new_wc);
2055 
2056 			return false;
2057 		}
2058 	}
2059 
2060 	/* This may be redundant in light of the last check (unacknowledged w with
2061 	   journal), but we'll check anyway in case additional validation is
2062 	   implemented. */
2063 	if (!mongoc_write_concern_is_valid(new_wc)) {
2064 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Write concern is not valid");
2065 		mongoc_write_concern_destroy(new_wc);
2066 
2067 		return false;
2068 	}
2069 
2070 	mongoc_uri_set_write_concern(uri, new_wc);
2071 	mongoc_write_concern_destroy(new_wc);
2072 
2073 	return true;
2074 } /* }}} */
2075 
2076 #ifdef MONGOC_ENABLE_SSL
2077 
php_phongo_mongoc_ssl_opts_from_uri(mongoc_ssl_opt_t * ssl_opt,mongoc_uri_t * uri,bool * any_ssl_option_set)2078 static void php_phongo_mongoc_ssl_opts_from_uri(mongoc_ssl_opt_t* ssl_opt, mongoc_uri_t* uri, bool* any_ssl_option_set)
2079 {
2080 	bool        insecure = mongoc_uri_get_option_as_bool(uri, MONGOC_URI_TLSINSECURE, false);
2081 	const char* pem_file = mongoc_uri_get_option_as_utf8(uri, MONGOC_URI_TLSCERTIFICATEKEYFILE, NULL);
2082 	const char* pem_pwd  = mongoc_uri_get_option_as_utf8(uri, MONGOC_URI_TLSCERTIFICATEKEYFILEPASSWORD, NULL);
2083 	const char* ca_file  = mongoc_uri_get_option_as_utf8(uri, MONGOC_URI_TLSCAFILE, NULL);
2084 
2085 	ssl_opt->pem_file               = pem_file ? estrdup(pem_file) : NULL;
2086 	ssl_opt->pem_pwd                = pem_pwd ? estrdup(pem_pwd) : NULL;
2087 	ssl_opt->ca_file                = ca_file ? estrdup(ca_file) : NULL;
2088 	ssl_opt->weak_cert_validation   = mongoc_uri_get_option_as_bool(uri, MONGOC_URI_TLSALLOWINVALIDCERTIFICATES, insecure);
2089 	ssl_opt->allow_invalid_hostname = mongoc_uri_get_option_as_bool(uri, MONGOC_URI_TLSALLOWINVALIDHOSTNAMES, insecure);
2090 
2091 	/* Boolean options default to false, so we cannot consider them for
2092 	 * any_ssl_option_set. This isn't actually a problem as libmongoc will
2093 	 * already have assigned them when creating the client, enabling SSL, and
2094 	 * assigning SSL options. Therefore, we only need to check for non-defaults
2095 	 * (i.e. non-NULL strings, true booleans). */
2096 	if (pem_file || pem_pwd || ca_file || ssl_opt->weak_cert_validation || ssl_opt->allow_invalid_hostname) {
2097 		*any_ssl_option_set = true;
2098 	}
2099 }
2100 
php_phongo_fetch_ssl_opt_string(zval * zoptions,const char * key)2101 static inline char* php_phongo_fetch_ssl_opt_string(zval* zoptions, const char* key)
2102 {
2103 	int       plen;
2104 	zend_bool pfree;
2105 	char*     pval;
2106 	char*     value;
2107 
2108 	pval  = php_array_fetch_string(zoptions, key, &plen, &pfree);
2109 	value = pfree ? pval : estrndup(pval, plen);
2110 
2111 	return value;
2112 }
2113 
php_phongo_make_ssl_opt(mongoc_uri_t * uri,zval * zoptions)2114 static mongoc_ssl_opt_t* php_phongo_make_ssl_opt(mongoc_uri_t* uri, zval* zoptions)
2115 {
2116 	mongoc_ssl_opt_t* ssl_opt;
2117 	bool              any_ssl_option_set = false;
2118 
2119 	if (!zoptions) {
2120 		return NULL;
2121 	}
2122 
2123 #if defined(MONGOC_ENABLE_SSL_SECURE_CHANNEL) || defined(MONGOC_ENABLE_SSL_SECURE_TRANSPORT)
2124 	if (php_array_existsc(zoptions, "ca_dir")) {
2125 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "\"ca_dir\" option is not supported by Secure Channel and Secure Transport");
2126 		return NULL;
2127 	}
2128 
2129 	if (php_array_existsc(zoptions, "capath")) {
2130 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "\"capath\" option is not supported by Secure Channel and Secure Transport");
2131 		return NULL;
2132 	}
2133 #endif
2134 
2135 #if defined(MONGOC_ENABLE_SSL_LIBRESSL) || defined(MONGOC_ENABLE_SSL_SECURE_TRANSPORT)
2136 	if (php_array_existsc(zoptions, "crl_file")) {
2137 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "\"crl_file\" option is not supported by LibreSSL and Secure Transport");
2138 		return NULL;
2139 	}
2140 #endif
2141 
2142 	ssl_opt = ecalloc(1, sizeof(mongoc_ssl_opt_t));
2143 
2144 	/* If SSL options are set in the URL, we need to read them and set them on
2145 	 * the options struct so we can merge potential options from passed in
2146 	 * driverOptions (zoptions) */
2147 	if (mongoc_uri_get_tls(uri)) {
2148 		php_phongo_mongoc_ssl_opts_from_uri(ssl_opt, uri, &any_ssl_option_set);
2149 	}
2150 
2151 #define PHONGO_SSL_OPTION_SWAP_STRING(o, n) \
2152 	if ((o)) {                              \
2153 		efree((char*) (o));                 \
2154 	}                                       \
2155 	(o) = php_phongo_fetch_ssl_opt_string(zoptions, n);
2156 
2157 	/* Apply driver options that don't have a corresponding URI option. These
2158 	 * are set directly on the SSL options struct. */
2159 	if (php_array_existsc(zoptions, "ca_dir")) {
2160 		PHONGO_SSL_OPTION_SWAP_STRING(ssl_opt->ca_dir, "ca_dir");
2161 		any_ssl_option_set = true;
2162 	} else if (php_array_existsc(zoptions, "capath")) {
2163 		PHONGO_SSL_OPTION_SWAP_STRING(ssl_opt->ca_dir, "capath");
2164 		any_ssl_option_set = true;
2165 
2166 		php_error_docref(NULL, E_DEPRECATED, "The \"capath\" context driver option is deprecated. Please use the \"ca_dir\" driver option instead.");
2167 	}
2168 
2169 	if (php_array_existsc(zoptions, "crl_file")) {
2170 		PHONGO_SSL_OPTION_SWAP_STRING(ssl_opt->crl_file, "crl_file");
2171 		any_ssl_option_set = true;
2172 	}
2173 
2174 #undef PHONGO_SSL_OPTION_SWAP_STRING
2175 
2176 	if (!any_ssl_option_set) {
2177 		efree(ssl_opt);
2178 		return NULL;
2179 	}
2180 
2181 	return ssl_opt;
2182 }
2183 
php_phongo_free_ssl_opt(mongoc_ssl_opt_t * ssl_opt)2184 static void php_phongo_free_ssl_opt(mongoc_ssl_opt_t* ssl_opt)
2185 {
2186 	if (ssl_opt->pem_file) {
2187 		efree((char*) ssl_opt->pem_file);
2188 	}
2189 
2190 	if (ssl_opt->pem_pwd) {
2191 		efree((char*) ssl_opt->pem_pwd);
2192 	}
2193 
2194 	if (ssl_opt->ca_file) {
2195 		efree((char*) ssl_opt->ca_file);
2196 	}
2197 
2198 	if (ssl_opt->ca_dir) {
2199 		efree((char*) ssl_opt->ca_dir);
2200 	}
2201 
2202 	if (ssl_opt->crl_file) {
2203 		efree((char*) ssl_opt->crl_file);
2204 	}
2205 
2206 	efree(ssl_opt);
2207 }
2208 
php_phongo_apply_driver_option_to_uri(mongoc_uri_t * uri,zval * zoptions,const char * driverOptionKey,const char * optionKey)2209 static inline bool php_phongo_apply_driver_option_to_uri(mongoc_uri_t* uri, zval* zoptions, const char* driverOptionKey, const char* optionKey)
2210 {
2211 	bool  ret;
2212 	char* value;
2213 
2214 	value = php_phongo_fetch_ssl_opt_string(zoptions, driverOptionKey);
2215 	ret   = mongoc_uri_set_option_as_utf8(uri, optionKey, value);
2216 	efree(value);
2217 
2218 	return ret;
2219 }
2220 
php_phongo_apply_driver_options_to_uri(mongoc_uri_t * uri,zval * zoptions)2221 static bool php_phongo_apply_driver_options_to_uri(mongoc_uri_t* uri, zval* zoptions)
2222 {
2223 	if (!zoptions) {
2224 		return true;
2225 	}
2226 
2227 	/* Map TLS driver options to the canonical tls options in the URI. */
2228 	if (php_array_existsc(zoptions, "allow_invalid_hostname")) {
2229 		if (!mongoc_uri_set_option_as_bool(uri, MONGOC_URI_TLSALLOWINVALIDHOSTNAMES, php_array_fetchc_bool(zoptions, "allow_invalid_hostname"))) {
2230 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" driver option", "allow_invalid_hostname");
2231 
2232 			return false;
2233 		}
2234 
2235 		php_error_docref(NULL, E_DEPRECATED, "The \"allow_invalid_hostname\" driver option is deprecated. Please use the \"tlsAllowInvalidHostnames\" URI option instead.");
2236 	}
2237 
2238 	if (php_array_existsc(zoptions, "weak_cert_validation")) {
2239 		if (!mongoc_uri_set_option_as_bool(uri, MONGOC_URI_TLSALLOWINVALIDCERTIFICATES, php_array_fetchc_bool(zoptions, "weak_cert_validation"))) {
2240 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" driver option", "weak_cert_validation");
2241 
2242 			return false;
2243 		}
2244 
2245 		php_error_docref(NULL, E_DEPRECATED, "The \"weak_cert_validation\" driver option is deprecated. Please use the \"tlsAllowInvalidCertificates\" URI option instead.");
2246 	} else if (php_array_existsc(zoptions, "allow_self_signed")) {
2247 		if (!mongoc_uri_set_option_as_bool(uri, MONGOC_URI_TLSALLOWINVALIDCERTIFICATES, php_array_fetchc_bool(zoptions, "allow_self_signed"))) {
2248 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" driver option", "allow_self_signed");
2249 
2250 			return false;
2251 		}
2252 
2253 		php_error_docref(NULL, E_DEPRECATED, "The \"allow_self_signed\" context driver option is deprecated. Please use the \"tlsAllowInvalidCertificates\" URI option instead.");
2254 	}
2255 
2256 	if (php_array_existsc(zoptions, "pem_file")) {
2257 		if (!php_phongo_apply_driver_option_to_uri(uri, zoptions, "pem_file", MONGOC_URI_TLSCERTIFICATEKEYFILE)) {
2258 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" driver option", "pem_file");
2259 
2260 			return false;
2261 		}
2262 
2263 		php_error_docref(NULL, E_DEPRECATED, "The \"pem_file\" driver option is deprecated. Please use the \"tlsCertificateKeyFile\" URI option instead.");
2264 	} else if (php_array_existsc(zoptions, "local_cert")) {
2265 		if (!php_phongo_apply_driver_option_to_uri(uri, zoptions, "local_cert", MONGOC_URI_TLSCERTIFICATEKEYFILE)) {
2266 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" driver option", "local_cert");
2267 
2268 			return false;
2269 		}
2270 
2271 		php_error_docref(NULL, E_DEPRECATED, "The \"local_cert\" context driver option is deprecated. Please use the \"tlsCertificateKeyFile\" URI option instead.");
2272 	}
2273 
2274 	if (php_array_existsc(zoptions, "pem_pwd")) {
2275 		if (!php_phongo_apply_driver_option_to_uri(uri, zoptions, "pem_pwd", MONGOC_URI_TLSCERTIFICATEKEYFILEPASSWORD)) {
2276 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" driver option", "pem_pwd");
2277 
2278 			return false;
2279 		}
2280 
2281 		php_error_docref(NULL, E_DEPRECATED, "The \"pem_pwd\" driver option is deprecated. Please use the \"tlsCertificateKeyFilePassword\" URI option instead.");
2282 	} else if (php_array_existsc(zoptions, "passphrase")) {
2283 		if (!php_phongo_apply_driver_option_to_uri(uri, zoptions, "passphrase", MONGOC_URI_TLSCERTIFICATEKEYFILEPASSWORD)) {
2284 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" driver option", "passphrase");
2285 
2286 			return false;
2287 		}
2288 
2289 		php_error_docref(NULL, E_DEPRECATED, "The \"passphrase\" context driver option is deprecated. Please use the \"tlsCertificateKeyFilePassword\" URI option instead.");
2290 	}
2291 
2292 	if (php_array_existsc(zoptions, "ca_file")) {
2293 		if (!php_phongo_apply_driver_option_to_uri(uri, zoptions, "ca_file", MONGOC_URI_TLSCAFILE)) {
2294 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" driver option", "ca_file");
2295 
2296 			return false;
2297 		}
2298 
2299 		php_error_docref(NULL, E_DEPRECATED, "The \"ca_file\" driver option is deprecated. Please use the \"tlsCAFile\" URI option instead.");
2300 	} else if (php_array_existsc(zoptions, "cafile")) {
2301 		if (!php_phongo_apply_driver_option_to_uri(uri, zoptions, "cafile", MONGOC_URI_TLSCAFILE)) {
2302 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Failed to parse \"%s\" driver option", "cafile");
2303 
2304 			return false;
2305 		}
2306 
2307 		php_error_docref(NULL, E_DEPRECATED, "The \"cafile\" context driver option is deprecated. Please use the \"tlsCAFile\" URI option instead.");
2308 	}
2309 
2310 	return true;
2311 }
2312 #endif
2313 
2314 /* APM callbacks */
php_phongo_dispatch_handlers(const char * name,zval * z_event)2315 static void php_phongo_dispatch_handlers(const char* name, zval* z_event)
2316 {
2317 	zval* value;
2318 
2319 	ZEND_HASH_FOREACH_VAL_IND(MONGODB_G(subscribers), value)
2320 	{
2321 		if (EG(exception)) {
2322 			break;
2323 		}
2324 		/* We can't use the zend_call_method_with_1_params macro here, as it
2325 		 * does a sizeof() on the name argument, which does only work with
2326 		 * constant names, but not with parameterized ones as it does
2327 		 * "sizeof(char*)" in that case. */
2328 		zend_call_method(PHONGO_COMPAT_OBJ_P(value), NULL, NULL, name, strlen(name), NULL, 1, z_event, NULL);
2329 	}
2330 	ZEND_HASH_FOREACH_END();
2331 }
2332 
php_phongo_command_started(const mongoc_apm_command_started_t * event)2333 static void php_phongo_command_started(const mongoc_apm_command_started_t* event)
2334 {
2335 	php_phongo_commandstartedevent_t* p_event;
2336 	zval                              z_event;
2337 
2338 	/* Return early if there are no APM subscribers to notify */
2339 	if (!MONGODB_G(subscribers) || zend_hash_num_elements(MONGODB_G(subscribers)) == 0) {
2340 		return;
2341 	}
2342 
2343 	object_init_ex(&z_event, php_phongo_commandstartedevent_ce);
2344 	p_event = Z_COMMANDSTARTEDEVENT_OBJ_P(&z_event);
2345 
2346 	p_event->client        = mongoc_apm_command_started_get_context(event);
2347 	p_event->command_name  = estrdup(mongoc_apm_command_started_get_command_name(event));
2348 	p_event->server_id     = mongoc_apm_command_started_get_server_id(event);
2349 	p_event->operation_id  = mongoc_apm_command_started_get_operation_id(event);
2350 	p_event->request_id    = mongoc_apm_command_started_get_request_id(event);
2351 	p_event->command       = bson_copy(mongoc_apm_command_started_get_command(event));
2352 	p_event->database_name = estrdup(mongoc_apm_command_started_get_database_name(event));
2353 
2354 	php_phongo_dispatch_handlers("commandStarted", &z_event);
2355 	zval_ptr_dtor(&z_event);
2356 }
2357 
php_phongo_command_succeeded(const mongoc_apm_command_succeeded_t * event)2358 static void php_phongo_command_succeeded(const mongoc_apm_command_succeeded_t* event)
2359 {
2360 	php_phongo_commandsucceededevent_t* p_event;
2361 	zval                                z_event;
2362 
2363 	/* Return early if there are no APM subscribers to notify */
2364 	if (!MONGODB_G(subscribers) || zend_hash_num_elements(MONGODB_G(subscribers)) == 0) {
2365 		return;
2366 	}
2367 
2368 	object_init_ex(&z_event, php_phongo_commandsucceededevent_ce);
2369 	p_event = Z_COMMANDSUCCEEDEDEVENT_OBJ_P(&z_event);
2370 
2371 	p_event->client          = mongoc_apm_command_succeeded_get_context(event);
2372 	p_event->command_name    = estrdup(mongoc_apm_command_succeeded_get_command_name(event));
2373 	p_event->server_id       = mongoc_apm_command_succeeded_get_server_id(event);
2374 	p_event->operation_id    = mongoc_apm_command_succeeded_get_operation_id(event);
2375 	p_event->request_id      = mongoc_apm_command_succeeded_get_request_id(event);
2376 	p_event->duration_micros = mongoc_apm_command_succeeded_get_duration(event);
2377 	p_event->reply           = bson_copy(mongoc_apm_command_succeeded_get_reply(event));
2378 
2379 	php_phongo_dispatch_handlers("commandSucceeded", &z_event);
2380 	zval_ptr_dtor(&z_event);
2381 }
2382 
php_phongo_command_failed(const mongoc_apm_command_failed_t * event)2383 static void php_phongo_command_failed(const mongoc_apm_command_failed_t* event)
2384 {
2385 	php_phongo_commandfailedevent_t* p_event;
2386 	zval                             z_event;
2387 	bson_error_t                     tmp_error = { 0 };
2388 	zend_class_entry*                default_exception_ce;
2389 
2390 	default_exception_ce = zend_exception_get_default();
2391 
2392 	/* Return early if there are no APM subscribers to notify */
2393 	if (!MONGODB_G(subscribers) || zend_hash_num_elements(MONGODB_G(subscribers)) == 0) {
2394 		return;
2395 	}
2396 
2397 	object_init_ex(&z_event, php_phongo_commandfailedevent_ce);
2398 	p_event = Z_COMMANDFAILEDEVENT_OBJ_P(&z_event);
2399 
2400 	p_event->client          = mongoc_apm_command_failed_get_context(event);
2401 	p_event->command_name    = estrdup(mongoc_apm_command_failed_get_command_name(event));
2402 	p_event->server_id       = mongoc_apm_command_failed_get_server_id(event);
2403 	p_event->operation_id    = mongoc_apm_command_failed_get_operation_id(event);
2404 	p_event->request_id      = mongoc_apm_command_failed_get_request_id(event);
2405 	p_event->duration_micros = mongoc_apm_command_failed_get_duration(event);
2406 	p_event->reply           = bson_copy(mongoc_apm_command_failed_get_reply(event));
2407 
2408 	/* We need to process and convert the error right here, otherwise
2409 	 * debug_info will turn into a recursive loop, and with the wrong trace
2410 	 * locations */
2411 	mongoc_apm_command_failed_get_error(event, &tmp_error);
2412 
2413 	object_init_ex(&p_event->z_error, phongo_exception_from_mongoc_domain(tmp_error.domain, tmp_error.code));
2414 	zend_update_property_string(default_exception_ce, PHONGO_COMPAT_OBJ_P(&p_event->z_error), ZEND_STRL("message"), tmp_error.message);
2415 	zend_update_property_long(default_exception_ce, PHONGO_COMPAT_OBJ_P(&p_event->z_error), ZEND_STRL("code"), tmp_error.code);
2416 
2417 	php_phongo_dispatch_handlers("commandFailed", &z_event);
2418 	zval_ptr_dtor(&z_event);
2419 }
2420 
2421 /* Sets the callbacks for APM */
php_phongo_set_monitoring_callbacks(mongoc_client_t * client)2422 int php_phongo_set_monitoring_callbacks(mongoc_client_t* client)
2423 {
2424 	int retval;
2425 
2426 	mongoc_apm_callbacks_t* callbacks = mongoc_apm_callbacks_new();
2427 
2428 	mongoc_apm_set_command_started_cb(callbacks, php_phongo_command_started);
2429 	mongoc_apm_set_command_succeeded_cb(callbacks, php_phongo_command_succeeded);
2430 	mongoc_apm_set_command_failed_cb(callbacks, php_phongo_command_failed);
2431 	retval = mongoc_client_set_apm_callbacks(client, callbacks, client);
2432 
2433 	mongoc_apm_callbacks_destroy(callbacks);
2434 
2435 	return retval;
2436 }
2437 
php_phongo_manager_prepare_manager_for_hash(zval * driverOptions,bool * free)2438 static zval* php_phongo_manager_prepare_manager_for_hash(zval* driverOptions, bool* free)
2439 {
2440 	php_phongo_manager_t* manager;
2441 	zval*                 autoEncryptionOpts      = NULL;
2442 	zval*                 keyVaultClient          = NULL;
2443 	zval*                 driverOptionsClone      = NULL;
2444 	zval*                 autoEncryptionOptsClone = NULL;
2445 	zval                  stackAutoEncryptionOptsClone;
2446 
2447 	*free = false;
2448 
2449 	if (!driverOptions) {
2450 		return NULL;
2451 	}
2452 
2453 	if (!php_array_existsc(driverOptions, "autoEncryption")) {
2454 		goto ref;
2455 	}
2456 
2457 	autoEncryptionOpts = php_array_fetchc(driverOptions, "autoEncryption");
2458 	if (Z_TYPE_P(autoEncryptionOpts) != IS_ARRAY) {
2459 		goto ref;
2460 	}
2461 
2462 	if (!php_array_existsc(autoEncryptionOpts, "keyVaultClient")) {
2463 		goto ref;
2464 	}
2465 
2466 	keyVaultClient = php_array_fetchc(autoEncryptionOpts, "keyVaultClient");
2467 	if (Z_TYPE_P(keyVaultClient) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(keyVaultClient), php_phongo_manager_ce)) {
2468 		goto ref;
2469 	}
2470 
2471 	*free = true;
2472 
2473 	manager = Z_MANAGER_OBJ_P(keyVaultClient);
2474 
2475 	driverOptionsClone      = ecalloc(sizeof(zval), 1);
2476 	autoEncryptionOptsClone = &stackAutoEncryptionOptsClone;
2477 
2478 	ZVAL_DUP(autoEncryptionOptsClone, autoEncryptionOpts);
2479 	ADD_ASSOC_STRINGL(autoEncryptionOptsClone, "keyVaultClient", manager->client_hash, manager->client_hash_len);
2480 
2481 	ZVAL_DUP(driverOptionsClone, driverOptions);
2482 	ADD_ASSOC_ZVAL_EX(driverOptionsClone, "autoEncryption", autoEncryptionOptsClone);
2483 
2484 	return driverOptionsClone;
2485 
2486 ref:
2487 	Z_ADDREF_P(driverOptions);
2488 	return driverOptions;
2489 }
2490 
2491 /* Creates a hash for a client by concatenating the URI string with serialized
2492  * options arrays. On success, a persistent string is returned (i.e. pefree()
2493  * should be used to free it) and hash_len will be set to the string's length.
2494  * On error, an exception will have been thrown and NULL will be returned. */
php_phongo_manager_make_client_hash(const char * uri_string,zval * options,zval * driverOptions,size_t * hash_len)2495 static char* php_phongo_manager_make_client_hash(const char* uri_string, zval* options, zval* driverOptions, size_t* hash_len)
2496 {
2497 	char*                hash    = NULL;
2498 	smart_str            var_buf = { 0 };
2499 	php_serialize_data_t var_hash;
2500 	zval*                serializable_driver_options = NULL;
2501 	bool                 free_driver_options         = false;
2502 
2503 	zval args;
2504 
2505 	array_init_size(&args, 4);
2506 	ADD_ASSOC_LONG_EX(&args, "pid", getpid());
2507 	ADD_ASSOC_STRING(&args, "uri", uri_string);
2508 
2509 	if (options) {
2510 		ADD_ASSOC_ZVAL_EX(&args, "options", options);
2511 		Z_ADDREF_P(options);
2512 	} else {
2513 		ADD_ASSOC_NULL_EX(&args, "options");
2514 	}
2515 
2516 	if (driverOptions) {
2517 		serializable_driver_options = php_phongo_manager_prepare_manager_for_hash(driverOptions, &free_driver_options);
2518 		ADD_ASSOC_ZVAL_EX(&args, "driverOptions", serializable_driver_options);
2519 	} else {
2520 		ADD_ASSOC_NULL_EX(&args, "driverOptions");
2521 	}
2522 
2523 	PHP_VAR_SERIALIZE_INIT(var_hash);
2524 	php_var_serialize(&var_buf, &args, &var_hash);
2525 	PHP_VAR_SERIALIZE_DESTROY(var_hash);
2526 
2527 	if (!EG(exception)) {
2528 		*hash_len = ZSTR_LEN(var_buf.s);
2529 		hash      = estrndup(ZSTR_VAL(var_buf.s), *hash_len);
2530 	}
2531 
2532 	zval_ptr_dtor(&args);
2533 
2534 	if (free_driver_options) {
2535 		efree(serializable_driver_options);
2536 	}
2537 
2538 	smart_str_free(&var_buf);
2539 
2540 	return hash;
2541 }
2542 
php_phongo_extract_handshake_data(zval * driver,const char * key,char ** value,size_t * value_len)2543 static bool php_phongo_extract_handshake_data(zval* driver, const char* key, char** value, size_t* value_len)
2544 {
2545 	zval* zvalue;
2546 
2547 	if (!php_array_exists(driver, key)) {
2548 		*value     = NULL;
2549 		*value_len = 0;
2550 
2551 		return true;
2552 	}
2553 
2554 	zvalue = php_array_fetch(driver, key);
2555 
2556 	if (Z_TYPE_P(zvalue) != IS_STRING) {
2557 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"%s\" handshake option to be a string, %s given", key, PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(zvalue));
2558 		return false;
2559 	}
2560 
2561 	*value     = estrdup(Z_STRVAL_P(zvalue));
2562 	*value_len = Z_STRLEN_P(zvalue);
2563 
2564 	return true;
2565 }
2566 
php_phongo_concat_handshake_data(const char * default_value,const char * custom_value,size_t custom_value_len)2567 static char* php_phongo_concat_handshake_data(const char* default_value, const char* custom_value, size_t custom_value_len)
2568 {
2569 	char* ret;
2570 	/* Length of the returned value needs to include a trailing space and null byte */
2571 	size_t ret_len = strlen(default_value) + 2;
2572 
2573 	if (custom_value) {
2574 		/* Increase the length by that of the custom value as well as the separator length */
2575 		ret_len += custom_value_len + PHONGO_METADATA_SEPARATOR_LEN;
2576 	}
2577 
2578 	ret = ecalloc(sizeof(char*), ret_len);
2579 
2580 	if (custom_value) {
2581 		snprintf(ret, ret_len, "%s%s%s ", default_value, PHONGO_METADATA_SEPARATOR, custom_value);
2582 	} else {
2583 		snprintf(ret, ret_len, "%s ", default_value);
2584 	}
2585 
2586 	return ret;
2587 }
2588 
php_phongo_handshake_data_append(const char * name,size_t name_len,const char * version,size_t version_len,const char * platform,size_t platform_len)2589 static void php_phongo_handshake_data_append(const char* name, size_t name_len, const char* version, size_t version_len, const char* platform, size_t platform_len)
2590 {
2591 	char*  php_version_string;
2592 	size_t php_version_string_len;
2593 	char*  driver_name;
2594 	char*  driver_version;
2595 	char*  full_platform;
2596 
2597 	php_version_string_len = strlen(PHP_VERSION) + PHONGO_METADATA_PHP_VERSION_PREFIX_LEN + 1;
2598 	php_version_string     = ecalloc(sizeof(char*), php_version_string_len);
2599 	snprintf(php_version_string, php_version_string_len, "%s%s", PHONGO_METADATA_PHP_VERSION_PREFIX, PHP_VERSION);
2600 
2601 	driver_name    = php_phongo_concat_handshake_data("ext-mongodb:PHP", name, name_len);
2602 	driver_version = php_phongo_concat_handshake_data(PHP_MONGODB_VERSION, version, version_len);
2603 	full_platform  = php_phongo_concat_handshake_data(php_version_string, platform, platform_len);
2604 
2605 	MONGOC_DEBUG(
2606 		"Setting driver handshake data: { name: '%s', version: '%s', platform: '%s' }",
2607 		driver_name,
2608 		driver_version,
2609 		full_platform);
2610 
2611 	mongoc_handshake_data_append(driver_name, driver_version, full_platform);
2612 
2613 	efree(php_version_string);
2614 	efree(driver_name);
2615 	efree(driver_version);
2616 	efree(full_platform);
2617 }
2618 
php_phongo_set_handshake_data(zval * driverOptions)2619 static void php_phongo_set_handshake_data(zval* driverOptions)
2620 {
2621 	char*  name         = NULL;
2622 	size_t name_len     = 0;
2623 	char*  version      = NULL;
2624 	size_t version_len  = 0;
2625 	char*  platform     = NULL;
2626 	size_t platform_len = 0;
2627 
2628 	if (driverOptions && php_array_existsc(driverOptions, "driver")) {
2629 		zval* driver = php_array_fetchc(driverOptions, "driver");
2630 
2631 		if (Z_TYPE_P(driver) != IS_ARRAY) {
2632 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"driver\" driver option to be an array, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(driver));
2633 			return;
2634 		}
2635 
2636 		if (!php_phongo_extract_handshake_data(driver, "name", &name, &name_len)) {
2637 			/* Exception already thrown */
2638 			goto cleanup;
2639 		}
2640 
2641 		if (!php_phongo_extract_handshake_data(driver, "version", &version, &version_len)) {
2642 			/* Exception already thrown */
2643 			goto cleanup;
2644 		}
2645 
2646 		if (!php_phongo_extract_handshake_data(driver, "platform", &platform, &platform_len)) {
2647 			/* Exception already thrown */
2648 			goto cleanup;
2649 		}
2650 	}
2651 
2652 	php_phongo_handshake_data_append(name, name_len, version, version_len, platform, platform_len);
2653 
2654 cleanup:
2655 	if (name) {
2656 		efree(name);
2657 	}
2658 	if (version) {
2659 		efree(version);
2660 	}
2661 	if (platform) {
2662 		efree(platform);
2663 	}
2664 }
2665 
php_phongo_make_mongo_client(const mongoc_uri_t * uri,zval * driverOptions)2666 static mongoc_client_t* php_phongo_make_mongo_client(const mongoc_uri_t* uri, zval* driverOptions) /* {{{ */
2667 {
2668 	const char *mongoc_version, *bson_version;
2669 
2670 #ifdef HAVE_SYSTEM_LIBMONGOC
2671 	mongoc_version = mongoc_get_version();
2672 #else
2673 	mongoc_version = "bundled";
2674 #endif
2675 
2676 #ifdef HAVE_SYSTEM_LIBBSON
2677 	bson_version = bson_get_version();
2678 #else
2679 	bson_version = "bundled";
2680 #endif
2681 
2682 	MONGOC_DEBUG(
2683 		"Creating Manager, phongo-%s[%s] - mongoc-%s(%s), libbson-%s(%s), php-%s",
2684 		PHP_MONGODB_VERSION,
2685 		PHP_MONGODB_STABILITY,
2686 		MONGOC_VERSION_S,
2687 		mongoc_version,
2688 		BSON_VERSION_S,
2689 		bson_version,
2690 		PHP_VERSION);
2691 
2692 	php_phongo_set_handshake_data(driverOptions);
2693 
2694 	return mongoc_client_new_from_uri(uri);
2695 } /* }}} */
2696 
php_phongo_persist_client(const char * hash,size_t hash_len,mongoc_client_t * client)2697 static void php_phongo_persist_client(const char* hash, size_t hash_len, mongoc_client_t* client)
2698 {
2699 	php_phongo_pclient_t* pclient = (php_phongo_pclient_t*) pecalloc(1, sizeof(php_phongo_pclient_t), 1);
2700 
2701 	pclient->created_by_pid = (int) getpid();
2702 	pclient->client         = client;
2703 
2704 	zend_hash_str_update_ptr(&MONGODB_G(pclients), hash, hash_len, pclient);
2705 }
2706 
php_phongo_find_client(const char * hash,size_t hash_len)2707 static mongoc_client_t* php_phongo_find_client(const char* hash, size_t hash_len)
2708 {
2709 	php_phongo_pclient_t* pclient;
2710 
2711 	if ((pclient = zend_hash_str_find_ptr(&MONGODB_G(pclients), hash, hash_len)) != NULL) {
2712 		return pclient->client;
2713 	}
2714 
2715 	return NULL;
2716 }
2717 
2718 #ifdef MONGOC_ENABLE_CLIENT_SIDE_ENCRYPTION
phongo_manager_set_auto_encryption_opts(php_phongo_manager_t * manager,zval * driverOptions)2719 static bool phongo_manager_set_auto_encryption_opts(php_phongo_manager_t* manager, zval* driverOptions) /* {{{ */
2720 {
2721 	zval*                          zAutoEncryptionOpts;
2722 	bson_error_t                   error                = { 0 };
2723 	mongoc_auto_encryption_opts_t* auto_encryption_opts = NULL;
2724 	bool                           retval               = false;
2725 
2726 	if (!driverOptions || !php_array_existsc(driverOptions, "autoEncryption")) {
2727 		return true;
2728 	}
2729 
2730 	zAutoEncryptionOpts = php_array_fetch(driverOptions, "autoEncryption");
2731 
2732 	if (Z_TYPE_P(zAutoEncryptionOpts) != IS_ARRAY) {
2733 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"autoEncryption\" driver option to be array, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(zAutoEncryptionOpts));
2734 		return false;
2735 	}
2736 
2737 	auto_encryption_opts = mongoc_auto_encryption_opts_new();
2738 
2739 	if (php_array_existsc(zAutoEncryptionOpts, "keyVaultClient")) {
2740 		zval* key_vault_client = php_array_fetch(zAutoEncryptionOpts, "keyVaultClient");
2741 
2742 		if (Z_TYPE_P(key_vault_client) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(key_vault_client), php_phongo_manager_ce)) {
2743 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"keyVaultClient\" encryption option to be %s, %s given", ZSTR_VAL(php_phongo_manager_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(key_vault_client));
2744 			goto cleanup;
2745 		}
2746 
2747 		mongoc_auto_encryption_opts_set_keyvault_client(auto_encryption_opts, Z_MANAGER_OBJ_P(key_vault_client)->client);
2748 	}
2749 
2750 	if (php_array_existsc(zAutoEncryptionOpts, "keyVaultNamespace")) {
2751 		char*     key_vault_ns;
2752 		char*     db_name;
2753 		char*     coll_name;
2754 		int       plen;
2755 		zend_bool pfree;
2756 
2757 		key_vault_ns = php_array_fetch_string(zAutoEncryptionOpts, "keyVaultNamespace", &plen, &pfree);
2758 
2759 		if (!phongo_split_namespace(key_vault_ns, &db_name, &coll_name)) {
2760 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"keyVaultNamespace\" encryption option to contain a full collection name");
2761 
2762 			if (pfree) {
2763 				efree(key_vault_ns);
2764 			}
2765 
2766 			goto cleanup;
2767 		}
2768 
2769 		mongoc_auto_encryption_opts_set_keyvault_namespace(auto_encryption_opts, db_name, coll_name);
2770 
2771 		efree(db_name);
2772 		efree(coll_name);
2773 
2774 		if (pfree) {
2775 			efree(key_vault_ns);
2776 		}
2777 	}
2778 
2779 	if (php_array_existsc(zAutoEncryptionOpts, "kmsProviders")) {
2780 		zval*  kms_providers  = php_array_fetch(zAutoEncryptionOpts, "kmsProviders");
2781 		bson_t bson_providers = BSON_INITIALIZER;
2782 
2783 		if (Z_TYPE_P(kms_providers) != IS_OBJECT && Z_TYPE_P(kms_providers) != IS_ARRAY) {
2784 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"kmsProviders\" encryption option to be an array or object");
2785 			goto cleanup;
2786 		}
2787 
2788 		php_phongo_zval_to_bson(kms_providers, PHONGO_BSON_NONE, &bson_providers, NULL);
2789 		if (EG(exception)) {
2790 			goto cleanup;
2791 		}
2792 
2793 		mongoc_auto_encryption_opts_set_kms_providers(auto_encryption_opts, &bson_providers);
2794 
2795 		bson_destroy(&bson_providers);
2796 	}
2797 
2798 	if (php_array_existsc(zAutoEncryptionOpts, "schemaMap")) {
2799 		zval*  schema_map = php_array_fetch(zAutoEncryptionOpts, "schemaMap");
2800 		bson_t bson_map   = BSON_INITIALIZER;
2801 
2802 		if (Z_TYPE_P(schema_map) != IS_OBJECT && Z_TYPE_P(schema_map) != IS_ARRAY) {
2803 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"schemaMap\" encryption option to be an array or object");
2804 			goto cleanup;
2805 		}
2806 
2807 		php_phongo_zval_to_bson(schema_map, PHONGO_BSON_NONE, &bson_map, NULL);
2808 		if (EG(exception)) {
2809 			goto cleanup;
2810 		}
2811 
2812 		mongoc_auto_encryption_opts_set_schema_map(auto_encryption_opts, &bson_map);
2813 
2814 		bson_destroy(&bson_map);
2815 	}
2816 
2817 	if (php_array_existsc(zAutoEncryptionOpts, "bypassAutoEncryption")) {
2818 		zend_bool bypass_auto_encryption = php_array_fetch_bool(zAutoEncryptionOpts, "bypassAutoEncryption");
2819 
2820 		mongoc_auto_encryption_opts_set_bypass_auto_encryption(auto_encryption_opts, bypass_auto_encryption);
2821 	}
2822 
2823 	if (php_array_existsc(zAutoEncryptionOpts, "extraOptions")) {
2824 		zval*  extra_options = php_array_fetch(zAutoEncryptionOpts, "extraOptions");
2825 		bson_t bson_options  = BSON_INITIALIZER;
2826 
2827 		php_phongo_zval_to_bson(extra_options, PHONGO_BSON_NONE, &bson_options, NULL);
2828 		if (EG(exception)) {
2829 			goto cleanup;
2830 		}
2831 
2832 		mongoc_auto_encryption_opts_set_extra(auto_encryption_opts, &bson_options);
2833 
2834 		bson_destroy(&bson_options);
2835 	}
2836 
2837 	if (!mongoc_client_enable_auto_encryption(manager->client, auto_encryption_opts, &error)) {
2838 		phongo_throw_exception_from_bson_error_t(&error);
2839 		goto cleanup;
2840 	}
2841 
2842 	retval = true;
2843 
2844 cleanup:
2845 	mongoc_auto_encryption_opts_destroy(auto_encryption_opts);
2846 	return retval;
2847 }
2848 /* }}} */
2849 
phongo_clientencryption_opts_from_zval(mongoc_client_t * defaultKeyVaultClient,zval * options)2850 static mongoc_client_encryption_opts_t* phongo_clientencryption_opts_from_zval(mongoc_client_t* defaultKeyVaultClient, zval* options) /* {{{ */
2851 {
2852 	mongoc_client_encryption_opts_t* opts;
2853 
2854 	opts = mongoc_client_encryption_opts_new();
2855 
2856 	if (!options || Z_TYPE_P(options) != IS_ARRAY) {
2857 		return opts;
2858 	}
2859 
2860 	if (php_array_existsc(options, "keyVaultClient")) {
2861 		zval* key_vault_client = php_array_fetch(options, "keyVaultClient");
2862 
2863 		if (Z_TYPE_P(key_vault_client) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(key_vault_client), php_phongo_manager_ce)) {
2864 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"keyVaultClient\" encryption option to be %s, %s given", ZSTR_VAL(php_phongo_manager_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(key_vault_client));
2865 			goto cleanup;
2866 		}
2867 
2868 		mongoc_client_encryption_opts_set_keyvault_client(opts, Z_MANAGER_OBJ_P(key_vault_client)->client);
2869 	} else {
2870 		mongoc_client_encryption_opts_set_keyvault_client(opts, defaultKeyVaultClient);
2871 	}
2872 
2873 	if (php_array_existsc(options, "keyVaultNamespace")) {
2874 		char*     keyvault_namespace;
2875 		char*     db_name;
2876 		char*     coll_name;
2877 		int       plen;
2878 		zend_bool pfree;
2879 
2880 		keyvault_namespace = php_array_fetchc_string(options, "keyVaultNamespace", &plen, &pfree);
2881 
2882 		if (!phongo_split_namespace(keyvault_namespace, &db_name, &coll_name)) {
2883 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"keyVaultNamespace\" encryption option to contain a full collection name");
2884 
2885 			if (pfree) {
2886 				efree(keyvault_namespace);
2887 			}
2888 
2889 			goto cleanup;
2890 		}
2891 
2892 		mongoc_client_encryption_opts_set_keyvault_namespace(opts, db_name, coll_name);
2893 		efree(db_name);
2894 		efree(coll_name);
2895 
2896 		if (pfree) {
2897 			efree(keyvault_namespace);
2898 		}
2899 	}
2900 
2901 	if (php_array_existsc(options, "kmsProviders")) {
2902 		zval*  kms_providers  = php_array_fetchc(options, "kmsProviders");
2903 		bson_t bson_providers = BSON_INITIALIZER;
2904 
2905 		if (Z_TYPE_P(kms_providers) != IS_ARRAY && Z_TYPE_P(kms_providers) != IS_OBJECT) {
2906 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"kmsProviders\" encryption option to be an array or object");
2907 			goto cleanup;
2908 		}
2909 
2910 		php_phongo_zval_to_bson(kms_providers, PHONGO_BSON_NONE, &bson_providers, NULL);
2911 		if (EG(exception)) {
2912 			goto cleanup;
2913 		}
2914 
2915 		mongoc_client_encryption_opts_set_kms_providers(opts, &bson_providers);
2916 		bson_destroy(&bson_providers);
2917 	}
2918 
2919 	return opts;
2920 
2921 cleanup:
2922 	if (opts) {
2923 		mongoc_client_encryption_opts_destroy(opts);
2924 	}
2925 
2926 	return NULL;
2927 } /* }}} */
2928 
phongo_clientencryption_init(php_phongo_clientencryption_t * clientencryption,mongoc_client_t * client,zval * options)2929 void phongo_clientencryption_init(php_phongo_clientencryption_t* clientencryption, mongoc_client_t* client, zval* options) /* {{{ */
2930 {
2931 	mongoc_client_encryption_t*      ce;
2932 	mongoc_client_encryption_opts_t* opts;
2933 	bson_error_t                     error = { 0 };
2934 
2935 	opts = phongo_clientencryption_opts_from_zval(client, options);
2936 	if (!opts) {
2937 		/* Exception already thrown */
2938 		goto cleanup;
2939 	}
2940 
2941 	ce = mongoc_client_encryption_new(opts, &error);
2942 	if (!ce) {
2943 		phongo_throw_exception_from_bson_error_t(&error);
2944 
2945 		goto cleanup;
2946 	}
2947 
2948 	clientencryption->client_encryption = ce;
2949 
2950 cleanup:
2951 	if (opts) {
2952 		mongoc_client_encryption_opts_destroy(opts);
2953 	}
2954 } /* }}} */
2955 
phongo_clientencryption_datakey_opts_from_zval(zval * options)2956 static mongoc_client_encryption_datakey_opts_t* phongo_clientencryption_datakey_opts_from_zval(zval* options) /* {{{ */
2957 {
2958 	mongoc_client_encryption_datakey_opts_t* opts;
2959 
2960 	opts = mongoc_client_encryption_datakey_opts_new();
2961 
2962 	if (!options || Z_TYPE_P(options) != IS_ARRAY) {
2963 		return opts;
2964 	}
2965 
2966 	if (php_array_existsc(options, "keyAltNames")) {
2967 		zval*      zkeyaltnames = php_array_fetchc(options, "keyAltNames");
2968 		HashTable* ht_data;
2969 		uint32_t   keyaltnames_count;
2970 		char**     keyaltnames;
2971 		uint32_t   i      = 0;
2972 		uint32_t   j      = 0;
2973 		bool       failed = false;
2974 
2975 		if (!zkeyaltnames || Z_TYPE_P(zkeyaltnames) != IS_ARRAY) {
2976 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected keyAltNames to be array, %s given", PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(zkeyaltnames));
2977 			goto cleanup;
2978 		}
2979 
2980 		ht_data           = HASH_OF(zkeyaltnames);
2981 		keyaltnames_count = ht_data ? zend_hash_num_elements(ht_data) : 0;
2982 		keyaltnames       = ecalloc(keyaltnames_count, sizeof(char*));
2983 
2984 		{
2985 			zend_string* string_key = NULL;
2986 			zend_ulong   num_key    = 0;
2987 			zval*        keyaltname;
2988 
2989 			ZEND_HASH_FOREACH_KEY_VAL(ht_data, num_key, string_key, keyaltname)
2990 			{
2991 				if (i >= keyaltnames_count) {
2992 					phongo_throw_exception(PHONGO_ERROR_LOGIC, "Iterating over too many keyAltNames. Please file a bug report");
2993 					failed = true;
2994 					break;
2995 				}
2996 
2997 				if (Z_TYPE_P(keyaltname) != IS_STRING) {
2998 					if (string_key) {
2999 						phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected keyAltName with index \"%s\" to be string, %s given", ZSTR_VAL(string_key), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(keyaltname));
3000 					} else {
3001 						phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected keyAltName with index \"%lu\" to be string, %s given", num_key, PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(keyaltname));
3002 					}
3003 
3004 					failed = true;
3005 					break;
3006 				}
3007 
3008 				keyaltnames[i] = estrdup(Z_STRVAL_P(keyaltname));
3009 				i++;
3010 			}
3011 			ZEND_HASH_FOREACH_END();
3012 		}
3013 
3014 		if (!failed) {
3015 			mongoc_client_encryption_datakey_opts_set_keyaltnames(opts, keyaltnames, keyaltnames_count);
3016 		}
3017 
3018 		for (j = 0; j < i; j++) {
3019 			efree(keyaltnames[j]);
3020 		}
3021 		efree(keyaltnames);
3022 
3023 		if (failed) {
3024 			goto cleanup;
3025 		}
3026 	}
3027 
3028 	if (php_array_existsc(options, "masterKey")) {
3029 		bson_t masterkey = BSON_INITIALIZER;
3030 
3031 		php_phongo_zval_to_bson(php_array_fetchc(options, "masterKey"), PHONGO_BSON_NONE, &masterkey, NULL);
3032 		if (EG(exception)) {
3033 			goto cleanup;
3034 		}
3035 
3036 		mongoc_client_encryption_datakey_opts_set_masterkey(opts, &masterkey);
3037 	}
3038 
3039 	return opts;
3040 
3041 cleanup:
3042 	if (opts) {
3043 		mongoc_client_encryption_datakey_opts_destroy(opts);
3044 	}
3045 
3046 	return NULL;
3047 } /* }}} */
3048 
phongo_clientencryption_create_datakey(php_phongo_clientencryption_t * clientencryption,zval * return_value,char * kms_provider,zval * options)3049 void phongo_clientencryption_create_datakey(php_phongo_clientencryption_t* clientencryption, zval* return_value, char* kms_provider, zval* options) /* {{{ */
3050 {
3051 	mongoc_client_encryption_datakey_opts_t* opts;
3052 	bson_value_t                             keyid;
3053 	bson_error_t                             error = { 0 };
3054 
3055 	opts = phongo_clientencryption_datakey_opts_from_zval(options);
3056 	if (!opts) {
3057 		/* Exception already thrown */
3058 		goto cleanup;
3059 	}
3060 
3061 	if (!mongoc_client_encryption_create_datakey(clientencryption->client_encryption, kms_provider, opts, &keyid, &error)) {
3062 		phongo_throw_exception_from_bson_error_t(&error);
3063 		goto cleanup;
3064 	}
3065 
3066 	if (!php_phongo_bson_value_to_zval(&keyid, return_value)) {
3067 		/* Exception already thrown */
3068 		goto cleanup;
3069 	}
3070 
3071 cleanup:
3072 	if (opts) {
3073 		mongoc_client_encryption_datakey_opts_destroy(opts);
3074 	}
3075 } /* }}} */
3076 
phongo_clientencryption_encrypt_opts_from_zval(zval * options)3077 static mongoc_client_encryption_encrypt_opts_t* phongo_clientencryption_encrypt_opts_from_zval(zval* options) /* {{{ */
3078 {
3079 	mongoc_client_encryption_encrypt_opts_t* opts;
3080 
3081 	opts = mongoc_client_encryption_encrypt_opts_new();
3082 
3083 	if (!options || Z_TYPE_P(options) != IS_ARRAY) {
3084 		return opts;
3085 	}
3086 
3087 	if (php_array_existsc(options, "keyId")) {
3088 		bson_value_t keyid;
3089 
3090 		php_phongo_zval_to_bson_value(php_array_fetchc(options, "keyId"), PHONGO_BSON_NONE, &keyid);
3091 		if (EG(exception)) {
3092 			goto cleanup;
3093 		}
3094 
3095 		mongoc_client_encryption_encrypt_opts_set_keyid(opts, &keyid);
3096 	}
3097 
3098 	if (php_array_existsc(options, "keyAltName")) {
3099 		char*     keyaltname;
3100 		int       plen;
3101 		zend_bool pfree;
3102 
3103 		keyaltname = php_array_fetch_string(options, "keyAltName", &plen, &pfree);
3104 		mongoc_client_encryption_encrypt_opts_set_keyaltname(opts, keyaltname);
3105 
3106 		if (pfree) {
3107 			efree(keyaltname);
3108 		}
3109 	}
3110 
3111 	if (php_array_existsc(options, "algorithm")) {
3112 		char*     algorithm;
3113 		int       plen;
3114 		zend_bool pfree;
3115 
3116 		algorithm = php_array_fetch_string(options, "algorithm", &plen, &pfree);
3117 		mongoc_client_encryption_encrypt_opts_set_algorithm(opts, algorithm);
3118 
3119 		if (pfree) {
3120 			efree(algorithm);
3121 		}
3122 	}
3123 
3124 	return opts;
3125 
3126 cleanup:
3127 	if (opts) {
3128 		mongoc_client_encryption_encrypt_opts_destroy(opts);
3129 	}
3130 
3131 	return NULL;
3132 } /* }}} */
3133 
phongo_clientencryption_encrypt(php_phongo_clientencryption_t * clientencryption,zval * zvalue,zval * zciphertext,zval * options)3134 void phongo_clientencryption_encrypt(php_phongo_clientencryption_t* clientencryption, zval* zvalue, zval* zciphertext, zval* options) /* {{{ */
3135 {
3136 	mongoc_client_encryption_encrypt_opts_t* opts;
3137 	bson_value_t                             ciphertext, value;
3138 	bson_error_t                             error = { 0 };
3139 
3140 	php_phongo_zval_to_bson_value(zvalue, PHONGO_BSON_NONE, &value);
3141 
3142 	opts = phongo_clientencryption_encrypt_opts_from_zval(options);
3143 	if (!opts) {
3144 		/* Exception already thrown */
3145 		goto cleanup;
3146 	}
3147 
3148 	if (!mongoc_client_encryption_encrypt(clientencryption->client_encryption, &value, opts, &ciphertext, &error)) {
3149 		phongo_throw_exception_from_bson_error_t(&error);
3150 		goto cleanup;
3151 	}
3152 
3153 	if (!php_phongo_bson_value_to_zval(&ciphertext, zciphertext)) {
3154 		/* Exception already thrown */
3155 		goto cleanup;
3156 	}
3157 
3158 cleanup:
3159 	if (opts) {
3160 		mongoc_client_encryption_encrypt_opts_destroy(opts);
3161 	}
3162 } /* }}} */
3163 
phongo_clientencryption_decrypt(php_phongo_clientencryption_t * clientencryption,zval * zciphertext,zval * zvalue)3164 void phongo_clientencryption_decrypt(php_phongo_clientencryption_t* clientencryption, zval* zciphertext, zval* zvalue) /* {{{ */
3165 {
3166 	bson_value_t ciphertext, value;
3167 	bson_error_t error = { 0 };
3168 
3169 	php_phongo_zval_to_bson_value(zciphertext, PHONGO_BSON_NONE, &ciphertext);
3170 
3171 	if (!mongoc_client_encryption_decrypt(clientencryption->client_encryption, &ciphertext, &value, &error)) {
3172 		phongo_throw_exception_from_bson_error_t(&error);
3173 		return;
3174 	}
3175 
3176 	if (!php_phongo_bson_value_to_zval(&value, zvalue)) {
3177 		/* Exception already thrown */
3178 		return;
3179 	}
3180 }
3181 /* }}} */
3182 #else /* MONGOC_ENABLE_CLIENT_SIDE_ENCRYPTION */
phongo_throw_exception_no_cse(php_phongo_error_domain_t domain,const char * message)3183 static void phongo_throw_exception_no_cse(php_phongo_error_domain_t domain, const char* message) /* {{{ */
3184 {
3185 	phongo_throw_exception(domain, "%s Please recompile with support for libmongocrypt using the with-mongodb-client-side-encryption configure switch.", message);
3186 }
3187 /* }}} */
3188 
phongo_manager_set_auto_encryption_opts(php_phongo_manager_t * manager,zval * driverOptions)3189 static bool phongo_manager_set_auto_encryption_opts(php_phongo_manager_t* manager, zval* driverOptions) /* {{{ */
3190 {
3191 	if (!driverOptions || !php_array_existsc(driverOptions, "autoEncryption")) {
3192 		return true;
3193 	}
3194 
3195 	phongo_throw_exception_no_cse(PHONGO_ERROR_INVALID_ARGUMENT, "Cannot enable automatic field-level encryption.");
3196 
3197 	return false;
3198 }
3199 /* }}} */
3200 
phongo_clientencryption_init(php_phongo_clientencryption_t * clientencryption,mongoc_client_t * client,zval * options)3201 void phongo_clientencryption_init(php_phongo_clientencryption_t* clientencryption, mongoc_client_t* client, zval* options) /* {{{ */
3202 {
3203 	phongo_throw_exception_no_cse(PHONGO_ERROR_RUNTIME, "Cannot configure clientEncryption object.");
3204 }
3205 /* }}} */
3206 
phongo_clientencryption_create_datakey(php_phongo_clientencryption_t * clientencryption,zval * return_value,char * kms_provider,zval * options)3207 void phongo_clientencryption_create_datakey(php_phongo_clientencryption_t* clientencryption, zval* return_value, char* kms_provider, zval* options) /* {{{ */
3208 {
3209 	phongo_throw_exception_no_cse(PHONGO_ERROR_RUNTIME, "Cannot create encryption key.");
3210 }
3211 /* }}} */
3212 
phongo_clientencryption_encrypt(php_phongo_clientencryption_t * clientencryption,zval * zvalue,zval * zciphertext,zval * options)3213 void phongo_clientencryption_encrypt(php_phongo_clientencryption_t* clientencryption, zval* zvalue, zval* zciphertext, zval* options) /* {{{ */
3214 {
3215 	phongo_throw_exception_no_cse(PHONGO_ERROR_RUNTIME, "Cannot encrypt value.");
3216 }
3217 /* }}} */
3218 
phongo_clientencryption_decrypt(php_phongo_clientencryption_t * clientencryption,zval * zciphertext,zval * zvalue)3219 void phongo_clientencryption_decrypt(php_phongo_clientencryption_t* clientencryption, zval* zciphertext, zval* zvalue) /* {{{ */
3220 {
3221 	phongo_throw_exception_no_cse(PHONGO_ERROR_RUNTIME, "Cannot decrypt value.");
3222 }
3223 /* }}} */
3224 #endif
3225 
phongo_manager_init(php_phongo_manager_t * manager,const char * uri_string,zval * options,zval * driverOptions)3226 void phongo_manager_init(php_phongo_manager_t* manager, const char* uri_string, zval* options, zval* driverOptions) /* {{{ */
3227 {
3228 	bson_t        bson_options = BSON_INITIALIZER;
3229 	mongoc_uri_t* uri          = NULL;
3230 #ifdef MONGOC_ENABLE_SSL
3231 	mongoc_ssl_opt_t* ssl_opt = NULL;
3232 #endif
3233 
3234 	if (!(manager->client_hash = php_phongo_manager_make_client_hash(uri_string, options, driverOptions, &manager->client_hash_len))) {
3235 		/* Exception should already have been thrown and there is nothing to free */
3236 		return;
3237 	}
3238 
3239 	if ((manager->client = php_phongo_find_client(manager->client_hash, manager->client_hash_len))) {
3240 		MONGOC_DEBUG("Found client for hash: %s\n", manager->client_hash);
3241 		goto cleanup;
3242 	}
3243 
3244 	if (options) {
3245 		php_phongo_zval_to_bson(options, PHONGO_BSON_NONE, &bson_options, NULL);
3246 	}
3247 
3248 	/* An exception may be thrown during BSON conversion */
3249 	if (EG(exception)) {
3250 		goto cleanup;
3251 	}
3252 
3253 	if (!(uri = php_phongo_make_uri(uri_string))) {
3254 		/* Exception should already have been thrown */
3255 		goto cleanup;
3256 	}
3257 
3258 	if (!php_phongo_apply_options_to_uri(uri, &bson_options) ||
3259 		!php_phongo_apply_rc_options_to_uri(uri, &bson_options) ||
3260 		!php_phongo_apply_rp_options_to_uri(uri, &bson_options) ||
3261 		!php_phongo_apply_wc_options_to_uri(uri, &bson_options)) {
3262 		/* Exception should already have been thrown */
3263 		goto cleanup;
3264 	}
3265 
3266 #ifdef MONGOC_ENABLE_SSL
3267 	if (!php_phongo_apply_driver_options_to_uri(uri, driverOptions)) {
3268 		/* Exception should already have been thrown */
3269 		goto cleanup;
3270 	}
3271 
3272 	ssl_opt = php_phongo_make_ssl_opt(uri, driverOptions);
3273 
3274 	/* An exception may be thrown during SSL option creation */
3275 	if (EG(exception)) {
3276 		goto cleanup;
3277 	}
3278 
3279 	if (!php_phongo_uri_finalize_tls(uri)) {
3280 		/* Exception should already have been thrown */
3281 		goto cleanup;
3282 	}
3283 #else
3284 	if (mongoc_uri_get_tls(uri)) {
3285 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Cannot create SSL client. SSL is not enabled in this build.");
3286 		goto cleanup;
3287 	}
3288 #endif
3289 
3290 	manager->client = php_phongo_make_mongo_client(uri, driverOptions);
3291 	mongoc_client_set_error_api(manager->client, MONGOC_ERROR_API_VERSION_2);
3292 
3293 	if (!manager->client) {
3294 		phongo_throw_exception(PHONGO_ERROR_RUNTIME, "Failed to create Manager from URI: '%s'", uri_string);
3295 		goto cleanup;
3296 	}
3297 
3298 #ifdef MONGOC_ENABLE_SSL
3299 	if (ssl_opt) {
3300 		mongoc_client_set_ssl_opts(manager->client, ssl_opt);
3301 	}
3302 #endif
3303 
3304 	if (!phongo_manager_set_auto_encryption_opts(manager, driverOptions)) {
3305 		/* Exception should already have been thrown */
3306 		goto cleanup;
3307 	}
3308 
3309 	MONGOC_DEBUG("Created client hash: %s\n", manager->client_hash);
3310 	php_phongo_persist_client(manager->client_hash, manager->client_hash_len, manager->client);
3311 
3312 cleanup:
3313 	bson_destroy(&bson_options);
3314 
3315 	if (uri) {
3316 		mongoc_uri_destroy(uri);
3317 	}
3318 
3319 #ifdef MONGOC_ENABLE_SSL
3320 	if (ssl_opt) {
3321 		php_phongo_free_ssl_opt(ssl_opt);
3322 	}
3323 #endif
3324 } /* }}} */
3325 
php_phongo_parse_int64(int64_t * retval,const char * data,size_t data_len)3326 bool php_phongo_parse_int64(int64_t* retval, const char* data, size_t data_len) /* {{{ */
3327 {
3328 	int64_t value;
3329 	char*   endptr = NULL;
3330 
3331 	/* bson_ascii_strtoll() sets errno if conversion fails. If conversion
3332 	 * succeeds, we still want to ensure that the entire string was parsed. */
3333 	value = bson_ascii_strtoll(data, &endptr, 10);
3334 
3335 	if (errno || (endptr && endptr != ((const char*) data + data_len))) {
3336 		return false;
3337 	}
3338 
3339 	*retval = value;
3340 
3341 	return true;
3342 } /* }}} */
3343 
3344 /* {{{ Memory allocation wrappers */
php_phongo_malloc(size_t num_bytes)3345 static void* php_phongo_malloc(size_t num_bytes) /* {{{ */
3346 {
3347 	return pemalloc(num_bytes, 1);
3348 } /* }}} */
3349 
php_phongo_calloc(size_t num_members,size_t num_bytes)3350 static void* php_phongo_calloc(size_t num_members, size_t num_bytes) /* {{{ */
3351 {
3352 	return pecalloc(num_members, num_bytes, 1);
3353 } /* }}} */
3354 
php_phongo_realloc(void * mem,size_t num_bytes)3355 static void* php_phongo_realloc(void* mem, size_t num_bytes)
3356 { /* {{{ */
3357 	return perealloc(mem, num_bytes, 1);
3358 } /* }}} */
3359 
php_phongo_free(void * mem)3360 static void php_phongo_free(void* mem) /* {{{ */
3361 {
3362 	if (mem) {
3363 		pefree(mem, 1);
3364 	}
3365 } /* }}} */
3366 
3367 /* }}} */
3368 
3369 /* {{{ M[INIT|SHUTDOWN] R[INIT|SHUTDOWN] G[INIT|SHUTDOWN] MINFO INI */
3370 
ZEND_INI_MH(OnUpdateDebug)3371 ZEND_INI_MH(OnUpdateDebug)
3372 {
3373 	void*** ctx     = NULL;
3374 	char*   tmp_dir = NULL;
3375 
3376 	/* Close any previously open log files */
3377 	if (MONGODB_G(debug_fd)) {
3378 		if (MONGODB_G(debug_fd) != stderr && MONGODB_G(debug_fd) != stdout) {
3379 			fclose(MONGODB_G(debug_fd));
3380 		}
3381 		MONGODB_G(debug_fd) = NULL;
3382 	}
3383 
3384 	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) {
3385 		mongoc_log_trace_disable();
3386 		mongoc_log_set_handler(NULL, NULL);
3387 
3388 		return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
3389 	}
3390 
3391 	if (strcasecmp(ZSTR_VAL(new_value), "stderr") == 0) {
3392 		MONGODB_G(debug_fd) = stderr;
3393 	} else if (strcasecmp(ZSTR_VAL(new_value), "stdout") == 0) {
3394 		MONGODB_G(debug_fd) = stdout;
3395 	} else if (
3396 		strcasecmp("1", ZSTR_VAL(new_value)) == 0 ||
3397 		strcasecmp("on", ZSTR_VAL(new_value)) == 0 ||
3398 		strcasecmp("yes", ZSTR_VAL(new_value)) == 0 ||
3399 		strcasecmp("true", ZSTR_VAL(new_value)) == 0) {
3400 
3401 		tmp_dir = NULL;
3402 	} else {
3403 		tmp_dir = ZSTR_VAL(new_value);
3404 	}
3405 
3406 	if (!MONGODB_G(debug_fd)) {
3407 		time_t       t;
3408 		int          fd = -1;
3409 		char*        prefix;
3410 		int          len;
3411 		zend_string* filename;
3412 
3413 		time(&t);
3414 		len = spprintf(&prefix, 0, "PHONGO-%ld", t);
3415 
3416 		fd = php_open_temporary_fd(tmp_dir, prefix, &filename);
3417 		if (fd != -1) {
3418 			const char* path    = ZSTR_VAL(filename);
3419 			MONGODB_G(debug_fd) = VCWD_FOPEN(path, "a");
3420 		}
3421 		efree(filename);
3422 		efree(prefix);
3423 		close(fd);
3424 	}
3425 
3426 	mongoc_log_trace_enable();
3427 	mongoc_log_set_handler(php_phongo_log, ctx);
3428 
3429 	return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
3430 }
3431 
3432 /* {{{ INI entries */
3433 PHP_INI_BEGIN()
STD_PHP_INI_ENTRY(PHONGO_DEBUG_INI,PHONGO_DEBUG_INI_DEFAULT,PHP_INI_ALL,OnUpdateDebug,debug,zend_mongodb_globals,mongodb_globals)3434 	STD_PHP_INI_ENTRY(PHONGO_DEBUG_INI, PHONGO_DEBUG_INI_DEFAULT, PHP_INI_ALL, OnUpdateDebug, debug, zend_mongodb_globals, mongodb_globals)
3435 PHP_INI_END()
3436 /* }}} */
3437 
3438 static void phongo_pclient_reset_once(php_phongo_pclient_t* pclient, int pid)
3439 {
3440 	if (pclient->last_reset_by_pid != pid) {
3441 		mongoc_client_reset(pclient->client);
3442 		pclient->last_reset_by_pid = pid;
3443 	}
3444 }
3445 
3446 /* Resets the libmongoc client if it has not already been reset for the current
3447  * PID (based on information in the hash table of persisted libmongoc clients).
3448  * This ensures that we do not reset a client multiple times from the same child
3449  * process. */
php_phongo_client_reset_once(mongoc_client_t * client,int pid)3450 void php_phongo_client_reset_once(mongoc_client_t* client, int pid)
3451 {
3452 	HashTable*            pclients;
3453 	zval*                 z_ptr;
3454 	php_phongo_pclient_t* pclient;
3455 
3456 	pclients = &MONGODB_G(pclients);
3457 
3458 	ZEND_HASH_FOREACH_VAL(pclients, z_ptr)
3459 	{
3460 		if ((Z_TYPE_P(z_ptr) != IS_PTR)) {
3461 			continue;
3462 		}
3463 
3464 		pclient = (php_phongo_pclient_t*) Z_PTR_P(z_ptr);
3465 
3466 		if (pclient->client == client) {
3467 			phongo_pclient_reset_once(pclient, pid);
3468 			return;
3469 		}
3470 	}
3471 	ZEND_HASH_FOREACH_END();
3472 }
3473 
php_phongo_pclient_destroy(php_phongo_pclient_t * pclient)3474 static inline void php_phongo_pclient_destroy(php_phongo_pclient_t* pclient)
3475 {
3476 	/* Do not destroy mongoc_client_t objects created by other processes. This
3477 	 * ensures that we do not shutdown sockets that may still be in use by our
3478 	 * parent process (see: CDRIVER-2049). While this is a leak, we are already
3479 	 * in MSHUTDOWN at this point. */
3480 	if (pclient->created_by_pid == getpid()) {
3481 		mongoc_client_destroy(pclient->client);
3482 	}
3483 
3484 	pefree(pclient, 1);
3485 }
3486 
3487 /* {{{ PHP_RINIT_FUNCTION */
PHP_RINIT_FUNCTION(mongodb)3488 PHP_RINIT_FUNCTION(mongodb)
3489 {
3490 	/* Initialize HashTable for APM subscribers, which is initialized to NULL in
3491 	 * GINIT and destroyed and reset to NULL in RSHUTDOWN. */
3492 	if (MONGODB_G(subscribers) == NULL) {
3493 		ALLOC_HASHTABLE(MONGODB_G(subscribers));
3494 		zend_hash_init(MONGODB_G(subscribers), 0, NULL, ZVAL_PTR_DTOR, 0);
3495 	}
3496 
3497 	return SUCCESS;
3498 }
3499 /* }}} */
3500 
3501 /* {{{ PHP_GINIT_FUNCTION */
PHP_GINIT_FUNCTION(mongodb)3502 PHP_GINIT_FUNCTION(mongodb)
3503 {
3504 	bson_mem_vtable_t bsonMemVTable = {
3505 		php_phongo_malloc,
3506 		php_phongo_calloc,
3507 		php_phongo_realloc,
3508 		php_phongo_free,
3509 	};
3510 #if defined(COMPILE_DL_MONGODB) && defined(ZTS)
3511 	ZEND_TSRMLS_CACHE_UPDATE();
3512 #endif
3513 	memset(mongodb_globals, 0, sizeof(zend_mongodb_globals));
3514 	mongodb_globals->bsonMemVTable = bsonMemVTable;
3515 
3516 	/* Initialize HashTable for persistent clients */
3517 	zend_hash_init(&mongodb_globals->pclients, 0, NULL, NULL, 1);
3518 }
3519 /* }}} */
3520 
php_phongo_fetch_internal_class(const char * class_name,size_t class_name_len)3521 static zend_class_entry* php_phongo_fetch_internal_class(const char* class_name, size_t class_name_len)
3522 {
3523 	zend_class_entry* pce;
3524 
3525 	if ((pce = zend_hash_str_find_ptr(CG(class_table), class_name, class_name_len))) {
3526 		return pce;
3527 	}
3528 
3529 	return NULL;
3530 }
3531 
php_phongo_std_get_gc(phongo_compat_object_handler_type * object,zval ** table,int * n)3532 static HashTable* php_phongo_std_get_gc(phongo_compat_object_handler_type* object, zval** table, int* n) /* {{{ */
3533 {
3534 	*table = NULL;
3535 	*n     = 0;
3536 	return zend_std_get_properties(object);
3537 } /* }}} */
3538 
3539 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(mongodb)3540 PHP_MINIT_FUNCTION(mongodb)
3541 {
3542 	(void) type; /* We don't care if we are loaded via dl() or extension= */
3543 
3544 	REGISTER_INI_ENTRIES();
3545 
3546 	/* Initialize libmongoc */
3547 	mongoc_init();
3548 
3549 	/* Initialize libbson */
3550 	bson_mem_set_vtable(&MONGODB_G(bsonMemVTable));
3551 
3552 	/* Prep default object handlers to be used when we register the classes */
3553 	memcpy(&phongo_std_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
3554 	/* Disable cloning by default. Individual classes can opt in if they need to
3555 	 * support this (e.g. BSON objects). */
3556 	phongo_std_object_handlers.clone_obj = NULL;
3557 	/* Ensure that get_gc delegates to zend_std_get_properties directly in case
3558 	 * our class defines a get_properties handler for debugging purposes. */
3559 	phongo_std_object_handlers.get_gc = php_phongo_std_get_gc;
3560 
3561 	/* Initialize zend_class_entry dependencies.
3562 	 *
3563 	 * Although DateTimeImmutable was introduced in PHP 5.5.0,
3564 	 * php_date_get_immutable_ce() is not available in PHP versions before
3565 	 * 5.5.24 and 5.6.8.
3566 	 *
3567 	 * Although JsonSerializable was introduced in PHP 5.4.0,
3568 	 * php_json_serializable_ce is not exported in PHP versions before 5.4.26
3569 	 * and 5.5.10. For later PHP versions, looking up the class manually also
3570 	 * helps with distros that disable LTDL_LAZY for dlopen() (e.g. Fedora).
3571 	 */
3572 	php_phongo_date_immutable_ce    = php_phongo_fetch_internal_class(ZEND_STRL("datetimeimmutable"));
3573 	php_phongo_json_serializable_ce = php_phongo_fetch_internal_class(ZEND_STRL("jsonserializable"));
3574 
3575 	if (php_phongo_json_serializable_ce == NULL) {
3576 		zend_error(E_ERROR, "JsonSerializable class is not defined. Please ensure that the 'json' module is loaded before the 'mongodb' module.");
3577 		return FAILURE;
3578 	}
3579 
3580 	/* Register base BSON classes first */
3581 	php_phongo_type_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3582 	php_phongo_serializable_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3583 	php_phongo_unserializable_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3584 
3585 	php_phongo_binary_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3586 	php_phongo_decimal128_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3587 	php_phongo_javascript_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3588 	php_phongo_maxkey_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3589 	php_phongo_minkey_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3590 	php_phongo_objectid_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3591 	php_phongo_regex_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3592 	php_phongo_timestamp_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3593 	php_phongo_utcdatetime_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3594 
3595 	php_phongo_binary_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3596 	php_phongo_dbpointer_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3597 	php_phongo_decimal128_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3598 	php_phongo_int64_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3599 	php_phongo_javascript_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3600 	php_phongo_maxkey_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3601 	php_phongo_minkey_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3602 	php_phongo_objectid_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3603 	php_phongo_persistable_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3604 	php_phongo_regex_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3605 	php_phongo_symbol_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3606 	php_phongo_timestamp_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3607 	php_phongo_undefined_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3608 	php_phongo_utcdatetime_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3609 
3610 	php_phongo_cursor_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3611 
3612 	php_phongo_bulkwrite_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3613 	php_phongo_clientencryption_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3614 	php_phongo_command_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3615 	php_phongo_cursor_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3616 	php_phongo_cursorid_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3617 	php_phongo_manager_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3618 	php_phongo_query_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3619 	php_phongo_readconcern_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3620 	php_phongo_readpreference_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3621 	php_phongo_server_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3622 	php_phongo_session_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3623 	php_phongo_writeconcern_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3624 	php_phongo_writeconcernerror_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3625 	php_phongo_writeerror_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3626 	php_phongo_writeresult_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3627 
3628 	/* Register base exception classes first */
3629 	php_phongo_exception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3630 	php_phongo_runtimeexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3631 	php_phongo_serverexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3632 	php_phongo_connectionexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3633 	php_phongo_writeexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3634 
3635 	php_phongo_authenticationexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3636 	php_phongo_bulkwriteexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3637 	php_phongo_commandexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3638 	php_phongo_connectiontimeoutexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3639 	php_phongo_encryptionexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3640 	php_phongo_executiontimeoutexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3641 	php_phongo_invalidargumentexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3642 	php_phongo_logicexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3643 	php_phongo_sslconnectionexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3644 	php_phongo_unexpectedvalueexception_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3645 
3646 	/* Register base APM classes first */
3647 	php_phongo_subscriber_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3648 	php_phongo_commandsubscriber_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3649 	php_phongo_commandfailedevent_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3650 	php_phongo_commandstartedevent_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3651 	php_phongo_commandsucceededevent_init_ce(INIT_FUNC_ARGS_PASSTHRU);
3652 
3653 	REGISTER_STRING_CONSTANT("MONGODB_VERSION", (char*) PHP_MONGODB_VERSION, CONST_CS | CONST_PERSISTENT);
3654 	REGISTER_STRING_CONSTANT("MONGODB_STABILITY", (char*) PHP_MONGODB_STABILITY, CONST_CS | CONST_PERSISTENT);
3655 
3656 	return SUCCESS;
3657 }
3658 /* }}} */
3659 
3660 /* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(mongodb)3661 PHP_MSHUTDOWN_FUNCTION(mongodb)
3662 {
3663 	HashTable* pclients = &MONGODB_G(pclients);
3664 	zval*      z_ptr;
3665 	(void) type; /* We don't care if we are loaded via dl() or extension= */
3666 
3667 	/* Destroy mongoc_client_t objects in reverse order. This is necessary to
3668 	 * prevent segmentation faults as clients may reference other clients in
3669 	 * encryption settings. */
3670 	ZEND_HASH_REVERSE_FOREACH_VAL(pclients, z_ptr)
3671 	{
3672 		if ((Z_TYPE_P(z_ptr) != IS_PTR)) {
3673 			continue;
3674 		}
3675 
3676 		php_phongo_pclient_destroy((php_phongo_pclient_t*) Z_PTR_P(z_ptr));
3677 	}
3678 	ZEND_HASH_FOREACH_END();
3679 
3680 	/* Destroy HashTable for persistent clients. mongoc_client_t objects have been destroyed earlier. */
3681 	zend_hash_destroy(&MONGODB_G(pclients));
3682 
3683 	bson_mem_restore_vtable();
3684 	/* Cleanup after libmongoc */
3685 	mongoc_cleanup();
3686 
3687 	UNREGISTER_INI_ENTRIES();
3688 
3689 	return SUCCESS;
3690 }
3691 /* }}} */
3692 
3693 /* {{{ PHP_RSHUTDOWN_FUNCTION */
PHP_RSHUTDOWN_FUNCTION(mongodb)3694 PHP_RSHUTDOWN_FUNCTION(mongodb)
3695 {
3696 	/* Destroy HashTable for APM subscribers, which was initialized in RINIT */
3697 	if (MONGODB_G(subscribers)) {
3698 		zend_hash_destroy(MONGODB_G(subscribers));
3699 		FREE_HASHTABLE(MONGODB_G(subscribers));
3700 		MONGODB_G(subscribers) = NULL;
3701 	}
3702 
3703 	return SUCCESS;
3704 }
3705 /* }}} */
3706 
3707 /* {{{ PHP_GSHUTDOWN_FUNCTION */
PHP_GSHUTDOWN_FUNCTION(mongodb)3708 PHP_GSHUTDOWN_FUNCTION(mongodb)
3709 {
3710 	mongodb_globals->debug = NULL;
3711 	if (mongodb_globals->debug_fd) {
3712 		fclose(mongodb_globals->debug_fd);
3713 		mongodb_globals->debug_fd = NULL;
3714 	}
3715 }
3716 /* }}} */
3717 
3718 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(mongodb)3719 PHP_MINFO_FUNCTION(mongodb)
3720 {
3721 	php_info_print_table_start();
3722 	php_info_print_table_header(2, "MongoDB support", "enabled");
3723 	php_info_print_table_row(2, "MongoDB extension version", PHP_MONGODB_VERSION);
3724 	php_info_print_table_row(2, "MongoDB extension stability", PHP_MONGODB_STABILITY);
3725 
3726 #ifdef HAVE_SYSTEM_LIBBSON
3727 	php_info_print_table_row(2, "libbson headers version", BSON_VERSION_S);
3728 	php_info_print_table_row(2, "libbson library version", bson_get_version());
3729 #else
3730 	php_info_print_table_row(2, "libbson bundled version", BSON_VERSION_S);
3731 #endif
3732 
3733 #ifdef HAVE_SYSTEM_LIBMONGOC
3734 	php_info_print_table_row(2, "libmongoc headers version", MONGOC_VERSION_S);
3735 	php_info_print_table_row(2, "libmongoc library version", mongoc_get_version());
3736 #else
3737 	/* Bundled libraries, buildtime = runtime */
3738 	php_info_print_table_row(2, "libmongoc bundled version", MONGOC_VERSION_S);
3739 #endif
3740 
3741 #ifdef MONGOC_ENABLE_SSL
3742 	php_info_print_table_row(2, "libmongoc SSL", "enabled");
3743 #if defined(MONGOC_ENABLE_SSL_OPENSSL)
3744 	php_info_print_table_row(2, "libmongoc SSL library", "OpenSSL");
3745 #elif defined(MONGOC_ENABLE_SSL_LIBRESSL)
3746 	php_info_print_table_row(2, "libmongoc SSL library", "LibreSSL");
3747 #elif defined(MONGOC_ENABLE_SSL_SECURE_TRANSPORT)
3748 	php_info_print_table_row(2, "libmongoc SSL library", "Secure Transport");
3749 #elif defined(MONGOC_ENABLE_SSL_SECURE_CHANNEL)
3750 	php_info_print_table_row(2, "libmongoc SSL library", "Secure Channel");
3751 #else
3752 	php_info_print_table_row(2, "libmongoc SSL library", "unknown");
3753 #endif
3754 #else /* MONGOC_ENABLE_SSL */
3755 	php_info_print_table_row(2, "libmongoc SSL", "disabled");
3756 #endif
3757 
3758 #ifdef MONGOC_ENABLE_CRYPTO
3759 	php_info_print_table_row(2, "libmongoc crypto", "enabled");
3760 #if defined(MONGOC_ENABLE_CRYPTO_LIBCRYPTO)
3761 	php_info_print_table_row(2, "libmongoc crypto library", "libcrypto");
3762 #elif defined(MONGOC_ENABLE_CRYPTO_COMMON_CRYPTO)
3763 	php_info_print_table_row(2, "libmongoc crypto library", "Common Crypto");
3764 #elif defined(MONGOC_ENABLE_CRYPTO_CNG)
3765 	php_info_print_table_row(2, "libmongoc crypto library", "CNG");
3766 #else
3767 	php_info_print_table_row(2, "libmongoc crypto library", "unknown");
3768 #endif
3769 #ifdef MONGOC_ENABLE_CRYPTO_SYSTEM_PROFILE
3770 	php_info_print_table_row(2, "libmongoc crypto system profile", "enabled");
3771 #else
3772 	php_info_print_table_row(2, "libmongoc crypto system profile", "disabled");
3773 #endif
3774 #else /* MONGOC_ENABLE_CRYPTO */
3775 	php_info_print_table_row(2, "libmongoc crypto", "disabled");
3776 #endif
3777 
3778 #ifdef MONGOC_ENABLE_SASL
3779 	php_info_print_table_row(2, "libmongoc SASL", "enabled");
3780 #else
3781 	php_info_print_table_row(2, "libmongoc SASL", "disabled");
3782 #endif
3783 
3784 #ifdef MONGOC_ENABLE_ICU
3785 	php_info_print_table_row(2, "libmongoc ICU", "enabled");
3786 #else
3787 	php_info_print_table_row(2, "libmongoc ICU", "disabled");
3788 #endif
3789 
3790 #ifdef MONGOC_ENABLE_COMPRESSION
3791 	php_info_print_table_row(2, "libmongoc compression", "enabled");
3792 #ifdef MONGOC_ENABLE_COMPRESSION_SNAPPY
3793 	php_info_print_table_row(2, "libmongoc compression snappy", "enabled");
3794 #else
3795 	php_info_print_table_row(2, "libmongoc compression snappy", "disabled");
3796 #endif
3797 #ifdef MONGOC_ENABLE_COMPRESSION_ZLIB
3798 	php_info_print_table_row(2, "libmongoc compression zlib", "enabled");
3799 #else
3800 	php_info_print_table_row(2, "libmongoc compression zlib", "disabled");
3801 #endif
3802 #ifdef MONGOC_ENABLE_COMPRESSION_ZSTD
3803 	php_info_print_table_row(2, "libmongoc compression zstd", "enabled");
3804 #else
3805 	php_info_print_table_row(2, "libmongoc compression zstd", "disabled");
3806 #endif
3807 #else /* MONGOC_ENABLE_COMPRESSION */
3808 	php_info_print_table_row(2, "libmongoc compression", "disabled");
3809 #endif
3810 
3811 #ifdef MONGOC_ENABLE_CLIENT_SIDE_ENCRYPTION
3812 #ifdef HAVE_SYSTEM_LIBMONGOCRYPT
3813 	php_info_print_table_row(2, "libmongocrypt headers version", MONGOCRYPT_VERSION);
3814 	php_info_print_table_row(2, "libmongocrypt library version", mongocrypt_version(NULL));
3815 #else
3816 	php_info_print_table_row(2, "libmongocrypt bundled version", MONGOCRYPT_VERSION);
3817 #endif
3818 
3819 #ifdef MONGOCRYPT_ENABLE_CRYPTO
3820 	php_info_print_table_row(2, "libmongocrypt crypto", "enabled");
3821 
3822 #if defined(MONGOCRYPT_ENABLE_CRYPTO_LIBCRYPTO)
3823 	php_info_print_table_row(2, "libmongocrypt crypto library", "libcrypto");
3824 #elif defined(MONGOCRYPT_ENABLE_CRYPTO_COMMON_CRYPTO)
3825 	php_info_print_table_row(2, "libmongocrypt crypto library", "Common Crypto");
3826 #elif defined(MONGOCRYPT_ENABLE_CRYPTO_CNG)
3827 	php_info_print_table_row(2, "libmongocrypt crypto library", "CNG");
3828 #else
3829 	php_info_print_table_row(2, "libmongocrypt crypto library", "unknown");
3830 #endif
3831 #else /* MONGOCRYPT_ENABLE_CRYPTO */
3832 	php_info_print_table_row(2, "libmongocrypt crypto", "disabled");
3833 #endif
3834 #else /* MONGOC_ENABLE_CLIENT_SIDE_ENCRYPTION */
3835 	php_info_print_table_row(2, "libmongocrypt", "disabled");
3836 #endif
3837 
3838 	php_info_print_table_end();
3839 
3840 	DISPLAY_INI_ENTRIES();
3841 }
3842 /* }}} */
3843 /* }}} */
3844 
3845 /* {{{ Shared function entries for disabling constructors and unserialize() */
PHP_FUNCTION(MongoDB_disabled___construct)3846 PHP_FUNCTION(MongoDB_disabled___construct) /* {{{ */
3847 {
3848 	phongo_throw_exception(PHONGO_ERROR_RUNTIME, "Accessing private constructor");
3849 } /* }}} */
3850 
PHP_FUNCTION(MongoDB_disabled___wakeup)3851 PHP_FUNCTION(MongoDB_disabled___wakeup) /* {{{ */
3852 {
3853 	zend_error_handling error_handling;
3854 
3855 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
3856 	if (zend_parse_parameters_none() == FAILURE) {
3857 		zend_restore_error_handling(&error_handling);
3858 		return;
3859 	}
3860 	zend_restore_error_handling(&error_handling);
3861 
3862 	phongo_throw_exception(PHONGO_ERROR_RUNTIME, "%s", "MongoDB\\Driver objects cannot be serialized");
3863 } /* }}} */
3864   /* }}} */
3865 
3866 /* {{{ mongodb_functions[]
3867 */
3868 ZEND_BEGIN_ARG_INFO_EX(ai_bson_fromPHP, 0, 0, 1)
3869 	ZEND_ARG_INFO(0, value)
3870 ZEND_END_ARG_INFO();
3871 
3872 ZEND_BEGIN_ARG_INFO_EX(ai_bson_toPHP, 0, 0, 1)
3873 	ZEND_ARG_INFO(0, bson)
3874 	ZEND_ARG_ARRAY_INFO(0, typemap, 0)
3875 ZEND_END_ARG_INFO();
3876 
3877 ZEND_BEGIN_ARG_INFO_EX(ai_bson_toJSON, 0, 0, 1)
3878 	ZEND_ARG_INFO(0, bson)
3879 ZEND_END_ARG_INFO();
3880 
3881 ZEND_BEGIN_ARG_INFO_EX(ai_bson_fromJSON, 0, 0, 1)
3882 	ZEND_ARG_INFO(0, json)
3883 ZEND_END_ARG_INFO();
3884 
3885 ZEND_BEGIN_ARG_INFO_EX(ai_mongodb_driver_monitoring_subscriber, 0, 0, 1)
3886 	ZEND_ARG_OBJ_INFO(0, subscriber, MongoDB\\Driver\\Monitoring\\Subscriber, 0)
3887 ZEND_END_ARG_INFO();
3888 
3889 static const zend_function_entry mongodb_functions[] = {
3890 	ZEND_NS_NAMED_FE("MongoDB\\BSON", fromPHP, PHP_FN(MongoDB_BSON_fromPHP), ai_bson_fromPHP)
3891 		ZEND_NS_NAMED_FE("MongoDB\\BSON", toPHP, PHP_FN(MongoDB_BSON_toPHP), ai_bson_toPHP)
3892 			ZEND_NS_NAMED_FE("MongoDB\\BSON", toJSON, PHP_FN(MongoDB_BSON_toJSON), ai_bson_toJSON)
3893 				ZEND_NS_NAMED_FE("MongoDB\\BSON", toCanonicalExtendedJSON, PHP_FN(MongoDB_BSON_toCanonicalExtendedJSON), ai_bson_toJSON)
3894 					ZEND_NS_NAMED_FE("MongoDB\\BSON", toRelaxedExtendedJSON, PHP_FN(MongoDB_BSON_toRelaxedExtendedJSON), ai_bson_toJSON)
3895 						ZEND_NS_NAMED_FE("MongoDB\\BSON", fromJSON, PHP_FN(MongoDB_BSON_fromJSON), ai_bson_fromJSON)
3896 							ZEND_NS_NAMED_FE("MongoDB\\Driver\\Monitoring", addSubscriber, PHP_FN(MongoDB_Driver_Monitoring_addSubscriber), ai_mongodb_driver_monitoring_subscriber)
3897 								ZEND_NS_NAMED_FE("MongoDB\\Driver\\Monitoring", removeSubscriber, PHP_FN(MongoDB_Driver_Monitoring_removeSubscriber), ai_mongodb_driver_monitoring_subscriber)
3898 									PHP_FE_END
3899 };
3900 /* }}} */
3901 
3902 static const zend_module_dep mongodb_deps[] = {
3903 	ZEND_MOD_REQUIRED("date")
3904 		ZEND_MOD_REQUIRED("json")
3905 			ZEND_MOD_REQUIRED("spl")
3906 				ZEND_MOD_REQUIRED("standard")
3907 					ZEND_MOD_END
3908 };
3909 
3910 /* {{{ mongodb_module_entry
3911  */
3912 zend_module_entry mongodb_module_entry = {
3913 	STANDARD_MODULE_HEADER_EX,
3914 	NULL,
3915 	mongodb_deps,
3916 	"mongodb",
3917 	mongodb_functions,
3918 	PHP_MINIT(mongodb),
3919 	PHP_MSHUTDOWN(mongodb),
3920 	PHP_RINIT(mongodb),
3921 	PHP_RSHUTDOWN(mongodb),
3922 	PHP_MINFO(mongodb),
3923 	PHP_MONGODB_VERSION,
3924 	PHP_MODULE_GLOBALS(mongodb),
3925 	PHP_GINIT(mongodb),
3926 	PHP_GSHUTDOWN(mongodb),
3927 	NULL,
3928 	STANDARD_MODULE_PROPERTIES_EX
3929 };
3930 /* }}} */
3931 
3932 #ifdef COMPILE_DL_MONGODB
3933 ZEND_GET_MODULE(mongodb)
3934 #endif
3935 
3936 /*
3937  * Local variables:
3938  * tab-width: 4
3939  * c-basic-offset: 4
3940  * End:
3941  * vim600: noet sw=4 ts=4 fdm=marker
3942  * vim<600: noet sw=4 ts=4
3943  */
3944