Leaked source code of windows server 2003
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.
 
 
 
 
 
 

1206 lines
33 KiB

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// LOCKUTIL.CPP
//
// HTTP 1.1/DAV 1.0 LOCK request handling UTILITIES
//
//
// Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
//
#include "_davfs.h"
#include <tchar.h> //_strspnp
#include <statetok.h>
#include <xlock.h>
#include "_shlkmgr.h"
// ========================================================================
//
// ScLockDiscoveryFromSNewLockData
//
// Takes an emitter and an already-constructed lockdiscovery node,
// and adds an activelock node for this CLock under it.
// May be called multiple times -- each call will add a new activelock
// node under the lockdiscovery node in en.
//$HACK:ROSEBUD_OFFICE9_TIMEOUT_HACK
// For the bug where rosebud waits until the last second
// before issueing the refresh. Need to filter out this check with
// the user agent string. The hack is to increase the timeout
// by 30 seconds and return the actual timeout. So we
// need the ecb/pmu to findout the user agent. If we
// remove this hack ever (I doubt if we can ever do that), then
// change the interface of ScLockDiscoveryFromCLock.
//$HACK:END ROSEBUD_OFFICE9_TIMEOUT_HACK
//
SCODE
ScLockDiscoveryFromSNewLockData(LPMETHUTIL pmu,
CXMLEmitter& emitter,
CEmitterNode& en,
SNewLockData * pnld,
LPCWSTR pwszLockToken)
{
BOOL fRollback;
BOOL fDepthInfinity;
DWORD dwLockScope;
DWORD dwLockType;
LPCWSTR pwszLockScope = NULL;
LPCWSTR pwszLockType = NULL;
HRESULT hr = S_OK;
DWORD dwSeconds = 0;
Assert(pmu);
Assert(pnld);
// Get the lock flags from the lock.
//
dwLockType = pnld->m_dwLockType;
// Note if the lock is a rollback
//
fRollback = !!(dwLockType & DAV_LOCKTYPE_ROLLBACK);
// Note if the lock is recursive
//
fDepthInfinity = !!(dwLockType & DAV_RECURSIVE_LOCK);
// Write lock?
//
if (dwLockType & GENERIC_WRITE)
{
pwszLockType = gc_wszLockTypeWrite;
}
#ifdef DBG
if (dwLockType & GENERIC_READ)
{
pwszLockType = L"read";
}
#else // !DBG
else
{
TrapSz ("Unexpected lock type!");
}
#endif // DBG, else
// Lock scope
//
dwLockScope = pnld->m_dwLockScope;
if (dwLockScope & DAV_SHARED_LOCK)
{
pwszLockScope = gc_wszLockScopeShared;
}
else
{
Assert (dwLockScope & DAV_EXCLUSIVE_LOCK);
pwszLockScope = gc_wszLockScopeExclusive;
}
dwSeconds = pnld->m_dwSecondsTimeout;
//$HACK:ROSEBUD_OFFICE9_TIMEOUT_HACK
// For the bug where rosebud waits until the last second
// before issueing the refresh. Need to filter out this check with
// the user agent string. The hack is to increase the timeout
// by 30 seconds. Now decrease 30 seconds to send requested timeout.
//
if (pmu && pmu->FIsOffice9Request())
{
if (dwSeconds > gc_dwSecondsHackTimeoutForRosebud)
{
dwSeconds -= gc_dwSecondsHackTimeoutForRosebud;
}
}
//$HACK: END: ROSEBUD_OFFICE9_TIMEOUT_HACK
// Construct the lockdiscovery node
//
hr = ScBuildLockDiscovery (emitter,
en,
pwszLockToken,
pwszLockType,
pwszLockScope,
fRollback,
fDepthInfinity,
dwSeconds,
pnld->m_pwszOwnerComment,
NULL);
if (FAILED (hr))
{
goto ret;
}
ret:
return hr;
}
// ------------------------------------------------------------------------
//
// ScAddSupportedLockProp
//
// Add a lockentry node with the listed information.
// NOTE: wszExtra is currently used for rollback information.
//
SCODE
ScAddSupportedLockProp (CEmitterNode& en,
LPCWSTR wszLockType,
LPCWSTR wszLockScope,
LPCWSTR wszExtra = NULL)
{
CEmitterNode enEntry;
SCODE sc = S_OK;
Assert (wszLockType);
Assert (wszLockScope);
// Create a lockentry node to hold this info.
//
sc = en.ScAddNode (gc_wszLockEntry, enEntry);
if (FAILED (sc))
goto ret;
// Create a node for the locktype under the lockentry.
//
{
// Must scope here, all sibling nodes must be constructed sequentially
//
CEmitterNode enType;
sc = enEntry.ScAddNode (wszLockType, enType);
if (FAILED (sc))
goto ret;
}
// Create a node for the locktype under the lockentry.
//
{
// Must scope here, all sibling nodes must be constructed sequentially
//
CEmitterNode enScope;
sc = enEntry.ScAddNode (wszLockScope, enScope);
if (FAILED (sc))
goto ret;
}
// If we have extra info, create a node for it under the lockentry.
//
if (wszExtra)
{
// Must scope here, all sibling nodes must be constructed sequentially
//
CEmitterNode enExtra;
sc = enEntry.ScAddNode (wszExtra, enExtra);
if (FAILED (sc))
goto ret;
}
ret:
return sc;
}
// ------------------------------------------------------------------------
//
// HrGetLockProp
//
// Get the requested lock property for the requested resource.
// (The lock properties are lockdiscovery and supportedlock.)
// Lockdiscovery and supportedlock should ALWAYS be found --
// they are required DAV: properties. Add an empty node if there is
// no real data to return.
// NOTE: This function still assumes that write is the only locktype.
// It will NOT add read/mixed locktypes.
//
// Returns
// S_FALSE if prop not found/not recognized.
// error only if something really bad happens.
//
//$REVIEW: Should I return the depth element too? -- No (for now).
//$REVIEW: Spec does NOT list depth under the lockentry XML element.
//
HRESULT
HrGetLockProp (LPMETHUTIL pmu,
LPCWSTR wszPropName,
LPCWSTR wszResource,
RESOURCE_TYPE rtResource,
CXMLEmitter& emitter,
CEmitterNode& enParent)
{
SCODE sc = S_OK;
Assert (pmu);
Assert (wszPropName);
Assert (wszResource);
if (!wcscmp (wszPropName, gc_wszLockDiscovery))
{
// Fill in lockdiscovery info.
//
// Check for any lock in our lock cache.
// This call will scan the lock cache for any matching items
// and add a 'DAV:activelock' node for each match.
// We pass in DAV_LOCKTYPE_FLAGS so that we will find all matches.
//
if (!CSharedLockMgr::Instance().FGetLockOnError (pmu,
wszResource,
DAV_LOCKTYPE_FLAGS,
TRUE, // Emit XML body
&emitter,
enParent.Pxn()))
{
// This resource is not in our lock cache.
//
FsLockTrace ("HrGetLockProp -- No locks found for lockdiscovery.\n");
// And return. This is a SUCCESS case!
//
}
}
else if (!wcscmp (wszPropName, gc_wszLockSupportedlock))
{
DWORD dwLockType;
CEmitterNode en;
// Construct the 'DAV:supportedlock' node
//
sc = en.ScConstructNode (emitter, enParent.Pxn(), gc_wszLockSupportedlock);
if (FAILED (sc))
goto ret;
// Get the list of supported lock flags from the impl.
//
dwLockType = DwGetSupportedLockType (rtResource);
if (!dwLockType)
{
// No locktypes are supported. We already have our empty
// supportedlock node.
// Just return. This is a SUCCESS case!
goto ret;
}
// Add a lockentry node under the supportedlock node for each
// combination of flags that we detect.
//
// NOTE: Currently, write is the only allowed access type.
//
if (dwLockType & GENERIC_WRITE)
{
// Add a lockentry for each lockscope in the flags.
//
if (dwLockType & DAV_SHARED_LOCK)
{
sc = ScAddSupportedLockProp (en,
gc_wszLockTypeWrite,
gc_wszLockScopeShared);
if (FAILED (sc))
goto ret;
// If we support lock rollback, add another lockentry for this combo.
//
if (dwLockType & DAV_LOCKTYPE_ROLLBACK)
{
sc = ScAddSupportedLockProp (en,
gc_wszLockTypeWrite,
gc_wszLockScopeShared,
gc_wszLockRollback);
if (FAILED (sc))
goto ret;
}
}
if (dwLockType & DAV_EXCLUSIVE_LOCK)
{
sc = ScAddSupportedLockProp (en,
gc_wszLockTypeWrite,
gc_wszLockScopeExclusive);
if (FAILED (sc))
goto ret;
// If we support lock rollback, add another lockentry for this combo.
//
if (dwLockType & DAV_LOCKTYPE_ROLLBACK)
{
sc = ScAddSupportedLockProp (en,
gc_wszLockTypeWrite,
gc_wszLockScopeExclusive,
gc_wszLockRollback);
if (FAILED (sc))
goto ret;
}
}
}
}
else
{
// Unrecognized lock property. So we clearly do not have one
//
sc = S_FALSE;
goto ret;
}
ret:
return sc;
}
// ------------------------------------------------------------------------
//
// FLockViolation
//
// TRUE return here means that we found a lock, and sent the response.
//
//$LATER: Need to be able to return an error here!
//
BOOL
FLockViolation (LPMETHUTIL pmu, HRESULT hr, LPCWSTR pwszPath, DWORD dwAccess)
{
BOOL fFound = FALSE;
SCODE sc = S_OK;
auto_ref_ptr<CXMLBody> pxb;
auto_ref_ptr<CXMLEmitter> emitter;
Assert (pmu);
Assert (pwszPath);
AssertSz (dwAccess, "FLockViolation: Looking for a lock with no access!");
// Construct the root ('DAV:prop') for the lock response
//$NOTE: this xml body is created NOT chunked
//
pxb.take_ownership (new CXMLBody (pmu, FALSE) );
emitter.take_ownership (new CXMLEmitter(pxb.get()));
sc = emitter->ScSetRoot (gc_wszProp);
if (FAILED (sc))
goto ret;
// If the error code is one of the "locked" error codes,
// check our lock cache for a corresponding lock object.
//
if ((ERROR_SHARING_VIOLATION == ((SCODE)hr) ||
HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION) == hr ||
STG_E_SHAREVIOLATION == hr) &&
CSharedLockMgr::Instance().FGetLockOnError (pmu, pwszPath, dwAccess, TRUE, emitter.get(), emitter->PxnRoot()))
{
// Set our found bit to TRUE now, so that we'll report the lock's
// existence, even if the emitting below fails!
// NOTE: This is important for scenarios, like HTTPEXT PROPPATCH
// and destination deletion for Overwrite handling, that
// PRE-check the lock cache (protocol-enforced locks)
// before trying to hit the file.
//
fFound = TRUE;
// Set content type header
//
pmu->SetResponseHeader (gc_szContent_Type, gc_szText_XML);
// Must set the response code before we set the body data.
//
pmu->SetResponseCode (HSC_LOCKED, NULL, 0);
// Emit the XML body
//
emitter->Done();
}
// Tell our caller if we found any locks on this item.
//
ret:
return fFound;
}
// ------------------------------------------------------------------------
//
// HrLockIdFromString
//
// Returns S_OK on success (syntax check and conversion).
// Returns E_DAV_INVALID_HEADER on syntax error or non-matching token guid (not ours).
// Returns other errors if something fatal happened.
//
HRESULT
HrLockIdFromString (LPMETHUTIL pmu,
LPCWSTR pwszToken,
LARGE_INTEGER * pliLockID)
{
HRESULT hr = S_OK;
LPCWSTR pwsz = pwszToken;
UINT cchGUIDString = gc_cchMaxGuid;
WCHAR rgwszGUIDString[gc_cchMaxGuid];
Assert (pmu);
Assert (pwszToken);
Assert (pliLockID);
(*pliLockID).QuadPart = 0;
// Skip any initial whitespace.
//
pwsz = _wcsspnp (pwsz, gc_wszLWS);
if (!pwsz)
{
FsLockTrace ("Dav: Invalid locktoken in HrLockIdFromString.\n");
hr = E_DAV_INVALID_HEADER;
goto ret;
}
// Skip delimiter: double-quotes or angle-brackets.
// It's okay if no delimiter is present. Caller just passed us raw locktoken string.
//
if (L'\"' == *pwsz ||
L'<' == *pwsz)
pwsz++;
if (wcsncmp (gc_wszOpaquelocktokenPrefix, pwsz, gc_cchOpaquelocktokenPrefix))
{
FsLockTrace ("Dav: Lock token is missing opaquelocktoken: prefix.\n");
hr = E_DAV_INVALID_HEADER;
goto ret;
}
// Skip the opaquelocktoken: prefix
//
pwsz += gc_cchOpaquelocktokenPrefix;
// Compare GUIDS here
//
hr = CSharedLockMgr::Instance().HrGetGUIDString(pmu->HitUser(),
cchGUIDString,
rgwszGUIDString,
&cchGUIDString);
if (FAILED(hr))
{
goto ret;
}
// Subtract L'\0' termination
//
Assert(cchGUIDString);
cchGUIDString--;
if (_wcsnicmp(pwsz, rgwszGUIDString, cchGUIDString))
{
FsLockTrace ("Dav: Error comparing guids -- not our locktoken!\n");
hr = E_DAV_INVALID_HEADER;
goto ret;
}
// Skip the GUID, go to the lockid string.
//
pwsz = wcschr (pwsz, L':');
if (!pwsz)
{
FsLockTrace ("Dav: Error skipping guid of opaquelocktoken.\n");
hr = E_DAV_INVALID_HEADER;
goto ret;
}
// And skip the colon separator.
//
Assert (L':' == *pwsz);
pwsz++;
// Convert the string to lockID and return (this one actually has boundary
// condition that is not covered - lockID can actually be 0 in theory if there
// were so many locks that we rolled over)
//
(*pliLockID).QuadPart = _wtoi64(pwsz);
if (0 == (*pliLockID).QuadPart)
{
hr = E_DAV_INVALID_HEADER;
goto ret;
}
ret:
return hr;
}
// ------------------------------------------------------------------------
// HrValidTokenExpression()
//
// Helper function for If: header processing.
// Once we've found a token, this function will check the path.
// (So this function only succeeds completely if the token is still valid,
// AND the token matches the provided path.)
// If this token is valid, this function returns S_OK
// If this token is not valid, this function returns E_DAV_INVALID_HEADER
// If other fatal errors occured we propogate them out of the function
//
HRESULT
HrValidTokenExpression (IMethUtil * pmu,
LPCWSTR pwszToken,
LPCWSTR pwszPath,
OUT LARGE_INTEGER * pliLockID)
{
HRESULT hr = S_OK;
LARGE_INTEGER liLockID;
Assert (pmu);
Assert (pwszToken);
Assert (pwszPath);
// Get the lock tokens
//
hr = HrLockIdFromString (pmu, pwszToken, &liLockID);
if (FAILED(hr))
{
// Unrecognized locktoken. Does not match.
//
goto ret;
}
// Check if the locktoken is valid (live in the cache).
// E_DAV_INVALID_HEADER means the lock was not found,
// paths conflicted or owners were not the same
//
hr = CSharedLockMgr::Instance().HrCheckLockID(liLockID,
pmu->HitUser(),
pwszPath);
if (FAILED(hr))
{
if (E_DAV_LOCK_NOT_FOUND == hr ||
HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) == hr ||
E_DAV_CONFLICTING_PATHS == hr)
{
hr = E_DAV_INVALID_HEADER;
}
goto ret;
}
// If they requested the lock id back, give it to 'em.
//
if (pliLockID)
{
*pliLockID = liLockID;
}
ret:
return hr;
}
// ------------------------------------------------------------------------
//
// HrCheckIfHeader
//
// Check the If header.
// Processing will check the lock cache to validate locktokens.
//
// The pmu (IMethUtil) is provided for access to the lock cache to check tokens.
// The pwszPath provides the path to match for untagged lists.
//
// Format of the If header
// If = "If" ":" ( 1*No-tag-list | 1*Tagged-list)
// No-tag-list = List
// Tagged-list = Resource 1*List
// Resource = Coded-url
// List = "(" 1*(["Not"](State-token | "[" entity-tag "]")) ")"
// State-token = Coded-url
// Coded-url = "<" URI ">"
// Basically, one thing has to match in the whole header in order for the
// entire header to be "good".
// Each URI has a _set_ of state lists. A list is enclosed in parentheses.
// Each list is a logical "and".
// A set of lists is a logical "or".
//
// Returns:
// S_OK Process the method.
// other error Map the error
// (412 will be handled by this case)
//
// DAV-compliance shortfalls
// We fall short of true DAV-compliance in three spots in this function.
// 1 - This code does not prevent (fail) an utagged list followed by
// a tagged list. Strict DAV-compliance would FAIL such an If-header
// as a bad request.
// 2 - This code does not "correctly" apply tagged lists with multiple
// URIs. Strict DAV-compliance would require evaluating the If-header
// once for each URI as the method is processed, and ignore any URIs
// in the tagged list that never were "processed". We don't (can't)
// process our MOVE/COPY/DELETEs that way, but instead do a pre-checking
// pass on the If: header. At pre-check time, we treat the If-header
// as if the tagged lists are all AND-ed together.
// THIS MEANS that if a URI is listed, and it doesn't have a good
// matching (valid) list, we will FAIL the whole method with 412 Precondition Failed.
// 3 - This code does not handle ETags in the If-header.
//
//$LATER: When we are part of the locktoken header, check the m_fPathsSet.
//$LATER: We might be able to get our info quicker if paths are already set!
//
HRESULT
HrCheckIfHeader (IMethUtil * m_pmu, // to ease the transition later...
LPCWSTR pwszDefaultPath)
{
HRESULT hr = S_OK;
BOOL fOneMatch = FALSE;
FETCH_TOKEN_TYPE tokenNext = TOKEN_SAME_LIST;
LPCWSTR pwsz;
LPCWSTR pwszToken;
LPCWSTR pwszPath = pwszDefaultPath;
CStackBuffer<WCHAR,MAX_PATH> pwszTranslated;
BOOL fFirstURI;
WCHAR rgwchEtag[MAX_PATH];
// Quick check -- if the header doesn't exist, just process the method.
//
pwsz = m_pmu->LpwszGetRequestHeader (gc_szLockToken, TRUE);
if (!pwsz)
return S_OK;
IFITER iter(pwsz);
// Double nested loop
// First loop (outer loop) looks through all the "tagged lists"
// (tagged list = URI + set of lists of tokens)
// If the first list is untagged, use the default path (the request URI)
// for the untagged first set of lists.
// Second loop looks through all the token lists for a single URI.
//
// NOTE: This code does NOT perfectly implement the draft.
// The draft says that an untagged production (no initial URI)
// can't have any subsequent URIs. Frankly, that's much more complex to
// implement -- need to set another bool var and DISALLOW that one case.
// So I'm skipping it for now. --BeckyAn
//
fFirstURI = TRUE;
for (pwsz = iter.PszNextToken (TOKEN_URI); // start with the first URI
pwsz || fFirstURI;
pwsz = iter.PszNextToken (TOKEN_NEW_URI)) // skip to the next URI in the list
{
// If our search for the first URI came up blank, use
// the default path instead.
// NOTE: This can only happen if it's the first URI (fFirstURI is TRUE)
// (we explicitly check psz in the loop condition, and QUIT the loop
// if neither psz or fFirstURI are true).
//
if (!pwsz)
{
Assert (fFirstURI);
pwszPath = pwszDefaultPath;
}
else
{
// If we have a name (tag, uri), use it instead of the default name.
//
CStackBuffer<WCHAR,MAX_PATH> pwszNormalized;
SCODE sc;
UINT cch;
// NOTE: Our psz is still quoted with <>. Unescaping must ignore these chars.
//
Assert (L'<' == *pwsz);
// Get sufficient buffer for canonicalization
//
cch = static_cast<UINT>(wcslen(pwsz + 1));
if (NULL == pwszNormalized.resize(CbSizeWsz(cch)))
{
FsLockTrace ("HrCheckIfHeader() - Error while allocating memory 0x%08lX\n", E_OUTOFMEMORY);
return E_OUTOFMEMORY;
}
// Canonicalize the URL taking into account that it may be fully qualified.
// Does not mater what value we pass in cch - it is out parameter only.
//
sc = ScCanonicalizePrefixedURL (pwsz + 1,
pwszNormalized.get(),
&cch);
if (S_OK != sc)
{
// We gave sufficient space
//
Assert(S_FALSE != sc);
FsLockTrace ("HrCheckIfHeader() - ScCanonicalizePrefixedURL() failed 0x%08lX\n", sc);
return sc;
}
// We're in a loop, so try to use a static buffer first when
// converting this storage path.
//
cch = pwszTranslated.celems();
sc = m_pmu->ScStoragePathFromUrl (pwszNormalized.get(),
pwszTranslated.get(),
&cch);
if (S_FALSE == sc)
{
if (NULL == pwszTranslated.resize(cch))
return E_OUTOFMEMORY;
sc = m_pmu->ScStoragePathFromUrl (pwszNormalized.get(),
pwszTranslated.get(),
&cch);
}
if (FAILED (sc))
{
FsLockTrace ("HrCheckIfHeader -- failed to translate a URI to a path.\n");
return sc;
}
Assert ((S_OK == sc) || (W_DAV_SPANS_VIRTUAL_ROOTS == sc));
// Sniff the last character and remove any final quoting '>' here.
//
cch = static_cast<UINT>(wcslen(pwszTranslated.get()));
if (L'>' == pwszTranslated[cch - 1])
pwszTranslated[cch - 1] = L'\0';
// Hold onto the path.
//
pwszPath = pwszTranslated.get();
}
Assert (pwszPath);
// This is no longer our first time through the URI loop. Clear our flag.
//
fFirstURI = FALSE;
// Loop through all tokens, checking as we go.
//$REVIEW: Right now, PszNextToken can't give different returns
//$REVIEW: for "not found" versus "syntax error".
//$REVIEW: That means we'll can't really give different, distinct
//$REVEIW: codes for syntax problems -- any failure is mapped to 412 Precond Failed.
//
for (pwszToken = iter.PszNextToken (TOKEN_START_LIST) ;
pwszToken;
pwszToken = iter.PszNextToken (tokenNext) )
{
Assert (pwszToken);
// Check this one token for validity.
//$LATER: These checks could be folded into the HrValidTokenExpression
//$LATER: call. This will be important later, when we have
//$LATER: more different token types to work with.
//
if (L'<' == *pwszToken)
{
hr = HrValidTokenExpression (m_pmu, pwszToken, pwszPath, NULL);
}
else if (L'[' == *pwszToken)
{
FILETIME ft;
hr = S_OK;
// Manually fetch the Etag for this item, and compare it
// against the provided Etag. Set the error code the
// same way that HrValidTokenExpression does:
// If the Etag does NOT match, set the error code to
// E_DAV_INVALID_HEADER.
// Remember to skip the enclosing brackets ([]) when
// comparing the Etag strings.
//
if (!FGetLastModTime (NULL, pwszPath, &ft))
hr = E_DAV_INVALID_HEADER;
else if (!FETagFromFiletime (&ft, rgwchEtag, m_pmu->GetEcb()))
hr = E_DAV_INVALID_HEADER;
else
{
// Skip the square bracket -- this level of quoting
// is just for the if-header, not
//
pwszToken++;
// Since we do not do week ETAG checking, if the
// ETAG starts with "W/" skip those bits
//
if (L'W' == *pwszToken)
{
Assert (L'/' == *(pwszToken + 1));
pwszToken += 2;
}
// Our current Etags must be quoted.
//
Assert (L'\"' == pwszToken[0]);
// Compare these etags, INcluding the double-quotes,
// but EXcluding the square-brackets (those were added
// just for the IF: header.
//
if (wcsncmp (rgwchEtag, pwszToken, wcslen(rgwchEtag)))
hr = E_DAV_INVALID_HEADER;
}
}
else
hr = E_FAIL;
if ((S_OK == hr && !iter.FCurrentNot()) ||
(S_OK != hr && iter.FCurrentNot()))
{
// Either token matches, and this is NOT a "Not" expression,
// OR the token does NOT match, and this IS a "Not" expression.
// This one expression in the current list is true.
// Rember this match, and check the next token in the same list.
// If we don't find another token in the same list, we will
// drop out of the for-each-token loop with fOneMatch TRUE,
// and we will know that one whole list matched, so this URI
// has a valid list.
//
fOneMatch = TRUE;
tokenNext = TOKEN_SAME_LIST;
continue;
}
else
{
// Either the token was not valid in a non-"Not" expression,
// or the token was valid in a "Not" expression.
// This one expression in this list is NOT true.
// That makes this list NOT true -- skip the rest of this
// list and move on to the next list for this URI.
//
fOneMatch = FALSE;
tokenNext = TOKEN_NEW_LIST;
continue;
}
} // rof - tokens in this list
// Check if we parsed a whole list with matches.
//
if (fOneMatch)
{
// This whole list matched! Return OK.
//
hr = S_OK;
}
else
{
// This list did not match.
//
// NOTE: We are quitting here if any one URI is lacking
// a matching list. We are treating the URI-sets as if they
// are AND-ed together. This is not strictly DAV-compliant.
// NOTE: See the comments at the top of this function about
// true DAV-compliance and multi-URI Ifs.
//
hr = E_DAV_IF_HEADER_FAILURE;
// We've failed. Quit now.
//
break;
}
} // rof - URIs in this header
return hr;
}
HRESULT
HrCheckStateHeaders (IMethUtil * pmu,
LPCWSTR pwszPath,
BOOL fGetMeth)
{
return HrCheckIfHeader(pmu, pwszPath);
}
// ------------------------------------------------------------------------
// CParseLockTokenHeader::FOneToken
// Special test -- F if not EXACTLY ONE item in the header.
BOOL
CParseLockTokenHeader::FOneToken()
{
LPCWSTR pwsz;
LPCWSTR pwszToken;
BOOL fOnlyOne = FALSE;
// Quick check -- if the header doesn't exist, just process the method.
//
pwsz = m_pmu->LpwszGetRequestHeader (gc_szLockToken, TRUE);
if (!pwsz)
return FALSE;
IFITER iter(pwsz);
// If we have LESS than one token, return FALSE.
pwszToken = iter.PszNextToken(TOKEN_START_LIST);
if (!pwszToken)
goto ret;
// If we have MORE than one token in this list, return FALSE.
pwszToken = iter.PszNextToken(TOKEN_SAME_LIST);
if (pwszToken)
goto ret;
// If we have other lists for this uri, return FALSE.
pwszToken = iter.PszNextToken(TOKEN_NEW_LIST);
if (pwszToken)
goto ret;
fOnlyOne = TRUE;
ret:
// We have exactly one token.
return fOnlyOne;
}
// ------------------------------------------------------------------------
// CParseLockTokenHeader::SetPaths
// Feed the relevant paths to this lock token parser.
HRESULT
CParseLockTokenHeader::SetPaths (LPCWSTR pwszPath, LPCWSTR pwszDest)
{
HRESULT hr = S_OK;
// They better be passing in at least one path.
Assert(pwszPath);
Assert(!m_fPathsSet);
// Copy the provided paths locally.
//
m_pwszPath = WszDupWsz (pwszPath);
m_cwchPath = static_cast<UINT>(wcslen (m_pwszPath.get()));
if (pwszDest)
{
m_pwszDest = WszDupWsz (pwszDest);
m_cwchDest = static_cast<UINT>(wcslen (m_pwszDest.get()));
}
m_fPathsSet = TRUE;
return hr;
}
// ------------------------------------------------------------------------
// CParseLockTokenHeader::HrGetLockIdForPath
// Get the token string for a path WITH a certain kind of access.
//$LATER: Obey fPathLookup (should be true on depth-type ops, when we add dir-locks)
//$LATER: Do back-path-lookup to find the dir-lock that is locking us.
HRESULT
CParseLockTokenHeader::HrGetLockIdForPath (LPCWSTR pwszPath,
DWORD dwAccess,
LARGE_INTEGER * pliLockID,
BOOL fPathLookup) // defaulted to FALSE
{
HRESULT hr = E_DAV_LOCK_NOT_FOUND;
FETCH_TOKEN_TYPE tokenNext = TOKEN_SAME_LIST;
LPCWSTR pwsz;
LPCWSTR pwszToken;
// Assert that we're in the correct state to call this method.
//
Assert(m_fPathsSet);
// Init our out parameter.
//
Assert(pliLockID);
(*pliLockID).QuadPart = 0;
// The requested path must be a child of one of our set paths.
//
Assert (!_wcsnicmp (pwszPath, m_pwszPath.get(), m_cwchPath) ||
(m_pwszDest.get() &&
!_wcsnicmp (pwszPath, m_pwszDest.get(), m_cwchDest)));
// Quick check -- if the header doesn't exist, just process the method.
//
pwsz = m_pmu->LpwszGetRequestHeader (gc_szLockToken, TRUE);
if (!pwsz)
return hr;
IFITER iter(pwsz);
// If this is a tagged production, there will be a URI here
// (pszToken will be non-NULL). In that case, search for
// the URI that matches (translates to match) our pwszPath.
// If there is NO URI here, we're a non-tagged production, and
// all lists & tokens are applied to the root URI of the request.
//
pwszToken = iter.PszNextToken (TOKEN_URI);
if (pwszToken)
{
// Loop through the tokens, looking only at uris.
// When we find the one that matches our given path, break out.
// Then the iter will hold our place, and the next set of code
// will search through the lists for this uri....
//
for (; // already fetched first URI token above
pwszToken;
pwszToken = iter.PszNextToken (TOKEN_NEW_URI) )
{
CStackBuffer<WCHAR,MAX_PATH> pwszNormalized;
CStackBuffer<WCHAR,MAX_PATH> pwszTranslated;
SCODE sc;
UINT cch;
Assert (pwszToken);
// NOTE: Our psz is still quoted with <>. Unescaping must ignore these chars.
//
Assert (L'<' == *pwszToken);
// Get sufficient buffer for canonicalization
//
cch = static_cast<UINT>(wcslen(pwszToken + 1));
if (NULL == pwszNormalized.resize(CbSizeWsz(cch)))
{
FsLockTrace ("CParseLockTokenHeader::HrGetLockIdForPath() - Error while allocating memory 0x%08lX\n", E_OUTOFMEMORY);
return E_OUTOFMEMORY;
}
// Canonicalize the URL taking into account that it may be fully qualified.
// Does not mater what value we pass in cch - it is out parameter only.
//
sc = ScCanonicalizePrefixedURL (pwszToken + 1,
pwszNormalized.get(),
&cch);
if (S_OK != sc)
{
// We gave sufficient space
//
Assert(S_FALSE != sc);
FsLockTrace ("HrCheckIfHeader() - ScCanonicalizePrefixedURL() failed 0x%08lX\n", sc);
return sc;
}
// We're in a loop, so try to use a static buffer first when
// converting this storage path.
//
cch = pwszTranslated.celems();
sc = m_pmu->ScStoragePathFromUrl (pwszNormalized.get(),
pwszTranslated.get(),
&cch);
if (S_FALSE == sc)
{
if (NULL == pwszTranslated.resize(cch))
{
return E_OUTOFMEMORY;
}
sc = m_pmu->ScStoragePathFromUrl (pwszNormalized.get(),
pwszTranslated.get(),
&cch);
}
if (FAILED (sc))
{
FsLockTrace ("HrCheckIfHeader -- failed to translate a URI to a path.\n");
return sc;
}
Assert ((S_OK == sc) || (W_DAV_SPANS_VIRTUAL_ROOTS == sc));
// Remove any final quoting '>' here.
//
cch = static_cast<UINT>(wcslen (pwszTranslated.get()));
if (L'>' == pwszTranslated[cch - 1])
pwszTranslated[cch - 1] = L'\0';
if (!_wcsicmp (pwszPath, pwszTranslated.get()))
break;
}
// If we fall out of the loop with NO pszToken, then we didn't
// find ANY matching paths.... return an error.
//
if (!pwszToken)
{
hr = E_DAV_LOCK_NOT_FOUND;
goto ret;
}
}
else if (_wcsicmp (pwszPath, m_pwszPath.get()))
{
// There is NO URI st the start, so we're a non-tagged production,
// BUT the caller was looking for some path BESIDES the root URI's path
// (didn't match m_pwszPath in the above test!!!).
// FAIL and tell them that we can't find any locktokens for this path.
//
hr = E_DAV_LOCK_NOT_FOUND;
goto ret;
}
// Now, the IFITER should be positioned at the start of the list
// that applies to this path.
// Look for a token under this tag that matches.
//
// Loop through all tokens, checking as we go.
//$REVIEW: Right now, PszNextToken can't give different returns
//$REVIEW: for "not found" versus "syntax error".
//$REVIEW: That means we'll never give "bad request" for syntax problems....
//
for (pwszToken = iter.PszNextToken (TOKEN_START_LIST);
pwszToken;
pwszToken = iter.PszNextToken (tokenNext) )
{
LARGE_INTEGER liLockID;
Assert (pwszToken);
// Check this one token for validity.
//
if (L'<' == *pwszToken)
{
hr = HrValidTokenExpression (m_pmu,
pwszToken,
pwszPath,
&liLockID);
}
else
{
// This is not a locktoken -- ignore it for now.
//
// This list still could have our locktoken -- keep looking in
// this same list.
//
// NTRaid#244243 -- However, this list might NOT have our locktoken.
// Need to look at any list for this uri.
//
tokenNext = TOKEN_ANY_LIST;
continue;
}
// We only want this lock token if it IS valid, AND
// it's not from a "Not" expression, AND it comes from a
// valid list. So, if we hit an invalid token, QUIT searching
// this list. (Skip ahead to the next list.)
//
if (S_OK == hr && !iter.FCurrentNot())
{
// The token matches, AND it's not from a "Not" expression.
// This one's good. Send it back.
//
*pliLockID = liLockID;
hr = S_OK;
goto ret;
}
else if (S_OK != hr && iter.FCurrentNot())
{
// The token does NOT match, and this IS a "Not" expression.
// This list still could be true overall -- keep looking in
// this same list.
//
// NTRaid#244243 -- However, this list might NOT have our locktoken.
// Need to look at any list for this uri.
//
tokenNext = TOKEN_ANY_LIST;
continue;
}
else
{
// Either the token was not valid in a non-"Not" expression,
// or the token was valid in a "Not" expression.
// This expression in this list is NOT true.
// Since this is not a "good" list, don't look here
// for a matching token -- skip to the next list.
//
tokenNext = TOKEN_NEW_LIST;
continue;
}
}
// We didn't find a token for this item.
//
hr = E_DAV_LOCK_NOT_FOUND;
ret:
return hr;
}