/* * S C R P T M P S . C P P * * Scriptmaps cacheing * * Copyright 1986-1997 Microsoft Corporation, All Rights Reserved */ #include "_davprs.h" #include "scrptmps.h" #include "instdata.h" // ======================================================================== // class CScriptMap // // Contains the parsed set of scriptmaps for a single metabase entry. // Contains lookup functions to find scriptmaps that match certain // conditions. // class CScriptMap : public IScriptMap, public CMTRefCounted { typedef struct _inclusions { DWORD fdwInclusions; LPCWSTR pwszMethods; BOOL FAllMethodsIncluded () { // When a script map has an empty inclusion verb list, it means all verbs included // (NOT all verbs excluded). // Assert (pwszMethods); return L'\0' == pwszMethods[0]; } } INCLUSIONS, * PINCLUSIONS; typedef CCache CInclusionsCache; // INCLUSIONS data storage area. // ChainedBuffer m_bInclusions; // Cache of scriptmap entries // CInclusionsCache m_cache; // Pointer to first 'star' scriptmap in the list. // This is the one that IIS will have called to process // the request for this url. This is used by the virtual // root cache. // // Note that we should ignore any 'star' scriptmaps when // evaluating for matches. The first 'star' gets a crack // at it, and that's that. // LPCWSTR m_pwszStarScriptmap; // Private accessors // VOID AddMapping (LPCWSTR pwszMap, HDRITER_W* pit); BOOL FLoadScriptmaps (LPWSTR pwszScriptMaps); // CLASS CIsMatch -------------------------------------------------------- // // Functional class to find if a given scriptmap applys to a URI // class CIsMatch : public CInclusionsCache::IOp { const CInclusionsCache& m_cache; const LPCWSTR m_pwszMethod; const METHOD_ID m_midMethod; BOOL m_fCGI; DWORD m_dwAccess; LPCWSTR m_pwszMatch; LPCWSTR m_pwszURI; SCODE m_sc; // NOT IMPLEMENTED // CIsMatch& operator=(const CIsMatch&); public: CIsMatch(const CInclusionsCache& cache, const LPCWSTR pwszMethod, const METHOD_ID midMethod, LPCWSTR pwszUri, LPCWSTR pwszMatch, DWORD dwAcc) : m_cache(cache), m_pwszMethod(pwszMethod), m_midMethod(midMethod), m_fCGI(FALSE), m_dwAccess(dwAcc), m_pwszURI(pwszUri), m_pwszMatch(pwszMatch), m_sc(S_OK) {} SCODE ScMatched() const { return m_sc; } BOOL FMatchIsCGI() const { return m_fCGI; } virtual BOOL operator()(const CRCWszi& crcwszi, const PINCLUSIONS& pin); }; // NOT IMPLEMENTED // CScriptMap& operator=(const CScriptMap&); CScriptMap(const CScriptMap&); // Helper function // public: // CREATORS // CScriptMap() : m_pwszStarScriptmap(NULL) { // Use COM-style ref-counting. Start with 1. // m_cRef = 1; } BOOL FInit (LPWSTR pwszScriptMaps); // Implementation of IRefCounted members // Simply route them to our own CMTRefCounted members. // void AddRef() { CMTRefCounted::AddRef(); } void Release() { CMTRefCounted::Release(); } // ACCESSORS // SCODE ScMatched (LPCWSTR pwszMethod, METHOD_ID midMethod, LPCWSTR pwszMap, DWORD dwAccess, BOOL * pfCGI) const; // Used by MOVE/COPY/DELETE to check for star scriptmapping // overrides // BOOL FSameStarScriptmapping (const IScriptMap * pism) const { const CScriptMap* prhs = static_cast(pism); if (m_pwszStarScriptmap != prhs->m_pwszStarScriptmap) { if (m_pwszStarScriptmap && prhs->m_pwszStarScriptmap) if (0 == _wcsicmp (m_pwszStarScriptmap, prhs->m_pwszStarScriptmap)) return TRUE; } else return TRUE; return FALSE; } }; CScriptMap::FInit (LPWSTR pwszScriptMaps) { // Init the cache // if ( !m_cache.FInit() ) return FALSE; // Load the scriptmaps // return FLoadScriptmaps(pwszScriptMaps); } void CScriptMap::AddMapping(LPCWSTR pwszMap, HDRITER_W * pitInclusions) { LPCWSTR pwszInclusion; METHOD_ID mid; PINCLUSIONS pin = NULL; Assert (pwszMap); // If there is a DLL, then we want to assemble an inclusions list // if (pitInclusions) { pin = m_bInclusions.Alloc (sizeof(INCLUSIONS)); // Record the start of the inclusion list // pin->pwszMethods = pitInclusions->PszRaw(); pin->fdwInclusions = 0; // Rip through the list and identify all the known // inclusions // while ((pwszInclusion = pitInclusions->PszNext()) != NULL) { mid = MidMethod (pwszInclusion); if (mid != MID_UNKNOWN) pin->fdwInclusions |= (1 << mid); } } // At this point, we can add the cache item... // ScriptMapTrace ("Dav: adding scriptmap for %S -- including %S\n", pwszMap, (pin && pin->pwszMethods) ? pin->pwszMethods : L"none"); // CRC the mapping and stuff it into the cache. // Note that we are safe in using the actual parameter string // here because the CScriptMap object's lifetime is the same // as the lifetime of the metadata on which it operates. See // \cal\src\_davprs\davmb.cpp for details. // CRCWszi crcwszi(pwszMap); (void) m_cache.FSet (crcwszi, pin); } BOOL CScriptMap::FLoadScriptmaps (LPWSTR pwszScriptMaps) { HDRITER_W it(NULL); UINT cchDav = static_cast(wcslen(gc_wszSignature)); Assert (pwszScriptMaps); ScriptMapTrace ("Dav: loading scriptmap cache\n"); // Add in the default CGI/BGI mappings // AddMapping (L".EXE", NULL); AddMapping (L".CGI", NULL); AddMapping (L".COM", NULL); AddMapping (L".DLL", NULL); AddMapping (L".ISA", NULL); // // Parse through the scriptmap list and build up the cache. // // Each mapping is a string of the form: // // "|<*>,,[,...]" // // Note that if any of the mappings is invalid we fail the whole call. // This is consistent with IIS' behavior. // UINT cchMapping = 0; for ( LPWSTR pwszMapping = pwszScriptMaps; *pwszMapping; pwszMapping += cchMapping ) { enum { ISZ_SM_EXT = 0, ISZ_SM_PATH, ISZ_SM_FLAGS, ISZ_SM_INCLUSION_LIST, CSZ_SM_FIELDS }; // Figure out the length of the mapping // including the null terminator // cchMapping = static_cast(wcslen(pwszMapping) + 1); // Special case: star (wildcard) scriptmaps. // // These should mostly be ignored. We will never // forward to a star scriptmap. If we find a star // scriptmap, the only reason to keep track of it // is so that we can compare it against another // star scriptmap when checking the feasibility // of a trans-vroot MOVE/COPY/DELETE. And for this // comparsion, we check for EXACT equality between // the scriptmaps by checking the entire scriptmap // string. // // See the comments regarding m_pszStarScriptMap // above for more detail. // if (L'*' == *pwszMapping) { if (NULL == m_pwszStarScriptmap) m_pwszStarScriptmap = pwszMapping; continue; } // Digest the metadata. // LPWSTR rgpwsz[CSZ_SM_FIELDS]; UINT cchUnused; if (!FParseMDData (pwszMapping, rgpwsz, CSZ_SM_FIELDS, &cchUnused)) { // FParseMDData() will return FALSE if there is no verb // exclusion list because it is an optional parameter. // If all the other parameters exist though then it's // really ok. // if (!(rgpwsz[ISZ_SM_EXT] && rgpwsz[ISZ_SM_PATH] && rgpwsz[ISZ_SM_FLAGS])) { DebugTrace ("CScriptMap::FLoadScriptMaps() - Malformed scriptmaps\n"); continue; } } // We belive that all the scriptmaps are // extension based. But other than that // there is no validation. // Assert (*rgpwsz[ISZ_SM_EXT] == L'.'); // If the path refers to our DAV DLL then skip this mapping. // // The way this works is: If the length of the path is at least // as long as the length of our DLL name AND the final component // of that path is the name of our DLL then skip the mapping. // Eg. "HTTPEXT.DLL" will match the first condition of the if, // "c:\foo\bar\HTTPEXT.DLL" will match the second condition of the if. // static const UINT cchDll = CchConstString(L".DLL"); UINT cchPath = static_cast(wcslen(rgpwsz[ISZ_SM_PATH])); if (cchPath == cchDav + cchDll || ((cchPath > cchDav + cchDll) && *(rgpwsz[ISZ_SM_PATH] + cchPath - cchDll - cchDav - 1) == L'\\')) { // Now we know the final piece of the path is the correct length. // Check the data! If it matches our dll name, skip this mapping. // if (!_wcsnicmp(rgpwsz[ISZ_SM_PATH] + cchPath - cchDll - cchDav, gc_wszSignature, cchDav) && !_wcsicmp(rgpwsz[ISZ_SM_PATH] + cchPath - cchDll, L".DLL")) { continue; } } // Feed the optional inclusion list into a header iterator // that AddMapping() will use to determine what verbs // are included for this mapping. If there is no inclusion // list then use an empty iterator. // // Adding a mapping with an empty iterator (vs. NULL) // allows the scriptmap matching code to distinguish // between a "real" scriptmap with an empty inclusion // list and a default CGI-style scriptmap like those // added at the beginning of this function. // it.NewHeader(rgpwsz[ISZ_SM_INCLUSION_LIST] ? rgpwsz[ISZ_SM_INCLUSION_LIST] : gc_wszEmpty); // Add the extension-based mapping // AddMapping (rgpwsz[ISZ_SM_EXT], &it); } return TRUE; } SCODE CScriptMap::ScMatched ( LPCWSTR pwszMethod, METHOD_ID midMethod, LPCWSTR pwszURI, DWORD dwAccess, BOOL * pfCGI) const { LPCWSTR pwsz; SCODE sc = S_OK; Assert(pwszURI); // // Scan down the URI, looking for extensions. When one is found // zip through the list of mappings. While this may not seem the // most optimal, it really is. If we simply scaned the URI for // each mapping. We would be scaning the URI multiple times. In // this model, we scan the URI once. // if ((pwsz = wcsrchr(pwszURI, L'.')) != NULL) { // We have an extension so take a look // CIsMatch cim(m_cache, pwszMethod, midMethod, pwszURI, pwsz, dwAccess); m_cache.ForEach(cim); sc = cim.ScMatched(); if (pfCGI && (sc != S_OK)) *pfCGI = cim.FMatchIsCGI(); } return sc; } // CLASS CIsMatch ------------------------------------------------------------ // //$REVIEW: Does this code work for DBCS/UTF-8 map names? These are filenames.... //$REVIEW: This function does not currently check the METHOD EXCLUSION LIST. //$REVIEW: This might cause us to report a match when actually there are NO matches. // BOOL CScriptMap::CIsMatch::operator()(const CRCWszi& crcwszi, const PINCLUSIONS& pin) { Assert (crcwszi.m_pwsz); // Every scriptmap in the cache should be an extension-based mapping. // Compare the extension vs. the part of the URI that we're looking at. // If they match then we have a scriptmap. // Assert (L'.' == *crcwszi.m_pwsz); UINT cch = static_cast(wcslen (crcwszi.m_pwsz)); if (!_wcsnicmp (crcwszi.m_pwsz, m_pwszMatch, cch) && ((m_pwszMatch[cch] == '\0') || !wcscmp (m_pwszMatch+cch, L"/") || !wcscmp (m_pwszMatch+cch, L"\\"))) { // Looks like we have a match // ScriptMapTrace ("Dav: %S matched scriptmap %S\n", m_pwszURI, crcwszi.m_pwsz); // However, we only allow execution of CGI type child // ISAPI's if EXECUTE priviledge is enabled // if ((pin != NULL) || (m_dwAccess & MD_ACCESS_EXECUTE)) m_sc = W_DAV_SCRIPTMAP_MATCH_FOUND; m_fCGI = !pin; } // See if it is included // Note that, if all methods are included, no need to do further checking // if ((m_sc != S_OK) && pin && !pin->FAllMethodsIncluded()) { ScriptMapTrace ("Dav: checking '%S' against scriptmap inclusions: %S\n", m_pwszMethod, pin->pwszMethods); // In the unknown method scenario, we just need // to iterate the set of methods that are included // and check it against the request method // if (m_midMethod == MID_UNKNOWN) { BOOL fIncluded = FALSE; HDRITER_W it(pin->pwszMethods); LPCWSTR pwsz; while ((pwsz = it.PszNext()) != NULL) { fIncluded = !wcscmp (pwsz, m_pwszMethod); if (fIncluded) break; } if (!fIncluded) { ScriptMapTrace ("Dav: unknown '%S' excluded from scriptmap\n", m_pwszMethod); m_sc = W_DAV_SCRIPTMAP_MATCH_EXCLUDED; } } // // Otherwise, the inclusions flags have the MID'th bit // set if it is excluded. // else if (!(pin->fdwInclusions & (1 << m_midMethod))) { ScriptMapTrace ("Dav: '%S' excluded from scriptmap\n", m_pwszMethod); m_sc = W_DAV_SCRIPTMAP_MATCH_EXCLUDED; } } return (m_sc == S_OK); } IScriptMap * NewScriptMap( LPWSTR pwszScriptMaps ) { auto_ref_ptr pScriptMap; pScriptMap.take_ownership (new CScriptMap()); if (pScriptMap->FInit(pwszScriptMaps)) return pScriptMap.relinquish(); return NULL; }