xref: /PHP-8.2/ext/com_dotnet/com_com.c (revision f6616846)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Author: Wez Furlong  <wez@thebrainroom.com>                          |
14    +----------------------------------------------------------------------+
15  */
16 
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #include "php.h"
22 #include "php_ini.h"
23 #include "ext/standard/info.h"
24 #include "php_com_dotnet.h"
25 #include "php_com_dotnet_internal.h"
26 #include "Zend/zend_exceptions.h"
27 
28 /* {{{ com_create_instance - ctor for COM class */
PHP_METHOD(com,__construct)29 PHP_METHOD(com, __construct)
30 {
31 	zval *object = getThis();
32 	zend_string *server_name = NULL;
33 	HashTable *server_params = NULL;
34 	php_com_dotnet_object *obj;
35 	char *module_name, *typelib_name = NULL;
36 	size_t module_name_len = 0, typelib_name_len = 0;
37 	zend_string *user_name = NULL, *password = NULL, *domain_name = NULL;
38 	OLECHAR *moniker;
39 	CLSID clsid;
40 	CLSCTX ctx = CLSCTX_SERVER;
41 	HRESULT res = E_FAIL;
42 	int mode = COMG(autoreg_case_sensitive) ? CONST_CS : 0;
43 	ITypeLib *TL = NULL;
44 	COSERVERINFO	info;
45 	COAUTHIDENTITY	authid = {0};
46 	COAUTHINFO		authinfo = {
47 		RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
48 		RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE,
49 		&authid, EOAC_NONE
50 	};
51 	zend_long cp = GetACP();
52 	const struct php_win32_cp *cp_it;
53 
54 	ZEND_PARSE_PARAMETERS_START(1, 4)
55 		Z_PARAM_STRING(module_name, module_name_len)
56 		Z_PARAM_OPTIONAL
57 		Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(server_params, server_name)
58 		Z_PARAM_LONG(cp)
59 		Z_PARAM_STRING(typelib_name, typelib_name_len)
60 	ZEND_PARSE_PARAMETERS_END();
61 
62 	php_com_initialize();
63 	obj = CDNO_FETCH(object);
64 
65 	cp_it = php_win32_cp_get_by_id((DWORD)cp);
66 	if (!cp_it) {
67 		php_com_throw_exception(E_INVALIDARG, "Could not create COM object - invalid codepage!");
68 		RETURN_THROWS();
69 	}
70 	obj->code_page = (int)cp;
71 
72 	if (server_name) {
73 		ctx = CLSCTX_REMOTE_SERVER;
74 	} else if (server_params) {
75 		zval *tmp;
76 
77 		/* decode the data from the array */
78 
79 		if (NULL != (tmp = zend_hash_str_find(server_params,
80 				"Server", sizeof("Server")-1))) {
81 			server_name = zval_get_string(tmp);
82 			ctx = CLSCTX_REMOTE_SERVER;
83 		}
84 
85 		if (NULL != (tmp = zend_hash_str_find(server_params,
86 				"Username", sizeof("Username")-1))) {
87 			user_name = zval_get_string(tmp);
88 		}
89 
90 		if (NULL != (tmp = zend_hash_str_find(server_params,
91 				"Password", sizeof("Password")-1))) {
92 			password = zval_get_string(tmp);
93 		}
94 
95 		if (NULL != (tmp = zend_hash_str_find(server_params,
96 				"Domain", sizeof("Domain")-1))) {
97 			domain_name = zval_get_string(tmp);
98 		}
99 
100 		if (NULL != (tmp = zend_hash_str_find(server_params,
101 				"Flags", sizeof("Flags")-1))) {
102 			ctx = (CLSCTX)zval_get_long(tmp);
103 		}
104 	}
105 
106 	if (server_name && !COMG(allow_dcom)) {
107 		php_com_throw_exception(E_ERROR, "DCOM has been disabled by your administrator [com.allow_dcom=0]");
108 		RETURN_THROWS();
109 	}
110 
111 	moniker = php_com_string_to_olestring(module_name, module_name_len, obj->code_page);
112 
113 	/* if instantiating a remote object, either directly, or via
114 	 * a moniker, fill in the relevant info */
115 	if (server_name) {
116 		info.dwReserved1 = 0;
117 		info.dwReserved2 = 0;
118 		info.pwszName = php_com_string_to_olestring(ZSTR_VAL(server_name), ZSTR_LEN(server_name), obj->code_page);
119 
120 		if (user_name) {
121 			authid.User = (OLECHAR*) ZSTR_VAL(user_name);
122 			authid.UserLength = (ULONG) ZSTR_LEN(user_name);
123 
124 			if (password) {
125 				authid.Password = (OLECHAR*) ZSTR_VAL(password);
126 				authid.PasswordLength = (ULONG) ZSTR_LEN(password);
127 			} else {
128 				authid.Password = (OLECHAR*) "";
129 				authid.PasswordLength = 0;
130 			}
131 
132 			if (domain_name) {
133 				authid.Domain = (OLECHAR*) ZSTR_VAL(domain_name);
134 				authid.DomainLength = (ULONG) ZSTR_LEN(domain_name);
135 			} else {
136 				authid.Domain = (OLECHAR*) "";
137 				authid.DomainLength = 0;
138 			}
139 			authid.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
140 			info.pAuthInfo = &authinfo;
141 		} else {
142 			info.pAuthInfo = NULL;
143 		}
144 	}
145 
146 	if (FAILED(CLSIDFromString(moniker, &clsid))) {
147 		/* try to use it as a moniker */
148 		IBindCtx *pBindCtx = NULL;
149 		IMoniker *pMoniker = NULL;
150 		ULONG ulEaten;
151 		BIND_OPTS2 bopt = {0};
152 
153 		if (SUCCEEDED(res = CreateBindCtx(0, &pBindCtx))) {
154 			if (server_name) {
155 				/* fill in the remote server info.
156 				 * MSDN docs indicate that this might be ignored in
157 				 * current win32 implementations, but at least we are
158 				 * doing the right thing in readiness for the day that
159 				 * it does work */
160 				bopt.cbStruct = sizeof(bopt);
161 				IBindCtx_GetBindOptions(pBindCtx, (BIND_OPTS*)&bopt);
162 				bopt.pServerInfo = &info;
163 				/* apparently, GetBindOptions will only ever return
164 				 * a regular BIND_OPTS structure.  My gut feeling is
165 				 * that it will modify the size field to reflect that
166 				 * so lets be safe and set it to the BIND_OPTS2 size
167 				 * again */
168 				bopt.cbStruct = sizeof(bopt);
169 				IBindCtx_SetBindOptions(pBindCtx, (BIND_OPTS*)&bopt);
170 			}
171 
172 			if (SUCCEEDED(res = MkParseDisplayName(pBindCtx, moniker, &ulEaten, &pMoniker))) {
173 				res = IMoniker_BindToObject(pMoniker, pBindCtx,
174 					NULL, &IID_IDispatch, (LPVOID*)&V_DISPATCH(&obj->v));
175 
176 				if (SUCCEEDED(res)) {
177 					V_VT(&obj->v) = VT_DISPATCH;
178 				}
179 
180 				IMoniker_Release(pMoniker);
181 			}
182 		}
183 		if (pBindCtx) {
184 			IBindCtx_Release(pBindCtx);
185 		}
186 	} else if (server_name) {
187 		MULTI_QI		qi;
188 
189 		qi.pIID = &IID_IDispatch;
190 		qi.pItf = NULL;
191 		qi.hr = S_OK;
192 
193 		res = CoCreateInstanceEx(&clsid, NULL, ctx, &info, 1, &qi);
194 
195 		if (SUCCEEDED(res)) {
196 			res = qi.hr;
197 			V_DISPATCH(&obj->v) = (IDispatch*)qi.pItf;
198 			V_VT(&obj->v) = VT_DISPATCH;
199 		}
200 	} else {
201 		res = CoCreateInstance(&clsid, NULL, CLSCTX_SERVER, &IID_IDispatch, (LPVOID*)&V_DISPATCH(&obj->v));
202 		if (SUCCEEDED(res)) {
203 			V_VT(&obj->v) = VT_DISPATCH;
204 		}
205 	}
206 
207 	if (server_name) {
208 		if (info.pwszName) efree(info.pwszName);
209 		if (server_params) zend_string_release(server_name);
210 	}
211 	if (user_name) zend_string_release(user_name);
212 	if (password) zend_string_release(password);
213 	if (domain_name) zend_string_release(domain_name);
214 
215 	efree(moniker);
216 
217 	if (FAILED(res)) {
218 		char *werr, *msg;
219 
220 		werr = php_win32_error_to_msg(res);
221 		spprintf(&msg, 0, "Failed to create COM object `%s': %s", module_name, werr);
222 		php_win32_error_msg_free(werr);
223 
224 		php_com_throw_exception(res, msg);
225 		efree(msg);
226 		RETURN_THROWS();
227 	}
228 
229 	/* we got the object and it lives ! */
230 
231 	/* see if it has TypeInfo available */
232 	if (FAILED(IDispatch_GetTypeInfo(V_DISPATCH(&obj->v), 0, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), &obj->typeinfo)) && typelib_name) {
233 		/* load up the library from the named file */
234 		TL = php_com_load_typelib_via_cache(typelib_name, obj->code_page);
235 
236 		if (TL) {
237 			if (COMG(autoreg_on)) {
238 				php_com_import_typelib(TL, mode, obj->code_page);
239 			}
240 
241 			/* cross your fingers... there is no guarantee that this ITypeInfo
242 			 * instance has any relation to this IDispatch instance... */
243 			ITypeLib_GetTypeInfo(TL, 0, &obj->typeinfo);
244 			ITypeLib_Release(TL);
245 		}
246 	} else if (obj->typeinfo && COMG(autoreg_on)) {
247 		UINT idx;
248 
249 		if (SUCCEEDED(ITypeInfo_GetContainingTypeLib(obj->typeinfo, &TL, &idx))) {
250 			/* check if the library is already in the cache by getting its name */
251 			BSTR name;
252 
253 			if (SUCCEEDED(ITypeLib_GetDocumentation(TL, -1, &name, NULL, NULL, NULL))) {
254 				zend_string *typelib_str = php_com_olestring_to_string(name, obj->code_page);
255 
256 				if (NULL != php_com_cache_typelib(TL, ZSTR_VAL(typelib_str), ZSTR_LEN(typelib_str))) {
257 					php_com_import_typelib(TL, mode, obj->code_page);
258 
259 					/* add a reference for the hash */
260 					ITypeLib_AddRef(TL);
261 				}
262 				zend_string_release_ex(typelib_str, /* persistent */ false);
263 			} else {
264 				/* try it anyway */
265 				php_com_import_typelib(TL, mode, obj->code_page);
266 			}
267 
268 			ITypeLib_Release(TL);
269 		}
270 	}
271 
272 }
273 /* }}} */
274 
275 /* {{{ Returns a handle to an already running instance of a COM object */
PHP_FUNCTION(com_get_active_object)276 PHP_FUNCTION(com_get_active_object)
277 {
278 	CLSID clsid;
279 	char *module_name;
280 	size_t module_name_len;
281 	zend_long code_page;
282 	bool code_page_is_null = 1;
283 	IUnknown *unk = NULL;
284 	IDispatch *obj = NULL;
285 	HRESULT res;
286 	OLECHAR *module = NULL;
287 
288 	php_com_initialize();
289 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|l!",
290 				&module_name, &module_name_len, &code_page, &code_page_is_null)) {
291 		RETURN_THROWS();
292 	}
293 
294 	if (code_page_is_null) {
295 		code_page = COMG(code_page);
296 	}
297 
298 	module = php_com_string_to_olestring(module_name, module_name_len, (int)code_page);
299 
300 	res = CLSIDFromString(module, &clsid);
301 
302 	if (FAILED(res)) {
303 		php_com_throw_exception(res, NULL);
304 	} else {
305 		res = GetActiveObject(&clsid, NULL, &unk);
306 
307 		if (FAILED(res)) {
308 			php_com_throw_exception(res, NULL);
309 		} else {
310 			res = IUnknown_QueryInterface(unk, &IID_IDispatch, &obj);
311 
312 			if (FAILED(res)) {
313 				php_com_throw_exception(res, NULL);
314 			} else if (obj) {
315 				/* we got our dispatchable object */
316 				php_com_wrap_dispatch(return_value, obj, (int)code_page);
317 			}
318 		}
319 	}
320 
321 	if (obj) {
322 		IDispatch_Release(obj);
323 	}
324 	if (unk) {
325 		IUnknown_Release(obj);
326 	}
327 	efree(module);
328 }
329 /* }}} */
330 
331 /* Performs an Invoke on the given com object.
332  * returns a failure code and creates an exception if there was an error */
php_com_invoke_helper(php_com_dotnet_object * obj,DISPID id_member,WORD flags,DISPPARAMS * disp_params,VARIANT * v,bool silent,bool allow_noarg)333 HRESULT php_com_invoke_helper(php_com_dotnet_object *obj, DISPID id_member,
334 		WORD flags, DISPPARAMS *disp_params, VARIANT *v, bool silent, bool allow_noarg)
335 {
336 	HRESULT hr;
337 	unsigned int arg_err;
338 	EXCEPINFO e = {0};
339 
340 	hr = IDispatch_Invoke(V_DISPATCH(&obj->v), id_member,
341 		&IID_NULL, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), flags, disp_params, v, &e, &arg_err);
342 
343 	if (!silent && FAILED(hr)) {
344 		char *desc = NULL, *msg = NULL;
345 
346 		switch (hr) {
347 			case DISP_E_EXCEPTION: {
348 				zend_string *source = NULL, *desc_str = NULL;
349 				if (e.bstrSource) {
350 					source = php_com_olestring_to_string(e.bstrSource, obj->code_page);
351 					SysFreeString(e.bstrSource);
352 				}
353 				if (e.bstrDescription) {
354 					desc_str = php_com_olestring_to_string(e.bstrDescription, obj->code_page);
355 					SysFreeString(e.bstrDescription);
356 				}
357 				if (PG(html_errors)) {
358 					spprintf(&msg, 0, "<b>Source:</b> %s<br/><b>Description:</b> %s",
359 						source ? ZSTR_VAL(source) : "Unknown",
360 						desc_str ? ZSTR_VAL(desc_str) : "Unknown");
361 				} else {
362 					spprintf(&msg, 0, "Source: %s\nDescription: %s",
363 						source ? ZSTR_VAL(source) : "Unknown",
364 						desc_str ? ZSTR_VAL(desc_str) : "Unknown");
365 				}
366 				if (desc_str) {
367 					zend_string_release_ex(desc_str, /* persistent */ false);
368 				}
369 				if (source) {
370 					zend_string_release_ex(source, /* persistent */ false);
371 				}
372 				if (e.bstrHelpFile) {
373 					SysFreeString(e.bstrHelpFile);
374 				}
375 				break;
376 			}
377 			case DISP_E_PARAMNOTFOUND:
378 			case DISP_E_TYPEMISMATCH:
379 				desc = php_win32_error_to_msg(hr);
380 				spprintf(&msg, 0, "Parameter %d: %s", arg_err, desc);
381 				php_win32_error_msg_free(desc);
382 				break;
383 
384 			case DISP_E_BADPARAMCOUNT:
385 				if ((disp_params->cArgs + disp_params->cNamedArgs == 0) && allow_noarg) {
386 					/* if getting a property and they are missing all parameters,
387 					 * we want to create a proxy object for them; so lets not create an
388 					 * exception here */
389 					msg = NULL;
390 					break;
391 				}
392 				/* else fall through */
393 
394 			default:
395 				desc = php_win32_error_to_msg(hr);
396 				spprintf(&msg, 0, "Error [0x%08x] %s", hr, desc);
397 				php_win32_error_msg_free(desc);
398 				break;
399 		}
400 
401 		if (msg) {
402 			php_com_throw_exception(hr, msg);
403 			efree(msg);
404 		}
405 	}
406 
407 	return hr;
408 }
409 
410 /* map an ID to a name */
php_com_get_id_of_name(php_com_dotnet_object * obj,zend_string * name,DISPID * dispid)411 HRESULT php_com_get_id_of_name(php_com_dotnet_object *obj, zend_string *name,
412 		DISPID *dispid)
413 {
414 	OLECHAR *olename;
415 	HRESULT hr;
416 	zval *tmp;
417 
418 	if (obj->id_of_name_cache && NULL != (tmp = zend_hash_find(obj->id_of_name_cache, name))) {
419 		*dispid = (DISPID)Z_LVAL_P(tmp);
420 		return S_OK;
421 	}
422 
423 	olename = php_com_string_to_olestring(ZSTR_VAL(name), ZSTR_LEN(name), obj->code_page);
424 
425 	if (obj->typeinfo) {
426 		hr = ITypeInfo_GetIDsOfNames(obj->typeinfo, &olename, 1, dispid);
427 		if (FAILED(hr)) {
428 			HRESULT hr1 = hr;
429 			hr = IDispatch_GetIDsOfNames(V_DISPATCH(&obj->v), &IID_NULL, &olename, 1, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), dispid);
430 			if (SUCCEEDED(hr) && hr1 != E_NOTIMPL) {
431 				/* fall back on IDispatch direct */
432 				ITypeInfo_Release(obj->typeinfo);
433 				obj->typeinfo = NULL;
434 			}
435 		}
436 	} else {
437 		hr = IDispatch_GetIDsOfNames(V_DISPATCH(&obj->v), &IID_NULL, &olename, 1, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), dispid);
438 	}
439 	efree(olename);
440 
441 	if (SUCCEEDED(hr)) {
442 		zval tmp;
443 
444 		/* cache the mapping */
445 		if (!obj->id_of_name_cache) {
446 			ALLOC_HASHTABLE(obj->id_of_name_cache);
447 			zend_hash_init(obj->id_of_name_cache, 2, NULL, NULL, 0);
448 		}
449 		ZVAL_LONG(&tmp, *dispid);
450 		zend_hash_update(obj->id_of_name_cache, name, &tmp);
451 	}
452 
453 	return hr;
454 }
455 
456 /* the core of COM */
php_com_do_invoke_byref(php_com_dotnet_object * obj,zend_internal_function * f,WORD flags,VARIANT * v,int nargs,zval * args)457 zend_result php_com_do_invoke_byref(php_com_dotnet_object *obj, zend_internal_function *f,
458 		WORD flags,	VARIANT *v, int nargs, zval *args)
459 {
460 	DISPID dispid, altdispid;
461 	DISPPARAMS disp_params;
462 	HRESULT hr;
463 	VARIANT *vargs = NULL, *byref_vals = NULL;
464 	int i, byref_count = 0, j;
465 
466 	/* assumption: that the active function (f) is the function we generated for the engine */
467 	if (!f) {
468 		return FAILURE;
469 	}
470 
471 	hr = php_com_get_id_of_name(obj, f->function_name, &dispid);
472 
473 	if (FAILED(hr)) {
474 		char *msg = NULL;
475 		char *winerr = php_win32_error_to_msg(hr);
476 		spprintf(&msg, 0, "Unable to lookup `%s': %s", f->function_name->val, winerr);
477 		php_win32_error_msg_free(winerr);
478 		php_com_throw_exception(hr, msg);
479 		efree(msg);
480 		return FAILURE;
481 	}
482 
483 
484 	if (nargs) {
485 		vargs = (VARIANT*)safe_emalloc(sizeof(VARIANT), nargs, 0);
486 	}
487 
488 	if (f->arg_info) {
489 		for (i = 0; i < nargs; i++) {
490 			if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) {
491 				byref_count++;
492 			}
493 		}
494 	}
495 
496 	if (byref_count) {
497 		byref_vals = (VARIANT*)safe_emalloc(sizeof(VARIANT), byref_count, 0);
498 		for (j = 0, i = 0; i < nargs; i++) {
499 			if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) {
500 				/* put the value into byref_vals instead */
501 				php_com_variant_from_zval(&byref_vals[j], &args[nargs - i - 1], obj->code_page);
502 
503 				/* if it is already byref, "move" it into the vargs array, otherwise
504 				 * make vargs a reference to this value */
505 				if (V_VT(&byref_vals[j]) & VT_BYREF) {
506 					memcpy(&vargs[i], &byref_vals[j], sizeof(vargs[i]));
507 					VariantInit(&byref_vals[j]); /* leave the variant slot empty to simplify cleanup */
508 				} else {
509 					VariantInit(&vargs[i]);
510 					V_VT(&vargs[i]) = V_VT(&byref_vals[j]) | VT_BYREF;
511 					/* union magic ensures that this works out */
512 					vargs[i].byref = &V_UINT(&byref_vals[j]);
513 				}
514 				j++;
515 			} else {
516 				php_com_variant_from_zval(&vargs[i], &args[nargs - i - 1], obj->code_page);
517 			}
518 		}
519 
520 	} else {
521 		/* Invoke'd args are in reverse order */
522 		for (i = 0; i < nargs; i++) {
523 			php_com_variant_from_zval(&vargs[i], &args[nargs - i - 1], obj->code_page);
524 		}
525 	}
526 
527 	disp_params.cArgs = nargs;
528 	disp_params.cNamedArgs = 0;
529 	disp_params.rgvarg = vargs;
530 	disp_params.rgdispidNamedArgs = NULL;
531 
532 	if (flags & DISPATCH_PROPERTYPUT) {
533 		altdispid = DISPID_PROPERTYPUT;
534 		disp_params.rgdispidNamedArgs = &altdispid;
535 		disp_params.cNamedArgs = 1;
536 	}
537 
538 	/* this will create an exception if needed */
539 	hr = php_com_invoke_helper(obj, dispid, flags, &disp_params, v, 0, 0);
540 
541 	/* release variants */
542 	if (vargs) {
543 		if (f && f->arg_info) {
544 			for (i = 0, j = 0; i < nargs; i++) {
545 				/* if this was byref, update the zval */
546 				if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) {
547 					zval *arg = &args[nargs - i - 1];
548 
549 					ZVAL_DEREF(arg);
550 					zval_ptr_dtor(arg);
551 					ZVAL_NULL(arg);
552 
553 					/* if the variant is pointing at the byref_vals, we need to map
554 					 * the pointee value as a zval; otherwise, the value is pointing
555 					 * into an existing PHP variant record */
556 					if (V_VT(&vargs[i]) & VT_BYREF) {
557 						if (vargs[i].byref == &V_UINT(&byref_vals[j])) {
558 							/* copy that value */
559 							php_com_zval_from_variant(arg, &byref_vals[j], obj->code_page);
560 						}
561 					} else {
562 						/* not sure if this can ever happen; the variant we marked as BYREF
563 						 * is no longer BYREF - copy its value */
564 						php_com_zval_from_variant(arg, &vargs[i], obj->code_page);
565 					}
566 					VariantClear(&byref_vals[j]);
567 					j++;
568 				}
569 				VariantClear(&vargs[i]);
570 			}
571 		} else {
572 			for (i = 0, j = 0; i < nargs; i++) {
573 				VariantClear(&vargs[i]);
574 			}
575 		}
576 		efree(vargs);
577 		if (byref_vals) efree(byref_vals);
578 	}
579 
580 	return SUCCEEDED(hr) ? SUCCESS : FAILURE;
581 }
582 
583 
584 
php_com_do_invoke_by_id(php_com_dotnet_object * obj,DISPID dispid,WORD flags,VARIANT * v,int nargs,zval * args,bool silent,bool allow_noarg)585 zend_result php_com_do_invoke_by_id(php_com_dotnet_object *obj, DISPID dispid,
586 		WORD flags,	VARIANT *v, int nargs, zval *args, bool silent, bool allow_noarg)
587 {
588 	DISPID altdispid;
589 	DISPPARAMS disp_params;
590 	HRESULT hr;
591 	VARIANT *vargs = NULL;
592 	int i;
593 
594 	if (nargs) {
595 		vargs = (VARIANT*)safe_emalloc(sizeof(VARIANT), nargs, 0);
596 	}
597 
598 	/* Invoke'd args are in reverse order */
599 	for (i = 0; i < nargs; i++) {
600 		php_com_variant_from_zval(&vargs[i], &args[nargs - i - 1], obj->code_page);
601 	}
602 
603 	disp_params.cArgs = nargs;
604 	disp_params.cNamedArgs = 0;
605 	disp_params.rgvarg = vargs;
606 	disp_params.rgdispidNamedArgs = NULL;
607 
608 	if (flags & DISPATCH_PROPERTYPUT) {
609 		altdispid = DISPID_PROPERTYPUT;
610 		disp_params.rgdispidNamedArgs = &altdispid;
611 		disp_params.cNamedArgs = 1;
612 	}
613 
614 	/* this will create an exception if needed */
615 	hr = php_com_invoke_helper(obj, dispid, flags, &disp_params, v, silent, allow_noarg);
616 
617 	/* release variants */
618 	if (vargs) {
619 		for (i = 0; i < nargs; i++) {
620 			VariantClear(&vargs[i]);
621 		}
622 		efree(vargs);
623 	}
624 
625 	/* a bit of a hack this, but it's needed for COM array access. */
626 	if (hr == DISP_E_BADPARAMCOUNT)
627 		return hr;
628 
629 	return SUCCEEDED(hr) ? SUCCESS : FAILURE;
630 }
631 
php_com_do_invoke(php_com_dotnet_object * obj,zend_string * name,WORD flags,VARIANT * v,int nargs,zval * args,bool allow_noarg)632 zend_result php_com_do_invoke(php_com_dotnet_object *obj, zend_string *name,
633 		WORD flags,	VARIANT *v, int nargs, zval *args, bool allow_noarg)
634 {
635 	DISPID dispid;
636 	HRESULT hr;
637 	char *msg = NULL;
638 
639 	hr = php_com_get_id_of_name(obj, name, &dispid);
640 
641 	if (FAILED(hr)) {
642 		char *winerr = php_win32_error_to_msg(hr);
643 		spprintf(&msg, 0, "Unable to lookup `%s': %s", name, winerr);
644 		php_win32_error_msg_free(winerr);
645 		php_com_throw_exception(hr, msg);
646 		efree(msg);
647 		return FAILURE;
648 	}
649 
650 	return php_com_do_invoke_by_id(obj, dispid, flags, v, nargs, args, 0, allow_noarg);
651 }
652 
653 /* {{{ Generate a globally unique identifier (GUID) */
PHP_FUNCTION(com_create_guid)654 PHP_FUNCTION(com_create_guid)
655 {
656 	GUID retval;
657 	OLECHAR *guid_string;
658 
659 	if (zend_parse_parameters_none() == FAILURE) {
660 		RETURN_THROWS();
661 	}
662 
663 	php_com_initialize();
664 	if (CoCreateGuid(&retval) == S_OK && StringFromCLSID(&retval, &guid_string) == S_OK) {
665 		RETVAL_STR(php_com_olestring_to_string(guid_string, CP_ACP));
666 
667 		CoTaskMemFree(guid_string);
668 	} else {
669 		RETURN_FALSE;
670 	}
671 }
672 /* }}} */
673 
674 /* {{{ Connect events from a COM object to a PHP object */
PHP_FUNCTION(com_event_sink)675 PHP_FUNCTION(com_event_sink)
676 {
677 	zval *object, *sinkobject;
678 	zend_string *sink_str = NULL;
679 	HashTable *sink_ht = NULL;
680 	zend_string *type_lib_name = NULL;
681 	zend_string *dispatch_name = NULL;
682 	php_com_dotnet_object *obj;
683 	ITypeInfo *typeinfo = NULL;
684 
685 	ZEND_PARSE_PARAMETERS_START(2, 3)
686 		Z_PARAM_OBJECT_OF_CLASS(object, php_com_variant_class_entry)
687 		Z_PARAM_OBJECT(sinkobject)
688 		Z_PARAM_OPTIONAL
689 		Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(sink_ht, sink_str)
690 	ZEND_PARSE_PARAMETERS_END();
691 
692 	RETVAL_FALSE;
693 
694 	php_com_initialize();
695 	obj = CDNO_FETCH(object);
696 
697 	if (sink_ht) {
698 		/* 0 => typelibname, 1 => dispname */
699 		zval *tmp;
700 
701 		if ((tmp = zend_hash_index_find(sink_ht, 0)) != NULL && Z_TYPE_P(tmp) == IS_STRING)
702 			type_lib_name = Z_STR_P(tmp);
703 		if ((tmp = zend_hash_index_find(sink_ht, 1)) != NULL && Z_TYPE_P(tmp) == IS_STRING)
704 			dispatch_name = Z_STR_P(tmp);
705 	} else if (sink_str) {
706 		dispatch_name = sink_str;
707 	}
708 
709 	typeinfo = php_com_locate_typeinfo(type_lib_name, obj, dispatch_name, /* sink */ true);
710 
711 	if (typeinfo) {
712 		HashTable *id_to_name;
713 
714 		ALLOC_HASHTABLE(id_to_name);
715 
716 		if (php_com_process_typeinfo(typeinfo, id_to_name, 0, &obj->sink_id, obj->code_page)) {
717 
718 			/* Create the COM wrapper for this sink */
719 			obj->sink_dispatch = php_com_wrapper_export_as_sink(sinkobject, &obj->sink_id, id_to_name);
720 
721 			/* Now hook it up to the source */
722 			php_com_object_enable_event_sink(obj, /* enable */ true);
723 			RETVAL_TRUE;
724 
725 		} else {
726 			FREE_HASHTABLE(id_to_name);
727 		}
728 	}
729 
730 	if (typeinfo) {
731 		ITypeInfo_Release(typeinfo);
732 	}
733 
734 }
735 /* }}} */
736 
737 /* {{{ Print out a PHP class definition for a dispatchable interface */
PHP_FUNCTION(com_print_typeinfo)738 PHP_FUNCTION(com_print_typeinfo)
739 {
740 	zend_object *object_zpp;
741 	zend_string *type_lib_name = NULL;
742 	zend_string *interface_name = NULL;
743 	bool want_sink = false;
744 	php_com_dotnet_object *obj = NULL;
745 	ITypeInfo *typeinfo;
746 
747 	ZEND_PARSE_PARAMETERS_START(1, 3)
748 		Z_PARAM_OBJ_OF_CLASS_OR_STR(object_zpp, php_com_variant_class_entry, type_lib_name)
749 		Z_PARAM_OPTIONAL
750 		Z_PARAM_STR_OR_NULL(interface_name)
751 		Z_PARAM_BOOL(want_sink)
752 	ZEND_PARSE_PARAMETERS_END();
753 
754 	php_com_initialize();
755 	if (object_zpp) {
756 		obj = (php_com_dotnet_object*)object_zpp;
757 	}
758 
759 	typeinfo = php_com_locate_typeinfo(type_lib_name, obj, interface_name, want_sink);
760 	if (typeinfo) {
761 		php_com_process_typeinfo(typeinfo, NULL, 1, NULL, obj ? obj->code_page : COMG(code_page));
762 		ITypeInfo_Release(typeinfo);
763 		RETURN_TRUE;
764 	}
765 
766 	php_error_docref(NULL, E_WARNING, "Unable to find typeinfo using the parameters supplied");
767 	RETURN_FALSE;
768 }
769 /* }}} */
770 
771 /* {{{ Process COM messages, sleeping for up to timeoutms milliseconds */
PHP_FUNCTION(com_message_pump)772 PHP_FUNCTION(com_message_pump)
773 {
774 	zend_long timeoutms = 0;
775 	MSG msg;
776 	DWORD result;
777 
778 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &timeoutms) == FAILURE)
779 		RETURN_THROWS();
780 
781 	php_com_initialize();
782 	result = MsgWaitForMultipleObjects(0, NULL, FALSE, (DWORD)timeoutms, QS_ALLINPUT);
783 
784 	if (result == WAIT_OBJECT_0) {
785 		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
786 			TranslateMessage(&msg);
787 			DispatchMessage(&msg);
788 		}
789 		/* we processed messages */
790 		RETVAL_TRUE;
791 	} else {
792 		/* we did not process messages (timed out) */
793 		RETVAL_FALSE;
794 	}
795 }
796 /* }}} */
797 
798 /* {{{ Loads a Typelibrary and registers its constants */
PHP_FUNCTION(com_load_typelib)799 PHP_FUNCTION(com_load_typelib)
800 {
801 	char *name;
802 	size_t namelen;
803 	ITypeLib *pTL = NULL;
804 	bool cs = TRUE;
805 	int codepage = COMG(code_page);
806 
807 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|b", &name, &namelen, &cs)) {
808 		RETURN_THROWS();
809 	}
810 
811 	if (!cs) {
812 		php_error_docref(NULL, E_WARNING, "com_load_typelib(): Argument #2 ($case_insensitive) is ignored since declaration of case-insensitive constants is no longer supported");
813 	}
814 
815 	RETVAL_FALSE;
816 
817 	php_com_initialize();
818 	pTL = php_com_load_typelib_via_cache(name, codepage);
819 	if (pTL) {
820 		if (php_com_import_typelib(pTL, cs ? CONST_CS : 0, codepage) == SUCCESS) {
821 			RETVAL_TRUE;
822 		}
823 
824 		ITypeLib_Release(pTL);
825 		pTL = NULL;
826 	}
827 }
828 /* }}} */
829