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