xref: /PHP-8.4/ext/com_dotnet/com_saproxy.c (revision 11accb5c)
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 /* This module implements a SafeArray proxy which is used internally
18  * by the engine when resolving multi-dimensional array accesses on
19  * SafeArray types.
20  * In addition, the proxy is now able to handle properties of COM objects
21  * that smell like PHP arrays.
22  * */
23 
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 
28 #include "php.h"
29 #include "php_ini.h"
30 #include "ext/standard/info.h"
31 #include "php_com_dotnet.h"
32 #include "php_com_dotnet_internal.h"
33 #include "Zend/zend_exceptions.h"
34 
35 typedef struct {
36 	zend_object std;
37 	/* the object we a proxying for; we hold a refcount to it */
38 	php_com_dotnet_object *obj;
39 
40 	/* how many dimensions we are indirecting to get into this element */
41 	LONG dimensions;
42 
43 	/* this is an array whose size_is(dimensions) */
44 	zval *indices;
45 
46 } php_com_saproxy;
47 
48 typedef struct {
49 	zend_object_iterator iter;
50 	zval proxy_obj;
51 	zval data;
52 	php_com_saproxy *proxy;
53 	LONG key;
54 	LONG imin, imax;
55 	LONG *indices;
56 } php_com_saproxy_iter;
57 
58 #define SA_FETCH(zv)			(php_com_saproxy*)Z_OBJ_P(zv)
59 
clone_indices(php_com_saproxy * dest,php_com_saproxy * src,int ndims)60 static inline void clone_indices(php_com_saproxy *dest, php_com_saproxy *src, int ndims)
61 {
62 	int i;
63 
64 	for (i = 0; i < ndims; i++) {
65 		ZVAL_DUP(&dest->indices[i], &src->indices[i]);
66 	}
67 }
68 
saproxy_property_read(zend_object * object,zend_string * member,int type,void ** cache_slot,zval * rv)69 static zval *saproxy_property_read(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv)
70 {
71 	ZVAL_NULL(rv);
72 
73 	php_com_throw_exception(E_INVALIDARG, "safearray has no properties");
74 
75 	return rv;
76 }
77 
saproxy_property_write(zend_object * object,zend_string * member,zval * value,void ** cache_slot)78 static zval *saproxy_property_write(zend_object *object, zend_string *member, zval *value, void **cache_slot)
79 {
80 	php_com_throw_exception(E_INVALIDARG, "safearray has no properties");
81 	return value;
82 }
83 
saproxy_read_dimension(zend_object * object,zval * offset,int type,zval * rv)84 static zval *saproxy_read_dimension(zend_object *object, zval *offset, int type, zval *rv)
85 {
86 	php_com_saproxy *proxy = (php_com_saproxy*) object;
87 	UINT dims, i;
88 	SAFEARRAY *sa;
89 	LONG ubound, lbound;
90 	HRESULT res;
91 
92 	ZVAL_NULL(rv);
93 
94 	if (V_VT(&proxy->obj->v) == VT_DISPATCH) {
95 		VARIANT v;
96 		zval *args;
97 
98 		/* prop-get using first dimension as the property name,
99 		 * all subsequent dimensions and the offset as parameters */
100 
101 		args = safe_emalloc(proxy->dimensions + 1, sizeof(zval), 0);
102 
103 		for (i = 1; i < (UINT) proxy->dimensions; i++) {
104 			args[i-1] = proxy->indices[i];
105 		}
106 		ZVAL_COPY_VALUE(&args[i-1], offset);
107 
108 		if (!try_convert_to_string(&proxy->indices[0])) {
109 			efree(args);
110 			return rv;
111 		}
112 		VariantInit(&v);
113 
114 		res = php_com_do_invoke(proxy->obj, Z_STR(proxy->indices[0]),
115 				DISPATCH_METHOD|DISPATCH_PROPERTYGET, &v,
116 			   	proxy->dimensions, args, 0);
117 
118 		efree(args);
119 
120 		if (res == SUCCESS) {
121 			php_com_zval_from_variant(rv, &v, proxy->obj->code_page);
122 			VariantClear(&v);
123 		} else if (res == DISP_E_BADPARAMCOUNT) {
124 			/* return another proxy */
125 			php_com_saproxy_create(object, rv, offset);
126 		}
127 
128 		return rv;
129 
130 	} else if (!V_ISARRAY(&proxy->obj->v)) {
131 		php_com_throw_exception(E_INVALIDARG, "invalid read from com proxy object");
132 		return rv;
133 	}
134 
135 	/* the SafeArray case */
136 
137 	/* offset/index must be an integer */
138 	convert_to_long(offset);
139 
140 	sa = V_ARRAY(&proxy->obj->v);
141 	dims = SafeArrayGetDim(sa);
142 
143 	if ((UINT) proxy->dimensions >= dims) {
144 		/* too many dimensions */
145 		php_com_throw_exception(E_INVALIDARG, "too many dimensions!");
146 		return rv;
147 	}
148 
149 	/* bounds check */
150 	SafeArrayGetLBound(sa, proxy->dimensions, &lbound);
151 	SafeArrayGetUBound(sa, proxy->dimensions, &ubound);
152 
153 	if (Z_LVAL_P(offset) < lbound || Z_LVAL_P(offset) > ubound) {
154 		php_com_throw_exception(DISP_E_BADINDEX, "index out of bounds");
155 		return rv;
156 	}
157 
158 	if (dims - 1 == proxy->dimensions) {
159 		LONG *indices;
160 		VARTYPE vt;
161 		VARIANT v;
162 
163 		VariantInit(&v);
164 
165 		/* we can return a real value */
166 		indices = safe_emalloc(dims, sizeof(LONG), 0);
167 
168 		/* copy indices from proxy */
169 		for (i = 0; i < dims; i++) {
170 			convert_to_long(&proxy->indices[i]);
171 			indices[i] = (LONG)Z_LVAL(proxy->indices[i]);
172 		}
173 
174 		/* add user-supplied index */
175 		indices[dims-1] = (LONG)Z_LVAL_P(offset);
176 
177 		/* now fetch the value */
178 		if (FAILED(SafeArrayGetVartype(sa, &vt)) || vt == VT_EMPTY) {
179 			vt = V_VT(&proxy->obj->v) & ~VT_ARRAY;
180 		}
181 
182 		if (vt == VT_VARIANT) {
183 			res = SafeArrayGetElement(sa, indices, &v);
184 		} else {
185 			V_VT(&v) = vt;
186 			res = SafeArrayGetElement(sa, indices, &v.lVal);
187 		}
188 
189 		efree(indices);
190 
191 		if (SUCCEEDED(res)) {
192 			php_com_wrap_variant(rv, &v, proxy->obj->code_page);
193 		} else {
194 			php_com_throw_exception(res, NULL);
195 		}
196 
197 		VariantClear(&v);
198 
199 	} else {
200 		/* return another proxy */
201 		php_com_saproxy_create(object, rv, offset);
202 	}
203 
204 	return rv;
205 }
206 
saproxy_write_dimension(zend_object * object,zval * offset,zval * value)207 static void saproxy_write_dimension(zend_object *object, zval *offset, zval *value)
208 {
209 	php_com_saproxy *proxy = (php_com_saproxy*) object;
210 	UINT dims, i;
211 	HRESULT res;
212 	VARIANT v;
213 
214 	if (V_VT(&proxy->obj->v) == VT_DISPATCH) {
215 		/* We do a prop-set using the first dimension as the property name,
216 		 * all subsequent dimensions and offset as parameters, with value as
217 		 * the final value */
218 		zval *args = safe_emalloc(proxy->dimensions + 2, sizeof(zval), 0);
219 
220 		for (i = 1; i < (UINT) proxy->dimensions; i++) {
221 			ZVAL_COPY_VALUE(&args[i-1], &proxy->indices[i]);
222 		}
223 		ZVAL_COPY_VALUE(&args[i-1], offset);
224 		ZVAL_COPY_VALUE(&args[i], value);
225 
226 		if (!try_convert_to_string(&proxy->indices[0])) {
227 			efree(args);
228 			return;
229 		}
230 		VariantInit(&v);
231 		if (SUCCESS == php_com_do_invoke(proxy->obj, Z_STR(proxy->indices[0]),
232 					DISPATCH_PROPERTYPUT, &v, proxy->dimensions + 1,
233 					args, 0)) {
234 			VariantClear(&v);
235 		}
236 
237 		efree(args);
238 
239 	} else if (V_ISARRAY(&proxy->obj->v)) {
240 		LONG *indices;
241 		VARTYPE vt;
242 
243 		dims = SafeArrayGetDim(V_ARRAY(&proxy->obj->v));
244 		indices = safe_emalloc(dims, sizeof(LONG), 0);
245 		/* copy indices from proxy */
246 		for (i = 0; i < dims; i++) {
247 			convert_to_long(&proxy->indices[i]);
248 			indices[i] = (LONG)Z_LVAL(proxy->indices[i]);
249 		}
250 
251 		/* add user-supplied index */
252 		convert_to_long(offset);
253 		indices[dims-1] = (LONG)Z_LVAL_P(offset);
254 
255 		if (FAILED(SafeArrayGetVartype(V_ARRAY(&proxy->obj->v), &vt)) || vt == VT_EMPTY) {
256 			vt = V_VT(&proxy->obj->v) & ~VT_ARRAY;
257 		}
258 
259 		VariantInit(&v);
260 		php_com_variant_from_zval(&v, value, proxy->obj->code_page);
261 
262 		if (V_VT(&v) != vt) {
263 			VariantChangeType(&v, &v, 0, vt);
264 		}
265 
266 		if (vt == VT_VARIANT) {
267 			res = SafeArrayPutElement(V_ARRAY(&proxy->obj->v), indices, &v);
268 		} else {
269 			res = SafeArrayPutElement(V_ARRAY(&proxy->obj->v), indices, &v.lVal);
270 		}
271 
272 		efree(indices);
273 		VariantClear(&v);
274 
275 		if (FAILED(res)) {
276 			php_com_throw_exception(res, NULL);
277 		}
278 	} else {
279 		php_com_throw_exception(E_NOTIMPL, "invalid write to com proxy object");
280 	}
281 }
282 
saproxy_property_exists(zend_object * object,zend_string * member,int check_empty,void ** cache_slot)283 static int saproxy_property_exists(zend_object *object, zend_string *member, int check_empty, void **cache_slot)
284 {
285 	/* no properties */
286 	return 0;
287 }
288 
saproxy_dimension_exists(zend_object * object,zval * member,int check_empty)289 static int saproxy_dimension_exists(zend_object *object, zval *member, int check_empty)
290 {
291 	/* TODO Add support */
292 	zend_throw_error(NULL, "Cannot check dimension on a COM object");
293 	return 0;
294 }
295 
saproxy_property_delete(zend_object * object,zend_string * member,void ** cache_slot)296 static void saproxy_property_delete(zend_object *object, zend_string *member, void **cache_slot)
297 {
298 	zend_throw_error(NULL, "Cannot delete properties from a COM object");
299 }
300 
saproxy_dimension_delete(zend_object * object,zval * offset)301 static void saproxy_dimension_delete(zend_object *object, zval *offset)
302 {
303 	zend_throw_error(NULL, "Cannot delete dimension from a COM object");
304 }
305 
saproxy_properties_get(zend_object * object)306 static HashTable *saproxy_properties_get(zend_object *object)
307 {
308 	/* no properties */
309 	return NULL;
310 }
311 
saproxy_method_get(zend_object ** object,zend_string * name,const zval * key)312 static zend_function *saproxy_method_get(zend_object **object, zend_string *name, const zval *key)
313 {
314 	/* no methods */
315 	return NULL;
316 }
317 
saproxy_constructor_get(zend_object * object)318 static zend_function *saproxy_constructor_get(zend_object *object)
319 {
320 	/* user cannot instantiate */
321 	return NULL;
322 }
323 
saproxy_class_name_get(const zend_object * object)324 static zend_string* saproxy_class_name_get(const zend_object *object)
325 {
326 	return zend_string_copy(php_com_saproxy_class_entry->name);
327 }
328 
saproxy_objects_compare(zval * object1,zval * object2)329 static int saproxy_objects_compare(zval *object1, zval *object2)
330 {
331 	ZEND_COMPARE_OBJECTS_FALLBACK(object1, object2);
332 	return -1;
333 }
334 
saproxy_object_cast(zend_object * readobj,zval * writeobj,int type)335 static zend_result saproxy_object_cast(zend_object *readobj, zval *writeobj, int type)
336 {
337 	return FAILURE;
338 }
339 
saproxy_count_elements(zend_object * object,zend_long * count)340 static zend_result saproxy_count_elements(zend_object *object, zend_long *count)
341 {
342 	php_com_saproxy *proxy = (php_com_saproxy*) object;
343 	LONG ubound, lbound;
344 
345 	if (!V_ISARRAY(&proxy->obj->v)) {
346 		return FAILURE;
347 	}
348 
349 	SafeArrayGetLBound(V_ARRAY(&proxy->obj->v), proxy->dimensions, &lbound);
350 	SafeArrayGetUBound(V_ARRAY(&proxy->obj->v), proxy->dimensions, &ubound);
351 
352 	*count = ubound - lbound + 1;
353 
354 	return SUCCESS;
355 }
356 
saproxy_free_storage(zend_object * object)357 static void saproxy_free_storage(zend_object *object)
358 {
359 	php_com_saproxy *proxy = (php_com_saproxy *)object;
360 //???	int i;
361 //???
362 //???	for (i = 0; i < proxy->dimensions; i++) {
363 //???		if (proxy->indices) {
364 //???				FREE_ZVAL(proxy->indices[i]);
365 //???		}
366 //???	}
367 
368 	OBJ_RELEASE(&proxy->obj->zo);
369 
370 	zend_object_std_dtor(object);
371 
372 	efree(proxy->indices);
373 }
374 
saproxy_clone(zend_object * object)375 static zend_object* saproxy_clone(zend_object *object)
376 {
377 	php_com_saproxy *proxy = (php_com_saproxy *) object;
378 	php_com_saproxy *cloneproxy;
379 
380 	cloneproxy = emalloc(sizeof(*cloneproxy));
381 	memcpy(cloneproxy, proxy, sizeof(*cloneproxy));
382 
383 	GC_ADDREF(&cloneproxy->obj->zo);
384 	cloneproxy->indices = safe_emalloc(cloneproxy->dimensions, sizeof(zval), 0);
385 	clone_indices(cloneproxy, proxy, proxy->dimensions);
386 
387 	return &cloneproxy->std;
388 }
389 
390 zend_object_handlers php_com_saproxy_handlers = {
391 	0,
392 	saproxy_free_storage,
393 	zend_objects_destroy_object,
394 	saproxy_clone,
395 	saproxy_property_read,
396 	saproxy_property_write,
397 	saproxy_read_dimension,
398 	saproxy_write_dimension,
399 	NULL,
400 	saproxy_property_exists,
401 	saproxy_property_delete,
402 	saproxy_dimension_exists,
403 	saproxy_dimension_delete,
404 	saproxy_properties_get,
405 	saproxy_method_get,
406 	saproxy_constructor_get,
407 	saproxy_class_name_get,
408 	saproxy_object_cast,
409 	saproxy_count_elements,
410 	NULL,									/* get_debug_info */
411 	NULL,									/* get_closure */
412 	NULL,									/* get_gc */
413 	NULL,									/* do_operation */
414 	saproxy_objects_compare,				/* compare */
415 	NULL,									/* get_properties_for */
416 };
417 
php_com_saproxy_create(zend_object * com_object,zval * proxy_out,zval * index)418 void php_com_saproxy_create(zend_object *com_object, zval *proxy_out, zval *index)
419 {
420 	php_com_saproxy *proxy, *rel = NULL;
421 
422 	proxy = ecalloc(1, sizeof(*proxy));
423 	proxy->dimensions = 1;
424 
425 	if (com_object->ce == php_com_saproxy_class_entry) {
426 		rel = (php_com_saproxy*) com_object;
427 		proxy->obj = rel->obj;
428 		proxy->dimensions += rel->dimensions;
429 	} else {
430 		proxy->obj = (php_com_dotnet_object*) com_object;
431 	}
432 
433 	GC_ADDREF(&proxy->obj->zo);
434 	proxy->indices = safe_emalloc(proxy->dimensions, sizeof(zval), 0);
435 
436 	if (rel) {
437 		clone_indices(proxy, rel, rel->dimensions);
438 	}
439 
440 	ZVAL_DUP(&proxy->indices[proxy->dimensions-1], index);
441 
442 	zend_object_std_init(&proxy->std, php_com_saproxy_class_entry);
443 	ZVAL_OBJ(proxy_out, &proxy->std);
444 }
445 
446 /* iterator */
447 
saproxy_iter_dtor(zend_object_iterator * iter)448 static void saproxy_iter_dtor(zend_object_iterator *iter)
449 {
450 	php_com_saproxy_iter *I = (php_com_saproxy_iter*)Z_PTR(iter->data);
451 
452 	zval_ptr_dtor(&I->proxy_obj);
453 
454 	efree(I->indices);
455 	efree(I);
456 }
457 
saproxy_iter_valid(zend_object_iterator * iter)458 static zend_result saproxy_iter_valid(zend_object_iterator *iter)
459 {
460 	php_com_saproxy_iter *I = (php_com_saproxy_iter*)Z_PTR(iter->data);
461 
462 	return (I->key < I->imax) ? SUCCESS : FAILURE;
463 }
464 
saproxy_iter_get_data(zend_object_iterator * iter)465 static zval* saproxy_iter_get_data(zend_object_iterator *iter)
466 {
467 	php_com_saproxy_iter *I = (php_com_saproxy_iter*)Z_PTR(iter->data);
468 	VARIANT v;
469 	VARTYPE vt;
470 	SAFEARRAY *sa;
471 
472 	I->indices[I->proxy->dimensions-1] = I->key;
473 
474 	sa = V_ARRAY(&I->proxy->obj->v);
475 
476 	if (FAILED(SafeArrayGetVartype(sa, &vt)) || vt == VT_EMPTY) {
477 		vt = V_VT(&I->proxy->obj->v) & ~VT_ARRAY;
478 	}
479 
480 	VariantInit(&v);
481 	if (vt == VT_VARIANT) {
482 		SafeArrayGetElement(sa, I->indices, &v);
483 	} else {
484 		V_VT(&v) = vt;
485 		SafeArrayGetElement(sa, I->indices, &v.lVal);
486 	}
487 
488 	ZVAL_NULL(&I->data);
489 	php_com_wrap_variant(&I->data, &v, I->proxy->obj->code_page);
490 	VariantClear(&v);
491 
492 	return &I->data;
493 }
494 
saproxy_iter_get_key(zend_object_iterator * iter,zval * key)495 static void saproxy_iter_get_key(zend_object_iterator *iter, zval *key)
496 {
497 	php_com_saproxy_iter *I = (php_com_saproxy_iter*)Z_PTR(iter->data);
498 
499 	if (I->key == -1) {
500 		ZVAL_NULL(key);
501 	} else {
502 		ZVAL_LONG(key, I->key);
503 	}
504 }
505 
saproxy_iter_move_forwards(zend_object_iterator * iter)506 static void saproxy_iter_move_forwards(zend_object_iterator *iter)
507 {
508 	php_com_saproxy_iter *I = (php_com_saproxy_iter*)Z_PTR(iter->data);
509 
510 	if (++I->key >= I->imax) {
511 		I->key = -1;
512 	}
513 }
514 
515 static const zend_object_iterator_funcs saproxy_iter_funcs = {
516 	saproxy_iter_dtor,
517 	saproxy_iter_valid,
518 	saproxy_iter_get_data,
519 	saproxy_iter_get_key,
520 	saproxy_iter_move_forwards,
521 	NULL,
522 	NULL, /* get_gc */
523 };
524 
525 
php_com_saproxy_iter_get(zend_class_entry * ce,zval * object,int by_ref)526 zend_object_iterator *php_com_saproxy_iter_get(zend_class_entry *ce, zval *object, int by_ref)
527 {
528 	php_com_saproxy *proxy = SA_FETCH(object);
529 	php_com_saproxy_iter *I;
530 	int i;
531 
532 	if (by_ref) {
533 		zend_throw_error(NULL, "An iterator cannot be used with foreach by reference");
534 		return NULL;
535 	}
536 
537 	I = ecalloc(1, sizeof(*I));
538 	I->iter.funcs = &saproxy_iter_funcs;
539 	Z_PTR(I->iter.data) = I;
540 
541 	I->proxy = proxy;
542 	Z_ADDREF_P(object);
543 	ZVAL_OBJ(&I->proxy_obj, Z_OBJ_P(object));
544 
545 	I->indices = safe_emalloc(proxy->dimensions + 1, sizeof(LONG), 0);
546 	for (i = 0; i < proxy->dimensions; i++) {
547 		convert_to_long(&proxy->indices[i]);
548 		I->indices[i] = (LONG)Z_LVAL(proxy->indices[i]);
549 	}
550 
551 	SafeArrayGetLBound(V_ARRAY(&proxy->obj->v), proxy->dimensions, &I->imin);
552 	SafeArrayGetUBound(V_ARRAY(&proxy->obj->v), proxy->dimensions, &I->imax);
553 
554 	I->key = I->imin;
555 
556 	return &I->iter;
557 }
558