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