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