|
|
/*--------------------------------------------------------------------------*
* * Microsoft Windows * Copyright (C) Microsoft Corporation, 1999 - 1999 * * File: xmlfile.cpp * * Contents: Implements extracting console icon from XML file * * History: 17-Dec-99 audriusz Created * *--------------------------------------------------------------------------*/
#include "stdafx.h"
#include "shlobj.h"
#include "Extract.h"
#include "base64.h"
#include "xmlfile.h"
#include "strings.h"
//---------------------------------------------------------------------------
// static (private) implementation helpers used thru this file
//---------------------------------------------------------------------------
static bool FindStringInData( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR pstrKey); static HRESULT DecodeBase64Fragment( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, HGLOBAL& hgResult); static HRESULT FindAndReadIconData(LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR strIconName, HGLOBAL& hglobIcon); static HRESULT LoadIconFromHGlobal(HGLOBAL hData, HICON& hIcon); static HRESULT ValidateXMLDocument(LPCSTR &pFileData, DWORD &dwLen, int *piBytesPerEnglishChar = NULL);
// following function is a friend of class CXMLPersistableIcon. If renaming, take this into accnt
static HRESULT LoadIconFromXMLData(LPCSTR pFileData, DWORD dwLen, CPersistableIcon &persistableIcon);
/***************************************************************************\
* * FUNCTION: FindStringInData * * PURPOSE: This function locates the string in provided data * NOTE - it matches first byte only (codepage of UNICODE string is ignored) * * PARAMETERS: * LPCSTR &pstrSource - [in/out] - data to search thru / possition * of the first char following the found match * int nBytesPerChar - [in] - width of the character * ( only the first byte of each character will be examined ) * DWORD &dwCharsLeft - [in/out] - init. data len / data left after matching string * LPCSTR pstrKey - [in] - substring to search * * RETURNS: * bool - true if succeeded * \***************************************************************************/
// Following sample illustrates algorithm used for the search.
// we will try to locate "Console" in the string "Microsoft Management Console"
//------------------------------------------------
// Standard search (a la strstr)
//------------------------------------------------
// 1.
// Microsoft Management Console
// Console <- comparing (fails - move to the next char)
// 2.
// Microsoft Management Console
// Console <- comparing (fails - move to the next char)
// ------------------------ (19 steps skipped)
// 22.
// Console <- comparing (succeeds)
//------------------------------------------------
// More inteligent search
//------------------------------------------------
// 1. ! <- last char in searched seq
// Microsoft Management Console
// Console <- comparing (fails - last char in searched seq is 'o';
// and 'o' is 3rd character from the end in the key;
// so we can advance by 2 chars to match it)
// 2. ! <- matching 'o' to last 'o' in the key
// Microsoft Management Console
// Console <- comparing (fails - last char in searched seq is 't';
// 't' is not in the key
// so we can advance by key length (7 chars) to skip it)
// 3. ! <- pos following the last char in searched seq
// Microsoft Management Console
// Console <- comparing (fails - last char in searched seq is 'e';
// 'e' is last character in the key;
// we still can advance by key length (7 chars) to skip it)
// 4. ! <- pos following the last char in searched seq
// Microsoft Management Console
// Console <- comparing (fails)
// 5. ! <- match point
// Microsoft Management Console
// Console <- comparing (fails)
// 6. ! <- match point
// Microsoft Management Console
// Console <- comparing (succeeds)
//------------------------------------------------
static bool FindStringInData( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR pstrKey) { typedef unsigned short KeyLen_t; static KeyLen_t nKeyDist[256]; // static - to keep stack small
// calculate the key length
DWORD dwKeyLen = strlen(pstrKey);
// test for empty search key
if (!dwKeyLen) return true; // we always match empty strings
// test for longer search key than data provided
if (dwKeyLen > dwCharsLeft) return false; // we'll never find longer substrings than the source
// key length var size is not too big to minimize tho lookup table size
KeyLen_t nKeyLen = (KeyLen_t)dwKeyLen;
// recheck here if the key isn't too long
if ((DWORD)nKeyLen != dwKeyLen) // key len does not fit to our variable -
return false; // we do not deel with such a long keys
// form the table holding minimal character distance from the end of pstrKey
// It is used for increasing search speed:
// When key does not match at current location, [instead of trying one location ahead,]
// algorythm checks the last character in sequence tested with a key (data[keylen-1]).
// now we check how far this character may be from the end of the key - we will have
// distance of all key length in case character is not a part of the key.
// we can safelly advance by that much. Sometimes we'll be positioning the key at whole
// key_len offsets from previous test position, sometimes less - depending on data.
// initialize the table. The distance is keylen value for all characters not existing in the key
for (unsigned i = 0; i < sizeof(nKeyDist)/sizeof(nKeyDist[0]); ++i) nKeyDist[i] = nKeyLen;
// now set minimal distance for characters in the key.
// Note, that the last character is not included intensionally - to make
// distance to it equal to whole key length
for (i = 0; i < nKeyLen - 1; ++i) nKeyDist[pstrKey[i]] = nKeyLen - (KeyLen_t)i - 1;
// we are done with initialization. Time for real work.
LPCSTR p = pstrSource; // to speed it up: we use local variables
DWORD dwLeft = dwCharsLeft;
while ( 1 ) { // set the pointers to start of inspected sequence
LPCSTR ke = pstrKey; // pointer to evaluating key char
LPCSTR pe = p; // pointer to evaluating source char
// try to match all characters in the key
KeyLen_t nToMatch = nKeyLen; while ( *pe == *ke ) { --nToMatch; pe += nBytesPerChar; ++ke;
// inspect if there still are some chars to match
if (!nToMatch) { // we return the possitive answer here
// change the reference parameters accordingly
// (pointing right after the string found)
pstrSource = pe; dwCharsLeft = dwLeft - nKeyLen; return true; } }
// chLastChar is used as an index
// need to cast the char to unsigned char - else it will
// not work correctly for values over 127
// NTRAID#NTBUG9-185761-2000/09/18 AUDRIUSZ
BYTE chLastChar = p[(nKeyLen - 1) * nBytesPerChar]; // the last char from evaluated source range
// the key couldn't be found at the position we inspected.
// we can advance source pointer as far as we can match
// the position of the last character to any entry in the key
// or whole key length else.
// We have a table built for that
const KeyLen_t nToSkip = nKeyDist[chLastChar];
if ((DWORD)nToSkip + (DWORD)nKeyLen >= dwLeft) return false; // gone too far... ( couldn't find the match )
p += (nToSkip * nBytesPerChar); dwLeft -= nToSkip; }
// we will not get here anyway...
return false; }
/***************************************************************************\
* * METHOD: DecodeBase64Fragment * * PURPOSE: decodes base64 data fragment pointed by arguments * * PARAMETERS: * LPCSTR &pstrSource - [in/out] - data to decode / possition * of the first char following the decoded data * int nBytesPerChar - [in] - width of the character * ( only the first byte of each character will be examined ) * DWORD &dwCharsLeft - [in/out] - init. data len / data left after skipping converted * HGLOBAL& hgResult - decoded data * * RETURNS: * HRESULT - result code * \***************************************************************************/ static HRESULT DecodeBase64Fragment( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, HGLOBAL& hgResult) { HRESULT hrStatus = S_OK; LPCSTR p = pstrSource; DWORD dwLeft = dwCharsLeft; const size_t ICON_ALLOCATION_LEN = 8*1024; // big enough to have 1 allocation in most cases
LPBYTE pDynamicBuffer = NULL; LPBYTE pConversionBuffer = NULL; size_t nCharsInDynamicBuffer = 0; size_t nDynamicBufferCapacity = 0; HGLOBAL hGlobAlloc = NULL; ASSERT(hResult == NULL);
static base64_table conv;
// convert until done or end is found
while (1) { // standard conversion. converts 4 chars (6bit each) to 3 bytes
BYTE inp[4]; memset(&inp, 0 ,sizeof(inp)); // collect 4 characters for conversion, if possible.
for (int nChars = 0; nChars < 4 && dwLeft && *p != '<' && *p != '='; --dwLeft) { BYTE bt = conv.map2six(*p); p += nBytesPerChar; if (bt != 0xff) inp[nChars++] = bt; }
// if nothing to convert - we are done
if (!nChars) break;
// make sure we have enough storage for result
if (nChars + nCharsInDynamicBuffer > nDynamicBufferCapacity) { // need to extend the dynamic buffer
LPBYTE pnewBuffer = (LPBYTE)realloc(pDynamicBuffer, nDynamicBufferCapacity + ICON_ALLOCATION_LEN);
if (!pnewBuffer) { hrStatus = E_OUTOFMEMORY; goto ON_ERROR; } // assign new pointer
pDynamicBuffer = pnewBuffer; nDynamicBufferCapacity += ICON_ALLOCATION_LEN;
pConversionBuffer = &pDynamicBuffer[nCharsInDynamicBuffer]; }
// decode and put the staff to the memory;
int nCharsPut = conv.decode4(inp, nChars, pConversionBuffer); // update count & current pointer
nCharsInDynamicBuffer += nCharsPut; pConversionBuffer += nCharsPut; }
// allocate the buffer and store the result data
// The same buffer is not reused for conversion, because
// it's assumed to be saffer to load icon from stream, containing only
// as much data as required ( we would have larger buffer, containing some
// uninitialized data at the end if returning buffer used for conversion)
hGlobAlloc = GlobalAlloc(GMEM_MOVEABLE, nCharsInDynamicBuffer); if (hGlobAlloc == NULL) { hrStatus = E_OUTOFMEMORY; goto ON_ERROR; }
// if we have characters, copy them to result
if (nCharsInDynamicBuffer) { LPVOID pResultStorage = GlobalLock(hGlobAlloc); if (pResultStorage == NULL) { hrStatus = E_OUTOFMEMORY; goto ON_ERROR; } memcpy(pResultStorage, pDynamicBuffer, nCharsInDynamicBuffer); GlobalUnlock(hGlobAlloc); }
// assign the memory handle to caller
hgResult = hGlobAlloc; hGlobAlloc = NULL; // assign null to avoid releasing it
// adjust poiters to start from where we finished the next time
pstrSource = p; dwCharsLeft = dwLeft;
hrStatus = S_OK; ON_ERROR: // note: ok result falls thru as well
if (hGlobAlloc) GlobalFree(hGlobAlloc); if (pDynamicBuffer) free(pDynamicBuffer);
return hrStatus; }
/***************************************************************************\
* * METHOD: FindAndReadIconData * * PURPOSE: Function locates Icon data in the xml file data and loads it into HGLOBAL * * PARAMETERS: * LPCSTR &pstrSource - [in/out] - data to look thru / possition * of the first char following the decoded icon data * int nBytesPerChar - [in] - width of the character * ( only the first byte of each character will be examined ) * DWORD &dwCharsLeft - [in/out] - init. data len / data left after skipping decoded * LPCSTR strIconName - [in] name of Icon to locate * - NOTE: it assumes icon data to be a base64-encoded stream, saved * as contents of XML element, having IconName as its attribute * HGLOBAL& hglobIcon - [out] - memory block containing icon data * * RETURNS: * HRESULT - result code * \***************************************************************************/ static HRESULT FindAndReadIconData(LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR strIconName, HGLOBAL& hglobIcon) { ASSERT(hglobIcon == NULL); // we do not free data here, pass null handler!
// make local vars for efficiency
DWORD dwLen = dwCharsLeft; LPCSTR pstrData = pstrSource;
// locate the string with the name of icon (assume it's unique enough)
const bool bIconFound = FindStringInData( pstrData, nBytesPerChar, dwLen, strIconName); if (!bIconFound) return E_FAIL;
// now locate the end of tag '>' ( start of the contents )
const bool bStartFound = FindStringInData( pstrData, nBytesPerChar, dwLen, ">" ); if (!bStartFound) return E_FAIL;
HRESULT hr = DecodeBase64Fragment( pstrData, nBytesPerChar, dwLen, hglobIcon); if (FAILED(hr)) return hr;
// update pointers to start from where we finished the next time
dwCharsLeft = dwLen; pstrSource = pstrData;
return S_OK; }
/***************************************************************************\
* * METHOD: LoadIconFromHGlobal * * PURPOSE: Function extracts HICON from stream contained in HGLOBAL * * PARAMETERS: * HGLOBAL hData [in] - data to load from * HICON& hIcon [out] - read icon * * RETURNS: * HRESULT - result code * \***************************************************************************/ static HRESULT LoadIconFromHGlobal(HGLOBAL hData, HICON& hIcon) { HRESULT hr = S_OK;
// create the stream
IStreamPtr spStream; hr = CreateStreamOnHGlobal(hData, FALSE/*do not release*/, &spStream); if (FAILED(hr)) return hr;
// read the icon as image list
HIMAGELIST himl = ImageList_Read (spStream);
if (!himl) return E_FAIL;
// retrieve icon from image list
hIcon = ImageList_GetIcon (himl, 0, ILD_NORMAL);
// destroy image list (no longer need it)
ImageList_Destroy (himl); return S_OK; }
/***************************************************************************\
* * METHOD: LoadIconFromXMLData * * PURPOSE: Loads icon from memory containing file data of XML document * * PARAMETERS: * LPCSTR pFileData - file data suspected to contain XML document * DWORD dwLen - the len of input data * CPersistableIcon &persistableIcon - Icon to initialize upon successful loading * * RETURNS: * HRESULT - result code (S_OK - icon loaded, error code else) * \***************************************************************************/ static HRESULT LoadIconFromXMLData(LPCSTR pFileData, DWORD dwLen, CPersistableIcon &persistableIcon) { HRESULT hr = S_OK; int nBytesPerChar = 0;
// check if we recognize the document contents
hr = ValidateXMLDocument(pFileData,dwLen, &nBytesPerChar); if (hr != S_OK) // hr == S_FALSE means format is not recognized
return E_FAIL;
// Get required keywords.
USES_CONVERSION; LPCSTR lpcstrLarge = T2CA(XML_ATTR_CONSOLE_ICON_LARGE); LPCSTR lpcstrSmall = T2CA(XML_ATTR_CONSOLE_ICON_SMALL); HICON hLargeIcon = NULL; HICON hSmallIcon = NULL;
// try to read large icon first
HGLOBAL hgLargeIcon = NULL; hr = FindAndReadIconData(pFileData, nBytesPerChar, dwLen, lpcstrLarge, hgLargeIcon ); if (FAILED(hr)) return hr;
// try to read small icon ( look behind the large one - it should be there!)
HGLOBAL hgSmallIcon = NULL; hr = FindAndReadIconData( pFileData, nBytesPerChar, dwLen, lpcstrSmall, hgSmallIcon ); if (FAILED(hr)) goto ON_ERROR;
// do get the handles of the icons!
hr = LoadIconFromHGlobal(hgLargeIcon, hLargeIcon); if (FAILED(hr)) goto ON_ERROR;
hr = LoadIconFromHGlobal(hgSmallIcon, hSmallIcon); if (FAILED(hr)) goto ON_ERROR;
persistableIcon.m_icon32.Attach (hLargeIcon); persistableIcon.m_icon16.Attach (hSmallIcon);
// done!
hr = S_OK;
ON_ERROR: if (hLargeIcon && FAILED(hr)) DestroyIcon(hLargeIcon); if (hSmallIcon && FAILED(hr)) DestroyIcon(hSmallIcon); if (hgLargeIcon) GlobalFree(hgLargeIcon); return hr; }
/***************************************************************************\
* * METHOD: ExtractIconFromXMLFile * * PURPOSE: Loads icon from file containing XML document * * PARAMETERS: * LPCTSTR lpstrFileName - name of file to inspect * CPersistableIcon &persistableIcon - Icon to initialize upon successful loading * * RETURNS: * HRESULT - result code * \***************************************************************************/ HRESULT ExtractIconFromXMLFile(LPCTSTR lpstrFileName, CPersistableIcon &persistableIcon) { HRESULT hrResult = S_OK;
// open the file
HANDLE hFile = CreateFile(lpstrFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) return hrResult = HRESULT_FROM_WIN32(GetLastError());
// map data into virtual memory
HANDLE hMapping = CreateFileMapping(hFile, NULL/*sec*/, PAGE_READONLY, 0/*sizeHi*/, 0/*sizeLo*/, NULL/*szname*/);
if (hMapping == NULL) { hrResult = HRESULT_FROM_WIN32(GetLastError()); CloseHandle(hFile); return hrResult; }
// get pointer to physical memory
LPVOID pData = MapViewOfFile(hMapping, FILE_MAP_READ, 0/*offHi*/, 0/*offLo*/, 0/*len*/);
if (pData) { // we are sure here the sizeHi is zero. mapping should fail else
DWORD dwLen = GetFileSize(hFile, NULL/*pSizeHi*/);
// try to load icon from mapped data
hrResult = LoadIconFromXMLData((LPCSTR)pData, dwLen, persistableIcon);
// we do not need a view any more
UnmapViewOfFile(pData); pData = NULL; // fall thru to release handles before return
} else // getting the view failed
{ hrResult = HRESULT_FROM_WIN32(GetLastError()); // fall thru to release handles before return
}
CloseHandle(hMapping); CloseHandle(hFile); return hrResult; }
/***************************************************************************\
* * METHOD: ValidateXMLDocument * * PURPOSE: Validates XML document loaded into memory * NOTE: it's rather __VERY__ weak inspection. it only checks if doc starts with '<' * * PARAMETERS: * LPCSTR &pFileData - [in/out] - data to look thru / start of xml documet contents * DWORD &dwLen - [in/out] - init. data len / data left after skipping header * int *piBytesPerEnglishChar - [out, optional] - bytes occupied by english character * * RETURNS: * HRESULT - (S_FALSE - data does not qualify for XML document) * \***************************************************************************/ static HRESULT ValidateXMLDocument(LPCSTR &pFileData, DWORD &dwLen, int *piBytesPerEnglishChar /*= NULL*/) { // default to ansi when not sure
int nBytesPerChar = 1;
if (dwLen >= 2) { // raw UNICODE big endian ?
if ((unsigned char)pFileData[1] == 0xff && (unsigned char)pFileData[0] == 0xfe) { // to maintain simplicity of the code, we will treat this like little endian.
// we just position file pointer incorrectly.
// since everything we are intersted:
// - is in page 0 (xml tags and base 64)
// - never is at the end of file ( closing tags expected )
// - we do not care about the page of any data
// :we can mix the page codes of the elements and pretend dealing w/ little endian
pFileData += 3; // skip UNICODE signature and first page number
dwLen -= 3; dwLen /= 2; // we count characters - seems like we have less of them
nBytesPerChar = 2; } // raw UNICODE little endian ?
else if ((unsigned char)pFileData[0] == 0xff && (unsigned char)pFileData[1] == 0xfe) { pFileData += 2; // skip UNICODE signature
dwLen -= 2; dwLen /= 2; // we count characters - seems like we have less of them
nBytesPerChar = 2; } // compressed UNICODE (UTF 8) ?
else if (dwLen >= 2 && (unsigned char)pFileData[0] == 0xef && (unsigned char)pFileData[1] == 0xbb && (unsigned char)pFileData[2] == 0xbf) { //just skip signature and treat it as ANSI
pFileData += 3; // skip UNICODE signature
dwLen -= 3; nBytesPerChar = 1; } }
// skip whitespaces
char ch; while (dwLen && (((ch = *pFileData)==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r'))) { pFileData += nBytesPerChar; --dwLen; }
// check if we have a valid xml file (it should open with '<')
if (!dwLen || *pFileData != '<') return S_FALSE;
if (piBytesPerEnglishChar) *piBytesPerEnglishChar = nBytesPerChar; return S_OK; }
|