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