Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions WSLDVCPlugin/WSLDVCCallback.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -652,8 +652,17 @@ class WSLDVCCallback :
}
}
}
// Sanitize the appId-derived leaf for the same reason as the .lnk
// leaf below: a reserved char (e.g. ':') yields an illegal path and
// CreateIconFile/IPersistFile::Save fails (wslg#1009).
WCHAR iconLeaf[MAX_PATH] = {};
if (wcscpy_s(iconLeaf, ARRAYSIZE(iconLeaf), updateAppList.appId) != 0)
{
return E_FAIL;
}
SanitizeFileName(iconLeaf, ARRAYSIZE(iconLeaf), updateAppList.appId);
if ((wcscat_s(iconPath, ARRAYSIZE(iconPath), L"\\") != 0) ||
(wcscat_s(iconPath, ARRAYSIZE(iconPath), updateAppList.appId) != 0) ||
(wcscat_s(iconPath, ARRAYSIZE(iconPath), iconLeaf) != 0) ||
(wcscat_s(iconPath, ARRAYSIZE(iconPath), L".ico") != 0))
{
return E_FAIL;
Expand Down Expand Up @@ -688,11 +697,20 @@ class WSLDVCCallback :
}
}
}
// Use description to name link file since this is name shows up
// at StartMenu UI. SHSetLocalizedName can't be uses since this
// is not in resource.
// Use the description to name the link file since this is the name shown
// at the StartMenu UI. SHSetLocalizedName can't be used since this
// is not in a resource.
// Sanitize only the filename leaf (appDesc is kept intact for
// SetDescription); names like "Code::Blocks" otherwise produce an
// illegal .lnk path and the shortcut is silently dropped (wslg#1009).
WCHAR linkLeaf[MAX_PATH] = {};
if (wcscpy_s(linkLeaf, ARRAYSIZE(linkLeaf), updateAppList.appDesc) != 0)
{
return E_FAIL;
}
SanitizeFileName(linkLeaf, ARRAYSIZE(linkLeaf), updateAppList.appId);
if ((wcscat_s(linkPath, ARRAYSIZE(linkPath), L"\\") != 0) ||
(wcscat_s(linkPath, ARRAYSIZE(linkPath), updateAppList.appDesc) != 0) ||
(wcscat_s(linkPath, ARRAYSIZE(linkPath), linkLeaf) != 0) ||
(wcscat_s(linkPath, ARRAYSIZE(linkPath), L".lnk") != 0))
{
return E_FAIL;
Expand Down
95 changes: 95 additions & 0 deletions WSLDVCPlugin/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,101 @@ void DebugPrint(const wchar_t* format, ...)
}
#endif // DBG_MESSAGE

// True if the filename base (the token before the first '.') is a reserved DOS
// device name. These are illegal as a filename even with an extension, e.g.
// "NUL.txt", and contain no reserved character, so they must be checked separately.
static bool IsReservedDeviceName(LPCWSTR name)
{
size_t base = 0;
while (name[base] && name[base] != L'.')
++base;

if (base == 3 &&
(_wcsnicmp(name, L"CON", 3) == 0 || _wcsnicmp(name, L"PRN", 3) == 0 ||
_wcsnicmp(name, L"AUX", 3) == 0 || _wcsnicmp(name, L"NUL", 3) == 0))
return true;

if (base == 4 &&
(_wcsnicmp(name, L"COM", 3) == 0 || _wcsnicmp(name, L"LPT", 3) == 0) &&
name[3] >= L'1' && name[3] <= L'9')
return true;

return false;
}

_Use_decl_annotations_
void SanitizeFileName(LPWSTR name, size_t cch, LPCWSTR uniqueId)
{
// Replace characters that are illegal in a Windows filename component with '_'.
// The Start-Menu tile text is this filename (SetDescription only sets the
// tooltip), so the displayed name changes for such apps -- acceptable and
// predictable, and it renders in any font (unlike look-alike glyph substitution).
bool changed = false;
for (LPWSTR p = name; *p; ++p)
{
switch (*p)
{
case L'\\': case L'/': case L':': case L'*':
case L'?': case L'"': case L'<': case L'>': case L'|':
*p = L'_'; changed = true; break;
default:
if (*p < 0x20) { *p = L'_'; changed = true; }
break;
}
}
// Win32: a trailing space or dot in a filename component is illegal.
size_t n = wcslen(name);
while (n > 0 && (name[n - 1] == L' ' || name[n - 1] == L'.'))
{
name[--n] = L'\0';
changed = true;
}

// A reserved DOS device name (CON, PRN, AUX, NUL, COM1..9, LPT1..9) is illegal
// as a filename base on its own. Windows keys this on the token before the first
// '.', so a trailing hash suffix wouldn't help "NUL.txt" -- prepend '_' to
// neutralize the base instead.
if (IsReservedDeviceName(name))
{
if (n + 1 < cch)
{
memmove(name + 1, name, (n + 1) * sizeof(WCHAR));
name[0] = L'_';
++n;
}
changed = true;
}

// If nothing was altered the leaf is already a valid, unambiguous filename --
// keep it verbatim (the common case, byte-for-byte as before).
if (!changed)
return;

// '_' substitution can map distinct names (e.g. "A:B" and "A/B") onto the same
// leaf, which would make two apps' .lnk/.ico files overwrite each other. Append
// a short, stable hash of the app's unique id so a sanitized leaf is always
// unique per app and deterministic across syncs. (wslg#1009)
UINT32 h = 2166136261u; // FNV-1a (32-bit)
for (LPCWSTR q = uniqueId; q && *q; ++q)
{
h ^= (UINT16)*q;
h *= 16777619u;
}
WCHAR suffix[10]; // "~" + 8 hex digits + NUL
swprintf_s(suffix, ARRAYSIZE(suffix), L"~%08X", h);

// Ensure leaf + suffix fits in cch; truncate the leaf if needed.
size_t suffixLen = wcslen(suffix);
if (n + suffixLen + 1 > cch)
{
n = (cch > suffixLen + 1) ? (cch - suffixLen - 1) : 0;
name[n] = L'\0';
while (n > 0 && (name[n - 1] == L' ' || name[n - 1] == L'.'))
name[--n] = L'\0';
}
wcscat_s(name, cch, suffix);
}

_Use_decl_annotations_
BOOL IsDirectoryPresent(LPCWSTR lpszPath)
{
Expand Down
8 changes: 8 additions & 0 deletions WSLDVCPlugin/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ void DebugPrint(const wchar_t* format, ...);
BOOL
IsDirectoryPresent(_In_z_ LPCWSTR lpszPath);

// Make a string safe to use as a Windows filename component, in place: replace
// reserved/control characters with '_' and trim trailing space/dot. If anything
// was changed, append a short stable hash of uniqueId so distinct names that
// sanitize to the same leaf don't collide on disk. Used to derive a safe .lnk/.ico
// leaf from app names that may contain ':' '/' etc. (wslg#1009).
void
SanitizeFileName(_Inout_updates_z_(cch) LPWSTR name, size_t cch, _In_z_ LPCWSTR uniqueId);

HRESULT
CreateShellLink(_In_z_ LPCWSTR lpszPathLink,
_In_z_ LPCWSTR lpszPathObj,
Expand Down