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