xref: /PHP-8.3/ext/com_dotnet/com_variant.c (revision edb0af6f)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Author: Wez Furlong <wez@thebrainroom.com>                           |
14    +----------------------------------------------------------------------+
15  */
16 
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #include "php.h"
22 #include "php_ini.h"
23 #include "ext/standard/info.h"
24 #include "php_com_dotnet.h"
25 #include "php_com_dotnet_internal.h"
26 
27 /* create an automation SafeArray from a PHP array.
28  * Only creates a single-dimensional array of variants.
29  * The keys of the PHP hash MUST be numeric. */
safe_array_from_zval(VARIANT * v,zval * z,int codepage)30 static void safe_array_from_zval(VARIANT *v, zval *z, int codepage)
31 {
32 	SAFEARRAY *sa = NULL;
33 	SAFEARRAYBOUND bound;
34 	HashPosition pos;
35 	int keytype;
36 	zend_string *strindex;
37 	zend_ulong intindex = 0;
38 	VARIANT *va;
39 	zval *item;
40 
41 	/* find the largest array index, and assert that all keys are integers */
42 	zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(z), &pos);
43 	for (;; zend_hash_move_forward_ex(Z_ARRVAL_P(z), &pos)) {
44 
45 		keytype = zend_hash_get_current_key_ex(Z_ARRVAL_P(z), &strindex, &intindex, &pos);
46 
47 		if (HASH_KEY_IS_STRING == keytype) {
48 			goto bogus;
49 		} else if (HASH_KEY_NON_EXISTENT == keytype) {
50 			break;
51 		} else if (intindex > UINT_MAX) {
52 			php_error_docref(NULL, E_WARNING, "COM: max number %u of elements in safe array exceeded", UINT_MAX);
53 			break;
54 		}
55 	}
56 
57 	/* allocate the structure */
58 	bound.lLbound = 0;
59 	bound.cElements = zend_hash_num_elements(Z_ARRVAL_P(z));
60 	sa = SafeArrayCreate(VT_VARIANT, 1, &bound);
61 
62 	/* get a lock on the array itself */
63 	SafeArrayAccessData(sa, &va);
64 	va = (VARIANT*)sa->pvData;
65 
66 	/* now fill it in */
67 	zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(z), &pos);
68 	for (;; zend_hash_move_forward_ex(Z_ARRVAL_P(z), &pos)) {
69 		if (NULL == (item = zend_hash_get_current_data_ex(Z_ARRVAL_P(z), &pos))) {
70 			break;
71 		}
72 		zend_hash_get_current_key_ex(Z_ARRVAL_P(z), &strindex, &intindex, &pos);
73 		if (intindex < bound.cElements) {
74 			php_com_variant_from_zval(&va[intindex], item, codepage);
75 		}
76 	}
77 
78 	/* Unlock it and stuff it into our variant */
79 	SafeArrayUnaccessData(sa);
80 	V_VT(v) = VT_ARRAY|VT_VARIANT;
81 	V_ARRAY(v) = sa;
82 
83 	return;
84 
85 bogus:
86 	php_error_docref(NULL, E_WARNING, "COM: converting from PHP array to VARIANT array; only arrays with numeric keys are allowed");
87 
88 	V_VT(v) = VT_NULL;
89 
90 	if (sa) {
91 		SafeArrayUnlock(sa);
92 		SafeArrayDestroy(sa);
93 	}
94 }
95 
php_com_variant_from_zval_ex(VARIANT * v,zval * z,int codepage,VARTYPE vt)96 static void php_com_variant_from_zval_ex(VARIANT *v, zval *z, int codepage, VARTYPE vt)
97 {
98 	php_com_dotnet_object *obj;
99 	uint8_t ztype = IS_NULL;
100 
101 	if (z) {
102 		ZVAL_DEREF(z);
103 		ztype = Z_TYPE_P(z);
104 	}
105 
106 	switch (ztype) {
107 		case IS_NULL:
108 			V_VT(v) = VT_NULL;
109 			break;
110 
111 		case IS_FALSE:
112 			V_VT(v) = VT_BOOL;
113 			V_BOOL(v) = VARIANT_FALSE;
114 			break;
115 
116 		case IS_TRUE:
117 			V_VT(v) = VT_BOOL;
118 			V_BOOL(v) = VARIANT_TRUE;
119 			break;
120 
121 		case IS_OBJECT:
122 			if (php_com_is_valid_object(z)) {
123 				obj = CDNO_FETCH(z);
124 				if (V_VT(&obj->v) == VT_DISPATCH) {
125 					/* pass the underlying object */
126 					V_VT(v) = VT_DISPATCH;
127 					if (V_DISPATCH(&obj->v)) {
128 						IDispatch_AddRef(V_DISPATCH(&obj->v));
129 					}
130 					V_DISPATCH(v) = V_DISPATCH(&obj->v);
131 				} else {
132 					/* pass the variant by reference */
133 					V_VT(v) = VT_VARIANT | VT_BYREF;
134 					V_VARIANTREF(v) = &obj->v;
135 				}
136 			} else {
137 				/* export the PHP object using our COM wrapper */
138 				V_VT(v) = VT_DISPATCH;
139 				V_DISPATCH(v) = php_com_wrapper_export(z);
140 			}
141 			break;
142 
143 		case IS_ARRAY:
144 			/* map as safe array */
145 			safe_array_from_zval(v, z, codepage);
146 			break;
147 
148 		case IS_LONG:
149 			if (vt == VT_ERROR) {
150 				V_VT(v) = VT_ERROR;
151 				V_ERROR(v) = Z_LVAL_P(z);
152 				break;
153 			}
154 #if SIZEOF_ZEND_LONG == 4
155 			V_VT(v) = VT_I4;
156 			V_I4(v) = Z_LVAL_P(z);
157 #else
158 			V_VT(v) = VT_I8;
159 			V_I8(v) = Z_LVAL_P(z);
160 #endif
161 			break;
162 
163 		case IS_DOUBLE:
164 			V_VT(v) = VT_R8;
165 			V_R8(v) = Z_DVAL_P(z);
166 			break;
167 
168 		case IS_STRING:
169 			V_VT(v) = VT_BSTR;
170 			V_BSTR(v) = php_com_string_to_bstr(Z_STR_P(z), codepage);
171 			break;
172 
173 		case IS_RESOURCE:
174 		case IS_CONSTANT_AST:
175 		default:
176 			V_VT(v) = VT_NULL;
177 			break;
178 	}
179 }
180 
php_com_variant_from_zval(VARIANT * v,zval * z,int codepage)181 PHP_COM_DOTNET_API void php_com_variant_from_zval(VARIANT *v, zval *z, int codepage)
182 {
183 	php_com_variant_from_zval_ex(v, z, codepage, VT_EMPTY);
184 }
185 
php_com_zval_from_variant(zval * z,VARIANT * v,int codepage)186 PHP_COM_DOTNET_API zend_result php_com_zval_from_variant(zval *z, VARIANT *v, int codepage)
187 {
188 	OLECHAR *olestring = NULL;
189 	zend_result ret = SUCCESS;
190 
191 	switch (V_VT(v)) {
192 		case VT_EMPTY:
193 		case VT_NULL:
194 		case VT_VOID:
195 			ZVAL_NULL(z);
196 			break;
197 		case VT_UI1:
198 			ZVAL_LONG(z, (zend_long)V_UI1(v));
199 			break;
200 		case VT_I1:
201 			ZVAL_LONG(z, (zend_long)V_I1(v));
202 			break;
203 		case VT_UI2:
204 			ZVAL_LONG(z, (zend_long)V_UI2(v));
205 			break;
206 		case VT_I2:
207 			ZVAL_LONG(z, (zend_long)V_I2(v));
208 			break;
209 		case VT_UI4:  /* TODO: promote to double if large? */
210 			ZVAL_LONG(z, (long)V_UI4(v));
211 			break;
212 		case VT_I4:
213 			ZVAL_LONG(z, (long)V_I4(v));
214 			break;
215 #if SIZEOF_ZEND_LONG == 8
216 		case VT_UI8:
217 			ZVAL_LONG(z, (zend_long)V_UI8(v));
218 			break;
219 		case VT_I8:
220 			ZVAL_LONG(z, (zend_long)V_I8(v));
221 			break;
222 #endif
223 		case VT_INT:
224 			ZVAL_LONG(z, V_INT(v));
225 			break;
226 		case VT_UINT: /* TODO: promote to double if large? */
227 			ZVAL_LONG(z, (zend_long)V_UINT(v));
228 			break;
229 		case VT_R4:
230 			ZVAL_DOUBLE(z, (double)V_R4(v));
231 			break;
232 		case VT_R8:
233 			ZVAL_DOUBLE(z, V_R8(v));
234 			break;
235 		case VT_BOOL:
236 			ZVAL_BOOL(z, V_BOOL(v) ? 1 : 0);
237 			break;
238 		case VT_BSTR:
239 			olestring = V_BSTR(v);
240 			if (olestring) {
241 				zend_string *str = php_com_bstr_to_string(olestring, codepage);
242 				ZVAL_STR(z, str);
243 				olestring = NULL;
244 			}
245 			break;
246 		case VT_UNKNOWN:
247 			if (V_UNKNOWN(v) != NULL) {
248 				IDispatch *disp;
249 
250 				if (SUCCEEDED(IUnknown_QueryInterface(V_UNKNOWN(v), &IID_IDispatch, &disp))) {
251 					php_com_wrap_dispatch(z, disp, codepage);
252 					IDispatch_Release(disp);
253 				} else {
254 					ret = FAILURE;
255 				}
256 			}
257 			break;
258 
259 		case VT_DISPATCH:
260 			if (V_DISPATCH(v) != NULL) {
261 				php_com_wrap_dispatch(z, V_DISPATCH(v), codepage);
262 			}
263 			break;
264 
265 		case VT_VARIANT:
266 			/* points to another variant */
267 			return php_com_zval_from_variant(z, V_VARIANTREF(v), codepage);
268 
269 		default:
270 			php_com_wrap_variant(z, v, codepage);
271 	}
272 
273 	if (olestring) {
274 		efree(olestring);
275 	}
276 
277 	if (ret == FAILURE) {
278 		php_error_docref(NULL, E_WARNING, "variant->zval: conversion from 0x%x ret=%d", V_VT(v), ret);
279 	}
280 
281 	return ret;
282 }
283 
284 
php_com_copy_variant(VARIANT * dstvar,VARIANT * srcvar)285 PHP_COM_DOTNET_API zend_result php_com_copy_variant(VARIANT *dstvar, VARIANT *srcvar)
286 {
287 	zend_result ret = SUCCESS;
288 
289 	switch (V_VT(dstvar) & ~VT_BYREF) {
290 	case VT_EMPTY:
291 	case VT_NULL:
292 	case VT_VOID:
293 		/* should not be possible */
294 		break;
295 
296 	case VT_UI1:
297 		if (V_VT(dstvar) & VT_BYREF) {
298 			*V_UI1REF(dstvar) = V_UI1(srcvar);
299 		} else {
300 			 V_UI1(dstvar) = V_UI1(srcvar);
301 		}
302 		break;
303 
304 	case VT_I1:
305 		if (V_VT(dstvar) & VT_BYREF) {
306 			*V_I1REF(dstvar) = V_I1(srcvar);
307 		} else {
308 			V_I1(dstvar) = V_I1(srcvar);
309 		}
310 		break;
311 
312 	case VT_UI2:
313 		if (V_VT(dstvar) & VT_BYREF) {
314 			*V_UI2REF(dstvar) = V_UI2(srcvar);
315 		} else {
316 			V_UI2(dstvar) = V_UI2(srcvar);
317 		}
318 		break;
319 
320 	case VT_I2:
321 		if (V_VT(dstvar) & VT_BYREF) {
322 			*V_I2REF(dstvar) = V_I2(srcvar);
323 		} else {
324 			V_I2(dstvar) = V_I2(srcvar);
325 		}
326 		break;
327 
328 	case VT_UI4:
329 		if (V_VT(dstvar) & VT_BYREF) {
330 			*V_UI4REF(dstvar) = V_UI4(srcvar);
331 		} else {
332 			V_UI4(dstvar) = V_UI4(srcvar);
333 		}
334 		break;
335 
336 	case VT_I4:
337 		if (V_VT(dstvar) & VT_BYREF) {
338 			*V_I4REF(dstvar) = V_I4(srcvar);
339 		} else {
340 			V_I4(dstvar) = V_I4(srcvar);
341 		}
342 		break;
343 #if SIZEOF_ZEND_LONG == 8
344 	case VT_UI8:
345 		if (V_VT(dstvar) & VT_BYREF) {
346 			*V_UI8REF(dstvar) = V_UI8(srcvar);
347 		} else {
348 			V_UI8(dstvar) = V_UI8(srcvar);
349 		}
350 		break;
351 
352 	case VT_I8:
353 		if (V_VT(dstvar) & VT_BYREF) {
354 			*V_I8REF(dstvar) = V_I8(srcvar);
355 		} else {
356 			V_I8(dstvar) = V_I8(srcvar);
357 		}
358 		break;
359 #endif
360 	case VT_INT:
361 		if (V_VT(dstvar) & VT_BYREF) {
362 			*V_INTREF(dstvar) = V_INT(srcvar);
363 		} else {
364 			V_INT(dstvar) = V_INT(srcvar);
365 		}
366 		break;
367 
368 	case VT_UINT:
369 		if (V_VT(dstvar) & VT_BYREF) {
370 			*V_UINTREF(dstvar) = V_UINT(srcvar);
371 		} else {
372 			V_UINT(dstvar) = V_UINT(srcvar);
373 		}
374 		break;
375 
376 	case VT_R4:
377 		if (V_VT(dstvar) & VT_BYREF) {
378 			*V_R4REF(dstvar) = V_R4(srcvar);
379 		} else {
380 			V_R4(dstvar) = V_R4(srcvar);
381 		}
382 		break;
383 
384 	case VT_R8:
385 		if (V_VT(dstvar) & VT_BYREF) {
386 			*V_R8REF(dstvar) = V_R8(srcvar);
387 		} else {
388 			V_R8(dstvar) = V_R8(srcvar);
389 		}
390 		break;
391 
392 	case VT_BOOL:
393 		if (V_VT(dstvar) & VT_BYREF) {
394 			*V_BOOLREF(dstvar) = V_BOOL(srcvar);
395 		} else {
396 			V_BOOL(dstvar) = V_BOOL(srcvar);
397 		}
398 		break;
399 
400 	case VT_BSTR:
401 		if (V_VT(dstvar) & VT_BYREF) {
402 			*V_BSTRREF(dstvar) = V_BSTR(srcvar);
403 		} else {
404 			V_BSTR(dstvar) = V_BSTR(srcvar);
405 		}
406 		break;
407 
408 	case VT_UNKNOWN:
409 		if (V_VT(dstvar) & VT_BYREF) {
410 			*V_UNKNOWNREF(dstvar) = V_UNKNOWN(srcvar);
411 		} else {
412 			V_UNKNOWN(dstvar) = V_UNKNOWN(srcvar);
413 		}
414 		break;
415 
416 	case VT_DISPATCH:
417 		if (V_VT(dstvar) & VT_BYREF) {
418 			*V_DISPATCHREF(dstvar) = V_DISPATCH(srcvar);
419 		} else {
420 			V_DISPATCH(dstvar) = V_DISPATCH(srcvar);
421 		}
422 		break;
423 
424 	case VT_VARIANT:
425 		return php_com_copy_variant(V_VARIANTREF(dstvar), srcvar);
426 
427 	default:
428 		php_error_docref(NULL, E_WARNING, "variant->__construct: failed to copy from 0x%x to 0x%x", V_VT(dstvar), V_VT(srcvar));
429 		ret = FAILURE;
430 	}
431 	return ret;
432 }
433 
434 /* {{{ com_variant_create_instance - ctor for new VARIANT() */
PHP_METHOD(variant,__construct)435 PHP_METHOD(variant, __construct)
436 {
437 	/* VARTYPE == unsigned short */ zend_long vt = VT_EMPTY;
438 	zend_long codepage = CP_ACP;
439 	zval *object = getThis();
440 	php_com_dotnet_object *obj;
441 	zval *zvalue = NULL;
442 	HRESULT res;
443 
444 	if (ZEND_NUM_ARGS() == 0) {
445 		/* just leave things as-is - an empty variant */
446 		return;
447 	}
448 
449 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(),
450 		"z!|ll", &zvalue, &vt, &codepage)) {
451 			RETURN_THROWS();
452 	}
453 
454 	php_com_initialize();
455 	obj = CDNO_FETCH(object);
456 
457 	if (ZEND_NUM_ARGS() == 3) {
458 		obj->code_page = (int)codepage;
459 	}
460 
461 	if (zvalue) {
462 		php_com_variant_from_zval_ex(&obj->v, zvalue, obj->code_page, vt);
463 	}
464 
465 	/* Only perform conversion if variant not already of type passed */
466 	if ((ZEND_NUM_ARGS() >= 2) && (vt != V_VT(&obj->v))) {
467 
468 		/* If already an array and VT_ARRAY is passed then:
469 			- if only VT_ARRAY passed then do not perform a conversion
470 			- if VT_ARRAY plus other type passed then perform conversion
471 			  but will probably fail (original behavior)
472 		*/
473 		if ((vt & VT_ARRAY) && (V_VT(&obj->v) & VT_ARRAY)) {
474 			zend_long orig_vt = vt;
475 
476 			vt &= ~VT_ARRAY;
477 			if (vt) {
478 				vt = orig_vt;
479 			}
480 		}
481 
482 		if (vt) {
483 			res = VariantChangeType(&obj->v, &obj->v, 0, (VARTYPE)vt);
484 
485 			if (FAILED(res)) {
486 				char *werr, *msg;
487 
488 				werr = php_win32_error_to_msg(res);
489 				spprintf(&msg, 0, "Variant type conversion failed: %s", werr);
490 				php_win32_error_msg_free(werr);
491 
492 				php_com_throw_exception(res, msg);
493 				efree(msg);
494 			}
495 		}
496 	}
497 
498 	if (V_VT(&obj->v) != VT_DISPATCH && obj->typeinfo) {
499 		ITypeInfo_Release(obj->typeinfo);
500 		obj->typeinfo = NULL;
501 	}
502 }
503 /* }}} */
504 
505 /* {{{ Assigns a new value for a variant object */
PHP_FUNCTION(variant_set)506 PHP_FUNCTION(variant_set)
507 {
508 	zval *zobj, *zvalue = NULL;
509 	php_com_dotnet_object *obj;
510 
511 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(),
512 			"Oz!", &zobj, php_com_variant_class_entry, &zvalue)) {
513 		RETURN_THROWS();
514 	}
515 
516 	obj = CDNO_FETCH(zobj);
517 
518 	/* dtor the old value */
519 	if (obj->typeinfo) {
520 		ITypeInfo_Release(obj->typeinfo);
521 		obj->typeinfo = NULL;
522 	}
523 	if (obj->sink_dispatch) {
524 		php_com_object_enable_event_sink(obj, /* enable */ false);
525 		IDispatch_Release(obj->sink_dispatch);
526 		obj->sink_dispatch = NULL;
527 	}
528 
529 	VariantClear(&obj->v);
530 
531 	php_com_variant_from_zval(&obj->v, zvalue, obj->code_page);
532 	/* remember we modified this variant */
533 	obj->modified = 1;
534 }
535 /* }}} */
536 
537 enum variant_binary_opcode {
538 	VOP_ADD, VOP_CAT, VOP_SUB, VOP_MUL, VOP_AND, VOP_DIV,
539 	VOP_EQV, VOP_IDIV, VOP_IMP, VOP_MOD, VOP_OR, VOP_POW,
540 	VOP_XOR
541 };
542 
543 enum variant_unary_opcode {
544 	VOP_ABS, VOP_FIX, VOP_INT, VOP_NEG, VOP_NOT
545 };
546 
variant_binary_operation(enum variant_binary_opcode op,INTERNAL_FUNCTION_PARAMETERS)547 static void variant_binary_operation(enum variant_binary_opcode op, INTERNAL_FUNCTION_PARAMETERS) /* {{{ */
548 {
549 	VARIANT vres;
550 	VARIANT left_val, right_val;
551 	VARIANT *vleft = NULL, *vright = NULL;
552 	zval *zleft = NULL, *zright = NULL;
553 	php_com_dotnet_object *obj;
554 	HRESULT result;
555 	int codepage = CP_ACP;
556 
557 	VariantInit(&left_val);
558 	VariantInit(&right_val);
559 	VariantInit(&vres);
560 
561 	if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
562 			ZEND_NUM_ARGS(), "OO", &zleft, php_com_variant_class_entry,
563 			&zright, php_com_variant_class_entry)) {
564 		obj = CDNO_FETCH(zleft);
565 		vleft = &obj->v;
566 		obj = CDNO_FETCH(zright);
567 		vright = &obj->v;
568 	} else if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
569 			ZEND_NUM_ARGS(), "Oz!", &zleft, php_com_variant_class_entry,
570 			&zright)) {
571 		obj = CDNO_FETCH(zleft);
572 		vleft = &obj->v;
573 		vright = &right_val;
574 		php_com_variant_from_zval(vright, zright, codepage);
575 	} else if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
576 			ZEND_NUM_ARGS(), "z!O", &zleft, &zright, php_com_variant_class_entry)) {
577 		obj = CDNO_FETCH(zright);
578 		vright = &obj->v;
579 		vleft = &left_val;
580 		php_com_variant_from_zval(vleft, zleft, codepage);
581 	} else if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(),
582 			"z!z!", &zleft, &zright)) {
583 
584 		vleft = &left_val;
585 		php_com_variant_from_zval(vleft, zleft, codepage);
586 
587 		vright = &right_val;
588 		php_com_variant_from_zval(vright, zright, codepage);
589 
590 	} else {
591 		RETURN_THROWS();
592 	}
593 
594 	switch (op) {
595 		case VOP_ADD:
596 			result = VarAdd(vleft, vright, &vres);
597 			break;
598 		case VOP_CAT:
599 			result = VarCat(vleft, vright, &vres);
600 			break;
601 		case VOP_SUB:
602 			result = VarSub(vleft, vright, &vres);
603 			break;
604 		case VOP_MUL:
605 			result = VarMul(vleft, vright, &vres);
606 			break;
607 		case VOP_AND:
608 			result = VarAnd(vleft, vright, &vres);
609 			break;
610 		case VOP_DIV:
611 			result = VarDiv(vleft, vright, &vres);
612 			break;
613 		case VOP_EQV:
614 			result = VarEqv(vleft, vright, &vres);
615 			break;
616 		case VOP_IDIV:
617 			result = VarIdiv(vleft, vright, &vres);
618 			break;
619 		case VOP_IMP:
620 			result = VarImp(vleft, vright, &vres);
621 			break;
622 		case VOP_MOD:
623 			result = VarMod(vleft, vright, &vres);
624 			break;
625 		case VOP_OR:
626 			result = VarOr(vleft, vright, &vres);
627 			break;
628 		case VOP_POW:
629 			result = VarPow(vleft, vright, &vres);
630 			break;
631 		case VOP_XOR:
632 			result = VarXor(vleft, vright, &vres);
633 			break;
634 		/*Let say it fails as no valid op has been given */
635 		default:
636 			result = E_INVALIDARG;
637 	}
638 
639 	if (SUCCEEDED(result)) {
640 		php_com_wrap_variant(return_value, &vres, codepage);
641 	} else {
642 		php_com_throw_exception(result, NULL);
643 	}
644 
645 	VariantClear(&vres);
646 	VariantClear(&left_val);
647 	VariantClear(&right_val);
648 }
649 /* }}} */
650 
651 /* {{{ "Adds" two variant values together and returns the result */
PHP_FUNCTION(variant_add)652 PHP_FUNCTION(variant_add)
653 {
654 	variant_binary_operation(VOP_ADD, INTERNAL_FUNCTION_PARAM_PASSTHRU);
655 }
656 /* }}} */
657 
658 /* {{{ concatenates two variant values together and returns the result */
PHP_FUNCTION(variant_cat)659 PHP_FUNCTION(variant_cat)
660 {
661 	variant_binary_operation(VOP_CAT, INTERNAL_FUNCTION_PARAM_PASSTHRU);
662 }
663 /* }}} */
664 
665 /* {{{ subtracts the value of the right variant from the left variant value and returns the result */
PHP_FUNCTION(variant_sub)666 PHP_FUNCTION(variant_sub)
667 {
668 	variant_binary_operation(VOP_SUB, INTERNAL_FUNCTION_PARAM_PASSTHRU);
669 }
670 /* }}} */
671 
672 /* {{{ multiplies the values of the two variants and returns the result */
PHP_FUNCTION(variant_mul)673 PHP_FUNCTION(variant_mul)
674 {
675 	variant_binary_operation(VOP_MUL, INTERNAL_FUNCTION_PARAM_PASSTHRU);
676 }
677 /* }}} */
678 
679 /* {{{ performs a bitwise AND operation between two variants and returns the result */
PHP_FUNCTION(variant_and)680 PHP_FUNCTION(variant_and)
681 {
682 	variant_binary_operation(VOP_AND, INTERNAL_FUNCTION_PARAM_PASSTHRU);
683 }
684 /* }}} */
685 
686 /* {{{ Returns the result from dividing two variants */
PHP_FUNCTION(variant_div)687 PHP_FUNCTION(variant_div)
688 {
689 	variant_binary_operation(VOP_DIV, INTERNAL_FUNCTION_PARAM_PASSTHRU);
690 }
691 /* }}} */
692 
693 /* {{{ Performs a bitwise equivalence on two variants */
PHP_FUNCTION(variant_eqv)694 PHP_FUNCTION(variant_eqv)
695 {
696 	variant_binary_operation(VOP_EQV, INTERNAL_FUNCTION_PARAM_PASSTHRU);
697 }
698 /* }}} */
699 
700 /* {{{ Converts variants to integers and then returns the result from dividing them */
PHP_FUNCTION(variant_idiv)701 PHP_FUNCTION(variant_idiv)
702 {
703 	variant_binary_operation(VOP_IDIV, INTERNAL_FUNCTION_PARAM_PASSTHRU);
704 }
705 /* }}} */
706 
707 /* {{{ Performs a bitwise implication on two variants */
PHP_FUNCTION(variant_imp)708 PHP_FUNCTION(variant_imp)
709 {
710 	variant_binary_operation(VOP_IMP, INTERNAL_FUNCTION_PARAM_PASSTHRU);
711 }
712 /* }}} */
713 
714 /* {{{ Divides two variants and returns only the remainder */
PHP_FUNCTION(variant_mod)715 PHP_FUNCTION(variant_mod)
716 {
717 	variant_binary_operation(VOP_MOD, INTERNAL_FUNCTION_PARAM_PASSTHRU);
718 }
719 /* }}} */
720 
721 /* {{{ Performs a logical disjunction on two variants */
PHP_FUNCTION(variant_or)722 PHP_FUNCTION(variant_or)
723 {
724 	variant_binary_operation(VOP_OR, INTERNAL_FUNCTION_PARAM_PASSTHRU);
725 }
726 /* }}} */
727 
728 /* {{{ Returns the result of performing the power function with two variants */
PHP_FUNCTION(variant_pow)729 PHP_FUNCTION(variant_pow)
730 {
731 	variant_binary_operation(VOP_POW, INTERNAL_FUNCTION_PARAM_PASSTHRU);
732 }
733 /* }}} */
734 
735 /* {{{ Performs a logical exclusion on two variants */
PHP_FUNCTION(variant_xor)736 PHP_FUNCTION(variant_xor)
737 {
738 	variant_binary_operation(VOP_XOR, INTERNAL_FUNCTION_PARAM_PASSTHRU);
739 }
740 /* }}} */
741 
variant_unary_operation(enum variant_unary_opcode op,INTERNAL_FUNCTION_PARAMETERS)742 static void variant_unary_operation(enum variant_unary_opcode op, INTERNAL_FUNCTION_PARAMETERS) /* {{{ */
743 {
744 	VARIANT vres;
745 	VARIANT left_val;
746 	VARIANT *vleft = NULL;
747 	zval *zleft = NULL;
748 	php_com_dotnet_object *obj;
749 	HRESULT result;
750 	int codepage = CP_ACP;
751 
752 	VariantInit(&left_val);
753 	VariantInit(&vres);
754 
755 	if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
756 			ZEND_NUM_ARGS(), "O", &zleft, php_com_variant_class_entry)) {
757 		obj = CDNO_FETCH(zleft);
758 		vleft = &obj->v;
759 	} else if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(),
760 			"z!", &zleft)) {
761 		vleft = &left_val;
762 		php_com_variant_from_zval(vleft, zleft, codepage);
763 	} else {
764 		RETURN_THROWS();
765 	}
766 
767 	switch (op) {
768 		case VOP_ABS:
769 			result = VarAbs(vleft, &vres);
770 			break;
771 		case VOP_FIX:
772 			result = VarFix(vleft, &vres);
773 			break;
774 		case VOP_INT:
775 			result = VarInt(vleft, &vres);
776 			break;
777 		case VOP_NEG:
778 			result = VarNeg(vleft, &vres);
779 			break;
780 		case VOP_NOT:
781 			result = VarNot(vleft, &vres);
782 			break;
783 		default:
784 			result = E_INVALIDARG;
785 	}
786 
787 	if (SUCCEEDED(result)) {
788 		php_com_wrap_variant(return_value, &vres, codepage);
789 	} else {
790 		php_com_throw_exception(result, NULL);
791 	}
792 
793 	VariantClear(&vres);
794 	VariantClear(&left_val);
795 }
796 /* }}} */
797 
798 /* {{{ Returns the absolute value of a variant */
PHP_FUNCTION(variant_abs)799 PHP_FUNCTION(variant_abs)
800 {
801 	variant_unary_operation(VOP_ABS, INTERNAL_FUNCTION_PARAM_PASSTHRU);
802 }
803 /* }}} */
804 
805 /* {{{ Returns the integer part ? of a variant */
PHP_FUNCTION(variant_fix)806 PHP_FUNCTION(variant_fix)
807 {
808 	variant_unary_operation(VOP_FIX, INTERNAL_FUNCTION_PARAM_PASSTHRU);
809 }
810 /* }}} */
811 
812 /* {{{ Returns the integer portion of a variant */
PHP_FUNCTION(variant_int)813 PHP_FUNCTION(variant_int)
814 {
815 	variant_unary_operation(VOP_INT, INTERNAL_FUNCTION_PARAM_PASSTHRU);
816 }
817 /* }}} */
818 
819 /* {{{ Performs logical negation on a variant */
PHP_FUNCTION(variant_neg)820 PHP_FUNCTION(variant_neg)
821 {
822 	variant_unary_operation(VOP_NEG, INTERNAL_FUNCTION_PARAM_PASSTHRU);
823 }
824 /* }}} */
825 
826 /* {{{ Performs bitwise not negation on a variant */
PHP_FUNCTION(variant_not)827 PHP_FUNCTION(variant_not)
828 {
829 	variant_unary_operation(VOP_NOT, INTERNAL_FUNCTION_PARAM_PASSTHRU);
830 }
831 /* }}} */
832 
833 /* {{{ Rounds a variant to the specified number of decimal places */
PHP_FUNCTION(variant_round)834 PHP_FUNCTION(variant_round)
835 {
836 	VARIANT vres;
837 	VARIANT left_val;
838 	VARIANT *vleft = NULL;
839 	zval *zleft = NULL;
840 	php_com_dotnet_object *obj;
841 	int codepage = CP_ACP;
842 	zend_long decimals = 0;
843 
844 	VariantInit(&left_val);
845 	VariantInit(&vres);
846 
847 	if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
848 			ZEND_NUM_ARGS(), "Ol", &zleft, php_com_variant_class_entry, &decimals)) {
849 		obj = CDNO_FETCH(zleft);
850 		vleft = &obj->v;
851 	} else if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(),
852 			"z!l", &zleft, &decimals)) {
853 		vleft = &left_val;
854 		php_com_variant_from_zval(vleft, zleft, codepage);
855 	} else {
856 		RETURN_THROWS();
857 	}
858 
859 	if (SUCCEEDED(VarRound(vleft, (int)decimals, &vres))) {
860 		php_com_wrap_variant(return_value, &vres, codepage);
861 	}
862 
863 	VariantClear(&vres);
864 	VariantClear(&left_val);
865 }
866 /* }}} */
867 
868 /* {{{ Compares two variants */
PHP_FUNCTION(variant_cmp)869 PHP_FUNCTION(variant_cmp)
870 {
871 	VARIANT left_val, right_val;
872 	VARIANT *vleft = NULL, *vright = NULL;
873 	zval *zleft = NULL, *zright = NULL;
874 	php_com_dotnet_object *obj;
875 	int codepage = CP_ACP;
876 	zend_long lcid = LOCALE_SYSTEM_DEFAULT;
877 	zend_long flags = 0;
878 	/* it is safe to ignore the warning for this line; see the comments in com_handlers.c */
879 	STDAPI VarCmp(LPVARIANT pvarLeft, LPVARIANT pvarRight, LCID lcid, DWORD flags);
880 
881 	VariantInit(&left_val);
882 	VariantInit(&right_val);
883 
884 	if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
885 			ZEND_NUM_ARGS(), "OO|ll", &zleft, php_com_variant_class_entry,
886 			&zright, php_com_variant_class_entry, &lcid, &flags)) {
887 		obj = CDNO_FETCH(zleft);
888 		vleft = &obj->v;
889 		obj = CDNO_FETCH(zright);
890 		vright = &obj->v;
891 	} else if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
892 			ZEND_NUM_ARGS(), "Oz!|ll", &zleft, php_com_variant_class_entry,
893 			&zright, &lcid, &flags)) {
894 		obj = CDNO_FETCH(zleft);
895 		vleft = &obj->v;
896 		vright = &right_val;
897 		php_com_variant_from_zval(vright, zright, codepage);
898 	} else if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
899 			ZEND_NUM_ARGS(), "z!O|ll", &zleft, &zright, php_com_variant_class_entry,
900 			&lcid, &flags)) {
901 		obj = CDNO_FETCH(zright);
902 		vright = &obj->v;
903 		vleft = &left_val;
904 		php_com_variant_from_zval(vleft, zleft, codepage);
905 	} else if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(),
906 			"z!z!|ll", &zleft, &zright, &lcid, &flags)) {
907 
908 		vleft = &left_val;
909 		php_com_variant_from_zval(vleft, zleft, codepage);
910 
911 		vright = &right_val;
912 		php_com_variant_from_zval(vright, zright, codepage);
913 
914 	} else {
915 		RETURN_THROWS();
916 	}
917 
918 	ZVAL_LONG(return_value, VarCmp(vleft, vright, (LCID)lcid, (ULONG)flags));
919 
920 	VariantClear(&left_val);
921 	VariantClear(&right_val);
922 }
923 /* }}} */
924 
925 /* {{{ Converts a variant date/time value to unix timestamp */
PHP_FUNCTION(variant_date_to_timestamp)926 PHP_FUNCTION(variant_date_to_timestamp)
927 {
928 	VARIANT vres;
929 	zval *zleft = NULL;
930 	php_com_dotnet_object *obj;
931 
932 	VariantInit(&vres);
933 
934 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(),
935 		"O", &zleft, php_com_variant_class_entry)) {
936 		RETURN_THROWS();
937 	}
938 	obj = CDNO_FETCH(zleft);
939 
940 	if (SUCCEEDED(VariantChangeType(&vres, &obj->v, 0, VT_DATE))) {
941 		SYSTEMTIME systime;
942 		struct tm tmv;
943 
944 		VariantTimeToSystemTime(V_DATE(&vres), &systime);
945 
946 		memset(&tmv, 0, sizeof(tmv));
947 		tmv.tm_year = systime.wYear - 1900;
948 		tmv.tm_mon = systime.wMonth - 1;
949 		tmv.tm_mday = systime.wDay;
950 		tmv.tm_hour = systime.wHour;
951 		tmv.tm_min = systime.wMinute;
952 		tmv.tm_sec = systime.wSecond;
953 		tmv.tm_isdst = -1;
954 
955 		tzset();
956 		RETVAL_LONG(mktime(&tmv));
957 	}
958 
959 	VariantClear(&vres);
960 }
961 /* }}} */
962 
963 /* {{{ Returns a variant date representation of a unix timestamp */
PHP_FUNCTION(variant_date_from_timestamp)964 PHP_FUNCTION(variant_date_from_timestamp)
965 {
966 	zend_long timestamp;
967 	time_t ttstamp;
968 	SYSTEMTIME systime;
969 	struct tm *tmv;
970 	VARIANT res;
971 
972 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "l",
973 			&timestamp)) {
974 		RETURN_THROWS();
975 	}
976 
977 	if (timestamp < 0) {
978 		zend_argument_value_error(1, "must be greater than or equal to 0");
979 		RETURN_THROWS();
980 	}
981 
982 	VariantInit(&res);
983 	tzset();
984 	ttstamp = timestamp;
985 	tmv = localtime(&ttstamp);
986 
987 	/* Invalid after 23:59:59, December 31, 3000, UTC */
988 	if (!tmv) {
989 		zend_argument_value_error(1, "must not go past 23:59:59, December 31, 3000, UTC");
990 		RETURN_THROWS();
991 	}
992 
993 	memset(&systime, 0, sizeof(systime));
994 
995 	systime.wDay = tmv->tm_mday;
996 	systime.wHour = tmv->tm_hour;
997 	systime.wMinute = tmv->tm_min;
998 	systime.wMonth = tmv->tm_mon + 1;
999 	systime.wSecond = tmv->tm_sec;
1000 	systime.wYear = tmv->tm_year + 1900;
1001 
1002 	V_VT(&res) = VT_DATE;
1003 	SystemTimeToVariantTime(&systime, &V_DATE(&res));
1004 
1005 	php_com_wrap_variant(return_value, &res, CP_ACP);
1006 
1007 	VariantClear(&res);
1008 }
1009 /* }}} */
1010 
1011 /* {{{ Returns the VT_XXX type code for a variant */
PHP_FUNCTION(variant_get_type)1012 PHP_FUNCTION(variant_get_type)
1013 {
1014 	zval *zobj;
1015 	php_com_dotnet_object *obj;
1016 
1017 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(),
1018 		"O", &zobj, php_com_variant_class_entry)) {
1019 		RETURN_THROWS();
1020 	}
1021 	obj = CDNO_FETCH(zobj);
1022 
1023 	RETURN_LONG(V_VT(&obj->v));
1024 }
1025 /* }}} */
1026 
1027 /* {{{ Convert a variant into another type.  Variant is modified "in-place" */
PHP_FUNCTION(variant_set_type)1028 PHP_FUNCTION(variant_set_type)
1029 {
1030 	zval *zobj;
1031 	php_com_dotnet_object *obj;
1032 	/* VARTYPE == unsigned short */ zend_long vt;
1033 	VARIANT vtmp;
1034 	HRESULT res;
1035 
1036 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(),
1037 		"Ol", &zobj, php_com_variant_class_entry, &vt)) {
1038 		RETURN_THROWS();
1039 	}
1040 	obj = CDNO_FETCH(zobj);
1041 
1042 	if (V_VT(&obj->v) == VT_ERROR) {
1043 		VariantInit(&vtmp);
1044 		V_VT(&vtmp) = VT_I4;
1045 		V_I4(&vtmp) = V_ERROR(&obj->v);
1046 	}
1047 	res = VariantChangeType(&obj->v, V_VT(&obj->v) != VT_ERROR ? &obj->v : &vtmp, 0, (VARTYPE)vt);
1048 
1049 	if (SUCCEEDED(res)) {
1050 		if (vt != VT_DISPATCH && obj->typeinfo) {
1051 			ITypeInfo_Release(obj->typeinfo);
1052 			obj->typeinfo = NULL;
1053 		}
1054 	} else {
1055 		char *werr, *msg;
1056 
1057 		werr = php_win32_error_to_msg(res);
1058 		spprintf(&msg, 0, "Variant type conversion failed: %s", werr);
1059 		php_win32_error_msg_free(werr);
1060 
1061 		php_com_throw_exception(res, msg);
1062 		efree(msg);
1063 	}
1064 }
1065 /* }}} */
1066 
1067 /* {{{ Convert a variant into a new variant object of another type */
PHP_FUNCTION(variant_cast)1068 PHP_FUNCTION(variant_cast)
1069 {
1070 	zval *zobj;
1071 	php_com_dotnet_object *obj;
1072 	/* VARTYPE == unsigned short */ zend_long vt;
1073 	VARIANT vres;
1074 	HRESULT res;
1075 
1076 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(),
1077 		"Ol", &zobj, php_com_variant_class_entry, &vt)) {
1078 		RETURN_THROWS();
1079 	}
1080 	obj = CDNO_FETCH(zobj);
1081 
1082 	VariantInit(&vres);
1083 	if (V_VT(&obj->v) == VT_ERROR) {
1084 		V_VT(&vres) = VT_I4;
1085 		V_I4(&vres) = V_ERROR(&obj->v);
1086 	}
1087 	res = VariantChangeType(&vres, V_VT(&vres) == VT_EMPTY ? &obj->v : &vres, 0, (VARTYPE)vt);
1088 
1089 	if (SUCCEEDED(res)) {
1090 		php_com_wrap_variant(return_value, &vres, obj->code_page);
1091 	} else {
1092 		char *werr, *msg;
1093 
1094 		werr = php_win32_error_to_msg(res);
1095 		spprintf(&msg, 0, "Variant type conversion failed: %s", werr);
1096 		php_win32_error_msg_free(werr);
1097 
1098 		php_com_throw_exception(res, msg);
1099 		efree(msg);
1100 	}
1101 
1102 	VariantClear(&vres);
1103 }
1104 /* }}} */
1105