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.

597 lines
20 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. #include "appwiz.h"
  15. #include "appsize.h"
  16. #include "findapp.h"
  17. #include "util.h"
  18. // Things to do:
  19. // 1. Move special strings into the RC file
  20. /*-------------------------------------------------------------------------
  21. Purpose: This function searches and returns the sub word (if one is found).
  22. pszStr is the big string, pszSrch is the candidate substring used
  23. in the search.
  24. Returns NULL if no subword is found.
  25. */
  26. LPCTSTR FindSubWord(LPCTSTR pszStr, LPCTSTR pszSrch)
  27. {
  28. LPCTSTR pszRet = NULL;
  29. LPCTSTR pszBegin = pszStr;
  30. // Search for the sub string from the beginning
  31. LPCTSTR pszSub;
  32. while (NULL != (pszSub = StrStrI(pszBegin, pszSrch)))
  33. {
  34. LPCTSTR pszPrev;
  35. LPCTSTR pszEnd = pszSub + lstrlen(pszSrch);
  36. // Is the previous character alphanumeric?
  37. if (pszSub != pszBegin)
  38. {
  39. ASSERT(pszSub > pszBegin);
  40. pszPrev = CharPrev(pszBegin, pszSub);
  41. ASSERT(pszPrev >= pszBegin);
  42. if (IsCharAlphaNumeric(*pszPrev))
  43. {
  44. // yes, go on searching
  45. pszBegin = pszEnd;
  46. continue;
  47. }
  48. }
  49. // Is the character after the sub string we found
  50. // alpha numeric?
  51. if (IsCharAlphaNumeric(*pszEnd))
  52. {
  53. // yes, go on searching
  54. pszBegin = pszEnd;
  55. continue;
  56. }
  57. // No to both questions above, it is a sub word!!
  58. pszRet = pszSub;
  59. break;
  60. }
  61. return pszRet;
  62. }
  63. int MatchMultipleSubWords(LPCTSTR pszStr, LPCTSTR pszSubWords)
  64. {
  65. if (!StrChrI(pszSubWords, TEXT(' ')))
  66. return 0;
  67. TCHAR szSubWords[MAX_PATH];
  68. StringCchCopy(szSubWords, ARRAYSIZE(szSubWords), pszSubWords);
  69. LPTSTR pszStart = szSubWords;
  70. LPTSTR pszSpace;
  71. int iNumMatches = 0;
  72. while (pszSpace = StrChrI(pszStart, TEXT(' ')))
  73. {
  74. *pszSpace = 0;
  75. if (FindSubWord(pszStr, pszStart))
  76. iNumMatches++;
  77. pszStart = ++pszSpace;
  78. }
  79. if (FindSubWord(pszStr, pszStart))
  80. iNumMatches++;
  81. return iNumMatches;
  82. }
  83. /*-------------------------------------------------------------------------
  84. Purpose: Removes the spaces from pszPath, including spaces in the middle
  85. of the folder or filespec. The resulting string is placed in
  86. pszBuf.
  87. Example:
  88. (before)
  89. "C:\Program Files\Microsoft Office\Word.exe"
  90. (after)
  91. "C:\ProgramFiles\MicrosoftOffice\Word.exe"
  92. */
  93. void PathRemoveSpaces(LPCTSTR pszPath, LPTSTR pszBuf, int cchBuf)
  94. {
  95. ASSERT(IS_VALID_STRING_PTR(pszPath, -1));
  96. ASSERT(IS_VALID_WRITE_BUFFER(pszBuf, TCHAR, cchBuf));
  97. --cchBuf; // Leave room for terminating NUL.
  98. while(0 < cchBuf && TEXT('\0') != *pszPath)
  99. {
  100. //
  101. // Skip beyond spaces.
  102. //
  103. while(TEXT(' ') == *pszPath)
  104. ++pszPath;
  105. if (TEXT('\0') != *pszPath)
  106. {
  107. //
  108. // Copy to output.
  109. //
  110. *pszBuf++ = *pszPath++;
  111. --cchBuf;
  112. }
  113. }
  114. *pszBuf = TEXT('\0');
  115. }
  116. // Returns TRUE if all chars in pszCharGroup is in pszString
  117. BOOL AllCharsInString(LPCTSTR pszString, LPCTSTR pszCharGroup)
  118. {
  119. if (!pszCharGroup || !pszCharGroup[0])
  120. return FALSE;
  121. LPCTSTR pszT = pszCharGroup;
  122. while (*pszT && StrChrI(pszString, *pszT))
  123. pszT++;
  124. return (*pszT == 0) ? TRUE : FALSE;
  125. }
  126. /*-------------------------------------------------------------------------
  127. Purpose: Given the full name (and sometimes the short name) of the app,
  128. this function determines whether the given pszName is a match.
  129. If bStrict is TRUE, the heuristic skips the slinky checks.
  130. Returns a ranking of the accuracy of the match:
  131. MATCH_LEVEL_NOMATCH - pszName does not match whatsoever
  132. MATCH_LEVEL_LOW - pszName somewhat matches
  133. MATCH_LEVEL_NORMAL - pszName matches pretty good
  134. MATCH_LEVEL_HIGH - pszName definitely matches
  135. */
  136. int MatchAppNameExact(
  137. LPCTSTR pszName,
  138. LPCTSTR pszAppFullName,
  139. LPCTSTR pszAppShortName,
  140. BOOL bStrict)
  141. {
  142. TraceMsg(TF_FINDAPP, "MatchAppName ---- %s | %s | %s ", pszName, pszAppShortName, pszAppFullName);
  143. ASSERT(IS_VALID_STRING_PTR(pszName, -1));
  144. // In the heuristic below, we never degrade from a better match
  145. // to a lower match.
  146. int iMatch = MATCH_LEVEL_NOMATCH;
  147. // Since the long fullname has the most accuracy, check that first.
  148. if (pszAppFullName && *pszAppFullName)
  149. {
  150. // Is pszName equivalent to the full name of the app?
  151. if (!lstrcmpi(pszAppFullName, pszName))
  152. iMatch = MATCH_LEVEL_HIGH; // Yes, definitely a high match
  153. else
  154. {
  155. // No, okay let's see if there are multiple (> 1) number of sub
  156. // words from pszName that match the subwords in the app's full name
  157. int iSubMatches = MatchMultipleSubWords(pszAppFullName, pszName);
  158. // More than three matches, definitely high match
  159. // NOTE: there could be a risk here, but I have not found a
  160. // counter example yet.
  161. if (iSubMatches > 3)
  162. iMatch = MATCH_LEVEL_HIGH;
  163. // NOTE: there is a risk here. For example:
  164. //
  165. // Microsoft Internet Explorer Setup Files vs.
  166. // Microsoft Internet Explorer ...
  167. else if ((iSubMatches > 1) && (!bStrict || (iSubMatches > 2)))
  168. iMatch = MATCH_LEVEL_NORMAL;
  169. // All these are turned off if we have a strict matching
  170. else if (!bStrict)
  171. {
  172. // If the potential folder name is a subset of the full name or
  173. // if all of the characters of the potential folder name can
  174. // be found in the full name, we have a low match
  175. // (Counter Ex: Microsoft vs. Microsoft Office)
  176. // NOTE: The reason for AllCharsInString is to detect case like
  177. // Ex: "PM65 vs. Adobe Page Maker 6.5"
  178. // There might be a risk in this, but I have not found a counter
  179. // example, yet.
  180. if (StrStrI(pszAppFullName, pszName) || AllCharsInString(pszAppFullName, pszName))
  181. iMatch = MATCH_LEVEL_LOW;
  182. }
  183. }
  184. }
  185. // Association between folder name and the reg key name(short name)
  186. // This is given second priority because the reg key name is unreliable (could be an ID)
  187. if (MATCH_LEVEL_HIGH > iMatch && pszAppShortName && *pszAppShortName)
  188. {
  189. // Does the string exactly match the app's shortname?
  190. if (!lstrcmpi(pszAppShortName, pszName))
  191. iMatch = MATCH_LEVEL_HIGH; // yes
  192. // All these are turned off if we have strict matching
  193. else if (!bStrict)
  194. {
  195. // Does the string contain the app's shortname?
  196. if (iMatch < MATCH_LEVEL_NORMAL && StrStrI(pszName, pszAppShortName))
  197. iMatch = MATCH_LEVEL_NORMAL; // yes
  198. // Or does the app's shortname contain the string?
  199. else if (iMatch < MATCH_LEVEL_LOW && StrStrI(pszAppShortName, pszName))
  200. iMatch = MATCH_LEVEL_LOW; // yes
  201. }
  202. }
  203. return iMatch;
  204. }
  205. /*-------------------------------------------------------------------------
  206. Purpose: This function tries some different heuristics to see how well
  207. pszCandidate matches the given variations of the app name
  208. (short and long names).
  209. If bStrict is TRUE, the heuristic skips the slinky checks.
  210. Returns a ranking of the accuracy of the match:
  211. MATCH_LEVEL_NOMATCH - pszName does not match whatsoever
  212. MATCH_LEVEL_LOW - pszName somewhat matches
  213. MATCH_LEVEL_NORMAL - pszName matches pretty good
  214. MATCH_LEVEL_HIGH - pszName definitely matches
  215. */
  216. int MatchAppName(
  217. LPCTSTR pszCandidate,
  218. LPCTSTR pszAppFullName,
  219. LPCTSTR pszAppShortName, OPTIONAL
  220. BOOL bStrict)
  221. {
  222. int iMatch = MATCH_LEVEL_NOMATCH;
  223. if (pszCandidate && *pszCandidate)
  224. {
  225. // Clean up all the strings MAX_PATH+1, in this case, we only stick a
  226. // ' ' on
  227. TCHAR szCleanFolderName[MAX_PATH+1];
  228. InsertSpaceBeforeVersion(pszCandidate, szCleanFolderName);
  229. // Now match the exact name
  230. iMatch = MatchAppNameExact(szCleanFolderName, pszAppFullName, pszAppShortName, bStrict);
  231. // Is there still no match, and do we have some flexibility to fudge?
  232. if (!bStrict)
  233. {
  234. int iNewMatch = MATCH_LEVEL_NOMATCH;
  235. // Yes; try finding it without the spaces in the filename and paths
  236. TCHAR szCandidate[MAX_PATH];
  237. TCHAR szFullName[MAX_PATH];
  238. TCHAR szShortName[MAX_PATH];
  239. PathRemoveSpaces(pszCandidate, szCandidate, ARRAYSIZE(szCandidate));
  240. PathRemoveSpaces(pszAppFullName, szFullName, ARRAYSIZE(szFullName));
  241. if (pszAppShortName && pszAppShortName[0])
  242. {
  243. PathRemoveSpaces(pszAppShortName, szShortName, ARRAYSIZE(szShortName));
  244. pszAppShortName = szShortName;
  245. }
  246. iNewMatch = MatchAppNameExact(szCandidate, szFullName, pszAppShortName, bStrict);
  247. if (iNewMatch > iMatch)
  248. iMatch = iNewMatch;
  249. }
  250. }
  251. return iMatch;
  252. }
  253. // This function returns a pointer to the beginning of the right most string
  254. // which looks like folder path. This only looks for paths with fixed drive
  255. // letters.
  256. //
  257. // NOTES:
  258. // 1. This funcion damages pszString
  259. // 2. We are really cracking the string, what happens
  260. // in localized versions? Are these going to be international char strings?
  261. //
  262. // Returns NULL if it could not find a legit-looking path.
  263. LPTSTR GetRightMostFolderPathInString(LPTSTR pszString)
  264. {
  265. // Reverse find the ':' in the path
  266. LPTSTR pszRoot = StrRChr(pszString, NULL, TEXT(':'));
  267. // Make sure what we found is not at the beginning of the whole
  268. // string or the last character of the string
  269. if (pszRoot && (pszRoot > pszString) && (*CharNext(pszRoot) == TEXT('\\')))
  270. {
  271. // Okay, now move back one, we should be pointing to the drive letter
  272. pszRoot--; // Don't have to use CharPrev since we're on a ':'
  273. TCHAR szDrive[2];
  274. szDrive[0] = *pszRoot;
  275. szDrive[1] = 0;
  276. CharUpper(szDrive);
  277. if ((szDrive[0] >= TEXT('C')) && (szDrive[0] <= TEXT('Z')))
  278. {
  279. // Yes, it is a real drive letter
  280. TCHAR atch[4];
  281. StringCchPrintf(atch, ARRAYSIZE(atch), TEXT("%c:\\"), *pszRoot);
  282. // We are only interested in fixed drives and let's check the path
  283. if (GetDriveType(atch) == DRIVE_FIXED)
  284. {
  285. PathRemoveFileSpec(pszRoot);
  286. return pszRoot;
  287. }
  288. }
  289. }
  290. return NULL;
  291. }
  292. // Given a full path, an app name, an app short name, finds the best match in this path
  293. // EX: App Name: Microsoft Office Short Name: Office
  294. // C:\Microsoft Office\Office --> C:\Microsoft Office
  295. int FindBestMatch(
  296. LPCTSTR pszFolder,
  297. LPCTSTR pszAppFullName,
  298. LPCTSTR pszAppShortName,
  299. BOOL bStrict,
  300. LPTSTR pszResult)
  301. {
  302. // This can't be a root directory
  303. ASSERT(!PathIsRoot(pszFolder));
  304. int iBest = MATCH_LEVEL_NOMATCH;
  305. int iPre = MATCH_LEVEL_NOMATCH;
  306. int iThis = MATCH_LEVEL_NOMATCH;
  307. TCHAR szPrefix[MAX_PATH];
  308. StringCchCopy(szPrefix, ARRAYSIZE(szPrefix), pszFolder);
  309. if (PathRemoveFileSpec(szPrefix) && !PathIsRoot(szPrefix))
  310. iPre = FindBestMatch(szPrefix, pszAppFullName, pszAppShortName, bStrict, pszResult);
  311. LPTSTR pszName = PathFindFileName(pszFolder);
  312. if (pszName)
  313. iThis = MatchAppName(pszName, pszAppFullName, pszAppShortName, bStrict);
  314. iBest = (iPre > iThis) ? iPre : iThis;
  315. // In case there is both match in the current folder and the previous folder
  316. // take this current one because:
  317. // 1. This folder is closer to the "Uninstall" or "Modify" string
  318. // 2. It costs less to walk this folder;
  319. if ((iThis > MATCH_LEVEL_NOMATCH) && (iThis >= iPre))
  320. {
  321. lstrcpy(pszResult, pszFolder);
  322. }
  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. StringCchCopy(szPath, ARRAYSIZE(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. StringCchCopy(szName, ARRAYSIZE(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. StringCchPrintf(szCommonFiles, ARRAYSIZE(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. }