You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1841 lines
44 KiB
1841 lines
44 KiB
/*
|
|
* 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 <msidxs.h>
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#include "_fssrch.h"
|
|
#include <oledberr.h>
|
|
#include <cierror.h>
|
|
|
|
// 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<CDBCreateCommand, SCODE *>
|
|
{
|
|
//
|
|
// Friend declarations required by OnDemandGlobal template
|
|
//
|
|
friend class Singleton<CDBCreateCommand>;
|
|
friend class RefCountedGlobal<CDBCreateCommand, SCODE *>;
|
|
|
|
//
|
|
// Pointer to the IDBCreateCommand object
|
|
//
|
|
auto_com_ptr<IDBCreateCommand> 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<IDBInitialize> pDBInit;
|
|
auto_com_ptr<IDBCreateSession> 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<WCHAR>& pwszPath)
|
|
{
|
|
SCODE sc = S_OK;
|
|
|
|
CStackBuffer<WCHAR,MAX_PATH> pwszTerminatedURI;
|
|
CStackBuffer<WCHAR,MAX_PATH> 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<WCHAR,MAX_FULLY_QUALIFIED_LENGTH> pwszSet;
|
|
auto_com_ptr<IRowset> 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<WCHAR>& sb,
|
|
CVRList& vrl,
|
|
CWsziList& lst)
|
|
{
|
|
CVRList::iterator it;
|
|
ChainedStringBuffer<WCHAR> 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<CVRoot> 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 <no mapping>
|
|
// DAV:getetag <no mapping>
|
|
// DAV:lockdiscovery <no mapping>
|
|
// DAV:supportedlock <no mapping>
|
|
|
|
// 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<WCHAR,128> 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<LPWSTR>(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<WCHAR> 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<UINT>(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<WCHAR> sbNameBuilder;
|
|
LPCWSTR pwszStart = pwsz;
|
|
ULONG cLevel = 0;
|
|
BOOL fInSingleQuote = FALSE;
|
|
|
|
sbNameBuilder.Append(static_cast<UINT>(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<WCHAR> pwszPath;
|
|
LPCWSTR pwszPathStart;
|
|
|
|
// Copy over bytes up to '"'.
|
|
//
|
|
pwsz++;
|
|
|
|
sbNameBuilder.Append(static_cast<UINT>(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<UINT>(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<UINT>(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<UINT>(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<UINT>(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<UINT>(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<UINT>(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<UINT>(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<IMDData> pMDData;
|
|
CResourceInfo cri;
|
|
CStackBuffer<WCHAR,128> pwszExt;
|
|
CVRList::iterator it;
|
|
LPWSTR pwszFile;
|
|
SCODE sc = S_OK;
|
|
UINT cch;
|
|
|
|
// Get the filename
|
|
//
|
|
pwszFile = reinterpret_cast<LPWSTR>(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<CVRoot> 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<WCHAR,128> pwszMbPathChild;
|
|
UINT cchPrefix;
|
|
UINT cchUrl = static_cast<UINT>(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<IColumnsInfo> pColInfo;
|
|
|
|
// QI to the IColumnsInfo interface, with which we can get the column information
|
|
//
|
|
sc = m_prs->QueryInterface (IID_IColumnsInfo,
|
|
reinterpret_cast<VOID**>(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<IUnknown**>(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<CMethUtil> m_pmu;
|
|
|
|
// Contexts
|
|
//
|
|
auto_ref_ptr<CNFSearch> 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<IStream> m_pstmRequest;
|
|
|
|
// The XML parser used to parse the request body using
|
|
// the node factory above.
|
|
//
|
|
auto_ref_ptr<IXMLParser> 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<CSearchRequest> 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<CSearchRequest> 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<CXMLEmitter> pmsr;
|
|
auto_ref_ptr<CXMLBody> 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<CSearchRequest> 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);
|
|
}
|
|
}
|