/*++

   Copyright    (c)    1996    Microsoft Corporation

   Module  Name :

        mimemap.cxx

   Abstract:

        This module defines the member functions for MIME_MAP class
            and MIME_MAP_ENTRY class

   Author:

           Murali R. Krishnan    ( MuraliK )     10-Jan-1995

   Functions Exported:

        MIME_MAP_ENTRY::MIME_MAP_ENTRY()

        MIME_MAP::MIME_MAP()
        MIME_MAP::~MIME_MAP()
        MIME_MAP::CleanupThis()
        MIME_MAP::InitFromRegistry()
        MIME_MAP::LookupMimeEntryForFileExt()
        MIME_MAP::LookupMimeEntryForMimeType()

--*/


/************************************************************
 *     Include Headers
 ************************************************************/

# include <tchar.h>
# include <tcpdllp.hxx>

# include "mimemap.hxx"
# include "hashtab.hxx"

# include "iistypes.hxx"
# include <imd.h>
# include <mb.hxx>
#if 1 // DBCS
# include <mbstring.h>
#endif


//
// Hard coded defaults for MimeEntries.
//

static const TCHAR  sg_rgchDefaultFileExt[] =  TEXT( "*");

static const TCHAR  sg_rgchDefaultMimeType[] =  TEXT("application/octet-stream");
static TCHAR  sg_rgchDefaultMimeEntry[] =
                        TEXT( "*,application/octet-stream");

/************************************************************
 *    Functions
 ************************************************************/

static LPTSTR
MMNextField( IN OUT LPTSTR *  ppchFields);

static BOOL
ReadMimeMapFromMetabase( MULTISZ *pmszMimeMap );


/************************************************************
 *    MIME_MAP_ENTRY member functions
 ************************************************************/

MIME_MAP_ENTRY::MIME_MAP_ENTRY(
    IN LPCTSTR pchMimeType,
    IN LPCTSTR pchFileExt)
: m_strFileExt      ( pchFileExt),
  m_strMimeType     ( pchMimeType),
  HT_ELEMENT        (),
  m_nRefs           ( 1)
/*++
    This function constructs a new MIME_MAP_ENTRY object.
    After initializing  various fields, it also sets the m_fValid flag.
    The user needs to check MIME_MAP_ENTRY::IsValid() for the newly
     constructed object.
--*/
{
    m_fValid = ( m_strFileExt.IsValid()  &&
                 m_strMimeType.IsValid());

} // MIME_MAP_ENTRY::MIME_MAP_ENTRY()



# if DBG

VOID
MIME_MAP_ENTRY::Print( VOID) const
{

    DBGPRINTF( ( DBG_CONTEXT,
                 "MIME_MAP_ENTRY( %08x)\tRefs=%d\tFileExt=%s\tMimeType=%s\t",
                 this,
                 m_nRefs,
                 m_strFileExt.QueryStr(),
                 m_strMimeType.QueryStr()
                 ));

    return;
} // MIME_MAP_ENTRY::Print()

# endif // DBG



/************************************************************
 *    MIME_MAP member functions
 ************************************************************/

# define NUM_MIME_BUCKETS      (7)

MIME_MAP::MIME_MAP( VOID)
/*++
    This function constructs a new MIME_MAP container object for
     containing the MIME_MAP_ENTRY objects.

    The MIME_MAP object is dummy constructed.
    It is set valid when we initialize the elements and
      create the MmeDefault entry.
--*/
:   m_fValid        ( FALSE),
    m_pMmeDefault   ( NULL),
    m_htMimeEntries ( NUM_MIME_BUCKETS, "MimeMapper", 0)
{
} // MIME_MAP::MIME_MAP()



VOID
MIME_MAP::CleanupThis( VOID)
/*++
    This function cleans up the MIME_MAP object, freeing all
     dynamically allocated space and reinitiallizing the list head.

    Returns:
        None
--*/
{

    if ( m_fValid) {

        // The mime entries in the hash table are cleaned when the hash
        // object is deleted.
        m_htMimeEntries.Cleanup();
        m_pMmeDefault = NULL;
        m_fValid = FALSE;
    }

    return;
} // MIME_MAP::CleanupThis()



