LCOV - code coverage report
Current view: top level - ext - colopl_timeshifter.c (source / functions) Coverage Total Hit
Test: Extension code coverage Lines: 88.7 % 168 149
Test Date: 2026-09-01 07:24:29 Functions: 100.0 % 27 27

            Line data    Source code
       1              : /*
       2              :   +----------------------------------------------------------------------+
       3              :   | COLOPL PHP TimeShifter.                                              |
       4              :   +----------------------------------------------------------------------+
       5              :   | Copyright (c) COLOPL, Inc.                                           |
       6              :   +----------------------------------------------------------------------+
       7              :   | This source file is subject to the BSD-3-Clause license that is      |
       8              :   | bundled with this package in the file LICENSE.                       |
       9              :   +----------------------------------------------------------------------+
      10              :   | Author: Go Kudo <g-kudo@colopl.co.jp>                                |
      11              :   +----------------------------------------------------------------------+
      12              : */
      13              : #ifdef HAVE_CONFIG_H
      14              : # include "config.h"
      15              : #endif
      16              : 
      17              : #include "php.h"
      18              : #include "Zend/zend_interfaces.h"
      19              : #include "ext/date/php_date.h"
      20              : #include "ext/standard/info.h"
      21              : 
      22              : #include "php_colopl_timeshifter.h"
      23              : #include "colopl_timeshifter_arginfo.h"
      24              : 
      25              : #include "shared_memory.h"
      26              : #include "hook.h"
      27              : 
      28              : /* True global */
      29              : typedef struct {
      30              :         bool is_hooked;
      31              :         colopl_timeshifter_interval_t interval;
      32              : } timeshifter_global_t;
      33              : sm_t timeshifter_global;
      34              : 
      35              : /* Module global */
      36              : ZEND_DECLARE_MODULE_GLOBALS(colopl_timeshifter);
      37              : 
      38              : PHP_INI_BEGIN()
      39              :         STD_PHP_INI_ENTRY("colopl_timeshifter.is_hook_pdo_mysql", "1", PHP_INI_SYSTEM, OnUpdateBool, is_hook_pdo_mysql, zend_colopl_timeshifter_globals, colopl_timeshifter_globals)
      40              :         STD_PHP_INI_ENTRY("colopl_timeshifter.is_hook_request_time", "1", PHP_INI_SYSTEM, OnUpdateBool, is_hook_request_time, zend_colopl_timeshifter_globals, colopl_timeshifter_globals)
      41              :         STD_PHP_INI_ENTRY("colopl_timeshifter.is_restore_per_request", "0", PHP_INI_ALL, OnUpdateBool, is_restore_per_request, zend_colopl_timeshifter_globals, colopl_timeshifter_globals)
      42              : PHP_INI_END()
      43              : 
      44          496 : static bool is_supported_interval_object(zval *interval)
      45              : {
      46          496 :         if (Z_OBJCE_P(interval) != php_date_get_interval_ce()) {
      47            4 :                 return false;
      48              :         }
      49              : 
      50          492 :         if (Z_OBJ_P(interval)->properties && zend_hash_num_elements(Z_OBJ_P(interval)->properties) > 0) {
      51            4 :                 return false;
      52              :         }
      53              : 
      54          488 :         return true;
      55              : }
      56              : 
      57          488 : static bool is_supported_interval_value(php_interval_obj *interval_obj)
      58              : {
      59          488 :         timelib_rel_time *diff = interval_obj->diff;
      60              : 
      61          488 :         if (!interval_obj->initialized || !diff) {
      62            0 :                 return false;
      63              :         }
      64              : 
      65          976 :         return !diff->have_weekday_relative &&
      66          488 :                 !diff->have_special_relative &&
      67         1456 :                 diff->first_last_day_of == 0 &&
      68          480 :                 diff->special.type == 0;
      69              : }
      70              : 
      71            8 : static void normalize_interval_days(timelib_rel_time *diff)
      72              : {
      73            8 :         if (diff->days != TIMELIB_UNSET && diff->days > 0 && diff->days != diff->d) {
      74            8 :                 diff->d = diff->days;
      75            8 :                 diff->y = 0;
      76            8 :                 diff->m = 0;
      77              :         }
      78            8 : }
      79              : 
      80          240 : static bool copy_interval_from_php(colopl_timeshifter_interval_t *dest, zval *src, bool normalize_days)
      81              : {
      82              :         php_interval_obj *interval_obj;
      83              : 
      84          240 :         memset(dest, 0, sizeof(colopl_timeshifter_interval_t));
      85              : 
      86          240 :         if (!is_supported_interval_object(src)) {
      87            0 :                 return false;
      88              :         }
      89              : 
      90          240 :         interval_obj = Z_PHPINTERVAL_P(src);
      91          240 :         if (!is_supported_interval_value(interval_obj)) {
      92            0 :                 return false;
      93              :         }
      94              : 
      95          240 :         dest->initialized = true;
      96          240 :         dest->civil_or_wall = interval_obj->civil_or_wall;
      97          240 :         memcpy(&dest->diff, interval_obj->diff, sizeof(timelib_rel_time));
      98          240 :         if (normalize_days) {
      99            8 :                 normalize_interval_days(&dest->diff);
     100              :         }
     101              : 
     102          240 :         return true;
     103              : }
     104              : 
     105         1042 : bool get_shift_interval(colopl_timeshifter_interval_t *interval) {
     106              :         timeshifter_global_t tg;
     107              : 
     108         1042 :         sm_read(&timeshifter_global, &tg);
     109         1042 :         if (tg.is_hooked) {
     110         1042 :                 memcpy(interval, &tg.interval, sizeof(colopl_timeshifter_interval_t));
     111         1042 :                 return true;
     112              :         }
     113              : 
     114            0 :         memset(interval, 0, sizeof(colopl_timeshifter_interval_t));
     115              : 
     116            0 :         return false;
     117              : }
     118              : 
     119           62 : void set_is_hooked(bool flag) {
     120              :         timeshifter_global_t tg;
     121              : 
     122           62 :         sm_read(&timeshifter_global, &tg);
     123           62 :         if (tg.is_hooked != flag) {
     124           62 :                 tg.is_hooked = flag;
     125           62 :                 sm_write(&timeshifter_global, &tg);
     126              :         }
     127           62 : }
     128              : 
     129         3663 : bool get_is_hooked() {
     130              :         timeshifter_global_t tg;
     131              : 
     132         3663 :         sm_read(&timeshifter_global, &tg);
     133         3663 :         return tg.is_hooked;
     134              : }
     135              : 
     136          240 : static bool register_shift_interval(const colopl_timeshifter_interval_t *interval)
     137              : {
     138              :         timeshifter_global_t tg;
     139              : 
     140          240 :         sm_read(&timeshifter_global, &tg);
     141          240 :         memcpy(&tg.interval, interval, sizeof(colopl_timeshifter_interval_t));
     142          240 :         tg.is_hooked = true;
     143          240 :         if (!sm_write(&timeshifter_global, &tg)) {
     144            0 :                 return false;
     145              :         }
     146              : 
     147          240 :         if (COLOPL_TS_G(is_hook_request_time)) {
     148          236 :                 apply_request_time_hook();
     149              :         }
     150              : 
     151          240 :         return true;
     152              : }
     153              : 
     154          256 : static bool register_hook_from_interval(zval *intern, bool normalize_days)
     155              : {
     156              :         colopl_timeshifter_interval_t interval;
     157              : 
     158          256 :         if (!is_supported_interval_object(intern)) {
     159            8 :                 return false;
     160              :         }
     161              : 
     162          248 :         if (!is_supported_interval_value(Z_PHPINTERVAL_P(intern))) {
     163            8 :                 return false;
     164              :         }
     165              : 
     166          240 :         if (!validate_shift_interval(intern)) {
     167            0 :                 return false;
     168              :         }
     169              : 
     170          240 :         if (!copy_interval_from_php(&interval, intern, normalize_days)) {
     171            0 :                 return false;
     172              :         }
     173              : 
     174          240 :         return register_shift_interval(&interval);
     175              : }
     176              : 
     177            4 : static bool create_current_datetime(zval *datetime)
     178              : {
     179            4 :         php_date_instantiate(php_date_get_immutable_ce(), datetime);
     180            4 :         if (!php_date_initialize(Z_PHPDATE_P(datetime), NULL, 0, NULL, NULL, PHP_DATE_INIT_CTOR)) {
     181            0 :                 zval_ptr_dtor(datetime);
     182            0 :                 ZVAL_UNDEF(datetime);
     183            0 :                 return false;
     184              :         }
     185              : 
     186            4 :         return true;
     187              : }
     188              : 
     189            4 : static bool register_hook_from_datetime(zval *date_time)
     190              : {
     191              :         zval now, interval;
     192            4 :         bool result = false;
     193              : 
     194            4 :         if (!create_current_datetime(&now)) {
     195            0 :                 return false;
     196              :         }
     197              : 
     198            4 :         ZVAL_UNDEF(&interval);
     199            4 :         zend_call_method_with_1_params(Z_OBJ_P(date_time), Z_OBJCE_P(date_time), NULL, "diff", &interval, &now);
     200            4 :         if (!EG(exception) && Z_TYPE(interval) == IS_OBJECT && Z_OBJCE(interval) == php_date_get_interval_ce()) {
     201            4 :                 result = register_hook_from_interval(&interval, true);
     202              :         }
     203              : 
     204            4 :         if (!Z_ISUNDEF(interval)) {
     205            4 :                 zval_ptr_dtor(&interval);
     206              :         }
     207            4 :         zval_ptr_dtor(&now);
     208              : 
     209            4 :         return result;
     210              : }
     211              : 
     212          248 : ZEND_FUNCTION(Colopl_ColoplTimeShifter_register_hook)
     213              : {
     214              :         zval *intern;
     215              : 
     216          248 :         ZEND_PARSE_PARAMETERS_START(1, 1)
     217          248 :                 Z_PARAM_OBJECT_OF_CLASS(intern, php_date_get_interval_ce())
     218          248 :         ZEND_PARSE_PARAMETERS_END();
     219              : 
     220          248 :         RETURN_BOOL(register_hook_from_interval(intern, false));
     221              : }
     222              : 
     223           54 : ZEND_FUNCTION(Colopl_ColoplTimeShifter_unregister_hook)
     224              : {
     225           54 :         set_is_hooked(false);
     226           54 : }
     227              : 
     228           40 : ZEND_FUNCTION(Colopl_ColoplTimeShifter_is_hooked)
     229              : {
     230           40 :         RETURN_BOOL(get_is_hooked());
     231              : }
     232              : 
     233            4 : ZEND_METHOD(Colopl_ColoplTimeShifter_Manager, isAvailable)
     234              : {
     235            4 :         ZEND_PARSE_PARAMETERS_NONE();
     236              : 
     237            4 :         RETURN_TRUE;
     238              : }
     239              : 
     240           12 : ZEND_METHOD(Colopl_ColoplTimeShifter_Manager, isHooked)
     241              : {
     242           12 :         ZEND_PARSE_PARAMETERS_NONE();
     243              : 
     244           12 :         RETURN_BOOL(get_is_hooked());
     245              : }
     246              : 
     247            8 : ZEND_METHOD(Colopl_ColoplTimeShifter_Manager, unhook)
     248              : {
     249            8 :         ZEND_PARSE_PARAMETERS_NONE();
     250              : 
     251            8 :         set_is_hooked(false);
     252              : 
     253            8 :         RETURN_TRUE;
     254              : }
     255              : 
     256            4 : ZEND_METHOD(Colopl_ColoplTimeShifter_Manager, hookDateTime)
     257              : {
     258              :         zval *date_time;
     259              :         bool result;
     260              : 
     261            4 :         ZEND_PARSE_PARAMETERS_START(1, 1)
     262            4 :                 Z_PARAM_OBJECT_OF_CLASS(date_time, php_date_get_interface_ce())
     263            4 :         ZEND_PARSE_PARAMETERS_END();
     264              : 
     265            4 :         result = register_hook_from_datetime(date_time);
     266            4 :         if (EG(exception)) {
     267            0 :                 RETURN_THROWS();
     268              :         }
     269              : 
     270            4 :         RETURN_BOOL(result);
     271              : }
     272              : 
     273            4 : ZEND_METHOD(Colopl_ColoplTimeShifter_Manager, hookDateInterval)
     274              : {
     275              :         zval *date_interval;
     276              : 
     277            4 :         ZEND_PARSE_PARAMETERS_START(1, 1)
     278            4 :                 Z_PARAM_OBJECT_OF_CLASS(date_interval, php_date_get_interval_ce())
     279            4 :         ZEND_PARSE_PARAMETERS_END();
     280              : 
     281            4 :         RETURN_BOOL(register_hook_from_interval(date_interval, true));
     282              : }
     283              : 
     284          274 : PHP_MINIT_FUNCTION(colopl_timeshifter)
     285              : {
     286              :         timeshifter_global_t tg;
     287              : 
     288          274 :         REGISTER_INI_ENTRIES();
     289              : 
     290          274 :         sm_init(&timeshifter_global, sizeof(timeshifter_global_t));
     291          274 :         sm_read(&timeshifter_global, &tg);
     292              : 
     293          274 :         tg.is_hooked = false;
     294              : 
     295          274 :         memset(&tg.interval, 0, sizeof(tg.interval));
     296              : 
     297          274 :         sm_write(&timeshifter_global, &tg);
     298              : 
     299          274 :         register_class_Colopl_ColoplTimeShifter_Manager();
     300              : 
     301          274 :         if (COLOPL_TS_G(is_hook_pdo_mysql) == true) {
     302          270 :                 register_pdo_hook();
     303              :         }
     304              : 
     305          274 :         if (!register_hooks()) {
     306            0 :                 return FAILURE;
     307              :         }
     308              : 
     309          274 :         if (get_is_hooked() && COLOPL_TS_G(is_hook_request_time)) {
     310            0 :                 apply_request_time_hook();
     311              :         }
     312              : 
     313              : #ifdef COLOPL_TIMESHIFTER_HAVE_PDO
     314          274 :         COLOPL_TS_G(pdo_mysql_orig_methods) = NULL;
     315              : #endif
     316              : 
     317          274 :         return SUCCESS;
     318              : }
     319              : 
     320          274 : PHP_MSHUTDOWN_FUNCTION(colopl_timeshifter)
     321              : {
     322          274 :         UNREGISTER_INI_ENTRIES();
     323              : 
     324          274 :         sm_free(&timeshifter_global);
     325              : 
     326          274 :         if (!unregister_hooks()) {
     327            0 :                 return FAILURE;
     328              :         }
     329              : 
     330          274 :         return SUCCESS;
     331              : }
     332              : 
     333          274 : PHP_RINIT_FUNCTION(colopl_timeshifter)
     334              : {
     335              : # if defined(ZTS) && defined(COMPILE_DL_COLOPL_TIMESHIFTER)
     336          138 :         ZEND_TSRMLS_CACHE_UPDATE();
     337              : # endif
     338              : 
     339          274 :         COLOPL_TS_G(orig_request_time) = 0;
     340          274 :         COLOPL_TS_G(orig_request_time_float) = 0.0;
     341          274 :         COLOPL_TS_G(in_internal_call) = false;
     342              : 
     343          274 :         return SUCCESS;
     344              : }
     345              : 
     346          274 : PHP_RSHUTDOWN_FUNCTION(colopl_timeshifter)
     347              : {
     348          274 :         if (COLOPL_TS_G(is_restore_per_request) && get_is_hooked()) {
     349            0 :                 set_is_hooked(false);
     350            0 :                 if (!unregister_hooks()) {
     351            0 :                         return FAILURE;
     352              :                 }
     353              :         }
     354              : 
     355          274 :         return SUCCESS;
     356              : }
     357              : 
     358           16 : PHP_MINFO_FUNCTION(colopl_timeshifter)
     359              : {
     360           16 :         php_info_print_table_start();
     361           16 :         php_info_print_table_header(2, "colopl_timeshifter support", "enabled");
     362           16 :         php_info_print_table_row(2, "timeshifter version", PHP_COLOPL_TIMESHIFTER_VERSION);
     363              : #ifdef COLOPL_TIMESHIFTER_HAVE_PDO
     364           16 :         php_info_print_table_row(2, "PDO Support", COLOPL_TS_G(orig_pdo_con) ? "Enable" : "Disable");
     365              : #else
     366              :         php_info_print_table_row(2, "PDO Support", "Disable");
     367              : #endif
     368           16 :         php_info_print_table_end();
     369           16 : }
     370              : 
     371          276 : PHP_GINIT_FUNCTION(colopl_timeshifter)
     372              : {
     373              : # if defined(ZTS) && defined(COMPILE_DL_COLOPL_TIMESHIFTER)
     374          140 :         ZEND_TSRMLS_CACHE_UPDATE();
     375              : # endif
     376              : 
     377          276 :         memset(colopl_timeshifter_globals, 0, sizeof(zend_colopl_timeshifter_globals));
     378          276 : }
     379              : 
     380          274 : PHP_GSHUTDOWN_FUNCTION(colopl_timeshifter)
     381              : {
     382          274 : }
     383              : 
     384              : /* PDO is optional: declare it as an optional dependency so its MINIT runs
     385              :  * before ours when it is loaded, without requiring it to be present. */
     386              : static const zend_module_dep colopl_timeshifter_deps[] = {
     387              :         ZEND_MOD_REQUIRED("date")
     388              :         ZEND_MOD_OPTIONAL("pdo")
     389              :         ZEND_MOD_END
     390              : };
     391              : 
     392              : zend_module_entry colopl_timeshifter_module_entry = {
     393              :         STANDARD_MODULE_HEADER_EX, NULL,
     394              :         colopl_timeshifter_deps,
     395              :         "colopl_timeshifter",
     396              :         ext_functions,
     397              :         PHP_MINIT(colopl_timeshifter),
     398              :         PHP_MSHUTDOWN(colopl_timeshifter),
     399              :         PHP_RINIT(colopl_timeshifter),
     400              :         PHP_RSHUTDOWN(colopl_timeshifter),
     401              :         PHP_MINFO(colopl_timeshifter),
     402              :         PHP_COLOPL_TIMESHIFTER_VERSION,
     403              :         PHP_MODULE_GLOBALS(colopl_timeshifter),
     404              :         PHP_GINIT(colopl_timeshifter),
     405              :         PHP_GSHUTDOWN(colopl_timeshifter),
     406              :         NULL,
     407              :         STANDARD_MODULE_PROPERTIES_EX
     408              : };
     409              : 
     410              : #ifdef COMPILE_DL_COLOPL_TIMESHIFTER
     411              : # ifdef ZTS
     412              : ZEND_TSRMLS_CACHE_DEFINE()
     413              : # endif
     414          274 : ZEND_GET_MODULE(colopl_timeshifter)
     415              : #endif
        

Generated by: LCOV version 2.0-1