1 /* MaxMind, Inc., licenses this file to you under the Apache License, Version
2  * 2.0 (the "License"); you may not use this file except in compliance with
3  * the License.  You may obtain a copy of the License at
4  *
5  * http://www.apache.org/licenses/LICENSE-2.0
6  *
7  * Unless required by applicable law or agreed to in writing, software
8  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
10  * License for the specific language governing permissions and limitations
11  * under the License.
12  */
13 
14 #include "php_maxminddb.h"
15 
16 #ifdef HAVE_CONFIG_H
17 #include "config.h"
18 #endif
19 
20 #include <php.h>
21 #include <zend.h>
22 
23 #include "Zend/zend_exceptions.h"
24 #include "ext/standard/info.h"
25 #include <maxminddb.h>
26 
27 #ifdef ZTS
28 #include <TSRM.h>
29 #endif
30 
31 #define __STDC_FORMAT_MACROS
32 #include <inttypes.h>
33 
34 #define PHP_MAXMINDDB_NS ZEND_NS_NAME("MaxMind", "Db")
35 #define PHP_MAXMINDDB_READER_NS ZEND_NS_NAME(PHP_MAXMINDDB_NS, "Reader")
36 #define PHP_MAXMINDDB_READER_EX_NS                                             \
37     ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, "InvalidDatabaseException")
38 
39 #ifdef ZEND_ENGINE_3
40 #define Z_MAXMINDDB_P(zv) php_maxminddb_fetch_object(Z_OBJ_P(zv))
41 #define _ZVAL_STRING ZVAL_STRING
42 #define _ZVAL_STRINGL ZVAL_STRINGL
43 typedef size_t strsize_t;
44 typedef zend_object free_obj_t;
45 #else
46 #define Z_MAXMINDDB_P(zv)                                                      \
47     (maxminddb_obj *)zend_object_store_get_object(zv TSRMLS_CC)
48 #define _ZVAL_STRING(a, b) ZVAL_STRING(a, b, 1)
49 #define _ZVAL_STRINGL(a, b, c) ZVAL_STRINGL(a, b, c, 1)
50 typedef int strsize_t;
51 typedef void free_obj_t;
52 #endif
53 
54 /* For PHP 8 compatibility */
55 #ifndef TSRMLS_C
56 #define TSRMLS_C
57 #endif
58 #ifndef TSRMLS_CC
59 #define TSRMLS_CC
60 #endif
61 #ifndef TSRMLS_DC
62 #define TSRMLS_DC
63 #endif
64 #ifndef ZEND_ACC_CTOR
65 #define ZEND_ACC_CTOR 0
66 #endif
67 
68 #ifdef ZEND_ENGINE_3
69 typedef struct _maxminddb_obj {
70     MMDB_s *mmdb;
71     zend_object std;
72 } maxminddb_obj;
73 #else
74 typedef struct _maxminddb_obj {
75     zend_object std;
76     MMDB_s *mmdb;
77 } maxminddb_obj;
78 #endif
79 
80 PHP_FUNCTION(maxminddb);
81 
82 static void
83 get_record(INTERNAL_FUNCTION_PARAMETERS, zval *record, int *prefix_len);
84 static const MMDB_entry_data_list_s *
85 handle_entry_data_list(const MMDB_entry_data_list_s *entry_data_list,
86                        zval *z_value TSRMLS_DC);
87 static const MMDB_entry_data_list_s *
88 handle_array(const MMDB_entry_data_list_s *entry_data_list,
89              zval *z_value TSRMLS_DC);
90 static const MMDB_entry_data_list_s *
91 handle_map(const MMDB_entry_data_list_s *entry_data_list,
92            zval *z_value TSRMLS_DC);
93 static void handle_uint128(const MMDB_entry_data_list_s *entry_data_list,
94                            zval *z_value TSRMLS_DC);
95 static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list,
96                           zval *z_value TSRMLS_DC);
97 static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list,
98                           zval *z_value TSRMLS_DC);
99 static zend_class_entry *lookup_class(const char *name TSRMLS_DC);
100 
101 #define CHECK_ALLOCATED(val)                                                   \
102     if (!val) {                                                                \
103         zend_error(E_ERROR, "Out of memory");                                  \
104         return;                                                                \
105     }
106 
107 #define THROW_EXCEPTION(name, ...)                                             \
108     {                                                                          \
109         zend_class_entry *exception_ce = lookup_class(name TSRMLS_CC);         \
110         zend_throw_exception_ex(exception_ce, 0 TSRMLS_CC, __VA_ARGS__);       \
111     }
112 
113 #if PHP_VERSION_ID < 50399
114 #define object_properties_init(zo, class_type)                                 \
115     {                                                                          \
116         zval *tmp;                                                             \
117         zend_hash_copy((*zo).properties,                                       \
118                        &class_type->default_properties,                        \
119                        (copy_ctor_func_t)zval_add_ref,                         \
120                        (void *)&tmp,                                           \
121                        sizeof(zval *));                                        \
122     }
123 #endif
124 
125 static zend_object_handlers maxminddb_obj_handlers;
126 static zend_class_entry *maxminddb_ce;
127 
128 static inline maxminddb_obj *
129 php_maxminddb_fetch_object(zend_object *obj TSRMLS_DC) {
130 #ifdef ZEND_ENGINE_3
131     return (maxminddb_obj *)((char *)(obj)-XtOffsetOf(maxminddb_obj, std));
132 #else
133     return (maxminddb_obj *)obj;
134 #endif
135 }
136 
137 ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_construct, 0, 0, 1)
138 ZEND_ARG_INFO(0, db_file)
139 ZEND_END_ARG_INFO()
140 
141 PHP_METHOD(MaxMind_Db_Reader, __construct) {
142     char *db_file = NULL;
143     strsize_t name_len;
144     zval *_this_zval = NULL;
145 
146     if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
147                                      getThis(),
148                                      "Os",
149                                      &_this_zval,
150                                      maxminddb_ce,
151                                      &db_file,
152                                      &name_len) == FAILURE) {
153         THROW_EXCEPTION("InvalidArgumentException",
154                         "The constructor takes exactly one argument.");
155         return;
156     }
157 
158     if (0 != php_check_open_basedir(db_file TSRMLS_CC) ||
159         0 != access(db_file, R_OK)) {
160         THROW_EXCEPTION("InvalidArgumentException",
161                         "The file \"%s\" does not exist or is not readable.",
162                         db_file);
163         return;
164     }
165 
166     MMDB_s *mmdb = (MMDB_s *)emalloc(sizeof(MMDB_s));
167     uint16_t status = MMDB_open(db_file, MMDB_MODE_MMAP, mmdb);
168 
169     if (MMDB_SUCCESS != status) {
170         THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
171                         "Error opening database file (%s). Is this a valid "
172                         "MaxMind DB file?",
173                         db_file);
174         efree(mmdb);
175         return;
176     }
177 
178     maxminddb_obj *mmdb_obj = Z_MAXMINDDB_P(getThis());
179     mmdb_obj->mmdb = mmdb;
180 }
181 
182 ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_get, 0, 0, 1)
183 ZEND_ARG_INFO(0, ip_address)
184 ZEND_END_ARG_INFO()
185 
186 PHP_METHOD(MaxMind_Db_Reader, get) {
187     int prefix_len = 0;
188     get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, return_value, &prefix_len);
189 }
190 
191 PHP_METHOD(MaxMind_Db_Reader, getWithPrefixLen) {
192     zval *record, *z_prefix_len;
193 #ifdef ZEND_ENGINE_3
194     zval _record, _z_prefix_len;
195     record = &_record;
196     z_prefix_len = &_z_prefix_len;
197 #else
198     ALLOC_INIT_ZVAL(record);
199     ALLOC_INIT_ZVAL(z_prefix_len);
200 #endif
201 
202     int prefix_len = 0;
203     get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, record, &prefix_len);
204 
205     array_init(return_value);
206     add_next_index_zval(return_value, record);
207 
208     ZVAL_LONG(z_prefix_len, prefix_len);
209     add_next_index_zval(return_value, z_prefix_len);
210 }
211 
212 static void
213 get_record(INTERNAL_FUNCTION_PARAMETERS, zval *record, int *prefix_len) {
214     char *ip_address = NULL;
215     strsize_t name_len;
216     zval *_this_zval = NULL;
217 
218     if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
219                                      getThis(),
220                                      "Os",
221                                      &_this_zval,
222                                      maxminddb_ce,
223                                      &ip_address,
224                                      &name_len) == FAILURE) {
225         THROW_EXCEPTION("InvalidArgumentException",
226                         "Method takes exactly one argument.");
227         return;
228     }
229 
230     const maxminddb_obj *mmdb_obj = (maxminddb_obj *)Z_MAXMINDDB_P(getThis());
231 
232     MMDB_s *mmdb = mmdb_obj->mmdb;
233 
234     if (NULL == mmdb) {
235         THROW_EXCEPTION("BadMethodCallException",
236                         "Attempt to read from a closed MaxMind DB.");
237         return;
238     }
239 
240     struct addrinfo hints = {
241         .ai_family = AF_UNSPEC,
242         .ai_flags = AI_NUMERICHOST,
243         // We set ai_socktype so that we only get one result back
244         .ai_socktype = SOCK_STREAM};
245 
246     struct addrinfo *addresses = NULL;
247     int gai_status = getaddrinfo(ip_address, NULL, &hints, &addresses);
248     if (gai_status) {
249         THROW_EXCEPTION("InvalidArgumentException",
250                         "The value \"%s\" is not a valid IP address.",
251                         ip_address);
252         return;
253     }
254     if (!addresses || !addresses->ai_addr) {
255         THROW_EXCEPTION(
256             "InvalidArgumentException",
257             "getaddrinfo was successful but failed to set the addrinfo");
258         return;
259     }
260 
261     int sa_family = addresses->ai_addr->sa_family;
262 
263     int mmdb_error = MMDB_SUCCESS;
264     MMDB_lookup_result_s result =
265         MMDB_lookup_sockaddr(mmdb, addresses->ai_addr, &mmdb_error);
266 
267     freeaddrinfo(addresses);
268 
269     if (MMDB_SUCCESS != mmdb_error) {
270         char *exception_name;
271         if (MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR == mmdb_error) {
272             exception_name = "InvalidArgumentException";
273         } else {
274             exception_name = PHP_MAXMINDDB_READER_EX_NS;
275         }
276         THROW_EXCEPTION(exception_name,
277                         "Error looking up %s. %s",
278                         ip_address,
279                         MMDB_strerror(mmdb_error));
280         return;
281     }
282 
283     *prefix_len = result.netmask;
284 
285     if (sa_family == AF_INET && mmdb->metadata.ip_version == 6) {
286         // We return the prefix length given the IPv4 address. If there is
287         // no IPv4 subtree, we return a prefix length of 0.
288         *prefix_len = *prefix_len >= 96 ? *prefix_len - 96 : 0;
289     }
290 
291     if (!result.found_entry) {
292         ZVAL_NULL(record);
293         return;
294     }
295 
296     MMDB_entry_data_list_s *entry_data_list = NULL;
297     int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list);
298 
299     if (MMDB_SUCCESS != status) {
300         THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
301                         "Error while looking up data for %s. %s",
302                         ip_address,
303                         MMDB_strerror(status));
304         MMDB_free_entry_data_list(entry_data_list);
305         return;
306     } else if (NULL == entry_data_list) {
307         THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
308                         "Error while looking up data for %s. Your database may "
309                         "be corrupt or you have found a bug in libmaxminddb.",
310                         ip_address);
311         return;
312     }
313 
314     handle_entry_data_list(entry_data_list, record TSRMLS_CC);
315     MMDB_free_entry_data_list(entry_data_list);
316 }
317 
318 ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_void, 0, 0, 0)
319 ZEND_END_ARG_INFO()
320 
321 PHP_METHOD(MaxMind_Db_Reader, metadata) {
322     if (ZEND_NUM_ARGS() != 0) {
323         THROW_EXCEPTION("InvalidArgumentException",
324                         "Method takes no arguments.");
325         return;
326     }
327 
328     const maxminddb_obj *const mmdb_obj =
329         (maxminddb_obj *)Z_MAXMINDDB_P(getThis());
330 
331     if (NULL == mmdb_obj->mmdb) {
332         THROW_EXCEPTION("BadMethodCallException",
333                         "Attempt to read from a closed MaxMind DB.");
334         return;
335     }
336 
337     const char *const name = ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, "Metadata");
338     zend_class_entry *metadata_ce = lookup_class(name TSRMLS_CC);
339 
340     object_init_ex(return_value, metadata_ce);
341 
342 #ifdef ZEND_ENGINE_3
343     zval _metadata_array;
344     zval *metadata_array = &_metadata_array;
345     ZVAL_NULL(metadata_array);
346 #else
347     zval *metadata_array;
348     ALLOC_INIT_ZVAL(metadata_array);
349 #endif
350 
351     MMDB_entry_data_list_s *entry_data_list;
352     MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list);
353 
354     handle_entry_data_list(entry_data_list, metadata_array TSRMLS_CC);
355     MMDB_free_entry_data_list(entry_data_list);
356 #if PHP_VERSION_ID >= 80000
357     zend_call_method_with_1_params(Z_OBJ_P(return_value),
358                                    metadata_ce,
359                                    &metadata_ce->constructor,
360                                    ZEND_CONSTRUCTOR_FUNC_NAME,
361                                    NULL,
362                                    metadata_array);
363     zval_ptr_dtor(metadata_array);
364 #elif defined(ZEND_ENGINE_3)
365     zend_call_method_with_1_params(return_value,
366                                    metadata_ce,
367                                    &metadata_ce->constructor,
368                                    ZEND_CONSTRUCTOR_FUNC_NAME,
369                                    NULL,
370                                    metadata_array);
371     zval_ptr_dtor(metadata_array);
372 #else
373     zend_call_method_with_1_params(&return_value,
374                                    metadata_ce,
375                                    &metadata_ce->constructor,
376                                    ZEND_CONSTRUCTOR_FUNC_NAME,
377                                    NULL,
378                                    metadata_array);
379     zval_ptr_dtor(&metadata_array);
380 #endif
381 }
382 
383 PHP_METHOD(MaxMind_Db_Reader, close) {
384     if (ZEND_NUM_ARGS() != 0) {
385         THROW_EXCEPTION("InvalidArgumentException",
386                         "Method takes no arguments.");
387         return;
388     }
389 
390     maxminddb_obj *mmdb_obj = (maxminddb_obj *)Z_MAXMINDDB_P(getThis());
391 
392     if (NULL == mmdb_obj->mmdb) {
393         THROW_EXCEPTION("BadMethodCallException",
394                         "Attempt to close a closed MaxMind DB.");
395         return;
396     }
397     MMDB_close(mmdb_obj->mmdb);
398     efree(mmdb_obj->mmdb);
399     mmdb_obj->mmdb = NULL;
400 }
401 
402 static const MMDB_entry_data_list_s *
403 handle_entry_data_list(const MMDB_entry_data_list_s *entry_data_list,
404                        zval *z_value TSRMLS_DC) {
405     switch (entry_data_list->entry_data.type) {
406         case MMDB_DATA_TYPE_MAP:
407             return handle_map(entry_data_list, z_value TSRMLS_CC);
408         case MMDB_DATA_TYPE_ARRAY:
409             return handle_array(entry_data_list, z_value TSRMLS_CC);
410         case MMDB_DATA_TYPE_UTF8_STRING:
411             _ZVAL_STRINGL(z_value,
412                           (char *)entry_data_list->entry_data.utf8_string,
413                           entry_data_list->entry_data.data_size);
414             break;
415         case MMDB_DATA_TYPE_BYTES:
416             _ZVAL_STRINGL(z_value,
417                           (char *)entry_data_list->entry_data.bytes,
418                           entry_data_list->entry_data.data_size);
419             break;
420         case MMDB_DATA_TYPE_DOUBLE:
421             ZVAL_DOUBLE(z_value, entry_data_list->entry_data.double_value);
422             break;
423         case MMDB_DATA_TYPE_FLOAT:
424             ZVAL_DOUBLE(z_value, entry_data_list->entry_data.float_value);
425             break;
426         case MMDB_DATA_TYPE_UINT16:
427             ZVAL_LONG(z_value, entry_data_list->entry_data.uint16);
428             break;
429         case MMDB_DATA_TYPE_UINT32:
430             handle_uint32(entry_data_list, z_value TSRMLS_CC);
431             break;
432         case MMDB_DATA_TYPE_BOOLEAN:
433             ZVAL_BOOL(z_value, entry_data_list->entry_data.boolean);
434             break;
435         case MMDB_DATA_TYPE_UINT64:
436             handle_uint64(entry_data_list, z_value TSRMLS_CC);
437             break;
438         case MMDB_DATA_TYPE_UINT128:
439             handle_uint128(entry_data_list, z_value TSRMLS_CC);
440             break;
441         case MMDB_DATA_TYPE_INT32:
442             ZVAL_LONG(z_value, entry_data_list->entry_data.int32);
443             break;
444         default:
445             THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
446                             "Invalid data type arguments: %d",
447                             entry_data_list->entry_data.type);
448             return NULL;
449     }
450     return entry_data_list;
451 }
452 
453 static const MMDB_entry_data_list_s *
454 handle_map(const MMDB_entry_data_list_s *entry_data_list,
455            zval *z_value TSRMLS_DC) {
456     array_init(z_value);
457     const uint32_t map_size = entry_data_list->entry_data.data_size;
458 
459     uint i;
460     for (i = 0; i < map_size && entry_data_list; i++) {
461         entry_data_list = entry_data_list->next;
462 
463         char *key = estrndup((char *)entry_data_list->entry_data.utf8_string,
464                              entry_data_list->entry_data.data_size);
465         if (NULL == key) {
466             THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
467                             "Invalid data type arguments");
468             return NULL;
469         }
470 
471         entry_data_list = entry_data_list->next;
472 #ifdef ZEND_ENGINE_3
473         zval _new_value;
474         zval *new_value = &_new_value;
475         ZVAL_NULL(new_value);
476 #else
477         zval *new_value;
478         ALLOC_INIT_ZVAL(new_value);
479 #endif
480         entry_data_list =
481             handle_entry_data_list(entry_data_list, new_value TSRMLS_CC);
482         add_assoc_zval(z_value, key, new_value);
483         efree(key);
484     }
485     return entry_data_list;
486 }
487 
488 static const MMDB_entry_data_list_s *
489 handle_array(const MMDB_entry_data_list_s *entry_data_list,
490              zval *z_value TSRMLS_DC) {
491     const uint32_t size = entry_data_list->entry_data.data_size;
492 
493     array_init(z_value);
494 
495     uint i;
496     for (i = 0; i < size && entry_data_list; i++) {
497         entry_data_list = entry_data_list->next;
498 #ifdef ZEND_ENGINE_3
499         zval _new_value;
500         zval *new_value = &_new_value;
501         ZVAL_NULL(new_value);
502 #else
503         zval *new_value;
504         ALLOC_INIT_ZVAL(new_value);
505 #endif
506         entry_data_list =
507             handle_entry_data_list(entry_data_list, new_value TSRMLS_CC);
508         add_next_index_zval(z_value, new_value);
509     }
510     return entry_data_list;
511 }
512 
513 static void handle_uint128(const MMDB_entry_data_list_s *entry_data_list,
514                            zval *z_value TSRMLS_DC) {
515     uint64_t high = 0;
516     uint64_t low = 0;
517 #if MMDB_UINT128_IS_BYTE_ARRAY
518     int i;
519     for (i = 0; i < 8; i++) {
520         high = (high << 8) | entry_data_list->entry_data.uint128[i];
521     }
522 
523     for (i = 8; i < 16; i++) {
524         low = (low << 8) | entry_data_list->entry_data.uint128[i];
525     }
526 #else
527     high = entry_data_list->entry_data.uint128 >> 64;
528     low = (uint64_t)entry_data_list->entry_data.uint128;
529 #endif
530 
531     char *num_str;
532     spprintf(&num_str, 0, "0x%016" PRIX64 "%016" PRIX64, high, low);
533     CHECK_ALLOCATED(num_str);
534 
535     _ZVAL_STRING(z_value, num_str);
536     efree(num_str);
537 }
538 
539 static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list,
540                           zval *z_value TSRMLS_DC) {
541     uint32_t val = entry_data_list->entry_data.uint32;
542 
543 #if LONG_MAX >= UINT32_MAX
544     ZVAL_LONG(z_value, val);
545     return;
546 #else
547     if (val <= LONG_MAX) {
548         ZVAL_LONG(z_value, val);
549         return;
550     }
551 
552     char *int_str;
553     spprintf(&int_str, 0, "%" PRIu32, val);
554     CHECK_ALLOCATED(int_str);
555 
556     _ZVAL_STRING(z_value, int_str);
557     efree(int_str);
558 #endif
559 }
560 
561 static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list,
562                           zval *z_value TSRMLS_DC) {
563     uint64_t val = entry_data_list->entry_data.uint64;
564 
565 #if LONG_MAX >= UINT64_MAX
566     ZVAL_LONG(z_value, val);
567     return;
568 #else
569     if (val <= LONG_MAX) {
570         ZVAL_LONG(z_value, val);
571         return;
572     }
573 
574     char *int_str;
575     spprintf(&int_str, 0, "%" PRIu64, val);
576     CHECK_ALLOCATED(int_str);
577 
578     _ZVAL_STRING(z_value, int_str);
579     efree(int_str);
580 #endif
581 }
582 
583 static zend_class_entry *lookup_class(const char *name TSRMLS_DC) {
584 #ifdef ZEND_ENGINE_3
585     zend_string *n = zend_string_init(name, strlen(name), 0);
586     zend_class_entry *ce = zend_lookup_class(n);
587     zend_string_release(n);
588     if (NULL == ce) {
589         zend_error(E_ERROR, "Class %s not found", name);
590     }
591     return ce;
592 #else
593     zend_class_entry **ce;
594     if (FAILURE == zend_lookup_class(name, strlen(name), &ce TSRMLS_CC)) {
595         zend_error(E_ERROR, "Class %s not found", name);
596     }
597     return *ce;
598 #endif
599 }
600 
601 static void maxminddb_free_storage(free_obj_t *object TSRMLS_DC) {
602     maxminddb_obj *obj =
603         php_maxminddb_fetch_object((zend_object *)object TSRMLS_CC);
604     if (obj->mmdb != NULL) {
605         MMDB_close(obj->mmdb);
606         efree(obj->mmdb);
607     }
608 
609     zend_object_std_dtor(&obj->std TSRMLS_CC);
610 #ifndef ZEND_ENGINE_3
611     efree(object);
612 #endif
613 }
614 
615 #ifdef ZEND_ENGINE_3
616 static zend_object *maxminddb_create_handler(zend_class_entry *type TSRMLS_DC) {
617     maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj));
618     zend_object_std_init(&obj->std, type TSRMLS_CC);
619     object_properties_init(&(obj->std), type);
620 
621     obj->std.handlers = &maxminddb_obj_handlers;
622 
623     return &obj->std;
624 }
625 #else
626 static zend_object_value
627 maxminddb_create_handler(zend_class_entry *type TSRMLS_DC) {
628     zend_object_value retval;
629 
630     maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj));
631     zend_object_std_init(&obj->std, type TSRMLS_CC);
632     object_properties_init(&(obj->std), type);
633 
634     retval.handle = zend_objects_store_put(
635         obj, NULL, maxminddb_free_storage, NULL TSRMLS_CC);
636     retval.handlers = &maxminddb_obj_handlers;
637 
638     return retval;
639 }
640 #endif
641 
642 // clang-format off
643 static zend_function_entry maxminddb_methods[] = {
644     PHP_ME(MaxMind_Db_Reader, __construct, arginfo_maxmindbreader_construct,
645            ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
646     PHP_ME(MaxMind_Db_Reader, close, arginfo_maxmindbreader_void, ZEND_ACC_PUBLIC)
647     PHP_ME(MaxMind_Db_Reader, get, arginfo_maxmindbreader_get,  ZEND_ACC_PUBLIC)
648     PHP_ME(MaxMind_Db_Reader, getWithPrefixLen, arginfo_maxmindbreader_get,  ZEND_ACC_PUBLIC)
649     PHP_ME(MaxMind_Db_Reader, metadata, arginfo_maxmindbreader_void, ZEND_ACC_PUBLIC)
650     { NULL, NULL, NULL }
651 };
652 // clang-format on
653 
654 PHP_MINIT_FUNCTION(maxminddb) {
655     zend_class_entry ce;
656 
657     INIT_CLASS_ENTRY(ce, PHP_MAXMINDDB_READER_NS, maxminddb_methods);
658     maxminddb_ce = zend_register_internal_class(&ce TSRMLS_CC);
659     maxminddb_ce->create_object = maxminddb_create_handler;
660     memcpy(&maxminddb_obj_handlers,
661            zend_get_std_object_handlers(),
662            sizeof(zend_object_handlers));
663     maxminddb_obj_handlers.clone_obj = NULL;
664 #ifdef ZEND_ENGINE_3
665     maxminddb_obj_handlers.offset = XtOffsetOf(maxminddb_obj, std);
666     maxminddb_obj_handlers.free_obj = maxminddb_free_storage;
667 #endif
668 
669     return SUCCESS;
670 }
671 
672 static PHP_MINFO_FUNCTION(maxminddb) {
673     php_info_print_table_start();
674 
675     php_info_print_table_row(2, "MaxMind DB Reader", "enabled");
676     php_info_print_table_row(
677         2, "maxminddb extension version", PHP_MAXMINDDB_VERSION);
678     php_info_print_table_row(
679         2, "libmaxminddb library version", MMDB_lib_version());
680 
681     php_info_print_table_end();
682 }
683 
684 zend_module_entry maxminddb_module_entry = {STANDARD_MODULE_HEADER,
685                                             PHP_MAXMINDDB_EXTNAME,
686                                             NULL,
687                                             PHP_MINIT(maxminddb),
688                                             NULL,
689                                             NULL,
690                                             NULL,
691                                             PHP_MINFO(maxminddb),
692                                             PHP_MAXMINDDB_VERSION,
693                                             STANDARD_MODULE_PROPERTIES};
694 
695 #ifdef COMPILE_DL_MAXMINDDB
696 ZEND_GET_MODULE(maxminddb)
697 #endif
698