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
1887 lines
52 KiB
/*
|
|
* C O N T E N T . C P P
|
|
*
|
|
* DAV content types
|
|
*
|
|
* Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
|
|
*/
|
|
|
|
#include "_davprs.h"
|
|
#include "content.h"
|
|
#include <ex\reg.h>
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CchExtMapping()
|
|
//
|
|
// Returns the size in characters required for a single mapping for
|
|
// writing to the metabase.
|
|
//
|
|
// The format of a mapping is null-terminated, comma-delimited string
|
|
// (e.g. ".ext,application/ext").
|
|
//
|
|
inline UINT
|
|
CchExtMapping( UINT cchExt,
|
|
UINT cchContentType )
|
|
{
|
|
return (cchExt +
|
|
1 + // ','
|
|
cchContentType +
|
|
1); // '\0'
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// PwchFormatExtMapping()
|
|
//
|
|
// Formats a single mapping for writing to the metabase.
|
|
//
|
|
// The format of a mapping is null-terminated, comma-delimited string
|
|
// (e.g. ".ext,application/ext").
|
|
//
|
|
// This function returns a pointer to the character beyond the null terminator
|
|
// in the formatted mapping.
|
|
//
|
|
inline WCHAR *
|
|
PwchFormatExtMapping( WCHAR * pwchBuf,
|
|
LPCWSTR pwszExt,
|
|
UINT cchExt,
|
|
LPCWSTR pwszContentType,
|
|
UINT cchContentType )
|
|
{
|
|
Assert(!IsBadReadPtr(pwszExt, sizeof(WCHAR) * (cchExt+1)));
|
|
Assert(!IsBadReadPtr(pwszContentType, sizeof(WCHAR) * (cchContentType+1)));
|
|
Assert(!IsBadWritePtr(pwchBuf, sizeof(WCHAR) * CchExtMapping(cchExt, cchContentType)));
|
|
|
|
// Dump in the extension first ...
|
|
//
|
|
memcpy(pwchBuf,
|
|
pwszExt,
|
|
sizeof(WCHAR) * cchExt);
|
|
|
|
pwchBuf += cchExt;
|
|
|
|
// ... followed by a comma
|
|
//
|
|
*pwchBuf++ = L',';
|
|
|
|
// ... followed by the content type
|
|
//
|
|
memcpy(pwchBuf,
|
|
pwszContentType,
|
|
sizeof(WCHAR) * cchContentType);
|
|
|
|
pwchBuf += cchContentType;
|
|
|
|
// ... and null-terminated.
|
|
//
|
|
*pwchBuf++ = '\0';
|
|
|
|
return pwchBuf;
|
|
}
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS CContentTypeMap
|
|
//
|
|
class CContentTypeMap : public IContentTypeMap
|
|
{
|
|
// Cache of mappings from filename extensions to content types
|
|
// (e.g. ".txt" --> "text/plain")
|
|
//
|
|
typedef CCache<CRCWszi, LPCWSTR> CMappingsCache;
|
|
|
|
CMappingsCache m_cache;
|
|
|
|
// Flag set if the mappings came from an inherited mime map.
|
|
//
|
|
BOOL m_fIsInherited;
|
|
|
|
// CREATORS
|
|
//
|
|
CContentTypeMap(BOOL fMappingsInherited) :
|
|
m_fIsInherited(fMappingsInherited)
|
|
{
|
|
}
|
|
|
|
BOOL CContentTypeMap::FInit( LPWSTR pwszContentTypeMappings );
|
|
|
|
// NOT IMPLEMENTED
|
|
//
|
|
CContentTypeMap(const CContentTypeMap&);
|
|
CContentTypeMap& operator=(CContentTypeMap&);
|
|
|
|
public:
|
|
// CREATORS
|
|
//
|
|
static CContentTypeMap * New( LPWSTR pwszContentTypeMappings,
|
|
BOOL fMappingsInherited );
|
|
|
|
// ACCESSORS
|
|
//
|
|
LPCWSTR PwszContentType( LPCWSTR pwszExt ) const
|
|
{
|
|
LPCWSTR * ppwszContentType = m_cache.Lookup( CRCWszi(pwszExt) );
|
|
|
|
//
|
|
// Return the content type (if there was one).
|
|
// Note that the returned pointer is good only
|
|
// for the lifetime of the IMDData object that
|
|
// scopes us since that is where the raw data lives.
|
|
//
|
|
return ppwszContentType ? *ppwszContentType : NULL;
|
|
}
|
|
|
|
BOOL FIsInherited() const { return m_fIsInherited; }
|
|
};
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CContentTypeMap::FInit()
|
|
//
|
|
BOOL
|
|
CContentTypeMap::FInit( LPWSTR pwszContentTypeMappings )
|
|
{
|
|
Assert( pwszContentTypeMappings );
|
|
|
|
//
|
|
// Initialize the cache of mappings
|
|
//
|
|
if ( !m_cache.FInit() )
|
|
return FALSE;
|
|
|
|
//
|
|
// The format of the data in the mappings is a sequence of
|
|
// null-terminated strings followed by an additional null.
|
|
// Each string is of the format ".ext,type/subtype".
|
|
//
|
|
|
|
//
|
|
// Parse out the extension and type/subtype for each
|
|
// item and add a corresponding mapping to the cache.
|
|
//
|
|
for ( LPWSTR pwszMapping = pwszContentTypeMappings; *pwszMapping; )
|
|
{
|
|
enum {
|
|
ISZ_CT_EXT = 0,
|
|
ISZ_CT_TYPE,
|
|
CSZ_CT_FIELDS
|
|
};
|
|
|
|
LPWSTR rgpwsz[CSZ_CT_FIELDS];
|
|
UINT cchMapping;
|
|
|
|
//
|
|
// Digest the metadata
|
|
//
|
|
if ( !FParseMDData( pwszMapping,
|
|
rgpwsz,
|
|
CSZ_CT_FIELDS,
|
|
&cchMapping ) )
|
|
{
|
|
DebugTrace( "CContentTypeMap::FInit() - Malformed metadata\n" );
|
|
return FALSE;
|
|
}
|
|
|
|
Assert(rgpwsz[ISZ_CT_EXT]);
|
|
|
|
//
|
|
// Verify that the first field is an extension or '*'
|
|
//
|
|
if ( L'.' != *rgpwsz[ISZ_CT_EXT] && wcscmp(rgpwsz[ISZ_CT_EXT], gc_wsz_Star) )
|
|
{
|
|
DebugTrace( "CContentTypeMap::FInit() - Bad extension\n" );
|
|
return FALSE;
|
|
}
|
|
|
|
Assert(rgpwsz[ISZ_CT_TYPE]);
|
|
|
|
//
|
|
// Whatever there is in the second field is expected to be the
|
|
// content type. Note that we don't do any syntactic checking
|
|
// there.
|
|
// The only special case we handle there is if the content
|
|
// type is a blank string. As IIS 6.0 treats that kind as
|
|
// application/octet-stream we will achieve the same behaviour
|
|
// by simply ignoring such bad content type that will make us
|
|
// default to application/octet-stream too. So omit content types
|
|
// with blank values.
|
|
//
|
|
if (L'\0' != *rgpwsz[ISZ_CT_TYPE])
|
|
{
|
|
// Add a mapping from the extension to the content type
|
|
//
|
|
if ( !m_cache.FSet(CRCWszi(rgpwsz[ISZ_CT_EXT]), rgpwsz[ISZ_CT_TYPE]) )
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Get the next mapping
|
|
//
|
|
pwszMapping += cchMapping;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CContentTypeMap::New()
|
|
//
|
|
CContentTypeMap *
|
|
CContentTypeMap::New( LPWSTR pwszContentTypeMappings,
|
|
BOOL fMappingsInherited )
|
|
{
|
|
auto_ref_ptr<CContentTypeMap> pContentTypeMap;
|
|
|
|
pContentTypeMap.take_ownership(new CContentTypeMap(fMappingsInherited));
|
|
|
|
if ( pContentTypeMap->FInit(pwszContentTypeMappings) )
|
|
return pContentTypeMap.relinquish();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// NewContentTypeMap()
|
|
//
|
|
// Creates a new content type map from a string of content type mappings.
|
|
//
|
|
IContentTypeMap *
|
|
NewContentTypeMap( LPWSTR pwszContentTypeMappings,
|
|
BOOL fMappingsInherited )
|
|
{
|
|
return CContentTypeMap::New( pwszContentTypeMappings,
|
|
fMappingsInherited );
|
|
}
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS CRegMimeMap
|
|
//
|
|
// Global registry-based mime map from file extension to content type.
|
|
//
|
|
class CRegMimeMap : public Singleton<CRegMimeMap>
|
|
{
|
|
//
|
|
// Friend declarations required by Singleton template
|
|
//
|
|
friend class Singleton<CRegMimeMap>;
|
|
|
|
//
|
|
// String buffer for cached strings
|
|
//
|
|
ChainedStringBuffer<WCHAR> m_sb;
|
|
|
|
// Cache of mappings from filename extensions to content types
|
|
// (e.g. ".txt" --> "text/plain")
|
|
//
|
|
CCache<CRCWszi, LPCWSTR> m_cache;
|
|
|
|
// A R/W lock that we will use when when reading from
|
|
// the cache or when adding cache misses This lock
|
|
// is used by PszContentType().
|
|
//
|
|
// FInitialize() does not use this lock (only initializes it)
|
|
// because it is called during dll load and we don't need to
|
|
// protect ourselves during dll load
|
|
//
|
|
CMRWLock m_rwl;
|
|
|
|
// CREATORS
|
|
//
|
|
CRegMimeMap() {}
|
|
|
|
// NOT IMPLEMENTED
|
|
//
|
|
CRegMimeMap(const CRegMimeMap&);
|
|
CRegMimeMap& operator=(CRegMimeMap&);
|
|
|
|
public:
|
|
// CREATORS
|
|
//
|
|
using Singleton<CRegMimeMap>::CreateInstance;
|
|
using Singleton<CRegMimeMap>::DestroyInstance;
|
|
BOOL FInitialize();
|
|
|
|
// ACCESSORS
|
|
//
|
|
using Singleton<CRegMimeMap>::Instance;
|
|
|
|
// Given an extension, return the Content-Type
|
|
// from the registry.
|
|
//$NOTE: This was a const function before but it
|
|
// cannot be a const function anymore because we
|
|
// can add to our caches on cache misses.
|
|
//
|
|
LPCWSTR PwszContentType( LPCWSTR pwszExt );
|
|
};
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CRegMimeMap::FInitialize()
|
|
//
|
|
// Load up the registry mappings. Any kind of failure (short of an
|
|
// exception) is not considered fatal. It just means that we will
|
|
// rely on the superceding metabase mappings.
|
|
//
|
|
BOOL
|
|
CRegMimeMap::FInitialize()
|
|
{
|
|
BOOL fRet = FALSE;
|
|
CRegKey regkeyClassesRoot;
|
|
DWORD dwResult;
|
|
|
|
//
|
|
// Initialize the cache of mappings
|
|
//
|
|
if ( !m_cache.FInit() )
|
|
goto ret;
|
|
|
|
// Init the R/W lock.
|
|
//
|
|
if (!m_rwl.FInitialize())
|
|
goto ret;
|
|
|
|
//
|
|
// Read in the mapping information from the registry
|
|
//
|
|
|
|
// Get the base of the classes hierarchy in the registry
|
|
//
|
|
dwResult = regkeyClassesRoot.DwOpen( HKEY_CLASSES_ROOT, L"" );
|
|
if ( dwResult != NO_ERROR )
|
|
goto ret;
|
|
|
|
// Iterate over all the entries looking for content-type associations
|
|
//
|
|
for ( DWORD iMapping = 0;; iMapping++ )
|
|
{
|
|
WCHAR wszSubKey[MAX_PATH];
|
|
DWORD cchSubKey;
|
|
DWORD dwDataType;
|
|
CRegKey regkeySub;
|
|
WCHAR wszContentType[MAX_PATH] = {0};
|
|
DWORD cbContentType = MAX_PATH;
|
|
|
|
//
|
|
// Locate the next subkey. If there isn't one then we're done.
|
|
//
|
|
cchSubKey = CElems(wszSubKey);
|
|
dwResult = regkeyClassesRoot.DwEnumSubKey( iMapping, wszSubKey, &cchSubKey );
|
|
if ( dwResult != NO_ERROR )
|
|
{
|
|
//
|
|
// Ignore keys that are larger than MAX_PATH.
|
|
// Note that keys larger than MAX_PATH shouldn't be allowed
|
|
// but if we didn't check and then hit one initialization
|
|
// would fail
|
|
//
|
|
if ( ERROR_MORE_DATA == dwResult )
|
|
continue;
|
|
|
|
fRet = (ERROR_NO_MORE_ITEMS == dwResult);
|
|
goto ret;
|
|
}
|
|
|
|
//
|
|
// Open that subkey.
|
|
//
|
|
dwResult = regkeySub.DwOpen( regkeyClassesRoot, wszSubKey );
|
|
if ( dwResult != NO_ERROR )
|
|
continue;
|
|
|
|
//
|
|
// Get the associated Media-Type (Content-Type)
|
|
//
|
|
dwResult = regkeySub.DwQueryValue( L"Content Type",
|
|
wszContentType,
|
|
&cbContentType,
|
|
&dwDataType );
|
|
if ( dwResult != NO_ERROR || dwDataType != REG_SZ )
|
|
continue;
|
|
|
|
//
|
|
// Add a mapping for this extension/content type pair.
|
|
//
|
|
// Note: FAdd() cannot fail here -- FAdd() only fails on
|
|
// allocator failures. Our allocators throw.
|
|
//
|
|
(VOID) m_cache.FAdd (CRCWszi(m_sb.AppendWithNull(wszSubKey)),
|
|
m_sb.AppendWithNull(wszContentType));
|
|
}
|
|
|
|
ret:
|
|
return fRet;
|
|
}
|
|
|
|
|
|
LPCWSTR
|
|
CRegMimeMap::PwszContentType( LPCWSTR pwszExt )
|
|
{
|
|
LPCWSTR pwszContentType = NULL;
|
|
LPCWSTR * ppwszContentType = NULL;
|
|
CRegKey regkeyClassesRoot;
|
|
CRegKey regkeySub;
|
|
DWORD dwResult;
|
|
DWORD dwDataType;
|
|
WCHAR prgwchContentType[MAX_PATH] = {0};
|
|
DWORD cbContentType;
|
|
|
|
// Grab a reader lock and check the cache.
|
|
//
|
|
{
|
|
CSynchronizedReadBlock srb(m_rwl);
|
|
|
|
ppwszContentType = m_cache.Lookup( CRCWszi(pwszExt) );
|
|
}
|
|
|
|
//
|
|
// Return the content type (if there was one).
|
|
// Note that the returned pointer is good only
|
|
// for the lifetime of the cache (since we never
|
|
// modify the cache after class initialization)
|
|
// which, in turn, is only good for the lifetime
|
|
// of this object. The external interface functions
|
|
// FGetContentTypeFromPath() and FGetContentTypeFromURI()
|
|
// both copy the returned content type into caller-supplied
|
|
// buffers.
|
|
//
|
|
if (ppwszContentType)
|
|
{
|
|
pwszContentType = *ppwszContentType;
|
|
goto ret;
|
|
}
|
|
|
|
// Otherwise, read in the mapping information from the registry
|
|
//
|
|
|
|
// Get the base of the classes hierarchy in the registry
|
|
//
|
|
dwResult = regkeyClassesRoot.DwOpen( HKEY_CLASSES_ROOT, L"" );
|
|
if ( dwResult != NO_ERROR )
|
|
goto ret;
|
|
|
|
|
|
// Open that subkey of the extension we are looking for.
|
|
//
|
|
dwResult = regkeySub.DwOpen( regkeyClassesRoot, pwszExt );
|
|
if ( dwResult != NO_ERROR )
|
|
goto ret;
|
|
|
|
// Get the associated Media-Type (Content-Type)
|
|
//
|
|
cbContentType = sizeof(prgwchContentType);
|
|
dwResult = regkeySub.DwQueryValue( L"Content Type",
|
|
prgwchContentType,
|
|
&cbContentType,
|
|
&dwDataType );
|
|
if ( dwResult != NO_ERROR || dwDataType != REG_SZ )
|
|
goto ret;
|
|
|
|
// Before adding the mapping for this extension/content type
|
|
// pair to the cache, take a writer lock and check the cache
|
|
// to see if someone has beaten us to it.
|
|
//
|
|
// Grab a reader lock and check the cache.
|
|
//
|
|
{
|
|
CSynchronizedWriteBlock swb(m_rwl);
|
|
|
|
ppwszContentType = m_cache.Lookup( CRCWszi(pwszExt) );
|
|
|
|
if (ppwszContentType)
|
|
{
|
|
pwszContentType = *ppwszContentType;
|
|
goto ret;
|
|
}
|
|
|
|
pwszContentType = m_sb.AppendWithNull(prgwchContentType);
|
|
|
|
Assert (pwszContentType);
|
|
|
|
// Note: FAdd() cannot fail here -- FAdd() only fails on
|
|
// allocator failures. Our allocators throw.
|
|
//
|
|
(VOID) m_cache.FAdd (CRCWszi(m_sb.AppendWithNull(pwszExt)),
|
|
pwszContentType);
|
|
}
|
|
ret:
|
|
return pwszContentType;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// FInitRegMimeMap()
|
|
//
|
|
BOOL
|
|
FInitRegMimeMap()
|
|
{
|
|
return CRegMimeMap::CreateInstance().FInitialize();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// DeinitRegMimeMap()
|
|
//
|
|
VOID
|
|
DeinitRegMimeMap()
|
|
{
|
|
CRegMimeMap::DestroyInstance();
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// HrGetContentTypeByExt()
|
|
//
|
|
// Fetch the content type of a resource based on its path/URI extension.
|
|
// This function searches the following three places, in order, for a mapping:
|
|
//
|
|
// 1) a caller-supplied content type map
|
|
// 2) the global (metabase) content type map
|
|
// 3) the global (registry) content type map
|
|
//
|
|
// Parameters:
|
|
//
|
|
// pContentTypeMapLocal [IN] If non-NULL, points to the content type
|
|
// map to search first.
|
|
//
|
|
// pwszExt [IN] Extension to search on
|
|
// pwszBuf [OUT] Buffer in which to copy the mapped
|
|
// content type
|
|
// pcchBuf [IN] Size of buffer in characters including 0 termination
|
|
// [OUT] Size of mapped content type
|
|
//
|
|
// pfIsGlobalMapping [OUT] (Optional) Pointer to flag which is set
|
|
// if the mapping is from a global map.
|
|
//
|
|
// Returns:
|
|
//
|
|
// S_OK
|
|
// if a mapping was found and copied into the caller-supplied buffer
|
|
// The size of the mapped content type is returned in *pcchzBuf.
|
|
//
|
|
// HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)
|
|
// if no mapping was found in any of the maps
|
|
//
|
|
// HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY)
|
|
// if a mapping was found, but the caller supplied buffer was too small.
|
|
// The required size of the buffer is returned in *pcchzBuf.
|
|
//
|
|
HRESULT
|
|
HrGetContentTypeByExt( const IEcb& ecb,
|
|
const IContentTypeMap * pContentTypeMapLocal,
|
|
LPCWSTR pwszExt,
|
|
LPWSTR pwszBuf,
|
|
UINT * pcchBuf,
|
|
BOOL * pfIsGlobalMapping )
|
|
{
|
|
Assert(!pfIsGlobalMapping || !IsBadWritePtr(pfIsGlobalMapping, sizeof(BOOL)));
|
|
|
|
LPCWSTR pwszContentType = NULL;
|
|
auto_ref_ptr<IMDData> pMDData;
|
|
const IContentTypeMap * pContentTypeMapGlobal;
|
|
|
|
//
|
|
// If a local map was specified then check it first for
|
|
// the extension based mapping.
|
|
//
|
|
if ( pContentTypeMapLocal )
|
|
pwszContentType = pContentTypeMapLocal->PwszContentType(pwszExt);
|
|
|
|
//
|
|
// If this doesn't yield a mapping then try the global mime map.
|
|
// Note: if we fail to get any metadata for the global mime map
|
|
// then use gc_szAppl_Octet_Stream rather than trying the registry.
|
|
// We'd rather use a "safe" default than a possibly intentionally
|
|
// overridden value from the registry.
|
|
//
|
|
if ( !pwszContentType )
|
|
{
|
|
if ( SUCCEEDED(HrMDGetData(ecb, gc_wsz_Lm_MimeMap, gc_wsz_Lm_MimeMap, pMDData.load())) )
|
|
{
|
|
pContentTypeMapGlobal = pMDData->GetContentTypeMap();
|
|
|
|
if ( pContentTypeMapGlobal )
|
|
{
|
|
pwszContentType = pContentTypeMapGlobal->PwszContentType(pwszExt);
|
|
if (pwszContentType && pfIsGlobalMapping)
|
|
*pfIsGlobalMapping = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pwszContentType = gc_wszAppl_Octet_Stream;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Nothing in the global mime map either?
|
|
// Then try the registry as a last resort.
|
|
//
|
|
if ( !pwszContentType )
|
|
{
|
|
pwszContentType = CRegMimeMap::Instance().PwszContentType(pwszExt);
|
|
if (pwszContentType && pfIsGlobalMapping)
|
|
*pfIsGlobalMapping = TRUE;
|
|
}
|
|
|
|
//
|
|
// If there wasn't anything in the registry either then there is
|
|
// no mapping for this extension.
|
|
//
|
|
if ( !pwszContentType )
|
|
return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
|
|
|
|
//
|
|
// If we did find a mapping via one of the above methods
|
|
// then attempt to copy it into the caller-supplied buffer.
|
|
// If the buffer is not big enough, return an appropriate error.
|
|
// Note: FCopyStringToBuf() will fill in the required size
|
|
// if the buffer was not big enough.
|
|
//
|
|
return FCopyStringToBuf( pwszContentType,
|
|
pwszBuf,
|
|
pcchBuf ) ?
|
|
|
|
S_OK : HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// PszExt()
|
|
//
|
|
// Returns any extension (i.e. characters including
|
|
// and following a '.') appearing in the string pointed
|
|
// to by pchPathBegin that appear at or before pchPathEnd.
|
|
//
|
|
// Returns NULL if there is no extension.
|
|
//
|
|
inline LPCWSTR
|
|
PwszExt( LPCWSTR pwchPathBegin,
|
|
LPCWSTR pwchPathEnd )
|
|
{
|
|
Assert(pwchPathEnd);
|
|
|
|
//
|
|
// Scan backward from the designated end of the path looking
|
|
// for a '.' that begins an extension. If we don't find one
|
|
// or we find a path separator ('/') then there is no extension.
|
|
//
|
|
while ( pwchPathEnd-- > pwchPathBegin )
|
|
{
|
|
if ( L'.' == *pwchPathEnd )
|
|
return pwchPathEnd;
|
|
|
|
if ( L'/' == *pwchPathEnd )
|
|
return NULL;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// FGetContentType()
|
|
//
|
|
// Fetches the content type of the resource at the specified path/URI
|
|
// and copies it into a caller-supplied buffer.
|
|
//
|
|
// The copied content type comes from one of the following mappings:
|
|
//
|
|
// 1) Via an explicit mapping from the specified path/URI extension.
|
|
// 2) Via a ".*" (default) mapping
|
|
// 3) application/octet-stream
|
|
//
|
|
// Parameters:
|
|
//
|
|
// pContentTypeMapLocal [IN] If non-NULL, points to a content type
|
|
// map for HrGetContentTypeByExt() to
|
|
// search first for each of the first
|
|
// two methods above.
|
|
//
|
|
// pwszPath [IN] Path whose content type is desired.
|
|
// pwszBuf [OUT] Buffer in which to copy the mapped
|
|
// content type
|
|
// pcchBuf [IN] Size of buffer in characters including 0 termination
|
|
// [OUT] Size of mapped content type
|
|
//
|
|
// pfIsGlobalMapping [OUT] (Optional) Pointer to flag which is set
|
|
// if the mapping is from a global map.
|
|
//
|
|
// Returns:
|
|
//
|
|
// TRUE
|
|
// if the mapping was successfully copied into the caller-supplied buffer.
|
|
// The size of the mapped content type is returned in *pcchzBuf.
|
|
//
|
|
// FALSE
|
|
// if the caller-supplied buffer was too small.
|
|
// The required size of the buffer is returned in *pcchzBuf.
|
|
//
|
|
BOOL
|
|
FGetContentType( const IEcb& ecb,
|
|
const IContentTypeMap * pContentTypeMapLocal,
|
|
LPCWSTR pwszPath,
|
|
LPWSTR pwszBuf,
|
|
UINT * pcchBuf,
|
|
BOOL * pfIsGlobalMapping )
|
|
{
|
|
HRESULT hr;
|
|
|
|
CStackBuffer<WCHAR> pwszCopy;
|
|
BOOL fCopy = FALSE;
|
|
UINT cchPath = static_cast<UINT>(wcslen(pwszPath));
|
|
|
|
// Scan backward to skip all '/' characters at the end.
|
|
//
|
|
while ( cchPath && (L'/' == pwszPath[cchPath-1]) )
|
|
{
|
|
cchPath--;
|
|
fCopy = TRUE; // Fine to keep the assignment here, as clients usually
|
|
// do not put multiple wacks at the end of the path.
|
|
}
|
|
|
|
if (fCopy)
|
|
{
|
|
// Make the copy of the path without ending wacks.
|
|
//
|
|
if (!pwszCopy.resize(CbSizeWsz(cchPath)))
|
|
return FALSE;
|
|
|
|
memcpy( pwszCopy.get(), pwszPath, cchPath * sizeof(WCHAR) );
|
|
pwszCopy[cchPath] = L'\0';
|
|
|
|
// Swap the pointers
|
|
//
|
|
pwszPath = pwszCopy.get();
|
|
}
|
|
|
|
//
|
|
// First check for an extension mapping in both the specified
|
|
// content type map and the global mime map.
|
|
//
|
|
// The loop checks progressively longer extensions. E.g. a path
|
|
// of "/foo/bar/baz.a.b.c" will be checked for ".c" then ".b.c"
|
|
// then ".a.b.c". This is consistent with IIS' behavior.
|
|
//
|
|
for ( LPCWSTR pwszExt = PwszExt(pwszPath, pwszPath + cchPath);
|
|
pwszExt;
|
|
pwszExt = PwszExt(pwszPath, pwszExt) )
|
|
{
|
|
hr = HrGetContentTypeByExt( ecb,
|
|
pContentTypeMapLocal,
|
|
pwszExt,
|
|
pwszBuf,
|
|
pcchBuf,
|
|
pfIsGlobalMapping );
|
|
|
|
if ( HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) != hr )
|
|
{
|
|
Assert( S_OK == hr || HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY) == hr );
|
|
return SUCCEEDED(hr);
|
|
}
|
|
}
|
|
|
|
//
|
|
// There is no extension mapping so check both maps
|
|
// for a ".*" (default) mapping. Note: don't set *pfIsGlobalMapping if
|
|
// the ".*" mapping is the only one that applies. The ".*" mapping is
|
|
// a catch-all; it is ok for local mime maps to override it.
|
|
//
|
|
hr = HrGetContentTypeByExt( ecb,
|
|
pContentTypeMapLocal,
|
|
L".*",
|
|
pwszBuf,
|
|
pcchBuf,
|
|
NULL );
|
|
|
|
if ( HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) != hr )
|
|
{
|
|
Assert( S_OK == hr || HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY) == hr );
|
|
return SUCCEEDED(hr);
|
|
}
|
|
|
|
//
|
|
// No ".*" mapping either so use the default default --
|
|
// application/octet-stream.
|
|
//
|
|
return FCopyStringToBuf( gc_wszAppl_Octet_Stream,
|
|
pwszBuf,
|
|
pcchBuf );
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// FGetContentTypeFromPath()
|
|
//
|
|
// Fetch the content type associated with the extension of the
|
|
// specified file path.
|
|
//
|
|
BOOL FGetContentTypeFromPath( const IEcb& ecb,
|
|
LPCWSTR pwszPath,
|
|
LPWSTR pwszBuf,
|
|
UINT * pcchBuf )
|
|
{
|
|
return FGetContentType( ecb,
|
|
NULL, // No local map to check
|
|
pwszPath,
|
|
pwszBuf,
|
|
pcchBuf,
|
|
NULL ); // Don't care where the mapping comes from
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// FGetContentTypeFromURI()
|
|
//
|
|
// Retrieves the content type for the specified URI.
|
|
//
|
|
BOOL
|
|
FGetContentTypeFromURI( const IEcb& ecb,
|
|
LPCWSTR pwszURI,
|
|
LPWSTR pwszBuf,
|
|
UINT * pcchBuf,
|
|
BOOL * pfIsGlobalMapping )
|
|
{
|
|
auto_ref_ptr<IMDData> pMDData;
|
|
|
|
//
|
|
// Fetch the metadata for this URI. If it has a content type map
|
|
// then use it to look for a mapping. If it does not have a content
|
|
// type map then check the global mime map.
|
|
//
|
|
// Note: if we fail to get the metadata at all then default the
|
|
// content type to application/octet-stream. Do not use the global
|
|
// mime map just because we cannot get the metadata.
|
|
//
|
|
if ( FAILED(HrMDGetData(ecb, pwszURI, pMDData.load())) )
|
|
{
|
|
DebugTrace( "FGetContentTypeFromURI() - HrMDGetData() failed to get metadata for %S. Using application/octet-stream...\n", pwszURI );
|
|
return FCopyStringToBuf( gc_wszAppl_Octet_Stream,
|
|
pwszBuf,
|
|
pcchBuf );
|
|
}
|
|
|
|
const IContentTypeMap * pContentTypeMap = pMDData->GetContentTypeMap();
|
|
|
|
//
|
|
// If there is a content type map specific to this URI then
|
|
// try it first looking for a "*" (unconditional) mapping.
|
|
//
|
|
if ( pContentTypeMap )
|
|
{
|
|
LPCWSTR pwszContentType = pContentTypeMap->PwszContentType(gc_wsz_Star);
|
|
if ( pwszContentType )
|
|
return FCopyStringToBuf( pwszContentType,
|
|
pwszBuf,
|
|
pcchBuf );
|
|
}
|
|
|
|
//
|
|
// There was either no "*" mapping or no URI-specific map
|
|
// so check the global maps
|
|
//
|
|
return FGetContentType( ecb,
|
|
pContentTypeMap,
|
|
pwszURI,
|
|
pwszBuf,
|
|
pcchBuf,
|
|
pfIsGlobalMapping );
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// ScApplyStarExt()
|
|
//
|
|
// Determines whether the mapping "*" --> pwszContentType should be used
|
|
// instead of the mapping *ppwszExt --> pwszContentType based on the
|
|
// following criteria:
|
|
//
|
|
// Use the mapping "*" --> pwszContentType if:
|
|
//
|
|
// o *ppwszExt is already "*", OR
|
|
// o a mapping exists in pwszMappings for *ppwszExt whose content type
|
|
// is not the same as pwszContentType, OR
|
|
// o a "*" mapping exists in pwszMappings.
|
|
//
|
|
// Use *ppwszExt --> pwszContentType otherwise.
|
|
//
|
|
// Returns:
|
|
//
|
|
// The value returned in *ppwszExt indicates the mapping to be used.
|
|
//
|
|
SCODE
|
|
ScApplyStarExt( LPWSTR pwszMappings,
|
|
LPCWSTR pwszContentType,
|
|
LPCWSTR * ppwszExt )
|
|
|
|
{
|
|
SCODE sc = S_OK;
|
|
|
|
Assert(pwszMappings);
|
|
Assert(!IsBadWritePtr(ppwszExt, sizeof(LPCWSTR)));
|
|
Assert(*ppwszExt);
|
|
Assert(pwszContentType);
|
|
|
|
//
|
|
// Parse out the extension and type/subtype for each
|
|
// item and check for conflicts or "*" mappings.
|
|
//
|
|
for ( LPWSTR pwszMapping = pwszMappings;
|
|
L'*' != *(*ppwszExt) && *pwszMapping; )
|
|
{
|
|
enum {
|
|
ISZ_CT_EXT = 0,
|
|
ISZ_CT_TYPE,
|
|
CSZ_CT_FIELDS
|
|
};
|
|
|
|
LPWSTR rgpwsz[CSZ_CT_FIELDS];
|
|
|
|
//
|
|
// Digest the metadata for this mapping
|
|
//
|
|
{
|
|
UINT cchMapping;
|
|
|
|
if ( !FParseMDData( pwszMapping,
|
|
rgpwsz,
|
|
CSZ_CT_FIELDS,
|
|
&cchMapping ) )
|
|
{
|
|
sc = E_FAIL;
|
|
DebugTrace("ScApplyStarExt() - Malformed metadata 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
|
|
pwszMapping += cchMapping;
|
|
}
|
|
|
|
Assert(rgpwsz[ISZ_CT_EXT]);
|
|
Assert(rgpwsz[ISZ_CT_TYPE]);
|
|
|
|
//
|
|
// If this is a "*" mapping OR
|
|
// If the extension matches *ppszExt AND
|
|
// the content types conflict
|
|
//
|
|
// then use a "*" mapping.
|
|
//
|
|
if ((L'*' == *rgpwsz[ISZ_CT_EXT]) ||
|
|
(!_wcsicmp((*ppwszExt), rgpwsz[ISZ_CT_EXT]) &&
|
|
_wcsicmp(pwszContentType, rgpwsz[ISZ_CT_TYPE])))
|
|
{
|
|
*ppwszExt = gc_wsz_Star;
|
|
}
|
|
|
|
//
|
|
// !!!IMPORTANT!!! FParseMDData() munges the mapping string.
|
|
// Specifically, it replaces the comma separator with a null.
|
|
// We always need to restore the comma so that the mappings
|
|
// string is not modified by this function!
|
|
//
|
|
*(rgpwsz[ISZ_CT_EXT] + wcslen(rgpwsz[ISZ_CT_EXT])) = L',';
|
|
}
|
|
|
|
ret:
|
|
|
|
return sc;
|
|
}
|
|
|
|
DEC_CONST WCHAR gc_wszIisWebFile[] = L"IisWebFile";
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// ScAddMimeMap()
|
|
//
|
|
// Adds the mapping pwszExt --> pwszContentType to the mime map at
|
|
// the metabase path pwszMDPath relative to the currently open
|
|
// metabase handle mdoh, creating a new mime map as required.
|
|
//
|
|
// A new mime map is required when there is no existing mime map
|
|
// (pwszMappings is NULL) or if a "*" mapping is being set. In the
|
|
// latter case, the "*" map overwrites whatever mapping is there.
|
|
//
|
|
SCODE
|
|
ScAddMimeMap( const CMDObjectHandle& mdoh,
|
|
LPCWSTR pwszMDPath,
|
|
LPWSTR pwszMappings,
|
|
UINT cchMappings,
|
|
LPCWSTR pwszExt,
|
|
LPCWSTR pwszContentType )
|
|
{
|
|
CStackBuffer<WCHAR> wszBuf;
|
|
UINT cchContentType;
|
|
UINT cchExt;
|
|
WCHAR * pwch;
|
|
|
|
Assert(pwszExt);
|
|
Assert(pwszContentType);
|
|
|
|
cchExt = static_cast<UINT>(wcslen(pwszExt));
|
|
cchContentType = static_cast<UINT>(wcslen(pwszContentType));
|
|
|
|
// If content type we want to set is blank, then do not
|
|
// attempt to do that - IIS does not properly understand
|
|
// such kind of thing, and the item with such content
|
|
// type is to be treated as application/octet-stream
|
|
// which will be guaranteed by the absence of content
|
|
// type in the metabase.
|
|
//
|
|
if (L'\0' == *pwszContentType)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
if (pwszMappings && L'*' != *pwszExt)
|
|
{
|
|
// IIS has an interesting concept of an empty mapping. Instead
|
|
// of just a single null (indicating an empty list of mapping
|
|
// strings) it uses a double null which to us would actually mean
|
|
// a list of strings consisting solely of the empty string!
|
|
// Anyway, if we add a mapping after this "empty mapping" neither
|
|
// IIS nor HTTPEXT will ever see it because the mime map checking
|
|
// implementations in both code bases treat the extraneous null
|
|
// as the list terminator.
|
|
//
|
|
// If we have an "empty" set of mappings then REPLACE
|
|
// it with a set consisting of just the new mapping.
|
|
//
|
|
if (2 == cchMappings && !*pwszMappings)
|
|
--cchMappings;
|
|
|
|
// Start at the end of the current mappings. Skip the extra
|
|
// null at the end. We will add it back later.
|
|
//
|
|
Assert(cchMappings >= 1);
|
|
Assert(L'\0' == pwszMappings[cchMappings-1]);
|
|
pwch = pwszMappings + cchMappings - 1;
|
|
}
|
|
else
|
|
{
|
|
// Allocate enough space including list terminating 0
|
|
//
|
|
if (!wszBuf.resize(CbSizeWsz(CchExtMapping(cchExt, cchContentType))))
|
|
return E_OUTOFMEMORY;
|
|
|
|
// Since this is the only mapping, start from the beginning
|
|
//
|
|
pwszMappings = wszBuf.get();
|
|
pwch = pwszMappings;
|
|
}
|
|
|
|
// Append the new mapping to the end of the existing mappings (if any).
|
|
//
|
|
pwch = PwchFormatExtMapping(pwch,
|
|
pwszExt,
|
|
cchExt,
|
|
pwszContentType,
|
|
cchContentType);
|
|
|
|
// Terminate the new set of mappings
|
|
//
|
|
*pwch++ = L'\0';
|
|
|
|
// Write the mappings out to the metabase
|
|
//
|
|
METADATA_RECORD mdrec;
|
|
|
|
//$ REVIEW: if the value for pwszMDPath is non-NULL, then this means that the key to
|
|
// which we are trying to right, does not exist at this point. If it did, we would have
|
|
// opened it directly and set the data on the node directly. In the case of it being
|
|
// non-NULL, that means that we must also set the MD_KEY_TYPE as well.
|
|
//
|
|
if (NULL != pwszMDPath)
|
|
{
|
|
mdrec.dwMDIdentifier = MD_KEY_TYPE;
|
|
mdrec.dwMDAttributes = METADATA_NO_ATTRIBUTES;
|
|
mdrec.dwMDUserType = IIS_MD_UT_FILE;
|
|
mdrec.dwMDDataType = STRING_METADATA;
|
|
mdrec.dwMDDataLen = CbSizeWsz(CchConstString(gc_wszIisWebFile));
|
|
mdrec.pbMDData = reinterpret_cast<LPBYTE>(const_cast<WCHAR*>(gc_wszIisWebFile));
|
|
(void) mdoh.HrSetMetaData (pwszMDPath, &mdrec);
|
|
}
|
|
//
|
|
//$ REVIEW: end.
|
|
|
|
mdrec.dwMDIdentifier = MD_MIME_MAP;
|
|
mdrec.dwMDAttributes = METADATA_INHERIT;
|
|
mdrec.dwMDUserType = IIS_MD_UT_FILE;
|
|
mdrec.dwMDDataType = MULTISZ_METADATA;
|
|
mdrec.dwMDDataLen = static_cast<DWORD>(pwch - pwszMappings) * sizeof(WCHAR);
|
|
mdrec.pbMDData = reinterpret_cast<LPBYTE>(pwszMappings);
|
|
|
|
return mdoh.HrSetMetaData(pwszMDPath, &mdrec);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// ScSetStarMimeMap()
|
|
//
|
|
SCODE
|
|
ScSetStarMimeMap( const IEcb& ecb,
|
|
LPCWSTR pwszURI,
|
|
LPCWSTR pwszContentType )
|
|
{
|
|
SCODE sc = E_OUTOFMEMORY;
|
|
|
|
// Get the metabase path corresponding to pwszURI.
|
|
//
|
|
CStackBuffer<WCHAR,MAX_PATH> pwszMDPathURI;
|
|
if (NULL == pwszMDPathURI.resize(CbMDPathW(ecb,pwszURI)))
|
|
return sc;
|
|
|
|
{
|
|
MDPathFromURIW(ecb, pwszURI, pwszMDPathURI.get());
|
|
CMDObjectHandle mdoh(ecb);
|
|
LPCWSTR pwszMDPathMimeMap;
|
|
|
|
// Open a metabase object at or above the path where we want to set
|
|
// the star mime map.
|
|
//
|
|
sc = HrMDOpenMetaObject( pwszMDPathURI.get(),
|
|
METADATA_PERMISSION_WRITE,
|
|
1000, // timeout in msec (1.0 sec)
|
|
&mdoh );
|
|
if (SUCCEEDED(sc))
|
|
{
|
|
pwszMDPathMimeMap = NULL;
|
|
}
|
|
else
|
|
{
|
|
if (sc != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))
|
|
{
|
|
DebugTrace ("ScSetStarMimeMap() - HrMDOpenMetaObject(pszMDPathURI) "
|
|
"failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
|
|
sc = HrMDOpenMetaObject( ecb.PwszMDPathVroot(),
|
|
METADATA_PERMISSION_WRITE,
|
|
1000,
|
|
&mdoh );
|
|
if (FAILED(sc))
|
|
{
|
|
DebugTrace("ScSetStarMimeMap() - HrMDOpenMetaObject(ecb.PwszMDPathVroot()) "
|
|
"failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
|
|
Assert(!_wcsnicmp(pwszMDPathURI.get(),
|
|
ecb.PwszMDPathVroot(),
|
|
wcslen(ecb.PwszMDPathVroot())));
|
|
|
|
pwszMDPathMimeMap = pwszMDPathURI.get() + wcslen(ecb.PwszMDPathVroot());
|
|
}
|
|
|
|
// Add the "*" mime map
|
|
//
|
|
sc = ScAddMimeMap(mdoh,
|
|
pwszMDPathMimeMap,
|
|
NULL, // Overwrite existing mimemap (if any)
|
|
0, //
|
|
gc_wsz_Star, // with "*" --> pszContentType
|
|
pwszContentType);
|
|
if (FAILED(sc))
|
|
{
|
|
DebugTrace("ScSetStarMimeMap() - ScAddMimeMap() failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
}
|
|
|
|
ret:
|
|
|
|
return sc;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// ScAddExtMimeMap() (aka the guts behind NT5:292139)
|
|
//
|
|
// Use the following algorithm to set the content type (pwszContentType)
|
|
// of the resource at pwszURI:
|
|
//
|
|
// If a mime map exists somewhere at or above the metabase path for
|
|
// pwszURI that has no mapping for the extension of pwszURI AND that
|
|
// mapping does NOT have a "*" mapping, then add a mapping from
|
|
// the extension of pwszURI to pwszContentType to that map.
|
|
//
|
|
// If no such map exists then create one at the site root and
|
|
// add the mapping there.
|
|
//
|
|
// In all other cases, add the mapping "*" --> pwszContentType
|
|
// at the level of pwszURI.
|
|
//
|
|
// The idea behind this complicated little routine is to reduce the number
|
|
// of "*" mappings that we create in the metabase to represent content types
|
|
// of resources with extensions that are not found in any administrator-defined
|
|
// mime map or global mime map. This helps most in scenarios where a new
|
|
// application is deployed which uses a heretofore unknown extension and
|
|
// the install utility (or admin) neglects to register a content type mapping
|
|
// for that application in any mime map.
|
|
//
|
|
// Without this functionality, we could end up creating "*" mappings for
|
|
// every resource created with an unknown extension. With time that would
|
|
// drag down the performance of the metabase significantly.
|
|
//
|
|
SCODE
|
|
ScAddExtMimeMap( const IEcb& ecb,
|
|
LPCWSTR pwszURI,
|
|
LPCWSTR pwszContentType )
|
|
{
|
|
// Metabase path corresponding to pwszURI. We form a relative path,
|
|
// off of this path, where we set a "*" mapping if we need to do so.
|
|
//
|
|
CStackBuffer<WCHAR,MAX_PATH> pwszMDPathURI(CbMDPathW(ecb, pwszURI));
|
|
if (!pwszMDPathURI.get())
|
|
return E_OUTOFMEMORY;
|
|
|
|
MDPathFromURIW(ecb, pwszURI, pwszMDPathURI.get());
|
|
UINT cchPathURI = static_cast<UINT>(wcslen(pwszMDPathURI.get()));
|
|
|
|
// Metabase path to the non-inherited mime map closest to pwszURI. When there
|
|
// is no such mime map, this is just the metabase path to the site root.
|
|
//
|
|
CStackBuffer<WCHAR,MAX_PATH> pwszMDPathMimeMap(CbSizeWsz(cchPathURI));
|
|
if (!pwszMDPathMimeMap.get())
|
|
return E_OUTOFMEMORY;
|
|
|
|
memcpy(pwszMDPathMimeMap.get(),
|
|
pwszMDPathURI.get(),
|
|
CbSizeWsz(cchPathURI));
|
|
UINT cchPathMimeMap = cchPathURI;
|
|
LPWSTR pwszMDPathMM = pwszMDPathMimeMap.get();
|
|
|
|
// Buffer for the metabase path to the site root (e.g. "/LM/W3SVC/1/root").
|
|
//
|
|
WCHAR rgwchMDPathSiteRoot[MAX_PATH];
|
|
SCODE sc = S_OK;
|
|
|
|
// Locate the non-inherited mime map "closest" to pszURI by probing successively
|
|
// shorter path prefixes until a non-inherited mime map is found or until we reach
|
|
// the site root, whichever happens first.
|
|
//
|
|
for ( ;; )
|
|
{
|
|
// Fetch the (hopefully cached) metadata for the current metabase path.
|
|
//
|
|
//$OPT
|
|
// Note the use of /LM/W3SVC as the "open" path. We use that path because
|
|
// it is guaranteed to exist (a requirement for this form of HrMDGetData())
|
|
// and because it is above the site root. It is also easily computable
|
|
// (it's a constant!). It does however lock a pretty huge portion of the
|
|
// metabase fetching the metadata. If this turns out to not perform well
|
|
// (i.e. the call fails due to timeout under normal usage) then we should
|
|
// evaluate whether a "lower" path -- like the site root -- would be a
|
|
// more appropriate choice.
|
|
//
|
|
auto_ref_ptr<IMDData> pMDDataMimeMap;
|
|
sc = HrMDGetData(ecb,
|
|
pwszMDPathMM,
|
|
gc_wsz_Lm_W3Svc,
|
|
pMDDataMimeMap.load());
|
|
if (FAILED(sc))
|
|
{
|
|
DebugTrace("ScAddExtMimeMap() - HrMDGetData(pwszMDPathMimeMap) failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
|
|
// Look for a mime map (inherited or not) in the metadata. If we don't find
|
|
// one then we'll want to create one at the site root.
|
|
//
|
|
IContentTypeMap * pContentTypeMap;
|
|
pContentTypeMap = pMDDataMimeMap->GetContentTypeMap();
|
|
if (!pContentTypeMap)
|
|
{
|
|
ULONG cchPathSiteRoot = CElems(rgwchMDPathSiteRoot) - gc_cch_Root;
|
|
|
|
// We did not find any mime map (inherited or otherwise) so
|
|
// set up to create a mime map at the site root.
|
|
//
|
|
// Get the instance root (e.g. "/LM/W3SVC/1")
|
|
//
|
|
if (!ecb.FGetServerVariable("INSTANCE_META_PATH",
|
|
rgwchMDPathSiteRoot,
|
|
&cchPathSiteRoot))
|
|
{
|
|
sc = HRESULT_FROM_WIN32(GetLastError());
|
|
DebugTrace("ScAddExtMimeMap() - ecb.FGetServerVariable(INSTANCE_META_PATH) failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
|
|
// Convert the size (in bytes) of the site root path to a length (in characters).
|
|
// Remember: cbPathSiteRoot includes the null terminator.
|
|
//
|
|
cchPathMimeMap = cchPathSiteRoot - 1;
|
|
|
|
// Tack on the "/root" part to get something like "/LM/W3SVC/1/root".
|
|
//
|
|
memcpy( rgwchMDPathSiteRoot + cchPathMimeMap,
|
|
gc_wsz_Root,
|
|
CbSizeWsz(gc_cch_Root)); // copy the null terminator too
|
|
|
|
cchPathMimeMap += gc_cch_Root;
|
|
pwszMDPathMM = rgwchMDPathSiteRoot;
|
|
break;
|
|
}
|
|
else if (!pContentTypeMap->FIsInherited())
|
|
{
|
|
// We found a non-inherited mime map at pwszMDPathMimeMap
|
|
// so we are done looking.
|
|
//
|
|
break;
|
|
}
|
|
|
|
// We found a mime map, but it was an inherited mime map,
|
|
// so back up one path component and check there. Eventually
|
|
// we will find the path where it was inherited from.
|
|
//
|
|
while ( L'/' != pwszMDPathMM[--cchPathMimeMap])
|
|
Assert(cchPathMimeMap > 0);
|
|
|
|
pwszMDPathMM[cchPathMimeMap] = L'\0';
|
|
}
|
|
|
|
// At this point, pwszMDPathMimeMap is the location of an existing non-inherited
|
|
// mime map or the site root. Now we want to lock down the metabase at this
|
|
// path (and everything below it) so that we can consistently check the actual
|
|
// current mime map contents (remember, we were looking at a cached view above!)
|
|
// and update them with the new mapping.
|
|
//
|
|
{
|
|
CMDObjectHandle mdoh(ecb);
|
|
METADATA_RECORD mdrec;
|
|
|
|
// Figure out the file extension on the URI. If it doesn't have one
|
|
// then we know right away that we are going to use a "*" mapping.
|
|
//
|
|
LPCWSTR pwszExt = PwszExt(pwszURI, pwszURI + wcslen(pwszURI));
|
|
if (!pwszExt)
|
|
pwszExt = gc_wsz_Star;
|
|
|
|
// Buffer size for the mime map metadata. 8K should be big enough
|
|
// for most mime maps -- the global mime map at lm/MimeMap is only ~4K.
|
|
//
|
|
enum { CCH_MAPPINGS_MAX = 2 * 1024 };
|
|
|
|
// Compute the size of the new mapping and do a quick check
|
|
// to handle any rogue user who tries to pull a fast one by creating
|
|
// a mapping that is ridiculously large.
|
|
//
|
|
UINT cchNewMapping = CchExtMapping(static_cast<UINT>(wcslen(pwszExt)),
|
|
static_cast<UINT>(wcslen(pwszContentType)));
|
|
|
|
if (cchNewMapping >= CCH_MAPPINGS_MAX )
|
|
{
|
|
sc = E_DAV_INVALID_HEADER; //HSC_BAD_REQUEST
|
|
goto ret;
|
|
}
|
|
|
|
// Buffer for the mime map metadata. 8K should be big enough for most
|
|
// mime maps -- the global mime map at lm/MimeMap is only ~4K.
|
|
// And don't forget to leave room at the end for the new mapping!
|
|
//
|
|
CStackBuffer<BYTE,4096> rgbData;
|
|
Assert (rgbData.size() == (CCH_MAPPINGS_MAX * sizeof(WCHAR)));
|
|
DWORD cbData = (CCH_MAPPINGS_MAX - cchNewMapping) * sizeof(WCHAR);
|
|
|
|
// Open the metadata object at the path we found. We know that the path
|
|
// that we want to open already exists -- if it is a path to some node
|
|
// with a non-inherited mime map then it is the path to the site root.
|
|
//
|
|
sc = HrMDOpenMetaObject( pwszMDPathMM,
|
|
METADATA_PERMISSION_WRITE |
|
|
METADATA_PERMISSION_READ,
|
|
1000, // timeout in msec (1.0 sec)
|
|
&mdoh );
|
|
if (FAILED(sc))
|
|
{
|
|
DebugTrace("ScAddExtMimeMap() - HrMDOpenMetaObject() failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
|
|
// Fetch the mime map.
|
|
//
|
|
mdrec.dwMDIdentifier = MD_MIME_MAP;
|
|
mdrec.dwMDAttributes = METADATA_INHERIT;
|
|
mdrec.dwMDUserType = IIS_MD_UT_FILE;
|
|
mdrec.dwMDDataType = MULTISZ_METADATA;
|
|
mdrec.dwMDDataLen = cbData;
|
|
mdrec.pbMDData = rgbData.get();
|
|
|
|
sc = mdoh.HrGetMetaData( NULL, // No relative path to the mime map.
|
|
// We opened a path directly there above.
|
|
&mdrec,
|
|
&cbData );
|
|
|
|
if (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == sc)
|
|
{
|
|
// In the unlikely event that the static-size buffer above wasn't
|
|
// big enough, then try reading again into a one that is.
|
|
//
|
|
// Again, leave enough room for the new mapping
|
|
//
|
|
mdrec.dwMDDataLen = cbData;
|
|
mdrec.pbMDData = rgbData.resize(cbData + cchNewMapping * sizeof(WCHAR));
|
|
sc = mdoh.HrGetMetaData( NULL, &mdrec, &cbData );
|
|
}
|
|
if (FAILED(sc))
|
|
{
|
|
if (MD_ERROR_DATA_NOT_FOUND != sc)
|
|
{
|
|
DebugTrace("ScAddExtMimeMap() - HrMDOpenMetaObject() failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
|
|
// If we don't find a mapping, that's fine. Most likely we are just
|
|
// at the site root. There is also a slim chance that the admin could
|
|
// have deleted the mapping between the time we found it in the cache
|
|
// and the time that we locked the path in the metabase.
|
|
//
|
|
mdrec.pbMDData = NULL;
|
|
sc = S_OK;
|
|
}
|
|
|
|
// If we don't have a mime map then use a "*" mapping unless we are
|
|
// at the site root in which case we should CREATE a mime map with
|
|
// a single mapping for the URI extension.
|
|
//
|
|
if (!mdrec.pbMDData)
|
|
{
|
|
if (rgwchMDPathSiteRoot != pwszMDPathMM)
|
|
pwszExt = gc_wsz_Star;
|
|
}
|
|
|
|
// If we found a mime map and it is still not inherited then we have
|
|
// some more checking to do. Yes, the mime map actually can be
|
|
// inherited at this point. See below for why.
|
|
//
|
|
else if (!(mdrec.dwMDAttributes & METADATA_ISINHERITED))
|
|
{
|
|
// Check whether we should apply a "*" mapping rather than
|
|
// the extension mapping that we ideally want. The rules
|
|
// governing this decision are outlined in ScApplyStarExt().
|
|
//
|
|
sc = ScApplyStarExt(reinterpret_cast<LPWSTR>(mdrec.pbMDData),
|
|
pwszContentType,
|
|
&pwszExt);
|
|
if (FAILED(sc))
|
|
{
|
|
DebugTrace("ScAddExtMimeMap() - ScApplyStarExt() failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
}
|
|
|
|
// We found a mime map, but for some oddball reason it now appears to be
|
|
// inherited! This can happen if the admin manages to change things
|
|
// between the time we check the cache and when we open pszMDPathMimeMap
|
|
// for writing. This should be a sufficiently rare case that falling back
|
|
// to a "*" mapping here isn't so bad.
|
|
//
|
|
else
|
|
{
|
|
Assert(mdrec.pbMDData);
|
|
Assert(mdrec.dwMDAttributes & METADATA_ISINHERITED);
|
|
pwszExt = gc_wsz_Star;
|
|
}
|
|
|
|
// Ok, we're all set. We have the extension ("*" or .somethingorother).
|
|
// We have the content type. We have the existing mappings (if any).
|
|
// Add in the new mapping.
|
|
//
|
|
// Note: if we are adding a "*" mapping, we always want to add it
|
|
// at the level of the URI. But since the metabase handle we have
|
|
// open is at some level above the URI, the path we pass to ScAddMimeMap()
|
|
// below must be relative to the path used to open the handle.
|
|
// Easy enough. That path is just what's left of the URI path
|
|
// beyond where we found (or would have created) the non-inherited
|
|
// mime map.
|
|
//
|
|
sc = ScAddMimeMap(mdoh,
|
|
(L'*' == *pwszExt) ?
|
|
pwszMDPathURI.get() + cchPathMimeMap :
|
|
NULL,
|
|
reinterpret_cast<LPWSTR>(mdrec.pbMDData),
|
|
mdrec.dwMDDataLen / sizeof(WCHAR),
|
|
pwszExt,
|
|
pwszContentType);
|
|
if (FAILED(sc))
|
|
{
|
|
DebugTrace("ScAddExtMimeMap() - ScAddMimeMap(pszExt) failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
}
|
|
|
|
ret:
|
|
|
|
return sc;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// ScSetContentType()
|
|
//
|
|
SCODE
|
|
ScSetContentType( const IEcb& ecb,
|
|
LPCWSTR pwszURI,
|
|
LPCWSTR pwszContentTypeWanted )
|
|
{
|
|
BOOL fIsGlobalMapping = FALSE;
|
|
CStackBuffer<WCHAR> pwszContentTypeCur;
|
|
SCODE sc = S_OK;
|
|
UINT cchContentTypeCur;
|
|
|
|
// Check what the content type would be if we didn't do anything.
|
|
// If it's what we want, then we're done. No need to open the metabase
|
|
// for anything!
|
|
//
|
|
cchContentTypeCur = pwszContentTypeCur.celems();
|
|
if ( !FGetContentTypeFromURI( ecb,
|
|
pwszURI,
|
|
pwszContentTypeCur.get(),
|
|
&cchContentTypeCur,
|
|
&fIsGlobalMapping ) )
|
|
{
|
|
if (!pwszContentTypeCur.resize(cchContentTypeCur * sizeof(WCHAR)))
|
|
{
|
|
sc = E_OUTOFMEMORY;
|
|
goto ret;
|
|
}
|
|
if ( !FGetContentTypeFromURI( ecb,
|
|
pwszURI,
|
|
pwszContentTypeCur.get(),
|
|
&cchContentTypeCur,
|
|
&fIsGlobalMapping))
|
|
{
|
|
//
|
|
// If the size of the content type keeps changing on us
|
|
// then the server is too busy. Give up.
|
|
//
|
|
sc = ERROR_PATH_BUSY;
|
|
DebugTrace("ScSetContentType() - FGetContentTypeFromURI() failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the content type is already what we want then don't change a thing.
|
|
//
|
|
if ( !_wcsicmp( pwszContentTypeWanted, pwszContentTypeCur.get()))
|
|
{
|
|
sc = S_OK;
|
|
goto ret;
|
|
}
|
|
|
|
//
|
|
// The current content type isn't what we want so we will have to set
|
|
// something in the metabase. If the mapping for this extension came
|
|
// from one of the global maps, then always override the mapping by
|
|
// setting a "*" mapping at the URI level. If the mapping was not
|
|
// a global one then what we do gets very complicated due to Raid NT5:292139....
|
|
//
|
|
if (fIsGlobalMapping)
|
|
{
|
|
sc = ScSetStarMimeMap(ecb,
|
|
pwszURI,
|
|
pwszContentTypeWanted);
|
|
if (FAILED(sc))
|
|
{
|
|
DebugTrace("ScSetContentType() - ScAddExtMimeMap() failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sc = ScAddExtMimeMap(ecb,
|
|
pwszURI,
|
|
pwszContentTypeWanted);
|
|
if (FAILED(sc))
|
|
{
|
|
DebugTrace("ScSetContentType() - ScAddExtMimeMap() failed 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
}
|
|
|
|
ret:
|
|
|
|
return sc;
|
|
}
|
|
|
|
/*
|
|
* ScCanAcceptContent()
|
|
*
|
|
* Purpose:
|
|
*
|
|
* Check if the given content type is acceptable
|
|
*
|
|
* Parameters:
|
|
*
|
|
* pwszAccepts [in] the Accept header;
|
|
* pwszApp [in] the application part of the content type
|
|
* pwszType [in] the sub type of the content type
|
|
*
|
|
* Returns:
|
|
*
|
|
* S_OK - if the request accepts the content type, no wildcard matching
|
|
* S_FALSE - if the request accepts the content type, wildcard matching
|
|
* E_DAV_RESPONSE_TYPE_UNACCEPTED - if the response type was unaccepted
|
|
*/
|
|
SCODE __fastcall
|
|
ScCanAcceptContent (LPCWSTR pwszAccepts, LPWSTR pwszApp, LPWSTR pwszType)
|
|
{
|
|
SCODE sc = E_DAV_RESPONSE_TYPE_UNACCEPTED;
|
|
HDRITER_W hit(pwszAccepts);
|
|
LPWSTR pwsz;
|
|
LPCWSTR pwszAppType;
|
|
LPCWSTR pwszSubType;
|
|
|
|
// Rip through the entries in the header...
|
|
//
|
|
while (NULL != (pwszAppType = hit.PszNext()))
|
|
{
|
|
pwsz = const_cast<LPWSTR>(pwszAppType);
|
|
|
|
// Search for the end of the application type
|
|
// '/' is the sub type separator, and ';' starts the parameters
|
|
//
|
|
while ( *pwsz &&
|
|
(L'/' != *pwsz) &&
|
|
(L';' != *pwsz) )
|
|
pwsz++;
|
|
|
|
if (L'/' == *pwsz)
|
|
{
|
|
// Make pwszAppType point to the application type ...
|
|
//
|
|
*pwsz++ = L'\0';
|
|
|
|
// ... and pszSubType point to the subtype
|
|
//
|
|
pwszSubType = pwsz;
|
|
while (*pwsz && (L';' != *pwsz))
|
|
pwsz++;
|
|
|
|
*pwsz = L'\0';
|
|
}
|
|
else
|
|
{
|
|
// There's not sub type.
|
|
//
|
|
*pwsz = L'\0';
|
|
|
|
// point pszSubType to a empty string, instead of setting it to NULL
|
|
//
|
|
pwszSubType = pwsz;
|
|
}
|
|
|
|
// Here're the rules:
|
|
//
|
|
// A application type * match any type (including */xxx)
|
|
// type/* match all subtypes of that app type
|
|
// type/subtype looks for exact match
|
|
//
|
|
if (!wcscmp (pwszAppType, gc_wsz_Star))
|
|
{
|
|
// This is a wild-card match. So, S_FALSE is used
|
|
// to distinguish this from an exact match.
|
|
//
|
|
sc = S_FALSE;
|
|
}
|
|
else if (!wcscmp (pwszAppType, pwszApp))
|
|
{
|
|
if (!wcscmp (pwszSubType, gc_wsz_Star))
|
|
{
|
|
// Again, a wild-card matching will result in
|
|
// an S_FALSE return.
|
|
//
|
|
sc = S_FALSE;
|
|
}
|
|
else if (!wcscmp (pwszSubType, pwszType))
|
|
{
|
|
// Exact matches return S_OK
|
|
//
|
|
sc = S_OK;
|
|
}
|
|
}
|
|
|
|
// If we had any sort of a match by this point, we are
|
|
// pretty much done.
|
|
//
|
|
if (!FAILED (sc))
|
|
break;
|
|
}
|
|
|
|
return sc;
|
|
}
|
|
|
|
/*
|
|
* ScIsAcceptable()
|
|
*
|
|
* Purpose:
|
|
*
|
|
* Checks if a given content type is acceptable for a given request.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* pmu [in] pointer to the IMethUtil object
|
|
* pwszContent [in] content type to ask about
|
|
*
|
|
* Returns:
|
|
*
|
|
* S_OK - if the request accepts the content type and the header existed
|
|
* S_FALSE - if the request accepts the content type and the header did not
|
|
* exist or was blank, or any wildcard matching occured
|
|
* E_DAV_RESPONSE_TYPE_UNACCEPTED - if the response type was unaccepted
|
|
* E_OUTOFMEMORY - if memory allocation failure occurs
|
|
*
|
|
*/
|
|
SCODE
|
|
ScIsAcceptable (IMethUtil * pmu, LPCWSTR pwszContent)
|
|
{
|
|
SCODE sc = S_OK;
|
|
LPCWSTR pwszAccept = NULL;
|
|
CStackBuffer<WCHAR> pwsz;
|
|
UINT cch;
|
|
LPWSTR pwch;
|
|
|
|
Assert( pmu );
|
|
Assert( pwszContent );
|
|
|
|
// If the accept header is NULL or empty, then we will gladly
|
|
// accept any type of file. Do not apply URL conversion rules
|
|
// for this header.
|
|
//
|
|
pwszAccept = pmu->LpwszGetRequestHeader (gc_szAccept, FALSE);
|
|
if (!pwszAccept || (0 == wcslen(pwszAccept)))
|
|
{
|
|
sc = S_FALSE;
|
|
goto ret;
|
|
}
|
|
|
|
// Make a local copy of the content-type seeing
|
|
// that we are going to munge while processing
|
|
//
|
|
cch = static_cast<UINT>(wcslen(pwszContent) + 1);
|
|
if (!pwsz.resize(cch * sizeof(WCHAR)))
|
|
{
|
|
sc = E_OUTOFMEMORY;
|
|
DebugTrace("ScIsAcceptable() - Failed to allocate memory 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
memcpy(pwsz.get(), pwszContent, cch * sizeof(WCHAR));
|
|
|
|
// Split the content type into its two components
|
|
//
|
|
for (pwch = pwsz.get(); *pwch && (L'/' != *pwch); pwch++)
|
|
;
|
|
|
|
// If there was app/type pair, we want to skip
|
|
// the '/' character. Otherwise, lets just see
|
|
// What we get.
|
|
//
|
|
if (*pwch != 0)
|
|
*pwch++ = 0;
|
|
|
|
// At this point, rgch refers to the application
|
|
// portion of the the content type. pch refers
|
|
// to the subtype. Do the search!
|
|
//
|
|
sc = ScCanAcceptContent (pwszAccept, pwsz.get(), pwch);
|
|
|
|
ret:
|
|
|
|
return sc;
|
|
}
|
|
|
|
/*
|
|
* ScIsContentType()
|
|
*
|
|
* Purpose:
|
|
*
|
|
* Check if the specified content type is provide by the client
|
|
* SCODE is returned as we need to differentiate unexpected
|
|
* content type and no content type case.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* pmu [in] pointer to the IMethUtil object
|
|
* pszType [in] the content type expected
|
|
* pszTypeAnother [in] optional, another valid content type
|
|
*
|
|
* Returns:
|
|
*
|
|
* S_OK - if the content type existed, and was one tat we expected
|
|
* E_DAV_MISSING_CONTENT_TYPE - if the request did not have the content
|
|
* type header
|
|
* E_DAV_UNKNOWN_CONTENT - content type existed but did not match expectation
|
|
* E_OUTOFMEMORY - if memory allocation failure occurs
|
|
*/
|
|
SCODE
|
|
ScIsContentType (IMethUtil * pmu, LPCWSTR pwszType, LPCWSTR pwszTypeAnother)
|
|
{
|
|
SCODE sc = S_OK;
|
|
const WCHAR wchDelimitSet[] = { L';', L'\t', L' ', L'\0' };
|
|
LPCWSTR pwszCntType = NULL;
|
|
CStackBuffer<WCHAR> pwszTemp;
|
|
UINT cchTemp;
|
|
|
|
// Make sure none is passing in null
|
|
//
|
|
Assert(pmu);
|
|
Assert(pwszType);
|
|
|
|
// Get content type. Do not apply URL conversion rules to this header.
|
|
//
|
|
pwszCntType = pmu->LpwszGetRequestHeader (gc_szContent_Type, FALSE);
|
|
|
|
// Error out if the content type does not exist
|
|
//
|
|
if (!pwszCntType)
|
|
{
|
|
sc = E_DAV_MISSING_CONTENT_TYPE;
|
|
DebugTrace("ScIsContentType() - Content type header is missing 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
|
|
// Find out the single content type in the header
|
|
//
|
|
cchTemp = static_cast<UINT>(wcscspn(pwszCntType, wchDelimitSet));
|
|
|
|
// At least we will find zero terminator. And if that is zero terminator then
|
|
// the entire string is the content type. Otherwise we copy it and zero terminate.
|
|
//
|
|
if (L'\0' != pwszCntType[cchTemp])
|
|
{
|
|
if (!pwszTemp.resize(CbSizeWsz(cchTemp)))
|
|
{
|
|
sc = E_OUTOFMEMORY;
|
|
DebugTrace("ScIsContentType() - Failed to allocate memory 0x%08lX\n", sc);
|
|
goto ret;
|
|
}
|
|
|
|
memcpy(pwszTemp.get(), pwszCntType, cchTemp * sizeof(WCHAR));
|
|
pwszTemp[cchTemp] = L'\0';
|
|
pwszCntType = pwszTemp.get();
|
|
}
|
|
|
|
// Now pwszCntType points to the string consisting just of null terminated content type.
|
|
// Check if it is requested content type.
|
|
//
|
|
if (!_wcsicmp(pwszCntType, pwszType))
|
|
goto ret;
|
|
|
|
if (pwszTypeAnother)
|
|
{
|
|
if (!_wcsicmp(pwszCntType, pwszTypeAnother))
|
|
goto ret;
|
|
}
|
|
sc = E_DAV_UNKNOWN_CONTENT;
|
|
|
|
ret:
|
|
|
|
return sc;
|
|
}
|