/*++ Copyright (c) 1994-1999 Microsoft Corporation Module Name : strfrn.cpp Abstract: String Functions Author: Ronald Meijer (ronaldm) Project: Internet Services Manager (cluster edition) Revision History: --*/ // // Include Files // #include "stdafx.h" #include "common.h" #include "ipa.h" #include "iisdebug.h" #include "util.h" #include #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif #define new DEBUG_NEW #ifdef _MT // // Thread protected stuff // #define RaiseThreadProtection() EnterCriticalSection(&_csSect) #define LowerThreadProtection() LeaveCriticalSection(&_csSect) static CRITICAL_SECTION _csSect; #else #pragma message("Module is not thread-safe") #define RaiseThreadProtection() #define LowerThreadProtection() #endif // _MT #define MAKE_NULL(obj) { if (obj) delete obj, obj = NULL; } CString AppendToDevicePath(CString szPath, LPCTSTR szName ) { TCHAR szPathCopy[_MAX_PATH] = _T(""); StrCpyN(szPathCopy, szPath, _MAX_PATH); LPTSTR p = szPathCopy; ASSERT(szPathCopy); ASSERT(szName); // Find end of the string while (*p){p = _tcsinc(p);} // If no trailing backslash then add one if (*(_tcsdec(szPathCopy, p)) != _T('\\')) {_tcscat(szPathCopy, _T("\\"));} // if there are spaces precluding szName, then skip while ( *szName == _T(' ') ) szName = _tcsinc(szName);; // make sure that the szName // does not look like this "\filename" CString csTempString = szName; if (_tcsicmp(csTempString.Left(1), _T("\\")) == 0) { csTempString = csTempString.Right( csTempString.GetLength() - 1); } // Add new name to existing path string StrCatN(szPathCopy, csTempString,_MAX_PATH); return szPathCopy; } // // Text copy functions // // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< BOOL PCToUnixText( OUT LPWSTR & lpstrDestination, IN const CString strSource ) /*++ Routine Description: Convert CR/LF string to LF string (T String to W String). Destination string will be allocated. Arguments: LPWSTR & lpstrDestination : Destination string const CString & strSource : Source string Return Value: TRUE for success, FALSE for failure. --*/ { int cch = strSource.GetLength() + 1; lpstrDestination = (LPWSTR)AllocMem(cch * sizeof(WCHAR)); if (lpstrDestination != NULL) { LPCTSTR lpS = strSource; LPWSTR lpD = lpstrDestination; do { if (*lpS != _T('\r')) { #ifdef UNICODE *lpD++ = *lpS; #else ::MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, lpS, 1, lpD++, 1); #endif // UNICODE } } while (*lpS++); return TRUE; } return FALSE; } BOOL UnixToPCText( OUT CString & strDestination, IN LPCWSTR lpstrSource ) /*++ Routine Description: Expand LF to CR/LF (no allocation necessary) W String to T String. Arguments: CString & strDestination : Destination string LPCWSTR lpstrSource : Source string Return Value: TRUE for success, FALSE for failure. --*/ { BOOL fSuccess = FALSE; try { LPCWSTR lpS = lpstrSource; // // Since we're doubling every linefeed length, assume // the worst possible expansion to start with. // int cch = (::lstrlenW(lpstrSource) + 1) * 2; LPTSTR lpD = strDestination.GetBuffer(cch); do { if (*lpS == L'\n') { *lpD++ = _T('\r'); } #ifdef UNICODE *lpD++ = *lpS; #else ::WideCharToMultiByte(CP_ACP, 0, lpS, 1, lpD++, 1, NULL, NULL); #endif // UNICODE } while (*lpS++); strDestination.ReleaseBuffer(); ++fSuccess; } catch(CMemoryException * e) { TRACEEOLID("Exception in UnixToPCText"); e->ReportError(); e->Delete(); } return fSuccess; } /* BOOL TextToText( OUT LPWSTR & lpstrDestination, IN const CString & strSource ) /*++ Routine Description: Straight copy with allocation. T String to W String. Arguments: LPWSTR & lpstrDestination : Destination string const CString & strSource : Source string Return Value: TRUE for success, FALSE for failure. --/ { int cch = strSource.GetLength() + 1; lpstrDestination = (LPWSTR)AllocMem(cch * sizeof(WCHAR)); if (lpstrDestination != NULL) { TWSTRCPY(lpstrDestination, strSource, cch); return TRUE; } return FALSE; } #ifndef UNICODE #define WBUFF_SIZE 255 LPWSTR ReferenceAsWideString( IN LPCTSTR str ) /*++ Routine Description: Reference a T string as a W string (non-unicode only). Arguments: LPCTSTR str : Source string Return Value: Wide char pointer to wide string. Notes: This uses an internal wide char buffer, which will be overwritten by subsequent calls to this function. --/ { static WCHAR wchBuff[WBUFF_SIZE + 1]; ::MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, str, -1, wchBuff, WBUFF_SIZE + 1 ); return wchBuff; } #endif !UNICODE */ LPSTR AllocAnsiString( IN LPCTSTR lpString ) /*++ Routine Description: Convert the wide string to an ansi (multi-byte) string, which is allocated by this function Arguments: LPCTSTR lpString : Input wide string Return Value: Pointer to the allocated string --*/ { // // Character counts are DBCS friendly // int cChars = lstrlen(lpString); int nLength = (cChars * 2) + 1; LPSTR lp = (LPSTR)AllocMem(nLength); if (lp) { ::WideCharToMultiByte( CP_ACP, 0, lpString, cChars + 1, lp, nLength, NULL, NULL ); } return lp; } LPTSTR AllocString( IN LPCTSTR lpString, IN int nLen ) /*++ Routine Description: Allocate and copy string Arguments: LPCTSTR lpString : Input string int nLen : Length or -1 to autodetermine Return Value: Pointer to the allocated string --*/ { if (nLen < 0) { nLen = lstrlen(lpString); } LPTSTR lp = (LPTSTR)AllocMem((nLen + 1) * sizeof(TCHAR)); if (lp) { lstrcpy(lp, lpString); } return lp; } BOOL IsUNCName( IN const CString & strDirPath ) /*++ Routine Description: Determine if the given string path is a UNC path. Arguments: const CString & strDirPath : Directory path string Return Value: TRUE if the path is a UNC path, FALSE otherwise. Notes: Any string of the form \\foo\bar\whatever is considered a UNC path, with the exception of \\.\device paths. No validation for the existance occurs, only for the correct format. also with the exeption of \\?\ --*/ { if (strDirPath.GetLength() >= 5) // It must be at least as long as \\x\y, { // LPCTSTR lp = strDirPath; // if (*lp == _T('\\') // It must begin with \\, && *(lp + 1) == _T('\\') // && *(lp + 2) != _T('.') // This is a device. && *(lp + 2) != _T('?') // This is a "?" device. && _tcschr(lp + 3, _T('\\')) // And have at least one more \ after that ) { // // Yes, it's a UNC path // return TRUE; } } // // No, it's not // return FALSE; } BOOL GetSpecialPathRealPath( IN INT iUsageFlag, IN const CString & strDirPath, OUT CString & strDestination ) { BOOL bReturn = FALSE; LPCTSTR lpszDeviceStuff = _T("\\\\.\\"); LPCTSTR lpszSpecialStuff = _T("\\\\?\\"); LPCTSTR lpszUNCDevice = _T("UNC\\"); LPCTSTR lpszUseStuff = NULL; // Default it with something strDestination = strDirPath; if (1 == iUsageFlag) { lpszUseStuff = lpszDeviceStuff; } else { lpszUseStuff = lpszSpecialStuff; } // Check for the "special stuff" BOOL bIsValidPath = (0 == _tcsnccmp(strDirPath, lpszUseStuff, lstrlen(lpszUseStuff))); // check if we need to verifiy that it is indeeded a valid devicepath if (bIsValidPath) { CString strTempPath; // verify that this is indeed a valid special path // grab everyting after the part we're interested in... // // and check if that is a fully qualified path // or a fully qualified UNC path. // // 1) \\?\c:\temp\testind.dll // 2) \\?\UNC\MyUnc\testing.dll // // check for #1 strTempPath = strDirPath.Right(strDirPath.GetLength() - lstrlen(lpszUseStuff)); // check if it starts with UNC if (0 == _tcsnccmp(strTempPath, lpszUNCDevice, lstrlen(lpszUNCDevice))) { CString strTempPath2; strTempPath2 = strTempPath.Right(strTempPath.GetLength() - lstrlen(lpszUNCDevice)); DebugTrace(_T("SpecialPath:%s,it's a UNC path!\r\n"),strTempPath2); // Append on the extra ("\\\\") when returning the munged path strDestination = _T("\\\\") + strTempPath2; bReturn = TRUE; } else { // check if the path if fully qualified and // if it's valid strDestination = strTempPath; if (PathIsRelative(strTempPath)) { // don't accept relative paths as valid type paths.. bReturn = FALSE; DebugTrace(_T("SpecialPath:%s,is relative.\r\n"),strTempPath); } else { // check if it has these 3 parts "c:\" if (IsFullyQualifiedPath(strTempPath)) { bReturn = TRUE; DebugTrace(_T("SpecialPath:%s a valid path\r\n"),strTempPath); } else { bReturn = FALSE; DebugTrace(_T("SpecialPath:%s,is missing drive part\r\n"),strTempPath); } } } } return bReturn; } BOOL IsSpecialPath( IN const CString & strDirPath, IN BOOL bCheckIfValid, IN BOOL bDevicePath ) /*++ Routine Description: Determine if the given path is of the form: 1) \\?\c:\temp\testind.dll 2) \\?\UNC\MyUnc\testing.dll Arguments: const CString & strDirPath : Directory path string BOOL bCheckIfValid : to say "return true only if it's a "special path" and if it's valid" BOOL bDevicePath : check on device path, not special path Return Value: TRUE if the path given is a special path, FALSE if it is not. if bCheckIfValid = TRUE then: TRUE if the path given is a special path and it's valid FALSE if it is not. --*/ { BOOL bIsSpecialPath = FALSE; LPCTSTR lpszDeviceStuff = _T("\\\\.\\"); LPCTSTR lpszSpecialStuff = _T("\\\\?\\"); LPCTSTR lpszUNCDevice = _T("UNC\\"); LPCTSTR lpszUseStuff = NULL; // Check for the "special stuff" if (bDevicePath) { lpszUseStuff = lpszDeviceStuff; } else { lpszUseStuff = lpszSpecialStuff; } bIsSpecialPath = (0 == _tcsnccmp(strDirPath, lpszUseStuff, lstrlen(lpszUseStuff))); // check if we need to verifiy that it is indeeded a valid devicepath if (bIsSpecialPath && bCheckIfValid) { bIsSpecialPath = FALSE; CString strTempPath; // verify that this is indeed a valid special path // grab everyting after the part we're interested in... // // and check if that is a fully qualified path // or a fully qualified UNC path. // // 1) \\?\c:\temp\testind.dll // 2) \\?\UNC\MyUnc\testing.dll // // check for #1 strTempPath = strDirPath.Right(strDirPath.GetLength() - lstrlen(lpszUseStuff)); // check if it starts with UNC if (0 == _tcsnccmp(strTempPath, lpszUNCDevice, lstrlen(lpszUNCDevice))) { bIsSpecialPath = TRUE; DebugTrace(_T("SpecialPath:%s,it's a UNC path!\r\n"),strTempPath); } else { // check if the path if fully qualified and // if it's valid if (!PathIsRelative(strTempPath)) { bIsSpecialPath = TRUE; DebugTrace(_T("SpecialPath:%s,it's NOT a UNC path!\r\n"),strTempPath); } } } return bIsSpecialPath; } BOOL IsDevicePath( IN const CString & strDirPath ) /*++ Routine Description: Determine if the given path is of the form "\\.\foobar" Arguments: const CString & strDirPath : Directory path string Return Value: TRUE if the path given is a device path, FALSE if it is not. --*/ { LPCTSTR lpszDevice = _T("\\\\.\\"); return (0 == _tcsnccmp(strDirPath, lpszDevice, lstrlen(lpszDevice))); } BOOL IsRestrictedFilename( IN const CString & strDirPath ) { // The following are the possible file names that can cause problems // Restricted Filenames: // CON // PRN // AUX // CLOCK$ // NUL // COM1-COM9 // LPT1-LPT9 return FALSE; } BOOL IsFullyQualifiedPath( IN const CString & strDirPath ) /*++ Routine Description: Determine if the given string is a fully qualified path name Arguments: const CString & strDirPath : Directory path string Return Value: TRUE if the path is a fully qualified path name --*/ { return strDirPath.GetLength() >= 3 && strDirPath[1] == _T(':') && strDirPath[2] == _T('\\'); } BOOL IsNetworkPath( IN const CString & strDirPath, OUT CString * pstrDrive, OPTIONAL OUT CString * pstrUNC OPTIONAL ) /*++ Routine Description: Determine if the path exists on a network directory in the context of the local machine. Arguments: const CString & strDirPath : Directory path string CString * pstrDrive : Returns drive CString * pstrUNC : Returns UNC path Return Value: TRUE if the path is a network path, FALSE if it is local. Notes: Only fully qualified paths with drive letters are checked. --*/ { BOOL fUNC = FALSE; CString strDrive; try { if (pstrDrive == NULL) { pstrDrive = &strDrive; } ASSERT(strDirPath[1] == _T(':')); if (strDirPath[1] == _T(':')) { *pstrDrive = _T("?:"); // // Fill in actual drive letter // pstrDrive->SetAt(0, strDirPath[0]); UINT nType = GetDriveType(*pstrDrive); // // DRIVE_NO_ROOT_DIR? this is a little dodgy, but // this is the result I get back after browsing // using a file open dialog. Weird. // fUNC = (nType == DRIVE_NO_ROOT_DIR || nType == DRIVE_REMOTE); } // // Return UNC path if requested // if (fUNC && pstrUNC != NULL) { DWORD dwSize = _MAX_PATH; LPTSTR lp = pstrUNC->GetBuffer(dwSize); ::WNetGetConnection(*pstrDrive, lp, &dwSize); pstrUNC->ReleaseBuffer(); } } catch(CException * e) { e->ReportError(); e->Delete(); } return fUNC; } LPCTSTR MakeUNCPath( IN OUT CString & strDir, IN LPCTSTR lpszOwner, IN LPCTSTR lpszDirectory ) /*++ Routine Description: Convert the given directory to a UNC path. Arguments: CString & strDir : UNC String. LPCTSTR lpszOwner : Computer name LPCTSTR lpszDirectory : Source string Return Value: Pointer to strDir Notes: The owner may or may not start with "\\". If it doesn't, the backslashes are provided. --*/ { // // Try to make make a unc path out of the directory // ASSERT(lpszDirectory[1] == _T(':')); if (lpszDirectory[1] != _T(':')) { strDir = _T(""); return (LPCTSTR)strDir; } // Problem here could be that lpszOwner is not a computername but rather // an IP Address!!!! LPCTSTR lpszServer = PURE_COMPUTER_NAME(lpszOwner); if (LooksLikeIPAddress(lpszServer)) { // // Get by ip address // CString strTemp; CIPAddress ia(lpszServer); if (NOERROR != MyGetHostName((DWORD)ia, strTemp)) { strTemp = lpszServer; } strDir.Format( _T("\\\\%s\\%c$\\%s"), PURE_COMPUTER_NAME((LPCTSTR) strTemp), lpszDirectory[0], lpszDirectory + 3 ); } else { strDir.Format( _T("\\\\%s\\%c$\\%s"), PURE_COMPUTER_NAME(lpszOwner), lpszDirectory[0], lpszDirectory + 3 ); } return (LPCTSTR)strDir; } BOOL IsURLName( IN const CString & strDirPath ) /*++ Routine Description: Determine if the given string path is an URL path. Arguments: const CString & strDirPath : Directory path string Return Value: TRUE if the path is an URL path, FALSE otherwise. Notes: Any string of the form protocol://whatever is considered an URL path --*/ { if (strDirPath.GetLength() >= 4) // It must be at least as long as x:// { // if (strDirPath.Find(_T("://")) > 0) // Must contain :// { // // Yes, it's an URL path // return TRUE; } } // // No, it's not // return FALSE; } int CStringFindNoCase( IN const CString & strSrc, IN LPCTSTR lpszSub ) /*++ Routine Description: This should be CString::FindNoCase(). Same as CString::Find(), but case-insensitive. Arguments: const CString & strSrc : Source string LPCTSTR lpszSub : String to look for. Return Value: The position of the substring, or -1 if not found. --*/ { LPCTSTR lp1 = strSrc; LPCTSTR lp2, lp3; int nPos = -1; while (*lp1) { lp2 = lp1; lp3 = lpszSub; while(*lp2 && *lp3 && _totupper(*lp2) == _totupper(*lp3)) { ++lp2; ++lp3; } if (!*lp3) { // // Found the substring // nPos = (int)(lp1 - (LPCTSTR)strSrc); break; } ++lp1; } return nPos; } DWORD ReplaceStringInString( OUT IN CString & strBuffer, IN CString & strTarget, IN CString & strReplacement, IN BOOL fCaseSensitive ) /*++ Routine Description: Replace the first occurrence of a string with a second string inside a third string. Arguments: CString & strBuffer : Buffer in which to replace CString & strTarget : String to look for CString & strReplacement : String to replace it with BOOL fCaseSensitive : TRUE for case sensitive replacement. Return Value: ERROR_SUCCESS for successful replacement. ERROR_INVALID_PARAMETER if any string is empty, ERROR_FILE_NOT_FOUND if the target string doesn't exist, or another win32 error code indicating failure. --*/ { if (strBuffer.IsEmpty() || strTarget.IsEmpty() || strReplacement.IsEmpty()) { return ERROR_INVALID_PARAMETER; } DWORD err = ERROR_FILE_NOT_FOUND; int nPos = fCaseSensitive ? strBuffer.Find(strTarget) : CStringFindNoCase(strBuffer, strTarget); if (nPos >= 0) { try { CString str(strBuffer.Left(nPos)); str += strReplacement; str += strBuffer.Mid(nPos + strTarget.GetLength()); strBuffer = str; err = ERROR_SUCCESS; } catch(CMemoryException * e) { e->Delete(); err = ERROR_NOT_ENOUGH_MEMORY; } } return err; } DWORD DeflateEnvironmentVariablePath( IN LPCTSTR lpszEnvVar, IN OUT CString & strTarget ) /*++ Routine Description: Take a path, and if the path represents a superset of the path in the environment variable given, replace the relevant portion of the path with the environment variable. Arguments: LPCTSTR lpszEnvVar : Environment variable CString & strTarget : Path Return Value: ERROR_SUCCESS if the replacement was done succesfully, ERROR_FILE_NOT_FOUND if the path represented by the environment variable is not contained within the string, or an other win32 error for error conditions --*/ { CError err; CString strEnv; if (!::GetEnvironmentVariable( lpszEnvVar, strEnv.GetBuffer(_MAX_PATH), _MAX_PATH )) { err.SetLastWinError(); } strEnv.ReleaseBuffer(); if (err.Succeeded()) { try { CString strReplacement(_T("%")); strReplacement += lpszEnvVar; strReplacement += _T("%"); err = ReplaceStringInString( strTarget, strEnv, strReplacement, FALSE ); } catch(CMemoryException * e) { err = ERROR_NOT_ENOUGH_MEMORY; e->Delete(); } } return err; } LPCTSTR GUIDToCString( IN REFGUID guid, OUT CString & str ) /*++ Routine Description: Convert a GUID to a CString, returning pointer to the string Arguments: REFGUID : GUID to be converted CString & str : Output string buffer Return Value: Pointer to the string --*/ { LPTSTR lpGUID = str.GetBuffer(MAX_PATH); if (lpGUID) { ::StringFromGUID2(guid, lpGUID, MAX_PATH); str.ReleaseBuffer(); } return str; } int CountCharsToDoubleNull( IN LPCTSTR lp ) /*++ Routine Description: Count TCHARS up to and including the double NULL. Arguments: LPCTSTR lp : TCHAR Stream Return Value: Number of chars up to and including the double NULL --*/ { int cChars = 0; for(;;) { ++cChars; if (lp[0] == _T('\0') && lp[1] == _T('\0')) { return ++cChars; } ++lp; } } DWORD ConvertDoubleNullListToStringList( IN LPCTSTR lpstrSrc, OUT CStringList & strlDest, IN int cChars OPTIONAL ) /*++ Routine Description: Convert a double null terminate list of null terminated strings to a more manageable CStringListEx Arguments: LPCTSTR lpstrSrc : Source list of strings CStringList & strlDest : Destination string list. int cChars : Number of characters in double NULL list. if -1, autodetermine length Return Value: ERROR_SUCCESS if the list was converted properly ERROR_INVALID_PARAMETER if the list was empty ERROR_NOT_ENOUGH_MEMORY if there was a mem exception --*/ { DWORD err = ERROR_SUCCESS; if (lpstrSrc == NULL) { return ERROR_INVALID_PARAMETER; } if (cChars < 0) { // // Calculate our own size. This might be off if multiple // blank linkes (0) appear in the multi_sz, so the character // size is definitely preferable // cChars = CountCharsToDoubleNull(lpstrSrc); } try { strlDest.RemoveAll(); if (cChars == 2 && *lpstrSrc == _T('\0')) { // // Special case: MULTI_SZ containing only // a double NULL are in fact blank entirely. // // N.B. IMHO this is a metabase bug -- RonaldM // --cChars; } // // Grab strings until only the final NULL remains // while (cChars > 1) { CString strTmp = lpstrSrc; strlDest.AddTail(strTmp); lpstrSrc += (strTmp.GetLength() + 1); cChars -= (strTmp.GetLength() + 1); } } catch(CMemoryException * e) { TRACEEOLID("!!! exception building stringlist"); err = ERROR_NOT_ENOUGH_MEMORY; e->Delete(); } return err; } DWORD ConvertStringListToDoubleNullList( IN CStringList & strlSrc, OUT DWORD & cchDest, OUT LPTSTR & lpstrDest ) /*++ Routine Description: Flatten the string list into a double null terminated list of null terminated strings. Arguments: CStringList & strlSrc : Source string list DWORD & cchDest : Size in characters of the resultant array (including terminating NULLs) LPTSTR & lpstrDest : Allocated flat array. Return Value: ERROR_SUCCESS if the list was converted properly ERROR_INVALID_PARAMETER if the list was empty ERROR_NOT_ENOUGH_MEMORY if there was a mem exception --*/ { cchDest = 0; lpstrDest = NULL; BOOL fNullPad = FALSE; // // Compute total size in characters // POSITION pos; for(pos = strlSrc.GetHeadPosition(); pos != NULL; /**/ ) { CString & str = strlSrc.GetNext(pos); TRACEEOLID(str); cchDest += str.GetLength() + 1; } if (!cchDest) { // // Special case: A totally empty MULTI_SZ // in fact consists of 2 (final) NULLS, instead // of 1 (final) NULL. This is required by the // metabase, but should be a bug. See note // at reversal function above. // ++cchDest; fNullPad = TRUE; } // // Remember final NULL // cchDest += 1; lpstrDest = AllocTString(cchDest); if (lpstrDest == NULL) { return ERROR_NOT_ENOUGH_MEMORY; } LPTSTR pch = lpstrDest; for(pos = strlSrc.GetHeadPosition(); pos != NULL; /**/ ) { CString & str = strlSrc.GetNext(pos); lstrcpy(pch, (LPCTSTR)str); pch += str.GetLength(); *pch++ = _T('\0'); } *pch++ = _T('\0'); if (fNullPad) { *pch++ = _T('\0'); } return ERROR_SUCCESS; } int ConvertSepLineToStringList( IN LPCTSTR lpstrIn, OUT CStringList & strlOut, IN LPCTSTR lpstrSep ) /*++ Routine Description: Convert a line containing multiple strings separated by a given character to a CStringListEx Arguments: LPCTSTR lpstrIn : Input line CStringListEx & strlOut : Output stringlist LPCTSTR lpstrSep : List of separators Return Value: The number of items added --*/ { int cItems = 0; strlOut.RemoveAll(); try { CString strSrc(lpstrIn); LPTSTR lp = strSrc.GetBuffer(0); lp = _tcstok(lp, lpstrSep); while (lp) { CString str(lp); strlOut.AddTail(str); lp = _tcstok(NULL, lpstrSep); ++cItems; } } catch(CMemoryException * e) { TRACEEOLID("Exception converting CSV list to stringlist"); e->ReportError(); e->Delete(); } return cItems; } LPCTSTR ConvertStringListToSepLine( IN CStringList & strlIn, OUT CString & strOut, IN LPCTSTR lpstrSep ) /*++ Routine Description: Convert stringlist into a single CString, each entry separated by the given separator string. Arguments: CStringListEx & strlIn : Input stringlist CString & strOut : Output string LPCTSTR lpstrSep : Separator string Return Value: Pointer to the output string. --*/ { strOut.Empty(); POSITION pos = strlIn.GetHeadPosition(); while(pos) { CString & str = strlIn.GetNext(pos); strOut += str; strOut += lpstrSep; } // // Fix for bug #286824 // Remove separator from the last line: it looks ugly in edit control // if (!strOut.IsEmpty()) { strOut.GetBufferSetLength(strOut.GetLength() - 1); strOut.ReleaseBuffer(); } return strOut; } BOOL CStringListEx::operator ==( IN const CStringList & strl ) /*++ Routine Description: Compare against CStringList. In order for two CStringLists to match, they must match in every element, which must be in the same order. Arguments: CStringList strl : String list to compare against. Return Value: TRUE if the two string lists are identical --*/ { if (strl.GetCount() != GetCount()) { return FALSE; } POSITION posa = strl.GetHeadPosition(); POSITION posb = GetHeadPosition(); while (posa) { ASSERT(posa); ASSERT(posb); CString & strA = strl.GetNext(posa); CString & strB = GetNext(posb); if (strA != strB) { return FALSE; } } return TRUE; } CStringListEx & CStringListEx::operator =( IN const CStringList & strl ) /*++ Routine Description: Assignment operator Arguments: const CStringList & strl : Source stringlist Return Value: Reference to this --*/ { RemoveAll(); POSITION pos = strl.GetHeadPosition(); while(pos) { CString & str = strl.GetNext(pos); AddTail(str); } return *this; } BOOL CvtStringToLong( IN LPCTSTR lpNumber, OUT DWORD * pdwValue ) /*++ Routine Description: Helper function to convert string (hex or decimal) to a dword. Arguments: LPCTSTR lpNumber : Input number DWORD * pdwValue : Returns the value Return Value: TRUE for success, FALSE for failure --*/ { const TCHAR rgchHex[] = _T("00112233445566778899aAbBcCdDeEfF"); DWORD dwResult = 0L; DWORD dwResultPrev = 0L; // // Assume a decimal base // DWORD dwBase = 10L; ASSERT_READ_PTR(lpNumber); ASSERT_WRITE_PTR(pdwValue); while (*lpNumber == _T(' ') || *lpNumber == _T('0')) { ++lpNumber; } if (*lpNumber == _T('x') || *lpNumber == _T('X')) { dwBase = 16L; ++lpNumber; } while (*lpNumber) { DWORD dwDigit; // // Search the character in the hexadecimal string // LPCTSTR pchDigit = _tcschr(rgchHex, *lpNumber); if (!pchDigit) { // // Character is not found // return FALSE; } dwDigit = DIFF(pchDigit - rgchHex) >> 1; if (dwDigit >= dwBase) { // // Hexadecimal character in a decimal integer // return FALSE; } dwResultPrev = dwResult; dwResult *= dwBase; dwResult += dwDigit; if (dwResult < dwResultPrev) { // // Overflow // return FALSE; } // // Parse the next character // ++lpNumber; } *pdwValue = dwResult; return TRUE; } const LPCTSTR g_cszMonths[] = { _T("Jan"), _T("Feb"), _T("Mar"), _T("Apr"), _T("May"), _T("Jun"), _T("Jul"), _T("Aug"), _T("Sep"), _T("Oct"), _T("Nov"), _T("Dec"), }; const LPCTSTR g_cszWeekDays[] = { _T("Sun"), _T("Mon"), _T("Tue"), _T("Wed"), _T("Thu"), _T("Fri"), _T("Sat"), }; inline BOOL SkipTillDigit(LPCTSTR & lp) { while (lp && *lp && !_istdigit(*lp)) ++lp; return lp != NULL; } inline BOOL SkipPastDigits(LPCTSTR & lp) { while (lp && *lp && _istdigit(*lp)) ++lp; return lp != NULL; } BOOL FetchIntField( LPCTSTR & lp, int & n ) { if (SkipTillDigit(lp)) { n = _ttoi(lp); if (n < 0) { ASSERT_MSG("Bogus string->int"); return FALSE; } return SkipPastDigits(lp); } return FALSE; } BOOL MatchString( LPCTSTR lpTarget, const LPCTSTR * rglp, int cElements, int & idx ) { for (idx = 0; idx < cElements; ++idx) { if (!_tcsnicmp(lpTarget, rglp[idx], _tcslen(rglp[idx]))) { return TRUE; } } return FALSE; } static g_dwCurrentTimeZone = TIME_ZONE_ID_INVALID; static TIME_ZONE_INFORMATION g_tzInfo; BOOL CvtGMTStringToInternal( IN LPCTSTR lpTime, OUT time_t * ptValue ) /*++ Routine Description: Convert GMT string to time in the local timezone format Arguments: IN LPCSTSTR lpTime : Input time string OUT time_t * ptValue : Output time_t Return Value: TRUE for success, FALSE for failure --*/ { ASSERT_READ_PTR(lpTime); ASSERT_WRITE_PTR(ptValue); int year, month, day, hour, minutes, seconds; do { if (!lpTime || !ptValue) { break; } if (!FetchIntField(lpTime, day)) { break; } ++lpTime; // Skip separator if (!MatchString(lpTime, g_cszMonths, 12, month)) { break; } ++month; // 0-based. if (!FetchIntField(lpTime, year) || !FetchIntField(lpTime, hour) || !FetchIntField(lpTime, minutes) || !FetchIntField(lpTime, seconds)) { break; } if (year < 100) { year += ((year < 50) ? 2000 : 1900); } CTime time(year, month, day, hour, minutes, seconds); if (time.GetTime() == (time_t)-1L) { break; } *ptValue = time.GetTime(); TRACEEOLID("GMT Time is " << ctime(ptValue)); // // Adjust for local time zone // RaiseThreadProtection(); if (g_dwCurrentTimeZone == TIME_ZONE_ID_INVALID) { // // GetTimeZoneInfo() is expensive, so cache its output // g_dwCurrentTimeZone = GetTimeZoneInformation(&g_tzInfo); } LowerThreadProtection(); if (g_dwCurrentTimeZone != TIME_ZONE_ID_INVALID) { _tzset(); struct tm * tb = localtime(ptValue); BOOL fDST = tb->tm_isdst; RaiseThreadProtection(); *ptValue -= (g_tzInfo.Bias * 60L); TRACEEOLID("local time (w/o dst adjustment) is " << ctime(ptValue)); if (fDST) { *ptValue -= (g_tzInfo.DaylightBias * 60L); } else { // // Almost always this is zero // *ptValue -= (g_tzInfo.StandardBias * 60L); } LowerThreadProtection(); TRACEEOLID("Local time adjusted for dst is " << ctime(ptValue)); } // // Time OK // return TRUE; } while(FALSE); // // Set max abs timeout value. // CTime time(2037, 12, 31, 0, 0, 0); *ptValue = (time_t)time.GetTime(); return FALSE; } void CvtInternalToGMTString( IN time_t tm, OUT CString & str ) /*++ Routine Description: Reverse of the above function. Convert internal time_t to a GMT time string (converting to GM time in the process) Arguments: time_t tm : Input time_t CString & str : Out CString Return Value: None --*/ { try { _tzset(); struct tm * tb = gmtime(&tm); str.Format( _T("%-3.3s, %02d %-3.3s %4d %02d:%02d:%02d GMT"), g_cszWeekDays[tb->tm_wday], tb->tm_mday, g_cszMonths[tb->tm_mon], tb->tm_year + 1900, tb->tm_hour, tb->tm_min, tb->tm_sec ); TRACEEOLID("Time string is " << str); } catch(CMemoryException * e) { e->ReportError(); e->Delete(); } } // // International numeric strings // // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // // Initialize library // BOOL InitIntlSettings() { #ifdef _MT INITIALIZE_CRITICAL_SECTION(&_csSect); #endif // _MT return CINumber::Allocate(); } // // De-initialize library // void TerminateIntlSettings() { CINumber::DeAllocate(); #ifdef _MT DeleteCriticalSection(&_csSect); #endif // _MT } // // Static Member Initialization // // NOTE: MFC classes CANNOT be global in an AFX extension, as they // will not be initialized properly. Solution is to allocate them. // CString * CINumber::_pstrThousandSeparator = NULL; CString * CINumber::_pstrDecimalPoint = NULL; CString * CINumber::_pstrBadNumber = NULL; CString * CINumber::_pstrCurrency = NULL; CString * CINumber::_pstr = NULL; BOOL CINumber::_fAllocated = FALSE; BOOL CINumber::_fCurrencyPrefix = TRUE; BOOL CINumber::_fInitialized = FALSE; CINumber::CINumber() /*++ Routine Description: Constructor Arguments: None Return Value: None --*/ { if (!CINumber::_fInitialized) { CINumber::Initialize(); } } CINumber::~CINumber() /*++ Routine Description: Destructor Arguments: None Return Value: None --*/ { } /* static */ BOOL CINumber::Allocate() /*++ Routine Description: Allocate with US settings Arguments: None Return Value: TRUE if allocation was successfull, FALSE otherwise --*/ { RaiseThreadProtection(); ASSERT(!IsAllocated()); if (!IsAllocated()) { try { // // NOTE: Cannot use globals for these, because // they will not be initialized properly // when used in an extension dll. // CINumber::_pstrThousandSeparator = new CString(_T(",")); CINumber::_pstrDecimalPoint = new CString(_T(".")); CINumber::_pstrBadNumber = new CString(_T("--")); CINumber::_pstrCurrency = new CString(_T("$ ")); CINumber::_pstr = new CString; _fAllocated = TRUE; } catch(CMemoryException * e) { TRACEEOLID("Initialization Failed"); e->ReportError(); e->Delete(); } } LowerThreadProtection(); return IsAllocated(); } /* static */ void CINumber::DeAllocate() /*++ Routine Description: Clean up allocations Arguments: N/A Return Value: N/A --*/ { RaiseThreadProtection(); ASSERT(IsAllocated()); if (IsAllocated()) { MAKE_NULL(CINumber::_pstrThousandSeparator); MAKE_NULL(CINumber::_pstrDecimalPoint); MAKE_NULL(CINumber::_pstrBadNumber); MAKE_NULL(CINumber::_pstrCurrency); MAKE_NULL(CINumber::_pstr); } LowerThreadProtection(); _fAllocated = FALSE; } /* static */ BOOL CINumber::Initialize( IN BOOL fUserSetting /* TRUE */ ) /*++ Routine Description: Initialize all the international settings, such as thousand separators and decimal points Parameters: BOOL fUserSetting If TRUE, use current user settings, if FALSE use system settings. Return Value: TRUE for success, FALSE for failure Notes: Note that this function only needs to be explicitly called when the country settings have changed, or when system settings are desired (user is default) --*/ { #define MAXLEN 6 int cErrors = 0; TRACEEOLID("Getting locale-dependend information"); ASSERT(IsAllocated()); if (!IsAllocated()) { Allocate(); } RaiseThreadProtection(); try { LCID lcid = fUserSetting ? ::GetUserDefaultLCID() : GetSystemDefaultLCID(); LCTYPE lctype = fUserSetting ? 0 : LOCALE_NOUSEROVERRIDE; // // Get Decimal Point // if (!::GetLocaleInfo( lcid, LOCALE_SDECIMAL | lctype, CINumber::_pstrDecimalPoint->GetBuffer(MAXLEN), MAXLEN )) { TRACEEOLID("Unable to get intl decimal point"); ++cErrors; } CINumber::_pstrDecimalPoint->ReleaseBuffer(); // // Get Thousand Separator // if (!::GetLocaleInfo( lcid, LOCALE_STHOUSAND | lctype, CINumber::_pstrThousandSeparator->GetBuffer(MAXLEN), MAXLEN )) { TRACEEOLID("Unable to get 1000 separator"); ++cErrors; } CINumber::_pstrThousandSeparator->ReleaseBuffer(); #ifndef _UNICODE // // Some countries have a space as a 1000 separator, // but for some reason, this is ansi 160, which // shows up as a space fine on windows apps, // looks like garbage on console apps. // if ((*CINumber::_pstrThousandSeparator)[0] == CHAR(160)) { CINumber::_pstrThousandSeparator->SetAt(0, ' '); TRACEEOLID("Space 1000 separator substituted"); } #endif // _UNICODE // // Get currency symbol // if (!::GetLocaleInfo( lcid, LOCALE_SCURRENCY | lctype, CINumber::_pstrCurrency->GetBuffer(MAXLEN), MAXLEN )) { TRACEEOLID("Unable to get currency symbol"); ++cErrors; } CINumber::_pstrCurrency->ReleaseBuffer(); } catch(CMemoryException * e) { TRACEEOLID("!!!exception in getting intl settings:"); e->ReportError(); e->Delete(); ++cErrors; } TRACEEOLID("Thousand Separator . . . . . : " << *CINumber::_pstrThousandSeparator); TRACEEOLID("Decimal Point . . . . . . . : " << *CINumber::_pstrDecimalPoint); TRACEEOLID("Currency Symbol. . . . . . . : " << *CINumber::_pstrCurrency); TRACEEOLID("Bad number . . . . . . . . . : " << *CINumber::_pstrBadNumber); TRACEEOLID("Currency Prefix. . . . . . . : " << CINumber::_fCurrencyPrefix); CINumber::_fInitialized = TRUE; LowerThreadProtection(); return cErrors == 0; } /* static */ double CINumber::BuildFloat( IN const LONG lInteger, IN const LONG lFraction ) /*++ Return Value: Combine integer and fraction to form float Parameters: const LONG lInteger Integer portion const LONG lFraction Fractional portion Return Value: float value --*/ { double flValue = 0.0; // // Negative fractions? // ASSERT(lFraction >= 0); if (lFraction >= 0) { flValue = (double)lFraction; while (flValue >= 1.0) { flValue /= 10.0; } // // Re-add (or subtract if the original number // was negative) the fractional part // if (lInteger > 0L) { flValue += (double)lInteger; } else { flValue -= (double)lInteger; flValue = -flValue; } } return flValue; } /* static */ LPCTSTR CINumber::ConvertLongToString( IN const LONG lSrc, OUT CString & str ) /*++ CINumber::ConvertLongToString Purpose: Convert long number to string with 1000 separators Parameters: const LONG lSrc Source number CString & str String to write to Return Value: Pointer to converted string --*/ { LPTSTR lpOutString = str.GetBuffer(16); // // Forget about the negative sign for now. // LONG lNum = (lSrc >= 0L) ? lSrc : -lSrc; int outstrlen = 0; do { lpOutString[outstrlen++] = _T('0') + (TCHAR)(lNum % 10L); lNum /= 10L; // // if more digits left and we're on a 1000 boundary (printed 3 digits, // or 3 digits + n*(3 digits + 1 comma), then print a 1000 separator. // Note: will only work if the 1000 separator is 1 character. // ASSERT(CINumber::_pstrThousandSeparator->GetLength() == 1); if (lNum != 0L && (outstrlen == 3 || outstrlen == 7 || outstrlen == 11)) { lstrcpy(lpOutString + outstrlen, *CINumber::_pstrThousandSeparator); outstrlen += CINumber::_pstrThousandSeparator->GetLength(); } } while (lNum > 0L); // // Add a negative sign if necessary. // if (lSrc < 0L) { lpOutString[outstrlen++] = _T('-'); } str.ReleaseBuffer(outstrlen); str.MakeReverse(); return (LPCTSTR)str; } /* static */ LPCTSTR CINumber::ConvertFloatToString( IN const double flSrc, IN int nPrecision, OUT CString & str ) /*++ Routine Description: Convert floating point number to string represenation Parameters: const double flSrc Source floating point number int nPrecision Number of decimal points CString & str String to convert to Return Value: Pointer to converted string. --*/ { // // Forget about the negative sign for now, // and the fractional portion. // TCHAR szFraction[256]; LPCTSTR lpFraction = NULL; ::_stprintf(szFraction, _T("%.*f"), nPrecision, flSrc); lpFraction = ::_tcschr(szFraction, _T('.') ); ASSERT(lpFraction != NULL); ++lpFraction; CINumber::ConvertLongToString((LONG)flSrc, str); str += *CINumber::_pstrDecimalPoint + lpFraction; return (LPCTSTR)str; } /* static */ BOOL CINumber::ConvertStringToLong( IN LPCTSTR lpsrc, OUT LONG & lValue ) /*++ Routine Description: Convert string to long integer. 1000 Separators will be treated correctly. Parameters: LPCTSTR lpsrc Source string LONG & lValue Value to convert to. Will be 0 in case of error Return Value: TRUE for success, FALSE for failure. --*/ { CString strNumber(lpsrc); LONG lBase = 1L; BOOL fNegative = FALSE; lValue = 0L; // // Empty strings are invalid // if (strNumber.IsEmpty()) { return FALSE; } // // Check for negative sign (at the end only) // if (strNumber[0] == _T('-')) { fNegative = TRUE; } strNumber.MakeReverse(); // // Strip negative sign // if (fNegative) { strNumber.ReleaseBuffer(strNumber.GetLength()-1); } // // Make sure the 1000 separator is only 1 char. See note below // ASSERT(CINumber::_pstrThousandSeparator->GetLength() == 1); for (int i = 0; i < strNumber.GetLength(); ++i) { if ((strNumber[i] >= _T('0')) && (strNumber[i] <= _T('9'))) { LONG lDigit = (LONG)(strNumber[i] - _T('0')); if (lDigit != 0L) { LONG lOldValue = lValue; LONG lDelta = (lDigit * lBase); if (lDelta / lDigit != lBase) { TRACEEOLID("Overflow!"); lValue = 0x7fffffff; return FALSE; } lValue += lDelta; if (lValue - lDelta != lOldValue) { TRACEEOLID("Overflow!"); lValue = 0x7fffffff; return FALSE; } } lBase *= 10L; } // // It's not a digit, maybe a thousand separator? // CAVEAT: If a thousand separator of more than // one character is used, this won't work. // else if ((strNumber[i] != (*CINumber::_pstrThousandSeparator)[0]) || (i != 3) && (i != 7) && (i != 11)) { // // This is just invalid, since it is not a thousand // separator in the proper location, nor a negative // sign. // TRACEEOLID("Invalid character " << (BYTE)strNumber[i] << " encountered"); return FALSE; } } if (fNegative) { lValue = -lValue; } return TRUE; } /* static */ BOOL CINumber::ConvertStringToFloat( IN LPCTSTR lpsrc, OUT double & flValue ) /*++ Routine Description: Convert fully decorated floating point string to double Parameters: LPCTSTR lpsrc Source string double & flValue float value generated from string Return Value: TRUE for success, FALSE for failure --*/ { CString strNumber(lpsrc); // // This only works if the decimal point is a // single character // ASSERT(CINumber::_pstrDecimalPoint->GetLength() == 1); // // Strip off the > 0 part // LONG lFraction = 0; int nPoint = strNumber.ReverseFind((*CINumber::_pstrDecimalPoint)[0]); if (nPoint >= 0) { // // Convert fractional part // LPCTSTR lpszFraction = (LPCTSTR)strNumber + ++nPoint; lFraction = ::_ttol(lpszFraction); strNumber.ReleaseBuffer(--nPoint); } // // Convert integer part // LONG lInteger; if (ConvertStringToLong(strNumber, lInteger)) { flValue = CINumber::BuildFloat(lInteger, lFraction); return TRUE; } return FALSE; } CILong::CILong() /*++ Routine Description: Constructor without arguments Parameters: None. Return Value: N/A --*/ : m_lValue(0L) { } CILong::CILong( IN LONG lValue ) /*++ Routine Description: Constructor taking LONG argument Parameters: LONG lValue Value to be set Return Value: N/A --*/ : m_lValue(lValue) { } CILong::CILong( IN LPCTSTR lpszValue ) /*++ Routine Description: Constructor taking string argument Parameters: LPCTSTR lpszValue String number Return Value: N/A --*/ { ConvertStringToLong(lpszValue, m_lValue); } CILong & CILong::operator =( IN LONG lValue ) /*++ Routine Description: Assignment operator taking long value Parameters: LONG lValue Value to be set Return Value: this object --*/ { m_lValue = lValue; return *this; } CILong & CILong::operator =( IN LPCTSTR lpszValue ) /*++ Routine Description: Assignment operator taking string value Parameters: LPCTSTR lpszValue String number Return Value: this object --*/ { ConvertStringToLong(lpszValue, m_lValue); return *this; } // // Arithmetic Shorthand operators // // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< CILong & CILong::operator +=( IN const LONG lValue ) { m_lValue += lValue; return *this; } // // As above // CILong & CILong::operator +=( IN const LPCTSTR lpszValue ) { LONG lValue; ConvertStringToLong(lpszValue, lValue); m_lValue += lValue; return *this; } // // As above // CILong & CILong::operator +=( IN const CILong& value ) { m_lValue += value.m_lValue; return *this; } // // As above // CILong & CILong::operator -=( IN const LONG lValue ) { m_lValue -= lValue; return *this; } // // As above // CILong & CILong::operator -=( IN const LPCTSTR lpszValue ) { LONG lValue; ConvertStringToLong(lpszValue, lValue); m_lValue -= lValue; return *this; } // // As above // CILong & CILong::operator -=( IN const CILong & value ) { m_lValue -= value.m_lValue; return *this; } // // As above // CILong & CILong::operator *=( IN const LONG lValue ) { m_lValue *= lValue; return *this; } // // As above // CILong & CILong::operator *=( IN const LPCTSTR lpszValue ) { LONG lValue; ConvertStringToLong(lpszValue, lValue); m_lValue *= lValue; return *this; } // // As above // CILong & CILong::operator *=( IN const CILong & value ) { m_lValue *= value.m_lValue; return *this; } // // As above // CILong & CILong::operator /=( IN const LONG lValue ) { m_lValue /= lValue; return *this; } // // As above // CILong & CILong::operator /=( IN const LPCTSTR lpszValue ) { LONG lValue; ConvertStringToLong(lpszValue, lValue); if (lValue != 0) { m_lValue /= lValue; } return *this; } // // As above // CILong & CILong::operator /=( IN const CILong& value ) { m_lValue /= value.m_lValue; return *this; }