windows-nt/Source/XPSP1/NT/net/upnp/host/upnphost/udhhttp/vroots.cpp

683 lines
18 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 2000.
//
// File: V R O O T S . C P P
//
// Contents: Implements the virtual root system for the HTTP server
//
// Notes:
//
// Author: danielwe 2000/11/6
//
//----------------------------------------------------------------------------
#include "pch.h"
#pragma hdrstop
#include "httpd.h"
#include "ncreg.h"
// Used to split a URL and Path Translated apart for ISAPI/ASP scripts
inline void SetPathInfo(PSTR *ppszPathInfo,PSTR pszInputURL,int iURLLen)
{
int iLen = strlen(pszInputURL+iURLLen) + 2;
// If we've mapped the virtual root "/" to a script, need an extra "/" for the path
// (normally we use the origial trailing "/", but in this case the "/" is the URL
// BUGBUG: Probably should rewrite the Virtual roots parsing
// mechanism so that it's cleaner.
*ppszPathInfo = MySzAllocA((iURLLen == 1) ? iLen + 1 : iLen);
if (! (*ppszPathInfo))
goto done;
if (iURLLen == 1)
{
(*ppszPathInfo)[0] = '/';
memcpy( (*ppszPathInfo) +1, pszInputURL + iURLLen, iLen);
}
else
memcpy(*ppszPathInfo, pszInputURL + iURLLen, iLen);
done:
// URL shouldn't contain path info, break it apart
pszInputURL[iURLLen] = 0;
}
PVROOTINFO CVRoots::MatchVRoot(PCSTR pszInputURL, int iInputLen)
{
int i, iMatch;
// If there was an error on setting up the vroots, m_pVRoots = NULL.
if (!m_pVRoots)
return NULL;
for(i=0, iMatch=-1; i<m_nVRoots; i++)
{
int iLen = m_pVRoots[i].iURLLen;
// If this root maps to physical path "\", special case.
// In general we store pszURL without trailing "/", however we have
// to store trailing "/" for root directory.
if (m_pVRoots[i].bRootDir && iLen != 1)
iLen--;
if(iLen && iInputLen >= iLen)
{
if(0 == _memicmp(pszInputURL, m_pVRoots[i].pszURL, iLen))
{
// If it's not root dir, always matched. Otherwise it's possible
// there wasn't a match. For root dirs, pszURL[iLen] is always "/"
if (!m_pVRoots[i].bRootDir || m_pVRoots[i].iURLLen == 1 || pszInputURL[iLen] == '/' || pszInputURL[iLen] == '\0')
{
TraceTag(ttidWebServer, "URL %s matched VRoot %s (path %S, perm=%d, auth=%d)",
pszInputURL, m_pVRoots[i].pszURL,
m_pVRoots[i].wszPath,
m_pVRoots[i].dwPermissions,
m_pVRoots[i].AuthLevel);
return &(m_pVRoots[i]);
}
}
}
}
TraceTag(ttidWebServer, "URL %s did not matched any VRoot", pszInputURL);
return NULL;
}
BOOL CVRoots::FillVRoot(PVROOTINFO pvr, LPWSTR wszURL, LPWSTR wszPath)
{
int err = 0; // err variable is used in non-Debug mode
const char cszDLL[] = ".dll";
const char cszASP[] = ".asp";
CHAR pszURL[MAX_PATH+1];
CHAR pszPath[MAX_PATH+1];
// convert URL to MBCS
int iLen = pvr->iURLLen = MyW2A(wszURL, pszURL, sizeof(pszURL));
if(!iLen)
{ myleave(83); }
pvr->iURLLen--; // -1 for null-term
pvr->iPathLen = wcslen(wszPath);
MyW2A(wszPath, pszPath, sizeof(pszPath));
// check to see if Vroot ends in .dll or .asp, in this case we send
// client not to the directory but to the script page.
if (pvr->iPathLen >= sizeof(cszDLL) &&
0 == strcmpi(pszPath + pvr->iPathLen - sizeof(cszDLL) +1,cszDLL))
{
pvr->ScriptType = SCRIPT_TYPE_EXTENSION;
}
else if (pvr->iPathLen >= sizeof(cszASP) &&
0 == strcmpi(pszPath + pvr->iPathLen - sizeof(cszASP) +1,cszASP))
{
pvr->ScriptType = SCRIPT_TYPE_ASP;
}
else
{
pvr->ScriptType = SCRIPT_TYPE_NONE;
}
// If one of URL or path ends in a slash, the other must too.
// If either the URL ends in a "/" or when the path ends in "\", we remove
// the extra symbol. However, in the case where either URL or path is
// root we don't do this.
if (pvr->iURLLen != 1 && pszURL[pvr->iURLLen-1]=='/')
{
pszURL[pvr->iURLLen-1] = L'\0';
pvr->iURLLen--;
}
else if (pvr->iURLLen == 1 && pszURL[0]=='/' && pvr->ScriptType == SCRIPT_TYPE_NONE)
{
// if it's the root URL, make sure correspinding path ends with "\"
// (if it's a directory only, leave ASP + ISAPI's alone)
if (wszPath[pvr->iPathLen-1] != L'\\')
{
wszPath[pvr->iPathLen] = L'\\';
pvr->iPathLen++;
wszPath[pvr->iPathLen] = L'\0';
}
}
// If Path ends in "\" (and it's not the root path or root virtual root)
// remove the "\"
if (pvr->iURLLen != 1 && pvr->iPathLen != 1 && wszPath[pvr->iPathLen-1]==L'\\')
{
wszPath[pvr->iPathLen-1] = L'\0';
pvr->iPathLen--;
}
else if (pvr->iPathLen == 1 && wszPath[0]==L'\\')
{
// Trailing "/" must match "\". However, we need a slight HACK to make this work
if (pszURL[pvr->iURLLen-1] != '/')
{
pszURL[pvr->iURLLen] = '/';
pvr->iURLLen++;
pszURL[pvr->iURLLen] = '\0';
}
pvr->bRootDir = TRUE;
}
pvr->pszURL = MySzDupA(pszURL);
pvr->wszPath = MySzDupW(wszPath);
// Fill in defaults for these
pvr->wszUserList = NULL;
pvr->dwPermissions = HTTP_DEFAULTP_PERMISSIONS;
pvr->AuthLevel = AUTH_PUBLIC;
TraceTag(ttidWebServer, "VROOT: (%s)=>(%s) perm=%d auth=%d ScriptType=%d",
pvr->pszURL, pvr->wszPath, pvr->dwPermissions,
pvr->AuthLevel,pvr->ScriptType);
done:
if(err)
{
return FALSE;
}
return TRUE;
}
VOID CVRoots::Sort()
{
BOOL fChange;
int i=0;
// We now want to sort the VRoots in descending order of URL-length so
// that when we match we'll find the longest match first!!
// Using a slow bubble-sort :-(
do {
fChange = FALSE;
for(i=0; i<m_nVRoots-1; i++)
{
if(m_pVRoots[i].iURLLen < m_pVRoots[i+1].iURLLen)
{
// swap the 2 vroots
VROOTINFO vtemp = m_pVRoots[i+1];
m_pVRoots[i+1] = m_pVRoots[i];
m_pVRoots[i] = vtemp;
fChange = TRUE;
}
}
} while(fChange);
}
static const WCHAR c_szPrefix[] = L"\\\\?\\";
static const int c_cchPrefix = celems(c_szPrefix);
BOOL CVRoots::Init()
{
int err = 0; // err variable is used in non-Debug mode
int i=0;
InitializeCriticalSection(&m_csVroot);
// Registry doesnt allow keynames longer than MAX_PATH so we won't map URL prefixes longer than MAX_PATH
WCHAR wszURL[MAX_PATH+1];
WCHAR wszPath[MAX_PATH+1];
WCHAR wszPathReal[MAX_PATH + 1] = {0};
wszURL[0]=wszPath[0]=0;
// open the VRoots key
CReg topreg(HKEY_LOCAL_MACHINE, RK_HTTPDVROOTS);
// allocate space for as many VRoots as we have subkeys
m_nVRoots = topreg.NumSubkeys();
if(!m_nVRoots)
myleave(80);
// Zero the memory so we know what to deallocate and what not to.
if(!(m_pVRoots = MyRgAllocZ(VROOTINFO, m_nVRoots)))
myleave(81);
// enumerate all subkeys. Their names are URLs, their default value is the corresponding path
// Note: EnumKey takes sizes in chars, not bytes!
for(i=0; i<m_nVRoots && topreg.EnumKey(wszURL, CCHSIZEOF(wszURL)); i++)
{
CReg subreg(topreg, wszURL);
// get the unnamed value. Again size is in chars, not bytes.
if(!subreg.ValueSZ(NULL, wszPath, CCHSIZEOF(wszPath)))
{
// iURLLen and iPathLen set to 0 already, so no case of corruption in MatchVRoot
subreg.Reset();
continue;
}
else
{
// Prepend the \\?\ prefix
//
lstrcpy(wszPathReal, c_szPrefix);
lstrcat(wszPathReal, wszPath);
if (!FillVRoot(&m_pVRoots[i], wszURL, wszPathReal))
myleave(121);
m_pVRoots[i].wszUserList = MySzDupW( subreg.ValueSZ(RV_USERLIST));
// default permissions is Read & Execute
m_pVRoots[i].dwPermissions = subreg.ValueDW(RV_PERM, HTTP_DEFAULTP_PERMISSIONS);
// default authentication is public
m_pVRoots[i].AuthLevel = (AUTHLEVEL)subreg.ValueDW(RV_AUTH, (DWORD)AUTH_PUBLIC);
// we don't fail if we can't load an extension map
LoadExtensionMap (&m_pVRoots[i], subreg);
}
subreg.Reset();
}
Sort();
done:
if(err)
{
TraceTag(ttidWebServer, "CVRoots::ctor FAILED due to err=%d GLE=%d "
"(num=%d i=%d pVRoots=0x%08x url=%s path=%s)",
err, GetLastError(), m_nVRoots, i, m_pVRoots, wszURL, wszPath);
return FALSE;
}
return TRUE;
}
void CVRoots::Cleanup()
{
if(!m_pVRoots)
return;
for(int i=0; i<m_nVRoots; i++)
{
MyFree(m_pVRoots[i].pszURL);
MyFree(m_pVRoots[i].wszPath);
MyFree(m_pVRoots[i].wszUserList);
FreeExtensionMap (&m_pVRoots[i]);
}
MyFree(m_pVRoots);
DeleteCriticalSection(&m_csVroot);
}
BOOL CVRoots::AddVRoot(LPWSTR szUrl, LPWSTR szPath)
{
PVROOTINFO pvrNew;
int err = 0;
LPSTR szaUrl = NULL;
int iInputLen;
szaUrl = SzFromWsz(szUrl);
if (!szaUrl)
{
// Can't use myleave since we don't have the critsec here
//
err = 400;
goto err;
}
iInputLen = strlen(szaUrl);
EnterCriticalSection(&m_csVroot);
pvrNew = MatchVRoot(szaUrl, iInputLen);
if(pvrNew)
{
TraceError("CVRoots::AddVRoot - already present", E_FAIL);
myleave(10);
}
m_pVRoots = MyRgReAlloc(VROOTINFO, m_pVRoots, m_nVRoots, m_nVRoots + 1);
if(!m_nVRoots)
myleave(100);
pvrNew = &m_pVRoots[m_nVRoots];
if (!FillVRoot(pvrNew, szUrl, szPath))
{
myleave(101);
}
m_nVRoots++;
HKEY hkeyVroot;
HKEY hkeyNew;
HRESULT hr;
hr = HrRegOpenKeyEx(HKEY_LOCAL_MACHINE, RK_HTTPDVROOTS, KEY_ALL_ACCESS,
&hkeyVroot);
if (SUCCEEDED(hr))
{
hr = HrRegCreateKeyEx(hkeyVroot, szUrl, 0, KEY_ALL_ACCESS, NULL,
&hkeyNew, NULL);
if (SUCCEEDED(hr))
{
// Pass NULL to set default value
//
hr = HrRegSetSz(hkeyNew, NULL, szPath);
RegCloseKey(hkeyNew);
}
RegCloseKey(hkeyVroot);
}
if (FAILED(hr))
{
TraceError("CVRoots::AddVRoot", hr);
myleave(111);
}
else
{
Sort();
}
done:
delete [] szaUrl;
LeaveCriticalSection(&m_csVroot);
err:
if(err)
{
return FALSE;
}
return TRUE;
}
BOOL CVRoots::RemoveVRoot(LPWSTR szwUrl)
{
int ivr;
LPSTR szUrl = NULL;
PVROOTINFO pvr;
int iInputLen;
int err = 0;
BOOL fFound = FALSE;
szUrl = SzFromWsz(szwUrl);
if (!szUrl)
{
myleave(100);
}
iInputLen = strlen(szUrl);
EnterCriticalSection(&m_csVroot);
pvr = MatchVRoot(szUrl, iInputLen);
if(!pvr)
{
myleave(140);
}
for (ivr = 0; ivr < m_nVRoots; ivr++)
{
if (&m_pVRoots[ivr] == pvr)
{
// Found the one to remove. So now let's shift the rest down by
// one
//
MoveMemory(&m_pVRoots[ivr], &m_pVRoots[ivr + 1],
sizeof(VROOTINFO) * (m_nVRoots - ivr - 1));
m_nVRoots--;
fFound = TRUE;
break;
}
}
AssertSz(fFound, "How come it was there a minute ago??");
HKEY hkeyVroot;
HKEY hkeyNew;
HRESULT hr;
hr = HrRegOpenKeyEx(HKEY_LOCAL_MACHINE, RK_HTTPDVROOTS, KEY_ALL_ACCESS,
&hkeyVroot);
if (SUCCEEDED(hr))
{
hr = HrRegDeleteKey(hkeyVroot, szwUrl);
RegCloseKey(hkeyVroot);
}
if (FAILED(hr))
{
TraceError("CVRoots::RemoveVRoot", hr);
myleave(111);
}
// No need to re-sort since we moved everything down by one
done:
LeaveCriticalSection(&m_csVroot);
delete [] szUrl;
if(err)
{
return FALSE;
}
return TRUE;
}
PWSTR CVRoots::URLAtoPathW(PSTR pszInputURL, PDWORD pdwPerm /*=0*/,
AUTHLEVEL* pAuthLevel /* =0 */,
SCRIPT_TYPE *pScriptType /*=0 */,
PSTR *ppszPathInfo /* =0 */,
WCHAR **ppwszUserList /*=0 */)
{
PSTR pszEndOfURL;
WCHAR *wszTemp = NULL;
int iInputLen = strlen(pszInputURL);
EnterCriticalSection(&m_csVroot);
PVROOTINFO pVRoot = MatchVRoot(pszInputURL, iInputLen);
if(!pVRoot)
{
LeaveCriticalSection(&m_csVroot);
return NULL;
}
// Do a lookup to see if the current URL contains and extension
// that is in the extension map for the VROOT. If so, this call
// will truncate the URL after the extension. Since the URL may
// have changed, the length is re-obtained on a successfull call.
if (MapExtToPath (pszInputURL, &pszEndOfURL))
{
if (ppszPathInfo && *ppszPathInfo == NULL)
*ppszPathInfo = MySzDupA (pszInputURL);
if (*pszEndOfURL != '\0')
{
*pszEndOfURL = 0;
iInputLen = strlen(pszInputURL);
}
}
// in computing the buffersize here we are assuming that an MBCS string of length N
// cannot produce a unicode string of length greater than N
int iOutLen = 1 + pVRoot->iPathLen + (iInputLen - pVRoot->iURLLen);
PWSTR wszOutPath = MyRgAllocNZ(WCHAR, iOutLen);
if (!wszOutPath)
{
LeaveCriticalSection(&m_csVroot);
return NULL;
}
// assemble the path. First, the mapped base path
memcpy(wszOutPath, pVRoot->wszPath, sizeof(WCHAR)*pVRoot->iPathLen);
if(pdwPerm)
*pdwPerm = pVRoot->dwPermissions;
if(pAuthLevel)
*pAuthLevel = pVRoot->AuthLevel;
if (pScriptType)
*pScriptType = pVRoot->ScriptType;
if (ppwszUserList)
*ppwszUserList = pVRoot->wszUserList;
// If the vroot specifies an ISAPI dll or ASP page don't copy path info over.
if (pVRoot->ScriptType != SCRIPT_TYPE_NONE)
{
if ( ppszPathInfo && pszInputURL[pVRoot->iURLLen] != 0)
{
SetPathInfo(ppszPathInfo,pszInputURL,pVRoot->iURLLen);
}
wszOutPath[pVRoot->iPathLen] = L'\0';
LeaveCriticalSection(&m_csVroot);
return wszOutPath;
}
// next the remainder of the URL, converted to wide
if (iOutLen-pVRoot->iPathLen == 0)
{
wszOutPath[pVRoot->iPathLen] = L'\0';
}
else
{
int iRet = MyA2W(pszInputURL+pVRoot->iURLLen, wszOutPath+pVRoot->iPathLen, iOutLen-pVRoot->iPathLen);
DEBUGCHK(iRet);
}
LeaveCriticalSection(&m_csVroot);
// Flip forward slashes around to backslashes
LPWSTR pchFlip = wszOutPath;
while (*pchFlip)
{
if (*pchFlip == '/')
{
*pchFlip = '\\';
}
pchFlip++;
}
return wszOutPath;
}
BOOL CVRoots::LoadExtensionMap(PVROOTINFO pvr, HKEY rootreg)
{
BOOL bWildCard;
WCHAR wszExt[MAX_PATH+1];
WCHAR wszPath[MAX_PATH+1];
CReg mapreg(rootreg, RV_EXTENSIONMAP);
if (!mapreg.IsOK())
{
pvr->nExtensions = 0;
return FALSE;
}
if (bWildCard = mapreg.ValueSZ(NULL, wszPath, CCHSIZEOF(wszPath)))
pvr->nExtensions = 1;
else
pvr->nExtensions = mapreg.NumValues();
if ((pvr->pExtMap = MyRgAllocZ(EXTMAP, pvr->nExtensions)) == NULL)
return FALSE;
if (bWildCard)
{
pvr->pExtMap[0].wszExt = MySzDupW(L"*");
pvr->pExtMap[0].wszPath = MySzDupW(wszPath);
return TRUE;
}
for (int i = 0; i < pvr->nExtensions; i++)
{
if (mapreg.EnumValue(wszExt, CCHSIZEOF(wszExt),
wszPath, CCHSIZEOF(wszPath)))
{
if (wszExt[0] != 0 && wszPath[0] != 0)
{
pvr->pExtMap[i].wszExt = MySzDupW(wszExt);
pvr->pExtMap[i].wszPath = MySzDupW(wszPath);
}
else
{
pvr->pExtMap[i].wszExt = MySzDupW(L"");
pvr->pExtMap[i].wszPath = MySzDupW(L"");
}
}
}
return TRUE;
}
void CVRoots::FreeExtensionMap (PVROOTINFO pvr)
{
if (pvr->pExtMap == NULL) return;
for (int i=0; i < pvr->nExtensions; i++)
{
MyFree(pvr->pExtMap[i].wszExt);
MyFree(pvr->pExtMap[i].wszPath);
}
MyFree(pvr->pExtMap);
pvr->pExtMap = NULL;
}
PWSTR CVRoots::MapExtToPath (PSTR pszInputURL, PSTR *ppszEndOfURL)
{
PSTR pszTemp, pszStart, pszEnd;
PWSTR wszPath = NULL;
WCHAR wszExt[MAX_PATH+1];
PVROOTINFO pvr;
int iInputLen = strlen(pszInputURL);
if (!FindExtInURL (pszInputURL, &pszStart, &pszEnd))
return NULL;
MyA2W (pszStart, wszExt, (int)((INT_PTR)(pszEnd - pszStart)));
wszExt [pszEnd - pszStart] = L'\0';
EnterCriticalSection(&m_csVroot);
if (!(pvr = MatchVRoot(pszInputURL, iInputLen)))
{
LeaveCriticalSection(&m_csVroot);
return NULL;
}
for (int i = 0; i < pvr->nExtensions; i++)
{
if ((i == 0 && pvr->pExtMap[i].wszExt[0] == L'*') ||
0 == _wcsicmp (pvr->pExtMap[i].wszExt, wszExt))
{
wszPath = pvr->pExtMap[i].wszPath;
if (ppszEndOfURL != NULL)
*ppszEndOfURL = pszEnd;
break;
}
}
LeaveCriticalSection(&m_csVroot);
return wszPath;
}
BOOL CVRoots::FindExtInURL (PSTR pszInputURL, PSTR *ppszStart, PSTR *ppszEnd)
{
*ppszStart = strrchr (pszInputURL, '.');
if ((*ppszEnd = *ppszStart) == NULL)
return FALSE;
while (**ppszEnd != '/' && **ppszEnd != '\0')
*ppszEnd = *ppszEnd + 1;
return TRUE;
}