xref: /PHP-8.2/ext/com_dotnet/com_handlers.c (revision efc73f24)
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 #include "Zend/zend_exceptions.h"
27 
com_property_read(zend_object * object,zend_string * member,int type,void ** cache_slot,zval * rv)28 static zval *com_property_read(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv)
29 {
30 	php_com_dotnet_object *obj;
31 	VARIANT v;
32 	HRESULT res;
33 
34 	ZVAL_NULL(rv);
35 
36 	obj = (php_com_dotnet_object*) object;
37 
38 	if (V_VT(&obj->v) == VT_DISPATCH) {
39 		VariantInit(&v);
40 
41 		res = php_com_do_invoke(obj, member, DISPATCH_METHOD|DISPATCH_PROPERTYGET,
42 			&v, 0, NULL, 1);
43 
44 		if (res == SUCCESS) {
45 			php_com_zval_from_variant(rv, &v, obj->code_page);
46 			VariantClear(&v);
47 		} else if (res == DISP_E_BADPARAMCOUNT) {
48 			zval zv;
49 
50 			ZVAL_STR(&zv, member);
51 			php_com_saproxy_create(object, rv, &zv);
52 		}
53 	} else {
54 		php_com_throw_exception(E_INVALIDARG, "this variant has no properties");
55 	}
56 
57 	return rv;
58 }
59 
com_property_write(zend_object * object,zend_string * member,zval * value,void ** cache_slot)60 static zval *com_property_write(zend_object *object, zend_string *member, zval *value, void **cache_slot)
61 {
62 	php_com_dotnet_object *obj;
63 	VARIANT v;
64 
65 	obj = (php_com_dotnet_object*) object;
66 
67 	if (V_VT(&obj->v) == VT_DISPATCH) {
68 		VariantInit(&v);
69 
70 		if (SUCCESS == php_com_do_invoke(obj, member,
71 				DISPATCH_PROPERTYPUT|DISPATCH_PROPERTYPUTREF, &v, 1, value, 0)) {
72 			VariantClear(&v);
73 		}
74 	} else {
75 		php_com_throw_exception(E_INVALIDARG, "this variant has no properties");
76 	}
77 	return value;
78 }
79 
com_read_dimension(zend_object * object,zval * offset,int type,zval * rv)80 static zval *com_read_dimension(zend_object *object, zval *offset, int type, zval *rv)
81 {
82 	php_com_dotnet_object *obj;
83 	VARIANT v;
84 
85 	ZVAL_NULL(rv);
86 
87 	obj = (php_com_dotnet_object*) object;
88 
89 	if (V_VT(&obj->v) == VT_DISPATCH) {
90 		VariantInit(&v);
91 
92 		if (SUCCESS == php_com_do_invoke_by_id(obj, DISPID_VALUE,
93 				DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, 1, offset, 0, 0)) {
94 			php_com_zval_from_variant(rv, &v, obj->code_page);
95 			VariantClear(&v);
96 		}
97 	} else if (V_ISARRAY(&obj->v)) {
98 		convert_to_long(offset);
99 
100 		if (SafeArrayGetDim(V_ARRAY(&obj->v)) == 1) {
101 			if (php_com_safearray_get_elem(&obj->v, &v, (LONG)Z_LVAL_P(offset))) {
102 				php_com_wrap_variant(rv, &v, obj->code_page);
103 				VariantClear(&v);
104 			}
105 		} else {
106 			php_com_saproxy_create(object, rv, offset);
107 		}
108 
109 	} else {
110 		php_com_throw_exception(E_INVALIDARG, "this variant is not an array type");
111 	}
112 
113 	return rv;
114 }
115 
com_write_dimension(zend_object * object,zval * offset,zval * value)116 static void com_write_dimension(zend_object *object, zval *offset, zval *value)
117 {
118 	php_com_dotnet_object *obj;
119 	zval args[2];
120 	VARIANT v;
121 	HRESULT res;
122 
123 	obj = (php_com_dotnet_object*) object;
124 
125 	if (offset == NULL) {
126 		php_com_throw_exception(DISP_E_BADINDEX, "appending to variants is not supported");
127 		return;
128 	}
129 
130 	if (V_VT(&obj->v) == VT_DISPATCH) {
131 		ZVAL_COPY_VALUE(&args[0], offset);
132 		ZVAL_COPY_VALUE(&args[1], value);
133 
134 		VariantInit(&v);
135 
136 		if (SUCCESS == php_com_do_invoke_by_id(obj, DISPID_VALUE,
137 				DISPATCH_METHOD|DISPATCH_PROPERTYPUT, &v, 2, args, 0, 0)) {
138 			VariantClear(&v);
139 		}
140 	} else if (V_ISARRAY(&obj->v)) {
141 		LONG indices = 0;
142 		VARTYPE vt;
143 
144 		if (SafeArrayGetDim(V_ARRAY(&obj->v)) == 1) {
145 			if (FAILED(SafeArrayGetVartype(V_ARRAY(&obj->v), &vt)) || vt == VT_EMPTY) {
146 				vt = V_VT(&obj->v) & ~VT_ARRAY;
147 			}
148 
149 			convert_to_long(offset);
150 			indices = (LONG)Z_LVAL_P(offset);
151 
152 			VariantInit(&v);
153 			php_com_variant_from_zval(&v, value, obj->code_page);
154 
155 			if (V_VT(&v) != vt) {
156 				VariantChangeType(&v, &v, 0, vt);
157 			}
158 
159 			if (vt == VT_VARIANT) {
160 				res = SafeArrayPutElement(V_ARRAY(&obj->v), &indices, &v);
161 			} else {
162 				res = SafeArrayPutElement(V_ARRAY(&obj->v), &indices, &v.lVal);
163 			}
164 
165 			VariantClear(&v);
166 
167 			if (FAILED(res)) {
168 				php_com_throw_exception(res, NULL);
169 			}
170 
171 		} else {
172 			php_com_throw_exception(DISP_E_BADINDEX, "this variant has multiple dimensions; you can't set a new value without specifying *all* dimensions");
173 		}
174 
175 	} else {
176 		php_com_throw_exception(E_INVALIDARG, "this variant is not an array type");
177 	}
178 }
179 
com_get_property_ptr_ptr(zend_object * object,zend_string * member,int type,void ** cache_slot)180 static zval *com_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot)
181 {
182 	return NULL;
183 }
184 
com_property_exists(zend_object * object,zend_string * member,int check_empty,void ** cache_slot)185 static int com_property_exists(zend_object *object, zend_string *member, int check_empty, void **cache_slot)
186 {
187 	DISPID dispid;
188 	php_com_dotnet_object *obj;
189 
190 	obj = (php_com_dotnet_object*) object;
191 
192 	if (V_VT(&obj->v) == VT_DISPATCH) {
193 		if (SUCCEEDED(php_com_get_id_of_name(obj, member, &dispid))) {
194 			/* TODO: distinguish between property and method! */
195 			return 1;
196 		}
197 	} else {
198 		/* TODO: check for safearray */
199 	}
200 
201 	return 0;
202 }
203 
com_dimension_exists(zend_object * object,zval * member,int check_empty)204 static int com_dimension_exists(zend_object *object, zval *member, int check_empty)
205 {
206 	/* TODO Add support */
207 	zend_throw_error(NULL, "Cannot check dimension on a COM object");
208 	return 0;
209 }
210 
com_property_delete(zend_object * object,zend_string * member,void ** cache_slot)211 static void com_property_delete(zend_object *object, zend_string *member, void **cache_slot)
212 {
213 	zend_throw_error(NULL, "Cannot delete properties from a COM object");
214 }
215 
com_dimension_delete(zend_object * object,zval * offset)216 static void com_dimension_delete(zend_object *object, zval *offset)
217 {
218 	zend_throw_error(NULL, "Cannot delete dimension from a COM object");
219 }
220 
com_properties_get(zend_object * object)221 static HashTable *com_properties_get(zend_object *object)
222 {
223 	/* TODO: use type-info to get all the names and values ?
224 	 * DANGER: if we do that, there is a strong possibility for
225 	 * infinite recursion when the hash is displayed via var_dump().
226 	 * Perhaps it is best to leave it un-implemented.
227 	 */
228 	return (HashTable *) &zend_empty_array;
229 }
230 
com_get_gc(zend_object * object,zval ** table,int * n)231 static HashTable *com_get_gc(zend_object *object, zval **table, int *n)
232 {
233 	*table = NULL;
234 	*n = 0;
235 	return NULL;
236 }
237 
function_dtor(zval * zv)238 static void function_dtor(zval *zv)
239 {
240 	zend_internal_function *f = (zend_internal_function*)Z_PTR_P(zv);
241 
242 	zend_string_release_ex(f->function_name, 0);
243 	if (f->arg_info) {
244 		efree(f->arg_info);
245 	}
246 	efree(f);
247 }
248 
PHP_FUNCTION(com_method_handler)249 static PHP_FUNCTION(com_method_handler)
250 {
251 	zval *object = getThis();
252 	zend_string *method = EX(func)->common.function_name;
253 	zval *args = NULL;
254 	php_com_dotnet_object *obj = CDNO_FETCH(object);
255 	int nargs;
256 	VARIANT v;
257 	zend_result ret = FAILURE;
258 
259 	if (V_VT(&obj->v) != VT_DISPATCH) {
260 		goto exit;
261 	}
262 
263 	nargs = ZEND_NUM_ARGS();
264 
265 	if (nargs) {
266 		args = (zval *)safe_emalloc(sizeof(zval), nargs, 0);
267 		zend_get_parameters_array_ex(nargs, args);
268 	}
269 
270 	VariantInit(&v);
271 
272 	if (SUCCESS == php_com_do_invoke_byref(obj, (zend_internal_function*)EX(func), DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, nargs, args)) {
273 		ret = php_com_zval_from_variant(return_value, &v, obj->code_page);
274 		VariantClear(&v);
275 	}
276 
277 	if (args) {
278 		efree(args);
279 	}
280 
281 exit:
282 	/* Cleanup trampoline */
283 	ZEND_ASSERT(EX(func)->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE);
284 	zend_string_release(EX(func)->common.function_name);
285 	zend_free_trampoline(EX(func));
286 	EX(func) = NULL;
287 }
288 
com_method_get(zend_object ** object_ptr,zend_string * name,const zval * key)289 static zend_function *com_method_get(zend_object **object_ptr, zend_string *name, const zval *key)
290 {
291 	zend_internal_function f, *fptr = NULL;
292 	zend_function *func;
293 	DISPID dummy;
294 	php_com_dotnet_object *obj = (php_com_dotnet_object*)*object_ptr;
295 
296 	if (V_VT(&obj->v) != VT_DISPATCH) {
297 		return NULL;
298 	}
299 
300 	if (FAILED(php_com_get_id_of_name(obj, name, &dummy))) {
301 		return NULL;
302 	}
303 
304 	/* check cache */
305 	if (obj->method_cache == NULL || NULL == (fptr = zend_hash_find_ptr(obj->method_cache, name))) {
306 		memset(&f, 0, sizeof(zend_internal_function));
307 		f.type = ZEND_INTERNAL_FUNCTION;
308 		f.num_args = 0;
309 		f.arg_info = NULL;
310 		f.scope = obj->ce;
311 		f.fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
312 		f.function_name = zend_string_copy(name);
313 		f.handler = PHP_FN(com_method_handler);
314 
315 		fptr = &f;
316 
317 		if (obj->typeinfo) {
318 			/* look for byref params */
319 			ITypeComp *comp;
320 			ITypeInfo *TI = NULL;
321 			DESCKIND kind;
322 			BINDPTR bindptr;
323 			OLECHAR *olename;
324 			ULONG lhash;
325 			int i;
326 
327 			if (SUCCEEDED(ITypeInfo_GetTypeComp(obj->typeinfo, &comp))) {
328 				olename = php_com_string_to_olestring(name->val, name->len, obj->code_page);
329 				lhash = LHashValOfNameSys(SYS_WIN32, LOCALE_NEUTRAL, olename);
330 
331 				if (SUCCEEDED(ITypeComp_Bind(comp, olename, lhash, INVOKE_FUNC, &TI, &kind, &bindptr))) {
332 					switch (kind) {
333 						case DESCKIND_FUNCDESC:
334 							f.arg_info = ecalloc(bindptr.lpfuncdesc->cParams, sizeof(zend_arg_info));
335 
336 							for (i = 0; i < bindptr.lpfuncdesc->cParams; i++) {
337 								bool by_ref = (bindptr.lpfuncdesc->lprgelemdescParam[i].paramdesc.wParamFlags & PARAMFLAG_FOUT) != 0;
338 								f.arg_info[i].type = (zend_type) ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(by_ref, 0, 0));
339 							}
340 
341 							f.num_args = bindptr.lpfuncdesc->cParams;
342 
343 							ITypeInfo_ReleaseFuncDesc(TI, bindptr.lpfuncdesc);
344 							break;
345 
346 							/* these should not happen, but *might* happen if the user
347 							 * screws up; lets avoid a leak in that case */
348 						case DESCKIND_VARDESC:
349 							ITypeInfo_ReleaseVarDesc(TI, bindptr.lpvardesc);
350 							break;
351 						case DESCKIND_TYPECOMP:
352 							ITypeComp_Release(bindptr.lptcomp);
353 							break;
354 
355 						case DESCKIND_NONE:
356 							break;
357 					}
358 					if (TI) {
359 						ITypeInfo_Release(TI);
360 					}
361 				}
362 				ITypeComp_Release(comp);
363 				efree(olename);
364 			}
365 		}
366 
367 		zend_set_function_arg_flags((zend_function*)&f);
368 		/* save this method in the cache */
369 		if (!obj->method_cache) {
370 			ALLOC_HASHTABLE(obj->method_cache);
371 			zend_hash_init(obj->method_cache, 2, NULL, function_dtor, 0);
372 		}
373 
374 		zend_hash_update_mem(obj->method_cache, name, &f, sizeof(f));
375 	}
376 
377 	if (fptr) {
378 		/* duplicate this into a new chunk of emalloc'd memory,
379 		 * since the engine will efree it */
380 		zend_string_addref(fptr->function_name);
381 		func = emalloc(sizeof(*fptr));
382 		memcpy(func, fptr, sizeof(*fptr));
383 
384 		return func;
385 	}
386 
387 	return NULL;
388 }
389 
com_class_name_get(const zend_object * object)390 static zend_string* com_class_name_get(const zend_object *object)
391 {
392 	php_com_dotnet_object *obj = (php_com_dotnet_object *)object;
393 
394 	return zend_string_copy(obj->ce->name);
395 }
396 
397 /* This compares two variants for equality */
com_objects_compare(zval * object1,zval * object2)398 static int com_objects_compare(zval *object1, zval *object2)
399 {
400 	php_com_dotnet_object *obja, *objb;
401 	int ret;
402 	/* strange header bug problem here... the headers define the proto without the
403 	 * flags parameter.  However, the MSDN docs state that there is a flags parameter,
404 	 * and my VC6 won't link unless the code uses the version with 4 parameters.
405 	 * So, we have this declaration here to fix it */
406 	STDAPI VarCmp(LPVARIANT pvarLeft, LPVARIANT pvarRight, LCID lcid, DWORD flags);
407 
408 	ZEND_COMPARE_OBJECTS_FALLBACK(object1, object2);
409 
410 	obja = CDNO_FETCH(object1);
411 	objb = CDNO_FETCH(object2);
412 
413 	switch (VarCmp(&obja->v, &objb->v, LOCALE_NEUTRAL, 0)) {
414 		case VARCMP_LT:
415 			ret = -1;
416 			break;
417 		case VARCMP_GT:
418 			ret = 1;
419 			break;
420 		case VARCMP_EQ:
421 			ret = 0;
422 			break;
423 		default:
424 			/* either or both operands are NULL...
425 			 * not 100% sure how to handle this */
426 			ret = -2;
427 	}
428 
429 	return ret;
430 }
431 
com_object_cast(zend_object * readobj,zval * writeobj,int type)432 static zend_result com_object_cast(zend_object *readobj, zval *writeobj, int type)
433 {
434 	php_com_dotnet_object *obj;
435 	VARIANT v;
436 	VARTYPE vt = VT_EMPTY;
437 	HRESULT res = S_OK;
438 
439 	obj = (php_com_dotnet_object*) readobj;
440 	ZVAL_NULL(writeobj);
441 	VariantInit(&v);
442 
443 	if (V_VT(&obj->v) == VT_DISPATCH) {
444 		if (SUCCESS != php_com_do_invoke_by_id(obj, DISPID_VALUE,
445 				DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v, 0, NULL, 1, 0)) {
446 			VariantCopy(&v, &obj->v);
447 		}
448 	} else {
449 		VariantCopy(&v, &obj->v);
450 	}
451 
452 	switch(type) {
453 		case IS_LONG:
454 		case _IS_NUMBER:
455 #if SIZEOF_ZEND_LONG == 4
456 			vt = VT_I4;
457 #else
458 			vt = VT_I8;
459 #endif
460 			break;
461 		case IS_DOUBLE:
462 			vt = VT_R8;
463 			break;
464 		case IS_FALSE:
465 		case IS_TRUE:
466 		case _IS_BOOL:
467 			vt = VT_BOOL;
468 			break;
469 		case IS_STRING:
470 			vt = VT_BSTR;
471 			break;
472 		default:
473 			;
474 	}
475 
476 	if (vt != VT_EMPTY && vt != V_VT(&v)) {
477 		res = VariantChangeType(&v, &v, 0, vt);
478 	}
479 
480 	if (SUCCEEDED(res)) {
481 		php_com_zval_from_variant(writeobj, &v, obj->code_page);
482 	}
483 
484 	VariantClear(&v);
485 
486 	if (SUCCEEDED(res)) {
487 		return SUCCESS;
488 	}
489 
490 	return zend_std_cast_object_tostring(readobj, writeobj, type);
491 }
492 
com_object_count(zend_object * object,zend_long * count)493 static zend_result com_object_count(zend_object *object, zend_long *count)
494 {
495 	php_com_dotnet_object *obj;
496 	LONG ubound = 0, lbound = 0;
497 
498 	obj = (php_com_dotnet_object*) object;
499 
500 	if (!V_ISARRAY(&obj->v)) {
501 		return FAILURE;
502 	}
503 
504 	SafeArrayGetLBound(V_ARRAY(&obj->v), 1, &lbound);
505 	SafeArrayGetUBound(V_ARRAY(&obj->v), 1, &ubound);
506 
507 	*count = ubound - lbound + 1;
508 
509 	return SUCCESS;
510 }
511 
512 zend_object_handlers php_com_object_handlers = {
513 	0,
514 	php_com_object_free_storage,
515 	zend_objects_destroy_object,
516 	php_com_object_clone,
517 	com_property_read,
518 	com_property_write,
519 	com_read_dimension,
520 	com_write_dimension,
521 	com_get_property_ptr_ptr,
522 	com_property_exists,
523 	com_property_delete,
524 	com_dimension_exists,
525 	com_dimension_delete,
526 	com_properties_get,
527 	com_method_get,
528 	zend_std_get_constructor,
529 	com_class_name_get,
530 	com_object_cast,
531 	com_object_count,
532 	NULL,									/* get_debug_info */
533 	NULL,									/* get_closure */
534 	com_get_gc,								/* get_gc */
535 	NULL,									/* do_operation */
536 	com_objects_compare,					/* compare */
537 	NULL,									/* get_properties_for */
538 };
539 
php_com_object_enable_event_sink(php_com_dotnet_object * obj,bool enable)540 void php_com_object_enable_event_sink(php_com_dotnet_object *obj, bool enable)
541 {
542 	if (obj->sink_dispatch) {
543 		IConnectionPointContainer *cont;
544 		IConnectionPoint *point;
545 
546 		if (SUCCEEDED(IDispatch_QueryInterface(V_DISPATCH(&obj->v),
547 				&IID_IConnectionPointContainer, (void**)&cont))) {
548 
549 			if (SUCCEEDED(IConnectionPointContainer_FindConnectionPoint(cont,
550 					&obj->sink_id, &point))) {
551 
552 				if (enable) {
553 					IConnectionPoint_Advise(point, (IUnknown*)obj->sink_dispatch, &obj->sink_cookie);
554 				} else {
555 					IConnectionPoint_Unadvise(point, obj->sink_cookie);
556 				}
557 				IConnectionPoint_Release(point);
558 			}
559 			IConnectionPointContainer_Release(cont);
560 		}
561 	}
562 }
563 
php_com_object_free_storage(zend_object * object)564 void php_com_object_free_storage(zend_object *object)
565 {
566 	php_com_dotnet_object *obj = (php_com_dotnet_object*)object;
567 
568 	if (obj->typeinfo) {
569 		ITypeInfo_Release(obj->typeinfo);
570 		obj->typeinfo = NULL;
571 	}
572 
573 	if (obj->sink_dispatch) {
574 		php_com_object_enable_event_sink(obj, /* enable */ false);
575 		IDispatch_Release(obj->sink_dispatch);
576 		obj->sink_dispatch = NULL;
577 	}
578 
579 	VariantClear(&obj->v);
580 
581 	if (obj->method_cache) {
582 		zend_hash_destroy(obj->method_cache);
583 		FREE_HASHTABLE(obj->method_cache);
584 	}
585 	if (obj->id_of_name_cache) {
586 		zend_hash_destroy(obj->id_of_name_cache);
587 		FREE_HASHTABLE(obj->id_of_name_cache);
588 	}
589 
590 	zend_object_std_dtor(object);
591 }
592 
php_com_object_clone(zend_object * object)593 zend_object* php_com_object_clone(zend_object *object)
594 {
595 	php_com_dotnet_object *cloneobj, *origobject;
596 
597 	origobject = (php_com_dotnet_object*) object;
598 	cloneobj = (php_com_dotnet_object*)emalloc(sizeof(php_com_dotnet_object));
599 
600 	memcpy(cloneobj, origobject, sizeof(*cloneobj));
601 
602 	/* VariantCopy will perform VariantClear; we don't want to clobber
603 	 * the IDispatch that we memcpy'd, so we init a new variant in the
604 	 * clone structure */
605 	VariantInit(&cloneobj->v);
606 	/* We use the Indirection-following version of the API since we
607 	 * want to clone as much as possible */
608 	VariantCopyInd(&cloneobj->v, &origobject->v);
609 
610 	if (cloneobj->typeinfo) {
611 		ITypeInfo_AddRef(cloneobj->typeinfo);
612 	}
613 
614 	return (zend_object*)cloneobj;
615 }
616 
php_com_object_new(zend_class_entry * ce)617 zend_object* php_com_object_new(zend_class_entry *ce)
618 {
619 	php_com_dotnet_object *obj;
620 
621 	php_com_initialize();
622 	obj = emalloc(sizeof(*obj));
623 	memset(obj, 0, sizeof(*obj));
624 
625 	VariantInit(&obj->v);
626 	obj->code_page = CP_ACP;
627 	obj->ce = ce;
628 
629 	zend_object_std_init(&obj->zo, ce);
630 
631 	obj->typeinfo = NULL;
632 
633 	return (zend_object*)obj;
634 }
635