/* * S T A T E T O K. C P P * * Sources implementation of DAV-Lock common definitions. * * Copyright 1986-1997 Microsoft Corporation, All Rights Reserved */ #include "_locks.h" // This is the character that will be part of the opaquelocktoken // for transaction tokens. // DEC_CONST WCHAR gc_wszTransactionOpaquePathPrefix[] = L"XN"; DEC_CONST UINT gc_cchTransactionOpaquePathPrefix = CchConstString(gc_wszTransactionOpaquePathPrefix); /* * This file contains the definitions used for parsing state token * relared headers. * * * 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 ">" * *$BIG NOTE * If headers are used for two things - once to check preconditions * of the operation and once to find out the lock contexts to be * used for the operation. The second part differs in store and fs * implementations - since in fs we look for the lock only if the * operation fails because of lock conflict. In our store implementation we have * lock contexts added to the login before we start the operation. * But if-header asks us to use only certain tokens with certain resources. * We fall short here in the store implementation. * * Precondition checking should behave exactly the same in the two impls. * * Notes on the match operator: * We use the caller defined match operator to determine whether the * resource (resource path) statisfies the condition. For non-tagged * production this condition is applied for (each of) the original * operand resources for the verb. We pass the recursive flag to * check for all sub-resources or not. In the case of tagged production, * it is little more complex - first for each tagged path the parser * determines whether it comes under the scope of the operation or * not. If it does come under the scope we call the operator to apply * the condition check. Here we do not want the match to be applied to * the child resources and the recursive flag is set to FALSE; * */ /* - CIfHeadParser - * This is used for syntax parse of the If: header as opposed to the * tokenization done by the IFITER. * * */ class CIfHeadParser { private: // The header string // LPCWSTR m_pwszHeader; // BOOL flag indicating if it is a tagged production or not // BOOL m_fTagged; // Bool flag to indicate child resource processing. // The flag is set differently for tag and non-tag // productions. However the meaning of the flag is // consistent - it is used to tell the matchop if // we want it to look the children of the given // resource. // BOOL m_fRecursive; SCODE ScValidateTagged(LPCWSTR pwszPath); SCODE ScValidateNonTagged(LPCWSTR rgpwszPaths[], DWORD cPaths, SCODE * pSC); // Takes an array of pointers to paths and an array of bool-flags. // Requires the size of the arrays (should be same). // SCODE ScValidateList(IN LPCWSTR *ppwszPathList, IN DWORD crPaths, OUT BOOL *pfMatch); SCODE ScMatch(LPCWSTR pwszPath); // Very private member shared by our methods // to keep track of current parse head. // LPCWSTR m_pwszParseHead; // String parser // IFITER m_iter; // Match operator given to us. // CStateMatchOp *m_popMatch; // NOT IMPLEMENTED // CIfHeadParser( const CIfHeadParser& ); CIfHeadParser& operator=( const CIfHeadParser& ); public: // Useful consts // enum { TAG_HEAD = L'<', TAG_TAIL = L'>', ETAG_HEAD = L'[', ETAG_TAIL = L']', LIST_HEAD = L'(', LIST_TAIL = L')' }; CIfHeadParser (LPCWSTR pwszHeader, CStateMatchOp *popMatch) : m_pwszHeader(pwszHeader), m_iter(pwszHeader), m_popMatch(popMatch) { Assert(pwszHeader); m_pwszParseHead = const_cast(pwszHeader); while (*m_pwszParseHead && iswspace(*m_pwszParseHead)) m_pwszParseHead++; // Checks if the header is a tagged or non-tagged production. // If we find a "Coded-URI" (a URIs inside angle brackets, ) // before the first list (before the first "(" char) // then we have a tagged production. // m_fTagged = (TAG_HEAD == *m_pwszParseHead); } ~CIfHeadParser() { } // Apply the if header production to the paths. // Path2 is optional. fRecursive says if the validation // is to be done to all children of the given path(s). // We may need to change the interface to support a list // of paths so that we can use it in Batch methods as well. // SCODE ScValidateIf(LPCWSTR rgpwszPaths[], DWORD cPaths, BOOL fRecursive = FALSE, SCODE * pSC = NULL); }; // -------------------------------------------------------------------------------- // ----------------------------- Free Helper Functions ---------------------------- // -------------------------------------------------------------------------------- /* - PwszSkipCodes - * * skip white spaces and the delimiters in a tagged string part * of an if-header. We expect the codes to be <> or []. * * *pdwLen must be zero or actual length of the input string. * when the call returns it will have the length of the token san * LWS and tags. * */ LPCWSTR PwszSkipCodes(IN LPCWSTR pwszTagged, IN OUT DWORD *pcchLen) { LPCWSTR pwszTokHead = pwszTagged; DWORD cchTokLen; Assert(pcchLen); // find the actual length, if not specified // if (! *pcchLen) *pcchLen = static_cast(wcslen(pwszTokHead)); cchTokLen = *pcchLen; // Calculate relevant token length skipping LWS in the // head and tail. // // Skip any LWS near the head // while((*pwszTokHead) && (iswspace(*pwszTokHead)) && (cchTokLen > 0)) { cchTokLen--; pwszTokHead++; } // Skip any LWS near the tail // while((cchTokLen > 0) && iswspace(pwszTokHead[cchTokLen-1])) { cchTokLen--; } // At least two characters are expected now // if (cchTokLen < 2) { *pcchLen = 0; DebugTrace("PszSkipCodes: Invalid token.\n"); return NULL; } // skip delimiters if they are present. // if (((*pwszTokHead == CIfHeadParser::TAG_HEAD) && (pwszTokHead[cchTokLen-1] == CIfHeadParser::TAG_TAIL)) || ((*pwszTokHead == CIfHeadParser::ETAG_HEAD) && (pwszTokHead[cchTokLen-1] == CIfHeadParser::ETAG_TAIL))) { pwszTokHead++; cchTokLen -= 2; } // LWS are legal within the tags as well. // Skip any LWS near the head // while((*pwszTokHead) && (iswspace(*pwszTokHead)) && (cchTokLen > 0)) { pwszTokHead++; cchTokLen--; } // Skip any LWS near the tail // while(iswspace(pwszTokHead[cchTokLen-1]) && (cchTokLen > 0)) { cchTokLen--; } if (cchTokLen > 0) { *pcchLen = cchTokLen; return pwszTokHead; } else { *pcchLen = 0; DebugTrace("PszSkipCodes Invalid token length.\n"); return NULL; } } // -------------------------------------------------------------------------------- // ----------------------------- CIfHeadParser Impl ------------------------------- // -------------------------------------------------------------------------------- /* - CIfHeadParser::ScValidateTagged - * * Apply the tagged production. * * * Simply put we do this: * * for each list within the list of list * we apply the list production * * we expect the parse string to be 1 * List as the resource is * already consumed by the caller. * */ //$REVIEW: How is this function any different from ScValidateNonTagged(pwsz, NULL)??? SCODE CIfHeadParser::ScValidateTagged(LPCWSTR pwszPath) { SCODE sc = E_DAV_IF_HEADER_FAILURE; LPCWSTR rpwszPath[1]; BOOL rfMatch[1]; BOOL fMatchAny = FALSE; Assert(m_fTagged); rpwszPath[0] = pwszPath; rfMatch[0] = FALSE; // Apply one list which is // LIST_HEAD 1 * ( [ not ] (statetoken | e-tag ) ) LIST_TAIL // while ( SUCCEEDED(sc = ScValidateList(rpwszPath, 1, rfMatch)) ) { if (TRUE == rfMatch[0]) fMatchAny = TRUE; } // Status cannot be succesfull there as that is condition // to exit the loop above // Assert(S_OK != sc); // Now if the status is special failing error // and we found the match then we need to return S_OK. // Otherwise we will go down and return whatever // error we are given. // if ((E_DAV_IF_HEADER_FAILURE == sc) && fMatchAny) { sc = S_OK; } return sc; } /* - CIfHeadParser::ScValidateNonTagged - * * Apply the non-tagged if header production. * * * Simply put we do this: * * for each list in the header * we apply the list production * * we expect the parse string to be 1 * List as the resource is * already consumed by the caller. * * Unlike the tagged production, a non tagged production is * applied to all the resources in the scope of the operation. * This is really complex and we shifted the complexity to * the ApplyList function below which supports two resources. * * If we succesfully finish the list, both the resources must * have atleast one successful (TRUE) list production for the * whole operation to succeed. * * If pSC is NULL we'll return success or failure based on whether * or not the if header passes or fails. * * If pSC is not NULL, it points to an array of SCODEs that * indicate whether or not the if header passed for each resource * in the list. Note that in this case we will return S_OK * as the return value even if one of the resources fails. We'll * only send back a failure if there was some other unexpected * error * */ SCODE CIfHeadParser::ScValidateNonTagged(LPCWSTR rgpwszPaths[], DWORD cPaths, SCODE * pSC) { CStackBuffer rgfMatches; // Flags indicating overall evaluation status for each path CStackBuffer rgfNextListMatch;// Flags used to return the results of validating next list SCODE sc = S_OK; DWORD iPath = 0; Assert(! m_fTagged); Assert(rgpwszPaths); Assert(cPaths); if ((NULL == rgfMatches.resize(sizeof(BOOL) * cPaths)) || (NULL == rgfNextListMatch.resize(sizeof(BOOL) * cPaths))) { sc = E_OUTOFMEMORY; goto ret; } // Init the match flag list: to default FALSE // for ( iPath = 0; iPath < cPaths; iPath++ ) { rgfMatches[iPath] = FALSE; if (pSC) { pSC[iPath] = E_DAV_IF_HEADER_FAILURE; } } // Apply one list which is // LIST_HEAD 1 * ( [ not ] (statetoken | e-tag ) ) LIST_TAIL // while ( SUCCEEDED(sc = ScValidateList(rgpwszPaths, cPaths, rgfNextListMatch.get())) ) { // For all the paths that evaluated to TRUE in this list, // update the result flag. // for ( iPath = 0; iPath < cPaths; iPath++ ) { // The result is interesting only for those whose current state is FALSE // because each of the lists of ids are OR'd together to decide whether // or not they passed. // if ( (FALSE == rgfMatches[iPath]) && (TRUE == rgfNextListMatch[iPath]) ) { // Note: you may be thinking why I don't break // here. With depth locks, same list/lock can // satisfy multiple resources. // rgfMatches[iPath] = TRUE; if (pSC) { // If we are asked for a resource by resource record // of matching resource, mark that we found a success // for the current resource. // pSC[iPath] = S_OK; } } } //$NOTE // Two levels of optimization on this evaluation may look feasible: // 1) Stop evaluating when we find all the paths are validated // 2) Do not validate a path if the path is already validated against a list // Both these optimizations will work for pre-condition evaluation, however // we use the state tokens to add lock content to the logon: so we still need // to parse the entire list to obtain all the applicable lock tokens.. // 3) Another possibility is to do only the lock-token matching in the above // scenario. There is no point in the etag/restag comparison if the path is // already validated: but lock token check is still required as we need to // collect all the lock tokens. This would require sharing the current global // results with the basic match function. I think I would do this some time later. //$NOTE // } // Check if that is any of special errors and reset the error code to S_OK // if that is the case. Otherwise fail out straight of. // if ((S_OK != sc) && (E_DAV_IF_HEADER_FAILURE != sc)) { goto ret; } else { sc = S_OK; } // if we were asked for a resource by resource account of matching resources // we have succeeded the request. Otherwise, if any resource failed, the // if header failed. // if (pSC) { sc = S_OK; goto ret; } for ( iPath = 0; iPath < cPaths; iPath++ ) { if (FALSE == rgfMatches[iPath]) { sc = E_DAV_IF_HEADER_FAILURE; break; } } ret: return sc; } /* - CIfHeadParser::ScValidateList - * * Apply the list production on the resources. * For non tagged resources we need to apply the * list to all operand resources. We iterate the * header once and achieve this. * * Return: FALSE on malformed input otherwise TRUE. * * we parse the list and apply the match operation on * all the resources. In order for a TRUE match result all * the list elements must succesfully "apply" to the * resource. If atleast one element did not apply * with a truth result, we stop applying elements to * that resource. * * We return on end of list or malformed list. * */ SCODE CIfHeadParser::ScValidateList(IN LPCWSTR *ppwszPathList, IN DWORD crPaths, OUT BOOL *pfMatch) { SCODE sc = S_OK; DWORD iIndex; // Do some input verification. // size of the list must be at least one. // Assert(crPaths>0); Assert(ppwszPathList[0]); Assert(pfMatch); #ifdef DBG { for (iIndex=0; iIndexFSetToken(m_pwszParseHead, fEtag)) { DebugTrace("CIfHeadParser::ScValidateList Invalid token\n"); // return immediately // sc = E_DAV_IF_HEADER_FAILURE; goto ret; } // Now we obtained one complete match condition- // for all the paths check if the condition is good. // we need to do this only if all previous matchs // succeeded for the given path. // i.e if a match failed, the list is anyway going to fail // for the particular path. // for (iIndex=0; iIndexScGetResourcePath(pwszUri, &pwszPath); if (FAILED(sc)) { // error code will be E_OUTOFMEMORY // if we get here // goto ret; } // Check if the tagged URI is within scope of // the method and apply the state match operation // only if it does. // for (iPath = 0; iPath < cPaths; iPath++) { // check path validity - depending on the operation depth. // If the operation is not deep, the paths must match // exactly. // if (FIsChildPath(rgpwszPaths[iPath], pwszPath, fRecursive)) { sc = ScValidateTagged(pwszPath); // If the caller wants a list of the scodes resource // by resource, set it into the array. Otherwise // we can stop verifying the resource because we've // already found a resource that fails the if statement. // Note that we return the failure in the scode array // only for pre-condition failures, other errors like // memory errors (or even redirect errors) fail the // whole request immediately. // if ((E_DAV_IF_HEADER_FAILURE == sc) && (pSC)) pSC[iPath] = sc; else if (FAILED(sc)) goto ret; // This path is done // break; } } } } else { sc = ScValidateNonTagged(rgpwszPaths, cPaths, pSC); goto ret; } sc = E_DAV_IF_HEADER_FAILURE; ret: return sc; } /* - CIfHeadParser::ScMatch - * Call the appropriate operator and return the value of the * expression. * * */ SCODE CIfHeadParser::ScMatch(LPCWSTR pwszPath) { SCODE sc = S_OK; BOOL fNot = m_iter.FCurrentNot(); Assert(m_popMatch); // determine the type of the token and call the // appropriate handler // switch(m_popMatch->GetTokenType()) { case CStateToken::TOKEN_LOCK: sc = m_popMatch->ScMatchLockToken(pwszPath, m_fRecursive); break; case CStateToken::TOKEN_RESTAG: sc = m_popMatch->ScMatchResTag(pwszPath); break; case CStateToken::TOKEN_ETAG: sc = m_popMatch->ScMatchETag(pwszPath, m_fRecursive); break; case CStateToken::TOKEN_TRANS: sc = m_popMatch->ScMatchTransactionToken(pwszPath); break; default: DebugTrace("CStateMatchOp::Unsupported token type\n"); sc = E_DAV_IF_HEADER_FAILURE; goto ret; } // Unless we applied the match operators above we // should not even reach here. // if (fNot) { if (E_DAV_IF_HEADER_FAILURE == sc) { sc = S_OK; } else if (S_OK == sc) { sc = E_DAV_IF_HEADER_FAILURE; } } ret: return sc; } // -------------------------------------------------------------------------------- // ----------------------------- CStateMatchOp Impl ------------------------------- // -------------------------------------------------------------------------------- /* - CStateMatchOp::ScParseIf - * * */ SCODE CStateMatchOp::ScParseIf(LPCWSTR pwszIfHeader, LPCWSTR rgpwszPaths[], DWORD cPaths, BOOL fRecur, SCODE * pSC) { SCODE sc = S_OK; CIfHeadParser ifParser(pwszIfHeader, this); sc = ifParser.ScValidateIf(rgpwszPaths, cPaths, fRecur, pSC); return sc; } // -------------------------------------------------------------------------------- // ----------------------------- CStateToken Impl ------------------------------- // -------------------------------------------------------------------------------- /* - CStateToken::FSetToken - * We expect pszToken to be an e-tag enclosed within [ ] or * a state token enclosed within < >. * */ BOOL CStateToken::FSetToken(LPCWSTR pwszToken, BOOL fEtag, DWORD dwLen) { LPCWSTR pwszTokHead = pwszToken; m_tType = TOKEN_NONE; // update the length and skip the tags // pwszTokHead = PwszSkipCodes(pwszToken, &dwLen); if ( (NULL == pwszTokHead) || (dwLen < 1) ) { return FALSE; } // add one for the null char. // dwLen++; // allocate buffer for the token. // we try to optimize allocations by using a heuristic // size value. Most of our tokens are of form // prefix-guid-smallstring // if ((NULL == m_pwszToken) || (dwLen > m_cchBuf)) { if (NULL != m_pwszToken) ExFree(m_pwszToken); if (dwLen > NORMAL_STATE_TOKEN_SIZE) { m_pwszToken = reinterpret_cast(ExAlloc(dwLen * sizeof(WCHAR))); m_cchBuf = dwLen; } else { m_pwszToken = reinterpret_cast(ExAlloc(NORMAL_STATE_TOKEN_SIZE * sizeof(WCHAR))); m_cchBuf = NORMAL_STATE_TOKEN_SIZE; } } if (NULL == m_pwszToken) { m_cchBuf = 0; return FALSE; } // Remember that dwLen contains size of the buffer (including // Null char). // Make our copy of the string // wcsncpy(m_pwszToken, pwszTokHead, (dwLen - 1)); // add the null character to terminate the string. // m_pwszToken[dwLen-1] = L'\0'; if (fEtag) { Assert(CIfHeadParser::ETAG_HEAD == *pwszToken); m_tType = CStateToken::TOKEN_ETAG; return TRUE; } // parse the token to find our the token type. // else if (0 == _wcsnicmp(pwszTokHead, gc_wszOpaquelocktokenPrefix, gc_cchOpaquelocktokenPrefix) ) { // Since token is a client input, let us be careful // with it. Make sure that the size is minimum expected, // which is opaquelocktoken:guid:. // Lock tokens, unfortunately, can be either transaction // or plain lock tokens. To find out if it is transaction // token, we will have to parse the token and reach the // extension part. For performance reasons I am going to // skip parsing and jump directly to the place where I // can get the information. This is not bad as we any way // correctly parse the token when we are looking for its // contents. // // gc_cchOpaquelocktokenPrefix includes the :, gc_cchMaxGuid // includes the null char (cch?). Hence the expression below. // if ( dwLen > (gc_cchOpaquelocktokenPrefix + gc_cchMaxGuid) ) { if (0 == _wcsnicmp(&pwszTokHead[gc_cchOpaquelocktokenPrefix + gc_cchMaxGuid], gc_wszTransactionOpaquePathPrefix, gc_cchTransactionOpaquePathPrefix) ) { m_tType = TOKEN_TRANS; return TRUE; } else { m_tType = TOKEN_LOCK; return TRUE; } } else { DebugTrace("CStateMatchOp::lock state token too small %ls\n", pwszTokHead); return FALSE; } } // Our restag-type URIs all start with 'r'. // (And no other URIs that start with 'r' are valid statetokens.) // else if (L'r' == *pwszTokHead) { m_tType = TOKEN_RESTAG; return TRUE; } else { DebugTrace("CStateMatchOp::Unsupported/unrecognized state token %ls\n", pwszTokHead); return FALSE; } } /* - CStateToken::FGetLockTokenInfo - * Parse the state token as if it is a lock token. Note that this * works for transaction tokens as well. * */ BOOL CStateToken::FGetLockTokenInfo(unsigned __int64 *pi64SeqNum, LPWSTR pwszGuid) { LPWSTR pwszToken = m_pwszToken; Assert(pwszGuid); if ((TOKEN_LOCK != m_tType) && (TOKEN_TRANS != m_tType)) { return FALSE; } // We assume that the token is validated when we reach here. // skip any LWS and the opaquetoken part. // while((*pwszToken) && iswspace(*pwszToken)) pwszToken++; // we check for opaque token when we set the token - so // just skip the portion // pwszToken += gc_cchOpaquelocktokenPrefix; // no check for validity of buf size. duh factor. // wcsncpy(pwszGuid, pwszToken, gc_cchMaxGuid - 1); // terminate the guid string. // pwszGuid[gc_cchMaxGuid - 1] = L'\0'; pwszToken = wcschr(pwszToken, L':'); if (NULL == pwszToken) { DebugTrace("CStateToken::FGetLockTokenInfo invalid lock token.\n"); return FALSE; } Assert(L':' == *pwszToken); // skip the ":" // pwszToken++; // Transaction tokens will have a T at the head of the extension // part of the token. // if (TOKEN_TRANS == m_tType) { Assert(gc_wszTransactionOpaquePathPrefix[0] == *pwszToken); pwszToken += gc_cchTransactionOpaquePathPrefix; } // the lock-id string follows // *pi64SeqNum = _wtoi64(pwszToken); //$TODO: // Is there a way to validate if atoi failed? // return TRUE; } /* - CStateToken::FIsEqual - * Nifty equality operator * * */ BOOL CStateToken::FIsEqual(CStateToken *pstokRhs) { if (pstokRhs->GetTokenType() != m_tType) return FALSE; LPCWSTR pwszLhs = m_pwszToken; LPCWSTR pwszRhs = pstokRhs->WszGetToken(); if (!pwszLhs || !pwszRhs) return FALSE; return (0 == _wcsicmp(pwszLhs, pwszRhs)); } /* - IFITER::PszNextToken - * * */ // ------------------------------------------------------------------------ // IFITER::PszNextToken // // Fetch the next token. // Can be restricted to the next token in this list (AND-ed set inside a // particular set of parens), the next token in a new list (new set of parens), // or the next token in the whole header-line. // LPCWSTR IFITER::PszNextToken (FETCH_TOKEN_TYPE type) { LPCWSTR pwsz; LPCWSTR pwszEnd; WCHAR wchEnd = L'\0'; // If no header existed, then there is nothing to do // if (NULL == m_pwch) return NULL; // Quick state-check. // If the current node is a "Not", then we MUST be in a list. // (Not is a qualifier on a token inside a list. Can't have a Not // outside of a list.) // Logically, m_fCurrentNot _implies_ m_state is STATE_LIST. // Assert (!m_fCurrentNot || STATE_LIST == m_state); // Clear our "Not" bit before starting our fetch of the next token. // If the token we return has a "Not" qualifier, set the flag correctly below. // m_fCurrentNot = FALSE; // Eat all the white space // while (*m_pwch && iswspace(*m_pwch)) m_pwch++; // Quit if there is nothing left to process // if (L'\0' == *m_pwch) return NULL; // If the last state was a LIST, we need to check for the close // of the list, and set our state back to NONE. // if (STATE_LIST == m_state) { // If the next char is a close paren, that's the end of this list. if (L')' == *m_pwch) { m_pwch++; m_state = STATE_NONE; // Eat all the white space // while (*m_pwch && iswspace(*m_pwch)) m_pwch++; // Quit if there is nothing left to process // if (L'\0' == *m_pwch) return NULL; // Update our state if we were asked for "any list item". // (Now we should find a list start.) // if (TOKEN_ANY_LIST == type) type = TOKEN_START_LIST; } } // If the caller asked for any list item, and we didn't change that // request because of our state above, change it here to specifically // search for the next item in the same list. // if (TOKEN_ANY_LIST == type) type = TOKEN_SAME_LIST; // // Process the request. // switch (type) { // This case is really dumb. I thought I might use // it for "counting" tokens. If it's not being used, remove it! // case TOKEN_NONE: { // If they're asking for a raw count (type == TOKEN_NONE), // give it to 'em..... // NOTE: This code is a little sloppy. It will count names // as state tokens. // m_pwch = wcschr (m_pwch, L'<'); if (!m_pwch) { return NULL; } wchEnd = L'>'; // Go copy the data. // break; } case TOKEN_NEW_URI: { // Grab a name, skipping all lists. // If there are no names left, give NULL. // Three places we could be -- NONE, NAME, LIST. // while (m_pwch && *m_pwch) { // If we're at a uri-delimiter now, AND // we're in the NONE state, just go fetch the token below... // if (L'<' == *m_pwch && STATE_NONE == m_state) { break; } #ifdef DBG // Debug-only check of our state. if (L'(' == *m_pwch) { Assert(STATE_NONE == m_state || STATE_NAME == m_state); } else if (L'<' == *m_pwch) { Assert(STATE_LIST == m_state); } #endif // DBG // Zip to the end of the current list. // m_pwch = wcschr (m_pwch + 1, L')'); if (!m_pwch) { return NULL; } m_pwch++; // Skip past the closing paren. // Eat all the white space // while (*m_pwch && iswspace(*m_pwch)) m_pwch++; // Quit if there is nothing left to process // if (L'\0' == *m_pwch) return NULL; m_state = STATE_NONE; } // Fallthrough to the next segment to check the token // and fetch our uri. } case TOKEN_URI: { // Grab a name, iff the next item is a name. // Otherwise, give NULL. // Quit if the next item is not a name. // if (L'<' != *m_pwch) return NULL; // Quit if we aren't in the correct state to look for a name. // (This could happen if we already have a name, or if we are // already INSIDE a list....) // if (STATE_NONE != m_state) return NULL; // Set our state and fallthru to fetch the data. // m_state = STATE_NAME; wchEnd = L'>'; // Go copy the data. // break; } case TOKEN_NEW_LIST: { // Fast-forward to the next new list and fetch the first item. // If we're still inside a list, must skip the rest of this list. // If there are no more new lists for this URI, return NULL. if (STATE_LIST == m_state) { // We're inside a list. Get out by seeking to the next // list-end-char (right paren). // m_pwch = wcschr (m_pwch, L')'); if (!m_pwch) return NULL; m_state = STATE_NONE; m_pwch++; // Skip past the closing paren. // Eat all the white space // while (*m_pwch && iswspace(*m_pwch)) m_pwch++; // Quit if there is nothing left to process // if (L'\0' == *m_pwch) return NULL; } Assert(m_pwch); Assert(*m_pwch); // And fallthrough here to the TOKEN_START_LIST case. // It will verify & skip past the list start char // and fetch out the next token. // } case TOKEN_START_LIST: { // Grab a list item, iff the next item is a NEW list item. // Otherwise, return NULL. // Quit if the next item is not a list. // if (L'(' != *m_pwch) return NULL; // Quit if we aren't in the correct state to look for a name. // (This could happen if we are already INSIDE a list....) // if (STATE_LIST == m_state) return NULL; // Fetch the token. // m_state = STATE_LIST; m_pwch++; // Skip the open paren. // Eat all the white space // while (*m_pwch && iswspace(*m_pwch)) m_pwch++; // Quit if there is nothing left to process // if (L'\0' == *m_pwch) return NULL; // Fallthrough to the TOKEN_SAME_LIST processing // to actually fetch the token. // } case TOKEN_SAME_LIST: { // Grab the next list item. // If the next item is not a list item, return NULL. // Quit if we aren't in the correct state to look for a name. // (This could happen if we are NOT inside a list....) // if (STATE_LIST != m_state) return NULL; // Check for the magic "Not" qualifier. // if (!_wcsnicmp (gc_wszNot, m_pwch, 3)) { // Remember the data and skip these chars. // m_fCurrentNot = TRUE; m_pwch += 3; // Eat all the white space // while (*m_pwch && iswspace(*m_pwch)) m_pwch++; // Quit if there is nothing left to process // if (L'\0' == *m_pwch) return NULL; } // Quit if the next item is not a token. // if (L'<' != *m_pwch && L'[' != *m_pwch) { return NULL; } // Fetch the token. // // Next token must start with either < for statetokens, or [ for etags. // if (L'<' == *m_pwch) { wchEnd = L'>'; } else if (L'[' == *m_pwch) { wchEnd = L']'; } else { DebugTrace("HrCheckIfHeaders -- Found list start, but no tokens!\n"); return NULL; } // Go copy the data. // break; } default: { DebugTrace("HrCheckIfHeaders -- Unrecognized request: 0x%0x", type); return NULL; } } // We should have set these items above. They are needed to // snip off the current token string (below). // Assert (m_pwch); Assert (*m_pwch); Assert (wchEnd); // Quick state-check. // If the current node is a "Not", then we MUST be in a list. // (Not is a qualifier on a token inside a list. Can't have a Not // outside of a list.) // Logically, m_fCurrentNot _implies_ m_state is STATE_LIST. // Assert (!m_fCurrentNot || STATE_LIST == m_state); // Find the end of this data item. // // Keep a pointer to the start, and seek for the end. //$REVIEW: Do we need to be super-careful here? //$REVIEW: This strchr *could* jump past stuff, but only in MALFORMED data. // pwsz = m_pwch; m_pwch = wcschr (pwsz + 1, wchEnd); if (!m_pwch) { // No end-of-token-char found for this token! // DebugTrace("HrCheckIfHeader -- No end char (%lc) found for token %ls", wchEnd, pwsz); return NULL; } // Save off the end pointer, then advance past the end char. // (m_pch now points to the start for the NEXT token.) // pwszEnd = m_pwch++; // Copy the data. // // The two pointers better be set before we try to copy the data. Assert (pwsz); Assert (pwszEnd); // The difference between, the two pointers gives us // the size of the current entry. // m_buf.AppendAt (0, static_cast(pwszEnd - pwsz + 1) * sizeof(WCHAR), pwsz); m_buf.Append (sizeof(WCHAR), L""); // NULL-terminate it! // Return the string // return m_buf.PContents(); }