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