windows-nt/Source/XPSP1/NT/shell/shell32/nametrans.cpp
2020-09-26 16:20:57 +08:00

309 lines
8.9 KiB
C++

#include "shellprv.h"
#include "ids.h"
#pragma hdrstop
#include "isproc.h"
// eventually expand this to do rename UI, right now it just picks a default name
// in the destination namespace
HRESULT QIThroughShellItem(IShellItem *psi, REFIID riid, void **ppv)
{
// todo: put this into shellitem
*ppv = NULL;
IShellFolder *psf;
HRESULT hr = psi->BindToHandler(NULL, BHID_SFObject, IID_PPV_ARG(IShellFolder, &psf));
if (SUCCEEDED(hr))
{
hr = psf->QueryInterface(riid, ppv);
psf->Release();
}
return hr;
}
BOOL IsValidChar(WCHAR chTest, LPWSTR pszValid, LPWSTR pszInvalid)
{
return (!pszValid || StrChr(pszValid, chTest)) &&
(!pszInvalid || !StrChr(pszInvalid, chTest));
}
const WCHAR c_rgSubstitutes[] = { '_', ' ', '~' };
WCHAR GetValidSubstitute(LPWSTR pszValid, LPWSTR pszInvalid)
{
for (int i = 0; i < ARRAYSIZE(c_rgSubstitutes); i++)
{
if (IsValidChar(c_rgSubstitutes[i], pszValid, pszInvalid))
{
return c_rgSubstitutes[i];
}
}
return 0;
}
HRESULT CheckCharsAndReplaceIfNecessary(IItemNameLimits *pinl, LPWSTR psz)
{
// returns S_OK if no chars replaced, S_FALSE otherwise
HRESULT hr = S_OK;
LPWSTR pszValid, pszInvalid;
if (SUCCEEDED(pinl->GetValidCharacters(&pszValid, &pszInvalid)))
{
WCHAR chSubs = GetValidSubstitute(pszValid, pszInvalid);
int iSrc = 0, iDest = 0;
while (psz[iSrc] != 0)
{
if (IsValidChar(psz[iSrc], pszValid, pszInvalid))
{
// use the char itself if it's valid
psz[iDest] = psz[iSrc];
iDest++;
}
else
{
// mark that we replaced a char
hr = S_FALSE;
if (chSubs)
{
// use a substitute if available
psz[iDest] = chSubs;
iDest++;
}
// else no valid char, just skip it
}
iSrc++;
}
psz[iDest] = 0;
if (pszValid)
CoTaskMemFree(pszValid);
if (pszInvalid)
CoTaskMemFree(pszInvalid);
}
return hr;
}
HRESULT BreakOutString(LPCWSTR psz, LPWSTR *ppszFilespec, LPWSTR *ppszExt)
{
// todo: detect the (2) in "New Text Document (2).txt" and reduce the filespec
// accordingly to prevent "(1) (1)" etc. in multiple copies
*ppszFilespec = NULL;
*ppszExt = NULL;
LPWSTR pszExt = PathFindExtension(psz);
// make an empty string if necessary. this makes our logic simpler later instead of having to
// handle the special case all the time.
HRESULT hr = SHStrDup(pszExt ? pszExt : L"", ppszExt);
if (SUCCEEDED(hr))
{
int iLenExt = lstrlen(*ppszExt);
int cchBufFilespec = lstrlen(psz) - iLenExt + 1;
*ppszFilespec = (LPWSTR)CoTaskMemAlloc(cchBufFilespec * sizeof(WCHAR));
if (*ppszFilespec)
{
lstrcpyn(*ppszFilespec, psz, cchBufFilespec);
hr = S_OK;
}
else
{
hr = E_OUTOFMEMORY;
}
}
if (FAILED(hr) && *ppszExt)
{
CoTaskMemFree(*ppszExt);
*ppszExt = NULL;
}
ASSERT((SUCCEEDED(hr) && *ppszFilespec && *ppszExt) || (FAILED(hr) && !*ppszFilespec && !*ppszExt));
return hr;
}
BOOL ItemExists(LPCWSTR pszName, IShellItem *psiDest)
{
BOOL fRet = FALSE;
IBindCtx *pbc;
HRESULT hr = BindCtx_CreateWithMode(STGX_MODE_READ, &pbc);
if (SUCCEEDED(hr))
{
ITransferDest *pitd;
hr = psiDest->BindToHandler(pbc, BHID_SFObject, IID_PPV_ARG(ITransferDest, &pitd));
if (FAILED(hr))
{
hr = CreateStg2StgExWrapper(psiDest, NULL, &pitd);
}
if (SUCCEEDED(hr))
{
DWORD dwDummy;
IUnknown *punk;
hr = pitd->OpenElement(pszName, STGX_MODE_READ, &dwDummy, IID_PPV_ARG(IUnknown, &punk));
if (SUCCEEDED(hr))
{
fRet = TRUE;
punk->Release();
}
pitd->Release();
}
pbc->Release();
}
return fRet;
}
HRESULT BuildName(LPCWSTR pszFilespec, LPCWSTR pszExt, int iOrd, int iMaxLen, LPWSTR *ppszName)
{
// some things are hardcoded here like the " (%d)" stuff. this limitation is equivalent to
// PathYetAnotherMakeUniqueName so we're okay.
WCHAR szOrd[10];
if (iOrd)
{
wnsprintf(szOrd, ARRAYSIZE(szOrd), L" (%d)", iOrd);
}
else
{
szOrd[0] = 0;
}
int iLenFilespecToUse = lstrlen(pszFilespec);
int iLenOrdToUse = lstrlen(szOrd);
int iLenExtToUse = lstrlen(pszExt);
int iLenTotal = iLenFilespecToUse + iLenOrdToUse + iLenExtToUse;
HRESULT hr = S_OK;
if (iLenTotal > iMaxLen)
{
// first reduce the filespec since its less important than the extension
iLenFilespecToUse = max(1, iLenFilespecToUse - (iLenTotal - iMaxLen));
iLenTotal = iLenFilespecToUse + iLenOrdToUse + iLenExtToUse;
if (iLenTotal > iMaxLen)
{
// next zap the extension.
iLenExtToUse = max(0, iLenExtToUse - (iLenTotal - iMaxLen));
iLenTotal = iLenFilespecToUse + iLenOrdToUse + iLenExtToUse;
if (iLenTotal > iMaxLen)
{
// now it's game over.
iLenOrdToUse = max(0, iLenOrdToUse - (iLenTotal - iMaxLen));
iLenTotal = iLenFilespecToUse + iLenOrdToUse + iLenExtToUse;
if (iLenTotal > iMaxLen)
{
hr = E_FAIL;
}
}
}
}
if (SUCCEEDED(hr))
{
int cchBuf = iLenTotal + 1;
*ppszName = (LPWSTR)CoTaskMemAlloc(cchBuf * sizeof(WCHAR));
if (*ppszName)
{
lstrcpyn(*ppszName, pszFilespec, iLenFilespecToUse + 1);
lstrcpyn(*ppszName + iLenFilespecToUse, szOrd, iLenOrdToUse + 1);
lstrcpyn(*ppszName + iLenFilespecToUse + iLenOrdToUse, pszExt, iLenExtToUse + 1);
hr = S_OK;
}
else
{
hr = E_OUTOFMEMORY;
}
}
return hr;
}
HRESULT FindUniqueName(LPCWSTR pszFilespec, LPCWSTR pszExt, int iMaxLen, IShellItem *psiDest, LPWSTR *ppszName)
{
*ppszName = NULL;
HRESULT hr = E_FAIL;
BOOL fFound = FALSE;
for (int i = 0; !fFound && (i < 1000); i++)
{
LPWSTR pszBuf;
if (SUCCEEDED(BuildName(pszFilespec, pszExt, i, iMaxLen, &pszBuf)))
{
if (!ItemExists(pszBuf, psiDest))
{
fFound = TRUE;
hr = S_OK;
*ppszName = pszBuf;
}
else
{
CoTaskMemFree(pszBuf);
}
}
}
ASSERT((SUCCEEDED(hr) && *ppszName) || (FAILED(hr) && !*ppszName));
return hr;
}
HRESULT AutoCreateName(IShellItem *psiDest, IShellItem *psi, LPWSTR *ppszName)
{
*ppszName = NULL;
LPWSTR pszOrigName;
HRESULT hr = psi->GetDisplayName(SIGDN_PARENTRELATIVEFORADDRESSBAR, &pszOrigName);
if (SUCCEEDED(hr))
{
IItemNameLimits *pinl;
if (SUCCEEDED(QIThroughShellItem(psiDest, IID_PPV_ARG(IItemNameLimits, &pinl))))
{
int iMaxLen;
if (FAILED(pinl->GetMaxLength(pszOrigName, &iMaxLen)))
{
// assume this for now in case of failure
iMaxLen = MAX_PATH;
}
if (S_OK != CheckCharsAndReplaceIfNecessary(pinl, pszOrigName) ||
lstrlen(pszOrigName) > iMaxLen)
{
// only if it started as an illegal name do we retry and provide uniqueness.
// (if its legal then leave it as non-unique so callers can do their confirm overwrite code).
LPWSTR pszFilespec, pszExt;
hr = BreakOutString(pszOrigName, &pszFilespec, &pszExt);
if (SUCCEEDED(hr))
{
hr = FindUniqueName(pszFilespec, pszExt, iMaxLen, psiDest, ppszName);
CoTaskMemFree(pszFilespec);
CoTaskMemFree(pszExt);
}
}
else
{
// the name is okay so let it go through
hr = S_OK;
*ppszName = pszOrigName;
pszOrigName = NULL;
}
pinl->Release();
}
else
{
// if the destination namespace doesn't have an IItemNameLimits then assume the
// name is all good. we're not going to probe or anything so this is the best
// we can do.
hr = S_OK;
*ppszName = pszOrigName;
pszOrigName = NULL;
}
if (pszOrigName)
{
CoTaskMemFree(pszOrigName);
}
}
if (FAILED(hr) && *ppszName)
{
CoTaskMemFree(*ppszName);
*ppszName = NULL;
}
ASSERT((SUCCEEDED(hr) && *ppszName) || (FAILED(hr) && !*ppszName));
return hr;
}