shell32: Fix parsing empty arguments in parse_dde_command().
[wine.git] / dlls / shell32 / dde.c
blob976dc2e394a261bea5e4efe6df8ecee415f8f7f1
1 /*
2 * Shell DDE Handling
4 * Copyright 2004 Robert Shearman
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #include <stdarg.h>
23 #define COBJMACROS
24 #include "windef.h"
25 #include "winbase.h"
26 #include "winuser.h"
27 #include "ddeml.h"
28 #include "shellapi.h"
29 #include "shobjidl.h"
30 #include "shlwapi.h"
32 #include "shell32_main.h"
34 #include "wine/debug.h"
36 WINE_DEFAULT_DEBUG_CHANNEL(shell);
38 /* String handles */
39 static HSZ hszProgmanTopic;
40 static HSZ hszProgmanService;
41 static HSZ hszAsterisk;
42 static HSZ hszShell;
43 static HSZ hszAppProperties;
44 static HSZ hszFolders;
45 static HSZ hszGroups;
46 /* DDE Instance ID */
47 static DWORD dwDDEInst;
49 static const char *debugstr_hsz( HSZ hsz )
51 WCHAR buffer[256];
52 if (!DdeQueryStringW( dwDDEInst, hsz, buffer, ARRAY_SIZE(buffer), CP_WINUNICODE ))
53 return "<unknown>";
54 return debugstr_w( buffer );
57 static WCHAR *strndupW(const WCHAR *src, DWORD len)
59 WCHAR *dest;
60 if (!src) return NULL;
61 dest = malloc((len + 1) * sizeof(*dest));
62 if (dest)
64 memcpy(dest, src, len * sizeof(WCHAR));
65 dest[len] = '\0';
67 return dest;
70 static inline BOOL Dde_OnConnect(HSZ hszTopic, HSZ hszService)
72 if ((hszTopic == hszProgmanTopic) && (hszService == hszProgmanService))
73 return TRUE;
74 if ((hszTopic == hszProgmanTopic) && (hszService == hszAppProperties))
75 return TRUE;
76 if ((hszTopic == hszShell) && (hszService == hszFolders))
77 return TRUE;
78 if ((hszTopic == hszShell) && (hszService == hszAppProperties))
79 return TRUE;
80 return FALSE;
83 static inline void Dde_OnConnectConfirm(HCONV hconv, HSZ hszTopic, HSZ hszService)
85 TRACE( "%p %s %s\n", hconv, debugstr_hsz(hszTopic), debugstr_hsz(hszService) );
88 static inline BOOL Dde_OnWildConnect(HSZ hszTopic, HSZ hszService)
90 FIXME("stub\n");
91 return FALSE;
94 static WCHAR *combine_path(const WCHAR *directory, const WCHAR *name, const WCHAR *extension, BOOL sanitize)
96 WCHAR *path;
97 int len, i;
99 len = wcslen(directory) + 1 + wcslen(name);
100 if (extension) len += wcslen(extension);
101 path = malloc((len + 1) * sizeof(WCHAR));
103 if (sanitize)
105 WCHAR *sanitized_name = wcsdup(name);
107 for (i = 0; i < wcslen(name); i++)
109 if (name[i] < ' ' || wcschr(L"*/:<>?\\|", name[i]))
110 sanitized_name[i] = '_';
113 PathCombineW(path, directory, sanitized_name);
114 free(sanitized_name);
116 else
118 PathCombineW(path, directory, name);
121 if (extension)
122 wcscat(path, extension);
124 return path;
127 /* Returned string must be freed by caller */
128 static WCHAR *get_programs_path(const WCHAR *name, BOOL sanitize)
130 WCHAR *programs, *path;
132 SHGetKnownFolderPath(&FOLDERID_Programs, 0, NULL, &programs);
133 path = combine_path(programs, name, NULL, sanitize);
134 CoTaskMemFree(programs);
136 return path;
139 static inline HDDEDATA Dde_OnRequest(UINT uFmt, HCONV hconv, HSZ hszTopic,
140 HSZ hszItem)
142 if (hszTopic == hszProgmanTopic && hszItem == hszGroups && uFmt == CF_TEXT)
144 WCHAR *programs;
145 WIN32_FIND_DATAW finddata;
146 HANDLE hfind;
147 int len = 1;
148 WCHAR *groups_data = malloc(sizeof(WCHAR)), *new_groups_data;
149 char *groups_dataA;
150 HDDEDATA ret = NULL;
152 groups_data[0] = 0;
153 programs = get_programs_path(L"*", FALSE);
154 hfind = FindFirstFileW(programs, &finddata);
155 if (hfind)
159 if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
160 wcscmp(finddata.cFileName, L".") && wcscmp(finddata.cFileName, L".."))
162 len += lstrlenW(finddata.cFileName) + 2;
163 new_groups_data = realloc(groups_data, len * sizeof(WCHAR));
164 if (!new_groups_data)
166 free(groups_data);
167 free(programs);
168 return NULL;
170 groups_data = new_groups_data;
171 lstrcatW(groups_data, finddata.cFileName);
172 lstrcatW(groups_data, L"\r\n");
174 } while (FindNextFileW(hfind, &finddata));
175 FindClose(hfind);
178 len = WideCharToMultiByte(CP_ACP, 0, groups_data, -1, NULL, 0, NULL, NULL);
179 groups_dataA = malloc(len);
180 if (groups_dataA)
182 WideCharToMultiByte(CP_ACP, 0, groups_data, -1, groups_dataA, len, NULL, NULL);
183 ret = DdeCreateDataHandle(dwDDEInst, (BYTE *)groups_dataA, len, 0, hszGroups, uFmt, 0);
186 free(groups_dataA);
187 free(groups_data);
188 free(programs);
189 return ret;
191 else if (hszTopic == hszProgmanTopic && hszItem == hszProgmanService && uFmt == CF_TEXT)
193 static BYTE groups_data[] = "\r\n";
194 FIXME( "returning empty groups list\n" );
195 /* This is a workaround for an application which expects some data
196 * and cannot handle NULL. */
197 return DdeCreateDataHandle( dwDDEInst, groups_data, sizeof(groups_data), 0, hszProgmanService, uFmt, 0 );
199 FIXME( "%u %p %s %s: stub\n", uFmt, hconv, debugstr_hsz(hszTopic), debugstr_hsz(hszItem) );
200 return NULL;
203 static DWORD PROGMAN_OnExecute(WCHAR *command, int argc, WCHAR **argv)
205 static WCHAR *last_group;
206 DWORD len;
208 if (!wcsicmp(command, L"CreateGroup"))
210 WCHAR *path;
212 if (argc < 1) return DDE_FNOTPROCESSED;
214 path = get_programs_path(argv[0], TRUE);
216 CreateDirectoryW(path, NULL);
217 ShellExecuteW(NULL, NULL, path, NULL, NULL, SW_SHOWNORMAL);
219 free(last_group);
220 last_group = path;
222 else if (!wcsicmp(command, L"DeleteGroup"))
224 WCHAR *path, *path2;
225 SHFILEOPSTRUCTW shfos = {0};
226 int ret;
228 if (argc < 1) return DDE_FNOTPROCESSED;
230 path = get_programs_path(argv[0], TRUE);
232 path2 = malloc((lstrlenW(path) + 2) * sizeof(*path));
233 lstrcpyW(path2, path);
234 path2[lstrlenW(path) + 1] = 0;
236 shfos.wFunc = FO_DELETE;
237 shfos.pFrom = path2;
238 shfos.fFlags = FOF_NOCONFIRMATION;
240 ret = SHFileOperationW(&shfos);
242 free(path2);
243 free(path);
245 if (ret || shfos.fAnyOperationsAborted) return DDE_FNOTPROCESSED;
247 else if (!wcsicmp(command, L"ShowGroup"))
249 WCHAR *path;
251 /* Win32 requires the second parameter to be present but seems to
252 * ignore its actual value. */
253 if (argc < 2) return DDE_FNOTPROCESSED;
255 path = get_programs_path(argv[0], TRUE);
257 ShellExecuteW(NULL, NULL, path, NULL, NULL, SW_SHOWNORMAL);
259 free(last_group);
260 last_group = path;
262 else if (!wcsicmp(command, L"AddItem"))
264 WCHAR *target, *space = NULL, *path, *name;
265 IShellLinkW *link;
266 IPersistFile *file;
267 HRESULT hres;
269 if (argc < 1) return DDE_FNOTPROCESSED;
271 hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
272 &IID_IShellLinkW, (void **)&link);
273 if (FAILED(hres)) return DDE_FNOTPROCESSED;
275 target = wcsdup(argv[0]);
276 while (!(len = SearchPathW(NULL, target, L".exe", 0, NULL, NULL)))
278 /* progressively remove words from the end of the command line until we get a valid file name */
279 space = wcsrchr(target, ' ');
280 if (!space)
282 IShellLinkW_Release(link);
283 free(target);
284 return DDE_FNOTPROCESSED;
286 *space = 0;
288 path = malloc(len * sizeof(WCHAR));
289 SearchPathW(NULL, target, L".exe", len, path, NULL);
290 IShellLinkW_SetPath(link, path);
291 if (space) IShellLinkW_SetArguments(link, argv[0] + (space - target) + 1);
292 free(target);
293 free(path);
295 if (argc >= 2) IShellLinkW_SetDescription(link, argv[1]);
296 if (argc >= 4) IShellLinkW_SetIconLocation(link, argv[2], wcstol(argv[3], NULL, 10));
297 if (argc >= 7) IShellLinkW_SetWorkingDirectory(link, argv[6]);
298 if (argc >= 8) IShellLinkW_SetHotkey(link, wcstol(argv[7], NULL, 10));
299 if (argc >= 9)
301 if (wcstol(argv[8], NULL, 10) == 0) IShellLinkW_SetShowCmd(link, SW_SHOWMINNOACTIVE);
302 else if (wcstol(argv[8], NULL, 10) == 1) IShellLinkW_SetShowCmd(link, SW_SHOWNORMAL);
305 hres = IShellLinkW_QueryInterface(link, &IID_IPersistFile, (void **)&file);
306 if (FAILED(hres))
308 IShellLinkW_Release(link);
309 return DDE_FNOTPROCESSED;
311 if (argc >= 2)
313 name = combine_path(last_group, argv[1], L".lnk", TRUE);
315 else
317 WCHAR *filename = wcsdup(PathFindFileNameW(argv[0]));
318 WCHAR *ext = PathFindExtensionW(filename);
319 *ext = '\0';
320 name = combine_path(last_group, filename, L".lnk", TRUE);
321 free(filename);
323 hres = IPersistFile_Save(file, name, TRUE);
325 free(name);
326 IPersistFile_Release(file);
327 IShellLinkW_Release(link);
329 if (FAILED(hres)) return DDE_FNOTPROCESSED;
331 else if (!wcsicmp(command, L"DeleteItem") || !wcsicmp(command, L"ReplaceItem"))
333 WCHAR *name;
334 BOOL ret;
336 if (argc < 1) return DDE_FNOTPROCESSED;
338 name = combine_path(last_group, argv[0], L".lnk", FALSE);
339 ret = DeleteFileW(name);
340 free(name);
342 if (!ret) return DDE_FNOTPROCESSED;
344 else if (!wcsicmp(command, L"ExitProgman"))
346 /* do nothing */
348 else
350 FIXME("unhandled command %s\n", debugstr_w(command));
351 return DDE_FNOTPROCESSED;
353 return DDE_FACK;
356 static DWORD parse_dde_command(HSZ hszTopic, WCHAR *command)
358 WCHAR *original = command;
359 WCHAR *opcode = NULL, **argv = NULL, **new_argv, *p;
360 int argc = 0, i;
361 DWORD ret = DDE_FACK;
363 while (*command == ' ') command++;
365 if (*command != '[') goto error;
366 while (*command == '[')
368 argc = 0;
369 argv = NULL;
371 command++;
372 while (*command == ' ') command++;
373 if (!(p = wcspbrk(command, L" ,()[]\""))) goto error;
375 opcode = strndupW(command, p - command);
377 command = p;
378 while (*command == ' ') command++;
379 if (*command == '(')
381 command++;
383 while (*command != ')')
385 while (*command == ' ') command++;
386 if (*command == '"')
388 command++;
389 if (!(p = wcschr(command, '"'))) goto error;
391 else
393 if (!(p = wcspbrk(command, L",()[]"))) goto error;
394 while (p > command && p[-1] == ' ') p--;
397 new_argv = realloc(argv, (argc + 1) * sizeof(*argv));
398 if (!new_argv) goto error;
399 argv = new_argv;
400 argv[argc] = strndupW(command, p - command);
401 argc++;
403 command = p;
404 if (*command == '"') command++;
405 while (*command == ' ') command++;
406 if (*command == ',') command++;
407 else if (*command != ')') goto error;
409 command++;
411 while (*command == ' ') command++;
414 if (*command != ']') goto error;
415 command++;
416 while (*command == ' ') command++;
418 if (hszTopic == hszProgmanTopic)
419 ret = PROGMAN_OnExecute(opcode, argc, argv);
420 else
422 FIXME("unhandled topic %s, command %s\n", debugstr_hsz(hszTopic), debugstr_w(opcode));
423 ret = DDE_FNOTPROCESSED;
426 free(opcode);
427 opcode = NULL;
428 for (i = 0; i < argc; i++) free(argv[i]);
429 free(argv);
431 if (ret == DDE_FNOTPROCESSED) break;
434 return ret;
436 error:
437 ERR("failed to parse command %s\n", debugstr_w(original));
438 free(opcode);
439 for (i = 0; i < argc; i++) free(argv[i]);
440 free(argv);
441 return DDE_FNOTPROCESSED;
444 static DWORD Dde_OnExecute(HCONV hconv, HSZ hszTopic, HDDEDATA hdata)
446 WCHAR *command;
447 DWORD len;
448 DWORD ret;
450 len = DdeGetData(hdata, NULL, 0, 0);
451 if (!len) return DDE_FNOTPROCESSED;
452 command = malloc(len);
453 DdeGetData(hdata, (BYTE *)command, len, 0);
455 TRACE("conv=%p topic=%s data=%s\n", hconv, debugstr_hsz(hszTopic), debugstr_w(command));
457 ret = parse_dde_command(hszTopic, command);
459 free(command);
460 return ret;
463 static inline void Dde_OnDisconnect(HCONV hconv)
465 TRACE( "%p\n", hconv );
468 static HDDEDATA CALLBACK DdeCallback(
469 UINT uType,
470 UINT uFmt,
471 HCONV hconv,
472 HSZ hsz1,
473 HSZ hsz2,
474 HDDEDATA hdata,
475 ULONG_PTR dwData1,
476 ULONG_PTR dwData2)
478 switch (uType)
480 case XTYP_CONNECT:
481 return (HDDEDATA)(DWORD_PTR)Dde_OnConnect(hsz1, hsz2);
482 case XTYP_CONNECT_CONFIRM:
483 Dde_OnConnectConfirm(hconv, hsz1, hsz2);
484 return NULL;
485 case XTYP_WILDCONNECT:
486 return (HDDEDATA)(DWORD_PTR)Dde_OnWildConnect(hsz1, hsz2);
487 case XTYP_REQUEST:
488 return Dde_OnRequest(uFmt, hconv, hsz1, hsz2);
489 case XTYP_EXECUTE:
490 return (HDDEDATA)(DWORD_PTR)Dde_OnExecute(hconv, hsz1, hdata);
491 case XTYP_DISCONNECT:
492 Dde_OnDisconnect(hconv);
493 return NULL;
494 default:
495 return NULL;
499 /*************************************************************************
500 * ShellDDEInit (SHELL32.@)
502 * Registers the Shell DDE services with the system so that applications
503 * can use them.
505 * PARAMS
506 * bInit [I] TRUE to initialize the services, FALSE to uninitialize.
508 * RETURNS
509 * Nothing.
511 void WINAPI ShellDDEInit(BOOL bInit)
513 TRACE("bInit = %s\n", bInit ? "TRUE" : "FALSE");
515 if (bInit)
517 DdeInitializeW(&dwDDEInst, DdeCallback, CBF_FAIL_ADVISES | CBF_FAIL_POKES, 0);
519 hszProgmanTopic = DdeCreateStringHandleW(dwDDEInst, L"Progman", CP_WINUNICODE);
520 hszProgmanService = DdeCreateStringHandleW(dwDDEInst, L"Progman", CP_WINUNICODE);
521 hszAsterisk = DdeCreateStringHandleW(dwDDEInst, L"*", CP_WINUNICODE);
522 hszShell = DdeCreateStringHandleW(dwDDEInst, L"Shell", CP_WINUNICODE);
523 hszAppProperties = DdeCreateStringHandleW(dwDDEInst, L"AppProperties", CP_WINUNICODE);
524 hszFolders = DdeCreateStringHandleW(dwDDEInst, L"Folders", CP_WINUNICODE);
525 hszGroups = DdeCreateStringHandleW(dwDDEInst, L"Groups", CP_WINUNICODE);
527 DdeNameService(dwDDEInst, hszFolders, 0, DNS_REGISTER);
528 DdeNameService(dwDDEInst, hszProgmanService, 0, DNS_REGISTER);
529 DdeNameService(dwDDEInst, hszShell, 0, DNS_REGISTER);
531 else
533 /* unregister all services */
534 DdeNameService(dwDDEInst, 0, 0, DNS_UNREGISTER);
536 DdeFreeStringHandle(dwDDEInst, hszFolders);
537 DdeFreeStringHandle(dwDDEInst, hszAppProperties);
538 DdeFreeStringHandle(dwDDEInst, hszShell);
539 DdeFreeStringHandle(dwDDEInst, hszAsterisk);
540 DdeFreeStringHandle(dwDDEInst, hszProgmanService);
541 DdeFreeStringHandle(dwDDEInst, hszProgmanTopic);
543 DdeUninitialize(dwDDEInst);