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.

781 lines
19 KiB

  1. /*++
  2. Copyright (c) 2000 Microsoft Corporation
  3. Module Name :
  4. mimemap.cxx
  5. Abstract:
  6. Store and retrieve mime-mapping for file types
  7. Author:
  8. Murali R. Krishnan (MuraliK) 10-Jan-1995
  9. Environment:
  10. Win32 - User Mode
  11. Project:
  12. UlW3.dll
  13. History:
  14. Anil Ruia (AnilR) 27-Mar-2000 Ported to IIS+
  15. --*/
  16. #include "precomp.hxx"
  17. #define g_pszDefaultFileExt L"*"
  18. #define g_pszDefaultMimeType L"application/octet-stream"
  19. LPWSTR g_pszDefaultMimeEntry = L"*,application/octet-stream";
  20. MIME_MAP *g_pMimeMap = NULL;
  21. HRESULT MIME_MAP::InitMimeMap()
  22. /*++
  23. Synopsis
  24. This function reads the mimemap stored either as a MULTI_SZ or as a
  25. sequence of REG_SZ
  26. Returns:
  27. HRESULT
  28. --*/
  29. {
  30. DBG_ASSERT(!IsValid());
  31. // First read metabase (common types will have priority)
  32. HRESULT hr = InitFromMetabase();
  33. if (SUCCEEDED(hr))
  34. {
  35. m_fValid = TRUE;
  36. }
  37. // Now read Chicago shell registration database
  38. hr = InitFromRegistryChicagoStyle();
  39. if (SUCCEEDED(hr))
  40. {
  41. m_fValid = TRUE;
  42. }
  43. //
  44. // If at least one succeeded, we are ok
  45. //
  46. if (IsValid())
  47. return S_OK;
  48. return hr;
  49. }
  50. static VOID GetFileExtension(IN LPWSTR pszPathName,
  51. OUT LPWSTR *ppszExt,
  52. OUT LPWSTR *ppszLastSlash)
  53. /*++
  54. Synopsis
  55. Gets The extension portion from a filename.
  56. Arguments
  57. pszPathName: The full path name (containing forward '/' or '\\' 's)
  58. ppszExt: Points to start of extension on return
  59. ppszLastSlash: Points to the last slash in the path name on return
  60. Return Value
  61. None
  62. --*/
  63. {
  64. LPWSTR pszExt = g_pszDefaultFileExt;
  65. DBG_ASSERT(ppszExt != NULL && ppszLastSlash != NULL);
  66. *ppszLastSlash = NULL;
  67. if (pszPathName)
  68. {
  69. LPWSTR pszLastDot;
  70. pszLastDot = wcsrchr(pszPathName, L'.');
  71. if (pszLastDot != NULL)
  72. {
  73. LPWSTR pszLastWhack = NULL;
  74. LPWSTR pszEnd;
  75. pszEnd = pszPathName + wcslen( pszPathName );
  76. while ( pszEnd >= pszPathName )
  77. {
  78. if ( *pszEnd == L'/' || *pszEnd == L'\\' )
  79. {
  80. pszLastWhack = pszEnd;
  81. break;
  82. }
  83. pszEnd--;
  84. }
  85. if (pszLastWhack == NULL)
  86. {
  87. pszLastWhack = pszPathName; // only file name specified.
  88. }
  89. if (pszLastDot >= pszLastWhack)
  90. {
  91. // if the dot comes only in the last component, then get ext
  92. pszExt = pszLastDot + 1; // +1 to skip last dot.
  93. *ppszLastSlash = pszLastWhack;
  94. }
  95. }
  96. }
  97. *ppszExt = pszExt;
  98. }
  99. const MIME_MAP_ENTRY *MIME_MAP::LookupMimeEntryForFileExt(
  100. IN LPWSTR pszPathName)
  101. /*++
  102. Synopsis
  103. This function maps FileExtension to MimeEntry.
  104. The function returns a single mime entry for given file's extension.
  105. If no match is found, the default mime entry is returned.
  106. The returned entry is a readonly pointer and should not be altered.
  107. The file extension is the key field in the Hash table for mime entries.
  108. We can use the hash table lookup function to find the entry.
  109. Arguments:
  110. pszPathName
  111. pointer to string containing the path for file. (either full path or
  112. just the file name). If NULL, then the default MimeMapEntry is returned.
  113. Returns:
  114. If a matching mime entry is found, a const pointer to MimeMapEntry
  115. object is returned. Otherwise the default mime map entry object is
  116. returned.
  117. --*/
  118. {
  119. MIME_MAP_ENTRY *pMmeMatch = m_pMmeDefault;
  120. DBG_ASSERT( IsValid());
  121. if (pszPathName != NULL && *pszPathName)
  122. {
  123. LPWSTR pszExt;
  124. LPWSTR pszLastSlash;
  125. GetFileExtension(pszPathName, &pszExt, &pszLastSlash );
  126. DBG_ASSERT(pszExt);
  127. if (!wcscmp(pszExt, g_pszDefaultFileExt))
  128. {
  129. return m_pMmeDefault;
  130. }
  131. for (;;)
  132. {
  133. //
  134. // Successfully got extension. Search in the list of MimeEntries.
  135. //
  136. FindKey(pszExt, &pMmeMatch);
  137. pszExt--;
  138. if ( NULL == pMmeMatch)
  139. {
  140. pMmeMatch = m_pMmeDefault;
  141. // Look backwards for another '.' so we can support extensions
  142. // like ".xyz.xyz" or ".a.b.c".
  143. if (pszExt > pszLastSlash)
  144. {
  145. pszExt--;
  146. while ((pszExt > pszLastSlash) && (*pszExt != L'.'))
  147. {
  148. pszExt--;
  149. }
  150. if (*(pszExt++) != L'.')
  151. {
  152. break;
  153. }
  154. }
  155. else
  156. {
  157. break;
  158. }
  159. }
  160. else
  161. {
  162. break;
  163. }
  164. }
  165. }
  166. return pMmeMatch;
  167. }
  168. BOOL MIME_MAP::AddMimeMapEntry(IN MIME_MAP_ENTRY *pMmeNew)
  169. {
  170. if (!wcscmp(pMmeNew->QueryFileExt(), g_pszDefaultFileExt))
  171. {
  172. m_pMmeDefault = pMmeNew;
  173. return TRUE;
  174. }
  175. if (InsertRecord(pMmeNew) == LK_SUCCESS)
  176. {
  177. return TRUE;
  178. }
  179. return FALSE;
  180. }
  181. BOOL MIME_MAP::CreateAndAddMimeMapEntry(
  182. IN LPWSTR pszMimeType,
  183. IN LPWSTR pszExtension)
  184. {
  185. DWORD dwError;
  186. MIME_MAP_ENTRY *pEntry = NULL;
  187. //
  188. // File extensions, stored by OLE/shell registration UI have leading
  189. // dot, we need to remove it , as other code won't like it.
  190. //
  191. if (pszExtension[0] == L'.')
  192. {
  193. pszExtension++;
  194. }
  195. //
  196. // First check if this extension is not yet present
  197. //
  198. FindKey(pszExtension, &pEntry);
  199. if (pEntry)
  200. {
  201. return TRUE;
  202. }
  203. MIME_MAP_ENTRY *pMmeNew;
  204. pMmeNew = new MIME_MAP_ENTRY(pszMimeType, pszExtension);
  205. if (!pMmeNew || !pMmeNew->IsValid())
  206. {
  207. //
  208. // unable to create a new MIME_MAP_ENTRY object.
  209. //
  210. if (pMmeNew)
  211. {
  212. delete pMmeNew;
  213. }
  214. return FALSE;
  215. }
  216. if (!AddMimeMapEntry(pMmeNew))
  217. {
  218. dwError = GetLastError();
  219. DBGPRINTF( ( DBG_CONTEXT,
  220. "MIME_MAP::InitFromRegistry()."
  221. " Failed to add new MIME Entry. Error = %d\n",
  222. dwError)
  223. );
  224. delete pMmeNew;
  225. return FALSE;
  226. }
  227. return TRUE;
  228. }
  229. static BOOL ReadMimeMapFromMetabase(OUT MULTISZ *pmszMimeMap)
  230. /*++
  231. Synopsis
  232. This function reads the mimemap stored either as a MULTI_SZ or as a
  233. sequence of REG_SZ and returns a double null terminated sequence of mime
  234. types on success. If there is any failure, the failures are ignored and
  235. it returns a NULL.
  236. Arguments:
  237. pmszMimeMap: MULTISZ which will contain the MimeMap on success
  238. Returns:
  239. BOOL
  240. --*/
  241. {
  242. MB mb(g_pW3Server->QueryMDObject());
  243. if (!mb.Open(L"/LM/MimeMap", METADATA_PERMISSION_READ))
  244. {
  245. //
  246. // if this fails, we're hosed.
  247. //
  248. DBGPRINTF((DBG_CONTEXT,"Open MD /LM/MimeMap returns %d\n", GetLastError()));
  249. return FALSE;
  250. }
  251. if (!mb.GetMultisz(L"", MD_MIME_MAP, IIS_MD_UT_FILE, pmszMimeMap))
  252. {
  253. DBGPRINTF((DBG_CONTEXT,"Unable to read mime map from metabase: %d\n",GetLastError() ));
  254. mb.Close();
  255. return FALSE;
  256. }
  257. mb.Close();
  258. return TRUE;
  259. }
  260. static LPWSTR MMNextField(IN OUT LPWSTR *ppszFields)
  261. /*++
  262. This function separates and terminates the next field and returns a
  263. pointer to the same.
  264. Also it updates the incoming pointer to point to start of next field.
  265. The fields are assumed to be separated by commas.
  266. --*/
  267. {
  268. LPTSTR pszComma;
  269. LPTSTR pszField = NULL;
  270. DBG_ASSERT( ppszFields != NULL);
  271. //
  272. // Look for a comma in the input.
  273. // If none present, assume that rest of string
  274. // consists of the next field.
  275. //
  276. pszField = *ppszFields;
  277. if ((pszComma = wcschr(*ppszFields, L',')) != NULL)
  278. {
  279. //
  280. // update *ppszFields to contain the next field.
  281. //
  282. *ppszFields = pszComma + 1; // goto next field.
  283. *pszComma = L'\0';
  284. }
  285. else
  286. {
  287. //
  288. // Assume everything till end of string is the current field.
  289. //
  290. *ppszFields = *ppszFields + wcslen(*ppszFields) + 1;
  291. }
  292. pszField = ( *pszField == L'\0') ? NULL : pszField;
  293. return pszField;
  294. }
  295. static MIME_MAP_ENTRY *
  296. ReadAndParseMimeMapEntry(IN OUT LPWSTR *ppszValues)
  297. /*++
  298. This function parses the string containing next mime map entry and
  299. related fields and if successful creates a new MIME_MAP_ENTRY
  300. object and returns it.
  301. Otherwise it returns NULL.
  302. In either case, the incoming pointer is updated to point to next entry
  303. in the string ( past terminating NULL), assuming incoming pointer is a
  304. multi-string ( double null terminated).
  305. Arguments:
  306. ppszValues: pointer to MULTISZ containing the MimeEntry values.
  307. Returns:
  308. On successful MIME_ENTRY being parsed, a new MIME_MAP_ENTRY object.
  309. On error returns NULL.
  310. --*/
  311. {
  312. MIME_MAP_ENTRY *pMmeNew = NULL;
  313. DBG_ASSERT( ppszValues != NULL);
  314. LPWSTR pszMimeEntry = *ppszValues;
  315. if ( pszMimeEntry != NULL && *pszMimeEntry != L'\0')
  316. {
  317. LPWSTR pszMimeType;
  318. LPWSTR pszFileExt;
  319. pszFileExt = MMNextField(ppszValues);
  320. pszMimeType = MMNextField(ppszValues);
  321. if ((pszMimeType == NULL) ||
  322. (pszFileExt == NULL))
  323. {
  324. DBGPRINTF( ( DBG_CONTEXT,
  325. " ReadAndParseMimeEntry()."
  326. " Invalid Mime String ( %S)."
  327. "MimeType( %08x): %S, FileExt( %08x): %S\n",
  328. pszMimeEntry,
  329. pszMimeType, pszMimeType,
  330. pszFileExt, pszFileExt
  331. ));
  332. DBG_ASSERT( pMmeNew == NULL);
  333. }
  334. else
  335. {
  336. // Strip leading dot.
  337. if (*pszFileExt == '.')
  338. {
  339. pszFileExt++;
  340. }
  341. pMmeNew = new MIME_MAP_ENTRY( pszMimeType, pszFileExt);
  342. if ( pMmeNew != NULL && !pMmeNew->IsValid())
  343. {
  344. //
  345. // unable to create a new MIME_MAP_ENTRY object. Delete it.
  346. //
  347. delete pMmeNew;
  348. pMmeNew = NULL;
  349. }
  350. }
  351. }
  352. return pMmeNew;
  353. }
  354. MIME_MAP::MIME_MAP(LPWSTR pszMimeMappings)
  355. : CTypedHashTable<MIME_MAP, MIME_MAP_ENTRY, LPWSTR>("MimeMapper"),
  356. m_fValid (TRUE),
  357. m_pMmeDefault (NULL)
  358. {
  359. while (*pszMimeMappings != L'\0')
  360. {
  361. MIME_MAP_ENTRY *pMmeNew;
  362. pMmeNew = ReadAndParseMimeMapEntry(&pszMimeMappings);
  363. //
  364. // If New MimeMap entry found, Create a new object and update list
  365. //
  366. if ((pMmeNew != NULL) &&
  367. !AddMimeMapEntry(pMmeNew))
  368. {
  369. DBGPRINTF((DBG_CONTEXT,
  370. "MIME_MAP::InitFromRegistry()."
  371. " Failed to add new MIME Entry. Error = %d\n",
  372. GetLastError()));
  373. delete pMmeNew;
  374. }
  375. } // while
  376. }
  377. HRESULT MIME_MAP::InitFromMetabase()
  378. /*++
  379. Synopsis
  380. This function reads the MIME_MAP entries from metabase and parses
  381. the entry, creates MIME_MAP_ENTRY object and adds the object to list
  382. of MimeMapEntries.
  383. Returns:
  384. HRESULT
  385. Format of Storage in registry:
  386. The entries are stored in NT in tbe metabase with a list of values in
  387. following format: file-extension, mimetype
  388. --*/
  389. {
  390. HRESULT hr = S_OK;
  391. LPTSTR pszValueAlloc = NULL;
  392. LPTSTR pszValue;
  393. MULTISZ mszMimeMap;
  394. //
  395. // There is some registry key for Mime Entries. Try open and read.
  396. //
  397. if (!ReadMimeMapFromMetabase(&mszMimeMap))
  398. {
  399. mszMimeMap.Reset();
  400. if (!mszMimeMap.Append(g_pszDefaultMimeEntry))
  401. {
  402. return HRESULT_FROM_WIN32(GetLastError());
  403. }
  404. }
  405. // Ignore all errors.
  406. hr = S_OK;
  407. pszValue = (LPWSTR)mszMimeMap.QueryPtr();
  408. //
  409. // Parse each MimeEntry in the string containing list of mime objects.
  410. //
  411. for (; m_pMmeDefault == NULL; pszValue = g_pszDefaultMimeEntry)
  412. {
  413. while (*pszValue != L'\0')
  414. {
  415. MIME_MAP_ENTRY *pMmeNew;
  416. pMmeNew = ReadAndParseMimeMapEntry( &pszValue);
  417. //
  418. // If New MimeMap entry found, Create a new object and update list
  419. //
  420. if ((pMmeNew != NULL) &&
  421. !AddMimeMapEntry(pMmeNew))
  422. {
  423. DBGPRINTF((DBG_CONTEXT,
  424. "MIME_MAP::InitFromRegistry()."
  425. " Failed to add new MIME Entry. Error = %d\n",
  426. GetLastError()));
  427. delete pMmeNew;
  428. }
  429. } // while
  430. } // for
  431. return hr;
  432. }
  433. HRESULT MIME_MAP::InitFromRegistryChicagoStyle()
  434. /*++
  435. Synopsis
  436. This function reads the list of MIME content-types available for
  437. registered file extensions. Global list of MIME objects is updated with
  438. not yet added extensions. This method should be invoked after
  439. server-specific map had been read, so it does not overwrite extensions
  440. common for two.
  441. Arguments:
  442. None.
  443. Returns:
  444. HRESULT
  445. --*/
  446. {
  447. HKEY hkeyMimeMap = NULL;
  448. HKEY hkeyMimeType = NULL;
  449. HKEY hkeyExtension = NULL;
  450. DWORD dwIndexSubKey;
  451. DWORD dwMimeSizeAllowed;
  452. DWORD dwType;
  453. DWORD cbValue;
  454. LPWSTR pszMimeMap = NULL;
  455. WCHAR szSubKeyName[MAX_PATH];
  456. WCHAR szExtension[MAX_PATH];
  457. LPWSTR pszMimeType;
  458. //
  459. // Read content types from all registered extensions
  460. //
  461. DWORD dwError = RegOpenKeyEx(HKEY_CLASSES_ROOT, // hkey
  462. L"", // reg entry string
  463. 0, // dwReserved
  464. KEY_READ, // access
  465. &hkeyMimeMap); // pHkeyReturned.
  466. if ( dwError != NO_ERROR)
  467. {
  468. DBGPRINTF( ( DBG_CONTEXT,
  469. "MIME_MAP::InitFromRegistry(). Cannot open RegKey %s."
  470. "Error = %d\n",
  471. "HKCR_",
  472. dwError) );
  473. goto AddDefault;
  474. }
  475. dwIndexSubKey = 0;
  476. *szSubKeyName = '\0';
  477. pszMimeType = szSubKeyName ;
  478. dwError = RegEnumKey(hkeyMimeMap,
  479. dwIndexSubKey,
  480. szExtension,
  481. sizeof(szExtension)/sizeof(WCHAR));
  482. while (dwError == ERROR_SUCCESS)
  483. {
  484. //
  485. // Some entries in HKEY_CLASSES_ROOT are extensions ( start with dot)
  486. // and others are file types. We don't need file types here .
  487. //
  488. if (L'.' == *szExtension)
  489. {
  490. //
  491. // Got next eligible extension
  492. //
  493. dwError = RegOpenKeyEx(HKEY_CLASSES_ROOT, // hkey
  494. szExtension, // reg entry string
  495. 0, // dwReserved
  496. KEY_READ, // access
  497. &hkeyExtension); // pHkeyReturned.
  498. if (dwError != NO_ERROR)
  499. {
  500. DBGPRINTF( ( DBG_CONTEXT,
  501. "MIME_MAP::InitFromRegistry(). "
  502. " Cannot open RegKey HKEY_CLASSES_ROOT\\%S."
  503. "Ignoring Error = %d\n",
  504. szExtension,
  505. dwError));
  506. break;
  507. }
  508. //
  509. // Now get content type for this extension if present
  510. //
  511. *szSubKeyName = '\0';
  512. cbValue = sizeof szSubKeyName;
  513. dwError = RegQueryValueEx(hkeyExtension,
  514. L"Content Type",
  515. NULL,
  516. &dwType,
  517. (LPBYTE)&szSubKeyName[0],
  518. &cbValue);
  519. if (dwError == NO_ERROR)
  520. {
  521. //
  522. // Now we have MIME type and file extension
  523. // Create a new object and update list
  524. //
  525. if (!CreateAndAddMimeMapEntry(szSubKeyName, szExtension))
  526. {
  527. dwError = GetLastError();
  528. DBGPRINTF((DBG_CONTEXT,
  529. "MIME_MAP::InitFromRegistry()."
  530. " Failed to add new MIME Entry. Error = %d\n",
  531. dwError)) ;
  532. }
  533. }
  534. RegCloseKey(hkeyExtension);
  535. }
  536. //
  537. // Attempt to read next extension
  538. //
  539. dwIndexSubKey++;
  540. dwError = RegEnumKey(hkeyMimeMap,
  541. dwIndexSubKey,
  542. szExtension,
  543. sizeof(szExtension)/sizeof(WCHAR));
  544. } // end_while
  545. dwError = RegCloseKey( hkeyMimeMap);
  546. AddDefault:
  547. //
  548. // Now after we are done with registry mapping - add default MIME type
  549. // in case if NT database does not exist
  550. //
  551. if (!CreateAndAddMimeMapEntry(g_pszDefaultMimeType,
  552. g_pszDefaultFileExt))
  553. {
  554. dwError = GetLastError();
  555. DBGPRINTF( ( DBG_CONTEXT,
  556. "MIME_MAP::InitFromRegistry()."
  557. "Failed to add new MIME Entry. Error = %d\n",
  558. dwError) );
  559. }
  560. return S_OK;
  561. }
  562. HRESULT InitializeMimeMap(IN LPWSTR pszRegEntry)
  563. /*++
  564. Creates a new mime map object and loads the registry entries from
  565. under this entry from \\MimeMap.
  566. --*/
  567. {
  568. HRESULT hr = E_FAIL;
  569. DBG_ASSERT(g_pMimeMap == NULL);
  570. g_pMimeMap = new MIME_MAP();
  571. if (g_pMimeMap == NULL)
  572. {
  573. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  574. }
  575. if (FAILED(hr = g_pMimeMap->InitMimeMap()))
  576. {
  577. DBGPRINTF((DBG_CONTEXT,"InitMimeMap failed with hr %x\n", hr));
  578. }
  579. return hr;
  580. } // InitializeMimeMap()
  581. VOID CleanupMimeMap()
  582. {
  583. if ( g_pMimeMap != NULL)
  584. {
  585. delete g_pMimeMap;
  586. g_pMimeMap = NULL;
  587. }
  588. } // CleanupMimeMap()
  589. HRESULT SelectMimeMappingForFileExt(IN WCHAR *pszFilePath,
  590. IN MIME_MAP *pMimeMap,
  591. OUT STRA *pstrMimeType)
  592. /*++
  593. Synopsis
  594. Obtains the mime type for file based on the file extension.
  595. Arguments
  596. pszFilePath pointer to path for the given file
  597. pstrMimeType pointer to string to store the mime type on return
  598. Returns:
  599. HRESULT
  600. --*/
  601. {
  602. HRESULT hr = S_OK;
  603. DBG_ASSERT (pstrMimeType != NULL);
  604. const MIME_MAP_ENTRY *pMmeMatch = NULL;
  605. //
  606. // Lookup in the metabase entry
  607. //
  608. if (pMimeMap)
  609. {
  610. pMmeMatch = pMimeMap->LookupMimeEntryForFileExt(pszFilePath);
  611. }
  612. //
  613. // If not found, lookup in the global entry
  614. //
  615. if (!pMmeMatch)
  616. {
  617. pMmeMatch = g_pMimeMap->LookupMimeEntryForFileExt(pszFilePath);
  618. }
  619. DBG_ASSERT(pMmeMatch != NULL);
  620. return pstrMimeType->CopyWTruncate(pMmeMatch->QueryMimeType());
  621. }