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.

1887 lines
52 KiB

  1. /*
  2. * C O N T E N T . C P P
  3. *
  4. * DAV content types
  5. *
  6. * Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
  7. */
  8. #include "_davprs.h"
  9. #include "content.h"
  10. #include <ex\reg.h>
  11. // ------------------------------------------------------------------------
  12. //
  13. // CchExtMapping()
  14. //
  15. // Returns the size in characters required for a single mapping for
  16. // writing to the metabase.
  17. //
  18. // The format of a mapping is null-terminated, comma-delimited string
  19. // (e.g. ".ext,application/ext").
  20. //
  21. inline UINT
  22. CchExtMapping( UINT cchExt,
  23. UINT cchContentType )
  24. {
  25. return (cchExt +
  26. 1 + // ','
  27. cchContentType +
  28. 1); // '\0'
  29. }
  30. // ------------------------------------------------------------------------
  31. //
  32. // PwchFormatExtMapping()
  33. //
  34. // Formats a single mapping for writing to the metabase.
  35. //
  36. // The format of a mapping is null-terminated, comma-delimited string
  37. // (e.g. ".ext,application/ext").
  38. //
  39. // This function returns a pointer to the character beyond the null terminator
  40. // in the formatted mapping.
  41. //
  42. inline WCHAR *
  43. PwchFormatExtMapping( WCHAR * pwchBuf,
  44. LPCWSTR pwszExt,
  45. UINT cchExt,
  46. LPCWSTR pwszContentType,
  47. UINT cchContentType )
  48. {
  49. Assert(!IsBadReadPtr(pwszExt, sizeof(WCHAR) * (cchExt+1)));
  50. Assert(!IsBadReadPtr(pwszContentType, sizeof(WCHAR) * (cchContentType+1)));
  51. Assert(!IsBadWritePtr(pwchBuf, sizeof(WCHAR) * CchExtMapping(cchExt, cchContentType)));
  52. // Dump in the extension first ...
  53. //
  54. memcpy(pwchBuf,
  55. pwszExt,
  56. sizeof(WCHAR) * cchExt);
  57. pwchBuf += cchExt;
  58. // ... followed by a comma
  59. //
  60. *pwchBuf++ = L',';
  61. // ... followed by the content type
  62. //
  63. memcpy(pwchBuf,
  64. pwszContentType,
  65. sizeof(WCHAR) * cchContentType);
  66. pwchBuf += cchContentType;
  67. // ... and null-terminated.
  68. //
  69. *pwchBuf++ = '\0';
  70. return pwchBuf;
  71. }
  72. // ========================================================================
  73. //
  74. // CLASS CContentTypeMap
  75. //
  76. class CContentTypeMap : public IContentTypeMap
  77. {
  78. // Cache of mappings from filename extensions to content types
  79. // (e.g. ".txt" --> "text/plain")
  80. //
  81. typedef CCache<CRCWszi, LPCWSTR> CMappingsCache;
  82. CMappingsCache m_cache;
  83. // Flag set if the mappings came from an inherited mime map.
  84. //
  85. BOOL m_fIsInherited;
  86. // CREATORS
  87. //
  88. CContentTypeMap(BOOL fMappingsInherited) :
  89. m_fIsInherited(fMappingsInherited)
  90. {
  91. }
  92. BOOL CContentTypeMap::FInit( LPWSTR pwszContentTypeMappings );
  93. // NOT IMPLEMENTED
  94. //
  95. CContentTypeMap(const CContentTypeMap&);
  96. CContentTypeMap& operator=(CContentTypeMap&);
  97. public:
  98. // CREATORS
  99. //
  100. static CContentTypeMap * New( LPWSTR pwszContentTypeMappings,
  101. BOOL fMappingsInherited );
  102. // ACCESSORS
  103. //
  104. LPCWSTR PwszContentType( LPCWSTR pwszExt ) const
  105. {
  106. LPCWSTR * ppwszContentType = m_cache.Lookup( CRCWszi(pwszExt) );
  107. //
  108. // Return the content type (if there was one).
  109. // Note that the returned pointer is good only
  110. // for the lifetime of the IMDData object that
  111. // scopes us since that is where the raw data lives.
  112. //
  113. return ppwszContentType ? *ppwszContentType : NULL;
  114. }
  115. BOOL FIsInherited() const { return m_fIsInherited; }
  116. };
  117. // ------------------------------------------------------------------------
  118. //
  119. // CContentTypeMap::FInit()
  120. //
  121. BOOL
  122. CContentTypeMap::FInit( LPWSTR pwszContentTypeMappings )
  123. {
  124. Assert( pwszContentTypeMappings );
  125. //
  126. // Initialize the cache of mappings
  127. //
  128. if ( !m_cache.FInit() )
  129. return FALSE;
  130. //
  131. // The format of the data in the mappings is a sequence of
  132. // null-terminated strings followed by an additional null.
  133. // Each string is of the format ".ext,type/subtype".
  134. //
  135. //
  136. // Parse out the extension and type/subtype for each
  137. // item and add a corresponding mapping to the cache.
  138. //
  139. for ( LPWSTR pwszMapping = pwszContentTypeMappings; *pwszMapping; )
  140. {
  141. enum {
  142. ISZ_CT_EXT = 0,
  143. ISZ_CT_TYPE,
  144. CSZ_CT_FIELDS
  145. };
  146. LPWSTR rgpwsz[CSZ_CT_FIELDS];
  147. UINT cchMapping;
  148. //
  149. // Digest the metadata
  150. //
  151. if ( !FParseMDData( pwszMapping,
  152. rgpwsz,
  153. CSZ_CT_FIELDS,
  154. &cchMapping ) )
  155. {
  156. DebugTrace( "CContentTypeMap::FInit() - Malformed metadata\n" );
  157. return FALSE;
  158. }
  159. Assert(rgpwsz[ISZ_CT_EXT]);
  160. //
  161. // Verify that the first field is an extension or '*'
  162. //
  163. if ( L'.' != *rgpwsz[ISZ_CT_EXT] && wcscmp(rgpwsz[ISZ_CT_EXT], gc_wsz_Star) )
  164. {
  165. DebugTrace( "CContentTypeMap::FInit() - Bad extension\n" );
  166. return FALSE;
  167. }
  168. Assert(rgpwsz[ISZ_CT_TYPE]);
  169. //
  170. // Whatever there is in the second field is expected to be the
  171. // content type. Note that we don't do any syntactic checking
  172. // there.
  173. // The only special case we handle there is if the content
  174. // type is a blank string. As IIS 6.0 treats that kind as
  175. // application/octet-stream we will achieve the same behaviour
  176. // by simply ignoring such bad content type that will make us
  177. // default to application/octet-stream too. So omit content types
  178. // with blank values.
  179. //
  180. if (L'\0' != *rgpwsz[ISZ_CT_TYPE])
  181. {
  182. // Add a mapping from the extension to the content type
  183. //
  184. if ( !m_cache.FSet(CRCWszi(rgpwsz[ISZ_CT_EXT]), rgpwsz[ISZ_CT_TYPE]) )
  185. return FALSE;
  186. }
  187. //
  188. // Get the next mapping
  189. //
  190. pwszMapping += cchMapping;
  191. }
  192. return TRUE;
  193. }
  194. // ------------------------------------------------------------------------
  195. //
  196. // CContentTypeMap::New()
  197. //
  198. CContentTypeMap *
  199. CContentTypeMap::New( LPWSTR pwszContentTypeMappings,
  200. BOOL fMappingsInherited )
  201. {
  202. auto_ref_ptr<CContentTypeMap> pContentTypeMap;
  203. pContentTypeMap.take_ownership(new CContentTypeMap(fMappingsInherited));
  204. if ( pContentTypeMap->FInit(pwszContentTypeMappings) )
  205. return pContentTypeMap.relinquish();
  206. return NULL;
  207. }
  208. // ------------------------------------------------------------------------
  209. //
  210. // NewContentTypeMap()
  211. //
  212. // Creates a new content type map from a string of content type mappings.
  213. //
  214. IContentTypeMap *
  215. NewContentTypeMap( LPWSTR pwszContentTypeMappings,
  216. BOOL fMappingsInherited )
  217. {
  218. return CContentTypeMap::New( pwszContentTypeMappings,
  219. fMappingsInherited );
  220. }
  221. // ========================================================================
  222. //
  223. // CLASS CRegMimeMap
  224. //
  225. // Global registry-based mime map from file extension to content type.
  226. //
  227. class CRegMimeMap : public Singleton<CRegMimeMap>
  228. {
  229. //
  230. // Friend declarations required by Singleton template
  231. //
  232. friend class Singleton<CRegMimeMap>;
  233. //
  234. // String buffer for cached strings
  235. //
  236. ChainedStringBuffer<WCHAR> m_sb;
  237. // Cache of mappings from filename extensions to content types
  238. // (e.g. ".txt" --> "text/plain")
  239. //
  240. CCache<CRCWszi, LPCWSTR> m_cache;
  241. // A R/W lock that we will use when when reading from
  242. // the cache or when adding cache misses This lock
  243. // is used by PszContentType().
  244. //
  245. // FInitialize() does not use this lock (only initializes it)
  246. // because it is called during dll load and we don't need to
  247. // protect ourselves during dll load
  248. //
  249. CMRWLock m_rwl;
  250. // CREATORS
  251. //
  252. CRegMimeMap() {}
  253. // NOT IMPLEMENTED
  254. //
  255. CRegMimeMap(const CRegMimeMap&);
  256. CRegMimeMap& operator=(CRegMimeMap&);
  257. public:
  258. // CREATORS
  259. //
  260. using Singleton<CRegMimeMap>::CreateInstance;
  261. using Singleton<CRegMimeMap>::DestroyInstance;
  262. BOOL FInitialize();
  263. // ACCESSORS
  264. //
  265. using Singleton<CRegMimeMap>::Instance;
  266. // Given an extension, return the Content-Type
  267. // from the registry.
  268. //$NOTE: This was a const function before but it
  269. // cannot be a const function anymore because we
  270. // can add to our caches on cache misses.
  271. //
  272. LPCWSTR PwszContentType( LPCWSTR pwszExt );
  273. };
  274. // ------------------------------------------------------------------------
  275. //
  276. // CRegMimeMap::FInitialize()
  277. //
  278. // Load up the registry mappings. Any kind of failure (short of an
  279. // exception) is not considered fatal. It just means that we will
  280. // rely on the superceding metabase mappings.
  281. //
  282. BOOL
  283. CRegMimeMap::FInitialize()
  284. {
  285. BOOL fRet = FALSE;
  286. CRegKey regkeyClassesRoot;
  287. DWORD dwResult;
  288. //
  289. // Initialize the cache of mappings
  290. //
  291. if ( !m_cache.FInit() )
  292. goto ret;
  293. // Init the R/W lock.
  294. //
  295. if (!m_rwl.FInitialize())
  296. goto ret;
  297. //
  298. // Read in the mapping information from the registry
  299. //
  300. // Get the base of the classes hierarchy in the registry
  301. //
  302. dwResult = regkeyClassesRoot.DwOpen( HKEY_CLASSES_ROOT, L"" );
  303. if ( dwResult != NO_ERROR )
  304. goto ret;
  305. // Iterate over all the entries looking for content-type associations
  306. //
  307. for ( DWORD iMapping = 0;; iMapping++ )
  308. {
  309. WCHAR wszSubKey[MAX_PATH];
  310. DWORD cchSubKey;
  311. DWORD dwDataType;
  312. CRegKey regkeySub;
  313. WCHAR wszContentType[MAX_PATH] = {0};
  314. DWORD cbContentType = MAX_PATH;
  315. //
  316. // Locate the next subkey. If there isn't one then we're done.
  317. //
  318. cchSubKey = CElems(wszSubKey);
  319. dwResult = regkeyClassesRoot.DwEnumSubKey( iMapping, wszSubKey, &cchSubKey );
  320. if ( dwResult != NO_ERROR )
  321. {
  322. //
  323. // Ignore keys that are larger than MAX_PATH.
  324. // Note that keys larger than MAX_PATH shouldn't be allowed
  325. // but if we didn't check and then hit one initialization
  326. // would fail
  327. //
  328. if ( ERROR_MORE_DATA == dwResult )
  329. continue;
  330. fRet = (ERROR_NO_MORE_ITEMS == dwResult);
  331. goto ret;
  332. }
  333. //
  334. // Open that subkey.
  335. //
  336. dwResult = regkeySub.DwOpen( regkeyClassesRoot, wszSubKey );
  337. if ( dwResult != NO_ERROR )
  338. continue;
  339. //
  340. // Get the associated Media-Type (Content-Type)
  341. //
  342. dwResult = regkeySub.DwQueryValue( L"Content Type",
  343. wszContentType,
  344. &cbContentType,
  345. &dwDataType );
  346. if ( dwResult != NO_ERROR || dwDataType != REG_SZ )
  347. continue;
  348. //
  349. // Add a mapping for this extension/content type pair.
  350. //
  351. // Note: FAdd() cannot fail here -- FAdd() only fails on
  352. // allocator failures. Our allocators throw.
  353. //
  354. (VOID) m_cache.FAdd (CRCWszi(m_sb.AppendWithNull(wszSubKey)),
  355. m_sb.AppendWithNull(wszContentType));
  356. }
  357. ret:
  358. return fRet;
  359. }
  360. LPCWSTR
  361. CRegMimeMap::PwszContentType( LPCWSTR pwszExt )
  362. {
  363. LPCWSTR pwszContentType = NULL;
  364. LPCWSTR * ppwszContentType = NULL;
  365. CRegKey regkeyClassesRoot;
  366. CRegKey regkeySub;
  367. DWORD dwResult;
  368. DWORD dwDataType;
  369. WCHAR prgwchContentType[MAX_PATH] = {0};
  370. DWORD cbContentType;
  371. // Grab a reader lock and check the cache.
  372. //
  373. {
  374. CSynchronizedReadBlock srb(m_rwl);
  375. ppwszContentType = m_cache.Lookup( CRCWszi(pwszExt) );
  376. }
  377. //
  378. // Return the content type (if there was one).
  379. // Note that the returned pointer is good only
  380. // for the lifetime of the cache (since we never
  381. // modify the cache after class initialization)
  382. // which, in turn, is only good for the lifetime
  383. // of this object. The external interface functions
  384. // FGetContentTypeFromPath() and FGetContentTypeFromURI()
  385. // both copy the returned content type into caller-supplied
  386. // buffers.
  387. //
  388. if (ppwszContentType)
  389. {
  390. pwszContentType = *ppwszContentType;
  391. goto ret;
  392. }
  393. // Otherwise, read in the mapping information from the registry
  394. //
  395. // Get the base of the classes hierarchy in the registry
  396. //
  397. dwResult = regkeyClassesRoot.DwOpen( HKEY_CLASSES_ROOT, L"" );
  398. if ( dwResult != NO_ERROR )
  399. goto ret;
  400. // Open that subkey of the extension we are looking for.
  401. //
  402. dwResult = regkeySub.DwOpen( regkeyClassesRoot, pwszExt );
  403. if ( dwResult != NO_ERROR )
  404. goto ret;
  405. // Get the associated Media-Type (Content-Type)
  406. //
  407. cbContentType = sizeof(prgwchContentType);
  408. dwResult = regkeySub.DwQueryValue( L"Content Type",
  409. prgwchContentType,
  410. &cbContentType,
  411. &dwDataType );
  412. if ( dwResult != NO_ERROR || dwDataType != REG_SZ )
  413. goto ret;
  414. // Before adding the mapping for this extension/content type
  415. // pair to the cache, take a writer lock and check the cache
  416. // to see if someone has beaten us to it.
  417. //
  418. // Grab a reader lock and check the cache.
  419. //
  420. {
  421. CSynchronizedWriteBlock swb(m_rwl);
  422. ppwszContentType = m_cache.Lookup( CRCWszi(pwszExt) );
  423. if (ppwszContentType)
  424. {
  425. pwszContentType = *ppwszContentType;
  426. goto ret;
  427. }
  428. pwszContentType = m_sb.AppendWithNull(prgwchContentType);
  429. Assert (pwszContentType);
  430. // Note: FAdd() cannot fail here -- FAdd() only fails on
  431. // allocator failures. Our allocators throw.
  432. //
  433. (VOID) m_cache.FAdd (CRCWszi(m_sb.AppendWithNull(pwszExt)),
  434. pwszContentType);
  435. }
  436. ret:
  437. return pwszContentType;
  438. }
  439. // ------------------------------------------------------------------------
  440. //
  441. // FInitRegMimeMap()
  442. //
  443. BOOL
  444. FInitRegMimeMap()
  445. {
  446. return CRegMimeMap::CreateInstance().FInitialize();
  447. }
  448. // ------------------------------------------------------------------------
  449. //
  450. // DeinitRegMimeMap()
  451. //
  452. VOID
  453. DeinitRegMimeMap()
  454. {
  455. CRegMimeMap::DestroyInstance();
  456. }
  457. // ------------------------------------------------------------------------
  458. //
  459. // HrGetContentTypeByExt()
  460. //
  461. // Fetch the content type of a resource based on its path/URI extension.
  462. // This function searches the following three places, in order, for a mapping:
  463. //
  464. // 1) a caller-supplied content type map
  465. // 2) the global (metabase) content type map
  466. // 3) the global (registry) content type map
  467. //
  468. // Parameters:
  469. //
  470. // pContentTypeMapLocal [IN] If non-NULL, points to the content type
  471. // map to search first.
  472. //
  473. // pwszExt [IN] Extension to search on
  474. // pwszBuf [OUT] Buffer in which to copy the mapped
  475. // content type
  476. // pcchBuf [IN] Size of buffer in characters including 0 termination
  477. // [OUT] Size of mapped content type
  478. //
  479. // pfIsGlobalMapping [OUT] (Optional) Pointer to flag which is set
  480. // if the mapping is from a global map.
  481. //
  482. // Returns:
  483. //
  484. // S_OK
  485. // if a mapping was found and copied into the caller-supplied buffer
  486. // The size of the mapped content type is returned in *pcchzBuf.
  487. //
  488. // HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)
  489. // if no mapping was found in any of the maps
  490. //
  491. // HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY)
  492. // if a mapping was found, but the caller supplied buffer was too small.
  493. // The required size of the buffer is returned in *pcchzBuf.
  494. //
  495. HRESULT
  496. HrGetContentTypeByExt( const IEcb& ecb,
  497. const IContentTypeMap * pContentTypeMapLocal,
  498. LPCWSTR pwszExt,
  499. LPWSTR pwszBuf,
  500. UINT * pcchBuf,
  501. BOOL * pfIsGlobalMapping )
  502. {
  503. Assert(!pfIsGlobalMapping || !IsBadWritePtr(pfIsGlobalMapping, sizeof(BOOL)));
  504. LPCWSTR pwszContentType = NULL;
  505. auto_ref_ptr<IMDData> pMDData;
  506. const IContentTypeMap * pContentTypeMapGlobal;
  507. //
  508. // If a local map was specified then check it first for
  509. // the extension based mapping.
  510. //
  511. if ( pContentTypeMapLocal )
  512. pwszContentType = pContentTypeMapLocal->PwszContentType(pwszExt);
  513. //
  514. // If this doesn't yield a mapping then try the global mime map.
  515. // Note: if we fail to get any metadata for the global mime map
  516. // then use gc_szAppl_Octet_Stream rather than trying the registry.
  517. // We'd rather use a "safe" default than a possibly intentionally
  518. // overridden value from the registry.
  519. //
  520. if ( !pwszContentType )
  521. {
  522. if ( SUCCEEDED(HrMDGetData(ecb, gc_wsz_Lm_MimeMap, gc_wsz_Lm_MimeMap, pMDData.load())) )
  523. {
  524. pContentTypeMapGlobal = pMDData->GetContentTypeMap();
  525. if ( pContentTypeMapGlobal )
  526. {
  527. pwszContentType = pContentTypeMapGlobal->PwszContentType(pwszExt);
  528. if (pwszContentType && pfIsGlobalMapping)
  529. *pfIsGlobalMapping = TRUE;
  530. }
  531. }
  532. else
  533. {
  534. pwszContentType = gc_wszAppl_Octet_Stream;
  535. }
  536. }
  537. //
  538. // Nothing in the global mime map either?
  539. // Then try the registry as a last resort.
  540. //
  541. if ( !pwszContentType )
  542. {
  543. pwszContentType = CRegMimeMap::Instance().PwszContentType(pwszExt);
  544. if (pwszContentType && pfIsGlobalMapping)
  545. *pfIsGlobalMapping = TRUE;
  546. }
  547. //
  548. // If there wasn't anything in the registry either then there is
  549. // no mapping for this extension.
  550. //
  551. if ( !pwszContentType )
  552. return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
  553. //
  554. // If we did find a mapping via one of the above methods
  555. // then attempt to copy it into the caller-supplied buffer.
  556. // If the buffer is not big enough, return an appropriate error.
  557. // Note: FCopyStringToBuf() will fill in the required size
  558. // if the buffer was not big enough.
  559. //
  560. return FCopyStringToBuf( pwszContentType,
  561. pwszBuf,
  562. pcchBuf ) ?
  563. S_OK : HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
  564. }
  565. // ------------------------------------------------------------------------
  566. //
  567. // PszExt()
  568. //
  569. // Returns any extension (i.e. characters including
  570. // and following a '.') appearing in the string pointed
  571. // to by pchPathBegin that appear at or before pchPathEnd.
  572. //
  573. // Returns NULL if there is no extension.
  574. //
  575. inline LPCWSTR
  576. PwszExt( LPCWSTR pwchPathBegin,
  577. LPCWSTR pwchPathEnd )
  578. {
  579. Assert(pwchPathEnd);
  580. //
  581. // Scan backward from the designated end of the path looking
  582. // for a '.' that begins an extension. If we don't find one
  583. // or we find a path separator ('/') then there is no extension.
  584. //
  585. while ( pwchPathEnd-- > pwchPathBegin )
  586. {
  587. if ( L'.' == *pwchPathEnd )
  588. return pwchPathEnd;
  589. if ( L'/' == *pwchPathEnd )
  590. return NULL;
  591. }
  592. return NULL;
  593. }
  594. // ------------------------------------------------------------------------
  595. //
  596. // FGetContentType()
  597. //
  598. // Fetches the content type of the resource at the specified path/URI
  599. // and copies it into a caller-supplied buffer.
  600. //
  601. // The copied content type comes from one of the following mappings:
  602. //
  603. // 1) Via an explicit mapping from the specified path/URI extension.
  604. // 2) Via a ".*" (default) mapping
  605. // 3) application/octet-stream
  606. //
  607. // Parameters:
  608. //
  609. // pContentTypeMapLocal [IN] If non-NULL, points to a content type
  610. // map for HrGetContentTypeByExt() to
  611. // search first for each of the first
  612. // two methods above.
  613. //
  614. // pwszPath [IN] Path whose content type is desired.
  615. // pwszBuf [OUT] Buffer in which to copy the mapped
  616. // content type
  617. // pcchBuf [IN] Size of buffer in characters including 0 termination
  618. // [OUT] Size of mapped content type
  619. //
  620. // pfIsGlobalMapping [OUT] (Optional) Pointer to flag which is set
  621. // if the mapping is from a global map.
  622. //
  623. // Returns:
  624. //
  625. // TRUE
  626. // if the mapping was successfully copied into the caller-supplied buffer.
  627. // The size of the mapped content type is returned in *pcchzBuf.
  628. //
  629. // FALSE
  630. // if the caller-supplied buffer was too small.
  631. // The required size of the buffer is returned in *pcchzBuf.
  632. //
  633. BOOL
  634. FGetContentType( const IEcb& ecb,
  635. const IContentTypeMap * pContentTypeMapLocal,
  636. LPCWSTR pwszPath,
  637. LPWSTR pwszBuf,
  638. UINT * pcchBuf,
  639. BOOL * pfIsGlobalMapping )
  640. {
  641. HRESULT hr;
  642. CStackBuffer<WCHAR> pwszCopy;
  643. BOOL fCopy = FALSE;
  644. UINT cchPath = static_cast<UINT>(wcslen(pwszPath));
  645. // Scan backward to skip all '/' characters at the end.
  646. //
  647. while ( cchPath && (L'/' == pwszPath[cchPath-1]) )
  648. {
  649. cchPath--;
  650. fCopy = TRUE; // Fine to keep the assignment here, as clients usually
  651. // do not put multiple wacks at the end of the path.
  652. }
  653. if (fCopy)
  654. {
  655. // Make the copy of the path without ending wacks.
  656. //
  657. if (!pwszCopy.resize(CbSizeWsz(cchPath)))
  658. return FALSE;
  659. memcpy( pwszCopy.get(), pwszPath, cchPath * sizeof(WCHAR) );
  660. pwszCopy[cchPath] = L'\0';
  661. // Swap the pointers
  662. //
  663. pwszPath = pwszCopy.get();
  664. }
  665. //
  666. // First check for an extension mapping in both the specified
  667. // content type map and the global mime map.
  668. //
  669. // The loop checks progressively longer extensions. E.g. a path
  670. // of "/foo/bar/baz.a.b.c" will be checked for ".c" then ".b.c"
  671. // then ".a.b.c". This is consistent with IIS' behavior.
  672. //
  673. for ( LPCWSTR pwszExt = PwszExt(pwszPath, pwszPath + cchPath);
  674. pwszExt;
  675. pwszExt = PwszExt(pwszPath, pwszExt) )
  676. {
  677. hr = HrGetContentTypeByExt( ecb,
  678. pContentTypeMapLocal,
  679. pwszExt,
  680. pwszBuf,
  681. pcchBuf,
  682. pfIsGlobalMapping );
  683. if ( HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) != hr )
  684. {
  685. Assert( S_OK == hr || HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY) == hr );
  686. return SUCCEEDED(hr);
  687. }
  688. }
  689. //
  690. // There is no extension mapping so check both maps
  691. // for a ".*" (default) mapping. Note: don't set *pfIsGlobalMapping if
  692. // the ".*" mapping is the only one that applies. The ".*" mapping is
  693. // a catch-all; it is ok for local mime maps to override it.
  694. //
  695. hr = HrGetContentTypeByExt( ecb,
  696. pContentTypeMapLocal,
  697. L".*",
  698. pwszBuf,
  699. pcchBuf,
  700. NULL );
  701. if ( HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) != hr )
  702. {
  703. Assert( S_OK == hr || HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY) == hr );
  704. return SUCCEEDED(hr);
  705. }
  706. //
  707. // No ".*" mapping either so use the default default --
  708. // application/octet-stream.
  709. //
  710. return FCopyStringToBuf( gc_wszAppl_Octet_Stream,
  711. pwszBuf,
  712. pcchBuf );
  713. }
  714. // ------------------------------------------------------------------------
  715. //
  716. // FGetContentTypeFromPath()
  717. //
  718. // Fetch the content type associated with the extension of the
  719. // specified file path.
  720. //
  721. BOOL FGetContentTypeFromPath( const IEcb& ecb,
  722. LPCWSTR pwszPath,
  723. LPWSTR pwszBuf,
  724. UINT * pcchBuf )
  725. {
  726. return FGetContentType( ecb,
  727. NULL, // No local map to check
  728. pwszPath,
  729. pwszBuf,
  730. pcchBuf,
  731. NULL ); // Don't care where the mapping comes from
  732. }
  733. // ------------------------------------------------------------------------
  734. //
  735. // FGetContentTypeFromURI()
  736. //
  737. // Retrieves the content type for the specified URI.
  738. //
  739. BOOL
  740. FGetContentTypeFromURI( const IEcb& ecb,
  741. LPCWSTR pwszURI,
  742. LPWSTR pwszBuf,
  743. UINT * pcchBuf,
  744. BOOL * pfIsGlobalMapping )
  745. {
  746. auto_ref_ptr<IMDData> pMDData;
  747. //
  748. // Fetch the metadata for this URI. If it has a content type map
  749. // then use it to look for a mapping. If it does not have a content
  750. // type map then check the global mime map.
  751. //
  752. // Note: if we fail to get the metadata at all then default the
  753. // content type to application/octet-stream. Do not use the global
  754. // mime map just because we cannot get the metadata.
  755. //
  756. if ( FAILED(HrMDGetData(ecb, pwszURI, pMDData.load())) )
  757. {
  758. DebugTrace( "FGetContentTypeFromURI() - HrMDGetData() failed to get metadata for %S. Using application/octet-stream...\n", pwszURI );
  759. return FCopyStringToBuf( gc_wszAppl_Octet_Stream,
  760. pwszBuf,
  761. pcchBuf );
  762. }
  763. const IContentTypeMap * pContentTypeMap = pMDData->GetContentTypeMap();
  764. //
  765. // If there is a content type map specific to this URI then
  766. // try it first looking for a "*" (unconditional) mapping.
  767. //
  768. if ( pContentTypeMap )
  769. {
  770. LPCWSTR pwszContentType = pContentTypeMap->PwszContentType(gc_wsz_Star);
  771. if ( pwszContentType )
  772. return FCopyStringToBuf( pwszContentType,
  773. pwszBuf,
  774. pcchBuf );
  775. }
  776. //
  777. // There was either no "*" mapping or no URI-specific map
  778. // so check the global maps
  779. //
  780. return FGetContentType( ecb,
  781. pContentTypeMap,
  782. pwszURI,
  783. pwszBuf,
  784. pcchBuf,
  785. pfIsGlobalMapping );
  786. }
  787. // ------------------------------------------------------------------------
  788. //
  789. // ScApplyStarExt()
  790. //
  791. // Determines whether the mapping "*" --> pwszContentType should be used
  792. // instead of the mapping *ppwszExt --> pwszContentType based on the
  793. // following criteria:
  794. //
  795. // Use the mapping "*" --> pwszContentType if:
  796. //
  797. // o *ppwszExt is already "*", OR
  798. // o a mapping exists in pwszMappings for *ppwszExt whose content type
  799. // is not the same as pwszContentType, OR
  800. // o a "*" mapping exists in pwszMappings.
  801. //
  802. // Use *ppwszExt --> pwszContentType otherwise.
  803. //
  804. // Returns:
  805. //
  806. // The value returned in *ppwszExt indicates the mapping to be used.
  807. //
  808. SCODE
  809. ScApplyStarExt( LPWSTR pwszMappings,
  810. LPCWSTR pwszContentType,
  811. LPCWSTR * ppwszExt )
  812. {
  813. SCODE sc = S_OK;
  814. Assert(pwszMappings);
  815. Assert(!IsBadWritePtr(ppwszExt, sizeof(LPCWSTR)));
  816. Assert(*ppwszExt);
  817. Assert(pwszContentType);
  818. //
  819. // Parse out the extension and type/subtype for each
  820. // item and check for conflicts or "*" mappings.
  821. //
  822. for ( LPWSTR pwszMapping = pwszMappings;
  823. L'*' != *(*ppwszExt) && *pwszMapping; )
  824. {
  825. enum {
  826. ISZ_CT_EXT = 0,
  827. ISZ_CT_TYPE,
  828. CSZ_CT_FIELDS
  829. };
  830. LPWSTR rgpwsz[CSZ_CT_FIELDS];
  831. //
  832. // Digest the metadata for this mapping
  833. //
  834. {
  835. UINT cchMapping;
  836. if ( !FParseMDData( pwszMapping,
  837. rgpwsz,
  838. CSZ_CT_FIELDS,
  839. &cchMapping ) )
  840. {
  841. sc = E_FAIL;
  842. DebugTrace("ScApplyStarExt() - Malformed metadata 0x%08lX\n", sc);
  843. goto ret;
  844. }
  845. pwszMapping += cchMapping;
  846. }
  847. Assert(rgpwsz[ISZ_CT_EXT]);
  848. Assert(rgpwsz[ISZ_CT_TYPE]);
  849. //
  850. // If this is a "*" mapping OR
  851. // If the extension matches *ppszExt AND
  852. // the content types conflict
  853. //
  854. // then use a "*" mapping.
  855. //
  856. if ((L'*' == *rgpwsz[ISZ_CT_EXT]) ||
  857. (!_wcsicmp((*ppwszExt), rgpwsz[ISZ_CT_EXT]) &&
  858. _wcsicmp(pwszContentType, rgpwsz[ISZ_CT_TYPE])))
  859. {
  860. *ppwszExt = gc_wsz_Star;
  861. }
  862. //
  863. // !!!IMPORTANT!!! FParseMDData() munges the mapping string.
  864. // Specifically, it replaces the comma separator with a null.
  865. // We always need to restore the comma so that the mappings
  866. // string is not modified by this function!
  867. //
  868. *(rgpwsz[ISZ_CT_EXT] + wcslen(rgpwsz[ISZ_CT_EXT])) = L',';
  869. }
  870. ret:
  871. return sc;
  872. }
  873. DEC_CONST WCHAR gc_wszIisWebFile[] = L"IisWebFile";
  874. // ------------------------------------------------------------------------
  875. //
  876. // ScAddMimeMap()
  877. //
  878. // Adds the mapping pwszExt --> pwszContentType to the mime map at
  879. // the metabase path pwszMDPath relative to the currently open
  880. // metabase handle mdoh, creating a new mime map as required.
  881. //
  882. // A new mime map is required when there is no existing mime map
  883. // (pwszMappings is NULL) or if a "*" mapping is being set. In the
  884. // latter case, the "*" map overwrites whatever mapping is there.
  885. //
  886. SCODE
  887. ScAddMimeMap( const CMDObjectHandle& mdoh,
  888. LPCWSTR pwszMDPath,
  889. LPWSTR pwszMappings,
  890. UINT cchMappings,
  891. LPCWSTR pwszExt,
  892. LPCWSTR pwszContentType )
  893. {
  894. CStackBuffer<WCHAR> wszBuf;
  895. UINT cchContentType;
  896. UINT cchExt;
  897. WCHAR * pwch;
  898. Assert(pwszExt);
  899. Assert(pwszContentType);
  900. cchExt = static_cast<UINT>(wcslen(pwszExt));
  901. cchContentType = static_cast<UINT>(wcslen(pwszContentType));
  902. // If content type we want to set is blank, then do not
  903. // attempt to do that - IIS does not properly understand
  904. // such kind of thing, and the item with such content
  905. // type is to be treated as application/octet-stream
  906. // which will be guaranteed by the absence of content
  907. // type in the metabase.
  908. //
  909. if (L'\0' == *pwszContentType)
  910. {
  911. return S_OK;
  912. }
  913. if (pwszMappings && L'*' != *pwszExt)
  914. {
  915. // IIS has an interesting concept of an empty mapping. Instead
  916. // of just a single null (indicating an empty list of mapping
  917. // strings) it uses a double null which to us would actually mean
  918. // a list of strings consisting solely of the empty string!
  919. // Anyway, if we add a mapping after this "empty mapping" neither
  920. // IIS nor HTTPEXT will ever see it because the mime map checking
  921. // implementations in both code bases treat the extraneous null
  922. // as the list terminator.
  923. //
  924. // If we have an "empty" set of mappings then REPLACE
  925. // it with a set consisting of just the new mapping.
  926. //
  927. if (2 == cchMappings && !*pwszMappings)
  928. --cchMappings;
  929. // Start at the end of the current mappings. Skip the extra
  930. // null at the end. We will add it back later.
  931. //
  932. Assert(cchMappings >= 1);
  933. Assert(L'\0' == pwszMappings[cchMappings-1]);
  934. pwch = pwszMappings + cchMappings - 1;
  935. }
  936. else
  937. {
  938. // Allocate enough space including list terminating 0
  939. //
  940. if (!wszBuf.resize(CbSizeWsz(CchExtMapping(cchExt, cchContentType))))
  941. return E_OUTOFMEMORY;
  942. // Since this is the only mapping, start from the beginning
  943. //
  944. pwszMappings = wszBuf.get();
  945. pwch = pwszMappings;
  946. }
  947. // Append the new mapping to the end of the existing mappings (if any).
  948. //
  949. pwch = PwchFormatExtMapping(pwch,
  950. pwszExt,
  951. cchExt,
  952. pwszContentType,
  953. cchContentType);
  954. // Terminate the new set of mappings
  955. //
  956. *pwch++ = L'\0';
  957. // Write the mappings out to the metabase
  958. //
  959. METADATA_RECORD mdrec;
  960. //$ REVIEW: if the value for pwszMDPath is non-NULL, then this means that the key to
  961. // which we are trying to right, does not exist at this point. If it did, we would have
  962. // opened it directly and set the data on the node directly. In the case of it being
  963. // non-NULL, that means that we must also set the MD_KEY_TYPE as well.
  964. //
  965. if (NULL != pwszMDPath)
  966. {
  967. mdrec.dwMDIdentifier = MD_KEY_TYPE;
  968. mdrec.dwMDAttributes = METADATA_NO_ATTRIBUTES;
  969. mdrec.dwMDUserType = IIS_MD_UT_FILE;
  970. mdrec.dwMDDataType = STRING_METADATA;
  971. mdrec.dwMDDataLen = CbSizeWsz(CchConstString(gc_wszIisWebFile));
  972. mdrec.pbMDData = reinterpret_cast<LPBYTE>(const_cast<WCHAR*>(gc_wszIisWebFile));
  973. (void) mdoh.HrSetMetaData (pwszMDPath, &mdrec);
  974. }
  975. //
  976. //$ REVIEW: end.
  977. mdrec.dwMDIdentifier = MD_MIME_MAP;
  978. mdrec.dwMDAttributes = METADATA_INHERIT;
  979. mdrec.dwMDUserType = IIS_MD_UT_FILE;
  980. mdrec.dwMDDataType = MULTISZ_METADATA;
  981. mdrec.dwMDDataLen = static_cast<DWORD>(pwch - pwszMappings) * sizeof(WCHAR);
  982. mdrec.pbMDData = reinterpret_cast<LPBYTE>(pwszMappings);
  983. return mdoh.HrSetMetaData(pwszMDPath, &mdrec);
  984. }
  985. // ------------------------------------------------------------------------
  986. //
  987. // ScSetStarMimeMap()
  988. //
  989. SCODE
  990. ScSetStarMimeMap( const IEcb& ecb,
  991. LPCWSTR pwszURI,
  992. LPCWSTR pwszContentType )
  993. {
  994. SCODE sc = E_OUTOFMEMORY;
  995. // Get the metabase path corresponding to pwszURI.
  996. //
  997. CStackBuffer<WCHAR,MAX_PATH> pwszMDPathURI;
  998. if (NULL == pwszMDPathURI.resize(CbMDPathW(ecb,pwszURI)))
  999. return sc;
  1000. {
  1001. MDPathFromURIW(ecb, pwszURI, pwszMDPathURI.get());
  1002. CMDObjectHandle mdoh(ecb);
  1003. LPCWSTR pwszMDPathMimeMap;
  1004. // Open a metabase object at or above the path where we want to set
  1005. // the star mime map.
  1006. //
  1007. sc = HrMDOpenMetaObject( pwszMDPathURI.get(),
  1008. METADATA_PERMISSION_WRITE,
  1009. 1000, // timeout in msec (1.0 sec)
  1010. &mdoh );
  1011. if (SUCCEEDED(sc))
  1012. {
  1013. pwszMDPathMimeMap = NULL;
  1014. }
  1015. else
  1016. {
  1017. if (sc != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))
  1018. {
  1019. DebugTrace ("ScSetStarMimeMap() - HrMDOpenMetaObject(pszMDPathURI) "
  1020. "failed 0x%08lX\n", sc);
  1021. goto ret;
  1022. }
  1023. sc = HrMDOpenMetaObject( ecb.PwszMDPathVroot(),
  1024. METADATA_PERMISSION_WRITE,
  1025. 1000,
  1026. &mdoh );
  1027. if (FAILED(sc))
  1028. {
  1029. DebugTrace("ScSetStarMimeMap() - HrMDOpenMetaObject(ecb.PwszMDPathVroot()) "
  1030. "failed 0x%08lX\n", sc);
  1031. goto ret;
  1032. }
  1033. Assert(!_wcsnicmp(pwszMDPathURI.get(),
  1034. ecb.PwszMDPathVroot(),
  1035. wcslen(ecb.PwszMDPathVroot())));
  1036. pwszMDPathMimeMap = pwszMDPathURI.get() + wcslen(ecb.PwszMDPathVroot());
  1037. }
  1038. // Add the "*" mime map
  1039. //
  1040. sc = ScAddMimeMap(mdoh,
  1041. pwszMDPathMimeMap,
  1042. NULL, // Overwrite existing mimemap (if any)
  1043. 0, //
  1044. gc_wsz_Star, // with "*" --> pszContentType
  1045. pwszContentType);
  1046. if (FAILED(sc))
  1047. {
  1048. DebugTrace("ScSetStarMimeMap() - ScAddMimeMap() failed 0x%08lX\n", sc);
  1049. goto ret;
  1050. }
  1051. }
  1052. ret:
  1053. return sc;
  1054. }
  1055. // ------------------------------------------------------------------------
  1056. //
  1057. // ScAddExtMimeMap() (aka the guts behind NT5:292139)
  1058. //
  1059. // Use the following algorithm to set the content type (pwszContentType)
  1060. // of the resource at pwszURI:
  1061. //
  1062. // If a mime map exists somewhere at or above the metabase path for
  1063. // pwszURI that has no mapping for the extension of pwszURI AND that
  1064. // mapping does NOT have a "*" mapping, then add a mapping from
  1065. // the extension of pwszURI to pwszContentType to that map.
  1066. //
  1067. // If no such map exists then create one at the site root and
  1068. // add the mapping there.
  1069. //
  1070. // In all other cases, add the mapping "*" --> pwszContentType
  1071. // at the level of pwszURI.
  1072. //
  1073. // The idea behind this complicated little routine is to reduce the number
  1074. // of "*" mappings that we create in the metabase to represent content types
  1075. // of resources with extensions that are not found in any administrator-defined
  1076. // mime map or global mime map. This helps most in scenarios where a new
  1077. // application is deployed which uses a heretofore unknown extension and
  1078. // the install utility (or admin) neglects to register a content type mapping
  1079. // for that application in any mime map.
  1080. //
  1081. // Without this functionality, we could end up creating "*" mappings for
  1082. // every resource created with an unknown extension. With time that would
  1083. // drag down the performance of the metabase significantly.
  1084. //
  1085. SCODE
  1086. ScAddExtMimeMap( const IEcb& ecb,
  1087. LPCWSTR pwszURI,
  1088. LPCWSTR pwszContentType )
  1089. {
  1090. // Metabase path corresponding to pwszURI. We form a relative path,
  1091. // off of this path, where we set a "*" mapping if we need to do so.
  1092. //
  1093. CStackBuffer<WCHAR,MAX_PATH> pwszMDPathURI(CbMDPathW(ecb, pwszURI));
  1094. if (!pwszMDPathURI.get())
  1095. return E_OUTOFMEMORY;
  1096. MDPathFromURIW(ecb, pwszURI, pwszMDPathURI.get());
  1097. UINT cchPathURI = static_cast<UINT>(wcslen(pwszMDPathURI.get()));
  1098. // Metabase path to the non-inherited mime map closest to pwszURI. When there
  1099. // is no such mime map, this is just the metabase path to the site root.
  1100. //
  1101. CStackBuffer<WCHAR,MAX_PATH> pwszMDPathMimeMap(CbSizeWsz(cchPathURI));
  1102. if (!pwszMDPathMimeMap.get())
  1103. return E_OUTOFMEMORY;
  1104. memcpy(pwszMDPathMimeMap.get(),
  1105. pwszMDPathURI.get(),
  1106. CbSizeWsz(cchPathURI));
  1107. UINT cchPathMimeMap = cchPathURI;
  1108. LPWSTR pwszMDPathMM = pwszMDPathMimeMap.get();
  1109. // Buffer for the metabase path to the site root (e.g. "/LM/W3SVC/1/root").
  1110. //
  1111. WCHAR rgwchMDPathSiteRoot[MAX_PATH];
  1112. SCODE sc = S_OK;
  1113. // Locate the non-inherited mime map "closest" to pszURI by probing successively
  1114. // shorter path prefixes until a non-inherited mime map is found or until we reach
  1115. // the site root, whichever happens first.
  1116. //
  1117. for ( ;; )
  1118. {
  1119. // Fetch the (hopefully cached) metadata for the current metabase path.
  1120. //
  1121. //$OPT
  1122. // Note the use of /LM/W3SVC as the "open" path. We use that path because
  1123. // it is guaranteed to exist (a requirement for this form of HrMDGetData())
  1124. // and because it is above the site root. It is also easily computable
  1125. // (it's a constant!). It does however lock a pretty huge portion of the
  1126. // metabase fetching the metadata. If this turns out to not perform well
  1127. // (i.e. the call fails due to timeout under normal usage) then we should
  1128. // evaluate whether a "lower" path -- like the site root -- would be a
  1129. // more appropriate choice.
  1130. //
  1131. auto_ref_ptr<IMDData> pMDDataMimeMap;
  1132. sc = HrMDGetData(ecb,
  1133. pwszMDPathMM,
  1134. gc_wsz_Lm_W3Svc,
  1135. pMDDataMimeMap.load());
  1136. if (FAILED(sc))
  1137. {
  1138. DebugTrace("ScAddExtMimeMap() - HrMDGetData(pwszMDPathMimeMap) failed 0x%08lX\n", sc);
  1139. goto ret;
  1140. }
  1141. // Look for a mime map (inherited or not) in the metadata. If we don't find
  1142. // one then we'll want to create one at the site root.
  1143. //
  1144. IContentTypeMap * pContentTypeMap;
  1145. pContentTypeMap = pMDDataMimeMap->GetContentTypeMap();
  1146. if (!pContentTypeMap)
  1147. {
  1148. ULONG cchPathSiteRoot = CElems(rgwchMDPathSiteRoot) - gc_cch_Root;
  1149. // We did not find any mime map (inherited or otherwise) so
  1150. // set up to create a mime map at the site root.
  1151. //
  1152. // Get the instance root (e.g. "/LM/W3SVC/1")
  1153. //
  1154. if (!ecb.FGetServerVariable("INSTANCE_META_PATH",
  1155. rgwchMDPathSiteRoot,
  1156. &cchPathSiteRoot))
  1157. {
  1158. sc = HRESULT_FROM_WIN32(GetLastError());
  1159. DebugTrace("ScAddExtMimeMap() - ecb.FGetServerVariable(INSTANCE_META_PATH) failed 0x%08lX\n", sc);
  1160. goto ret;
  1161. }
  1162. // Convert the size (in bytes) of the site root path to a length (in characters).
  1163. // Remember: cbPathSiteRoot includes the null terminator.
  1164. //
  1165. cchPathMimeMap = cchPathSiteRoot - 1;
  1166. // Tack on the "/root" part to get something like "/LM/W3SVC/1/root".
  1167. //
  1168. memcpy( rgwchMDPathSiteRoot + cchPathMimeMap,
  1169. gc_wsz_Root,
  1170. CbSizeWsz(gc_cch_Root)); // copy the null terminator too
  1171. cchPathMimeMap += gc_cch_Root;
  1172. pwszMDPathMM = rgwchMDPathSiteRoot;
  1173. break;
  1174. }
  1175. else if (!pContentTypeMap->FIsInherited())
  1176. {
  1177. // We found a non-inherited mime map at pwszMDPathMimeMap
  1178. // so we are done looking.
  1179. //
  1180. break;
  1181. }
  1182. // We found a mime map, but it was an inherited mime map,
  1183. // so back up one path component and check there. Eventually
  1184. // we will find the path where it was inherited from.
  1185. //
  1186. while ( L'/' != pwszMDPathMM[--cchPathMimeMap])
  1187. Assert(cchPathMimeMap > 0);
  1188. pwszMDPathMM[cchPathMimeMap] = L'\0';
  1189. }
  1190. // At this point, pwszMDPathMimeMap is the location of an existing non-inherited
  1191. // mime map or the site root. Now we want to lock down the metabase at this
  1192. // path (and everything below it) so that we can consistently check the actual
  1193. // current mime map contents (remember, we were looking at a cached view above!)
  1194. // and update them with the new mapping.
  1195. //
  1196. {
  1197. CMDObjectHandle mdoh(ecb);
  1198. METADATA_RECORD mdrec;
  1199. // Figure out the file extension on the URI. If it doesn't have one
  1200. // then we know right away that we are going to use a "*" mapping.
  1201. //
  1202. LPCWSTR pwszExt = PwszExt(pwszURI, pwszURI + wcslen(pwszURI));
  1203. if (!pwszExt)
  1204. pwszExt = gc_wsz_Star;
  1205. // Buffer size for the mime map metadata. 8K should be big enough
  1206. // for most mime maps -- the global mime map at lm/MimeMap is only ~4K.
  1207. //
  1208. enum { CCH_MAPPINGS_MAX = 2 * 1024 };
  1209. // Compute the size of the new mapping and do a quick check
  1210. // to handle any rogue user who tries to pull a fast one by creating
  1211. // a mapping that is ridiculously large.
  1212. //
  1213. UINT cchNewMapping = CchExtMapping(static_cast<UINT>(wcslen(pwszExt)),
  1214. static_cast<UINT>(wcslen(pwszContentType)));
  1215. if (cchNewMapping >= CCH_MAPPINGS_MAX )
  1216. {
  1217. sc = E_DAV_INVALID_HEADER; //HSC_BAD_REQUEST
  1218. goto ret;
  1219. }
  1220. // Buffer for the mime map metadata. 8K should be big enough for most
  1221. // mime maps -- the global mime map at lm/MimeMap is only ~4K.
  1222. // And don't forget to leave room at the end for the new mapping!
  1223. //
  1224. CStackBuffer<BYTE,4096> rgbData;
  1225. Assert (rgbData.size() == (CCH_MAPPINGS_MAX * sizeof(WCHAR)));
  1226. DWORD cbData = (CCH_MAPPINGS_MAX - cchNewMapping) * sizeof(WCHAR);
  1227. // Open the metadata object at the path we found. We know that the path
  1228. // that we want to open already exists -- if it is a path to some node
  1229. // with a non-inherited mime map then it is the path to the site root.
  1230. //
  1231. sc = HrMDOpenMetaObject( pwszMDPathMM,
  1232. METADATA_PERMISSION_WRITE |
  1233. METADATA_PERMISSION_READ,
  1234. 1000, // timeout in msec (1.0 sec)
  1235. &mdoh );
  1236. if (FAILED(sc))
  1237. {
  1238. DebugTrace("ScAddExtMimeMap() - HrMDOpenMetaObject() failed 0x%08lX\n", sc);
  1239. goto ret;
  1240. }
  1241. // Fetch the mime map.
  1242. //
  1243. mdrec.dwMDIdentifier = MD_MIME_MAP;
  1244. mdrec.dwMDAttributes = METADATA_INHERIT;
  1245. mdrec.dwMDUserType = IIS_MD_UT_FILE;
  1246. mdrec.dwMDDataType = MULTISZ_METADATA;
  1247. mdrec.dwMDDataLen = cbData;
  1248. mdrec.pbMDData = rgbData.get();
  1249. sc = mdoh.HrGetMetaData( NULL, // No relative path to the mime map.
  1250. // We opened a path directly there above.
  1251. &mdrec,
  1252. &cbData );
  1253. if (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == sc)
  1254. {
  1255. // In the unlikely event that the static-size buffer above wasn't
  1256. // big enough, then try reading again into a one that is.
  1257. //
  1258. // Again, leave enough room for the new mapping
  1259. //
  1260. mdrec.dwMDDataLen = cbData;
  1261. mdrec.pbMDData = rgbData.resize(cbData + cchNewMapping * sizeof(WCHAR));
  1262. sc = mdoh.HrGetMetaData( NULL, &mdrec, &cbData );
  1263. }
  1264. if (FAILED(sc))
  1265. {
  1266. if (MD_ERROR_DATA_NOT_FOUND != sc)
  1267. {
  1268. DebugTrace("ScAddExtMimeMap() - HrMDOpenMetaObject() failed 0x%08lX\n", sc);
  1269. goto ret;
  1270. }
  1271. // If we don't find a mapping, that's fine. Most likely we are just
  1272. // at the site root. There is also a slim chance that the admin could
  1273. // have deleted the mapping between the time we found it in the cache
  1274. // and the time that we locked the path in the metabase.
  1275. //
  1276. mdrec.pbMDData = NULL;
  1277. sc = S_OK;
  1278. }
  1279. // If we don't have a mime map then use a "*" mapping unless we are
  1280. // at the site root in which case we should CREATE a mime map with
  1281. // a single mapping for the URI extension.
  1282. //
  1283. if (!mdrec.pbMDData)
  1284. {
  1285. if (rgwchMDPathSiteRoot != pwszMDPathMM)
  1286. pwszExt = gc_wsz_Star;
  1287. }
  1288. // If we found a mime map and it is still not inherited then we have
  1289. // some more checking to do. Yes, the mime map actually can be
  1290. // inherited at this point. See below for why.
  1291. //
  1292. else if (!(mdrec.dwMDAttributes & METADATA_ISINHERITED))
  1293. {
  1294. // Check whether we should apply a "*" mapping rather than
  1295. // the extension mapping that we ideally want. The rules
  1296. // governing this decision are outlined in ScApplyStarExt().
  1297. //
  1298. sc = ScApplyStarExt(reinterpret_cast<LPWSTR>(mdrec.pbMDData),
  1299. pwszContentType,
  1300. &pwszExt);
  1301. if (FAILED(sc))
  1302. {
  1303. DebugTrace("ScAddExtMimeMap() - ScApplyStarExt() failed 0x%08lX\n", sc);
  1304. goto ret;
  1305. }
  1306. }
  1307. // We found a mime map, but for some oddball reason it now appears to be
  1308. // inherited! This can happen if the admin manages to change things
  1309. // between the time we check the cache and when we open pszMDPathMimeMap
  1310. // for writing. This should be a sufficiently rare case that falling back
  1311. // to a "*" mapping here isn't so bad.
  1312. //
  1313. else
  1314. {
  1315. Assert(mdrec.pbMDData);
  1316. Assert(mdrec.dwMDAttributes & METADATA_ISINHERITED);
  1317. pwszExt = gc_wsz_Star;
  1318. }
  1319. // Ok, we're all set. We have the extension ("*" or .somethingorother).
  1320. // We have the content type. We have the existing mappings (if any).
  1321. // Add in the new mapping.
  1322. //
  1323. // Note: if we are adding a "*" mapping, we always want to add it
  1324. // at the level of the URI. But since the metabase handle we have
  1325. // open is at some level above the URI, the path we pass to ScAddMimeMap()
  1326. // below must be relative to the path used to open the handle.
  1327. // Easy enough. That path is just what's left of the URI path
  1328. // beyond where we found (or would have created) the non-inherited
  1329. // mime map.
  1330. //
  1331. sc = ScAddMimeMap(mdoh,
  1332. (L'*' == *pwszExt) ?
  1333. pwszMDPathURI.get() + cchPathMimeMap :
  1334. NULL,
  1335. reinterpret_cast<LPWSTR>(mdrec.pbMDData),
  1336. mdrec.dwMDDataLen / sizeof(WCHAR),
  1337. pwszExt,
  1338. pwszContentType);
  1339. if (FAILED(sc))
  1340. {
  1341. DebugTrace("ScAddExtMimeMap() - ScAddMimeMap(pszExt) failed 0x%08lX\n", sc);
  1342. goto ret;
  1343. }
  1344. }
  1345. ret:
  1346. return sc;
  1347. }
  1348. // ------------------------------------------------------------------------
  1349. //
  1350. // ScSetContentType()
  1351. //
  1352. SCODE
  1353. ScSetContentType( const IEcb& ecb,
  1354. LPCWSTR pwszURI,
  1355. LPCWSTR pwszContentTypeWanted )
  1356. {
  1357. BOOL fIsGlobalMapping = FALSE;
  1358. CStackBuffer<WCHAR> pwszContentTypeCur;
  1359. SCODE sc = S_OK;
  1360. UINT cchContentTypeCur;
  1361. // Check what the content type would be if we didn't do anything.
  1362. // If it's what we want, then we're done. No need to open the metabase
  1363. // for anything!
  1364. //
  1365. cchContentTypeCur = pwszContentTypeCur.celems();
  1366. if ( !FGetContentTypeFromURI( ecb,
  1367. pwszURI,
  1368. pwszContentTypeCur.get(),
  1369. &cchContentTypeCur,
  1370. &fIsGlobalMapping ) )
  1371. {
  1372. if (!pwszContentTypeCur.resize(cchContentTypeCur * sizeof(WCHAR)))
  1373. {
  1374. sc = E_OUTOFMEMORY;
  1375. goto ret;
  1376. }
  1377. if ( !FGetContentTypeFromURI( ecb,
  1378. pwszURI,
  1379. pwszContentTypeCur.get(),
  1380. &cchContentTypeCur,
  1381. &fIsGlobalMapping))
  1382. {
  1383. //
  1384. // If the size of the content type keeps changing on us
  1385. // then the server is too busy. Give up.
  1386. //
  1387. sc = ERROR_PATH_BUSY;
  1388. DebugTrace("ScSetContentType() - FGetContentTypeFromURI() failed 0x%08lX\n", sc);
  1389. goto ret;
  1390. }
  1391. }
  1392. //
  1393. // If the content type is already what we want then don't change a thing.
  1394. //
  1395. if ( !_wcsicmp( pwszContentTypeWanted, pwszContentTypeCur.get()))
  1396. {
  1397. sc = S_OK;
  1398. goto ret;
  1399. }
  1400. //
  1401. // The current content type isn't what we want so we will have to set
  1402. // something in the metabase. If the mapping for this extension came
  1403. // from one of the global maps, then always override the mapping by
  1404. // setting a "*" mapping at the URI level. If the mapping was not
  1405. // a global one then what we do gets very complicated due to Raid NT5:292139....
  1406. //
  1407. if (fIsGlobalMapping)
  1408. {
  1409. sc = ScSetStarMimeMap(ecb,
  1410. pwszURI,
  1411. pwszContentTypeWanted);
  1412. if (FAILED(sc))
  1413. {
  1414. DebugTrace("ScSetContentType() - ScAddExtMimeMap() failed 0x%08lX\n", sc);
  1415. goto ret;
  1416. }
  1417. }
  1418. else
  1419. {
  1420. sc = ScAddExtMimeMap(ecb,
  1421. pwszURI,
  1422. pwszContentTypeWanted);
  1423. if (FAILED(sc))
  1424. {
  1425. DebugTrace("ScSetContentType() - ScAddExtMimeMap() failed 0x%08lX\n", sc);
  1426. goto ret;
  1427. }
  1428. }
  1429. ret:
  1430. return sc;
  1431. }
  1432. /*
  1433. * ScCanAcceptContent()
  1434. *
  1435. * Purpose:
  1436. *
  1437. * Check if the given content type is acceptable
  1438. *
  1439. * Parameters:
  1440. *
  1441. * pwszAccepts [in] the Accept header;
  1442. * pwszApp [in] the application part of the content type
  1443. * pwszType [in] the sub type of the content type
  1444. *
  1445. * Returns:
  1446. *
  1447. * S_OK - if the request accepts the content type, no wildcard matching
  1448. * S_FALSE - if the request accepts the content type, wildcard matching
  1449. * E_DAV_RESPONSE_TYPE_UNACCEPTED - if the response type was unaccepted
  1450. */
  1451. SCODE __fastcall
  1452. ScCanAcceptContent (LPCWSTR pwszAccepts, LPWSTR pwszApp, LPWSTR pwszType)
  1453. {
  1454. SCODE sc = E_DAV_RESPONSE_TYPE_UNACCEPTED;
  1455. HDRITER_W hit(pwszAccepts);
  1456. LPWSTR pwsz;
  1457. LPCWSTR pwszAppType;
  1458. LPCWSTR pwszSubType;
  1459. // Rip through the entries in the header...
  1460. //
  1461. while (NULL != (pwszAppType = hit.PszNext()))
  1462. {
  1463. pwsz = const_cast<LPWSTR>(pwszAppType);
  1464. // Search for the end of the application type
  1465. // '/' is the sub type separator, and ';' starts the parameters
  1466. //
  1467. while ( *pwsz &&
  1468. (L'/' != *pwsz) &&
  1469. (L';' != *pwsz) )
  1470. pwsz++;
  1471. if (L'/' == *pwsz)
  1472. {
  1473. // Make pwszAppType point to the application type ...
  1474. //
  1475. *pwsz++ = L'\0';
  1476. // ... and pszSubType point to the subtype
  1477. //
  1478. pwszSubType = pwsz;
  1479. while (*pwsz && (L';' != *pwsz))
  1480. pwsz++;
  1481. *pwsz = L'\0';
  1482. }
  1483. else
  1484. {
  1485. // There's not sub type.
  1486. //
  1487. *pwsz = L'\0';
  1488. // point pszSubType to a empty string, instead of setting it to NULL
  1489. //
  1490. pwszSubType = pwsz;
  1491. }
  1492. // Here're the rules:
  1493. //
  1494. // A application type * match any type (including */xxx)
  1495. // type/* match all subtypes of that app type
  1496. // type/subtype looks for exact match
  1497. //
  1498. if (!wcscmp (pwszAppType, gc_wsz_Star))
  1499. {
  1500. // This is a wild-card match. So, S_FALSE is used
  1501. // to distinguish this from an exact match.
  1502. //
  1503. sc = S_FALSE;
  1504. }
  1505. else if (!wcscmp (pwszAppType, pwszApp))
  1506. {
  1507. if (!wcscmp (pwszSubType, gc_wsz_Star))
  1508. {
  1509. // Again, a wild-card matching will result in
  1510. // an S_FALSE return.
  1511. //
  1512. sc = S_FALSE;
  1513. }
  1514. else if (!wcscmp (pwszSubType, pwszType))
  1515. {
  1516. // Exact matches return S_OK
  1517. //
  1518. sc = S_OK;
  1519. }
  1520. }
  1521. // If we had any sort of a match by this point, we are
  1522. // pretty much done.
  1523. //
  1524. if (!FAILED (sc))
  1525. break;
  1526. }
  1527. return sc;
  1528. }
  1529. /*
  1530. * ScIsAcceptable()
  1531. *
  1532. * Purpose:
  1533. *
  1534. * Checks if a given content type is acceptable for a given request.
  1535. *
  1536. * Parameters:
  1537. *
  1538. * pmu [in] pointer to the IMethUtil object
  1539. * pwszContent [in] content type to ask about
  1540. *
  1541. * Returns:
  1542. *
  1543. * S_OK - if the request accepts the content type and the header existed
  1544. * S_FALSE - if the request accepts the content type and the header did not
  1545. * exist or was blank, or any wildcard matching occured
  1546. * E_DAV_RESPONSE_TYPE_UNACCEPTED - if the response type was unaccepted
  1547. * E_OUTOFMEMORY - if memory allocation failure occurs
  1548. *
  1549. */
  1550. SCODE
  1551. ScIsAcceptable (IMethUtil * pmu, LPCWSTR pwszContent)
  1552. {
  1553. SCODE sc = S_OK;
  1554. LPCWSTR pwszAccept = NULL;
  1555. CStackBuffer<WCHAR> pwsz;
  1556. UINT cch;
  1557. LPWSTR pwch;
  1558. Assert( pmu );
  1559. Assert( pwszContent );
  1560. // If the accept header is NULL or empty, then we will gladly
  1561. // accept any type of file. Do not apply URL conversion rules
  1562. // for this header.
  1563. //
  1564. pwszAccept = pmu->LpwszGetRequestHeader (gc_szAccept, FALSE);
  1565. if (!pwszAccept || (0 == wcslen(pwszAccept)))
  1566. {
  1567. sc = S_FALSE;
  1568. goto ret;
  1569. }
  1570. // Make a local copy of the content-type seeing
  1571. // that we are going to munge while processing
  1572. //
  1573. cch = static_cast<UINT>(wcslen(pwszContent) + 1);
  1574. if (!pwsz.resize(cch * sizeof(WCHAR)))
  1575. {
  1576. sc = E_OUTOFMEMORY;
  1577. DebugTrace("ScIsAcceptable() - Failed to allocate memory 0x%08lX\n", sc);
  1578. goto ret;
  1579. }
  1580. memcpy(pwsz.get(), pwszContent, cch * sizeof(WCHAR));
  1581. // Split the content type into its two components
  1582. //
  1583. for (pwch = pwsz.get(); *pwch && (L'/' != *pwch); pwch++)
  1584. ;
  1585. // If there was app/type pair, we want to skip
  1586. // the '/' character. Otherwise, lets just see
  1587. // What we get.
  1588. //
  1589. if (*pwch != 0)
  1590. *pwch++ = 0;
  1591. // At this point, rgch refers to the application
  1592. // portion of the the content type. pch refers
  1593. // to the subtype. Do the search!
  1594. //
  1595. sc = ScCanAcceptContent (pwszAccept, pwsz.get(), pwch);
  1596. ret:
  1597. return sc;
  1598. }
  1599. /*
  1600. * ScIsContentType()
  1601. *
  1602. * Purpose:
  1603. *
  1604. * Check if the specified content type is provide by the client
  1605. * SCODE is returned as we need to differentiate unexpected
  1606. * content type and no content type case.
  1607. *
  1608. * Parameters:
  1609. *
  1610. * pmu [in] pointer to the IMethUtil object
  1611. * pszType [in] the content type expected
  1612. * pszTypeAnother [in] optional, another valid content type
  1613. *
  1614. * Returns:
  1615. *
  1616. * S_OK - if the content type existed, and was one tat we expected
  1617. * E_DAV_MISSING_CONTENT_TYPE - if the request did not have the content
  1618. * type header
  1619. * E_DAV_UNKNOWN_CONTENT - content type existed but did not match expectation
  1620. * E_OUTOFMEMORY - if memory allocation failure occurs
  1621. */
  1622. SCODE
  1623. ScIsContentType (IMethUtil * pmu, LPCWSTR pwszType, LPCWSTR pwszTypeAnother)
  1624. {
  1625. SCODE sc = S_OK;
  1626. const WCHAR wchDelimitSet[] = { L';', L'\t', L' ', L'\0' };
  1627. LPCWSTR pwszCntType = NULL;
  1628. CStackBuffer<WCHAR> pwszTemp;
  1629. UINT cchTemp;
  1630. // Make sure none is passing in null
  1631. //
  1632. Assert(pmu);
  1633. Assert(pwszType);
  1634. // Get content type. Do not apply URL conversion rules to this header.
  1635. //
  1636. pwszCntType = pmu->LpwszGetRequestHeader (gc_szContent_Type, FALSE);
  1637. // Error out if the content type does not exist
  1638. //
  1639. if (!pwszCntType)
  1640. {
  1641. sc = E_DAV_MISSING_CONTENT_TYPE;
  1642. DebugTrace("ScIsContentType() - Content type header is missing 0x%08lX\n", sc);
  1643. goto ret;
  1644. }
  1645. // Find out the single content type in the header
  1646. //
  1647. cchTemp = static_cast<UINT>(wcscspn(pwszCntType, wchDelimitSet));
  1648. // At least we will find zero terminator. And if that is zero terminator then
  1649. // the entire string is the content type. Otherwise we copy it and zero terminate.
  1650. //
  1651. if (L'\0' != pwszCntType[cchTemp])
  1652. {
  1653. if (!pwszTemp.resize(CbSizeWsz(cchTemp)))
  1654. {
  1655. sc = E_OUTOFMEMORY;
  1656. DebugTrace("ScIsContentType() - Failed to allocate memory 0x%08lX\n", sc);
  1657. goto ret;
  1658. }
  1659. memcpy(pwszTemp.get(), pwszCntType, cchTemp * sizeof(WCHAR));
  1660. pwszTemp[cchTemp] = L'\0';
  1661. pwszCntType = pwszTemp.get();
  1662. }
  1663. // Now pwszCntType points to the string consisting just of null terminated content type.
  1664. // Check if it is requested content type.
  1665. //
  1666. if (!_wcsicmp(pwszCntType, pwszType))
  1667. goto ret;
  1668. if (pwszTypeAnother)
  1669. {
  1670. if (!_wcsicmp(pwszCntType, pwszTypeAnother))
  1671. goto ret;
  1672. }
  1673. sc = E_DAV_UNKNOWN_CONTENT;
  1674. ret:
  1675. return sc;
  1676. }