static LPTSTR
MMNextField( IN OUT LPTSTR *  ppchFields)
/*++
    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 pchComma;
    LPTSTR pchField = NULL;

    DBG_ASSERT( ppchFields != NULL);

    //
    // Look for a comma in the input.
    // If none present, assume that rest of string
    //  consists of the next field.
    //

    pchField  = *ppchFields;

    if ( ( pchComma = _tcschr( *ppchFields, TEXT(','))) != NULL) {

        //
        // Terminate current field. Store current field name in pchComma and
        //  update *ppchFields to contain the next field.
        //
        *pchComma = TEXT( '\0');    // terminate this field with a NULL.
        *ppchFields = pchComma + 1; // goto next field.

    } else {

        //
        // Assume everything till end of string is the current field.
        //

        *ppchFields = *ppchFields + _tcslen( *ppchFields) + 1;
    }

    pchField = ( *pchField == TEXT( '\0')) ? NULL : pchField;
    return ( pchField);
} // MMNextField()



static PMIME_MAP_ENTRY
ReadAndParseMimeMapEntry( IN OUT LPTSTR * 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 string containing the MimeEntry values.

    Returns:
        On successful MIME_ENTRY being parsed, a new MIME_MAP_ENTRY object.
        On error returns NULL.
--*/
{
    PMIME_MAP_ENTRY  pMmeNew = NULL;
    DBG_ASSERT( ppszValues != NULL);
    LPTSTR pszMimeEntry = *ppszValues;


    IF_DEBUG( MIME_MAP) {

        DBGPRINTF( ( DBG_CONTEXT, "ReadAndParseMimeMapEntry( %s)\n",
                     *ppszValues));
    }

    if ( pszMimeEntry != NULL && *pszMimeEntry != TEXT( '\0')) {

        LPTSTR pchMimeType;
        LPTSTR pchFileExt;

        pchFileExt      = MMNextField( ppszValues);
        pchMimeType     = MMNextField( ppszValues);

        if ( pchMimeType  == NULL  ||
             pchFileExt   == NULL
            )  {

            DBGPRINTF( ( DBG_CONTEXT,
                        " ReadAndParseMimeEntry()."
                        " Invalid Mime String ( %s)."
                        "MimeType( %08x): %s, FileExt( %08x): %s,",
                        pszMimeEntry,
                        pchMimeType, pchMimeType,
                        pchFileExt,  pchFileExt
                        ));

            DBG_ASSERT( pMmeNew == NULL);

        } else {


            // Strip leading dot.

            if (*pchFileExt == '.')
            {
                pchFileExt++;
            }

            pMmeNew = new MIME_MAP_ENTRY( pchMimeType, pchFileExt);

            if ( pMmeNew != NULL && !pMmeNew->IsValid()) {

                //
                // unable to create a new MIME_MAP_ENTRY object. Delete it.
                //
                delete pMmeNew;
                pMmeNew = NULL;
            }
        }
    }

    return ( pMmeNew);
} // ReadAndParseMimeMapEntry()


DWORD
MIME_MAP::InitMimeMap( VOID )
/*++
  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:

  Returns:
     NULL on failure to open/read metabase entries
     non-NULL string allocated using TCP_ALLOC containing double null
      terminated sequence of strings with MimeMapEntries.
     If non-NULL the pointer should be freed using TCP_FREE by caller.
--*/

