Notepad3/src/PathLib.c

986 lines
31 KiB
C

// ============================================================================
//
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
//
// https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
//
// In the Windows API (with some exceptions discussed in the following paragraphs),
// the maximum length for a path is MAX_PATH, which is defined as 260 characters.
// A local path is structured in the following order: drive letter, colon, backslash,
// name components separated by backslashes, and a terminating null character.
// For example, the maximum path on drive D is "D:\some 256-character path string<NUL>"
// where "<NUL>" represents the invisible terminating null character for the current
// system codepage.
// (The characters < > are used here for visual clarity and cannot be part of a valid path string.)
//
// The Windows API has many functions that also have Unicode versions to permit an
// extended-length path for a maximum total path length of 32767 characters.
// This type of path is composed of components separated by backslashes,
// each up to the value returned in the lpMaximumComponentLength parameter of the
// GetVolumeInformation function (this value is commonly 255 characters).
// To specify an extended-length path, use the "\\?\" prefix. For example,
// "\\?\D:\very long path".
//
// The "\\?\" prefix can also be used with paths constructed according to the universal
// naming convention (UNC). To specify such a path using UNC, use the "\\?\UNC\" prefix.
// For example, "\\?\UNC\server\share", where "server" is the name of the computer and
// "share" is the name of the shared folder.
// These prefixes are not used as part of the path itself.
// They indicate that the path should be passed to the system with minimal modification,
// which means that you cannot use forward slashes to represent path separators,
// or a period to represent the current directory, or double dots to represent the
// parent directory. Because you cannot use the "\\?\" prefix with a relative path,
// relative paths are always limited to a total of MAX_PATH characters.
//
// There is no need to perform any Unicode normalization on path and file name strings
// for use by the Windows file I/O API functions because the file system treats path and
// file names as an opaque sequence of WCHARs. Any normalization that your application
// requires should be performed with this in mind, external of any calls to related
// Windows file I/O API functions.
//
// When using an API to create a directory, the specified path cannot be so long that you
// cannot append an 8.3 file name (that is, the directory name cannot exceed MAX_PATH minus 12).
//
// The shell and the file system have different requirements.
// It is possible to create a path with the Windows API that the shell user interface
// is not able to interpret properly.
//
// ============================================================================
// ============================================================================
// TODO: if (IsWindows10OrGreater() && OptInRemovedMaxPathLimit()) {}
// https://docs.microsoft.com/de-de/windows/win32/api/fileapi/nf-fileapi-getfileattributesa
//
// These are the directory management functions that no longer have MAX_PATH restrictions
// if you opt - in to long path behavior :
// - CreateDirectoryW
// - CreateDirectoryExW
// - GetCurrentDirectoryW
// - RemoveDirectoryW
// - SetCurrentDirectoryW
//
// These are the file management functions that no longer have MAX_PATH restrictions
// if you opt - in to long path behavior :
// - CopyFileW
// - CopyFile2
// - CopyFileExW
// - CreateFileW
// - CreateFile2
// - CreateHardLinkW
// - CreateSymbolicLinkW
// - DeleteFileW
// - FindFirstFileW
// - FindFirstFileExW
// - FindNextFileW
// - GetFileAttributesW
// - GetFileAttributesExW
// - SetFileAttributesW
// - GetFullPathNameW
// - GetLongPathNameW
// - MoveFileW
// - MoveFileExW
// - MoveFileWithProgressW
// - ReplaceFileW
// - SearchPathW
// - FindFirstFileNameW
// - FindNextFileNameW
// - FindFirstStreamW
// - FindNextStreamW
// - GetCompressedFileSizeW
// - GetFinalPathNameByHandleW
//
// ============================================================================
#if !defined(WINVER)
#define WINVER 0x602 /*_WIN32_WINNT_WIN8*/
#endif
#if !defined(_WIN32_WINNT)
#define _WIN32_WINNT 0x602 /*_WIN32_WINNT_WIN8*/
#endif
#if !defined(NTDDI_VERSION)
#define NTDDI_VERSION 0x06020000 /*NTDDI_WIN7*/
#endif
#define VC_EXTRALEAN 1
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#include <processenv.h>
#include <stdbool.h>
#include <strsafe.h>
#include <fileapi.h>
#define PATHCCH_NO_DEPRECATE 1 // <- get rid of this!
#include <pathcch.h>
// get rid of this:
#include <shlobj.h>
#include <shellapi.h>
#include <shlwapi.h>
#define NP3_PATH_LIB_IMPLEMENTATION 1
#include "DynStrg.h"
#include "PathLib.h"
#pragma comment(linker, "/defaultlib:Pathcch")
/**************************************************/
/* */
/* PRIVATE API */
/* */
/**************************************************/
#define COUNTOF(ar) ARRAYSIZE(ar)
#define CONSTSTRGLEN(s) (COUNTOF(s) - 1)
const wchar_t* const PATHLONG_PREFIX = L"\\\\?\\";
#define PATHLONG_PREFIX_LEN (COUNTOF(PATHLONG_PREFIX) - 1)
#define PATHLONG_MAX_CCH PATHCCH_MAX_CCH
#define IS_VALID_HANDLE(HNDL) ((HNDL) && ((HNDL) != INVALID_HANDLE_VALUE))
//==== StrSafe extensions =======================================================
__forceinline bool StrIsEmptyW(LPCWSTR s)
{
return (!s || (*s == L'\0'));
}
//inline size_t StringCchLenA(LPCSTR s, size_t n) {
// n = (n ? n : STRSAFE_MAX_CCH); size_t len; return (size_t)(!s ? 0 : (SUCCEEDED(StringCchLengthA(s, n, &len)) ? len : n));
//}
inline size_t StringCchLenA(LPCSTR s, size_t n)
{
n = (n ? n : STRSAFE_MAX_CCH);
return (s ? strnlen_s(s, n) : 0LL);
}
//inline size_t StringCchLenW(LPCWSTR s, size_t n) {
// n = (n ? n : STRSAFE_MAX_CCH); size_t len; return (size_t)(!s ? 0 : (SUCCEEDED(StringCchLengthW(s, n, &len)) ? len : n));
//}
inline size_t StringCchLenW(LPCWSTR s, size_t n)
{
n = (n ? n : STRSAFE_MAX_CCH);
return (s ? wcsnlen_s(s, n) : 0LL);
}
#if defined(UNICODE) || defined(_UNICODE)
#define StringCchLen(s, n) StringCchLenW((s), (n))
#else
#define StringCchLen(s, n) StringCchLenA((s), (n))
#endif
// ----------------------------------------------------------------------------
static bool OptInRemoveMaxPathLimit()
{
static int s_MaxPathLimitRemoved = -1;
switch (s_MaxPathLimitRemoved) {
case -1: {
// Function pointer to driver function
BOOLEAN(WINAPI * pRtlAreLongPathsEnabled)(void) = NULL;
s_MaxPathLimitRemoved = 0; // at least called once
HINSTANCE const hNTdllDll = LoadLibrary(L"ntdll.dll");
if (hNTdllDll) {
// get the function pointer to RtlAreLongPathsEnabled
pRtlAreLongPathsEnabled = (BOOLEAN(WINAPI*)(void))GetProcAddress(hNTdllDll, "RtlAreLongPathsEnabled");
if (pRtlAreLongPathsEnabled != NULL) {
s_MaxPathLimitRemoved = pRtlAreLongPathsEnabled() ? 1 : 0;
}
FreeLibrary(hNTdllDll);
}
return (s_MaxPathLimitRemoved == 1);
}
case 1:
return true;
default:
break;
}
return false;
}
// ----------------------------------------------------------------------------
__forceinline HSTRINGW ToHStrgW(HPATHL hpth)
{
if (!hpth)
return NULL;
return (HSTRINGW)hpth;
}
// ----------------------------------------------------------------------------
/**************************************************/
/* */
/* PUBLIC API */
/* */
/**************************************************/
HPATHL PTHAPI Path_Allocate(const wchar_t* path)
{
HSTRINGW hstr = StrgCreate();
if (path) {
StrgSet(hstr, path);
}
return (HPATHL)hstr;
}
// ----------------------------------------------------------------------------
void PTHAPI Path_Release(HPATHL hpth)
{
HSTRINGW hstr = ToHStrgW(hpth);
StrgDestroy(hstr);
}
// ----------------------------------------------------------------------------
int PTHAPI Path_Reset(HPATHL hpth, const wchar_t* path)
{
HSTRINGW hstr = ToHStrgW(hpth);
return StrgSet(hstr, path);
}
// ----------------------------------------------------------------------------
size_t PTHAPI Path_GetLength(HPATHL hpth)
{
HSTRINGW hstr = ToHStrgW(hpth);
return StrgGetLength(hstr);
}
// ----------------------------------------------------------------------------
bool PTHAPI Path_Append(HPATHL hpth, HPATHL hmore)
{
HSTRINGW hstr = ToHStrgW(hpth);
if (!hstr)
return false;
size_t const hstr_len = StrgGetLength(hstr);
size_t const hmore_len = Path_GetLength(hmore);
if (!hmore_len) {
return true;
}
LPCWSTR wmore = Path_Get(hmore);
LPWSTR wbuf = StrgWriteAccessBuf(hstr, hstr_len + hmore_len + PATHLONG_PREFIX_LEN + 8);
size_t const cch = StrgGetAllocLength(hstr);
DWORD const dwFlags = PATHCCH_ALLOW_LONG_PATHS;
// Windows 10, version 1703:
// PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH
bool const bOK = SUCCEEDED(PathCchAppendEx(wbuf, cch, wmore, dwFlags));
StrgSanitize(hstr);
return bOK;
}
// ----------------------------------------------------------------------------
// This function does not convert forward slashes (/) into back slashes (\).
// With untrusted input, this function by itself, cannot be used to convert
// paths into a form that can be compared with other paths for sub-path or identity.
// Callers that need that ability should convert forward to back slashes before
// using this function.
//
bool PTHAPI Path_Canonicalize(HPATHL hpth_out, HPATHL hpth_in)
{
HSTRINGW hstr_out = ToHStrgW(hpth_out);
if (!hstr_out)
return false;
LPCWSTR wbuf_in = Path_Get(hpth_in);
LPWSTR wbuf_out = StrgWriteAccessBuf(hstr_out, Path_GetLength(hpth_in) + PATHLONG_PREFIX_LEN + 8);
size_t const cch_out = StrgGetAllocLength(hstr_out);
DWORD const dwFlags = PATHCCH_ALLOW_LONG_PATHS;
// Windows 10, version 1703:
// PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH
// PATHCCH_ENSURE_TRAILING_SLASH
bool const bOK = SUCCEEDED(PathCchCanonicalizeEx(wbuf_out, cch_out, wbuf_in, dwFlags));
StrgSanitize(hstr_out);
return bOK;
}
// ----------------------------------------------------------------------------
bool PTHAPI Path_RemoveFileSpec(HPATHL hpth)
{
HSTRINGW hstr = ToHStrgW(hpth);
if (!hstr)
return false;
size_t const hstr_len = StrgGetLength(hstr);
LPWSTR wbuf = StrgWriteAccessBuf(hstr, hstr_len);
size_t cch = StrgGetAllocLength(hstr);
bool const bOK = SUCCEEDED(PathCchRemoveFileSpec(wbuf, cch));
StrgSanitize(hstr);
return bOK;
}
// ----------------------------------------------------------------------------
bool PTHAPI Path_RenameExtension(HPATHL hpth, const wchar_t* ext)
{
HSTRINGW hstr = ToHStrgW(hpth);
if (!hstr)
return false;
size_t const hstr_len = StrgGetLength(hstr);
LPWSTR wbuf = StrgWriteAccessBuf(hstr, hstr_len + 64);
size_t cch = StrgGetAllocLength(hstr);
bool const bOK = SUCCEEDED(PathCchRenameExtension(wbuf, cch, ext));
StrgSanitize(hstr);
return bOK;
}
// ----------------------------------------------------------------------------
void PTHAPI Path_ExpandEnvStrings(HPATHL hpth)
{
HSTRINGW hstr = ToHStrgW(hpth);
if (!hstr)
return;
LPCWSTR const path_buf = StrgGet(hstr);
HSTRINGW hstr_exp = StrgCreate();
size_t const min_len = max(ExpandEnvironmentStringsW(path_buf, NULL, 0), MAX_PATH);
LPWSTR expth_buf = StrgWriteAccessBuf(hstr_exp, min_len);
DWORD const nSize = (DWORD)StrgGetAllocLength(hstr_exp);
ExpandEnvironmentStringsW(path_buf, expth_buf, nSize);
StrgSanitize(hstr_exp);
StrgSet(hstr, expth_buf);
StrgDestroy(hstr_exp);
}
// ----------------------------------------------------------------------------
bool PTHAPI Path_IsExistingFile(HPATHL hpth)
{
HSTRINGW hstr = ToHStrgW(hpth);
if (!hstr)
return false;
HSTRINGW hxpth = StrgCopy(hstr);
if (!OptInRemoveMaxPathLimit()) {
if (StrgFind(hxpth, PATHLONG_PREFIX, 0) != 0) {
StrgInsert(hxpth, 0, PATHLONG_PREFIX);
}
}
LPCWSTR expth_buf = StrgWriteAccessBuf(hxpth, 1);
DWORD const dwFileAttrib = GetFileAttributesW(expth_buf);
///bool const bAccessOK = (dwFileAttrib != INVALID_FILE_ATTRIBUTES);
///if (!bAccessOK) {
/// DWORD const dwError = GetLastError();
/// switch (dwError) {
/// case ERROR_FILE_NOT_FOUND:
/// break;
/// case ERROR_PATH_NOT_FOUND:
/// break;
/// case ERROR_ACCESS_DENIED:
/// break;
/// default:
/// break;
/// }
///}
///bool const bIsDirectory = (dwFileAttrib & FILE_ATTRIBUTE_DIRECTORY);
return IsExistingFile(dwFileAttrib);
}
// ----------------------------------------------------------------------------
// ============================================================================
// Some Old MAX_PATH stuff
// TODO: refactor to DynStrg parameter
// ============================================================================
//=============================================================================
//
// GetKnownFolderPath()
//
bool PTHAPI GetKnownFolderPath(REFKNOWNFOLDERID rfid, LPWSTR lpOutPath, size_t cchCount)
{
//const DWORD dwFlags = (KF_FLAG_DEFAULT_PATH | KF_FLAG_NOT_PARENT_RELATIVE | KF_FLAG_NO_ALIAS);
const DWORD dwFlags = KF_FLAG_NO_ALIAS;
PWSTR pszPath = NULL;
HRESULT hr = SHGetKnownFolderPath(rfid, dwFlags, NULL, &pszPath);
if (SUCCEEDED(hr) && pszPath) {
StringCchCopy(lpOutPath, cchCount, pszPath);
CoTaskMemFree(pszPath);
return true;
}
return false;
}
//=============================================================================
//
// PathGetModuleDirectory()
//
void PTHAPI PathGetAppDirectory(LPWSTR lpszDest, DWORD cchDest)
{
GetModuleFileName(NULL, lpszDest, cchDest);
PathRemoveFileSpec(lpszDest);
PathCanonicalizeEx(lpszDest, cchDest);
}
//=============================================================================
//
// PathRelativeToApp()
//
void PTHAPI PathRelativeToApp(
LPWSTR lpszSrc, LPWSTR lpszDest, int cchDest, bool bSrcIsFile,
bool bUnexpandEnv, bool bUnexpandMyDocs)
{
WCHAR wchAppDir[MAX_PATH] = { L'\0' };
WCHAR wchWinDir[MAX_PATH] = { L'\0' };
WCHAR wchUserFiles[MAX_PATH] = { L'\0' };
WCHAR wchPath[MAX_PATH] = { L'\0' };
WCHAR wchResult[MAX_PATH] = { L'\0' };
DWORD dwAttrTo = (bSrcIsFile) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_DIRECTORY;
PathGetAppDirectory(wchAppDir, COUNTOF(wchAppDir));
(void)GetWindowsDirectory(wchWinDir, COUNTOF(wchWinDir));
GetKnownFolderPath(&FOLDERID_Documents, wchUserFiles, COUNTOF(wchUserFiles));
if (bUnexpandMyDocs &&
!PathIsRelative(lpszSrc) &&
!PathIsPrefix(wchUserFiles, wchAppDir) &&
PathIsPrefix(wchUserFiles, lpszSrc) &&
PathRelativePathTo(wchPath, wchUserFiles, FILE_ATTRIBUTE_DIRECTORY, lpszSrc, dwAttrTo)) {
StringCchCopy(wchUserFiles, COUNTOF(wchUserFiles), L"%CSIDL:MYDOCUMENTS%");
PathAppend(wchUserFiles, wchPath);
StringCchCopy(wchPath, COUNTOF(wchPath), wchUserFiles);
}
else if (PathIsRelative(lpszSrc) || PathCommonPrefix(wchAppDir, wchWinDir, NULL)) {
StringCchCopyN(wchPath, COUNTOF(wchPath), lpszSrc, COUNTOF(wchPath));
}
else {
if (!PathRelativePathTo(wchPath, wchAppDir, FILE_ATTRIBUTE_DIRECTORY, lpszSrc, dwAttrTo)) {
StringCchCopyN(wchPath, COUNTOF(wchPath), lpszSrc, COUNTOF(wchPath));
}
}
if (bUnexpandEnv) {
if (!PathUnExpandEnvStrings(wchPath, wchResult, COUNTOF(wchResult))) {
StringCchCopyN(wchResult, COUNTOF(wchResult), wchPath, COUNTOF(wchResult));
}
}
else {
StringCchCopyN(wchResult, COUNTOF(wchResult), wchPath, COUNTOF(wchResult));
}
int cchLen = (cchDest == 0) ? MAX_PATH : cchDest;
if (lpszDest == NULL || lpszSrc == lpszDest) {
StringCchCopyN(lpszSrc, cchLen, wchResult, cchLen);
}
else {
StringCchCopyN(lpszDest, cchLen, wchResult, cchLen);
}
}
//=============================================================================
//
// PathAbsoluteFromApp()
//
void PTHAPI PathAbsoluteFromApp(LPWSTR lpszSrc, LPWSTR lpszDest, int cchDest, bool bExpandEnv)
{
WCHAR wchPath[MAX_PATH] = { L'\0' };
WCHAR wchResult[MAX_PATH] = { L'\0' };
if (lpszSrc == NULL) {
ZeroMemory(lpszDest, (cchDest == 0) ? MAX_PATH : cchDest);
return;
}
if (StrCmpNI(lpszSrc, L"%CSIDL:MYDOCUMENTS%", CONSTSTRGLEN("%CSIDL:MYDOCUMENTS%")) == 0) {
GetKnownFolderPath(&FOLDERID_Documents, wchPath, COUNTOF(wchPath));
PathAppend(wchPath, lpszSrc + CONSTSTRGLEN("%CSIDL:MYDOCUMENTS%"));
}
else {
StringCchCopyN(wchPath, COUNTOF(wchPath), lpszSrc, COUNTOF(wchPath));
}
if (bExpandEnv) {
ExpandEnvironmentStringsEx(wchPath, COUNTOF(wchPath));
}
if (PathIsRelative(wchPath)) {
PathGetAppDirectory(wchResult, COUNTOF(wchResult));
PathAppend(wchResult, wchPath);
}
else {
StringCchCopyN(wchResult, COUNTOF(wchResult), wchPath, COUNTOF(wchPath));
}
PathCanonicalizeEx(wchResult, MAX_PATH);
if (PathGetDriveNumber(wchResult) != -1) {
CharUpperBuff(wchResult, 1);
}
if (lpszDest == NULL || lpszSrc == lpszDest) {
StringCchCopyN(lpszSrc, ((cchDest == 0) ? MAX_PATH : cchDest), wchResult, COUNTOF(wchResult));
}
else {
StringCchCopyN(lpszDest, ((cchDest == 0) ? MAX_PATH : cchDest), wchResult, COUNTOF(wchResult));
}
}
///////////////////////////////////////////////////////////////////////////////
//
//
// Name: PathIsLnkFile()
//
// Purpose: Determine whether pszPath is a Windows Shell Link File by
// comparing the filename extension with L".lnk"
//
// Manipulates:
//
bool PTHAPI PathIsLnkFile(LPCWSTR pszPath)
{
WCHAR tchResPath[MAX_PATH] = { L'\0' };
if (!pszPath || !*pszPath) {
return false;
}
if (_wcsicmp(PathFindExtension(pszPath), L".lnk") != 0) {
return false;
}
return PathGetLnkPath(pszPath, tchResPath, COUNTOF(tchResPath));
}
///////////////////////////////////////////////////////////////////////////////
//
//
// Name: PathGetLnkPath()
//
// Purpose: Try to get the path to which a lnk-file is linked
//
//
// Manipulates: pszResPath
//
bool PTHAPI PathGetLnkPath(LPCWSTR pszLnkFile, LPWSTR pszResPath, int cchResPath)
{
IShellLink* psl = NULL;
WIN32_FIND_DATA fd = { 0 };
bool bSucceeded = false;
if (SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL,
CLSCTX_INPROC_SERVER,
&IID_IShellLink, (void**)&psl))) {
IPersistFile* ppf = NULL;
if (SUCCEEDED(psl->lpVtbl->QueryInterface(psl, &IID_IPersistFile, (void**)&ppf))) {
WORD wsz[MAX_PATH] = { L'\0' };
/*MultiByteToWideCharEx(CP_ACP,MB_PRECOMPOSED,pszLnkFile,-1,wsz,MAX_PATH);*/
StringCchCopy(wsz, COUNTOF(wsz), pszLnkFile);
if (SUCCEEDED(ppf->lpVtbl->Load(ppf, wsz, STGM_READ))) {
if (NOERROR == psl->lpVtbl->GetPath(psl, pszResPath, cchResPath, &fd, 0)) {
bSucceeded = true;
}
}
ppf->lpVtbl->Release(ppf);
}
psl->lpVtbl->Release(psl);
}
// This additional check seems reasonable
if (StrIsEmptyW(pszResPath)) {
bSucceeded = false;
}
if (bSucceeded) {
PathCanonicalizeEx(pszResPath, cchResPath);
}
return (bSucceeded);
}
///////////////////////////////////////////////////////////////////////////////
//
//
// Name: PathIsLnkToDirectory()
//
// Purpose: Determine wheter pszPath is a Windows Shell Link File which
// refers to a directory
//
// Manipulates: pszResPath
//
bool PTHAPI PathIsLnkToDirectory(LPCWSTR pszPath, LPWSTR pszResPath, int cchResPath)
{
if (PathIsLnkFile(pszPath)) {
WCHAR tchResPath[MAX_PATH] = { L'\0' };
if (PathGetLnkPath(pszPath, tchResPath, sizeof(WCHAR) * COUNTOF(tchResPath))) {
if (PathIsDirectory(tchResPath)) {
StringCchCopyN(pszResPath, cchResPath, tchResPath, COUNTOF(tchResPath));
return (true);
}
}
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
//
//
// Name: PathCreateDeskLnk()
//
// Purpose: Modified to create a desktop link to Notepad2
//
// Manipulates:
//
bool PTHAPI PathCreateDeskLnk(LPCWSTR pszDocument, LPCWSTR pszDescription)
{
WCHAR tchExeFile[MAX_PATH] = { L'\0' };
WCHAR tchDocTemp[MAX_PATH] = { L'\0' };
WCHAR tchArguments[MAX_PATH + 16] = { L'\0' };
WCHAR tchLinkDir[MAX_PATH] = { L'\0' };
WCHAR tchLnkFileName[MAX_PATH] = { L'\0' };
IShellLink* psl;
bool bSucceeded = false;
BOOL fMustCopy;
if (StrIsEmptyW(pszDocument)) {
return true;
}
// init strings
GetModuleFileName(NULL, tchExeFile, COUNTOF(tchExeFile));
PathCanonicalizeEx(tchExeFile, COUNTOF(tchExeFile));
StringCchCopy(tchDocTemp, COUNTOF(tchDocTemp), pszDocument);
PathQuoteSpaces(tchDocTemp);
StringCchCopy(tchArguments, COUNTOF(tchArguments), L"-n ");
StringCchCat(tchArguments, COUNTOF(tchArguments), tchDocTemp);
GetKnownFolderPath(&FOLDERID_Desktop, tchLinkDir, COUNTOF(tchLinkDir));
// Try to construct a valid filename...
if (!SHGetNewLinkInfo(pszDocument, tchLinkDir, tchLnkFileName, &fMustCopy, SHGNLI_PREFIXNAME)) {
return false;
}
if (SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL,
CLSCTX_INPROC_SERVER,
&IID_IShellLink, (void**)&psl))) {
IPersistFile* ppf;
if (SUCCEEDED(psl->lpVtbl->QueryInterface(psl, &IID_IPersistFile, (void**)&ppf))) {
WORD wsz[MAX_PATH] = { L'\0' };
/*MultiByteToWideCharEx(CP_ACP,MB_PRECOMPOSED,tchLnkFileName,-1,wsz,MAX_PATH);*/
StringCchCopy(wsz, COUNTOF(wsz), tchLnkFileName);
psl->lpVtbl->SetPath(psl, tchExeFile);
psl->lpVtbl->SetArguments(psl, tchArguments);
psl->lpVtbl->SetDescription(psl, pszDescription);
if (SUCCEEDED(ppf->lpVtbl->Save(ppf, wsz, true))) {
bSucceeded = true;
}
ppf->lpVtbl->Release(ppf);
}
psl->lpVtbl->Release(psl);
}
return (bSucceeded);
}
///////////////////////////////////////////////////////////////////////////////
//
//
// Name: PathCreateFavLnk()
//
// Purpose: Modified to create a Notepad2 favorites link
//
// Manipulates:
//
bool PTHAPI PathCreateFavLnk(LPCWSTR pszName, LPCWSTR pszTarget, LPCWSTR pszDir)
{
WCHAR tchLnkFileName[MAX_PATH] = { L'\0' };
IShellLink* psl;
bool bSucceeded = false;
if (StrIsEmptyW(pszName)) {
return true;
}
StringCchCopy(tchLnkFileName, COUNTOF(tchLnkFileName), pszDir);
PathAppend(tchLnkFileName, pszName);
StringCchCat(tchLnkFileName, COUNTOF(tchLnkFileName), L".lnk");
if (PathIsExistingFile(tchLnkFileName)) {
return false;
}
if (SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL,
CLSCTX_INPROC_SERVER,
&IID_IShellLink, (void**)&psl))) {
IPersistFile* ppf;
if (SUCCEEDED(psl->lpVtbl->QueryInterface(psl, &IID_IPersistFile, (void**)&ppf))) {
WORD wsz[MAX_PATH] = { L'\0' };
/*MultiByteToWideCharEx(CP_ACP,MB_PRECOMPOSED,tchLnkFileName,-1,wsz,MAX_PATH);*/
StringCchCopy(wsz, COUNTOF(wsz), tchLnkFileName);
psl->lpVtbl->SetPath(psl, pszTarget);
if (SUCCEEDED(ppf->lpVtbl->Save(ppf, wsz, true))) {
bSucceeded = true;
}
ppf->lpVtbl->Release(ppf);
}
psl->lpVtbl->Release(psl);
}
return (bSucceeded);
}
//=============================================================================
//
// ExpandEnvironmentStringsEx()
//
void PTHAPI ExpandEnvironmentStringsEx(LPWSTR lpSrc, size_t cchSrc)
{
HSTRINGW hstr_exp = StrgCreate();
size_t const min_len = ExpandEnvironmentStringsW(lpSrc, NULL, 0);
LPWSTR exp_buf = StrgWriteAccessBuf(hstr_exp, min_len);
DWORD const cchexp = (DWORD)StrgGetAllocLength(hstr_exp);
if (ExpandEnvironmentStringsW(lpSrc, exp_buf, cchexp)) {
StrgSanitize(hstr_exp);
StringCchCopyNW(lpSrc, cchSrc, exp_buf, StrgGetAllocLength(hstr_exp));
}
StrgDestroy(hstr_exp);
}
//=============================================================================
//
// PathCanonicalizeEx()
//
bool PTHAPI PathCanonicalizeEx(LPWSTR lpszPath, size_t cchPath)
{
WCHAR filePath[MAX_PATH] = { L'\0' };
StringCchCopyN(filePath, COUNTOF(filePath), lpszPath, cchPath);
ExpandEnvironmentStringsEx(filePath, COUNTOF(filePath));
if (PathIsRelative(filePath)) {
WCHAR tchModule[MAX_PATH] = { L'\0' };
GetModuleFileName(NULL, tchModule, COUNTOF(tchModule));
PathRemoveFileSpec(tchModule);
PathAppend(tchModule, lpszPath);
StringCchCopyN(filePath, COUNTOF(filePath), tchModule, COUNTOF(tchModule));
}
return PathCanonicalize(lpszPath, filePath);
}
//=============================================================================
//
// GetLongPathNameEx()
//
DWORD PTHAPI GetLongPathNameEx(LPWSTR lpszPath, DWORD cchBuffer)
{
DWORD const dwRet = GetLongPathNameW(lpszPath, lpszPath, cchBuffer);
if (dwRet) {
if (PathGetDriveNumber(lpszPath) != -1) {
CharUpperBuff(lpszPath, 1);
}
}
return dwRet;
}
//=============================================================================
//
// _SHGetFileInfoEx()
//
// Return a default name when the file has been removed, and always append
// a filename extension
//
static DWORD_PTR _SHGetFileInfoEx(LPCWSTR pszPath, DWORD dwFileAttributes,
SHFILEINFO* psfi, UINT cbFileInfo, UINT uFlags)
{
if (PathIsExistingFile(pszPath)) {
DWORD_PTR dw = SHGetFileInfo(pszPath, dwFileAttributes, psfi, cbFileInfo, uFlags);
if (StringCchLenW(psfi->szDisplayName, COUNTOF(psfi->szDisplayName)) < StringCchLen(PathFindFileName(pszPath), MAX_PATH)) {
StringCchCat(psfi->szDisplayName, COUNTOF(psfi->szDisplayName), PathFindExtension(pszPath));
}
return (dw);
}
DWORD_PTR dw = SHGetFileInfo(pszPath, FILE_ATTRIBUTE_NORMAL, psfi, cbFileInfo, uFlags | SHGFI_USEFILEATTRIBUTES);
if (StringCchLenW(psfi->szDisplayName, COUNTOF(psfi->szDisplayName)) < StringCchLen(PathFindFileName(pszPath), MAX_PATH)) {
StringCchCat(psfi->szDisplayName, COUNTOF(psfi->szDisplayName), PathFindExtension(pszPath));
}
return (dw);
}
//=============================================================================
//
// PathResolveDisplayName()
//
void PTHAPI PathGetDisplayName(LPWSTR lpszDestPath, DWORD cchDestBuffer, LPCWSTR lpszSourcePath)
{
SHFILEINFO shfi;
UINT const shfi_size = (UINT)sizeof(SHFILEINFO);
ZeroMemory(&shfi, shfi_size);
if (_SHGetFileInfoEx(lpszSourcePath, FILE_ATTRIBUTE_NORMAL, &shfi, shfi_size, SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES)) {
StringCchCopy(lpszDestPath, cchDestBuffer, shfi.szDisplayName);
}
else {
StringCchCopy(lpszDestPath, cchDestBuffer, PathFindFileName(lpszSourcePath));
}
}
//=============================================================================
//
// NormalizePathEx()
//
DWORD PTHAPI NormalizePathEx(LPWSTR lpszPath, DWORD cchBuffer, LPCWSTR lpszWorkDir, bool bRealPath, bool bSearchPathIfRelative)
{
WCHAR tmpFilePath[MAX_PATH] = { L'\0' };
StringCchCopyN(tmpFilePath, COUNTOF(tmpFilePath), lpszPath, cchBuffer);
ExpandEnvironmentStringsEx(tmpFilePath, COUNTOF(tmpFilePath));
PathUnquoteSpaces(tmpFilePath);
if (PathIsRelative(tmpFilePath)) {
StringCchCopy(lpszPath, cchBuffer, lpszWorkDir);
PathAppend(lpszPath, tmpFilePath);
if (bSearchPathIfRelative) {
if (!PathIsExistingFile(lpszPath)) {
PathStripPath(tmpFilePath);
if (SearchPath(NULL, tmpFilePath, NULL, cchBuffer, lpszPath, NULL) == 0) {
StringCchCopy(lpszPath, cchBuffer, tmpFilePath);
}
}
}
}
else {
StringCchCopy(lpszPath, cchBuffer, tmpFilePath);
}
PathCanonicalizeEx(lpszPath, cchBuffer);
GetLongPathNameEx(lpszPath, cchBuffer);
if (PathIsLnkFile(lpszPath)) {
PathGetLnkPath(lpszPath, lpszPath, cchBuffer);
}
if (bRealPath) {
// get real path name (by zufuliu)
HANDLE hFile = CreateFile(lpszPath, // file to open
GENERIC_READ, // open for reading
FILE_SHARE_READ | FILE_SHARE_WRITE, // share anyway
NULL, // default security
OPEN_EXISTING, // existing file only
FILE_ATTRIBUTE_NORMAL, // normal file
NULL); // no attr. template
if (IS_VALID_HANDLE(hFile)) {
if (GetFinalPathNameByHandleW(hFile, tmpFilePath,
COUNTOF(tmpFilePath), FILE_NAME_OPENED) > 0) {
if (StrCmpN(tmpFilePath, L"\\\\?\\", 4) == 0) {
WCHAR* p = tmpFilePath + 4;
if (StrCmpN(p, L"UNC\\", 4) == 0) {
p += 2;
*p = L'\\';
}
StringCchCopy(lpszPath, cchBuffer, p);
}
}
CloseHandle(hFile);
}
}
return (DWORD)StringCchLen(lpszPath, cchBuffer);
}
// ============================================================================
// ============================================================================
const wchar_t* PTHAPI Path_Get(HPATHL hpth)
{
HSTRINGW hstr = ToHStrgW(hpth);
return StrgGet(hstr);
}
// ----------------------------------------------------------------------------
size_t PTHAPI Path_GetBufCount(HPATHL hpth)
{
HSTRINGW hstr = ToHStrgW(hpth);
return StrgGetAllocLength(hstr);
}
// ----------------------------------------------------------------------------
// get wchar buffer with at least MAX_PATH size
// TODO: get rid of this intermediate state handler
wchar_t *PTHAPI Path_AccessBuf(HPATHL hpth, size_t len)
{
HSTRINGW hstr = ToHStrgW(hpth);
return StrgWriteAccessBuf(hstr, max(len, MAX_PATH));
}
// ----------------------------------------------------------------------------