Fix crash on Win32 Namespace prefixed file name
Implement support for Win32 Namespace prefixed file name in Notepad++. (Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces) Support the Win32-filenames escaped by \\?\ or \\?\UNC\, possible globbing in filenames (\\?\C:\fil?.txt) and shell links (\\?\C:\file.txt.lnk) included. Unsupported (temporarily - it needs further patches for Notepad++): - any raw filename with length exceeding the MAX_PATH. - any nonstandard Windows OS filename: with 'dot' or 'space' char(s) at the name end, WinOS reserved ones: AUX, CON, PRN, NUL, COM1-9, LPT1-9 and the ones with invalid ASCII chars in it (0-31, <, >, | , "). Fix #12453, close #12613pull/12641/head
parent
ee336b24c1
commit
4f1aa7b004
|
@ -1519,6 +1519,109 @@ HFONT createFont(const TCHAR* fontName, int fontSize, bool isBold, HWND hDestPar
|
|||
return newFont;
|
||||
}
|
||||
|
||||
// "For file I/O, the "\\?\" prefix to a path string tells the Windows APIs to disable all string parsing
|
||||
// and to send the string that follows it straight to the file system..."
|
||||
// Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces
|
||||
bool isWin32NamespacePrefixedFileName(const generic_string& fileName)
|
||||
{
|
||||
// TODO:
|
||||
// ?! how to handle similar NT Object Manager path style prefix case \??\...
|
||||
// (the \??\ prefix instructs the NT Object Manager to search in the caller's local device directory for an alias...)
|
||||
|
||||
// the following covers the \\?\... raw Win32-filenames or the \\?\UNC\... UNC equivalents
|
||||
// and also its *nix like forward slash equivalents
|
||||
return (fileName.starts_with(TEXT("\\\\?\\")) || fileName.starts_with(TEXT("//?/")));
|
||||
}
|
||||
|
||||
bool isWin32NamespacePrefixedFileName(const TCHAR* szFileName)
|
||||
{
|
||||
const generic_string fileName = szFileName;
|
||||
return isWin32NamespacePrefixedFileName(fileName);
|
||||
}
|
||||
|
||||
bool isUnsupportedFileName(const generic_string& fileName)
|
||||
{
|
||||
bool isUnsupported = true;
|
||||
|
||||
// until the N++ (and its plugins) will not be prepared for filenames longer than the MAX_PATH,
|
||||
// we have to limit also the maximum supported length below
|
||||
if ((fileName.size() > 0) && (fileName.size() < MAX_PATH))
|
||||
{
|
||||
// possible raw filenames can contain space(s) or dot(s) at its end (e.g. "\\?\C:\file."), but the N++ advanced
|
||||
// Open/SaveAs IFileOpenDialog/IFileSaveDialog COM-interface based dialogs currently do not handle this well
|
||||
// (but e.g. direct N++ Ctrl+S works ok even with these filenames)
|
||||
if (!fileName.ends_with(_T('.')) && !fileName.ends_with(_T(' ')))
|
||||
{
|
||||
bool invalidASCIIChar = false;
|
||||
|
||||
for (size_t pos = 0; pos < fileName.size(); ++pos)
|
||||
{
|
||||
TCHAR c = fileName.at(pos);
|
||||
if (c <= 31)
|
||||
{
|
||||
invalidASCIIChar = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// as this could be also a complete filename with path and there could be also a globbing used,
|
||||
// we tolerate here some other reserved Win32-filename chars: /, \, :, ?, *
|
||||
switch (c)
|
||||
{
|
||||
case '<':
|
||||
case '>':
|
||||
case '"':
|
||||
case '|':
|
||||
invalidASCIIChar = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidASCIIChar)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!invalidASCIIChar)
|
||||
{
|
||||
// strip input string to a filename without a possible path and extension(s)
|
||||
generic_string fileNameOnly;
|
||||
size_t pos = fileName.find_first_of(TEXT("."));
|
||||
if (pos != std::string::npos)
|
||||
fileNameOnly = fileName.substr(0, pos);
|
||||
else
|
||||
fileNameOnly = fileName;
|
||||
|
||||
pos = fileNameOnly.find_last_of(TEXT("\\"));
|
||||
if (pos == std::string::npos)
|
||||
pos = fileNameOnly.find_last_of(TEXT("/"));
|
||||
if (pos != std::string::npos)
|
||||
fileNameOnly = fileNameOnly.substr(pos + 1);
|
||||
|
||||
const std::vector<generic_string> reservedWin32NamespaceDeviceList{
|
||||
TEXT("CON"), TEXT("PRN"), TEXT("AUX"), TEXT("NUL"),
|
||||
TEXT("COM1"), TEXT("COM2"), TEXT("COM3"), TEXT("COM4"), TEXT("COM5"), TEXT("COM6"), TEXT("COM7"), TEXT("COM8"), TEXT("COM9"),
|
||||
TEXT("LPT1"), TEXT("LPT2"), TEXT("LPT3"), TEXT("LPT4"), TEXT("LPT5"), TEXT("LPT6"), TEXT("LPT7"), TEXT("LPT8"), TEXT("LPT9")
|
||||
};
|
||||
|
||||
// last check is for all the old reserved Windows OS filenames
|
||||
if (std::find(reservedWin32NamespaceDeviceList.begin(), reservedWin32NamespaceDeviceList.end(), fileNameOnly) == reservedWin32NamespaceDeviceList.end())
|
||||
{
|
||||
// ok, the current filename tested is not even on the blacklist
|
||||
isUnsupported = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isUnsupported;
|
||||
}
|
||||
|
||||
bool isUnsupportedFileName(const TCHAR* szFileName)
|
||||
{
|
||||
const generic_string fileName = szFileName;
|
||||
return isUnsupportedFileName(fileName);
|
||||
}
|
||||
|
||||
|
||||
Version::Version(const generic_string& versionStr)
|
||||
{
|
||||
try {
|
||||
|
|
|
@ -232,6 +232,11 @@ generic_string getDateTimeStrFrom(const generic_string& dateTimeFormat, const SY
|
|||
|
||||
HFONT createFont(const TCHAR* fontName, int fontSize, bool isBold, HWND hDestParent);
|
||||
|
||||
bool isWin32NamespacePrefixedFileName(const generic_string& fileName);
|
||||
bool isWin32NamespacePrefixedFileName(const TCHAR* szFileName);
|
||||
bool isUnsupportedFileName(const generic_string& fileName);
|
||||
bool isUnsupportedFileName(const TCHAR* szFileName);
|
||||
|
||||
class Version final
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "fileBrowser.h"
|
||||
#include <tchar.h>
|
||||
#include <unordered_set>
|
||||
#include "Common.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -120,8 +121,10 @@ DWORD WINAPI Notepad_plus::monitorFileOnChange(void * params)
|
|||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void resolveLinkFile(generic_string& linkFilePath)
|
||||
bool resolveLinkFile(generic_string& linkFilePath)
|
||||
{
|
||||
bool isResolved = false;
|
||||
|
||||
IShellLink* psl;
|
||||
WCHAR targetFilePath[MAX_PATH];
|
||||
WIN32_FIND_DATA wfd = {};
|
||||
|
@ -149,6 +152,7 @@ void resolveLinkFile(generic_string& linkFilePath)
|
|||
if (SUCCEEDED(hres) && hres != S_FALSE)
|
||||
{
|
||||
linkFilePath = targetFilePath;
|
||||
isResolved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,6 +162,8 @@ void resolveLinkFile(generic_string& linkFilePath)
|
|||
}
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
return isResolved;
|
||||
}
|
||||
|
||||
BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive, bool isReadOnly, int encoding, const TCHAR *backupFileName, FILETIME fileNameTimestamp)
|
||||
|
@ -167,30 +173,75 @@ BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive,
|
|||
return BUFFER_INVALID;
|
||||
|
||||
generic_string targetFileName = fileName;
|
||||
resolveLinkFile(targetFileName);
|
||||
bool isResolvedLinkFileName = resolveLinkFile(targetFileName);
|
||||
|
||||
bool isRawFileName;
|
||||
if (isResolvedLinkFileName)
|
||||
isRawFileName = false;
|
||||
else
|
||||
isRawFileName = isWin32NamespacePrefixedFileName(fileName);
|
||||
|
||||
if (isUnsupportedFileName(isResolvedLinkFileName ? targetFileName : fileName))
|
||||
{
|
||||
// TODO:
|
||||
// for the raw filenames we can allow even the usually unsupported filenames in the future,
|
||||
// but not now as it is not fully supported by the N++ COM IFileDialog based Open/SaveAs dialogs
|
||||
//if (isRawFileName)
|
||||
//{
|
||||
// int answer = _nativeLangSpeaker.messageBox("OpenNonconformingWin32FileName",
|
||||
// _pPublicInterface->getHSelf(),
|
||||
// TEXT("You are about to open a file with unusual filename:\n\"$STR_REPLACE$\""),
|
||||
// TEXT("Open Nonconforming Win32-Filename"),
|
||||
// MB_OKCANCEL | MB_ICONWARNING | MB_APPLMODAL,
|
||||
// 0,
|
||||
// isResolvedLinkFileName ? targetFileName.c_str() : fileName.c_str());
|
||||
// if (answer != IDOK)
|
||||
// return BUFFER_INVALID; // aborted by user
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// unsupported, use the existing N++ file dialog to report
|
||||
_nativeLangSpeaker.messageBox("OpenFileError",
|
||||
_pPublicInterface->getHSelf(),
|
||||
TEXT("Cannot open file \"$STR_REPLACE$\"."),
|
||||
TEXT("ERROR"),
|
||||
MB_OK,
|
||||
0,
|
||||
isResolvedLinkFileName ? targetFileName.c_str() : fileName.c_str());
|
||||
return BUFFER_INVALID;
|
||||
//}
|
||||
}
|
||||
|
||||
//If [GetFullPathName] succeeds, the return value is the length, in TCHARs, of the string copied to lpBuffer, not including the terminating null character.
|
||||
//If the lpBuffer buffer is too small to contain the path, the return value [of GetFullPathName] is the size, in TCHARs, of the buffer that is required to hold the path and the terminating null character.
|
||||
//If [GetFullPathName] fails for any other reason, the return value is zero.
|
||||
|
||||
NppParameters& nppParam = NppParameters::getInstance();
|
||||
TCHAR longFileName[longFileNameBufferSize];
|
||||
WCHAR longFileName[longFileNameBufferSize] = { 0 };
|
||||
|
||||
const DWORD getFullPathNameResult = ::GetFullPathName(targetFileName.c_str(), longFileNameBufferSize, longFileName, NULL);
|
||||
if (getFullPathNameResult == 0)
|
||||
if (isRawFileName)
|
||||
{
|
||||
return BUFFER_INVALID;
|
||||
// use directly the raw file name, skip the GetFullPathName WINAPI and alike...)
|
||||
wcsncpy_s(longFileName, _countof(longFileName), fileName.c_str(), _TRUNCATE);
|
||||
}
|
||||
if (getFullPathNameResult > longFileNameBufferSize)
|
||||
else
|
||||
{
|
||||
return BUFFER_INVALID;
|
||||
}
|
||||
assert(_tcslen(longFileName) == getFullPathNameResult);
|
||||
const DWORD getFullPathNameResult = ::GetFullPathName(targetFileName.c_str(), longFileNameBufferSize, longFileName, NULL);
|
||||
if (getFullPathNameResult == 0)
|
||||
{
|
||||
return BUFFER_INVALID;
|
||||
}
|
||||
if (getFullPathNameResult > longFileNameBufferSize)
|
||||
{
|
||||
return BUFFER_INVALID;
|
||||
}
|
||||
assert(wcslen(longFileName) == getFullPathNameResult);
|
||||
|
||||
if (_tcschr(longFileName, '~'))
|
||||
{
|
||||
// ignore the returned value of function due to win64 redirection system
|
||||
::GetLongPathName(longFileName, longFileName, longFileNameBufferSize);
|
||||
if (wcschr(longFileName, '~'))
|
||||
{
|
||||
// ignore the returned value of function due to win64 redirection system
|
||||
::GetLongPathName(longFileName, longFileName, longFileNameBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
bool isSnapshotMode = backupFileName != NULL && PathFileExists(backupFileName);
|
||||
|
@ -257,7 +308,11 @@ BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive,
|
|||
isWow64Off = true;
|
||||
}
|
||||
|
||||
bool globbing = wcsrchr(longFileName, TCHAR('*')) || wcsrchr(longFileName, TCHAR('?'));
|
||||
bool globbing;
|
||||
if (isRawFileName)
|
||||
globbing = (wcsrchr(longFileName, TCHAR('*')) || (abs(longFileName - wcsrchr(longFileName, TCHAR('?'))) > 3));
|
||||
else
|
||||
globbing = (wcsrchr(longFileName, TCHAR('*')) || wcsrchr(longFileName, TCHAR('?')));
|
||||
|
||||
if (!isSnapshotMode) // if not backup mode, or backupfile path is invalid
|
||||
{
|
||||
|
|
|
@ -717,11 +717,19 @@ BufferID FileManager::loadFile(const TCHAR* filename, Document doc, int encoding
|
|||
ownDoc = true;
|
||||
}
|
||||
|
||||
TCHAR fullpath[MAX_PATH];
|
||||
::GetFullPathName(filename, MAX_PATH, fullpath, NULL);
|
||||
if (_tcschr(fullpath, '~'))
|
||||
WCHAR fullpath[MAX_PATH] = { 0 };
|
||||
if (isWin32NamespacePrefixedFileName(filename))
|
||||
{
|
||||
::GetLongPathName(fullpath, fullpath, MAX_PATH);
|
||||
// use directly the raw file name, skip the GetFullPathName WINAPI
|
||||
wcsncpy_s(fullpath, _countof(fullpath), filename, _TRUNCATE);
|
||||
}
|
||||
else
|
||||
{
|
||||
::GetFullPathName(filename, MAX_PATH, fullpath, NULL);
|
||||
if (wcschr(fullpath, '~'))
|
||||
{
|
||||
::GetLongPathName(fullpath, fullpath, MAX_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
bool isSnapshotMode = backupFileName != NULL && PathFileExists(backupFileName);
|
||||
|
@ -1116,11 +1124,19 @@ SavingStatus FileManager::saveBuffer(BufferID id, const TCHAR * filename, bool i
|
|||
bool isHiddenOrSys = false;
|
||||
DWORD attrib = 0;
|
||||
|
||||
TCHAR fullpath[MAX_PATH];
|
||||
::GetFullPathName(filename, MAX_PATH, fullpath, NULL);
|
||||
if (_tcschr(fullpath, '~'))
|
||||
WCHAR fullpath[MAX_PATH] = { 0 };
|
||||
if (isWin32NamespacePrefixedFileName(filename))
|
||||
{
|
||||
::GetLongPathName(fullpath, fullpath, MAX_PATH);
|
||||
// use directly the raw file name, skip the GetFullPathName WINAPI
|
||||
wcsncpy_s(fullpath, _countof(fullpath), filename, _TRUNCATE);
|
||||
}
|
||||
else
|
||||
{
|
||||
::GetFullPathName(filename, MAX_PATH, fullpath, NULL);
|
||||
if (wcschr(fullpath, '~'))
|
||||
{
|
||||
::GetLongPathName(fullpath, fullpath, MAX_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
if (PathFileExists(fullpath))
|
||||
|
|
Loading…
Reference in New Issue