{
    DWORD   dwError  = NO_ERROR;
    DWORD   dwErrorChicago  = NO_ERROR;


    if ( IsValid()) {

        //
        //  There is some mime mapping already present. Cleanup first
        //

        CleanupThis();
    }

    DBG_ASSERT( !IsValid());

    // First read INETSERVICES MIME database ( common types will have priority)
    dwError = InitFromMetabase( );

    if (dwError == NO_ERROR ) {
        m_fValid = TRUE;
    }

    //  Now read Chicago shell registration database
    dwErrorChicago = InitFromRegistryChicagoStyle( );

    // If at least one succeeded - return success
    if (dwErrorChicago == NO_ERROR ||
        dwError == NO_ERROR ) {
        m_fValid = TRUE;
        return NO_ERROR;
    }

    return dwError;
}


static VOID
GetFileExtension( IN CONST TCHAR *  pchPathName,
                  OUT LPCTSTR *     ppstrExt,
                  OUT LPCTSTR *     ppstrLastSlash)
{
    LPCTSTR   pchExt  = sg_rgchDefaultFileExt;

    DBG_ASSERT( ppstrExt != NULL && ppstrLastSlash != NULL );

    *ppstrLastSlash = NULL;

    if ( pchPathName ) {

        LPCTSTR   pchLastDot;

        pchLastDot = _tcsrchr( pchPathName, TEXT( '.'));

        if ( pchLastDot != NULL) {

            LPCTSTR   pchLastWhack;

#if 1 // DBCS enabling for document path and file name
            pchLastWhack = (PCHAR)_mbsrchr( (PUCHAR)pchPathName, TEXT( '\\'));
#else
            pchLastWhack = _tcsrchr( pchPathName, TEXT( '\\'));
#endif

            if ( pchLastWhack == NULL) {

                pchLastWhack = pchPathName;  // only file name specified.
            }

            if ( pchLastDot >= pchLastWhack) {
                // if the dot comes only in the last component, then get ext
                pchExt = pchLastDot + 1;  // +1 to skip last dot.
                *ppstrLastSlash = pchLastWhack;
            }
        }

    }
    *ppstrExt = pchExt;
} // GetFileExtension()




DWORD
MIME_MAP::LookupMimeEntryForMimeType(
    IN const STR &                 strMimeType,
    OUT PCMIME_MAP_ENTRY  *        prgMme,
    IN OUT LPDWORD                 pnMmeEntries)
/*++
    This function maps MimeType to an array of MimeMapEntry objects that match
        the given MimeType.

    Before calling this function,
       ensure that you had already locked this object.
    After completing use of the array, unlock the MIME_MAP.
    The reason is:
       To avoid changes in the data while using the read only members of
            MIME_MAP.

    Arguments:
        strMimeType         string containing the MimeType used in search
        prgpMme             pointer to an array of pointers to Mme.
                            The array is initialized to contain the
                              read only pointers to the MIME_MAP_ENTRY objects.
                            If prgpMme is NULL, then
                              number of matches is counted and returned.
        pnMmeEntries        pointer to count of entries in the array
                             ( when called).
                            On successful return contains total numb of entries
                             present in the array or count of entries required.

    Returns:

        NO_ERROR on success.
        ERROR_INSUFFICIENT_BUFFER  if the prgMme does not have enough space for
                copying all the read-only pointers to matched entries.
        other Win32 errors if any.
--*/
{
    DWORD nMaxMme   = 0;
    DWORD iMmeFound = 0;   // index into array for MmeFound
    HT_ITERATOR   hti;
    HT_ELEMENT * phte;

    DBG_ASSERT( IsValid());

    if ( pnMmeEntries != NULL) {

        nMaxMme = *pnMmeEntries;   // max that we can store.
        *pnMmeEntries = 0;         // number found. set to default value
    }

    if ( strMimeType.IsEmpty() || nMaxMme == 0) {

        SetLastError( ERROR_INVALID_PARAMETER);
        return ( ERROR_INVALID_PARAMETER);
    }

    DWORD dwErr;
    dwErr = m_htMimeEntries.InitializeIterator( &hti);

    if ( NO_ERROR == dwErr) {
        DWORD iMmeFound = 0;

        while ( (dwErr =  m_htMimeEntries.FindNextElement( &hti, &phte))
                == NO_ERROR) {


            PMIME_MAP_ENTRY pMme = (PMIME_MAP_ENTRY ) phte;
            DBG_ASSERT( pMme!= NULL);

            if ( !_tcsicmp( pMme->QueryMimeType(),
                            strMimeType.QueryStr())) {

                //
                // We found the matching Mme. Add it to array of found.
                //

                if ( prgMme != NULL && iMmeFound < nMaxMme) {

                    // store the pointer to found match

                    prgMme[iMmeFound] = pMme;
                }

                iMmeFound++;
            } // found a match

            //
            // release the element foind before fetching the next one
            //
            phte->Dereference();
        } // while
    }

    DBG_REQUIRE( NO_ERROR == m_htMimeEntries.CloseIterator( &hti));

    dwErr = ( iMmeFound > nMaxMme) ? ERROR_INSUFFICIENT_BUFFER : NO_ERROR;

    *pnMmeEntries = iMmeFound;

    return ( dwErr);
} // MIME_MAP::LookupMimeEntryForMimeType()



