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