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(×hifter_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(×hifter_global, &tg);
123 62 : if (tg.is_hooked != flag) {
124 62 : tg.is_hooked = flag;
125 62 : sm_write(×hifter_global, &tg);
126 : }
127 62 : }
128 :
129 3663 : bool get_is_hooked() {
130 : timeshifter_global_t tg;
131 :
132 3663 : sm_read(×hifter_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(×hifter_global, &tg);
141 240 : memcpy(&tg.interval, interval, sizeof(colopl_timeshifter_interval_t));
142 240 : tg.is_hooked = true;
143 240 : if (!sm_write(×hifter_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(×hifter_global, sizeof(timeshifter_global_t));
291 274 : sm_read(×hifter_global, &tg);
292 :
293 274 : tg.is_hooked = false;
294 :
295 274 : memset(&tg.interval, 0, sizeof(tg.interval));
296 :
297 274 : sm_write(×hifter_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(×hifter_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
|