windows-nt/Source/XPSP1/NT/inetsrv/iis/admin/common/strfn.cpp
2020-09-26 16:20:57 +08:00

2703 lines
45 KiB
C++

/*++
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 <pudebug.h>
#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; }
//
// 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.
--*/
{
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.
&& _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
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 _tcsnccmp(strDirPath, lpszDevice, lstrlen(lpszDevice)) == 0;
}
BOOL
PathIsValid(LPCTSTR path)
{
LPCTSTR p = path;
BOOL rc = TRUE;
if (p == NULL || *p == 0)
return FALSE;
while (*p != 0)
{
switch (*p)
{
case TEXT('|'):
case TEXT('>'):
case TEXT('<'):
case TEXT('/'):
case TEXT('?'):
case TEXT('*'):
// case TEXT(';'):
// case TEXT(','):
case TEXT('"'):
rc = FALSE;
break;
default:
if (*p < TEXT(' '))
{
rc = FALSE;
}
break;
}
if (!rc)
{
break;
}
p++;
}
return rc;
}
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(':'));
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;
}