Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

308 lines
9.2 KiB

  1. #include "shellprv.h"
  2. #include "ids.h"
  3. #pragma hdrstop
  4. #include "isproc.h"
  5. // eventually expand this to do rename UI, right now it just picks a default name
  6. // in the destination namespace
  7. HRESULT QIThroughShellItem(IShellItem *psi, REFIID riid, void **ppv)
  8. {
  9. // todo: put this into shellitem
  10. *ppv = NULL;
  11. IShellFolder *psf;
  12. HRESULT hr = psi->BindToHandler(NULL, BHID_SFObject, IID_PPV_ARG(IShellFolder, &psf));
  13. if (SUCCEEDED(hr))
  14. {
  15. hr = psf->QueryInterface(riid, ppv);
  16. psf->Release();
  17. }
  18. return hr;
  19. }
  20. BOOL IsValidChar(WCHAR chTest, LPWSTR pszValid, LPWSTR pszInvalid)
  21. {
  22. return (!pszValid || StrChr(pszValid, chTest)) &&
  23. (!pszInvalid || !StrChr(pszInvalid, chTest));
  24. }
  25. const WCHAR c_rgSubstitutes[] = { '_', ' ', '~' };
  26. WCHAR GetValidSubstitute(LPWSTR pszValid, LPWSTR pszInvalid)
  27. {
  28. for (int i = 0; i < ARRAYSIZE(c_rgSubstitutes); i++)
  29. {
  30. if (IsValidChar(c_rgSubstitutes[i], pszValid, pszInvalid))
  31. {
  32. return c_rgSubstitutes[i];
  33. }
  34. }
  35. return 0;
  36. }
  37. HRESULT CheckCharsAndReplaceIfNecessary(IItemNameLimits *pinl, LPWSTR psz)
  38. {
  39. // returns S_OK if no chars replaced, S_FALSE otherwise
  40. HRESULT hr = S_OK;
  41. LPWSTR pszValid, pszInvalid;
  42. if (SUCCEEDED(pinl->GetValidCharacters(&pszValid, &pszInvalid)))
  43. {
  44. WCHAR chSubs = GetValidSubstitute(pszValid, pszInvalid);
  45. int iSrc = 0, iDest = 0;
  46. while (psz[iSrc] != 0)
  47. {
  48. if (IsValidChar(psz[iSrc], pszValid, pszInvalid))
  49. {
  50. // use the char itself if it's valid
  51. psz[iDest] = psz[iSrc];
  52. iDest++;
  53. }
  54. else
  55. {
  56. // mark that we replaced a char
  57. hr = S_FALSE;
  58. if (chSubs)
  59. {
  60. // use a substitute if available
  61. psz[iDest] = chSubs;
  62. iDest++;
  63. }
  64. // else no valid char, just skip it
  65. }
  66. iSrc++;
  67. }
  68. psz[iDest] = 0;
  69. if (pszValid)
  70. CoTaskMemFree(pszValid);
  71. if (pszInvalid)
  72. CoTaskMemFree(pszInvalid);
  73. }
  74. return hr;
  75. }
  76. HRESULT BreakOutString(LPCWSTR psz, LPWSTR *ppszFilespec, LPWSTR *ppszExt)
  77. {
  78. // todo: detect the (2) in "New Text Document (2).txt" and reduce the filespec
  79. // accordingly to prevent "(1) (1)" etc. in multiple copies
  80. *ppszFilespec = NULL;
  81. *ppszExt = NULL;
  82. LPWSTR pszExt = PathFindExtension(psz);
  83. // make an empty string if necessary. this makes our logic simpler later instead of having to
  84. // handle the special case all the time.
  85. HRESULT hr = SHStrDup(pszExt ? pszExt : L"", ppszExt);
  86. if (SUCCEEDED(hr))
  87. {
  88. int iLenExt = lstrlen(*ppszExt);
  89. int cchBufFilespec = lstrlen(psz) - iLenExt + 1;
  90. *ppszFilespec = (LPWSTR)CoTaskMemAlloc(cchBufFilespec * sizeof(WCHAR));
  91. if (*ppszFilespec)
  92. {
  93. StrCpyN(*ppszFilespec, psz, cchBufFilespec);
  94. hr = S_OK;
  95. }
  96. else
  97. {
  98. hr = E_OUTOFMEMORY;
  99. }
  100. }
  101. if (FAILED(hr) && *ppszExt)
  102. {
  103. CoTaskMemFree(*ppszExt);
  104. *ppszExt = NULL;
  105. }
  106. ASSERT((SUCCEEDED(hr) && *ppszFilespec && *ppszExt) || (FAILED(hr) && !*ppszFilespec && !*ppszExt));
  107. return hr;
  108. }
  109. BOOL ItemExists(LPCWSTR pszName, IShellItem *psiDest)
  110. {
  111. BOOL fRet = FALSE;
  112. IBindCtx *pbc;
  113. HRESULT hr = BindCtx_CreateWithMode(STGX_MODE_READ, &pbc);
  114. if (SUCCEEDED(hr))
  115. {
  116. ITransferDest *pitd;
  117. hr = psiDest->BindToHandler(pbc, BHID_SFObject, IID_PPV_ARG(ITransferDest, &pitd));
  118. if (FAILED(hr))
  119. {
  120. hr = CreateStg2StgExWrapper(psiDest, NULL, &pitd);
  121. }
  122. if (SUCCEEDED(hr))
  123. {
  124. DWORD dwDummy;
  125. IUnknown *punk;
  126. hr = pitd->OpenElement(pszName, STGX_MODE_READ, &dwDummy, IID_PPV_ARG(IUnknown, &punk));
  127. if (SUCCEEDED(hr))
  128. {
  129. fRet = TRUE;
  130. punk->Release();
  131. }
  132. pitd->Release();
  133. }
  134. pbc->Release();
  135. }
  136. return fRet;
  137. }
  138. HRESULT BuildName(LPCWSTR pszFilespec, LPCWSTR pszExt, int iOrd, int iMaxLen, LPWSTR *ppszName)
  139. {
  140. // some things are hardcoded here like the " (%d)" stuff. this limitation is equivalent to
  141. // PathYetAnotherMakeUniqueName so we're okay.
  142. WCHAR szOrd[10];
  143. if (iOrd)
  144. {
  145. StringCchPrintf(szOrd, ARRAYSIZE(szOrd), L" (%d)", iOrd);
  146. }
  147. else
  148. {
  149. szOrd[0] = 0;
  150. }
  151. int iLenFilespecToUse = lstrlen(pszFilespec);
  152. int iLenOrdToUse = lstrlen(szOrd);
  153. int iLenExtToUse = lstrlen(pszExt);
  154. int iLenTotal = iLenFilespecToUse + iLenOrdToUse + iLenExtToUse;
  155. HRESULT hr = S_OK;
  156. if (iLenTotal > iMaxLen)
  157. {
  158. // first reduce the filespec since its less important than the extension
  159. iLenFilespecToUse = max(1, iLenFilespecToUse - (iLenTotal - iMaxLen));
  160. iLenTotal = iLenFilespecToUse + iLenOrdToUse + iLenExtToUse;
  161. if (iLenTotal > iMaxLen)
  162. {
  163. // next zap the extension.
  164. iLenExtToUse = max(0, iLenExtToUse - (iLenTotal - iMaxLen));
  165. iLenTotal = iLenFilespecToUse + iLenOrdToUse + iLenExtToUse;
  166. if (iLenTotal > iMaxLen)
  167. {
  168. // now it's game over.
  169. iLenOrdToUse = max(0, iLenOrdToUse - (iLenTotal - iMaxLen));
  170. iLenTotal = iLenFilespecToUse + iLenOrdToUse + iLenExtToUse;
  171. if (iLenTotal > iMaxLen)
  172. {
  173. hr = E_FAIL;
  174. }
  175. }
  176. }
  177. }
  178. if (SUCCEEDED(hr))
  179. {
  180. int cchBuf = iLenTotal + 1;
  181. *ppszName = (LPWSTR)CoTaskMemAlloc(cchBuf * sizeof(WCHAR));
  182. if (*ppszName)
  183. {
  184. StrCpyN(*ppszName, pszFilespec, iLenFilespecToUse + 1);
  185. StrCpyN(*ppszName + iLenFilespecToUse, szOrd, iLenOrdToUse + 1);
  186. StrCpyN(*ppszName + iLenFilespecToUse + iLenOrdToUse, pszExt, iLenExtToUse + 1);
  187. hr = S_OK;
  188. }
  189. else
  190. {
  191. hr = E_OUTOFMEMORY;
  192. }
  193. }
  194. return hr;
  195. }
  196. HRESULT FindUniqueName(LPCWSTR pszFilespec, LPCWSTR pszExt, int iMaxLen, IShellItem *psiDest, LPWSTR *ppszName)
  197. {
  198. *ppszName = NULL;
  199. HRESULT hr = E_FAIL;
  200. BOOL fFound = FALSE;
  201. for (int i = 0; !fFound && (i < 1000); i++)
  202. {
  203. LPWSTR pszBuf;
  204. if (SUCCEEDED(BuildName(pszFilespec, pszExt, i, iMaxLen, &pszBuf)))
  205. {
  206. if (!ItemExists(pszBuf, psiDest))
  207. {
  208. fFound = TRUE;
  209. hr = S_OK;
  210. *ppszName = pszBuf;
  211. }
  212. else
  213. {
  214. CoTaskMemFree(pszBuf);
  215. }
  216. }
  217. }
  218. ASSERT((SUCCEEDED(hr) && *ppszName) || (FAILED(hr) && !*ppszName));
  219. return hr;
  220. }
  221. HRESULT AutoCreateName(IShellItem *psiDest, IShellItem *psi, LPWSTR *ppszName)
  222. {
  223. *ppszName = NULL;
  224. LPWSTR pszOrigName;
  225. HRESULT hr = psi->GetDisplayName(SIGDN_PARENTRELATIVEFORADDRESSBAR, &pszOrigName);
  226. if (SUCCEEDED(hr))
  227. {
  228. IItemNameLimits *pinl;
  229. if (SUCCEEDED(QIThroughShellItem(psiDest, IID_PPV_ARG(IItemNameLimits, &pinl))))
  230. {
  231. int iMaxLen;
  232. if (FAILED(pinl->GetMaxLength(pszOrigName, &iMaxLen)))
  233. {
  234. // assume this for now in case of failure
  235. iMaxLen = MAX_PATH;
  236. }
  237. if (S_OK != CheckCharsAndReplaceIfNecessary(pinl, pszOrigName) ||
  238. lstrlen(pszOrigName) > iMaxLen)
  239. {
  240. // only if it started as an illegal name do we retry and provide uniqueness.
  241. // (if its legal then leave it as non-unique so callers can do their confirm overwrite code).
  242. LPWSTR pszFilespec, pszExt;
  243. hr = BreakOutString(pszOrigName, &pszFilespec, &pszExt);
  244. if (SUCCEEDED(hr))
  245. {
  246. hr = FindUniqueName(pszFilespec, pszExt, iMaxLen, psiDest, ppszName);
  247. CoTaskMemFree(pszFilespec);
  248. CoTaskMemFree(pszExt);
  249. }
  250. }
  251. else
  252. {
  253. // the name is okay so let it go through
  254. hr = S_OK;
  255. *ppszName = pszOrigName;
  256. pszOrigName = NULL;
  257. }
  258. pinl->Release();
  259. }
  260. else
  261. {
  262. // if the destination namespace doesn't have an IItemNameLimits then assume the
  263. // name is all good. we're not going to probe or anything so this is the best
  264. // we can do.
  265. hr = S_OK;
  266. *ppszName = pszOrigName;
  267. pszOrigName = NULL;
  268. }
  269. if (pszOrigName)
  270. {
  271. CoTaskMemFree(pszOrigName);
  272. }
  273. }
  274. if (FAILED(hr) && *ppszName)
  275. {
  276. CoTaskMemFree(*ppszName);
  277. *ppszName = NULL;
  278. }
  279. ASSERT((SUCCEEDED(hr) && *ppszName) || (FAILED(hr) && !*ppszName));
  280. return hr;
  281. }