/* * F S S E A R C H . C P P * * Sources file system implementation of DAV-Search * * Copyright 1986-1997 Microsoft Corporation, All Rights Reserved */ #include "_davfs.h" #ifdef __cplusplus extern "C" { #endif #include #ifdef __cplusplus } #endif #include "_fssrch.h" #include #include // 20001801-5de6-11d1-8e38-00c04fb9386d is FMTID_PropertySet as defined // in pbagex.h. It's the guid of custom props, // static const WCHAR gsc_wszSetPropertyName[] = L"SET PROPERTYNAME '20001801-5de6-11d1-8e38-00c04fb9386d' PROPID '%s' AS \"%s\""; // gsc_wszPath is used for the Tripoli prop "Path", so don't move to common sz.cpp // static const WCHAR gsc_wszSelectPath[] = L"SELECT Path "; static const ULONG MAX_FULLY_QUALIFIED_LENGTH = 2048; static const WCHAR gsc_wszShallow[] = L"Shallow"; static const ULONG gsc_cchShallow = CchConstString(gsc_wszShallow); // class CDBCreateCommand ---------------------------------------------------- // class CDBCreateCommand : private OnDemandGlobal { // // Friend declarations required by OnDemandGlobal template // friend class Singleton; friend class RefCountedGlobal; // // Pointer to the IDBCreateCommand object // auto_com_ptr m_pDBCreateCommand; // CREATORS // // Declared private to ensure that arbitrary instances // of this class cannot be created. The Singleton // template (declared as a friend above) controls // the sole instance of this class. // CDBCreateCommand() {} BOOL FInit( SCODE * psc ); public: static SCODE CreateCommand( ICommandText ** ppCommandText ); static VOID Release() { DeinitIfUsed(); } }; BOOL CDBCreateCommand::FInit( SCODE * psc ) { SCODE sc = S_OK; auto_com_ptr pDBInit; auto_com_ptr pDBCS; // Get provider "MSIDXS" // sc = CoCreateInstance(CLSID_MSIDXS, NULL, CLSCTX_INPROC_SERVER, IID_IDBInitialize, (void **)&pDBInit); if (FAILED(sc)) { DebugTrace ("Failed to initialized provider MSIDXS \n"); goto ret; } // Initialize the provider // sc = pDBInit->Initialize(); if (FAILED(sc)) { DebugTrace ("IDBInitialize::Initialize failed\n"); goto ret; } // Get IDBCreateSession // sc = pDBInit->QueryInterface(IID_IDBCreateSession, (void**) &pDBCS); if (FAILED(sc)) { DebugTrace("QI for IDBCreateSession failed\n"); goto ret; } // Create a Session object // sc = pDBCS->CreateSession(NULL, IID_IDBCreateCommand, (IUnknown**) m_pDBCreateCommand.load()); if (FAILED(sc)) { DebugTrace("pDBCS->CreateSession failed\n"); goto ret; } ret: *psc = sc; return SUCCEEDED(sc); } SCODE CDBCreateCommand::CreateCommand( ICommandText ** ppCommandText ) { SCODE sc = S_OK; if ( !FInitOnFirstUse( &sc ) ) { DebugTrace( "CDBCreateCommand::CreateCommand() - DwInitRef() failed (0x%08lX)\n", sc ); goto ret; } Assert( Instance().m_pDBCreateCommand ); sc = Instance().m_pDBCreateCommand->CreateCommand (NULL, IID_ICommandText, (IUnknown**) ppCommandText); ret: return sc; } // ReleaseDBCreateCommandObject() // // Called from FSTerminate to free the DBCreateCommand object before quit // VOID ReleaseDBCreateCommandObject() { CDBCreateCommand::Release(); } // Search specifics ---------------------------------------------------------- // BOOL IsLegalVarChar(WCHAR wch) { return iswalnum(wch) || (L'.' == wch) || (L':' == wch) || (L'-' == wch) || (L'_' == wch) || (L'/' == wch) || (L'*' == wch); // * included to support 'select *' } // // FTranslateScope // detect whether a given URI or a path is under the // davfs virutal directory // // pmu [in] pointer to IMethUtil object // pwszURIOrPath [in] URI or the physical path, non-NULL terminated // cchPath [in] the number of chars of the path // ppwszPath [in] receive the pointer to the translated path // BOOL FTranslateScope (LPMETHUTIL pmu, LPCWSTR pwszURI, ULONG cchURI, auto_heap_ptr& pwszPath) { SCODE sc = S_OK; CStackBuffer pwszTerminatedURI; CStackBuffer pwszURINormalized; UINT cchURINormalized; UINT cch; // We need to make a copy of '\0' terminated URI // if (NULL == pwszTerminatedURI.resize(CbSizeWsz(cchURI))) { sc = E_OUTOFMEMORY; DebugTrace("FTranslatedScope() - Error while allocating memory 0x%08lX\n", sc); return FALSE; } memcpy(pwszTerminatedURI.get(), pwszURI, cchURI * sizeof(WCHAR)); pwszTerminatedURI[cchURI] = L'\0'; // We need to unescape the scope URI before translate // cchURINormalized = pwszURINormalized.celems(); sc = ScNormalizeUrl (pwszTerminatedURI.get(), &cchURINormalized, pwszURINormalized.get(), NULL); if (S_FALSE == sc) { if (NULL == pwszURINormalized.resize(cchURINormalized * sizeof(WCHAR))) { sc = E_OUTOFMEMORY; DebugTrace("FTranslatedScope() - Error while allocating memory 0x%08lX\n", sc); return FALSE; } sc = ScNormalizeUrl (pwszTerminatedURI.get(), &cchURINormalized, pwszURINormalized.get(), NULL); // Since we've given ScNormalizeUrl() the space it asked for, // we should never get S_FALSE again. Assert this! // Assert(S_FALSE != sc); } if (FAILED (sc)) { DebugTrace("FTranslatedScope() - ScNormalizeUrl() failed 0x%08lX\n", sc); return FALSE; } // Do the translation and check the validation // // At most we should go through the processing below twice, as the byte // count required is an out param. // cch = MAX_PATH; do { pwszPath.realloc(cch * sizeof(WCHAR)); sc = pmu->ScStoragePathFromUrl (pwszURINormalized.get(), pwszPath, &cch); } while (sc == S_FALSE); if (FAILED (sc)) { DebugTrace ("FTranslateScope() - IMethUtil::ScStoragePathFromUrl() failed to translate scope URI 0x%08lX\n", sc); return FALSE; } //$ SECURITY: // // Check to see if the scope is really a short filename. // sc = ScCheckIfShortFileName (pwszPath, pmu->HitUser()); if (FAILED (sc)) { DebugTrace ("FTranslateScope() - ScCheckIfShortFileName() failed to scope, is short filename 0x%08lX\n", sc); return FALSE; } //$ SECURITY: // // Check to see if the destination is really the default // data stream via alternate file access. // sc = ScCheckForAltFileStream (pwszPath); if (FAILED (sc)) { DebugTrace ("FTranslateScope() - ScCheckForAltFileStream() failed to scope, is short filename 0x%08lX\n", sc); return FALSE; } return TRUE; } // // ScSetPropertyName // // execute SET PROPERTYNAME command on the passed in property // so that Index Server will be aware of this prop // SCODE ScSetPropertyName(ICommandText * pCommandText, LPWSTR pwszName) { CStackBuffer pwszSet; auto_com_ptr pRowset; SCODE sc = S_OK; int cchNeeded; int cchStored; Assert(pCommandText != NULL); Assert(pwszName != NULL); if ((NULL == pCommandText) || (NULL == pwszName)) { sc = E_POINTER; goto ret; } // cchNeeded is the length of the final formatted string, including the // terminating null // cchNeeded = CchConstString(gsc_wszSetPropertyName) + wcslen(pwszName) * 2 + 1; if (NULL == pwszSet.resize(cchNeeded * sizeof(WCHAR))) { sc = E_OUTOFMEMORY; goto ret; } // generate the SET PROPERTYNAME command // cchStored = _snwprintf(pwszSet.get(), cchNeeded, gsc_wszSetPropertyName, pwszName, pwszName); // _snwprintf returns the number of chars stored, not including the terminating null. // It returns a negative value if the buffer was too short to store the formatted string // plus the terminating null. // So, a non-negative result means that the string plus the terminating null was stored. // // We check even more strictly here - we always expect our up-front length calculation // to be exact. // Assert(cchStored == cchNeeded - 1); if (cchStored != cchNeeded - 1) { sc = E_FAIL; goto ret; } // set the command text // sc = pCommandText->SetCommandText(DBGUID_DEFAULT, pwszSet.get()); if (FAILED(sc)) { DebugTrace ("failed to set command text %ws\n", pwszSet.get()); goto ret; } // do the actual set // sc = pCommandText->Execute(NULL, IID_IRowset, 0, 0, (IUnknown**) &pRowset); if (FAILED(sc)) { DebugTrace ("failed to execute %ws\n", pwszSet.get()); goto ret; } Assert (DB_S_NORESULT == sc); Assert (!pRowset); ret: return (sc == DB_S_NORESULT) ? S_OK : sc; } void AddChildVrPaths (IMethUtil* pmu, LPCWSTR pwszUrl, ChainedStringBuffer& sb, CVRList& vrl, CWsziList& lst) { CVRList::iterator it; ChainedStringBuffer sbLocal; // See if there are child vroots to process as well. We don't // have a path at this time for scoping, so we can pass NULL and // duplicates will get removed when we sort/unique. // if (S_OK == pmu->ScFindChildVRoots (pwszUrl, sbLocal, vrl)) { for (it = vrl.begin(); it != vrl.end(); it++) { auto_ref_ptr cvr; if (pmu->FGetChildVRoot (it->m_pwsz, cvr)) { LPCWSTR pwszPath; UINT cch; // Add it to the list // cch = cvr->CchGetVRPath (&pwszPath); lst.push_back(CRCWszi(sb.Append (CbSizeWsz(cch), pwszPath))); } } lst.sort(); lst.unique(); } } // Tripoli prop names // static const WCHAR gsc_Tripoli_wszFilename[] = L"filename"; static const WCHAR gsc_Tripoli_wszSize[] = L"size"; static const WCHAR gsc_Tripoli_wszCreate[] = L"create"; static const WCHAR gsc_Tripoli_wszWrite[] = L"write"; static const WCHAR gsc_Tripoli_wszAttrib[] = L"attrib"; // ScMapReservedPropInWhereClause // // Helper function to map DAV reserved props to // SCODE ScMapReservedPropInWhereClause (LPWSTR pwszName, UINT * pirp) { UINT irp; SCODE sc = S_OK; Assert (pirp); // We only care those properties not stored in propertybag // RESERVED_GET is just for this purpose // if (CFSProp::FReservedProperty (pwszName, CFSProp::RESERVED_GET, &irp)) { // Here's our mapping table // // DAV Prop Tripoli prop // // DAV:getcontentlength size // DAV:displayname filename // DAV:creationdate create // DAV:lastmodified write // DAV:ishidden attrib // DAV:iscollection attrib // DAV:resourcetype // DAV:getetag // DAV:lockdiscovery // DAV:supportedlock // Now that we are to overwrite dav reserved prop name with // the Tripoli prop name in place, the buffer must have enough // space // Assert this fact that all the six reserved we will ever map // satisfy this requirement // Assert ((wcslen(sc_rp[iana_rp_content_length].pwsz) >= wcslen (gsc_Tripoli_wszSize)) && (wcslen(sc_rp[iana_rp_creation_date].pwsz) >= wcslen (gsc_Tripoli_wszCreate)) && (wcslen(sc_rp[iana_rp_displayname].pwsz) >= wcslen (gsc_Tripoli_wszFilename)) && (wcslen(sc_rp[iana_rp_last_modified].pwsz) >= wcslen (gsc_Tripoli_wszWrite)) && (wcslen(sc_rp[iana_rp_ishidden].pwsz) >= wcslen (gsc_Tripoli_wszAttrib)) && (wcslen(sc_rp[iana_rp_iscollection].pwsz) >= wcslen (gsc_Tripoli_wszAttrib))); switch (irp) { case iana_rp_content_length: wcscpy (pwszName, gsc_Tripoli_wszSize); break; case iana_rp_creation_date: wcscpy (pwszName, gsc_Tripoli_wszCreate); break; case iana_rp_displayname: wcscpy (pwszName, gsc_Tripoli_wszFilename); break; case iana_rp_last_modified: wcscpy (pwszName, gsc_Tripoli_wszWrite); break; case iana_rp_ishidden: case iana_rp_iscollection: wcscpy (pwszName, gsc_Tripoli_wszAttrib); break; case iana_rp_etag: case iana_rp_resourcetype: case iana_rp_lockdiscovery: case iana_rp_supportedlock: // Among these four props, we data type of resourcetype is // a XML node, no way to express that in SQL. // And the rest three, we don't have a Tripoli mapping for them // // DB_E_ERRORSINCOMMAND will be mapped to 400 Bad Request // sc = DB_E_ERRORSINCOMMAND; goto ret; default: // Catch the bad boy // AssertSz (FALSE, "Unexpected reserved props"); break; } *pirp = irp; } ret: return sc; } const WCHAR gsc_wszStar[] = L"*"; const WCHAR gsc_wszAll[] = L"all"; const WCHAR gsc_wszDistinct[] = L"distinct"; // FSSearch::ScSetSQL // // Translate a SQL query, basically is to just replace the alias with the // corresponding namespace. // SCODE CFSSearch::ScSetSQL (CParseNmspcCache * pnsc, LPCWSTR pwszSQL) { BOOL fPropAdded = FALSE; BOOL fStarUsed = FALSE; BOOL fQuoted = FALSE; CStackBuffer pwszUrlT; LPCWSTR pwsz; LPCWSTR pwszNameBegin; LPCWSTR pwszWordBegin; SCODE sc = S_OK; UINT cLen; typedef enum { SQL_NO_STATE, SQL_SELECT, SQL_FROM, SQL_WHERE, SQL_MORE } SQL_STATE; SQL_STATE state = SQL_NO_STATE; // Create the command text object // sc = CDBCreateCommand::CreateCommand (m_pCommandText.load()); if (FAILED(sc)) goto ret; // Parse out the SQL // pwsz = const_cast(pwszSQL); Assert (pwsz); while (*pwsz) { // Filter out white spaces // while (*pwsz && iswspace(*pwsz)) pwsz++; // check to see if we reach the end of the string // if (!(*pwsz)) break; // remember the starting position // pwszWordBegin = pwsz; if (IsLegalVarChar(*pwsz)) { CStackBuffer pwszName; pwszNameBegin = pwsz; cLen = 0; // look for a variable // if (fQuoted) { // Pick up the propname as a whole // while (*pwsz && (*pwsz != L'"')) pwsz++; } else { while (*pwsz && IsLegalVarChar(*pwsz)) pwsz++; } // Translate the name here // cLen = static_cast(pwsz - pwszNameBegin); if (NULL == pwszName.resize(CbSizeWsz(cLen))) { sc = E_OUTOFMEMORY; goto ret; } wcsncpy (pwszName.get(), pwszNameBegin, cLen); pwszName[cLen] = 0; switch (state) { case SQL_NO_STATE: if (!_wcsnicmp (pwszWordBegin, gc_wszSelect, pwsz-pwszWordBegin)) state = SQL_SELECT; break; case SQL_SELECT: if (!_wcsnicmp (pwszWordBegin, gc_wszFrom, pwsz-pwszWordBegin)) { // Empty select statement is an error // if (!fPropAdded && !fStarUsed) { sc = E_INVALIDARG; goto ret; } // We've finished the SELECT statement. // Note that all that we need is 'SELECT path '. // We take care of all the rest ourselves, so restruct // the SELECT path here before we continue // m_sbSQL.Reset(); m_sbSQL.Append(gsc_wszSelectPath); state = SQL_FROM; break; } // Add to our list of properties to retrieve // if (!wcscmp(pwszName.get(), gsc_wszStar)) { sc = m_cfc.ScGetAllProps (NULL); if (FAILED(sc)) goto ret; fStarUsed = TRUE; } else { // Following Monarch Stage 1. // if (!fQuoted) { if (!_wcsicmp(pwszName.get(), gsc_wszAll)) break; if (!_wcsicmp(pwszName.get(), gsc_wszDistinct)) { // Monarch does not allow distinct // sc = E_INVALIDARG; goto ret; } } // Normal props // sc = m_cfc.ScAddProp (NULL, pwszName.get(), FALSE); if (FAILED(sc)) goto ret; fPropAdded = TRUE; } break; case SQL_FROM: { BOOL fScopeExist = FALSE; CWsziList lst; CWsziList::iterator itPath; LPCWSTR pwszScopePath = m_pmu->LpwszPathTranslated(); LPCWSTR pwszUrl = m_pmu->LpwszRequestUrl(); BOOL fShallow = FALSE; // Monarch Syntax: // FROM { SCOPE( [ 'Scope_Arguments' ] ) | View_Name } // Scope_Arguments = // ' [ Traversal_Type ] ( "Path" [ , "Path", ...] // Path can be URI or physical path // We verify every path should must be under our // virtual directory and we allow only one path // Note, if we ever want to accept multiple path, then // we need some extra code, mainly another for loop. // For now, talk with Joels, keep it this way. // if (!_wcsnicmp (pwszWordBegin, gc_wszScope, pwsz-pwszWordBegin)) { StringBuffer sbNameBuilder; LPCWSTR pwszStart = pwsz; ULONG cLevel = 0; BOOL fInSingleQuote = FALSE; sbNameBuilder.Append(static_cast(sizeof(WCHAR) * wcslen(pwszName.get())), pwszName.get()); // Parse the scope arguments list // while (*pwsz) { if (L'(' == *pwsz) { cLevel++; } else if (L')' == *pwsz) { if (NULL == (--cLevel)) break; } else if (L'\'' == *pwsz) { // If this is a closing single quote, flip the // switch. // if (fInSingleQuote) { // It's an error if no scope inside '' // if (!fScopeExist) { sc = E_INVALIDARG; goto ret; } // The next single quote will be an // opening single quote // fInSingleQuote = FALSE; } else { // We need to find out the traversal type // as we rely on Monarch to check syntax, so it // is OK for us to assume the syntax is correct, // Anything we missed can be caught later in // Monarch. // pwsz++; while (*pwsz && iswspace(*pwsz)) pwsz++; // Check if it is "Shallow Traversal", again // we check only the word "shallow", any syntax // error can be caught later in Monarch. // if (!_wcsnicmp(pwsz, gsc_wszShallow, gsc_cchShallow)) fShallow = TRUE; // The next single quote will be a closing // sinlge quote // fInSingleQuote = TRUE; // we've point to the next char, so loop back // immediately // continue; } } else if (L'"' == *pwsz) { auto_heap_ptr pwszPath; LPCWSTR pwszPathStart; // Copy over bytes up to '"'. // pwsz++; sbNameBuilder.Append(static_cast(sizeof(WCHAR) * (pwsz -pwszStart)), pwszStart); // Look for the start of scope // while ((*pwsz) && iswspace(*pwsz)) pwsz++; pwszPathStart = pwsz; // We really only allow a single // path in our scope. Fail others // with bad request // if (fScopeExist) { sc = E_INVALIDARG; goto ret; } // look for the end of the path // while (*(pwsz) && *pwsz != L'"') pwsz++; if (!(*pwsz)) break; fScopeExist = TRUE; // Translate the scope: // - forbid the physical path // - translate the URI and reject // any URI beyond our VR // if (pwsz > pwszPathStart) { UINT cchUrlT; if (!FTranslateScope (m_pmu, pwszPathStart, static_cast(pwsz-pwszPathStart), pwszPath)) { // return an error that would be mapped to // HSC_FORBIDDEN // sc = STG_E_DISKISWRITEPROTECTED; Assert (HSC_FORBIDDEN == HscFromHresult(sc)); goto ret; } // use the translated physical path // pwszScopePath = AppendChainedSz(m_csb, pwszPath); lst.push_back(CRCWszi(pwszScopePath)); // Allocate space for the URL and keep it hanging on // cchUrlT = static_cast(pwsz - pwszPathStart); if (NULL == pwszUrlT.resize(CbSizeWsz(cchUrlT))) { sc = E_OUTOFMEMORY; goto ret; } memcpy(pwszUrlT.get(), pwszPathStart, cchUrlT * sizeof(WCHAR)); pwszUrlT[cchUrlT] = L'\0'; pwszUrl = pwszUrlT.get(); } else { // we've got a "". Insert the request URI // Assert (pwsz == pwszPathStart); Assert ((*pwsz == L'"') && (*(pwsz-1) == L'"')); lst.push_back(CRCWszi(pwszScopePath)); } pwszStart = pwsz; } pwsz++; } // Syntax check // if (fInSingleQuote || !(*pwsz)) { // unbalanced ', " or ) // sc = E_INVALIDARG; goto ret; } // include ')' // pwsz++; if (!fScopeExist) { static WCHAR gs_wszScopeBegin[] = L"('\""; sbNameBuilder.Append(sizeof(WCHAR) * CchConstString(gs_wszScopeBegin), gs_wszScopeBegin); // Pickup the request uri // lst.push_back(CRCWszi(pwszScopePath)); } // Search Child Vroots only if we are doing // a non-shallow traversal. //$ REVIEW(zyang). // Here we drop the subvroot in the shallow search // This is not quite right, Assume we are searching /fs // and it has a sub vroot /fs/sub. we expect to see // /fs/sub in the search result. but we lost it. // However, if we include this sub vroot in the search // path, it's even worse, as a shallow traversal on // /fs/sub will give us all /fs/sub/*, which is another // level deep. // There's no easy fix for this, unless, we keep a list // of first level vroots and emit ourselves. That's // of extra code, and don't know how much it would buy us. // As a compromise for now, we simply drop the sub vroot // in shallow search. // if (!fShallow) { AddChildVrPaths (m_pmu, pwszUrl, m_csb, m_vrl, lst); } // Construct the scope // Assert (!lst.empty()); itPath = lst.begin(); sbNameBuilder.Append(static_cast(sizeof(WCHAR) * wcslen(itPath->m_pwsz)), itPath->m_pwsz); for (itPath++; itPath != lst.end(); itPath++) { static WCHAR gs_wszScopeMiddle[] = L"\", \""; sbNameBuilder.Append(sizeof(WCHAR) * CchConstString(gs_wszScopeMiddle), gs_wszScopeMiddle); sbNameBuilder.Append(static_cast(sizeof(WCHAR) * wcslen(itPath->m_pwsz)), itPath->m_pwsz); } static WCHAR gs_wszScopeEnd[] = L"\"')"; sbNameBuilder.Append(sizeof(WCHAR) * CchConstString(gs_wszScopeEnd), gs_wszScopeEnd); // Get the size of constructed string without NULL // termination // cLen = sbNameBuilder.CchSize(); // NULL terminate the string // sbNameBuilder.FTerminate(); // Replace with the new string // if (NULL == pwszName.resize(CbSizeWsz(cLen))) { sc = E_OUTOFMEMORY; goto ret; } lstrcpyW (pwszName.get(), sbNameBuilder.PContents()); Assert(cLen == wcslen(pwszName.get())); Assert(cLen+1 <= pwszName.celems()); // After the Scope is processed, the only thing that // we want to do is the custom properties. so we don't // care if the rest is a WHERE or an ORDER BY or else // state = SQL_MORE; } break; } case SQL_WHERE: case SQL_MORE: // It's not easy for us to tell which prop is custom prop // and thus need to be set to the command object. // without a real parse tree, we can't tell names from // operators and literals. // // a good guess is that if the prop is quoted by double // quote, we can treat it as a custom prop. Note, this // imposes the requirment that all props, including // namespaceless props must be quoted. all unquoted // are either Tripoli props or operators/literals which // we can just copy over. this makes our life easier // // We need to map some DAV reserved properties to tripoli // props when they appear in the where clause // if (fQuoted) { UINT irp = sc_crp_set_reserved; //max value sc = ScMapReservedPropInWhereClause (pwszName.get(), &irp); if (FAILED(sc)) goto ret; if (irp != sc_crp_set_reserved) cLen = static_cast(wcslen(pwszName.get())); else { // SET PROPERTYNAME on custom props // sc = ScSetPropertyName (m_pCommandText, pwszName.get()); if (FAILED(sc)) { DebugTrace ("Failed to set custom prop %ws to Monarch\n", pwszName.get()); goto ret; } } } break; default: break; } // Append the name // m_sbSQL.Append(sizeof(WCHAR)*cLen, pwszName.get()); if (L'"' != *pwsz) { // add seperator // m_sbSQL.Append(L" "); } } else if (L'\'' == *pwsz) { // copy literals over pwsz++; while (*pwsz && (L'\'' != *pwsz)) pwsz++; if (!*pwsz) { DebugTrace("unbalanced single quote\n"); sc = E_INVALIDARG; goto ret; } else { Assert(L'\'' == *pwsz); // copy over // pwsz++; m_sbSQL.Append( static_cast(pwsz-pwszWordBegin) * sizeof(WCHAR), pwszWordBegin); } // add seperator // m_sbSQL.Append(L" "); } else if (L'"' == *pwsz) { // toggle the flag // fQuoted = !fQuoted; pwsz++; m_sbSQL.Append(L"\""); // Apeend seperator after closing '"' // if (!fQuoted) m_sbSQL.Append(L" "); } else { // some char we don't have interest on, just copy over // while (*pwsz && !IsLegalVarChar(*pwsz) && (L'\'' != *pwsz) && (L'"' != *pwsz)) pwsz++; // Append the name // m_sbSQL.Append( static_cast(pwsz-pwszWordBegin) * sizeof(WCHAR), pwszWordBegin); } } // Close the string // m_sbSQL.Append(sizeof(WCHAR), L""); SearchTrace ("Search: translated query is: \"%ls\"\n", PwszSQL()); ret: return sc; } static void SafeWcsCopy (LPWSTR pwszDst, LPCWSTR pwszSrc) { // Make sure we are not doing any evil copies... // Assert (pwszDst && pwszSrc && (pwszDst <= pwszSrc)); if (pwszDst == pwszSrc) return; while (*pwszSrc) *pwszDst++ = *pwszSrc++; *pwszDst = L'\0'; return; } SCODE CFSSearch::ScEmitRow (CXMLEmitter& emitter) { auto_ref_ptr pMDData; CResourceInfo cri; CStackBuffer pwszExt; CVRList::iterator it; LPWSTR pwszFile; SCODE sc = S_OK; UINT cch; // Get the filename // pwszFile = reinterpret_cast(m_pData.get()); sc = cri.ScGetResourceInfo (pwszFile); if (FAILED (sc)) goto ret; // FSPropTarget sort of needs the URI of the target. // What is really important here, is the file extension. // We can fake it out by just pretending the file // is the URL name. // cch = pwszExt.celems(); sc = m_pmu->ScUrlFromStoragePath(pwszFile, pwszExt.get(), &cch); if (S_FALSE == sc) { if (NULL == pwszExt.resize(cch * sizeof(WCHAR))) { sc = E_OUTOFMEMORY; goto ret; } sc = m_pmu->ScUrlFromStoragePath(pwszFile, pwszExt.get(), &cch); } if (S_OK != sc) { Assert (S_FALSE != sc); goto ret; } // Strip the prefix // SafeWcsCopy(pwszExt.get(), PwszUrlStrippedOfPrefix(pwszExt.get())); // Emit the row (ie. call ScFindFileProps()) if-and-only-if // We know this url is to be indexed. In particular, can we // sniff the metabase, and is the index bit set. //$178052: We also need to respect the dirbrowsing bit // SearchTrace ("Search: found row at '%S'\n", pwszExt.get()); if (SUCCEEDED (m_pmu->HrMDGetData (pwszExt.get(), pMDData.load()))) { if (pMDData->FIsIndexed() && (pMDData->DwDirBrowsing() & MD_DIRBROW_ENABLED)) { // Find the properties // sc = ScFindFileProps (m_pmu, m_cfc, emitter, pwszExt.get(), pwszFile, NULL, cri, TRUE /*fEmbedErrorsInResponse*/); if (FAILED (sc)) goto ret; } else SearchTrace ("Search: found '%S' is not indexed\n", pwszExt); pMDData.clear(); } // See if any of the other translation contexts apply to this // path as well. // for (it = m_vrl.begin(); it != m_vrl.end(); it++) { auto_ref_ptr cvr; if (!m_pmu->FGetChildVRoot (it->m_pwsz, cvr)) continue; cch = pwszExt.celems(); sc = ScUrlFromSpannedStoragePath (pwszFile, *(cvr.get()), pwszExt.get(), &cch); if (S_FALSE == sc) { if (NULL == pwszExt.resize(cch * sizeof(WCHAR))) { sc = E_OUTOFMEMORY; goto ret; } sc = ScUrlFromSpannedStoragePath (pwszFile, *(cvr.get()), pwszExt.get(), &cch); } if (S_OK == sc) { SafeWcsCopy (pwszExt.get(), PwszUrlStrippedOfPrefix(pwszExt.get())); SearchTrace ("Search: found row at '%S'\n", pwszExt.get()); // Again, we have to see if this resource is even allowed // to be indexed... // LPCWSTR pwszMbPathVRoot; CStackBuffer pwszMbPathChild; UINT cchPrefix; UINT cchUrl = static_cast(wcslen(pwszExt.get())); // Map the URI to its equivalent metabase path, and make sure // the URL is stripped before we call into the MDPath processing // cchPrefix = cvr->CchPrefixOfMetabasePath (&pwszMbPathVRoot); if (NULL == pwszMbPathChild.resize(CbSizeWsz(cchPrefix + cchUrl))) { sc = E_OUTOFMEMORY; goto ret; } memcpy (pwszMbPathChild.get(), pwszMbPathVRoot, cchPrefix * sizeof(WCHAR)); memcpy (pwszMbPathChild.get() + cchPrefix, pwszExt.get(), (cchUrl + 1) * sizeof(WCHAR)); // As above, Emit the row (ie. call ScFindFileProps()) // if-and-only-if We know this url is to be indexed. // In particular, can we sniff the metabase, and is // the index bit set. // if (SUCCEEDED(m_pmu->HrMDGetData (pwszMbPathChild.get(), pwszMbPathVRoot, pMDData.load()))) { if (pMDData->FIsIndexed()) { // ... and get the properties // sc = ScFindFileProps (m_pmu, m_cfc, emitter, pwszExt.get(), pwszFile, cvr.get(), cri, TRUE /*fEmbedErrorsInResponse*/); if (FAILED (sc)) goto ret; } else SearchTrace ("Search: found '%S' is not indexed\n", pwszExt); } } } sc = S_OK; ret: return sc; } SCODE CFSSearch::ScCreateAccessor() { SCODE sc = S_OK; DBORDINAL cCols = 0; auto_com_ptr pColInfo; // QI to the IColumnsInfo interface, with which we can get the column information // sc = m_prs->QueryInterface (IID_IColumnsInfo, reinterpret_cast(pColInfo.load())); if (FAILED(sc)) goto ret; // get all the column information // sc = pColInfo->GetColumnInfo (&cCols, &m_rgInfo, &m_pwszBuf); if (FAILED(sc)) goto ret; // 'Path' is the only property in our SELECT list // Assert (cCols == 1); m_rgBindings = (DBBINDING *) g_heap.Alloc (sizeof (DBBINDING)); // set the m_rgBindings according to the information we know // m_rgBindings->dwPart = DBPART_VALUE | DBPART_STATUS; // ignored fields // m_rgBindings->eParamIO = DBPARAMIO_NOTPARAM; // set column ordinal // m_rgBindings->iOrdinal = m_rgInfo->iOrdinal; // set the type // m_rgBindings->wType = m_rgInfo->wType; // we own the memory // m_rgBindings->dwMemOwner = DBMEMOWNER_CLIENTOWNED; // set the maximum length of the column // Assert (m_rgInfo->wType == DBTYPE_WSTR); m_rgBindings->cbMaxLen = m_rgInfo->ulColumnSize * sizeof(WCHAR); // offset to the value in the consumer's buffer // m_rgBindings->obValue = 0; // offset to the status // m_rgBindings->obStatus = Align8(m_rgBindings->cbMaxLen); // we'll see how to deal with objects as we know more // m_rgBindings->pObject = NULL; // not used field // m_rgBindings->pTypeInfo = NULL; m_rgBindings->pBindExt = NULL; m_rgBindings->dwFlags = 0; // Create the accessor // sc = m_pAcc->CreateAccessor (DBACCESSOR_ROWDATA, // row accessor 1, // number of bindings m_rgBindings, // array of bindings 0, // cbRowSize, not used &m_hAcc, // HACCESSOR * NULL); // binding status if (FAILED(sc)) goto ret; ret: return sc; } SCODE CFSSearch::ScMakeQuery() { SCODE sc = S_OK; // Make sure we have a query to play with // m_pCommandText is initialized in ScSetSQL, if m_pCommantText // is NULL, most likely is becuase ScSetSQL is not called // if (!PwszSQL() || !m_pCommandText.get()) { sc = E_DAV_NO_QUERY; goto ret; } // Set the command text // sc = m_pCommandText->SetCommandText (DBGUID_DEFAULT, PwszSQL()); if (FAILED (sc)) { DebugTrace("pCommandText->SetCommandText failed\n"); goto ret; } // Excute the query // sc = m_pCommandText->Execute (NULL, IID_IRowset, 0, 0, reinterpret_cast(m_prs.load())); if (FAILED(sc) || (!m_prs)) { DebugTrace("pCommandText->Execute failed\n"); // Munge a few, select error codes // Map these errors locally, as they may only come back from Execute // switch (sc) { case QUERY_E_FAILED: //$REVIEW: Is this a bad request? case QUERY_E_INVALIDQUERY: case QUERY_E_INVALIDRESTRICTION: case QUERY_E_INVALIDSORT: case QUERY_E_INVALIDCATEGORIZE: case QUERY_E_ALLNOISE: case QUERY_E_TOOCOMPLEX: case QUERY_E_TIMEDOUT: //$REVIEW: Is this a bad request? case QUERY_E_DUPLICATE_OUTPUT_COLUMN: case QUERY_E_INVALID_OUTPUT_COLUMN: case QUERY_E_INVALID_DIRECTORY: case QUERY_E_DIR_ON_REMOVABLE_DRIVE: case QUERY_S_NO_QUERY: sc = E_INVALIDARG; // All query errors will be mapped to 400 break; } goto ret; } ret: return sc; } // DAV-Search Implementation ------------------------------------------------- // class CSearchRequest : public CMTRefCounted, private IAsyncIStreamObserver { // // Reference to the CMethUtil // auto_ref_ptr m_pmu; // Contexts // auto_ref_ptr m_pnfs; CFSSearch m_csc; // Request body as an IStream. This stream is async -- it can // return E_PENDING from Read() calls. // auto_ref_ptr m_pstmRequest; // The XML parser used to parse the request body using // the node factory above. // auto_ref_ptr m_pxprs; // IAsyncIStreamObserver // VOID AsyncIOComplete(); // State functions // VOID ParseBody(); VOID DoSearch(); VOID SendResponse( SCODE sc ); // NOT IMPLEMENTED // CSearchRequest (const CSearchRequest&); CSearchRequest& operator= (const CSearchRequest&); public: // CREATORS // CSearchRequest(LPMETHUTIL pmu) : m_pmu(pmu), m_csc(pmu) { } // MANIPULATORS // VOID Execute(); }; VOID CSearchRequest::Execute() { CResourceInfo cri; LPCWSTR pwsz; LPCWSTR pwszPath = m_pmu->LpwszPathTranslated(); SCODE sc = S_OK; // // First off, tell the pmu that we want to defer the response. // Even if we send it synchronously (i.e. due to an error in // this function), we still want to use the same mechanism that // we would use for async. // m_pmu->DeferResponse(); // Do ISAPI application and IIS access bits checking // sc = m_pmu->ScIISCheck (m_pmu->LpwszRequestUrl(), MD_ACCESS_READ); if (FAILED(sc)) { // Either the request has been forwarded, or some bad error occurred. // In either case, quit here and map the error! // SendResponse(sc); return; } // Look to see the Content-length - required for this operation // to continue. // // if (NULL == m_pmu->LpwszGetRequestHeader (gc_szContent_Length, FALSE)) { pwsz = m_pmu->LpwszGetRequestHeader (gc_szTransfer_Encoding, FALSE); if (!pwsz || _wcsicmp (pwsz, gc_wszChunked)) { DavTrace ("Dav: PUT: missing content-length in request\n"); SendResponse(E_DAV_MISSING_LENGTH); return; } } // Search must have a content-type header and value must be text/xml // sc = ScIsContentTypeXML (m_pmu.get()); if (FAILED(sc)) { DebugTrace ("Dav: PROPPATCH fails without specifying a text/xml contenttype\n"); SendResponse(sc); return; } // Check to see if the resource exists // sc = cri.ScGetResourceInfo (pwszPath); if (FAILED (sc)) { SendResponse(sc); return; } // Ensure the URI and resource match // (void) ScCheckForLocationCorrectness (m_pmu.get(), cri, NO_REDIRECT); // Check state headers here. // sc = HrCheckStateHeaders (m_pmu.get(), pwszPath, FALSE); if (FAILED (sc)) { DebugTrace ("DavFS: If-State checking failed.\n"); SendResponse(sc); return; } // BIG NOTE ABOUT LOCKING // // The mechanism we actually use to do the search doesn't // have any way to access our locked files. So we are punting // on supporting locktokens passed into SEARCH. // So, for now, on DAVFS, don't bother to check locktokens. // (This isn't a big problem because currently DAVFS can only // lock single files, not whole directories, AND becuase currently // our only locktype is WRITE, so our locks won't prevent the // content-indexer from READING the file!) // // NOTE: We still have to consider if-state-match headers, // but that is done elsewhere (above -- HrCheckStateHeaders). // // Instantiate the XML parser // m_pnfs.take_ownership(new CNFSearch(m_csc)); m_pstmRequest.take_ownership(m_pmu->GetRequestBodyIStream(*this)); sc = ScNewXMLParser( m_pnfs.get(), m_pstmRequest.get(), m_pxprs.load() ); if (FAILED(sc)) { DebugTrace( "CSearchRequest::Execute() - ScNewXMLParser() failed (0x%08lX)\n", sc ); SendResponse(sc); return; } // Start parsing it into the context // ParseBody(); } VOID CSearchRequest::ParseBody() { Assert( m_pxprs.get() ); Assert( m_pnfs.get() ); Assert( m_pstmRequest.get() ); // // Add a ref for the following async operation. // Use auto_ref_ptr rather than AddRef() for exception safety. // auto_ref_ptr pRef(this); SCODE sc = ScParseXML (m_pxprs.get(), m_pnfs.get()); if ( SUCCEEDED(sc) ) { Assert( S_OK == sc || S_FALSE == sc ); DoSearch(); } else if ( E_PENDING == sc ) { // // The operation is pending -- AsyncIOComplete() will take ownership // ownership of the reference when it is called. // pRef.relinquish(); } else { DebugTrace( "CSearchRequest::ParseBody() - ScParseXML() failed (0x%08lX)\n", sc ); SendResponse(sc); } } VOID CSearchRequest::AsyncIOComplete() { // Take ownership of the reference added for the async operation. // auto_ref_ptr pRef; pRef.take_ownership(this); ParseBody(); } VOID CSearchRequest::DoSearch() { SCODE sc; // Do the search // sc = m_csc.ScMakeQuery(); if (FAILED (sc)) { SendResponse(sc); return; } // All header must be emitted before chunked XML emitting starts // m_pmu->SetResponseHeader (gc_szContent_Type, gc_szText_XML); // Set the response code and go // m_pmu->SetResponseCode( HscFromHresult(W_DAV_PARTIAL_SUCCESS), NULL, 0, CSEFromHresult(W_DAV_PARTIAL_SUCCESS) ); // Emit the results // auto_ref_ptr pmsr; auto_ref_ptr pxb; // Get the XML body // pxb.take_ownership (new CXMLBody (m_pmu.get())); pmsr.take_ownership (new CXMLEmitter(pxb.get(), m_csc.PPreloadNamespaces())); sc = pmsr->ScSetRoot (gc_wszMultiResponse); if (FAILED (sc)) { SendResponse(sc); return; } sc = m_csc.ScEmitResults (*pmsr); if (FAILED (sc)) { SendResponse(sc); return; } // Done with the reponse // pmsr->Done(); m_pmu->SendCompleteResponse(); } VOID CSearchRequest::SendResponse( SCODE sc ) { // // Set the response code and go // m_pmu->SetResponseCode( HscFromHresult(sc), NULL, 0, CSEFromHresult(sc) ); m_pmu->SendCompleteResponse(); } void DAVSearch (LPMETHUTIL pmu) { auto_ref_ptr pRequest(new CSearchRequest(pmu)); pRequest->Execute(); } // class CSearchRowsetContext ------------------------------------------------ // enum { CROW_GROUP = 16 }; // Mapping from DBSTATUS to HSC. // ULONG CSearchRowsetContext::HscFromDBStatus (ULONG ulStatus) { switch (ulStatus) { case DBSTATUS_S_OK: case DBSTATUS_S_ISNULL: case DBSTATUS_S_TRUNCATED: case DBSTATUS_S_DEFAULT: return HSC_OK; case DBSTATUS_E_BADACCESSOR: return HSC_BAD_REQUEST; case DBSTATUS_E_UNAVAILABLE: return HSC_NOT_FOUND; case DBSTATUS_E_PERMISSIONDENIED: return HSC_UNAUTHORIZED; case DBSTATUS_E_DATAOVERFLOW: return HSC_INSUFFICIENT_SPACE; case DBSTATUS_E_CANTCONVERTVALUE: case DBSTATUS_E_SIGNMISMATCH: case DBSTATUS_E_CANTCREATE: case DBSTATUS_E_INTEGRITYVIOLATION: case DBSTATUS_E_SCHEMAVIOLATION: case DBSTATUS_E_BADSTATUS: // What error shoud these match to? // return 400 temporarily. // return HSC_BAD_REQUEST; default: TrapSz ("New DBStutus value"); return HSC_NOT_FOUND; } } SCODE CSearchRowsetContext::ScEmitResults (CXMLEmitter& emitter) { SCODE sc = S_OK; BOOL fReadAll = FALSE; // Allocate enough space for the data buffer // if (!m_pData) { ULONG_PTR cbSize; // Get the IAccessor interface, used later to release the accessor // sc = m_prs->QueryInterface (IID_IAccessor, (LPVOID *)&m_pAcc); if (FAILED(sc)) goto ret; // Create the accessor // sc = ScCreateAccessor(); if (FAILED(sc)) goto ret; // Calculate the size of the buffer needed by each row. // (including a ULONG for status) // cbSize = Align8(m_rgBindings->cbMaxLen) + Align8(sizeof(ULONG)); // allocate enough memory for the data buffer on stack // m_pData = (BYTE *)g_heap.Alloc(cbSize); } while (!fReadAll) { sc = m_prs->GetNextRows(NULL, 0, CROW_GROUP, (DBCOUNTITEM *) &m_cHRow, &m_rgHRow); if (sc) { if (sc == DB_S_ENDOFROWSET) { // we have read all the rows, we'll be done after this loop // fReadAll = TRUE; } else goto ret; } if (!m_cHRow) { // no rows available, this happens when no rows in the rowset at all // break; } AssertSz (m_rgHRow, "something really bad happened"); // For each row we have now, get data and convert it to XML and dump to the stream. // for (ULONG ihrow = 0; ihrow < m_cHRow; ihrow++) { AssertSz(m_rgHRow[ihrow], "returned row handle is NULL"); // get the data of one row. // sc = m_prs->GetData(m_rgHRow[ihrow], m_hAcc, m_pData); if (FAILED(sc) && (sc != DB_E_ERRORSOCCURRED)) goto ret; // Emit the row // sc = ScEmitRow (emitter); if (FAILED(sc)) goto ret; } // Don't forget to clean up. // sc = m_prs->ReleaseRows (m_cHRow, m_rgHRow, NULL, NULL, NULL); if (FAILED(sc)) goto ret; // free the memory retured from OLEDB provider with IMalloc::Free // CoTaskMemFree (m_rgHRow); m_rgHRow = NULL; m_cHRow = 0; } ret: CleanUp(); return sc; } VOID CSearchRowsetContext::CleanUp() { // Try out best to clean up // clean the array of HRows // if (m_rgHRow) { m_prs->ReleaseRows (m_cHRow, m_rgHRow, NULL, NULL, NULL); CoTaskMemFree (m_rgHRow); } // Release the accessor handle // if (m_hAcc != DB_INVALID_HACCESSOR) { m_pAcc->ReleaseAccessor (m_hAcc, NULL); } }