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