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