[SHELLFIND] Add missing dependency to CMakeLists.txt
[reactos.git] / dll / win32 / browseui / parsecmdline.cpp
1 /*
2 * ReactOS browseui
3 *
4 * Copyright 2014 David Quintana <gigaherz@gmail.com>
5 *
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.
10 *
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.
15 *
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include "precomp.h"
22 #include <strsafe.h>
23
24 extern "C"
25 BOOL WINAPI GUIDFromStringW(
26 _In_ PCWSTR psz,
27 _Out_ LPGUID pguid
28 );
29
30 static BOOL _CopyAndUnquoteText(LPCWSTR strFieldSource, LPWSTR strField, size_t cchField)
31 {
32 WCHAR cChar;
33 PWSTR tmpField = strField;
34 size_t lenField = 1;
35 BOOL inQuote = FALSE;
36
37 // Remove leading whitespace
38 cChar = *strFieldSource;
39 while (cChar == L' ' || cChar == L'\t' || cChar == L'\n' || cChar == L'\r')
40 {
41 strFieldSource = CharNextW(strFieldSource);
42 cChar = *strFieldSource;
43 }
44
45 while (cChar && cChar != L'=' && cChar != L',')
46 {
47 if (cChar == L'"')
48 {
49 // [1] is always valid read because of null-termination
50 if (inQuote && strFieldSource[1] == L'"')
51 {
52 if (lenField < cchField)
53 {
54 // Append
55 *(tmpField++) = L'"';
56 ++lenField;
57 }
58
59 // Skip second quote
60 strFieldSource++;
61 }
62 else
63 {
64 inQuote = !inQuote;
65 }
66 }
67 else
68 {
69 if (inQuote || (cChar != L'=' && cChar != L','))
70 {
71 if (lenField < cchField)
72 {
73 // Append
74 *(tmpField++) = cChar;
75 ++lenField;
76 }
77 }
78 }
79
80 strFieldSource = CharNextW(strFieldSource);
81 cChar = *strFieldSource;
82 }
83
84 // Remove trailing whitespace
85 while (tmpField > strField)
86 {
87 tmpField = CharPrevW(strField, tmpField);
88 cChar = *tmpField;
89 if (cChar != L' ' && cChar != L'\t' && cChar != L'\n' && cChar != L'\r')
90 {
91 tmpField = CharNextW(tmpField);
92 break;
93 }
94 }
95
96 // Terminate output string
97 *tmpField = 0;
98
99 return TRUE;
100 }
101
102 static BOOL _FindNextArg(PCWSTR * pstrFieldSource)
103 {
104 PCWSTR strFieldSource = *pstrFieldSource;
105 WCHAR cChar = *strFieldSource;
106 BOOL inQuote = FALSE;
107
108 while (cChar)
109 {
110 if (!inQuote && (cChar == L'=' || cChar == L','))
111 break;
112
113 if (cChar == L'"')
114 inQuote = !inQuote;
115
116 strFieldSource = CharNextW(strFieldSource);
117 cChar = *strFieldSource;
118 }
119
120 if (cChar == 0)
121 {
122 *pstrFieldSource = strFieldSource;
123 return FALSE;
124 }
125
126 *pstrFieldSource = CharNextW(strFieldSource);
127 return TRUE;
128 }
129
130 static PCWSTR _FindFirstField(PCWSTR strFieldSource)
131 {
132 //Find end of first arg, because
133 // behaviour is different if the first separator is an '='
134 BOOL inQuote = FALSE;
135 PCWSTR tmpArgs = strFieldSource;
136 WCHAR cChar = *tmpArgs;
137 while (cChar)
138 {
139 if (cChar == L'=')
140 break;
141
142 if (cChar == L',')
143 break;
144
145 if (cChar == L'\"')
146 inQuote = !inQuote;
147
148 tmpArgs = CharNextW(tmpArgs);
149 cChar = *tmpArgs;
150 }
151
152 // Skip the text before the first equal sign, if not quoted, unless the arg 0 was requested.
153 if (*tmpArgs == L'=' && !inQuote)
154 {
155 strFieldSource = ++tmpArgs;
156 TRACE("Skipped content before the first '=', remainder=%S\n", strFieldSource);
157 }
158
159 return strFieldSource;
160 }
161
162 static BOOL _ReadNextArg(PCWSTR * pstrFieldSource, PWSTR strField, size_t cchField)
163 {
164 // Copy and unquote text
165 _CopyAndUnquoteText(*pstrFieldSource, strField, cchField);
166
167 return _FindNextArg(pstrFieldSource);
168 }
169
170 static LPITEMIDLIST _ILReadFromSharedMemory(PCWSTR strField)
171 {
172 LPITEMIDLIST ret = NULL;
173
174 // Ensure it really is an IDLIST-formatted parameter
175 // Format for IDLIST params: ":pid:shared"
176 if (*strField != L':')
177 return NULL;
178
179 HANDLE hData = IntToPtr(StrToIntW(strField + 1));
180 PWSTR strSecond = StrChrW(strField + 1, L':');
181
182 if (strSecond)
183 {
184 int pid = StrToIntW(strSecond + 1);
185 void* pvShared = SHLockShared(hData, pid);
186 if (pvShared)
187 {
188 ret = ILClone((LPCITEMIDLIST) pvShared);
189 SHUnlockShared(pvShared);
190 SHFreeShared(hData, pid);
191 }
192 }
193 return ret;
194 }
195
196 static HRESULT _ParsePathToPidl(PWSTR strPath, LPITEMIDLIST * pidl)
197 {
198 CComPtr<IShellFolder> psfDesktop;
199
200 HRESULT hr = SHGetDesktopFolder(&psfDesktop);
201 if (FAILED(hr))
202 return hr;
203
204 return psfDesktop->ParseDisplayName(NULL, NULL, strPath, NULL, pidl, NULL);
205 }
206
207 static LPITEMIDLIST _GetDocumentsPidl()
208 {
209 CComPtr<IShellFolder> ppshf;
210 LPITEMIDLIST pidl;
211 WCHAR guid [] = L"::{450d8fba-ad25-11d0-98a8-0800361b1103}";
212
213 if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_MYDOCUMENTS, &pidl)))
214 return pidl;
215
216 if (FAILED(SHGetDesktopFolder(&ppshf)))
217 return NULL;
218
219 if (FAILED(ppshf->ParseDisplayName(NULL, NULL, guid, NULL, &pidl, NULL)))
220 return NULL;
221
222 return pidl;
223 }
224
225 /*************************************************************************
226 * SHExplorerParseCmdLine [BROWSEUI.107]
227 */
228 extern "C"
229 UINT_PTR
230 WINAPI
231 SHExplorerParseCmdLine(ExplorerCommandLineParseResults * pInfo)
232 {
233 WCHAR strField[MAX_PATH];
234 WCHAR strDir[MAX_PATH];
235
236 PCWSTR strCmdLine = GetCommandLineW();
237 PCWSTR strFieldArray = PathGetArgsW(strCmdLine);
238
239 if (!*strFieldArray)
240 {
241 pInfo->dwFlags = 9;
242 pInfo->pidlPath = _GetDocumentsPidl();
243 if (!pInfo->pidlPath)
244 {
245 GetWindowsDirectoryW(strDir, MAX_PATH);
246 PathStripToRootW(strDir);
247 pInfo->pidlPath = ILCreateFromPathW(strDir);
248 }
249 return (LONG_PTR)(pInfo->pidlPath);
250 }
251
252 PCWSTR strNextArg = _FindFirstField(strFieldArray);
253
254 BOOL hasNext = TRUE;
255
256 hasNext = _ReadNextArg(&strNextArg, strField, _countof(strField));
257 while (TRUE)
258 {
259 // Basic flags-only params first
260 if (!StrCmpIW(strField, L"/N"))
261 {
262 pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_N | SH_EXPLORER_CMDLINE_FLAG_ONE;
263 TRACE("CmdLine Parser: Parsed %S flag. dwFlags=%08lx\n", strField, pInfo->dwFlags);
264 }
265 else if (!StrCmpIW(strField, L"/S"))
266 {
267 pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_S;
268 TRACE("CmdLine Parser: Parsed %S flag. dwFlags=%08lx\n", strField, pInfo->dwFlags);
269 }
270 else if (!StrCmpIW(strField, L"/E"))
271 {
272 pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_E;
273 TRACE("CmdLine Parser: Parsed %S flag. dwFlags=%08lx\n", strField, pInfo->dwFlags);
274 }
275 else if (!StrCmpIW(strField, L"/SELECT"))
276 {
277 pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_SELECT;
278 TRACE("CmdLine Parser: Parsed %S flag. dwFlags=%08lx\n", strField, pInfo->dwFlags);
279 }
280 else if (!StrCmpIW(strField, L"/NOUI"))
281 {
282 pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_NOUI;
283 TRACE("CmdLine Parser: Parsed %S flag. dwFlags=%08lx\n", strField, pInfo->dwFlags);
284 }
285 else if (!StrCmpIW(strField, L"-embedding"))
286 {
287 pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_EMBED;
288 TRACE("CmdLine Parser: Parsed %S flag. dwFlags=%08lx\n", strField, pInfo->dwFlags);
289 }
290 else if (!StrCmpIW(strField, L"/SEPARATE"))
291 {
292 pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_SEPARATE;
293 TRACE("CmdLine Parser: Parsed %S flag. dwFlags=%08lx\n", strField, pInfo->dwFlags);
294 }
295 else if (!StrCmpIW(strField, L"/INPROC"))
296 {
297 // No idea what Inproc is supposed to do, but it gets a GUID, and parses it.
298
299 TRACE("CmdLine Parser: Found %S flag\n", strField);
300
301 if (!hasNext)
302 return FALSE;
303
304 hasNext = _ReadNextArg(&strNextArg, strField, _countof(strField));
305
306 if (!GUIDFromStringW(strField, &(pInfo->guidInproc)))
307 return FALSE;
308
309 pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_INPROC;
310
311 TRACE("CmdLine Parser: Parsed /INPROC flag. dwFlags=%08lx, guidInproc=%S\n", pInfo->dwFlags, strField);
312 }
313 else if (!StrCmpIW(strField, L"/ROOT"))
314 {
315 LPITEMIDLIST pidlRoot = NULL;
316
317 // The window should be rooted
318
319 TRACE("CmdLine Parser: Found %S flag\n", strField);
320
321 if (!pInfo->pidlPath)
322 return FALSE;
323
324 if (!hasNext)
325 return FALSE;
326
327 hasNext = _ReadNextArg(&strNextArg, strField, _countof(strField));
328
329 // Root may be a pidl
330 if (!StrCmpIW(strField, L"/IDLIST"))
331 {
332 if (hasNext)
333 {
334 hasNext = _ReadNextArg(&strNextArg, strField, _countof(strField));
335 }
336 pidlRoot = _ILReadFromSharedMemory(strField);
337 }
338 else
339 {
340 // Or just a path string
341 _ParsePathToPidl(strField, &pidlRoot);
342 }
343
344 pInfo->pidlRoot = pidlRoot;
345
346 // The root defaults to the desktop
347 if (!pidlRoot)
348 {
349 if (FAILED(SHGetSpecialFolderLocation(0, CSIDL_DESKTOP, &(pInfo->pidlRoot))))
350 pInfo->pidlRoot = NULL;
351 }
352
353 // TODO: Create rooted PIDL from pInfo->pidlPath and pInfo->pidlRoot
354
355 TRACE("CmdLine Parser: Parsed /ROOT flag. dwFlags=%08lx, pidlRoot=%p\n", pInfo->dwFlags, pInfo->pidlRoot);
356 }
357 else
358 {
359 // Anything else is part of the target path to browse to
360 TRACE("CmdLine Parser: Found target path %S\n", strField);
361
362 // Which can be a shared-memory itemidlist
363 if (!StrCmpIW(strField, L"/IDLIST"))
364 {
365 LPITEMIDLIST pidlArg;
366
367 if (!hasNext)
368 return FALSE;
369
370 hasNext = _ReadNextArg(&strNextArg, strField, _countof(strField));
371 pidlArg = _ILReadFromSharedMemory(strField);
372 if (!pidlArg)
373 return FALSE;
374
375 if (pInfo->pidlPath)
376 ILFree(pInfo->pidlPath);
377 pInfo->pidlPath = pidlArg;
378
379 TRACE("CmdLine Parser: Parsed target path. dwFlags=%08lx, pidlPath=%p\n", pInfo->dwFlags, pInfo->pidlPath);
380 }
381 else
382 {
383 // Or just a plain old string.
384
385 if (PathIsDirectoryW(strField))
386 PathAddBackslash(strField);
387
388 WCHAR szPath[MAX_PATH];
389 DWORD result = GetFullPathNameW(strField, _countof(szPath), szPath, NULL);
390
391 if (result != 0 && result <= _countof(szPath) && PathFileExistsW(szPath))
392 StringCchCopyW(strField, _countof(strField), szPath);
393
394 LPITEMIDLIST pidlPath = ILCreateFromPathW(strField);
395
396 pInfo->pidlPath = pidlPath;
397
398 if (pidlPath)
399 {
400 pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_IDLIST;
401 TRACE("CmdLine Parser: Parsed target path. dwFlags=%08lx, pidlPath=%p\n", pInfo->dwFlags, pInfo->pidlPath);
402 }
403 else
404 {
405 // The path could not be parsed into an ID List,
406 // so pass it on as a plain string.
407
408 PWSTR field = StrDupW(strField);
409 pInfo->strPath = field;
410 if (field)
411 {
412 pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_STRING;
413 TRACE("CmdLine Parser: Parsed target path. dwFlags=%08lx, strPath=%S\n", pInfo->dwFlags, field);
414 }
415 }
416
417 }
418 }
419
420 if (!hasNext)
421 break;
422 hasNext = _ReadNextArg(&strNextArg, strField, _countof(strField));
423 }
424
425 return TRUE;
426 }