PCMIME_MAP_ENTRY
MIME_MAP::LookupMimeEntryForFileExt(
    IN const TCHAR *     pchPathName)
/*++
    This function mapes 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:
        pchPathName     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.

--*/
{
    PMIME_MAP_ENTRY pMmeMatch = m_pMmeDefault;

    DBG_ASSERT( IsValid());

    if ( pchPathName != NULL && *pchPathName ) {

        LPCTSTR         pchExt;
        LPCTSTR         pchLastSlash;

        GetFileExtension( pchPathName, &pchExt, &pchLastSlash );
        DBG_ASSERT( pchExt);
        DWORD cchExt = strlen( pchExt);

        for ( ;; )
        {
            //
            // Successfully got extension. Search in the list of MimeEntries.
            //

            pMmeMatch = (PMIME_MAP_ENTRY ) m_htMimeEntries.Lookup( pchExt, cchExt);

            pchExt--;

            if ( NULL == pMmeMatch)
            {
                pMmeMatch = m_pMmeDefault;

                // Look backwards for another '.' so we can support extensions
                // like ".xyz.xyz" or ".a.b.c".

                if ( pchExt > pchLastSlash )
                {
                    pchExt--;
                    while ( ( pchExt > pchLastSlash ) && ( *pchExt != '.' ) )
                    {
                        pchExt--;
                    }

                    if ( *(pchExt++) != '.' )
                    {
                        break;
                    }
                }
                else
                {
                    break;
                }
            }
            else
            {
                // mime map table is special - we do not handle ref counts
                // at all outside the mime-map object. Neither is there
                // deletion till the program ends. Just deref it here.

                DBG_REQUIRE( pMmeMatch->Dereference() > 0);
                break;
            }
        }
    }

    return ( pMmeMatch);
} // MIME_MAP::LookupMimeEntryForFileExt()



BOOL
MIME_MAP::AddMimeMapEntry( IN PMIME_MAP_ENTRY  pMmeNew)
/*++

    This function adds the new MIME_MAP_ENTRY to the list of entries
     maintained in MIME_MAP

    Arguments:
        pMmeNew      poitner to newly created MimeMapEntry object.

    Returns:
        Win32 error codes. NO_ERROR on success.
--*/
{
    BOOL fReturn = FALSE;

    if ( pMmeNew == NULL || !pMmeNew->IsValid()) {

        SetLastError( ERROR_INVALID_PARAMETER);
        DBG_ASSERT( !fReturn);
    } else {

        DBG_ASSERT( m_htMimeEntries.IsValid());

        fReturn = m_htMimeEntries.Insert( (HT_ELEMENT * ) pMmeNew);

        if ( !_tcscmp( pMmeNew->QueryFileExt(), sg_rgchDefaultFileExt)) {

            m_pMmeDefault = pMmeNew;    // Use this as default
        }
    }

    return ( fReturn);
} // MIME_MAP::AddMimeMapEntry()



