1 /* win32 shell tools */
2
3 /* shell_quote()
4 surround dangerous strings with double quotes.
5 escape quotes and backslashes.
6 dest should be twice as large as source
7 + 2 (for quotes) + 1 for zero byte + 1 for possible endslash */
shell_quote(char * dest,const char * source)8 int shell_quote (char * dest, const char * source) {
9 const char * characters = " &<>|^\t";
10 /* Chars other than command separators are actual only when command
11 interpreter is used */
12 BOOL ech, quote = !(*source); /* quote empty arguments */
13 int escaped = 0;
14 char * dcp = dest;
15 *dcp++ = ' ';
16 while (*source) {
17 quote = quote || strchr(characters,*source);
18 ech = *source == '\\';
19 if (!escaped && *source == '"') *dcp++ = '\\';
20 *dcp++ = *source++;
21 escaped = !escaped && ech;
22 }
23 if (quote) {
24 if (escaped) *dcp++ = '\\'; /* double ending slash */
25 *dcp++ = '"'; *dest = '"'; }
26 *dcp = 0;
27 /* shift string left if no quote was inserted */
28 if (!quote) for (dcp = dest;;dcp++) if (!(*dcp = dcp[1])) break;
29 return dcp - dest;
30 }
31
32 /*========== shell shortcut resolution ==========*/
33 #include <shlobj.h>
34
35 /* is_cygwin_symlink based on path.cc from cygwin sources
36 only CYGWIN=winsymlinks is supported (i.e., a symlink is a windows *.lnk
37 file); system file with a cookie is NOT (FIXME!)
38 for win_shortcut_hdr description see also,
39 http://msdn.microsoft.com/en-us/library/dd871305(PROT.10).aspx */
40
41 static const GUID GUID_shortcut =
42 {0x00021401L, 0, 0, {0xc0, 0, 0, 0, 0, 0, 0, 0x46}};
43
44 enum {
45 WSH_FLAG_IDLIST = 0x01,
46 WSH_FLAG_FILE = 0x02,
47 WSH_FLAG_DESC = 0x04,
48 WSH_FLAG_RELPATH = 0x08,
49 WSH_FLAG_WD = 0x10,
50 WSH_FLAG_CMDLINE = 0x20,
51 WSH_FLAG_ICON = 0x40
52 };
53
54 struct win_shortcut_hdr
55 {
56 DWORD size; /* Header size in bytes. Must contain 0x4c. */
57 GUID magic; /* GUID of shortcut files. */
58 DWORD flags; /* Content flags. See above. */
59
60 /* The next fields from attr to icon_no are always set to 0 in Cygwin
61 and U/Win shortcuts. */
62 DWORD attr; /* Target file attributes. */
63 FILETIME ctime; /* These filetime items are never touched by the */
64 FILETIME mtime; /* system, apparently. Values don't matter. */
65 FILETIME atime;
66 DWORD filesize; /* Target filesize. */
67 DWORD icon_no; /* Icon number. */
68
69 DWORD run; /* Values defined in winuser.h. Use SW_NORMAL. */
70 DWORD hotkey; /* Hotkey value. Set to 0. */
71 DWORD dummy[2]; /* Future extension probably. Always 0. */
72 };
73
74 #define WINSHDRSIZE sizeof(struct win_shortcut_hdr)
75
76 static int
cmp_shortcut_header(struct win_shortcut_hdr * file_header)77 cmp_shortcut_header (struct win_shortcut_hdr *file_header)
78 {
79 /* A Cygwin or U/Win shortcut only contains a description and a relpath.
80 Cygwin shortcuts also might contain an ITEMIDLIST. The run type is
81 always set to SW_NORMAL. */
82 DWORD * pzero = &file_header->attr;
83 /* FILETIME is two DWORDS so check it as array of DWORDS */
84 while (pzero < &file_header->run && !*pzero++);
85 return file_header->size == WINSHDRSIZE
86 && !memcmp (&file_header->magic, &GUID_shortcut, sizeof GUID_shortcut)
87 && (file_header->flags & ~WSH_FLAG_IDLIST)
88 == (WSH_FLAG_DESC | WSH_FLAG_RELPATH)
89 && pzero >= &file_header->run
90 && file_header->run == SW_NORMAL;
91 }
92
93 enum cygsym_enum { cygsym_notsym = 0, cygsym_issym, cygsym_err };
94
95 enum cygsym_enum is_cygwin_symlink (const char * filename);
96
is_cygwin_symlink(const char * filename)97 enum cygsym_enum is_cygwin_symlink (const char * filename)
98 {
99 HANDLE handle;
100 enum cygsym_enum result = cygsym_err;
101 handle = CreateFile(filename,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,
102 NULL,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,NULL);
103 if (handle == INVALID_HANDLE_VALUE) return result;
104 do {
105 DWORD got;
106 BY_HANDLE_FILE_INFORMATION finfo;
107 struct win_shortcut_hdr header;
108 if (!GetFileInformationByHandle (handle, &finfo)) break;
109 result = cygsym_notsym;
110 if (!(finfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) break;
111 if (GetFileSize (handle, NULL) > 8192) break;
112 if (!ReadFile (handle, &header, WINSHDRSIZE, &got, NULL)) break;
113 if (got != WINSHDRSIZE || !cmp_shortcut_header (&header))
114 break;
115 result = cygsym_issym;
116 } while (0);
117 CloseHandle(handle);
118 return result;
119 }
120
121 /* We will need "breakthrough" (BT) version of every COM function
122 which could be called on unitialized (in the current thread)
123 COM library */
124
125 HRESULT BTCoCreateInstance (REFCLSID rclsid, LPUNKNOWN pUnkOuter,
126 DWORD dwClsContext, REFIID riid,
127 LPVOID * ppv);
128
BTCoCreateInstance(REFCLSID rclsid,LPUNKNOWN pUnkOuter,DWORD dwClsContext,REFIID riid,LPVOID * ppv)129 HRESULT BTCoCreateInstance (REFCLSID rclsid, LPUNKNOWN pUnkOuter,
130 DWORD dwClsContext, REFIID riid,
131 LPVOID * ppv)
132 {
133 HRESULT result = CoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, ppv);
134 if (result != CO_E_NOTINITIALIZED || CoInitialize(NULL) != S_OK)
135 return result;
136 return CoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, ppv);
137 }
138
139 /* extracts a filename field from windows shortcut
140 > filename: name the shortcut file
141 < resolved: buffer not less than MAX_PATH
142 < result: true if link was successfully resolved */
resolve_shell_shortcut(LPCSTR filename,LPSTR resolved)143 static BOOL resolve_shell_shortcut (LPCSTR filename, LPSTR resolved) {
144 HRESULT hres;
145 IShellLink* psl;
146 WIN32_FIND_DATA wfd;
147 BOOL result = FALSE;
148 IPersistFile* ppf;
149
150 /* no matter it's FS error or not cygwin shortcut -
151 probably it should be fixed */
152 if (is_cygwin_symlink(filename) != cygsym_issym) return FALSE;
153 /* Get a pointer to the IShellLink interface. */
154 hres = BTCoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
155 &IID_IShellLink, (LPVOID*)&psl);
156 if (FAILED(hres)) return FALSE;
157 /* Get a pointer to the IPersistFile interface. */
158 hres = psl->lpVtbl->QueryInterface(psl, &IID_IPersistFile,(LPVOID *) &ppf);
159 if (SUCCEEDED(hres)) {
160 WCHAR wsz[MAX_PATH];
161 /* Ensure that the string is Unicode. */
162 MultiByteToWideChar(CP_ACP, 0, filename, -1, wsz,MAX_PATH);
163 /* Load the shortcut. */
164 hres = ppf->lpVtbl->Load(ppf, wsz, STGM_READ);
165 if (SUCCEEDED(hres)) {
166 /* Get the path to the link target. */
167 hres = psl->lpVtbl->GetPath(psl, resolved, MAX_PATH,
168 (WIN32_FIND_DATA *)&wfd,
169 4 /* SLGP_RAWPATH */);
170 if (SUCCEEDED(hres)) result = TRUE;
171 if (!*resolved) {
172 /* empty string. maybe broken link. try to get description
173 as cygwin stores filenames there */
174 hres = psl->lpVtbl->GetDescription(psl, resolved, MAX_PATH);
175 if (FAILED(hres)) *resolved = 0;
176 }
177 }
178 /* Release the pointer to the IPersistFile interface. */
179 ppf->lpVtbl->Release(ppf);
180 }
181 /* Release the pointer to the IShellLink interface. */
182 psl->lpVtbl->Release(psl);
183 return result;
184 }
185
186 #if !defined(cpslashp)
187 # define cpslashp(c) ((c) == '\\' || (c) == '/')
188 #endif
189
190 /* Uses the base from filename to augment pathname.
191 Return false when (pathname is relative AND filename doesn't
192 contain the base), so pathname (contained in shortcut)
193 cannot be referenced other than to current directory
194 what is wrong. */
augment_relative_pathname(LPCSTR filename,LPSTR pathname)195 static BOOL augment_relative_pathname(LPCSTR filename, LPSTR pathname) {
196 /* check if pathname is absolute */
197 /* what to do with "/bar/foo" pathnames ?*/
198 if (cpslashp(pathname[0])) return FALSE; /* let's panic */
199 if (((pathname[0] >= 'a' && pathname[0] <= 'z')
200 || (pathname[0] >= 'A' && pathname[0] <= 'Z'))
201 && pathname[1] == ':' && cpslashp(pathname[2])) return TRUE;
202 if (pathname[0] == '\\' && pathname[1] == '\\') return TRUE;
203 {
204 int fl = strlen(filename);
205 int pl = strlen(pathname);
206 const char * cp = filename + fl - 1;
207 /* find the last slash */
208 for (;!cpslashp(*cp) && cp > filename;cp--);
209 if (!cpslashp(*cp)) return FALSE; /* no slash */
210 memmove(pathname + (cp - filename + 1),pathname,pl + 1);
211 memmove(pathname,filename,cp - filename + 1);
212 }
213 return TRUE;
214 }
215
216 typedef enum {
217 shell_shortcut_notresolved = 0,
218 shell_shortcut_notexists,
219 shell_shortcut_file,
220 shell_shortcut_directory
221 } shell_shortcut_target_t;
222
223 /* resolves shortcuts to shortcuts
224 > filename : name of link file to resolve
225 < resolved : buffer to receive resolved name
226 < result : status of resolving and target file attributes */
227 static shell_shortcut_target_t
resolve_shell_shortcut_more(LPCSTR filename,LPSTR resolved)228 resolve_shell_shortcut_more (LPCSTR filename, LPSTR resolved)
229 {
230 char pathname[_MAX_PATH];
231 char pathname1[_MAX_PATH];
232 int dirp = 0;
233 int exists = 0;
234 int try_counter = 33;
235 int l, resolvedp = resolve_shell_shortcut(filename,pathname)
236 && augment_relative_pathname(filename,pathname);
237 /* handle links to links. cygwin can do such */
238 while (resolvedp && try_counter--) {
239 l=strlen(pathname);
240 if (l >= 4 && stricmp(pathname+l-4,".lnk") == 0)
241 resolvedp = resolve_shell_shortcut(pathname,pathname1)
242 && augment_relative_pathname(pathname,pathname1)
243 && strcpy(pathname,pathname1);
244 else {
245 /* not a link to shortcut but can be the symbolic filename */
246 strcpy(pathname+l,".lnk");
247 if (!resolve_shell_shortcut(pathname,pathname1)
248 || !augment_relative_pathname(pathname,pathname1)) {
249 pathname[l] = '\0'; break; }
250 else strcpy(pathname,pathname1);
251 }
252 }
253 if (resolvedp) { /* additional checks */
254 DWORD fileattr = GetFileAttributes(pathname);
255 exists = fileattr != 0xFFFFFFFF;
256 dirp = exists && fileattr&FILE_ATTRIBUTE_DIRECTORY;
257 if (resolved) {
258 strcpy(resolved,pathname);
259 if (dirp) strcat(resolved,"\\");
260 }
261 if (dirp) return shell_shortcut_directory;
262 if (exists && !dirp) return shell_shortcut_file;
263 return shell_shortcut_notexists;
264 } else return shell_shortcut_notresolved;
265 }
266
267 /* see if a file is normal file or it is a "shell symlink"
268 If directory+filename exists do nothing return false
269 If it doesn't but direstory+filename+".lnk" exists then
270 try to read it. On reading success return true with lnk
271 filename value as resolved. See resolve_shell_shortcut also.
272 > filename: resolving file name
273 < resolved: buffer for resolved path and filename.
274 < result: shell_shortcut_notresolved if file exists or link is invalid.
275 otherwise - shortcut target status */
276 static shell_shortcut_target_t
resolve_shell_symlink(LPCSTR filename,LPSTR resolved)277 resolve_shell_symlink (LPCSTR filename, LPSTR resolved)
278 {
279 char pathname[_MAX_PATH];
280 DWORD fileattr;
281
282 strcpy(pathname,filename);
283 fileattr = GetFileAttributes(pathname);
284 if (fileattr != 0xFFFFFFFF) return shell_shortcut_notresolved;
285 strcat(pathname,".lnk");
286 fileattr = GetFileAttributes(pathname);
287 if (fileattr == 0xFFFFFFFF) return shell_shortcut_notresolved;
288 return resolve_shell_shortcut_more(pathname,resolved);
289 }
290
291 /* the ultimate shortcut megaresolver
292 style inspired by directory_search_scandir
293 > namein: absolute filename pointing to file or directory
294 wildcards (only asterisk) may appear only as filename
295 < nameout: filename with directory and file shortcuts resolved
296 on failure holds filename resolved so far
297 < result: true if resolving succeeded */
real_path(LPCSTR namein,LPSTR nameout)298 BOOL real_path (LPCSTR namein, LPSTR nameout) {
299 WIN32_FIND_DATA wfd;
300 HANDLE h = NULL;
301 char * nametocheck;
302 char * nametocheck_end;
303 int name_len;
304 /* drive|dir1|dir2|name
305 ^nametocheck
306 ^nametocheck_end */
307 char saved_char;
308 BOOL next_name = 0;/* if we found an lnk and need to start over */
309 int try_counter = 33;
310 if ((name_len = strlen(namein)) >= MAX_PATH) return FALSE;
311 strcpy(nameout,namein);
312 do { /* whole file names */
313 next_name = FALSE;
314 if (!*nameout) return FALSE;
315 /* skip drive or host or first slash */
316 nametocheck = nameout;
317 if (((*nametocheck >= 'a' && *nametocheck <= 'z')
318 || (*nametocheck >= 'A' && *nametocheck <= 'Z'))
319 && nametocheck[1] == ':')
320 { if (cpslashp(nametocheck[2])) {
321 /* drive */
322 nametocheck += 3;
323 } else {
324 /* default directory on drive */
325 char drive[4] = "C:.", *name;
326 int default_len;
327 drive[0] = namein[0];
328 if (!GetFullPathName(drive,_MAX_PATH,nameout,&name)
329 || (default_len = strlen(nameout)) + name_len
330 >= _MAX_PATH) return FALSE;
331 nameout[default_len] = '\\';
332 strcpy(nameout + default_len + 1, namein + 2);
333 name_len += default_len - 1; /* Was C:lisp.exe
334 Now C:\clisp\lisp.exe
335 removed 2
336 added default_len + 1 chars */
337 nametocheck += default_len + 1;
338 } }
339 else if (nametocheck[0]=='\\' && nametocheck[1]=='\\') {
340 int i;
341 /* host */
342 nametocheck+=2;
343 for (i=0;i<2;i++) {/* skip host and sharename */
344 while (*nametocheck && !cpslashp(*nametocheck))
345 nametocheck++;
346 if (*nametocheck) nametocheck++; else return FALSE;
347 }
348 } else if (cpslashp(*nametocheck)) nametocheck++;
349 /* prefix skipped; start checking */
350 do { /* each component after just skipped */
351 int dots_only = 0;
352 int have_stars = 0;
353 /* separate a component */
354 for (nametocheck_end = nametocheck;
355 *nametocheck_end && !cpslashp(*nametocheck_end);
356 nametocheck_end++);
357 if (*nametocheck_end && nametocheck_end == nametocheck)
358 return FALSE;/* two slashes one after another */
359 /* save slash or zero */
360 saved_char = *nametocheck_end;
361 *nametocheck_end = 0;
362 /* Is it . or .. ? FFF handles this strange way */
363 { char * cp = nametocheck;
364 for (;*cp=='.';cp++);
365 dots_only = !(*cp) && cp > nametocheck; }
366 /* Asterisks in the middle of filename: error
367 Asterisks as pathname: success */
368 { char * cp = nametocheck;
369 for (;*cp && *cp!='*';cp++);
370 have_stars = *cp == '*'; }
371 if (have_stars && saved_char) return FALSE;
372 if (!have_stars) {
373 if (dots_only || !*nametocheck) {
374 /* treat 'start/./end', 'drive/', 'host/' specially */
375 /* search for ....\.\* */
376 char saved[2];
377 if (nametocheck_end - nameout + 2 > MAX_PATH) return FALSE;
378 saved[0] = nametocheck_end[1]; saved[1] = nametocheck_end[2];
379 /* !*nametocheck here means there was "something\" before */
380 strcpy(nametocheck_end,*nametocheck?"\\*":"*");
381 h = FindFirstFile(nameout,&wfd);
382 nametocheck_end[1] = saved[0]; nametocheck_end[2] = saved[1];
383 nametocheck_end[0] = 0;
384 if (h != INVALID_HANDLE_VALUE) {
385 FindClose(h); /* don't substitute */
386 } else return FALSE; /* don't try lnk */
387 } else {/* not only dots */
388 h = FindFirstFile(nameout,&wfd);
389 if (h != INVALID_HANDLE_VALUE) {
390 /* make space for full (non 8.3) name component */
391 int l = strlen(wfd.cFileName),
392 oldl = nametocheck_end - nametocheck,
393 new_name_len = name_len + l - oldl;
394 FindClose(h);
395 if (new_name_len >= _MAX_PATH) return FALSE;
396 if (l != oldl) {
397 int restlen =
398 saved_char?(name_len - (nametocheck_end - nameout)):0;
399 memmove(nametocheck+l,nametocheck_end,restlen+1);
400 }
401 strncpy(nametocheck,wfd.cFileName,l);
402 nametocheck_end = nametocheck + l;
403 name_len = new_name_len;
404 } else {/* try shortcut
405 Note: something\cyglink.lnk doesn't resolve to the contents
406 of cyglink.lnk so one can read/write symlink .lnk
407 files although they are not present in DIRECTORY output.
408 Is it bug or feature? */
409 char saved[4];
410 char resolved[MAX_PATH];
411 int resolved_len;
412 shell_shortcut_target_t rresult;
413 if (nametocheck_end - nameout + 4 > MAX_PATH) return FALSE;
414 strncpy(saved,nametocheck_end+1,4);
415 strncpy(nametocheck_end,".lnk",5);
416 rresult = resolve_shell_shortcut_more(nameout,resolved);
417 strncpy(nametocheck_end+1,saved,4);
418 *nametocheck_end = 0;
419 /* use saved_char as directory indicator */
420 if (rresult == shell_shortcut_notresolved
421 || rresult == shell_shortcut_notexists
422 || (saved_char ? rresult == shell_shortcut_file
423 : rresult == shell_shortcut_directory))
424 return FALSE;
425 resolved_len = strlen(resolved);
426 if (saved_char) {
427 /*need to subst nameout..nametocheck-1 with resolved path */
428 int l2 = name_len - (nametocheck_end - nameout);
429 if (resolved_len + l2 + 2 > MAX_PATH) return FALSE;
430 strncpy(resolved + resolved_len, nametocheck_end + 1, l2);
431 name_len = l2 - 1;
432 }
433 name_len += resolved_len;
434 strcpy(nameout,resolved);
435 next_name = TRUE;
436 }
437 }
438 }
439 if (!next_name) {
440 *nametocheck_end = saved_char;
441 nametocheck = nametocheck_end;
442 if (*nametocheck) nametocheck++;
443 }
444 } while (!next_name && *nametocheck);
445 if (!(--try_counter)) return FALSE;
446 } while (next_name);
447 return TRUE;
448 }
449