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