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