windows-nt/Source/XPSP1/NT/inetsrv/iis/iisrearc/iisplus/ulw3/mimemap.cxx
2020-09-26 16:20:57 +08:00

782 lines
19 KiB
C++

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name :
mimemap.cxx
Abstract:
Store and retrieve mime-mapping for file types
Author:
Murali R. Krishnan (MuraliK) 10-Jan-1995
Environment:
Win32 - User Mode
Project:
UlW3.dll
History:
Anil Ruia (AnilR) 27-Mar-2000 Ported to IIS+
--*/
#include "precomp.hxx"
#define g_pszDefaultFileExt L"*"
#define g_pszDefaultMimeType L"application/octet-stream"
LPWSTR g_pszDefaultMimeEntry = L"*,application/octet-stream";
MIME_MAP *g_pMimeMap = NULL;
HRESULT MIME_MAP::InitMimeMap()
/*++
Synopsis
This function reads the mimemap stored either as a MULTI_SZ or as a
sequence of REG_SZ
Returns:
HRESULT
--*/
{
DBG_ASSERT(!IsValid());
// First read metabase (common types will have priority)
HRESULT hr = InitFromMetabase();
if (SUCCEEDED(hr))
{
m_fValid = TRUE;
}
// Now read Chicago shell registration database
hr = InitFromRegistryChicagoStyle();
if (SUCCEEDED(hr))
{
m_fValid = TRUE;
}
//
// If at least one succeeded, we are ok
//
if (IsValid())
return S_OK;
return hr;
}
static VOID GetFileExtension(IN LPWSTR pszPathName,
OUT LPWSTR *ppszExt,
OUT LPWSTR *ppszLastSlash)
/*++
Synopsis
Gets The extension portion from a filename.
Arguments
pszPathName: The full path name (containing forward '/' or '\\' 's)
ppszExt: Points to start of extension on return
ppszLastSlash: Points to the last slash in the path name on return
Return Value
None
--*/
{
LPWSTR pszExt = g_pszDefaultFileExt;
DBG_ASSERT(ppszExt != NULL && ppszLastSlash != NULL);
*ppszLastSlash = NULL;
if (pszPathName)
{
LPWSTR pszLastDot;
pszLastDot = wcsrchr(pszPathName, L'.');
if (pszLastDot != NULL)
{
LPWSTR pszLastWhack = NULL;
LPWSTR pszEnd;
pszEnd = pszPathName + wcslen( pszPathName );
while ( pszEnd >= pszPathName )
{
if ( *pszEnd == L'/' || *pszEnd == L'\\' )
{
pszLastWhack = pszEnd;
break;
}
pszEnd--;
}
if (pszLastWhack == NULL)
{
pszLastWhack = pszPathName; // only file name specified.
}
if (pszLastDot >= pszLastWhack)
{
// if the dot comes only in the last component, then get ext
pszExt = pszLastDot + 1; // +1 to skip last dot.
*ppszLastSlash = pszLastWhack;
}
}
}
*ppszExt = pszExt;
}
const MIME_MAP_ENTRY *MIME_MAP::LookupMimeEntryForFileExt(
IN LPWSTR pszPathName)
/*++
Synopsis
This function maps FileExtension to MimeEntry.
The function returns a single mime entry for given file's extension.
If no match is found, the default mime entry is returned.
The returned entry is a readonly pointer and should not be altered.
The file extension is the key field in the Hash table for mime entries.
We can use the hash table lookup function to find the entry.
Arguments:
pszPathName
pointer to string containing the path for file. (either full path or
just the file name). If NULL, then the default MimeMapEntry is returned.
Returns:
If a matching mime entry is found, a const pointer to MimeMapEntry
object is returned. Otherwise the default mime map entry object is
returned.
--*/
{
MIME_MAP_ENTRY *pMmeMatch = m_pMmeDefault;
DBG_ASSERT( IsValid());
if (pszPathName != NULL && *pszPathName)
{
LPWSTR pszExt;
LPWSTR pszLastSlash;
GetFileExtension(pszPathName, &pszExt, &pszLastSlash );
DBG_ASSERT(pszExt);
if (!wcscmp(pszExt, g_pszDefaultFileExt))
{
return m_pMmeDefault;
}
for (;;)
{
//
// Successfully got extension. Search in the list of MimeEntries.
//
FindKey(pszExt, &pMmeMatch);
pszExt--;
if ( NULL == pMmeMatch)
{
pMmeMatch = m_pMmeDefault;
// Look backwards for another '.' so we can support extensions
// like ".xyz.xyz" or ".a.b.c".
if (pszExt > pszLastSlash)
{
pszExt--;
while ((pszExt > pszLastSlash) && (*pszExt != L'.'))
{
pszExt--;
}
if (*(pszExt++) != L'.')
{
break;
}
}
else
{
break;
}
}
else
{
break;
}
}
}
return pMmeMatch;
}
BOOL MIME_MAP::AddMimeMapEntry(IN MIME_MAP_ENTRY *pMmeNew)
{
if (!wcscmp(pMmeNew->QueryFileExt(), g_pszDefaultFileExt))
{
m_pMmeDefault = pMmeNew;
return TRUE;
}
if (InsertRecord(pMmeNew) == LK_SUCCESS)
{
return TRUE;
}
return FALSE;
}
BOOL MIME_MAP::CreateAndAddMimeMapEntry(
IN LPWSTR pszMimeType,
IN LPWSTR pszExtension)
{
DWORD dwError;
MIME_MAP_ENTRY *pEntry = NULL;
//
// File extensions, stored by OLE/shell registration UI have leading
// dot, we need to remove it , as other code won't like it.
//
if (pszExtension[0] == L'.')
{
pszExtension++;
}
//
// First check if this extension is not yet present
//
FindKey(pszExtension, &pEntry);
if (pEntry)
{
return TRUE;
}
MIME_MAP_ENTRY *pMmeNew;
pMmeNew = new MIME_MAP_ENTRY(pszMimeType, pszExtension);
if (!pMmeNew || !pMmeNew->IsValid())
{
//
// unable to create a new MIME_MAP_ENTRY object.
//
if (pMmeNew)
{
delete pMmeNew;
}
return FALSE;
}
if (!AddMimeMapEntry(pMmeNew))
{
dwError = GetLastError();
DBGPRINTF( ( DBG_CONTEXT,
"MIME_MAP::InitFromRegistry()."
" Failed to add new MIME Entry. Error = %d\n",
dwError)
);
delete pMmeNew;
return FALSE;
}
return TRUE;
}
static BOOL ReadMimeMapFromMetabase(OUT MULTISZ *pmszMimeMap)
/*++
Synopsis
This function reads the mimemap stored either as a MULTI_SZ or as a
sequence of REG_SZ and returns a double null terminated sequence of mime
types on success. If there is any failure, the failures are ignored and
it returns a NULL.
Arguments:
pmszMimeMap: MULTISZ which will contain the MimeMap on success
Returns:
BOOL
--*/
{
MB mb(g_pW3Server->QueryMDObject());
if (!mb.Open(L"/LM/MimeMap", METADATA_PERMISSION_READ))
{
//
// if this fails, we're hosed.
//
DBGPRINTF((DBG_CONTEXT,"Open MD /LM/MimeMap returns %d\n", GetLastError()));
return FALSE;
}
if (!mb.GetMultisz(L"", MD_MIME_MAP, IIS_MD_UT_FILE, pmszMimeMap))
{
DBGPRINTF((DBG_CONTEXT,"Unable to read mime map from metabase: %d\n",GetLastError() ));
mb.Close();
return FALSE;
}
mb.Close();
return TRUE;
}
static LPWSTR MMNextField(IN OUT LPWSTR *ppszFields)
/*++
This function separates and terminates the next field and returns a
pointer to the same.
Also it updates the incoming pointer to point to start of next field.
The fields are assumed to be separated by commas.
--*/
{
LPTSTR pszComma;
LPTSTR pszField = NULL;
DBG_ASSERT( ppszFields != NULL);
//
// Look for a comma in the input.
// If none present, assume that rest of string
// consists of the next field.
//
pszField = *ppszFields;
if ((pszComma = wcschr(*ppszFields, L',')) != NULL)
{
//
// update *ppszFields to contain the next field.
//
*ppszFields = pszComma + 1; // goto next field.
*pszComma = L'\0';
}
else
{
//
// Assume everything till end of string is the current field.
//
*ppszFields = *ppszFields + wcslen(*ppszFields) + 1;
}
pszField = ( *pszField == L'\0') ? NULL : pszField;
return pszField;
}
static MIME_MAP_ENTRY *
ReadAndParseMimeMapEntry(IN OUT LPWSTR *ppszValues)
/*++
This function parses the string containing next mime map entry and
related fields and if successful creates a new MIME_MAP_ENTRY
object and returns it.
Otherwise it returns NULL.
In either case, the incoming pointer is updated to point to next entry
in the string ( past terminating NULL), assuming incoming pointer is a
multi-string ( double null terminated).
Arguments:
ppszValues: pointer to MULTISZ containing the MimeEntry values.
Returns:
On successful MIME_ENTRY being parsed, a new MIME_MAP_ENTRY object.
On error returns NULL.
--*/
{
MIME_MAP_ENTRY *pMmeNew = NULL;
DBG_ASSERT( ppszValues != NULL);
LPWSTR pszMimeEntry = *ppszValues;
if ( pszMimeEntry != NULL && *pszMimeEntry != L'\0')
{
LPWSTR pszMimeType;
LPWSTR pszFileExt;
pszFileExt = MMNextField(ppszValues);
pszMimeType = MMNextField(ppszValues);
if ((pszMimeType == NULL) ||
(pszFileExt == NULL))
{
DBGPRINTF( ( DBG_CONTEXT,
" ReadAndParseMimeEntry()."
" Invalid Mime String ( %S)."
"MimeType( %08x): %S, FileExt( %08x): %S\n",
pszMimeEntry,
pszMimeType, pszMimeType,
pszFileExt, pszFileExt
));
DBG_ASSERT( pMmeNew == NULL);
}
else
{
// Strip leading dot.
if (*pszFileExt == '.')
{
pszFileExt++;
}
pMmeNew = new MIME_MAP_ENTRY( pszMimeType, pszFileExt);
if ( pMmeNew != NULL && !pMmeNew->IsValid())
{
//
// unable to create a new MIME_MAP_ENTRY object. Delete it.
//
delete pMmeNew;
pMmeNew = NULL;
}
}
}
return pMmeNew;
}
MIME_MAP::MIME_MAP(LPWSTR pszMimeMappings)
: CTypedHashTable<MIME_MAP, MIME_MAP_ENTRY, LPWSTR>("MimeMapper"),
m_fValid (TRUE),
m_pMmeDefault (NULL)
{
while (*pszMimeMappings != L'\0')
{
MIME_MAP_ENTRY *pMmeNew;
pMmeNew = ReadAndParseMimeMapEntry(&pszMimeMappings);
//
// If New MimeMap entry found, Create a new object and update list
//
if ((pMmeNew != NULL) &&
!AddMimeMapEntry(pMmeNew))
{
DBGPRINTF((DBG_CONTEXT,
"MIME_MAP::InitFromRegistry()."
" Failed to add new MIME Entry. Error = %d\n",
GetLastError()));
delete pMmeNew;
}
} // while
}
HRESULT MIME_MAP::InitFromMetabase()
/*++
Synopsis
This function reads the MIME_MAP entries from metabase and parses
the entry, creates MIME_MAP_ENTRY object and adds the object to list
of MimeMapEntries.
Returns:
HRESULT
Format of Storage in registry:
The entries are stored in NT in tbe metabase with a list of values in
following format: file-extension, mimetype
--*/
{
HRESULT hr = S_OK;
LPTSTR pszValueAlloc = NULL;
LPTSTR pszValue;
MULTISZ mszMimeMap;
//
// There is some registry key for Mime Entries. Try open and read.
//
if (!ReadMimeMapFromMetabase(&mszMimeMap))
{
mszMimeMap.Reset();
if (!mszMimeMap.Append(g_pszDefaultMimeEntry))
{
return HRESULT_FROM_WIN32(GetLastError());
}
}
// Ignore all errors.
hr = S_OK;
pszValue = (LPWSTR)mszMimeMap.QueryPtr();
//
// Parse each MimeEntry in the string containing list of mime objects.
//
for (; m_pMmeDefault == NULL; pszValue = g_pszDefaultMimeEntry)
{
while (*pszValue != L'\0')
{
MIME_MAP_ENTRY *pMmeNew;
pMmeNew = ReadAndParseMimeMapEntry( &pszValue);
//
// If New MimeMap entry found, Create a new object and update list
//
if ((pMmeNew != NULL) &&
!AddMimeMapEntry(pMmeNew))
{
DBGPRINTF((DBG_CONTEXT,
"MIME_MAP::InitFromRegistry()."
" Failed to add new MIME Entry. Error = %d\n",
GetLastError()));
delete pMmeNew;
}
} // while
} // for
return hr;
}
HRESULT MIME_MAP::InitFromRegistryChicagoStyle()
/*++
Synopsis
This function reads the list of MIME content-types available for
registered file extensions. Global list of MIME objects is updated with
not yet added extensions. This method should be invoked after
server-specific map had been read, so it does not overwrite extensions
common for two.
Arguments:
None.
Returns:
HRESULT
--*/
{
HKEY hkeyMimeMap = NULL;
HKEY hkeyMimeType = NULL;
HKEY hkeyExtension = NULL;
DWORD dwIndexSubKey;
DWORD dwMimeSizeAllowed;
DWORD dwType;
DWORD cbValue;
LPWSTR pszMimeMap = NULL;
WCHAR szSubKeyName[MAX_PATH];
WCHAR szExtension[MAX_PATH];
LPWSTR pszMimeType;
//
// Read content types from all registered extensions
//
DWORD dwError = RegOpenKeyEx(HKEY_CLASSES_ROOT, // hkey
L"", // reg entry string
0, // dwReserved
KEY_READ, // access
&hkeyMimeMap); // pHkeyReturned.
if ( dwError != NO_ERROR)
{
DBGPRINTF( ( DBG_CONTEXT,
"MIME_MAP::InitFromRegistry(). Cannot open RegKey %s."
"Error = %d\n",
"HKCR_",
dwError) );
goto AddDefault;
}
dwIndexSubKey = 0;
*szSubKeyName = '\0';
pszMimeType = szSubKeyName ;
dwError = RegEnumKey(hkeyMimeMap,
dwIndexSubKey,
szExtension,
sizeof(szExtension)/sizeof(WCHAR));
while (dwError == ERROR_SUCCESS)
{
//
// Some entries in HKEY_CLASSES_ROOT are extensions ( start with dot)
// and others are file types. We don't need file types here .
//
if (L'.' == *szExtension)
{
//
// Got next eligible extension
//
dwError = RegOpenKeyEx(HKEY_CLASSES_ROOT, // hkey
szExtension, // reg entry string
0, // dwReserved
KEY_READ, // access
&hkeyExtension); // pHkeyReturned.
if (dwError != NO_ERROR)
{
DBGPRINTF( ( DBG_CONTEXT,
"MIME_MAP::InitFromRegistry(). "
" Cannot open RegKey HKEY_CLASSES_ROOT\\%S."
"Ignoring Error = %d\n",
szExtension,
dwError));
break;
}
//
// Now get content type for this extension if present
//
*szSubKeyName = '\0';
cbValue = sizeof szSubKeyName;
dwError = RegQueryValueEx(hkeyExtension,
L"Content Type",
NULL,
&dwType,
(LPBYTE)&szSubKeyName[0],
&cbValue);
if (dwError == NO_ERROR)
{
//
// Now we have MIME type and file extension
// Create a new object and update list
//
if (!CreateAndAddMimeMapEntry(szSubKeyName, szExtension))
{
dwError = GetLastError();
DBGPRINTF((DBG_CONTEXT,
"MIME_MAP::InitFromRegistry()."
" Failed to add new MIME Entry. Error = %d\n",
dwError)) ;
}
}
RegCloseKey(hkeyExtension);
}
//
// Attempt to read next extension
//
dwIndexSubKey++;
dwError = RegEnumKey(hkeyMimeMap,
dwIndexSubKey,
szExtension,
sizeof(szExtension)/sizeof(WCHAR));
} // end_while
dwError = RegCloseKey( hkeyMimeMap);
AddDefault:
//
// Now after we are done with registry mapping - add default MIME type
// in case if NT database does not exist
//
if (!CreateAndAddMimeMapEntry(g_pszDefaultMimeType,
g_pszDefaultFileExt))
{
dwError = GetLastError();
DBGPRINTF( ( DBG_CONTEXT,
"MIME_MAP::InitFromRegistry()."
"Failed to add new MIME Entry. Error = %d\n",
dwError) );
}
return S_OK;
}
HRESULT InitializeMimeMap(IN LPWSTR pszRegEntry)
/*++
Creates a new mime map object and loads the registry entries from
under this entry from \\MimeMap.
--*/
{
HRESULT hr = E_FAIL;
DBG_ASSERT(g_pMimeMap == NULL);
g_pMimeMap = new MIME_MAP();
if (g_pMimeMap == NULL)
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
if (FAILED(hr = g_pMimeMap->InitMimeMap()))
{
DBGPRINTF((DBG_CONTEXT,"InitMimeMap failed with hr %x\n", hr));
}
return hr;
} // InitializeMimeMap()
VOID CleanupMimeMap()
{
if ( g_pMimeMap != NULL)
{
delete g_pMimeMap;
g_pMimeMap = NULL;
}
} // CleanupMimeMap()
HRESULT SelectMimeMappingForFileExt(IN WCHAR *pszFilePath,
IN MIME_MAP *pMimeMap,
OUT STRA *pstrMimeType)
/*++
Synopsis
Obtains the mime type for file based on the file extension.
Arguments
pszFilePath pointer to path for the given file
pstrMimeType pointer to string to store the mime type on return
Returns:
HRESULT
--*/
{
HRESULT hr = S_OK;
DBG_ASSERT (pstrMimeType != NULL);
const MIME_MAP_ENTRY *pMmeMatch = NULL;
//
// Lookup in the metabase entry
//
if (pMimeMap)
{
pMmeMatch = pMimeMap->LookupMimeEntryForFileExt(pszFilePath);
}
//
// If not found, lookup in the global entry
//
if (!pMmeMatch)
{
pMmeMatch = g_pMimeMap->LookupMimeEntryForFileExt(pszFilePath);
}
DBG_ASSERT(pMmeMatch != NULL);
return pstrMimeType->CopyWTruncate(pMmeMatch->QueryMimeType());
}