Source code of Windows XP (NT5)
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.

598 lines
19 KiB

  1. //---------------------------------------------------------------------------
  2. //
  3. // Copyright (c) Microsoft Corporation
  4. //
  5. // File: findapp.cpp
  6. //
  7. // Implements hueristics to find the folder of an application
  8. //
  9. // History:
  10. // 2-17-98 by dli implemented FindAppFolder
  11. // 5-01-98 added lots of little functioins
  12. //------------------------------------------------------------------------
  13. #include "priv.h"
  14. // Do not build this file if on Win9X or NT4
  15. #ifndef DOWNLEVEL_PLATFORM
  16. #include "appwiz.h"
  17. #include "appsize.h"
  18. #include "findapp.h"
  19. #include "util.h"
  20. // Things to do:
  21. // 1. Move special strings into the RC file
  22. /*-------------------------------------------------------------------------
  23. Purpose: This function searches and returns the sub word (if one is found).
  24. pszStr is the big string, pszSrch is the candidate substring used
  25. in the search.
  26. Returns NULL if no subword is found.
  27. */
  28. LPCTSTR FindSubWord(LPCTSTR pszStr, LPCTSTR pszSrch)
  29. {
  30. LPCTSTR pszRet = NULL;
  31. LPCTSTR pszBegin = pszStr;
  32. // Search for the sub string from the beginning
  33. LPCTSTR pszSub;
  34. while (NULL != (pszSub = StrStrI(pszBegin, pszSrch)))
  35. {
  36. LPCTSTR pszPrev;
  37. LPCTSTR pszEnd = pszSub + lstrlen(pszSrch);
  38. // Is the previous character alphanumeric?
  39. if (pszSub != pszBegin)
  40. {
  41. ASSERT(pszSub > pszBegin);
  42. pszPrev = CharPrev(pszBegin, pszSub);
  43. ASSERT(pszPrev >= pszBegin);
  44. if (IsCharAlphaNumeric(*pszPrev))
  45. {
  46. // yes, go on searching
  47. pszBegin = pszEnd;
  48. continue;
  49. }
  50. }
  51. // Is the character after the sub string we found
  52. // alpha numeric?
  53. if (IsCharAlphaNumeric(*pszEnd))
  54. {
  55. // yes, go on searching
  56. pszBegin = pszEnd;
  57. continue;
  58. }
  59. // No to both questions above, it is a sub word!!
  60. pszRet = pszSub;
  61. break;
  62. }
  63. return pszRet;
  64. }
  65. int MatchMultipleSubWords(LPCTSTR pszStr, LPCTSTR pszSubWords)
  66. {
  67. if (!StrChrI(pszSubWords, TEXT(' ')))
  68. return 0;
  69. TCHAR szSubWords[MAX_PATH];
  70. lstrcpy(szSubWords, pszSubWords);
  71. LPTSTR pszStart = szSubWords;
  72. LPTSTR pszSpace;
  73. int iNumMatches = 0;
  74. while (pszSpace = StrChrI(pszStart, TEXT(' ')))
  75. {
  76. *pszSpace = 0;
  77. if (FindSubWord(pszStr, pszStart))
  78. iNumMatches++;
  79. pszStart = ++pszSpace;
  80. }
  81. if (FindSubWord(pszStr, pszStart))
  82. iNumMatches++;
  83. return iNumMatches;
  84. }
  85. /*-------------------------------------------------------------------------
  86. Purpose: Removes the spaces from pszPath, including spaces in the middle
  87. of the folder or filespec. The resulting string is placed in
  88. pszBuf.
  89. Example:
  90. (before)
  91. "C:\Program Files\Microsoft Office\Word.exe"
  92. (after)
  93. "C:\ProgramFiles\MicrosoftOffice\Word.exe"
  94. */
  95. void PathRemoveSpaces(LPCTSTR pszPath, LPTSTR pszBuf, int cchBuf)
  96. {
  97. ASSERT(IS_VALID_STRING_PTR(pszPath, -1));
  98. ASSERT(IS_VALID_WRITE_BUFFER(pszBuf, TCHAR, cchBuf));
  99. --cchBuf; // Leave room for terminating NUL.
  100. while(0 < cchBuf && TEXT('\0') != *pszPath)
  101. {
  102. //
  103. // Skip beyond spaces.
  104. //
  105. while(TEXT(' ') == *pszPath)
  106. ++pszPath;
  107. if (TEXT('\0') != *pszPath)
  108. {
  109. //
  110. // Copy to output.
  111. //
  112. *pszBuf++ = *pszPath++;
  113. --cchBuf;
  114. }
  115. }
  116. *pszBuf = TEXT('\0');
  117. }
  118. // Returns TRUE if all chars in pszCharGroup is in pszString
  119. BOOL AllCharsInString(LPCTSTR pszString, LPCTSTR pszCharGroup)
  120. {
  121. if (!pszCharGroup || !pszCharGroup[0])
  122. return FALSE;
  123. LPCTSTR pszT = pszCharGroup;
  124. while (*pszT && StrChrI(pszString, *pszT))
  125. pszT++;
  126. return (*pszT == 0) ? TRUE : FALSE;
  127. }
  128. /*-------------------------------------------------------------------------
  129. Purpose: Given the full name (and sometimes the short name) of the app,
  130. this function determines whether the given pszName is a match.
  131. If bStrict is TRUE, the heuristic skips the slinky checks.
  132. Returns a ranking of the accuracy of the match:
  133. MATCH_LEVEL_NOMATCH - pszName does not match whatsoever
  134. MATCH_LEVEL_LOW - pszName somewhat matches
  135. MATCH_LEVEL_NORMAL - pszName matches pretty good
  136. MATCH_LEVEL_HIGH - pszName definitely matches
  137. */
  138. int MatchAppNameExact(
  139. LPCTSTR pszName,
  140. LPCTSTR pszAppFullName,
  141. LPCTSTR pszAppShortName,
  142. BOOL bStrict)
  143. {
  144. TraceMsg(TF_FINDAPP, "MatchAppName ---- %s | %s | %s ", pszName, pszAppShortName, pszAppFullName);
  145. ASSERT(IS_VALID_STRING_PTR(pszName, -1));
  146. // In the heuristic below, we never degrade from a better match
  147. // to a lower match.
  148. int iMatch = MATCH_LEVEL_NOMATCH;
  149. // Since the long fullname has the most accuracy, check that first.
  150. if (pszAppFullName && *pszAppFullName)
  151. {
  152. // Is pszName equivalent to the full name of the app?
  153. if (!lstrcmpi(pszAppFullName, pszName))
  154. iMatch = MATCH_LEVEL_HIGH; // Yes, definitely a high match
  155. else
  156. {
  157. // No, okay let's see if there are multiple (> 1) number of sub
  158. // words from pszName that match the subwords in the app's full name
  159. int iSubMatches = MatchMultipleSubWords(pszAppFullName, pszName);
  160. // More than three matches, definitely high match
  161. // NOTE: there could be a risk here, but I have not found a
  162. // counter example yet.
  163. if (iSubMatches > 3)
  164. iMatch = MATCH_LEVEL_HIGH;
  165. // NOTE: there is a risk here. For example:
  166. //
  167. // Microsoft Internet Explorer Setup Files vs.
  168. // Microsoft Internet Explorer ...
  169. else if ((iSubMatches > 1) && (!bStrict || (iSubMatches > 2)))
  170. iMatch = MATCH_LEVEL_NORMAL;
  171. // All these are turned off if we have a strict matching
  172. else if (!bStrict)
  173. {
  174. // If the potential folder name is a subset of the full name or
  175. // if all of the characters of the potential folder name can
  176. // be found in the full name, we have a low match
  177. // (Counter Ex: Microsoft vs. Microsoft Office)
  178. // NOTE: The reason for AllCharsInString is to detect case like
  179. // Ex: "PM65 vs. Adobe Page Maker 6.5"
  180. // There might be a risk in this, but I have not found a counter
  181. // example, yet.
  182. if (StrStrI(pszAppFullName, pszName) || AllCharsInString(pszAppFullName, pszName))
  183. iMatch = MATCH_LEVEL_LOW;
  184. }
  185. }
  186. }
  187. // Association between folder name and the reg key name(short name)
  188. // This is given second priority because the reg key name is unreliable (could be an ID)
  189. if (MATCH_LEVEL_HIGH > iMatch && pszAppShortName && *pszAppShortName)
  190. {
  191. // Does the string exactly match the app's shortname?
  192. if (!lstrcmpi(pszAppShortName, pszName))
  193. iMatch = MATCH_LEVEL_HIGH; // yes
  194. // All these are turned off if we have strict matching
  195. else if (!bStrict)
  196. {
  197. // Does the string contain the app's shortname?
  198. if (iMatch < MATCH_LEVEL_NORMAL && StrStrI(pszName, pszAppShortName))
  199. iMatch = MATCH_LEVEL_NORMAL; // yes
  200. // Or does the app's shortname contain the string?
  201. else if (iMatch < MATCH_LEVEL_LOW && StrStrI(pszAppShortName, pszName))
  202. iMatch = MATCH_LEVEL_LOW; // yes
  203. }
  204. }
  205. return iMatch;
  206. }
  207. /*-------------------------------------------------------------------------
  208. Purpose: This function tries some different heuristics to see how well
  209. pszCandidate matches the given variations of the app name
  210. (short and long names).
  211. If bStrict is TRUE, the heuristic skips the slinky checks.
  212. Returns a ranking of the accuracy of the match:
  213. MATCH_LEVEL_NOMATCH - pszName does not match whatsoever
  214. MATCH_LEVEL_LOW - pszName somewhat matches
  215. MATCH_LEVEL_NORMAL - pszName matches pretty good
  216. MATCH_LEVEL_HIGH - pszName definitely matches
  217. */
  218. int MatchAppName(
  219. LPCTSTR pszCandidate,
  220. LPCTSTR pszAppFullName,
  221. LPCTSTR pszAppShortName, OPTIONAL
  222. BOOL bStrict)
  223. {
  224. int iMatch = MATCH_LEVEL_NOMATCH;
  225. if (pszCandidate && *pszCandidate)
  226. {
  227. // Clean up all the strings MAX_PATH+1, in this case, we only stick a
  228. // ' ' on
  229. TCHAR szCleanFolderName[MAX_PATH+1];
  230. InsertSpaceBeforeVersion(pszCandidate, szCleanFolderName);
  231. // Now match the exact name
  232. iMatch = MatchAppNameExact(szCleanFolderName, pszAppFullName, pszAppShortName, bStrict);
  233. // Is there still no match, and do we have some flexibility to fudge?
  234. if (!bStrict)
  235. {
  236. int iNewMatch = MATCH_LEVEL_NOMATCH;
  237. // Yes; try finding it without the spaces in the filename and paths
  238. TCHAR szCandidate[MAX_PATH];
  239. TCHAR szFullName[MAX_PATH];
  240. TCHAR szShortName[MAX_PATH];
  241. PathRemoveSpaces(pszCandidate, szCandidate, ARRAYSIZE(szCandidate));
  242. PathRemoveSpaces(pszAppFullName, szFullName, ARRAYSIZE(szFullName));
  243. if (pszAppShortName && pszAppShortName[0])
  244. {
  245. PathRemoveSpaces(pszAppShortName, szShortName, ARRAYSIZE(szShortName));
  246. pszAppShortName = szShortName;
  247. }
  248. iNewMatch = MatchAppNameExact(szCandidate, szFullName, pszAppShortName, bStrict);
  249. if (iNewMatch > iMatch)
  250. iMatch = iNewMatch;
  251. }
  252. }
  253. return iMatch;
  254. }
  255. // This function returns a pointer to the beginning of the right most string
  256. // which looks like folder path. This only looks for paths with fixed drive
  257. // letters.
  258. //
  259. // NOTES:
  260. // 1. This funcion damages pszString
  261. // 2. We are really cracking the string, what happens
  262. // in localized versions? Are these going to be international char strings?
  263. //
  264. // Returns NULL if it could not find a legit-looking path.
  265. LPTSTR GetRightMostFolderPathInString(LPTSTR pszString)
  266. {
  267. // Reverse find the ':' in the path
  268. LPTSTR pszRoot = StrRChr(pszString, NULL, TEXT(':'));
  269. // Make sure what we found is not at the beginning of the whole
  270. // string or the last character of the string
  271. if (pszRoot && (pszRoot > pszString) && (*CharNext(pszRoot) == TEXT('\\')))
  272. {
  273. // Okay, now move back one, we should be pointing to the drive letter
  274. pszRoot--; // Don't have to use CharPrev since we're on a ':'
  275. TCHAR szDrive[2];
  276. szDrive[0] = *pszRoot;
  277. szDrive[1] = 0;
  278. CharUpper(szDrive);
  279. if ((szDrive[0] >= TEXT('C')) && (szDrive[0] <= TEXT('Z')))
  280. {
  281. // Yes, it is a real drive letter
  282. TCHAR atch[4];
  283. wsprintf(atch, TEXT("%c:\\"), *pszRoot);
  284. // We are only interested in fixed drives and let's check the path
  285. if (GetDriveType(atch) == DRIVE_FIXED)
  286. {
  287. PathRemoveFileSpec(pszRoot);
  288. return pszRoot;
  289. }
  290. }
  291. }
  292. return NULL;
  293. }
  294. // Given a full path, an app name, an app short name, finds the best match in this path
  295. // EX: App Name: Microsoft Office Short Name: Office
  296. // C:\Microsoft Office\Office --> C:\Microsoft Office
  297. int FindBestMatch(
  298. LPCTSTR pszFolder,
  299. LPCTSTR pszAppFullName,
  300. LPCTSTR pszAppShortName,
  301. BOOL bStrict,
  302. LPTSTR pszResult)
  303. {
  304. // This can't be a root directory
  305. ASSERT(!PathIsRoot(pszFolder));
  306. int iBest = MATCH_LEVEL_NOMATCH;
  307. int iPre = MATCH_LEVEL_NOMATCH;
  308. int iThis = MATCH_LEVEL_NOMATCH;
  309. TCHAR szPrefix[MAX_PATH];
  310. lstrcpy(szPrefix, pszFolder);
  311. if (PathRemoveFileSpec(szPrefix) && !PathIsRoot(szPrefix))
  312. iPre = FindBestMatch(szPrefix, pszAppFullName, pszAppShortName, bStrict, pszResult);
  313. LPTSTR pszName = PathFindFileName(pszFolder);
  314. if (pszName)
  315. iThis = MatchAppName(pszName, pszAppFullName, pszAppShortName, bStrict);
  316. iBest = (iPre > iThis) ? iPre : iThis;
  317. // In case there is both match in the current folder and the previous folder
  318. // take this current one because:
  319. // 1. This folder is closer to the "Uninstall" or "Modify" string
  320. // 2. It costs less to walk this folder;
  321. if ((iThis > MATCH_LEVEL_NOMATCH) && (iThis >= iPre))
  322. lstrcpy(pszResult, pszFolder);
  323. return iBest;
  324. }
  325. /*--------------------------------------------------------------------------
  326. Purpose: Given a file name or a folder name, compare it with our list of setup
  327. app names.
  328. NOTE: the comparason are done as the following: We compare the name with the first portion
  329. and the last portion of our setup name EX:
  330. name --> myuninst.exe or uninstall.exe
  331. Setup Name --> uninst
  332. should bother return TRUE
  333. */
  334. BOOL IsFileOrFolderSetup(LPTSTR pszName, LPCTSTR pszDoubleString)
  335. {
  336. ASSERT(pszName);
  337. ASSERT(pszDoubleString);
  338. BOOL bRet = FALSE;
  339. // Neither pszName of pszDoubleString should be NULL
  340. if (pszName && pszDoubleString)
  341. {
  342. PathRemoveExtension(pszName);
  343. int cchName = lstrlen(pszName);
  344. LPCTSTR pszT = pszDoubleString;
  345. while (*pszT)
  346. {
  347. int cch = lstrlen(pszT);
  348. // NOTE: we compare from the beginning and from the end
  349. if (!StrCmpNI(pszName, pszT, cch) ||
  350. ((cchName > cch) && !StrCmpNI(pszName + cchName - cch, pszT, cch)))
  351. {
  352. bRet = TRUE;
  353. break;
  354. }
  355. pszT += lstrlen(pszT) + 1;
  356. }
  357. }
  358. return bRet;
  359. }
  360. /*-------------------------------------------------------------------------
  361. Purpose: Sniffs the pszFolder for any signs that the path refers to a setup
  362. program. Paths that have foldernames or filespecs with the word
  363. "setup" or "install" are suspect. Returns TRUE if it looks like
  364. it might be a setup app or folder.
  365. An example is "c:\program files\microsoft office\office\setup\outlook\olmaint.exe".
  366. This function will return TRUE because "setup" is one of the parent
  367. folder names.
  368. cStripLevel means how many levels we will go up the directory ladder
  369. */
  370. BOOL PathIsSetup(LPCTSTR pszFolder, int cStripLevel)
  371. {
  372. ASSERT(IS_VALID_STRING_PTR(pszFolder, -1));
  373. ASSERT(cStripLevel > 0);
  374. BOOL bRet = FALSE;
  375. TCHAR szPath[MAX_PATH];
  376. TCHAR szName[MAX_PATH];
  377. lstrcpy(szPath, pszFolder);
  378. static TCHAR s_szNames[MAX_PATH];
  379. static BOOL s_bNamesLoaded = FALSE;
  380. if (!s_bNamesLoaded)
  381. {
  382. LoadAndStrip(IDS_SETUPAPPNAMES, s_szNames, ARRAYSIZE(s_szNames));
  383. s_bNamesLoaded = TRUE;
  384. }
  385. LPTSTR pszName;
  386. int iStripLevel = cStripLevel;
  387. while ((iStripLevel-- > 0) && (NULL != (pszName = PathFindFileName(szPath))))
  388. {
  389. lstrcpy(szName, pszName);
  390. if (IsFileOrFolderSetup(szName, s_szNames))
  391. {
  392. bRet = TRUE;
  393. break;
  394. }
  395. else if (!PathRemoveFileSpec(szPath) || PathIsRoot(szPath))
  396. break;
  397. }
  398. return bRet;
  399. }
  400. BOOL PathIsCommonFiles(LPCTSTR pszPath)
  401. {
  402. TCHAR szCommonFiles[MAX_PATH];
  403. TCHAR szShortCommonFiles[MAX_PATH];
  404. ASSERT(IS_VALID_STRING_PTR(pszPath, -1));
  405. // This definitely need to be put in the RC file
  406. wsprintf(szCommonFiles, TEXT("%c:\\Program Files\\Common Files"), pszPath[0]);
  407. BOOL bShort = GetShortPathName(szCommonFiles, szShortCommonFiles, ARRAYSIZE(szShortCommonFiles));
  408. if (bShort)
  409. {
  410. ASSERT(szShortCommonFiles[0] == szCommonFiles[0]);
  411. }
  412. return PathIsPrefix(szCommonFiles, pszPath) || (bShort && PathIsPrefix(szShortCommonFiles, pszPath));
  413. }
  414. // returns TRUE if windows directory is the prefix of pszPath
  415. BOOL PathIsUnderWindows(LPCTSTR pszPath)
  416. {
  417. TCHAR szWindows[MAX_PATH];
  418. if (GetWindowsDirectory(szWindows, ARRAYSIZE(szWindows)))
  419. {
  420. // Is this path somewhere below the windows directory?
  421. return PathIsPrefix(szWindows, pszPath);
  422. }
  423. return FALSE;
  424. }
  425. /*-------------------------------------------------------------------------
  426. Purpose: This function looks for a valid-looking path in the given pszInfo
  427. string that may indicate where the app is installed. This attempts
  428. to weed out suspect paths like references to setup programs in
  429. other folders.
  430. Returns TRUE if a useful path was found. pszOut will contain the
  431. path.
  432. */
  433. BOOL ParseInfoString(LPCTSTR pszInfo, LPCTSTR pszFullName, LPCTSTR pszShortName, LPTSTR pszOut)
  434. {
  435. ASSERT(IS_VALID_STRING_PTR(pszInfo, -1));
  436. ASSERT(IS_VALID_STRING_PTR(pszFullName, -1));
  437. ASSERT(pszOut);
  438. *pszOut = 0;
  439. // if it starts with rundll, forget it!
  440. if (!StrCmpNI(pszInfo, TEXT("rundll"), SIZECHARS(TEXT("rundll"))))
  441. return FALSE;
  442. // more strings we bail on ...
  443. TCHAR szInfoT[MAX_INFO_STRING];
  444. lstrcpyn(szInfoT, pszInfo, SIZECHARS(szInfoT));
  445. // The algorithm: we crack the string, and go from the right most path inside the string
  446. // to the left most one by one and guess which one is a more reasonable
  447. LPTSTR pszFolder;
  448. while (NULL != (pszFolder = GetRightMostFolderPathInString(szInfoT)))
  449. {
  450. TCHAR szFullPath[MAX_PATH];
  451. // GetLongPathName does not work on Win 95
  452. if (StrChrI(pszFolder, TEXT('\\')) && GetLongPathName(pszFolder, szFullPath, ARRAYSIZE(szFullPath)))
  453. {
  454. // Make sure this actually is a path and not a root drive
  455. if (PathIsDirectory(szFullPath) && !PathIsRoot(szFullPath) && !PathIsUnderWindows(szFullPath))
  456. {
  457. // No; then we'll consider it
  458. LPTSTR pszFolderName;
  459. BOOL bStop = FALSE;
  460. // Find out the last folder name
  461. // If it is "setup" or "install", move up until it's not or we can't move up any more
  462. while(NULL != (pszFolderName = PathFindFileName(szFullPath)) &&
  463. PathIsSetup(pszFolderName, 1))
  464. {
  465. // Have we reached the root of the path?
  466. if (!PathRemoveFileSpec(szFullPath) || PathIsRoot(szFullPath))
  467. {
  468. // Yes; don't go any further
  469. bStop = TRUE;
  470. break;
  471. }
  472. }
  473. // We still reject those strings with "setup" or "install" in the middle,
  474. // or those under the program files common files
  475. if (!bStop && !PathIsRoot(szFullPath) &&
  476. !PathIsSetup(szFullPath, 3) && !PathIsCommonFiles(szFullPath))
  477. {
  478. if (MATCH_LEVEL_NOMATCH < FindBestMatch(szFullPath, pszFullName, pszShortName, FALSE, pszOut))
  479. return TRUE;
  480. }
  481. }
  482. }
  483. *pszFolder = 0;
  484. continue;
  485. }
  486. return FALSE;
  487. }
  488. #endif //DOWNLEVEL_PLATFORM