xref: /reactos/dll/win32/browseui/parsecmdline.cpp (revision d6eebaa4)
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 // Returns FALSE, TRUE or an address.
229 extern "C"
230 UINT_PTR
231 WINAPI
232 SHExplorerParseCmdLine(_Out_ PEXPLORER_CMDLINE_PARSE_RESULTS pInfo)
233 {
234     WCHAR   strField[MAX_PATH];
235     WCHAR   strDir[MAX_PATH];
236 
237     PCWSTR strCmdLine = GetCommandLineW();
238     PCWSTR strFieldArray = PathGetArgsW(strCmdLine);
239 
240     if (!*strFieldArray)
241     {
242         pInfo->dwFlags = 9;
243         pInfo->pidlPath = _GetDocumentsPidl();
244         if (!pInfo->pidlPath)
245         {
246             GetWindowsDirectoryW(strDir, MAX_PATH);
247             PathStripToRootW(strDir);
248             pInfo->pidlPath = ILCreateFromPathW(strDir);
249         }
250         return (UINT_PTR)pInfo->pidlPath;
251     }
252 
253     PCWSTR strNextArg = _FindFirstField(strFieldArray);
254 
255     BOOL hasNext = _ReadNextArg(&strNextArg, strField, _countof(strField));
256 
257     while (TRUE)
258     {
259         // Basic flags-only params first
260         if (!StrCmpIW(strField, L"/N"))
261         {
262             pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_NEWWND | SH_EXPLORER_CMDLINE_FLAG_NOREUSE;
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 (!hasNext)
322                 return FALSE;
323 
324             hasNext = _ReadNextArg(&strNextArg, strField, _countof(strField));
325 
326             // Root may be a pidl
327             if (!StrCmpIW(strField, L"/IDLIST"))
328             {
329                 if (hasNext)
330                 {
331                     hasNext = _ReadNextArg(&strNextArg, strField, _countof(strField));
332                 }
333                 pidlRoot = _ILReadFromSharedMemory(strField);
334             }
335             else
336             {
337                 // Or just a path string
338                 _ParsePathToPidl(strField, &pidlRoot);
339             }
340 
341             pInfo->pidlRoot = pidlRoot;
342 
343             // The root defaults to the desktop
344             if (!pidlRoot)
345             {
346                 if (FAILED(SHGetSpecialFolderLocation(0, CSIDL_DESKTOP, &(pInfo->pidlRoot))))
347                     pInfo->pidlRoot = NULL;
348             }
349 
350             // TODO: Create rooted PIDL from pInfo->pidlPath and pInfo->pidlRoot
351 
352             TRACE("CmdLine Parser: Parsed /ROOT flag. dwFlags=%08lx, pidlRoot=%p\n", pInfo->dwFlags, pInfo->pidlRoot);
353         }
354         else
355         {
356             // Anything else is part of the target path to browse to
357             TRACE("CmdLine Parser: Found target path %S\n", strField);
358 
359             // Which can be a shared-memory itemidlist
360             if (!StrCmpIW(strField, L"/IDLIST"))
361             {
362                 LPITEMIDLIST pidlArg;
363 
364                 if (!hasNext)
365                     return FALSE;
366 
367                 hasNext = _ReadNextArg(&strNextArg, strField, _countof(strField));
368                 pidlArg = _ILReadFromSharedMemory(strField);
369                 if (!pidlArg)
370                     return FALSE;
371 
372                 if (pInfo->pidlPath)
373                     ILFree(pInfo->pidlPath);
374                 pInfo->pidlPath = pidlArg;
375 
376                 TRACE("CmdLine Parser: Parsed target path. dwFlags=%08lx, pidlPath=%p\n", pInfo->dwFlags, pInfo->pidlPath);
377             }
378             else
379             {
380                 // Or just a plain old string.
381 
382                 if (PathIsDirectoryW(strField))
383                     PathAddBackslash(strField);
384 
385                 WCHAR szPath[MAX_PATH];
386                 DWORD result = GetFullPathNameW(strField, _countof(szPath), szPath, NULL);
387 
388                 if (result != 0 && result <= _countof(szPath) && PathFileExistsW(szPath))
389                     StringCchCopyW(strField, _countof(strField), szPath);
390 
391                 LPITEMIDLIST pidlPath = ILCreateFromPathW(strField);
392 
393                 pInfo->pidlPath = pidlPath;
394 
395                 if (pidlPath)
396                 {
397                     pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_IDLIST;
398                     TRACE("CmdLine Parser: Parsed target path. dwFlags=%08lx, pidlPath=%p\n", pInfo->dwFlags, pInfo->pidlPath);
399                 }
400                 else
401                 {
402                     // The path could not be parsed into an ID List,
403                     // so pass it on as a plain string.
404 
405                     PWSTR field;
406                     SHStrDupW(strField, &field);
407                     pInfo->strPath = field;
408                     if (field)
409                     {
410                         pInfo->dwFlags |= SH_EXPLORER_CMDLINE_FLAG_STRING;
411                         TRACE("CmdLine Parser: Parsed target path. dwFlags=%08lx, strPath=%S\n", pInfo->dwFlags, field);
412                     }
413                 }
414             }
415         }
416 
417         if (!hasNext)
418             break;
419         hasNext = _ReadNextArg(&strNextArg, strField, _countof(strField));
420     }
421 
422     return TRUE;
423 }
424