mirror of
https://github.com/notepad-plus-plus/notepad-plus-plus.git
synced 2025-07-27 07:44:24 +02:00
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 #12613
This commit is contained in:
parent
ee336b24c1
commit
4f1aa7b004
@ -1519,6 +1519,109 @@ HFONT createFont(const TCHAR* fontName, int fontSize, bool isBold, HWND hDestPar
|
|||||||
return newFont;
|
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)
|
Version::Version(const generic_string& versionStr)
|
||||||
{
|
{
|
||||||
try {
|
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);
|
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
|
class Version final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "fileBrowser.h"
|
#include "fileBrowser.h"
|
||||||
#include <tchar.h>
|
#include <tchar.h>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include "Common.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@ -120,8 +121,10 @@ DWORD WINAPI Notepad_plus::monitorFileOnChange(void * params)
|
|||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resolveLinkFile(generic_string& linkFilePath)
|
bool resolveLinkFile(generic_string& linkFilePath)
|
||||||
{
|
{
|
||||||
|
bool isResolved = false;
|
||||||
|
|
||||||
IShellLink* psl;
|
IShellLink* psl;
|
||||||
WCHAR targetFilePath[MAX_PATH];
|
WCHAR targetFilePath[MAX_PATH];
|
||||||
WIN32_FIND_DATA wfd = {};
|
WIN32_FIND_DATA wfd = {};
|
||||||
@ -149,6 +152,7 @@ void resolveLinkFile(generic_string& linkFilePath)
|
|||||||
if (SUCCEEDED(hres) && hres != S_FALSE)
|
if (SUCCEEDED(hres) && hres != S_FALSE)
|
||||||
{
|
{
|
||||||
linkFilePath = targetFilePath;
|
linkFilePath = targetFilePath;
|
||||||
|
isResolved = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,6 +162,8 @@ void resolveLinkFile(generic_string& linkFilePath)
|
|||||||
}
|
}
|
||||||
CoUninitialize();
|
CoUninitialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return isResolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive, bool isReadOnly, int encoding, const TCHAR *backupFileName, FILETIME fileNameTimestamp)
|
BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive, bool isReadOnly, int encoding, const TCHAR *backupFileName, FILETIME fileNameTimestamp)
|
||||||
@ -167,15 +173,59 @@ BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive,
|
|||||||
return BUFFER_INVALID;
|
return BUFFER_INVALID;
|
||||||
|
|
||||||
generic_string targetFileName = fileName;
|
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 [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 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.
|
//If [GetFullPathName] fails for any other reason, the return value is zero.
|
||||||
|
|
||||||
NppParameters& nppParam = NppParameters::getInstance();
|
NppParameters& nppParam = NppParameters::getInstance();
|
||||||
TCHAR longFileName[longFileNameBufferSize];
|
WCHAR longFileName[longFileNameBufferSize] = { 0 };
|
||||||
|
|
||||||
|
if (isRawFileName)
|
||||||
|
{
|
||||||
|
// use directly the raw file name, skip the GetFullPathName WINAPI and alike...)
|
||||||
|
wcsncpy_s(longFileName, _countof(longFileName), fileName.c_str(), _TRUNCATE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
const DWORD getFullPathNameResult = ::GetFullPathName(targetFileName.c_str(), longFileNameBufferSize, longFileName, NULL);
|
const DWORD getFullPathNameResult = ::GetFullPathName(targetFileName.c_str(), longFileNameBufferSize, longFileName, NULL);
|
||||||
if (getFullPathNameResult == 0)
|
if (getFullPathNameResult == 0)
|
||||||
{
|
{
|
||||||
@ -185,13 +235,14 @@ BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive,
|
|||||||
{
|
{
|
||||||
return BUFFER_INVALID;
|
return BUFFER_INVALID;
|
||||||
}
|
}
|
||||||
assert(_tcslen(longFileName) == getFullPathNameResult);
|
assert(wcslen(longFileName) == getFullPathNameResult);
|
||||||
|
|
||||||
if (_tcschr(longFileName, '~'))
|
if (wcschr(longFileName, '~'))
|
||||||
{
|
{
|
||||||
// ignore the returned value of function due to win64 redirection system
|
// ignore the returned value of function due to win64 redirection system
|
||||||
::GetLongPathName(longFileName, longFileName, longFileNameBufferSize);
|
::GetLongPathName(longFileName, longFileName, longFileNameBufferSize);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool isSnapshotMode = backupFileName != NULL && PathFileExists(backupFileName);
|
bool isSnapshotMode = backupFileName != NULL && PathFileExists(backupFileName);
|
||||||
if (isSnapshotMode && !PathFileExists(longFileName)) // UNTITLED
|
if (isSnapshotMode && !PathFileExists(longFileName)) // UNTITLED
|
||||||
@ -257,7 +308,11 @@ BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive,
|
|||||||
isWow64Off = true;
|
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
|
if (!isSnapshotMode) // if not backup mode, or backupfile path is invalid
|
||||||
{
|
{
|
||||||
|
@ -717,12 +717,20 @@ BufferID FileManager::loadFile(const TCHAR* filename, Document doc, int encoding
|
|||||||
ownDoc = true;
|
ownDoc = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TCHAR fullpath[MAX_PATH];
|
WCHAR fullpath[MAX_PATH] = { 0 };
|
||||||
|
if (isWin32NamespacePrefixedFileName(filename))
|
||||||
|
{
|
||||||
|
// use directly the raw file name, skip the GetFullPathName WINAPI
|
||||||
|
wcsncpy_s(fullpath, _countof(fullpath), filename, _TRUNCATE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
::GetFullPathName(filename, MAX_PATH, fullpath, NULL);
|
::GetFullPathName(filename, MAX_PATH, fullpath, NULL);
|
||||||
if (_tcschr(fullpath, '~'))
|
if (wcschr(fullpath, '~'))
|
||||||
{
|
{
|
||||||
::GetLongPathName(fullpath, fullpath, MAX_PATH);
|
::GetLongPathName(fullpath, fullpath, MAX_PATH);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool isSnapshotMode = backupFileName != NULL && PathFileExists(backupFileName);
|
bool isSnapshotMode = backupFileName != NULL && PathFileExists(backupFileName);
|
||||||
if (isSnapshotMode && !PathFileExists(fullpath)) // if backup mode and fullpath doesn't exist, we guess is UNTITLED
|
if (isSnapshotMode && !PathFileExists(fullpath)) // if backup mode and fullpath doesn't exist, we guess is UNTITLED
|
||||||
@ -1116,12 +1124,20 @@ SavingStatus FileManager::saveBuffer(BufferID id, const TCHAR * filename, bool i
|
|||||||
bool isHiddenOrSys = false;
|
bool isHiddenOrSys = false;
|
||||||
DWORD attrib = 0;
|
DWORD attrib = 0;
|
||||||
|
|
||||||
TCHAR fullpath[MAX_PATH];
|
WCHAR fullpath[MAX_PATH] = { 0 };
|
||||||
|
if (isWin32NamespacePrefixedFileName(filename))
|
||||||
|
{
|
||||||
|
// use directly the raw file name, skip the GetFullPathName WINAPI
|
||||||
|
wcsncpy_s(fullpath, _countof(fullpath), filename, _TRUNCATE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
::GetFullPathName(filename, MAX_PATH, fullpath, NULL);
|
::GetFullPathName(filename, MAX_PATH, fullpath, NULL);
|
||||||
if (_tcschr(fullpath, '~'))
|
if (wcschr(fullpath, '~'))
|
||||||
{
|
{
|
||||||
::GetLongPathName(fullpath, fullpath, MAX_PATH);
|
::GetLongPathName(fullpath, fullpath, MAX_PATH);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (PathFileExists(fullpath))
|
if (PathFileExists(fullpath))
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user