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