501 lines
16 KiB
C++
501 lines
16 KiB
C++
|
/*--
|
||
|
Copyright (c) 1995-1998 Microsoft Corporation
|
||
|
Module Name: filter.cpp
|
||
|
Author: John Spaith
|
||
|
Abstract: ISAPI Filter call back functions
|
||
|
--*/
|
||
|
|
||
|
// BUGBUG - don't support advanced header management on SF_NOTIFY_SEND_RESPONSE.
|
||
|
// IIS allows response headers to be deleted or set to different values after
|
||
|
// they are set with a call to SetHeader or AddHeader.
|
||
|
// Fix: None. This takes +300 lines of code, doesn't add much. Instead we
|
||
|
// dump all header data in a buffer.
|
||
|
#include "pch.h"
|
||
|
#pragma hdrstop
|
||
|
|
||
|
|
||
|
#include "httpd.h"
|
||
|
|
||
|
|
||
|
// Allocates and returns a buffer size cbSize. This will be deleted at the
|
||
|
// end of the session.
|
||
|
|
||
|
// The MSDN docs on this claim this returns a BOOL. Looking in httpfilt.h
|
||
|
// shows that the actual implementation is a VOID *.
|
||
|
|
||
|
VOID* WINAPI AllocMem(PHTTP_FILTER_CONTEXT pfc, DWORD cbSize, DWORD dwReserved)
|
||
|
{
|
||
|
CHECKPFC(pfc);
|
||
|
// NOTE: alloc mem isn't directly related to connection, don't check m_pFINfo->fAccept like
|
||
|
// the other filters do
|
||
|
|
||
|
return((CHttpRequest*)pfc->ServerContext)->m_pFInfo->AllocMem(cbSize,dwReserved);
|
||
|
}
|
||
|
|
||
|
VOID* CFilterInfo::AllocMem(DWORD cbSize, DWORD dwReserved)
|
||
|
{
|
||
|
if (cbSize == 0)
|
||
|
return NULL;
|
||
|
|
||
|
if (NULL == m_pAllocMem)
|
||
|
{
|
||
|
m_pAllocMem = MyRgAllocZ(PVOID,VALUE_GROW_SIZE);
|
||
|
if (! (m_pAllocMem))
|
||
|
{
|
||
|
TraceTag(ttidWebServer, "CFilterInfo::AllocMem failed on inital alloc, GLE=%d",GetLastError());
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
else if (m_nAllocBlocks % VALUE_GROW_SIZE == 0)
|
||
|
{
|
||
|
m_pAllocMem = MyRgReAlloc(PVOID,m_pAllocMem,m_nAllocBlocks,m_nAllocBlocks + VALUE_GROW_SIZE);
|
||
|
if ( !m_pAllocMem)
|
||
|
{
|
||
|
TraceTag(ttidWebServer, "CFilterInfo::AllocMem failed on re-allocing for block %d, GLE=%d",m_nAllocBlocks+1,GetLastError());
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (! (m_pAllocMem[m_nAllocBlocks] = MyRgAllocNZ(PVOID,cbSize)))
|
||
|
{
|
||
|
TraceTag(ttidWebServer, "CFilterInfo::AllocMem failed on allocating block %d, GLE=%d",m_nAllocBlocks+1,GetLastError());
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
m_nAllocBlocks++;
|
||
|
return m_pAllocMem[m_nAllocBlocks-1];
|
||
|
}
|
||
|
|
||
|
|
||
|
void CFilterInfo::FreeAllocMem()
|
||
|
{
|
||
|
DWORD dwI;
|
||
|
|
||
|
if (0 == m_nAllocBlocks || ! (m_pAllocMem))
|
||
|
return;
|
||
|
|
||
|
for (dwI = 0; dwI < m_nAllocBlocks; dwI++)
|
||
|
{
|
||
|
MyFree(m_pAllocMem[dwI]);
|
||
|
}
|
||
|
MyFree(m_pAllocMem);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Adds httpd headers.
|
||
|
// In effect this is identical to a call to SetHeader or AddHeader except that
|
||
|
// the user formats the whole string (name and value) before caalling
|
||
|
// AddResponseHeaders, in Add/Set Header they come in 2 seperate fields.
|
||
|
|
||
|
BOOL WINAPI AddResponseHeaders(PHTTP_FILTER_CONTEXT pfc,LPSTR lpszHeaders,DWORD dwReserved)
|
||
|
{
|
||
|
CHECKPFC(pfc);
|
||
|
CHECKPTR(lpszHeaders);
|
||
|
CHECKFILTER(pfc);
|
||
|
|
||
|
if (dwReserved != 0)
|
||
|
{
|
||
|
SetLastError(ERROR_INVALID_PARAMETER);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return((CHttpRequest*)pfc->ServerContext)->AddResponseHeaders(lpszHeaders,dwReserved);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
BOOL CHttpRequest::AddResponseHeaders(LPSTR lpszHeaders,DWORD dwReserved)
|
||
|
{
|
||
|
// Can't set response headers on 0.9 version. Err code is like IIS.
|
||
|
if (m_dwVersion <= MAKELONG(9, 0))
|
||
|
{
|
||
|
SetLastError(ERROR_NOT_SUPPORTED);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
// According to MSDN, it's invalid to use this fcn during or after
|
||
|
// SEND_RESPONSE event. However, IIS doesn't report an error if
|
||
|
// this is called when it shouldn't be, so neither do we.
|
||
|
if ( m_pFInfo->m_dwSFEvent & (SF_NOTIFY_END_OF_NET_SESSION | SF_NOTIFY_END_OF_REQUEST |
|
||
|
SF_NOTIFY_LOG | SF_NOTIFY_SEND_RAW_DATA |
|
||
|
SF_NOTIFY_SEND_RESPONSE))
|
||
|
return TRUE;
|
||
|
|
||
|
// Note: We don't use CBuffer::AddHeaders because it does formatting, we assume
|
||
|
// here filter alreadf formatted everything correctly.
|
||
|
|
||
|
return m_bufRespHeaders.AppendData(lpszHeaders, strlen(lpszHeaders));
|
||
|
}
|
||
|
|
||
|
BOOL WINAPI GetServerVariable(PHTTP_FILTER_CONTEXT pfc, PSTR psz, PVOID pv, PDWORD pdw)
|
||
|
{
|
||
|
CHECKPFC(pfc);
|
||
|
CHECKPTRS3(psz, pv, pdw);
|
||
|
|
||
|
// We don't check m_fFAccept here because this function is read only,
|
||
|
// doesn't try and do anything across the network. Like IIS.
|
||
|
return((CHttpRequest*)pfc->ServerContext)->GetServerVariable(psz, pv, pdw, TRUE);
|
||
|
}
|
||
|
|
||
|
BOOL WINAPI WriteClient(PHTTP_FILTER_CONTEXT pfc, PVOID pv, PDWORD pdw, DWORD dwFlags)
|
||
|
{
|
||
|
CHECKPFC(pfc);
|
||
|
CHECKPTRS2(pv, pdw);
|
||
|
// CHECKFILTER(pfc); // IIS always accepts WriteClient, even if filter has returned an error at some point.
|
||
|
|
||
|
if (dwFlags != 0)
|
||
|
{
|
||
|
SetLastError(ERROR_INVALID_PARAMETER);
|
||
|
return FALSE;
|
||
|
}
|
||
|
return((CHttpRequest*)pfc->ServerContext)->WriteClient(pv,pdw,TRUE);
|
||
|
}
|
||
|
|
||
|
// Misc Server features. See MSDN for full docs.
|
||
|
BOOL WINAPI ServerSupportFunction(PHTTP_FILTER_CONTEXT pfc, enum SF_REQ_TYPE sfReq,
|
||
|
PVOID pData, ULONG_PTR ul1, ULONG_PTR ul2)
|
||
|
{
|
||
|
CHECKPFC(pfc);
|
||
|
CHECKFILTER(pfc);
|
||
|
|
||
|
return((CHttpRequest*)pfc->ServerContext)->ServerSupportFunction(sfReq, pData, ul1, ul2);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
BOOL CHttpRequest::ServerSupportFunction(enum SF_REQ_TYPE sfReq,PVOID pData,
|
||
|
ULONG_PTR ul1, ULONG_PTR ul2)
|
||
|
{
|
||
|
switch (sfReq)
|
||
|
{
|
||
|
case SF_REQ_ADD_HEADERS_ON_DENIAL:
|
||
|
{
|
||
|
return MyStrCatA(&m_pFInfo->m_pszDenyHeader,(PSTR) pData);
|
||
|
}
|
||
|
|
||
|
case SF_REQ_DISABLE_NOTIFICATIONS:
|
||
|
{
|
||
|
// u11 has data as to which flags to deactivate for this session,
|
||
|
|
||
|
// For example, setting u11 = SF_NOTIFY_SEND_RAW_DATA | SF_NOTIFY_LOG
|
||
|
// would disable notifications to the filter that called this fcn for
|
||
|
// those events on this request only (per request, not per session!).
|
||
|
// These values are reset to 1's at the end of each request in CHttpRequest::ReInit
|
||
|
|
||
|
m_pFInfo->m_pdwEnable[m_pFInfo->m_iFIndex] &= (m_pFInfo->m_pdwEnable[m_pFInfo->m_iFIndex] ^ ul1);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
case SF_REQ_SEND_RESPONSE_HEADER:
|
||
|
{
|
||
|
// no Connection header...let ISAPI send one if it wants
|
||
|
CHttpResponse resp(m_socket, STATUS_OK, CONN_CLOSE,this);
|
||
|
m_rs = STATUS_OK;
|
||
|
// no body, default or otherwise (leave that to the ISAPI), but add default headers
|
||
|
resp.SendResponse((PSTR) ul1, (PSTR) pData, TRUE);
|
||
|
|
||
|
|
||
|
// The reasons for doing this are documented in FilterNoResponse()
|
||
|
// Note: We don't check this sent headers here because IIS doesn't,
|
||
|
// so it's possible to make multiple calls with this option even though it
|
||
|
// doesn't make that much sense for a filter to do so.
|
||
|
|
||
|
m_pFInfo->m_fSentHeaders = TRUE;
|
||
|
m_fKeepAlive = FALSE;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
case SF_REQ_SET_NEXT_READ_SIZE:
|
||
|
{
|
||
|
m_pFInfo->m_dwNextReadSize = (DWORD)ul1;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
// BUGBUG -- unsupported flags.
|
||
|
// case SF_REQ_NORMALIZE_URL: // TBD
|
||
|
// case SF_REQ_GET_CONNID: // IIS doesn't support in versions 4+
|
||
|
// case SF_REQ_GET_PROPERTY: // Relates to meta database IIS uses but we don't
|
||
|
// case SF_REQ_SET_PROXY_INFO: // No proxy stuff in CE
|
||
|
default:
|
||
|
{
|
||
|
TraceTag(ttidWebServer, "Unsupported or invald ServerSupportFcn request = 0x%08x",sfReq);
|
||
|
SetLastError(ERROR_INVALID_PARAMETER);
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//***************************************************************************
|
||
|
// Extended filter header options
|
||
|
//
|
||
|
// These fcns allow the filter to perform more advanced header fncs automatically,
|
||
|
// On PREPROC_HEADERS event, it's possible for the filter to change CHttpRequest
|
||
|
// state information through the SetHeaders and AddHeaders callback. We support
|
||
|
// changing URL, Version, If-modified-since, and method through the preproc
|
||
|
// headers calls.
|
||
|
|
||
|
// In a SEND_RESPONSE event, it's possible for the filter to modify the response
|
||
|
// headers through SetHEaders or AddHeaders.
|
||
|
|
||
|
// Unlike IIS, we don't allow the filter to delete a header once it's set, or
|
||
|
// to overwrite a header's information with new info. We only allow the header
|
||
|
// data to be appended to.
|
||
|
|
||
|
//***************************************************************************
|
||
|
|
||
|
|
||
|
BOOL WINAPI GetHeader(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszName, LPVOID lpvBuffer, LPDWORD lpdwSize)
|
||
|
{
|
||
|
CHECKPFC(pfc);
|
||
|
CHECKPTRS3(lpszName, lpvBuffer, lpdwSize);
|
||
|
CHECKFILTER(pfc);
|
||
|
|
||
|
return((CHttpRequest*)pfc->ServerContext)->GetHeader(lpszName, lpvBuffer, lpdwSize);
|
||
|
}
|
||
|
|
||
|
BOOL WINAPI SetHeader(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszName, LPSTR lpszValue)
|
||
|
{
|
||
|
CHECKPFC(pfc);
|
||
|
CHECKPTRS2(lpszName, lpszValue);
|
||
|
CHECKFILTER(pfc);
|
||
|
|
||
|
return((CHttpRequest*)pfc->ServerContext)->SetHeader(lpszName, lpszValue);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Retrieves raw header value for specified name.
|
||
|
BOOL CHttpRequest::GetHeader(LPSTR lpszName, LPVOID lpvBuffer, LPDWORD lpdwSize)
|
||
|
{
|
||
|
DEBUG_CODE_INIT;
|
||
|
BOOL ret = FALSE;
|
||
|
DWORD cbSizeNeeded;
|
||
|
char szBuf[MAXHEADERS];
|
||
|
PSTR pszRet = (PSTR)-1;
|
||
|
PSTR pszTrav = NULL;
|
||
|
PSTR pszEndOfHeaders = NULL;
|
||
|
DWORD cbName;
|
||
|
|
||
|
|
||
|
// this is the only event we allow this for. This is like IIS, but not docced in MSDN.
|
||
|
if (m_pFInfo->m_dwSFEvent != SF_NOTIFY_PREPROC_HEADERS)
|
||
|
{
|
||
|
SetLastError(ERROR_INVALID_INDEX);
|
||
|
myleave(1405);
|
||
|
}
|
||
|
|
||
|
// For method, url, and version (from simple request line) we get the data
|
||
|
// from CHttpRequest
|
||
|
|
||
|
if (0==_stricmp(lpszName, "version"))
|
||
|
{
|
||
|
// There's no sprintf in older versions of CE.
|
||
|
// sprintf(szBuf, "HTTP/%d.%d", HIWORD(m_dwVersion), LOWORD(m_dwVersion));
|
||
|
|
||
|
WriteHTTPVersion(szBuf,m_dwVersion);
|
||
|
pszRet = szBuf;
|
||
|
}
|
||
|
else if (0 == _stricmp(lpszName, "url"))
|
||
|
pszRet = m_pszURL;
|
||
|
else if (0 == _stricmp(lpszName, "method"))
|
||
|
pszRet = m_pszMethod;
|
||
|
else
|
||
|
{
|
||
|
// if it's not one of the 3 special values, we search through the raw
|
||
|
// buffer for the header name.
|
||
|
pszTrav = (PSTR) m_bufRequest.Headers();
|
||
|
pszTrav = strstr(pszTrav,cszCRLF); // skip past simple http header
|
||
|
pszEndOfHeaders = pszTrav + m_bufRequest.GetINextOut();
|
||
|
|
||
|
cbName = strlen(lpszName);
|
||
|
|
||
|
for (; pszTrav; pszTrav = strstr(pszTrav,cszCRLF))
|
||
|
{
|
||
|
pszTrav += sizeof("\r\n") - 1;
|
||
|
|
||
|
// reached end of headers, double CRLF
|
||
|
if (*pszTrav == '\r')
|
||
|
break;
|
||
|
|
||
|
// Make sure we don't walk off the end of the buffer.
|
||
|
if ((int) cbName > (pszEndOfHeaders - pszTrav))
|
||
|
break;
|
||
|
|
||
|
if (0 == _memicmp(pszTrav,lpszName,cbName))
|
||
|
{
|
||
|
pszTrav += cbName;
|
||
|
if (' ' == *pszTrav) // must be a space next for a match
|
||
|
{
|
||
|
pszRet = pszTrav + 1;
|
||
|
pszTrav = strstr(pszTrav,cszCRLF);
|
||
|
DEBUGCHK(pszTrav != NULL); // should catch improperly formatted headers in parser
|
||
|
*pszTrav = 0; // make this the end of string temporarily
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ((PSTR)(-1) == pszRet)
|
||
|
{
|
||
|
// unknown var
|
||
|
SetLastError(ERROR_INVALID_INDEX);
|
||
|
myleave(1400);
|
||
|
}
|
||
|
|
||
|
if ((cbSizeNeeded = strlen(pszRet)+1) > *lpdwSize)
|
||
|
{
|
||
|
*lpdwSize = cbSizeNeeded;
|
||
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||
|
myleave(1401);
|
||
|
}
|
||
|
memcpy(lpvBuffer, pszRet, cbSizeNeeded);
|
||
|
ret = TRUE;
|
||
|
|
||
|
done:
|
||
|
|
||
|
TraceTag(ttidWebServer, "HTTPD:GetHeader failed with variable name<<%s>>,err = %d ",lpszName,err);
|
||
|
|
||
|
if (pszTrav)
|
||
|
*pszTrav = '\r'; // reset the value
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
// BUGBUG: SetHeader and AddHeader both use SetHeader fcn, breaks IIS spec.
|
||
|
// Fix: None. On IIS for a PREPROC_HEADER event, filter can call (for instance)
|
||
|
// an AddHeader("URL","url.htm"), which will append to the end of the existing URL.
|
||
|
// In this case, if the requested URL was "foo.htm", IIS would lookup foo.htm,url.htm
|
||
|
// This isn't worth the hassle (the code to do it is below, though).
|
||
|
|
||
|
|
||
|
BOOL CHttpRequest::SetHeader(LPSTR lpszName, LPSTR lpszValue)
|
||
|
{
|
||
|
DEBUG_CODE_INIT;
|
||
|
PSTR pszNew = NULL;
|
||
|
BOOL ret = FALSE;
|
||
|
|
||
|
|
||
|
TraceTag(ttidWebServer, "Servicing SetHeader request with name<<%s>>,value<<%s>> ",lpszName,lpszValue);
|
||
|
if (0==_stricmp(lpszName, "version"))
|
||
|
{
|
||
|
SetHTTPVersion(lpszValue,&m_dwVersion);
|
||
|
ret = TRUE;
|
||
|
}
|
||
|
else if (0 == _stricmp(lpszName, "url"))
|
||
|
{
|
||
|
pszNew = MySzDupA(lpszValue);
|
||
|
if (!pszNew)
|
||
|
myleave(256);
|
||
|
|
||
|
MyFree(m_pszURL);
|
||
|
m_pszURL = pszNew;
|
||
|
ret = TRUE;
|
||
|
}
|
||
|
else if (0 == _stricmp(lpszName, "method"))
|
||
|
{
|
||
|
pszNew = MySzDupA(lpszValue);
|
||
|
if (!pszNew)
|
||
|
myleave(257);
|
||
|
|
||
|
MyFree(m_pszMethod);
|
||
|
m_pszMethod = pszNew;
|
||
|
ret = TRUE;
|
||
|
}
|
||
|
// BUGBUG - changing headers on the preproc event would require advanced
|
||
|
// header management.
|
||
|
// Fix: None. If the filter writer wants to change the state that headers
|
||
|
// set they'll have plenty of oppurtunity to do it in later events.
|
||
|
else if (m_pFInfo->m_dwSFEvent == SF_NOTIFY_PREPROC_HEADERS)
|
||
|
{
|
||
|
myretleave(TRUE,0);
|
||
|
}
|
||
|
else // custom response header, like "Content-length:" or whatever else
|
||
|
{
|
||
|
// Can't set response headers on 0.9 version. Err code is like IIS.
|
||
|
if (m_dwVersion <= MAKELONG(9, 0))
|
||
|
{
|
||
|
SetLastError(ERROR_NOT_SUPPORTED);
|
||
|
myleave(258);
|
||
|
}
|
||
|
|
||
|
ret = m_bufRespHeaders.AddHeader(lpszName,lpszValue);
|
||
|
}
|
||
|
|
||
|
|
||
|
done:
|
||
|
TraceTag(ttidWebServer, "SetHeader failed with request with name<<%s>>,value<<%s>>, GLE=%d ",lpszName,lpszValue,GetLastError());
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// This handles a special case for Filters / extensions. IF a call is made
|
||
|
// to ISAPI Extension ServerSupportFunction with HSE_REQ_MAP_URL_TO_PATH, then
|
||
|
// a filter call to SF_NOTIFY_URL_MAP is performed.
|
||
|
|
||
|
// We can't call the filter directly because SF_NOTIFY_URL_MAP usually gets
|
||
|
// path info from the CHttpRequest class, but in this case it's getting it's
|
||
|
// data from and writing out to a user buffer.
|
||
|
|
||
|
// pszBuf is the original string passed into ServerSupportFunction by the ISAPI.
|
||
|
// When the function begins it has a virtual path, on successful termination it has a physical path
|
||
|
// pdwSize is it's size
|
||
|
// wszPath is it's mapped virtual root path
|
||
|
// dwBufNeeded is the size of the buffer required to the physical path.
|
||
|
|
||
|
BOOL CHttpRequest::FilterMapURL(PSTR pszBuf, WCHAR *wszPath, DWORD *pdwSize, DWORD dwBufNeeded, PSTR pszURLEx)
|
||
|
{
|
||
|
DEBUG_CODE_INIT;
|
||
|
PSTR pszPhysicalOrg;
|
||
|
PSTR pszPhysicalNew;
|
||
|
PSTR pszVirtual = pszURLEx ? pszURLEx : pszBuf;
|
||
|
DWORD cbBufNew = dwBufNeeded;
|
||
|
BOOL ret = FALSE;
|
||
|
|
||
|
|
||
|
// Regardless of if buffer was big enough to hold the original data, we always
|
||
|
// allocate a buf for the filter. The filter may end up changing the data
|
||
|
// so it's small enough to fit in the buffer.
|
||
|
|
||
|
// Don't use MySzDupWtoA here because we alread know the length needed, MySzDupWtoA
|
||
|
// would needlessly recompute it.
|
||
|
|
||
|
if (NULL == (pszPhysicalOrg = MyRgAllocNZ(CHAR,dwBufNeeded)))
|
||
|
myleave(710);
|
||
|
MyW2A(wszPath, pszPhysicalOrg, dwBufNeeded);
|
||
|
pszPhysicalNew = pszPhysicalOrg; // Keep a copy of pointer for freeing, CallFilter may modify it
|
||
|
|
||
|
if ( !CallFilter(SF_NOTIFY_URL_MAP,&pszVirtual,(int*) &cbBufNew,&pszPhysicalNew))
|
||
|
myleave(711);
|
||
|
|
||
|
// Buffer isn't big enough
|
||
|
if (*pdwSize < cbBufNew)
|
||
|
{
|
||
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||
|
myleave(712);
|
||
|
}
|
||
|
|
||
|
// Copy changes over. pszPhysicalNew will be filter set or will be
|
||
|
// the original wszPath converted to ASCII without filter.
|
||
|
if (NULL != pszPhysicalNew)
|
||
|
{
|
||
|
memcpy(pszBuf,pszPhysicalNew,cbBufNew);
|
||
|
}
|
||
|
|
||
|
ret = TRUE;
|
||
|
done:
|
||
|
TraceTag(ttidWebServer, "FilterMapURL failed, err = %d, GLE=%X",err,GetLastError());
|
||
|
|
||
|
*pdwSize = cbBufNew;
|
||
|
MyFree(pszPhysicalOrg);
|
||
|
return ret;
|
||
|
}
|