# if DBG

VOID
MIME_MAP::Print( VOID)
{
    DBGPRINTF( ( DBG_CONTEXT,
                "MIME_MAP ( %08x). \tIsValid() = %d\n",
                this,  IsValid())
              );


#if 0
    HT_ITERATOR hti;
    HT_ELEMENT * phte;

    DWORD dwErr;

    dwErr = m_htMimeEntries.InitializeIterator( &hti);

    if ( NO_ERROR == dwErr) {

        while ( (dwErr = m_htMimeEntries.FindNextElement( &hti, &phte))
                == NO_ERROR) {

            DBG_ASSERT( NULL != phte);
            phte->Print();
        } // while()
    }

    DBG_REQUIRE( NO_ERROR == m_htMimeEntries.CloseIterator( &hti));
# endif // 0

    m_htMimeEntries.Print( 1);

    if ( m_pMmeDefault != NULL) {

        DBGPRINTF( ( DBG_CONTEXT, "Default MimeMapEntry is: \n"));
        m_pMmeDefault->Print();
    } else {

        DBGPRINTF( ( DBG_CONTEXT, "Default MimeMapEntry is NULL\n"));
    }

    return;

} // MIME_MAP::Print()


# endif // DBG




static BOOL
ReadMimeMapFromMetabase( MULTISZ *pmszMimeMap )
/*++
  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:
     pszRegKey   pointer to NULL terminated string containing  registry entry.

  Returns:
     NULL on failure to open/read registry entries
     non-NULL string allocated using TCP_ALLOC containing double null
      terminated sequence of strings with MimeMapEntries.
     If non-NULL the pointer should be freed using TCP_FREE by caller.
--*/
{
    MB      mb( (IMDCOM*) IIS_SERVICE::QueryMDObject()  );

    if ( !mb.Open( "/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("", MD_MIME_MAP, IIS_MD_UT_FILE, pmszMimeMap))
    {
        DBGPRINTF((DBG_CONTEXT,"Unable to read mime map from metabase: %d\n",GetLastError() ));
        return FALSE;
    }

    return TRUE;

} // ReadMimeMapFromMetabase()


DWORD
MIME_MAP::InitFromMetabase( VOID )
/*++
    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.

    Arguments:

    Returns:
        Win32 error code. NO_ERROR on success.

    Format of Storage in registry:
        The entries are stored in NT in tbe metabase
          with a list of values in following format.
            file-extension,i mimetype
        It can be stored using MULTI_SZ, but above form is convenient for both
          Windows 95 ( withoug MULTI_SZ) and WindowsNT.

--*/
{
    DWORD   dwError  = NO_ERROR;


    LPTSTR  pszValueAlloc = NULL;    // to be free using TCP_FREE()
    LPTSTR  pszValue;
    MULTISZ mszMimeMap;

    //
    //  There is some registry key for Mime Entries. Try open and read.
    //

    if ( !ReadMimeMapFromMetabase( &mszMimeMap ) )
    {
        mszMimeMap.Reset();

        if (!mszMimeMap.Append(sg_rgchDefaultMimeEntry))
        {
            return GetLastError();
        }
    }

    // Ignore all errors.
    dwError  = NO_ERROR;

    pszValue = (LPTSTR)mszMimeMap.QueryPtr();

    //
    // Parse each MimeEntry in the string containing list of mime objects.
    //

    for( ; m_pMmeDefault == NULL;              // repeat until default is set
        pszValue = sg_rgchDefaultMimeEntry  // force default mapping in iter 2.
        ) {

        while ( *pszValue != TEXT( '\0')) {

            PMIME_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;
                    //break;
            }
        } // while
    } // for


    return ( dwError);

} // MIME_MAP::InitFromRegistryNtStyle




DWORD
MIME_MAP::InitFromRegistryChicagoStyle( VOID )
/*++
  This function reads the list of MIME content-types available for regsitered file
  extensions. Global list of MIME objects is updated with not added yet 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:

     FALSE on failure to open/read registry entries
     TRUE  on success ( does not mean any objects were added)

--*/
{
    HKEY    hkeyMimeMap = NULL;
    HKEY    hkeyMimeType = NULL;
    HKEY    hkeyExtension = NULL;

    DWORD   dwError = ERROR_SUCCESS;
    DWORD   dwErrorChild = ERROR_SUCCESS;
    DWORD   dwIndexSubKey;
    DWORD   dwMimeSizeAllowed ;
    DWORD   dwType;
    DWORD   cbValue;

    LPTSTR  pszMimeMap = NULL;

    TCHAR   szSubKeyName[MAX_PATH];
    TCHAR   szExtension[MAX_PATH];

    PTSTR   pszMimeType;

    //
    // Read content types from all registered extensions
    //
    dwError = RegOpenKeyEx(HKEY_CLASSES_ROOT,       // hkey
                           "",                      // 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));

    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 (!::IsDBCSLeadByte(*szExtension) &&
            TEXT('.') == *szExtension) {

            //
            // Got next eligible extension
            //
            dwErrorChild = RegOpenKeyEx( HKEY_CLASSES_ROOT, // hkey
                                         szExtension,       // reg entry string
                                         0,                 // dwReserved
                                         KEY_READ,          // access
                                         &hkeyExtension);   // pHkeyReturned.

            if ( dwErrorChild != NO_ERROR) {

                DBGPRINTF( ( DBG_CONTEXT,
                             "MIME_MAP::InitFromRegistry(). "
                             " Cannot open RegKey HKEY_CLASSES_ROOT\\%s."
                             "Ignoring Error = %d\n",
                             szExtension,
                             dwErrorChild));
                break;
            }

            //
            // Now get content type for this extension if present
            //
            *szSubKeyName = '\0';
            cbValue = sizeof(szSubKeyName);

            dwErrorChild = RegQueryValueEx(hkeyExtension,
                                         "Content Type",
                                         NULL,
                                         &dwType,
                                         (LPBYTE)&szSubKeyName[0],
                                         &cbValue);
            if ( dwErrorChild == 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));

    } // 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(sg_rgchDefaultMimeType,
                              sg_rgchDefaultFileExt)) {

        dwError = GetLastError();

        DBGPRINTF( ( DBG_CONTEXT,
                     "MIME_MAP::InitFromRegistry()."
                     "Failed to add new MIME Entry. Error = %d\n",
                     dwError) );
    }

    return ( NO_ERROR);

} // InitFromRegistryChicagoStyle



BOOL
MIME_MAP::CreateAndAddMimeMapEntry(
    IN  LPCTSTR     pszMimeType,
    IN  LPCTSTR     pszExtension
    )
{
    DWORD                   dwError;
    PCMIME_MAP_ENTRY        pEntry = NULL;

    //
    // First check if this extension is not yet present
    //

    pEntry = LookupMimeEntryForFileExt( pszExtension );
    if ( pEntry )
    {
        if ( !_tcscmp( pszExtension, sg_rgchDefaultFileExt ) ||
             ( pEntry != m_pMmeDefault ) )
        {
            IF_DEBUG(MIME_MAP) {
                DBGPRINTF( ( DBG_CONTEXT,
                         "MIME_MAP::CreateAndAddMimeEntry."
                         " New MIME Entry already exists for extension %s .\n",
                        pszExtension)
                       );
            }
            return TRUE;
        }
    }

    //
    // File extensions, stored by OLE/shell registration UI have leading
    // dot, we need to remove it , as other code won't like it.
    //
    if (!::IsDBCSLeadByte(*pszExtension) &&
        TEXT('.') == *pszExtension) {
        pszExtension = ::CharNext(pszExtension);
    }

    PMIME_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;

} // MIME_MAP::CreateAndAddMimeMapEntry