/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* Author: * Garrett Arch Blythe * blythe@netscape.com */ #include "stdafx.h" #include "extgen.h" /*-----------------------------------------------------------------------** ** Things you do not care about, private internal implemenation details. ** **-----------------------------------------------------------------------*/ size_t ext_Extract(char *pOutExtension, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName); BOOL ext_ExtByIndex(int iIndex, char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName, const char *pMimeType); BOOL ext_PreserveExt(const char *pExt, DWORD dwFlags, const char *pOrigName); BOOL ext_ShellExecutable(const char *pExt, DWORD dwFlags); BOOL ext_PreserveMime(const char *pExt, const char *pMimeType); BOOL ext_ExtByMimeIndex(int iIndex, char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pMimeType); #ifdef XP_WIN32 void ext_MimeDatabase(char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pMimeType); #endif size_t EXT_Invent(char *pOutExtension, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName, const char *pMimeType) { // Ensure we have a buffer to provide consistent functionality if // the buffer is missing. char aBuffer[_MAX_EXT]; if(!pOutExtension) { pOutExtension = aBuffer; stExtBufSize = sizeof(aBuffer); } // You may want to provide a big enough buffer to support extensions // on the native file system. ASSERT(stExtBufSize >= _MAX_EXT); // Limit in parameters. // We only allow the no period flag, and dot three flag, // as we manipulate the other flags herein. ASSERT((dwFlags & (EXT_NO_PERIOD | EXT_DOT_THREE)) == dwFlags); dwFlags &= (EXT_NO_PERIOD | EXT_DOT_THREE); // Initialize out parameters. size_t stReturns = 0; *pOutExtension = '\0'; // We want to preserve the file extension, respect the MIME type, // and want the Shell to know how to handle the file. // This is absolutely the best scenario. if(!stReturns) { DWORD dwPassFlags = dwFlags | EXT_PRESERVE_EXT | EXT_PRESERVE_MIME | EXT_SHELL_EXECUTABLE | EXT_EXECUTABLE_IDENTITY; stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType); } // We want to respect MIME type over file extension, but we also // want the Shell to understand how to use the file. Try to // find any extension under that MIME type which the Shell // will handle and use it. if(!stReturns) { DWORD dwPassFlags = dwFlags | EXT_PRESERVE_MIME | EXT_SHELL_EXECUTABLE | EXT_EXECUTABLE_IDENTITY; stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType); } // We give up on respecting MIME type, and we just want the Shell // to be able to handle the file. Is the current file // extension good enough? if(!stReturns) { DWORD dwPassFlags = dwFlags | EXT_PRESERVE_EXT | EXT_SHELL_EXECUTABLE | EXT_EXECUTABLE_IDENTITY; stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType); } // Extension has no shell association. Would be nice if the extension // were in the MIME list, however. if(!stReturns) { DWORD dwPassFlags = dwFlags | EXT_PRESERVE_EXT | EXT_PRESERVE_MIME; stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType); } // Extension has no shell association. We want to respect // MIME type over filename, as we might want to know the MIME // type some time in the future. See if we have an extension // for the MIME type. if(!stReturns) { DWORD dwPassFlags = dwFlags | EXT_PRESERVE_MIME; stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType); } // Extension has no shell association. We have no extension for the MIME type. // Simply give up and use the extension of the file if present. if(!stReturns) { DWORD dwPassFlags = dwFlags | EXT_PRESERVE_EXT; stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType); } // All done. return(stReturns); } size_t EXT_Generate(char *pOutExtension, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName, const char *pMimeType) { // Ensure we have a buffer to provide consistent functionality if // the buffer is missing. char aBuffer[_MAX_EXT]; if(!pOutExtension) { pOutExtension = aBuffer; stExtBufSize = sizeof(aBuffer); } // You may want to provide a big enough buffer to support extensions // on the native file system. ASSERT(stExtBufSize >= _MAX_EXT); // Initialize out parameters. *pOutExtension = '\0'; // Get the period business out of the way first. char *pExt = pOutExtension; if(!(dwFlags & EXT_NO_PERIOD) && (stExtBufSize > 1)) { // We use strcat so that the string is NULL terminated. strcat(pExt, "."); pExt++; } // We do not want to change the extension for a mime type of // application/octet-stream or applciation/x-msdownload. // In some cases, these files are binary and we used to miss // name them with .EXE extensions. if(pMimeType) { BOOL bPreserveExtension = FALSE; if(0 == stricmp(APPLICATION_OCTET_STREAM, pMimeType)) { bPreserveExtension = TRUE; } else if(0 == stricmp("application/x-msdownload", pMimeType)) { bPreserveExtension = TRUE; } if(bPreserveExtension) { dwFlags |= EXT_PRESERVE_EXT; } } // This loop traverses all extensions possible, and will verify // that the extension matches all flags before continuing. BOOL bSuccess = FALSE; for(int iIndex = 0; ext_ExtByIndex(iIndex, pExt, stExtBufSize - (pExt - pOutExtension), dwFlags, pOrigName, pMimeType); iIndex++) { // Do not check empty extensions. if(!*pExt) { continue; } // See if the extension holds up to the flags. if((dwFlags & EXT_PRESERVE_EXT) && !ext_PreserveExt(pExt, dwFlags, pOrigName)) { continue; } if((dwFlags & EXT_SHELL_EXECUTABLE) && !ext_ShellExecutable(pExt, dwFlags)) { continue; } if((dwFlags & EXT_PRESERVE_MIME) && !ext_PreserveMime(pExt, pMimeType)) { continue; } // Made it through all checks. // Extension is good. bSuccess = TRUE; break; } // Finally, if were going to fail, lets give caller // an empty buffer to work with (to help get around // those people that dont check error codes). if(!bSuccess) { *pOutExtension = '\0'; } // All done. return(strlen(pOutExtension)); } /*--------------------------------------------------------------------** ** Goal is to provide an abstract way to iterate through all possible ** ** extensions these attributes can produce. ** ** Caller must know to ignore empty strings if we return TRUE to ** ** avoid having intimate knowledge about what we do inside here. ** ** ** ** WIN32: Offset will be greater since were doing one special ** ** step. ** ** EXT_OFFSET if subtracted from iIndex will yield a zero index into ** ** another list. ** **--------------------------------------------------------------------*/ #ifdef XP_WIN32 #define EXT_OFFSET 2 #else #define EXT_OFFSET 1 #endif BOOL ext_ExtByIndex(int iIndex, char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName, const char *pMimeType) { // We assume a few things in the internal implementation. ASSERT(pExt); // Set out parameters. BOOL bReturns = FALSE; *pExt = '\0'; if(!iIndex) { // First extension should always be that of the file. // Always return TRUE, such that the following steps are // executed. ext_Extract(pExt, stExtBufSize, dwFlags, pOrigName); bReturns = TRUE; } #ifdef XP_WIN32 else if (1 == iIndex) { // Check the MIME database. // Always return TRUE, such that the following steps are // executed. ext_MimeDatabase(pExt, stExtBufSize, dwFlags, pMimeType); bReturns = TRUE; } #endif else { // Iterate through extensions found by MIME type. // Notice that the index is correct on Win32 and // Win16 due to compile time manipulation // of the offset. int iMimeIndex = iIndex - EXT_OFFSET; bReturns = ext_ExtByMimeIndex(iMimeIndex, pExt, stExtBufSize, dwFlags, pMimeType); } // Return wether or not another extension is returned. return(bReturns); } #undef EXT_OFFSET /*-----------------------------------------------------------------** ** Goal is to specifically iterate our interally kept mime list(s) ** ** Caller should know how to ignore empty strings with successful ** ** return value. **-----------------------------------------------------------------*/ BOOL ext_ExtByMimeIndex(int iIndex, char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pMimeType) { // We assume a few things in the internal implementation. ASSERT(pExt); // Clear any out params. BOOL bReturns = FALSE; *pExt = '\0'; // No need to attempt if no Mime type. if(pMimeType) { // Find the list of extensions of that MIME type. XP_List *pTypesList = cinfo_MasterListPointer(); NET_cdataStruct *pListEntry = NULL; while ((pListEntry = (NET_cdataStruct *)XP_ListNextObject(pTypesList))) { if(pListEntry->ci.type != NULL) { if(!stricmp(pListEntry->ci.type, pMimeType)) { if(pListEntry->num_exts > iIndex) { // We consider getting this far success. bReturns = TRUE; char *pFinally = pListEntry->exts[iIndex]; if(pFinally) { if('.' == *pFinally) { // Go beyond the period. pFinally++; } // See if it matches the flags. BOOL bGood = FALSE; if(strlen(pFinally) < stExtBufSize) { if(dwFlags & EXT_DOT_THREE) { if(strlen(pFinally) <= 3) { bGood = TRUE; } } else { bGood = TRUE; } } if(bGood) { strcpy(pExt, pFinally); } } } // No need to continue loop after MIME type found. break; } } } } return(bReturns); } /*------------------------------------------------------------------** ** Goal is to determine wether or not the extension of the original ** ** file is preserved in the extension passed in. ** **------------------------------------------------------------------*/ BOOL ext_PreserveExt(const char *pExt, DWORD dwFlags, const char *pOrigName) { BOOL bRetval = FALSE; if(pExt) { char aBuffer[_MAX_EXT]; aBuffer[0] = '\0'; if(pOrigName) { // Extract the extension. // Dont check return value. // Well want to return true if both strings are emptpy. size_t stOrig = ext_Extract(aBuffer, sizeof(aBuffer), dwFlags, pOrigName); if(!stOrig) { aBuffer[0] = '\0'; } } if(!stricmp(aBuffer, pExt)) { // Good. bRetval = TRUE; } } return(bRetval); } /*-----------------------------------------------------------------** ** Goal is to determine wether or not the given extension is shell ** ** executable. ** **-----------------------------------------------------------------*/ BOOL ext_ShellExecutable(const char *pExt, DWORD dwFlags) { BOOL bRetval = FALSE; // Empty strings fail. if(pExt) { // Pass off to executable finder, which should just // rely on the shell to find the information with a // little extra logic. // We'll need to add a period. char *pBuffer = (char *)XP_ALLOC(strlen(pExt) + 2); if(pBuffer) { strcpy(pBuffer, "."); strcat(pBuffer, pExt); bRetval = FEU_FindExecutable(pBuffer, NULL, dwFlags & EXT_EXECUTABLE_IDENTITY ? TRUE : FALSE, TRUE); XP_FREE(pBuffer); pBuffer = NULL; } } return(bRetval); } /*----------------------------------------------------------------------** ** Goal is to determine wether or not the passed in extension correctly ** ** represents the MIME type. ** **----------------------------------------------------------------------*/ BOOL ext_PreserveMime(const char *pExt, const char *pMimeType) { BOOL bRetval = FALSE; // Need an extension and a MIME type to continue. // We need both so that we can make sure the extension is in // the mime type list. if(pExt && pMimeType) { // Find the MIME type in our internal list. // We will not depend on any external information, // as all the inner workings are actually // controlled by our inner list, regardless of // how the system actually has things mapped out. // Find the list of extensions of that MIME type. XP_List *pTypesList = cinfo_MasterListPointer(); NET_cdataStruct *pListEntry = NULL; while ((pListEntry = (NET_cdataStruct *)XP_ListNextObject(pTypesList))) { if(pListEntry->ci.type != NULL) { if(!stricmp(pListEntry->ci.type, pMimeType)) { // See if extension is in the list. char *pListExt; for(int iNum = 0; iNum < pListEntry->num_exts; iNum++) { pListExt = pListEntry->exts[iNum]; if('.' == *pListExt) { // No need to compare with period. pListExt++; } // Compare without case. if(!stricmp(pListExt, pExt)) { // Mime type is represented by the extension. // Preserved. bRetval = TRUE; break; } } // No need to continue loop after MIME type found. break; } } } } return(bRetval); } /*-------------------------------------------------------------------------** ** Goal is to simply strip off the end of the filename without overflowing ** ** the buffer. We do not care about adding a period. ** **-------------------------------------------------------------------------*/ size_t ext_Extract(char *pOutExtension, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName) { size_t stReturns = 0; if(pOutExtension) { if(pOrigName) { // Make a copy of the name we can mess with. char *pMuck = XP_STRDUP(pOrigName); if(pMuck) { // Want to discard anyting beyond a '?' (get form post) // or a '#' (named anchor). char *pDiscard; if(pDiscard = strchr(pMuck, '?')) { *pDiscard = '\0'; } if(pDiscard = strchr(pMuck, '#')) { *pDiscard = '\0'; } // Find the trailing slash or backslash. char *pForeSlash = strrchr(pMuck, '/'); char *pBackSlash = strrchr(pMuck, '\\'); char *pSlash = pForeSlash > pBackSlash ? pForeSlash : pBackSlash; // Find the trailing period. char *pPeriod = strrchr(pMuck, '.'); // Period must be present. // Period must come after slash. if(pPeriod && (pPeriod > pSlash)) { // Go one past the period. pPeriod++; // Go over until end of buffer is reached, // until we hit end of string, or // until we hit a non-alpha-numeric character. // We could support spaces and other valid fname // chars, but we havent up to this point // and see no need to further complicate the // issues right now. while(*pPeriod && isalnum(*pPeriod) && (stExtBufSize - 1)) { *pOutExtension = *pPeriod; pPeriod++; pOutExtension++; stExtBufSize--; stReturns++; // Consider 8.3 if((dwFlags & EXT_DOT_THREE) && (stReturns >= 3)) { break; } } } XP_FREE(pMuck); pMuck = NULL; } } // End the out parameter (or sets to empty on failure). ASSERT(stExtBufSize); *pOutExtension = '\0'; } return(stReturns); } #ifdef XP_WIN32 /*------------------------------------------------------------------------** ** Goal is to look up the mime type and extension in the system registry. ** **------------------------------------------------------------------------*/ void ext_MimeDatabase(char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pMimeType) { // Clear out params. ASSERT(pExt); *pExt = '\0'; if(pMimeType) { // Open up the MIME database. HKEY hMime = NULL; char aMime[_MAX_PATH]; strcpy(aMime, "MIME\\Database\\Content Type\\"); strcat(aMime, pMimeType); LONG lCheckOpen = RegOpenKeyEx(HKEY_CLASSES_ROOT, aMime, 0, KEY_QUERY_VALUE, &hMime); if(ERROR_SUCCESS == lCheckOpen) { // Determine default extension. char aExt[_MAX_EXT]; aExt[0] = '\0'; DWORD dwExtSize = sizeof(aExt); LONG lCheckQuery = RegQueryValueEx(hMime, "Extension", NULL, NULL, (unsigned char *)aExt, &dwExtSize); LONG lCheckClose = RegCloseKey(hMime); ASSERT(ERROR_SUCCESS == lCheckClose); hMime = NULL; if(ERROR_SUCCESS == lCheckQuery) { char *pEval = aExt; if('.' == *pEval) { // Go beyond period, dont want it. pEval++; } if(*pEval) { BOOL bGood = FALSE; // Got an extension. // See if it is suitable. // First, can it fit in our buffer? if(strlen(pEval) < stExtBufSize) { // Check any relevant flags. if(dwFlags & EXT_DOT_THREE) { if(strlen(pEval) <= 3) { bGood = TRUE; } } else { bGood = TRUE; } } if(bGood) { // Good match. strcpy(pExt, pEval); } } } } } } #endif