//+--------------------------------------------------------------------------- // // 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; }